Skip to content

Cherry-pick from develop to release/1.0.1#2142

Open
Andrey Bragin (EugeneTheDev) wants to merge 28 commits into
release/1.0.1from
eugenethedev/cherry-picks-to-1.0.1
Open

Cherry-pick from develop to release/1.0.1#2142
Andrey Bragin (EugeneTheDev) wants to merge 28 commits into
release/1.0.1from
eugenethedev/cherry-picks-to-1.0.1

Conversation

@EugeneTheDev

Copy link
Copy Markdown
Collaborator

No description provided.

Antonii (antoniibelyshev) and others added 25 commits June 15, 2026 16:37
<!--
Thank you for opening a pull request!

Please add a brief description of the proposed change here.
Also, please tick the appropriate points in the checklist below.
-->
feat(CliAIAgent):
introduce `CliAIAgent` which calls another cli agent under the hood:
e.g. claude, codex, etc.
`CliAIAgent` is an instance of `AIAgent`, and can also be used as a node
in a graph, e.g. `val claudeNode by ClaudeCodeAgent(...).asNode()`

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

---

#### Type of the changes
- [x] New feature (non-breaking change which adds functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Tests improvement
- [ ] Refactoring
- [ ] CI/CD changes
- [ ] Dependencies update

#### Checklist
- [x] The pull request has a description of the proposed change
- [x] I read the [Contributing
Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md)
before opening the pull request
- [x] The pull request uses **`develop`** as the base branch
- [x] Tests for the changes have been added
- [x] All new and existing tests passed

##### Additional steps for pull requests adding a new feature
- [ ] An issue describing the proposed change exists
- [ ] The pull request includes a link to the issue
- [ ] The change was discussed and approved in the issue
- [ ] Docs have been added / updated

(cherry picked from commit 0a70557)
Most of Android devices are on API versions below 35.

closes
#2059

(cherry picked from commit c31b172)
<!--
PR title should follow Conventional Commits format:
  type(scope): description

For breaking changes, append ! after type/scope:
  type(scope)!: description

Examples:
  feat(agents): add streaming response node
  fix(prompt): handle null responses in PromptExecutor
  refactor(agents)!: remove deprecated methods from Tool

See CONTRIBUTING.md for more details
-->

Add `Gemini3_1Pro_Preview`, `Gemini3_1FlashLite_Preview`,
`Gemini3_1FlashLite`, and `Gemini3_5Flash` profiles.

<!-- Include DEPRECATED section below only if this PR deprecates any
public APIs. Otherwise, delete it. -->
DEPRECATED:
* `Gemini 3 Pro Preview` profile is no longer supported.
`Gemini3_1Pro_Preview` is recommended instead.

(cherry picked from commit 2689548)
…f nullable collections (#1943)

## Summary

- Nullable `List<X>?` fields produced `{"type": ["array", "null"],
"items": {...}}`, which OpenAI's
GPT-5-family strict-mode validator rejects (surfaces as `'Invalid schema
for response_format ...
got "type": "None"'`). Switch
`OpenAIStandardJsonSchemaGenerator.processList` to emit
`{"anyOf": [{"type": "array", "items": ...}, {"type": "null"}]}` for
nullable lists, mirroring
the shape already used for nullable objects.
- Non-nullable lists, nullable primitives, and the LLM-agnostic
`StandardJsonSchemaGenerator` are
unchanged. Only the OpenAI-specific generator is touched, so other
providers keep their existing
type-union encoding.
- Adds a regression test
(`testGenerateOpenAIStandardJsonSchemaForNullableLists`) covering both
    primitive (`List<String>?`) and `$ref` (`List<Tag>?`) item types.

## Out of scope (could follow up)

- `OpenAIBasicJsonSchemaGenerator` still emits the OpenAPI-3.0-style
`nullable: true` keyword, which
OpenAI strict mode silently ignores. Same root cause family, different
generator — happy to file
a follow-up if useful.
closes #1930

(cherry picked from commit 54b8ecb)
…2085)

<!--
PR title should follow Conventional Commits format:
  type(scope): description

For breaking changes, append ! after type/scope:
  type(scope)!: description

Examples:
  feat(agents): add streaming response node
  fix(prompt): handle null responses in PromptExecutor
  refactor(agents)!: remove deprecated methods from Tool

See CONTRIBUTING.md for more details
-->

Add round-trip tests for redesigned message parts.

(cherry picked from commit 75f1351)
…2091)

