Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAVIA_PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This fork carries provider changes for Tardigrade. Consumers use upstream `effec
| Completion evidence | OpenAI compatible | Reject a final sentinel without a provider finish reason. |
| Reasoning replay | OpenAI compatible | Preserve the observed reasoning field and combine assistant reasoning, text, and calls during replay. |
| Shared metadata types | OpenAI compatible | Align approval metadata with the OpenAI provider declarations. |
| Function call signatures | OpenAI, OpenAI compatible | Replay `encrypted_content` from function call items with reasoning items in received order when `store` is false. The compatible provider declares the type. |

## Response format wrapper

Expand Down Expand Up @@ -144,3 +145,9 @@ Thinking configuration accepts `display` (`"summarized"` or `"omitted"`) for ada
Responses report `usage.output_tokens_details.thinking_tokens`. The adapter maps it to `outputTokens.reasoning` for generated and streamed responses. A later `message_delta` without the detail keeps the earlier count. The fields are added to the generated schemas and recorded as a spec patch in `codegen.yml`.

`packages/ai/anthropic/test/AnthropicLanguageModel.test.ts` checks configuration decoding, the request body for both thinking types, and reasoning usage with and without the detail.

## Function call signatures

Some providers that serve reasoning models over the Responses protocol return opaque reasoning state as `encrypted_content` on `function_call` items. A stream can send it only on `response.output_item.done`, after `response.function_call_arguments.done`. Streamed function calls are therefore emitted on item completion, with the completed item's `encrypted_content` in `metadata.openai.encryptedContent`. A call whose arguments completed without an item completion is emitted at the terminal response event, before the finish part, in output order. Replay sends the value back on the `function_call` input item, after the preceding reasoning items.

`packages/ai/openai/test/OpenAiLanguageModel.test.ts` covers generated responses and unchanged OpenAI requests. `packages/ai/clavia/test/ProviderStreams.test.ts` covers a signature only on the completed item, interleaved reasoning replay, and calls without item completion.
171 changes: 171 additions & 0 deletions packages/ai/clavia/test/ProviderStreams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,177 @@ for (const store of [false, true]) {
}))
}

it.effect("replays streamed OpenAI function call encrypted content in received order", () =>
Effect.gen(function*() {
const reasoning = (id: string, encrypted_content: string) => ({
type: "reasoning",
id,
encrypted_content,
summary: [{ type: "summary_text", text: "Check" }]
})
const call = (id: string, encrypted_content?: string) => ({
type: "function_call",
id: `fc_${id}`,
call_id: id,
name: "read",
arguments: JSON.stringify({ path: id }),
...(encrypted_content === undefined ? {} : { encrypted_content })
})
const output = [
reasoning("rs_a", "opaque-a"),
call("a", "signed-a"),
reasoning("rs_b", "opaque-b"),
call("b", "signed-b")
]
const events = [
{ type: "response.output_item.added", output_index: 0, item: output[0] },
{
type: "response.reasoning_summary_text.delta",
item_id: "rs_a",
output_index: 0,
summary_index: 0,
delta: "Check"
},
{ type: "response.output_item.done", output_index: 0, item: output[0] },
// Signature on both the added and the completed item.
{ type: "response.output_item.added", output_index: 1, item: output[1] },
{
type: "response.function_call_arguments.done",
output_index: 1,
item_id: "fc_a",
arguments: "{\"path\":\"a\"}"
},
{ type: "response.output_item.done", output_index: 1, item: output[1] },
{ type: "response.output_item.added", output_index: 2, item: output[2] },
{
type: "response.reasoning_summary_text.delta",
item_id: "rs_b",
output_index: 2,
summary_index: 0,
delta: "Check"
},
{ type: "response.output_item.done", output_index: 2, item: output[2] },
// Signature only on the completed item, after argument completion.
{ type: "response.output_item.added", output_index: 3, item: call("b") },
{
type: "response.function_call_arguments.done",
output_index: 3,
item_id: "fc_b",
arguments: "{\"path\":\"b\"}"
},
{ type: "response.output_item.done", output_index: 3, item: output[3] },
{
type: "response.completed",
response: {
id: "response",
created_at: 1,
model: "fixture",
status: "completed",
output,
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 }
}
}
]
const { stream, requests } = fixture("openai", "valid", false, "native", {
events,
config: { store: false, include: ["reasoning.encrypted_content"] }
})
const parts = yield* Stream.runCollect(stream())
assert.deepStrictEqual(
parts.flatMap((part) => part.type === "tool-call" ? [part.metadata.openai?.encryptedContent] : []),
["signed-a", "signed-b"]
)
const restored = Schema.decodeUnknownSync(Prompt.Prompt)(
JSON.parse(JSON.stringify(Prompt.fromResponseParts(parts)))
)
const results = Prompt.make([{
role: "tool",
content: ["a", "b"].map((id) => ({ type: "tool-result", id, name: "read", result: "result", isFailure: false }))
}])
yield* Stream.runDrain(stream(Prompt.concat(restored, results)))
const replay = Schema.decodeUnknownSync(Schema.Struct({ input: Schema.Array(Schema.Unknown) }))(requests[1]).input
assert.deepStrictEqual(replay.slice(0, 4), [
{
type: "reasoning",
id: "rs_a",
summary: [{ type: "summary_text", text: "Check" }],
encrypted_content: "opaque-a"
},
{
type: "function_call",
name: "read",
call_id: "a",
arguments: "{\"path\":\"a\"}",
id: "fc_a",
encrypted_content: "signed-a"
},
{
type: "reasoning",
id: "rs_b",
summary: [{ type: "summary_text", text: "Check" }],
encrypted_content: "opaque-b"
},
{
type: "function_call",
name: "read",
call_id: "b",
arguments: "{\"path\":\"b\"}",
id: "fc_b",
encrypted_content: "signed-b"
}
])
}))

it.effect("flushes streamed OpenAI function calls without item completion at the terminal event", () =>
Effect.gen(function*() {
const call = (id: string, encrypted_content?: string) => ({
type: "function_call",
id: `fc_${id}`,
call_id: id,
name: "read",
arguments: "",
...(encrypted_content === undefined ? {} : { encrypted_content })
})
const events = [
...[call("a", "signed-a"), call("b")].flatMap((item, output_index) => [
{ type: "response.output_item.added", output_index, item },
{
type: "response.function_call_arguments.done",
output_index,
item_id: item.id,
arguments: JSON.stringify({ path: item.call_id })
}
]),
{
type: "response.completed",
response: {
id: "response",
created_at: 1,
model: "fixture",
status: "completed",
output: [],
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 }
}
}
]
const { stream } = fixture("openai", "valid", false, "native", { events })
const parts = yield* Stream.runCollect(stream())
assert.deepStrictEqual(
parts.flatMap((part): Array<unknown> =>
part.type === "tool-call"
? [{ id: part.id, params: part.params, metadata: part.metadata.openai }]
: part.type === "finish"
? [part.reason]
: []
),
[
{ id: "a", params: { path: "a" }, metadata: { itemId: "fc_a", encryptedContent: "signed-a" } },
{ id: "b", params: { path: "b" }, metadata: { itemId: "fc_b" } },
"tool-calls"
]
)
}))

it.effect("rejects multiple compatible choices before sending a request", () =>
Effect.gen(function*() {
const { stream, requests } = fixture("compat", "valid", false, "reasoning", { config: { n: 2 } })
Expand Down
10 changes: 10 additions & 0 deletions packages/ai/openai-compat/src/OpenAiLanguageModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ declare module "effect/unstable/ai/Prompt" {
* The status to send for the tool-call item.
*/
readonly status?: MessageStatus | null
/**
* Opaque reasoning state returned on a Responses function call item.
* Declared to match the OpenAI provider declarations.
*/
readonly encryptedContent?: string | null
} | null
}

Expand Down Expand Up @@ -436,6 +441,11 @@ declare module "effect/unstable/ai/Response" {
* The OpenAI item ID associated with the tool call.
*/
readonly itemId?: string | null
/**
* Opaque reasoning state returned on a Responses function call item.
* Declared to match the OpenAI provider declarations.
*/
readonly encryptedContent?: string | null
} | null
}

Expand Down
Loading
Loading