From 3cc61856e1859d2e91cb79fe04712c1cca33ab1f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 25 Jun 2026 23:48:52 -0400 Subject: [PATCH] fix(core): replay OpenAI reasoning statelessly --- packages/core/src/session/runner/model.ts | 8 +- .../core/test/session-runner-model.test.ts | 79 ++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/packages/core/src/session/runner/model.ts b/packages/core/src/session/runner/model.ts index b864df8ba38a..a53ed19e23f5 100644 --- a/packages/core/src/session/runner/model.ts +++ b/packages/core/src/session/runner/model.ts @@ -3,7 +3,7 @@ export * as SessionRunnerModel from "./model" import { type Model } from "@opencode-ai/llm" import * as AnthropicMessages from "@opencode-ai/llm/protocols/anthropic-messages" import * as OpenAICompatibleChat from "@opencode-ai/llm/protocols/openai-compatible-chat" -import * as OpenAIResponses from "@opencode-ai/llm/protocols/openai-responses" +import * as OpenAI from "@opencode-ai/llm/providers/openai" import { Auth, type AnyRoute } from "@opencode-ai/llm/route" import { Context, Effect, Layer, Schema } from "effect" import { produce } from "immer" @@ -139,8 +139,12 @@ export const fromCatalogModel = ( }) const key = apiKey(resolved, credential) if (resolved.api.type === "aisdk" && resolved.api.package === "@ai-sdk/openai") { + const store = resolved.request.body.store + const route = OpenAI.configure({ + providerOptions: typeof store === "boolean" ? { openai: { store } } : undefined, + }).responses(resolved.api.id).route return Effect.succeed( - withDefaults(resolved, OpenAIResponses.route) + withDefaults(resolved, route) .with({ auth: key === undefined ? Auth.none : Auth.bearer(key) }) .model({ id: resolved.api.id }), ) diff --git a/packages/core/test/session-runner-model.test.ts b/packages/core/test/session-runner-model.test.ts index 94669b24be92..f3a3d8776911 100644 --- a/packages/core/test/session-runner-model.test.ts +++ b/packages/core/test/session-runner-model.test.ts @@ -1,5 +1,6 @@ import { describe, expect } from "bun:test" -import { LLM } from "@opencode-ai/llm" +import { LLM, Message, ToolCallPart, ToolResultPart } from "@opencode-ai/llm" +import * as OpenAIResponses from "@opencode-ai/llm/protocols/openai-responses" import { LLMClient } from "@opencode-ai/llm/route" import { DateTime, Effect } from "effect" import { Headers } from "effect/unstable/http" @@ -72,6 +73,82 @@ describe("SessionRunnerModel", () => { }), ) + it.effect("replays encrypted reasoning for stateless catalog OpenAI tool continuations", () => + Effect.gen(function* () { + const catalog = ModelV2.Info.make({ + ...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }), + api: { + type: "aisdk", + package: "@ai-sdk/openai", + id: ModelV2.ID.make("custom-reasoning-model"), + url: "https://openai.example/v1", + }, + request: { + headers: {}, + body: { include: ["reasoning.encrypted_content"] }, + }, + }) + const resolved = yield* SessionRunnerModel.fromCatalogModel(catalog) + const continuation = LLM.request({ + model: resolved, + messages: [ + Message.user("Inspect the fixture."), + Message.assistant([ + { + type: "reasoning", + text: "I should use the fixture tool.", + providerMetadata: { + openai: { + itemId: "rs_fixture_1", + reasoningEncryptedContent: "encrypted-fixture-state", + }, + }, + }, + ToolCallPart.make({ id: "call_fixture_1", name: "inspect_fixture", input: { id: "fixture" } }), + ]), + Message.tool( + ToolResultPart.make({ + id: "call_fixture_1", + name: "inspect_fixture", + result: { status: "ok" }, + }), + ), + ], + }) + const prepared = yield* LLMClient.prepare(continuation) + + expect(prepared.body.input.some((item) => "type" in item && item.type === "item_reference")).toBe(false) + expect(prepared.body.store).toBe(false) + expect(resolved.route.defaults.http?.body).toMatchObject({ include: ["reasoning.encrypted_content"] }) + expect(prepared.body.input).toContainEqual({ + type: "reasoning", + id: "rs_fixture_1", + summary: [{ type: "summary_text", text: "I should use the fixture tool." }], + encrypted_content: "encrypted-fixture-state", + }) + expect(prepared.body.input).toContainEqual({ + type: "function_call_output", + call_id: "call_fixture_1", + output: '{"status":"ok"}', + }) + + const stateful = yield* SessionRunnerModel.fromCatalogModel( + ModelV2.Info.make({ + ...catalog, + request: { ...catalog.request, body: { ...catalog.request.body, store: true } }, + }), + ) + const statefulPrepared = yield* LLMClient.prepare( + LLM.updateRequest(continuation, { + model: stateful, + }), + ) + + expect(statefulPrepared.body.store).toBe(true) + expect(statefulPrepared.body.input).toContainEqual({ type: "item_reference", id: "rs_fixture_1" }) + }), + ) + it.effect("uses merged API settings for OpenAI-compatible auth and request defaults", () => Effect.gen(function* () { const resolved = yield* SessionRunnerModel.fromCatalogModel(