Add koog-agents-additions dependency to Readme and Quickstart
documentation.

closes #2086 and #2087

(cherry picked from commit 384473a)
ci(agents): add public API compatibility checks via ABI validation
Enable Kotlin's built-in ABI validation to catch breaking changes to the
public API
before they reach the main branch. The check runs as part of the CI
compilation job
and blocks merge if any public API has changed without updating the
committed dumps.
### What's included
- **Convention plugins**: Enabled `abiValidation` in both
`ai.kotlin.multiplatform.gradle.kts`
 and `ai.kotlin.jvm.gradle.kts`.
- **Beta module exclusion**: Modules with `isBeta = true` have all ABI
tasks disabled automatically.
- **CI integration**: Added `./gradlew checkLegacyAbi` step to the
`compilation` job
in `.github/workflows/checks.yml`.
- **Contribution guide**: Documented the public API compatibility
workflow in `CONTRIBUTING.md`,
including how to update dumps when making intentional API changes.
### Developer workflow
When changing a public API in a stable module:
1. Make code changes
2. Run `./gradlew updateLegacyAbi`
3. Review the diff in `api/` directories
4. Commit the updated `.api` files alongside code changes

(cherry picked from commit 7cee8b8)
…ay (#2090)

Fix for #2089 added check guard for Bedrcok Nova empty system prompt
array

(cherry picked from commit 4f3a14b)
Background: #1916
(cherry picked from commit 0cea34f)
Adds support for Spring `WebClient` as a `KoogHttpClient` implementation
and removes `@Experimental` annotations from stable HTTP client
APIs/modules.
Adds documentation for http clients, factories and automatic resolution
on JVM.

closes KG-820

(cherry picked from commit 39c84a2)
<!--
PR title should follow Conventional Commits format:
  type(scope): description

For breaking changes, append ! after type/scope:
  type(scope)!: description

Examples:
  feat(agents): add streaming response node
  fix(prompt): handle null responses in PromptExecutor
  refactor(agents)!: remove deprecated methods from Tool

See CONTRIBUTING.md for more details
-->
Replace a deprecated Google model with an up-to-date version.
Add DeepSeek integration tests, ACP message conversion tests, Metadata
retrieval tests, and nullable tool schema support.
Fix Ollama tests run.

(cherry picked from commit e439121)
Refactored AgentCore long-term memory integration tests and
added auto-discovery strategies test

(cherry picked from commit 13cd127)
)

Closes #2002.

### Summary

`StreamFrameFlowBuilder.emitToolCallDelta()` treated every chunk with a
non-null tool-call `id` as a new tool call. This fix treats a repeated
`id` as a continuation of the pending tool call, so OpenAI-compatible
providers that repeat the same `id` on each streaming chunk no longer
fragment one logical tool call into multiple premature
`ToolCallComplete` frames.

### Fix

A new tool call now begins only when a present `id` or `index` differs
from the pending call, instead of whenever `id` is non-null.

Before:

```kotlin
val new: PendingToolCall = if (sanitizedId != null || index != previous?.index) {
```

After:

```kotlin
val isNewToolCall =
    (sanitizedId != null && sanitizedId != previous?.id) ||
        (index != null && index != previous?.index)
val new: PendingToolCall = if (isNewToolCall) {
```

