From 326fee33875e7eb59b1e6bb71a79bfadcbbf3615 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 2 Aug 2026 22:15:12 +0000 Subject: [PATCH] fix(coding-agent): preserved legacy simple stream options - Routed streamSimpleOpenAIResponses through the central simple-stream dispatcher. - Added wire-level coverage for hidden reasoning summary translation. Fixes #7403 --- .../src/extensibility/legacy-pi-ai-shim.ts | 28 +++++++-- .../legacy-pi-ai-root-exports.test.ts | 58 +++++++++++++++++-- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts b/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts index 002f1d454..26e832d92 100644 --- a/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts +++ b/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.ts @@ -19,7 +19,15 @@ * `types.ts` via the `export *` below — pi-ai still exports both as types, * only the runtime `Type` builder and `StringEnum()` helper were removed. */ -import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai"; +import { + type Api, + type AssistantMessage, + type AssistantMessageEventStream, + type Context, + type Model, + type SimpleStreamOptions, + streamSimple, +} from "@oh-my-pi/pi-ai"; import type { Effort } from "@oh-my-pi/pi-catalog/effort"; import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking"; import { @@ -121,11 +129,19 @@ export { calculateCost, getBundledModel, getBundledModels, getBundledProviders, export const getModel = getBundledModel; export const getModels = getBundledModels; -// Upstream's `/compat` entrypoint exposes this legacy name for the simple -// OpenAI Responses stream. OMP's provider uses the same simple-options -// contract under `streamOpenAIResponses`; alias it for rewritten compat -// imports from extensions such as pi-openai-server-compaction. -export { streamOpenAIResponses as streamSimpleOpenAIResponses } from "@oh-my-pi/pi-ai"; +/** + * Stream OpenAI Responses through the historical simple-options contract. + * + * Legacy `/compat` callers pass {@link SimpleStreamOptions}; routing through + * `streamSimple` preserves option normalization before provider dispatch. + */ +export function streamSimpleOpenAIResponses( + model: Model<"openai-responses">, + context: Context, + options?: SimpleStreamOptions, +): AssistantMessageEventStream { + return streamSimple(model, context, options); +} /** * Compatibility re-exports for runtime helpers that upstream * `@earendil-works/pi-ai` exposed from its package root but omp's diff --git a/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts b/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts index 9423654ee..52d45be01 100644 --- a/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts +++ b/packages/coding-agent/test/extensibility/legacy-pi-ai-root-exports.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "bun:test"; -import type { AssistantMessage } from "@oh-my-pi/pi-ai"; -import { streamOpenAIResponses } from "@oh-my-pi/pi-ai"; +import type { AssistantMessage, FetchImpl } from "@oh-my-pi/pi-ai"; +import { buildModel } from "@oh-my-pi/pi-catalog/build"; +import { Effort } from "@oh-my-pi/pi-catalog/effort"; import { isContextOverflow, parseJsonWithRepair, @@ -55,7 +56,56 @@ describe("legacy pi-ai shim root exports", () => { // parseStreamingJson completes a truncated object at the streaming edge. expect(parseStreamingJson<{ a: number }>('{"a": 1')).toEqual({ a: 1 }); }); - it("aliases the legacy simple OpenAI Responses stream", () => { - expect(streamSimpleOpenAIResponses).toBe(streamOpenAIResponses); + it("maps legacy simple options before streaming OpenAI Responses", async () => { + const requests: unknown[] = []; + const fetchMock: FetchImpl = Object.assign( + async () => + new Response( + JSON.stringify({ + error: { message: "intentional test response", type: "invalid_request_error" }, + }), + { status: 400, headers: { "content-type": "application/json" } }, + ), + { preconnect: fetch.preconnect }, + ); + const model = buildModel({ + id: "legacy-simple-options", + name: "Legacy Simple Options", + api: "openai-responses", + provider: "openai", + baseUrl: "https://responses.example.test/v1", + reasoning: true, + compat: { + supportsReasoningParams: true, + supportsReasoningEffort: true, + }, + thinking: { + mode: "effort", + efforts: [Effort.High], + }, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 16_384, + }); + + const result = await streamSimpleOpenAIResponses( + model, + { messages: [{ role: "user", content: "hello", timestamp: 0 }] }, + { + apiKey: "test-key", + reasoning: Effort.High, + hideThinkingSummary: true, + fetch: fetchMock, + onPayload: request => { + requests.push(request); + }, + }, + ).result(); + + expect(result.stopReason).toBe("error"); + expect(requests).toHaveLength(1); + expect(requests[0]).toMatchObject({ reasoning: { effort: "high" } }); + expect(JSON.stringify(requests[0])).not.toContain('"summary"'); }); });