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
This commit is contained in:
roboomp
2026-08-02 22:15:12 +00:00
parent c34c2830ce
commit 326fee3387
2 changed files with 76 additions and 10 deletions
@@ -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
@@ -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"');
});
});