This follows the same id-change principle as `emitReasoningDelta`;
absent or blank ids still continue the pending call (the blank case
preserves the #1915 handling). The fix lives in the shared
`StreamFrameFlowBuilder` because all streaming clients (OpenAI,
OpenRouter, Mistral, Anthropic, DeepSeek, Ollama, Bedrock, Dashscope,
Google) emit tool-call deltas through it.

### Tests

Two regression tests in `StreamFrameFlowBuilderTest`:
`testEmitToolCallDeltaWithRepeatedIdAppendsToExisting` (three chunks
with the same `id` combine into one `ToolCallComplete`) and
`testEmitToolCallDeltaWithRepeatedIdSeparatesDistinctToolCalls`
(switching to a different `id` still starts a separate tool call).
`:prompt:prompt-model:jvmTest` (20 tests, 0 failures), `wasmJsNodeTest`,
and `ktlintCheck` all pass locally; the full `./gradlew build` (Android
and browser targets) is left to CI, since this contributor environment
lacks the Android SDK and a headless browser.

---------

Co-authored-by: Anastasiia.Zarechneva <Anastasiia.Zarechneva@jetbrains.com>
(cherry picked from commit 581d6af)
…2099)

<!--
PR title should follow Conventional Commits format:
  type(scope): description

For breaking changes, append ! after type/scope:
  type(scope)!: description

Examples:
  feat(agents): add streaming response node
  fix(prompt): handle null responses in PromptExecutor
  refactor(agents)!: remove deprecated methods from Tool

See CONTRIBUTING.md for more details
-->

Drop .lowercase() call in AnthropicLLMClient enum serialization.

<!-- Include references to related issues below, e.g., closes #1, closes
KG-1. Otherwise, delete it. -->
closes #2020

(cherry picked from commit c779c04)
)

Introduces model resolution as a preliminary step before prompt
execution—allowing decorators (such as `ContextualPromptExecutor`) to be
certain which `LLModel` will be used for the given operation.
Currently, dynamic model resolution happens internally (ie via fallback
in `MultiLLMPromptExecutor`) which might result in misleading model
information (ie `ContextualPromptExecutor` triggers `onLLMCallCompleted`
with `requestModel` instead of `effectiveModel`).

Adds `resolveModel` and execution paths accepting `ResolvedModel` to
`PromptExecutorAPI`. All of these have default implementations that make
existing executors work in the same way as before.

Adds `DynamicPromptExecutor`: a `PromptExecutor` subclass that exposes
model resolution as an explicit step (`resolveModel`) before LLM
dispatch. Subclasses translate a requested LLModel into a ResolvedModel
(e.g. fallback selection); the LLModel entry points are finalized to
flow through resolveModel exactly once per call.

`MultiLLMPromptExecutor` and `RoutingLLMPromptExecutor` move fallback
logic into `resolveModel`.

PromptExecutor stays open — existing direct subclasses are unaffected.

(cherry picked from commit 6befd8b)
`FinishTool` (the internal tool that `subgraphWithTask<Input, Output>`
uses to carry the structured result) generates its schema via
`kotlinx-schema-generator-json`.

For a sealed `Output` two things stacked:

- `JsonSchema.toActualPropertyDefinition` only dispatched on `type` and
dropped the `oneOf` that the generator emits for polymorphic top-level
schemas
- FinishTool requested `includePolymorphicDiscriminator = false`, so
even the per-branch schemas omitted the `type` const that
kotlinx-serialization needs to decode back into a sealed instance.

The combined effect: `subgraphWithTask<String, MySealedInterface>`
advertised an empty `result` Object to the LLM and could not decode
whatever it returned.

The converter now prefers `oneOf`/`anyOf` when present, and FinishTool
generates its schema with the discriminator enabled. Other tools keep
`defaultJsonSchemaConfig` unchanged.

OpenAI and Google emit the resulting `anyOf` directly, and Anthropic's
pre-existing "AnyOf type is not supported" limitation continues to apply
there.

New tests pin the converter at the schema layer, the FinishTool
descriptor and round-trip, and the end-to-end behavior through
`subgraphWithTask` with a raw `PromptExecutor` returning the polymorphic
JSON.

closes #1941

(cherry picked from commit 482a434)
…erator (#1875)

`executeStructured` crashed with `IllegalArgumentException: Encountered
unsupported type while generating JSON schema: CONTEXTUAL` when the
response data class contained a `@Contextual` property (e.g.
`java.util.UUID`, any non-`@Serializable` type registered via
`SerializersModule`).

`GenericJsonSchemaGenerator.process()` had no branch for
`SerialKind.CONTEXTUAL`, so it fell through to the `else` clause and
threw immediately — before any LLM request was made.

Added an explicit `SerialKind.CONTEXTUAL` branch that:
1. Calls `serializersModule.getContextualDescriptor(descriptor)` (stable
API in kotlinx.serialization 1.7+) to resolve the actual serializer
registered in the `Json` instance's module.
2. Delegates to `process()` recursively with the resolved descriptor, so
the type is generated correctly (e.g. a UUID serialized as a string →
`"type": "string"`).
3. If no contextual serializer is found, throws a **descriptive**
`IllegalArgumentException` explaining how to fix it (register via
`SerializersModule { contextual(...) }` or use `@Serializable(with =
...)`).

| File | Change |
|---|---|
| `GenericJsonSchemaGenerator.kt` | Add `SerialKind.CONTEXTUAL` branch;
`@OptIn(ExperimentalSerializationApi::class)` on class |
| `JsonSchemaGeneratorTest.kt` | 3 new test cases: basic generator,
standard generator, and missing-serializer error path |

```
./gradlew :prompt:prompt-structure:jvmTest --tests "ai.koog.prompt.structure.json.generator.JsonSchemaGeneratorTest"
BUILD SUCCESSFUL
```

All 3 new tests pass alongside the existing suite.

Use `@Serializable(with = MySerializer::class)` on the property instead
of `@Contextual`, as noted in the issue.

closes #1486

(cherry picked from commit 6ee3b63)
)

Added support for DataStore with Okio on the web target. Consequently,
MemoryAppSettings has been replaced with a DataStore-based
implementation.

(cherry picked from commit 45e3730)
…tible requests (#2096)

## What

`MessagePart.Tool.Call.args` already holds the **JSON-encoded**
arguments string (the parse side builds it via the `JsonObject`
constructor, and `function.arguments` is read back with
`Json.parseToJsonElement(...)`). When rebuilding request history it was
passed through `Json.encodeToString()` **again**, producing a
**double-encoded** (quoted) JSON string on the wire.

OpenAI tolerates this, but strict OpenAI-compatible backends — notably
**DashScope / Qwen** — reject the request:

> `<400> InternalError.Algo.InvalidParameter: The "function.arguments"
parameter of the code model must be in JSON format.`

This breaks every tool-calling **agent** loop on DashScope at the
follow-up `nodeLLMSendToolResults` call (the first tool call runs;
replaying the result 400s).

## Fix

Emit `args` verbatim in both serialization paths:

- `AbstractOpenAILLMClient.createMessages` — Chat Completions
- `OpenAILLMClient.convertPromptToResponsesMessages` — Responses API

### On the wire
```jsonc
// before (double-encoded) — decodes to the STRING "{\"a\":3,\"b\":4}"
"arguments":"\"{\\\"a\\\":3,\\\"b\\\":4}\""
// after — decodes to the OBJECT {"a":3,"b":4}
"arguments":"{\"a\":3,\"b\":4}"
```

## Tests

Adds `testToolCallArgumentsAreNotDoubleEncodedInRequest` to
`OpenAIChatCompletionLLMClientTest`: it replays an assistant tool call
through the client, captures the outgoing request body, and asserts
`function.arguments` decode to a `JsonObject` (not a double-encoded
string). The pre-existing round-trip tests only asserted the tool-call
**name**, never the **arguments**, which is why this regressed
unnoticed.

Also verified end-to-end against the live DashScope `qwen-plus`
endpoint: the single-encoded form returns `200`, the double-encoded form
returns the `400` above.

> Reproduced against the **China-mainland** endpoint — base URL
`https://dashscope.aliyuncs.com/` with the compatible-mode path
`compatible-mode/v1/chat/completions` (note `DashscopeClientSettings`
defaults to the international host
`https://dashscope-intl.aliyuncs.com/`). The defect is in shared
serialization, so it is endpoint-independent, but the captured 400 above
came from the mainland path.

closes #2095

(cherry picked from commit 485e294)
<!--
PR title should follow Conventional Commits format:
  type(scope): description

For breaking changes, append ! after type/scope:
  type(scope)!: description

Examples:
  feat(agents): add streaming response node
  fix(prompt): handle null responses in PromptExecutor
  refactor(agents)!: remove deprecated methods from Tool

See CONTRIBUTING.md for more details
-->

Widens `Message.Tool.Result.parts` from `List<ContentPart.Text>` to
`List<ContentPart>`, allowing tools to return images and files alongside
text. Anthropic, OpenAI and Gemini clients handle the new content types;
unsupported API paths throw a clear error.

Closes #1491

(cherry picked from commit 6a35c2d)
…serve generics (#2116)

`JacksonSerializer.decodeFromJSONElement` was passing
`javaType.rawClass` to `objectMapper.treeToValue`, discarding generic
type parameters. This causes parameterized types (e.g. `List<Person>`)
to deserialize elements as `Map` instead of the expected type.

The fix passes the full `JavaType` to `treeToValue`, consistent with how
`decodeFromString` already works. This also removes the now-unnecessary
`@Suppress("UNCHECKED_CAST")` and explicit `as T` cast.

closes #2115

(cherry picked from commit 58a8168)
<!--
PR title should follow Conventional Commits format:
  type(scope): description

For breaking changes, append ! after type/scope:
  type(scope)!: description

Examples:
  feat(agents): add streaming response node
  fix(prompt): handle null responses in PromptExecutor
  refactor(agents)!: remove deprecated methods from Tool

See CONTRIBUTING.md for more details
-->

Add support for the newly released Anthropic Claude Fable 5 model.

(cherry picked from commit e4e9e40)
Rename MessageTokenizer storage key:
"agents-features-tracing"->"agents-features-message-tokenizer"

MessageTokenizer AIAgentStorageKey named "agents-features-tracing",
causing a runtime exception when Tracing feature is also installed (they
have the same name):
```
ai.koog.agents.core.agent.GraphAIAgent - Execution exception reported by server!
java.lang.IllegalArgumentException: Feature ai.koog.agents.core.agent.entity.AIAgentStorageKey@35e21111(name=agents-features-tracing) is found, but it is not of the expected type.
Expected type: MessageTokenizer
Actual type: Tracing
        at ai.koog.agents.core.feature.pipeline.AIAgentPipelineImpl.feature(AIAgentPipelineImpl.kt:104)
```

Closes #2143

(cherry picked from commit d97a6e4)
Strategy input on `StrategyStartingContext`:
- Added `input: Any?` field to `StrategyStartingContext` (mirrors the
`result: Any?` field already on `StrategyCompletedContext`).
- Use case: sometimes it is useful to hook into the agent's execution
and perform an action that depends on the user's query at runtime, e.g.
retrieve relevant memories from a dedicated collection and inject them
into the prompt before the strategy starts. Without the input on the
starting event there was no way for a feature to see what the agent had
been invoked with.

(cherry picked from commit a720371)
… support (#1889)

Small public API addition to support the agent optimization
infrastructure built on top of koog.

**`freshHistory` parameter for subgraphs:** Added `freshHistory: Boolean
= false` to `AIAgentSubgraph`, all `subgraph()` DSL overloads, all
`subgraphWithTask()` overloads, and `setupSubgraphWithTask()`

When `true`, the subgraph starts with an empty conversation history
instead of inheriting the parent context's prompt. The subgraph's
history is discarded upon completion. Use case: optimizable subgraphs
where each module should reason independently, and where few-shot
demonstrations should be the only conversation history.

---------

Co-authored-by: Denis Lochmelis <denis.lochmelis@jetbrains.com>
(cherry picked from commit a457663)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.