@oh-my-pi/pi-ai 16.2.1 → 16.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/types/auth-gateway/server.d.ts +0 -19
- package/dist/types/auth-retry.d.ts +2 -10
- package/dist/types/auth-storage.d.ts +1 -2
- package/dist/types/dialect/demotion.d.ts +22 -0
- package/dist/types/dialect/index.d.ts +2 -0
- package/dist/types/error/abort.d.ts +14 -0
- package/dist/types/error/auth-classify.d.ts +16 -0
- package/dist/types/error/auth.d.ts +27 -0
- package/dist/types/error/aws.d.ts +23 -0
- package/dist/types/error/classes.d.ts +102 -0
- package/dist/types/error/finalize.d.ts +39 -0
- package/dist/types/error/flags.d.ts +70 -0
- package/dist/types/error/format.d.ts +20 -0
- package/dist/types/error/gateway.d.ts +20 -0
- package/dist/types/error/index.d.ts +13 -0
- package/dist/types/error/oauth.d.ts +43 -0
- package/dist/types/error/provider.d.ts +42 -0
- package/dist/types/{rate-limit-utils.d.ts → error/rate-limit.d.ts} +14 -1
- package/dist/types/error/retryable.d.ts +27 -0
- package/dist/types/error/validation.d.ts +32 -0
- package/dist/types/index.d.ts +1 -3
- package/dist/types/providers/amazon-bedrock.d.ts +0 -5
- package/dist/types/providers/anthropic-client.d.ts +2 -16
- package/dist/types/providers/anthropic.d.ts +6 -1
- package/dist/types/providers/aws-eventstream.d.ts +2 -1
- package/dist/types/providers/cursor.d.ts +8 -7
- package/dist/types/providers/google-gemini-cli.d.ts +0 -5
- package/dist/types/providers/google-shared.d.ts +0 -5
- package/dist/types/providers/ollama.d.ts +0 -5
- package/dist/types/providers/openai-codex/request-transformer.d.ts +2 -2
- package/dist/types/providers/openai-codex/response-handler.d.ts +1 -1
- package/dist/types/providers/openai-codex-responses.d.ts +2 -2
- package/dist/types/providers/openai-responses-server-schema.d.ts +6 -0
- package/dist/types/providers/openai-responses-wire.d.ts +1 -1
- package/dist/types/providers/openai-responses.d.ts +3 -2
- package/dist/types/providers/openai-shared.d.ts +8 -6
- package/dist/types/providers/pi-native-client.d.ts +0 -9
- package/dist/types/registry/oauth/xai-oauth.d.ts +0 -9
- package/dist/types/types.d.ts +6 -0
- package/dist/types/usage/openai-codex-base-url.d.ts +17 -0
- package/dist/types/utils/block-symbols.d.ts +20 -0
- package/dist/types/utils/openai-http.d.ts +4 -8
- package/dist/types/utils/retry.d.ts +2 -14
- package/dist/types/utils/stream-markup-healing.d.ts +14 -5
- package/dist/types/utils/thinking-loop.d.ts +3 -1
- package/package.json +8 -4
- package/src/api-registry.ts +3 -1
- package/src/auth-broker/discover.ts +7 -2
- package/src/auth-broker/remote-store.ts +8 -7
- package/src/auth-gateway/server.ts +27 -115
- package/src/auth-retry.ts +9 -21
- package/src/auth-storage.ts +34 -49
- package/src/dialect/demotion.ts +31 -0
- package/src/dialect/factory.ts +0 -2
- package/src/dialect/index.ts +2 -0
- package/src/dialect/owned-stream.ts +0 -1
- package/src/dialect/thinking.ts +22 -10
- package/src/error/abort.ts +18 -0
- package/src/error/auth-classify.ts +30 -0
- package/src/error/auth.ts +48 -0
- package/src/error/aws.ts +31 -0
- package/src/error/classes.ts +186 -0
- package/src/error/finalize.ts +69 -0
- package/src/error/flags.ts +486 -0
- package/src/error/format.ts +36 -0
- package/src/error/gateway.ts +96 -0
- package/src/error/index.ts +13 -0
- package/src/error/oauth.ts +58 -0
- package/src/error/provider.ts +56 -0
- package/src/{rate-limit-utils.ts → error/rate-limit.ts} +9 -3
- package/src/error/retryable.ts +70 -0
- package/src/error/validation.ts +44 -0
- package/src/index.ts +1 -3
- package/src/providers/amazon-bedrock.ts +61 -57
- package/src/providers/anthropic-client.ts +13 -41
- package/src/providers/anthropic-messages-server.ts +9 -2
- package/src/providers/anthropic.ts +84 -147
- package/src/providers/aws-credentials.ts +41 -11
- package/src/providers/aws-eventstream.ts +12 -21
- package/src/providers/azure-openai-responses.ts +27 -17
- package/src/providers/cursor.ts +59 -53
- package/src/providers/devin.ts +28 -20
- package/src/providers/gitlab-duo-workflow.ts +30 -10
- package/src/providers/gitlab-duo.ts +22 -6
- package/src/providers/google-auth.ts +15 -5
- package/src/providers/google-gemini-cli.ts +46 -60
- package/src/providers/google-shared.ts +40 -38
- package/src/providers/google-vertex.ts +3 -2
- package/src/providers/google.ts +5 -1
- package/src/providers/mock.ts +10 -7
- package/src/providers/ollama.ts +40 -30
- package/src/providers/openai-chat-server.ts +2 -1
- package/src/providers/openai-codex/request-transformer.ts +13 -12
- package/src/providers/openai-codex/response-handler.ts +1 -1
- package/src/providers/openai-codex-responses.ts +931 -1012
- package/src/providers/openai-completions.ts +46 -36
- package/src/providers/openai-responses-server-schema.ts +3 -0
- package/src/providers/openai-responses-server.ts +82 -30
- package/src/providers/openai-responses-wire.ts +1 -1
- package/src/providers/openai-responses.ts +50 -24
- package/src/providers/openai-shared.ts +118 -51
- package/src/providers/pi-native-client.ts +12 -18
- package/src/providers/pi-native-server.ts +9 -6
- package/src/providers/register-builtins.ts +5 -2
- package/src/providers/transform-messages.ts +31 -63
- package/src/registry/alibaba-coding-plan.ts +6 -5
- package/src/registry/api-key-login.ts +4 -3
- package/src/registry/api-key-validation.ts +4 -3
- package/src/registry/cloudflare-ai-gateway.ts +4 -3
- package/src/registry/coreweave.ts +2 -1
- package/src/registry/deepseek.ts +2 -1
- package/src/registry/google-antigravity.ts +2 -1
- package/src/registry/google-gemini-cli.ts +2 -1
- package/src/registry/kagi.ts +4 -3
- package/src/registry/kilo.ts +32 -10
- package/src/registry/litellm.ts +4 -3
- package/src/registry/llama-cpp.ts +3 -2
- package/src/registry/lm-studio.ts +3 -2
- package/src/registry/nvidia.ts +7 -7
- package/src/registry/oauth/anthropic.ts +23 -6
- package/src/registry/oauth/callback-server.ts +5 -3
- package/src/registry/oauth/cursor.ts +18 -4
- package/src/registry/oauth/devin.ts +14 -3
- package/src/registry/oauth/github-copilot.ts +21 -10
- package/src/registry/oauth/gitlab-duo-workflow.ts +15 -3
- package/src/registry/oauth/gitlab-duo.ts +30 -6
- package/src/registry/oauth/google-antigravity.ts +14 -5
- package/src/registry/oauth/google-gemini-cli.ts +24 -7
- package/src/registry/oauth/google-oauth-shared.ts +6 -3
- package/src/registry/oauth/index.ts +19 -8
- package/src/registry/oauth/kimi.ts +40 -10
- package/src/registry/oauth/openai-codex.ts +26 -11
- package/src/registry/oauth/opencode.ts +4 -3
- package/src/registry/oauth/perplexity.ts +33 -11
- package/src/registry/oauth/xai-oauth.ts +62 -18
- package/src/registry/oauth/xiaomi.ts +21 -9
- package/src/registry/ollama-cloud.ts +5 -4
- package/src/registry/ollama.ts +3 -2
- package/src/registry/parallel.ts +4 -3
- package/src/registry/qwen-portal.ts +4 -3
- package/src/registry/tavily.ts +4 -3
- package/src/registry/vercel-ai-gateway.ts +4 -3
- package/src/registry/vllm.ts +3 -2
- package/src/stream.ts +79 -24
- package/src/types.ts +6 -0
- package/src/usage/claude.ts +2 -1
- package/src/usage/github-copilot.ts +4 -3
- package/src/usage/google-antigravity.ts +2 -1
- package/src/usage/openai-codex-base-url.ts +30 -10
- package/src/utils/abort.ts +4 -2
- package/src/utils/block-symbols.ts +32 -0
- package/src/utils/event-stream.ts +15 -3
- package/src/utils/http-inspector.ts +4 -4
- package/src/utils/idle-iterator.ts +6 -5
- package/src/utils/openai-http.ts +11 -46
- package/src/utils/parse-bind.ts +7 -5
- package/src/utils/proxy.ts +2 -1
- package/src/utils/retry.ts +7 -23
- package/src/utils/schema/normalize.ts +3 -2
- package/src/utils/stream-markup-healing.ts +41 -18
- package/src/utils/thinking-loop.ts +15 -11
- package/src/utils/validation.ts +4 -3
- package/dist/types/dialect/pi.d.ts +0 -9
- package/dist/types/errors.d.ts +0 -24
- package/dist/types/utils/overflow.d.ts +0 -55
- package/src/dialect/pi.md +0 -55
- package/src/dialect/pi.ts +0 -600
- package/src/errors.ts +0 -32
- package/src/utils/overflow.ts +0 -140
|
@@ -3,7 +3,9 @@ import { isKimiModelId } from "@oh-my-pi/pi-catalog/identity";
|
|
|
3
3
|
import { resolveWireModelId } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
4
4
|
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
|
5
5
|
import type { ResolvedOpenAICompat } from "@oh-my-pi/pi-catalog/types";
|
|
6
|
-
import { $env,
|
|
6
|
+
import { $env, parseStreamingJson, parseStreamingJsonThrottled } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { renderDemotedThinking } from "../dialect/demotion";
|
|
8
|
+
import * as AIError from "../error";
|
|
7
9
|
import { getKimiCommonHeaders } from "../registry/oauth/kimi";
|
|
8
10
|
import { getEnvApiKey } from "../stream";
|
|
9
11
|
import type {
|
|
@@ -27,9 +29,10 @@ import type {
|
|
|
27
29
|
} from "../types";
|
|
28
30
|
import { normalizeSystemPrompts } from "../utils";
|
|
29
31
|
import { createAbortSourceTracker } from "../utils/abort";
|
|
32
|
+
import { kStreamingLastParseLen } from "../utils/block-symbols";
|
|
30
33
|
import { hasVisibleAssistantContent, withEmptyCompletionRetry } from "../utils/empty-completion-retry";
|
|
31
34
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
32
|
-
import {
|
|
35
|
+
import type { RawHttpRequestDump } from "../utils/http-inspector";
|
|
33
36
|
import {
|
|
34
37
|
getOpenAIStreamFirstEventTimeoutMs,
|
|
35
38
|
getOpenAIStreamIdleTimeoutMs,
|
|
@@ -126,10 +129,6 @@ type OpenAICompletionsToolMessageParam = ChatCompletionToolMessageParam & {
|
|
|
126
129
|
name?: string;
|
|
127
130
|
};
|
|
128
131
|
|
|
129
|
-
type OpenAICompletionsContentBlockWithIndex = AssistantMessage["content"][number] & {
|
|
130
|
-
index?: unknown;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
132
|
type OpenAICompletionsUsageLike = {
|
|
134
133
|
completion_tokens?: unknown;
|
|
135
134
|
prompt_tokens?: unknown;
|
|
@@ -559,14 +558,16 @@ const streamOpenAICompletionsOnce = (
|
|
|
559
558
|
const stream = new AssistantMessageEventStream();
|
|
560
559
|
|
|
561
560
|
(async () => {
|
|
562
|
-
const startTime =
|
|
561
|
+
const startTime = performance.now();
|
|
563
562
|
let firstTokenTime: number | undefined;
|
|
564
563
|
const policy = resolveOpenAICompatForRequest(model, options);
|
|
565
564
|
|
|
566
565
|
const output: AssistantMessage = createInitialResponsesAssistantMessage(model.api, model.provider, model.id);
|
|
567
566
|
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
568
567
|
const abortTracker = createAbortSourceTracker(options?.signal);
|
|
569
|
-
const firstEventTimeoutAbortError = new
|
|
568
|
+
const firstEventTimeoutAbortError = new AIError.StreamTimeoutError(
|
|
569
|
+
OPENAI_COMPLETIONS_FIRST_EVENT_TIMEOUT_MESSAGE,
|
|
570
|
+
);
|
|
570
571
|
const { requestAbortController, requestSignal } = abortTracker;
|
|
571
572
|
const onSseEvent = options?.onSseEvent;
|
|
572
573
|
const rawSseObserver = onSseEvent
|
|
@@ -746,7 +747,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
746
747
|
type ToolCallStreamBlock = ToolCall & {
|
|
747
748
|
partialArgs?: string | Record<string, unknown>;
|
|
748
749
|
streamIndex?: number;
|
|
749
|
-
|
|
750
|
+
[kStreamingLastParseLen]?: number;
|
|
750
751
|
};
|
|
751
752
|
type OpenAIStreamBlock = TextContent | ThinkingContent | ToolCallStreamBlock;
|
|
752
753
|
const pendingToolCallBlocks: ToolCallStreamBlock[] = [];
|
|
@@ -777,7 +778,6 @@ const streamOpenAICompletionsOnce = (
|
|
|
777
778
|
block.arguments =
|
|
778
779
|
typeof block.partialArgs === "string" ? parseStreamingJson(block.partialArgs) : block.partialArgs;
|
|
779
780
|
delete block.partialArgs;
|
|
780
|
-
delete block.lastParseLen;
|
|
781
781
|
if (block.streamIndex !== undefined) {
|
|
782
782
|
toolCallBlockByIndex.delete(block.streamIndex);
|
|
783
783
|
delete block.streamIndex;
|
|
@@ -867,7 +867,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
867
867
|
|
|
868
868
|
const appendTextDelta = (text: string): void => {
|
|
869
869
|
if (!text) return;
|
|
870
|
-
if (!firstTokenTime) firstTokenTime =
|
|
870
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
871
871
|
appendText(output, stream, text);
|
|
872
872
|
};
|
|
873
873
|
// Tracks the last full cumulative reasoning snapshot per signature (the
|
|
@@ -894,7 +894,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
894
894
|
lastCumulativeReasoningBySignature.set(key, thinking);
|
|
895
895
|
if (!emittedThinking) return;
|
|
896
896
|
}
|
|
897
|
-
if (!firstTokenTime) firstTokenTime =
|
|
897
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
898
898
|
appendThinking(output, stream, emittedThinking, signature);
|
|
899
899
|
};
|
|
900
900
|
|
|
@@ -1068,7 +1068,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
1068
1068
|
|
|
1069
1069
|
const normalizedDeltaText = normalizeStreamingContentText(choice.delta.content);
|
|
1070
1070
|
if (normalizedDeltaText.length > 0) {
|
|
1071
|
-
if (!firstTokenTime) firstTokenTime =
|
|
1071
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
1072
1072
|
const hasStructuredToolCalls =
|
|
1073
1073
|
Array.isArray(choice.delta.tool_calls) && choice.delta.tool_calls.length > 0;
|
|
1074
1074
|
|
|
@@ -1145,10 +1145,13 @@ const streamOpenAICompletionsOnce = (
|
|
|
1145
1145
|
delta = rawArgs;
|
|
1146
1146
|
const prev = typeof block.partialArgs === "string" ? block.partialArgs : "";
|
|
1147
1147
|
block.partialArgs = prev + rawArgs;
|
|
1148
|
-
const throttled = parseStreamingJsonThrottled(
|
|
1148
|
+
const throttled = parseStreamingJsonThrottled(
|
|
1149
|
+
block.partialArgs,
|
|
1150
|
+
block[kStreamingLastParseLen] ?? 0,
|
|
1151
|
+
);
|
|
1149
1152
|
if (throttled) {
|
|
1150
1153
|
block.arguments = throttled.value;
|
|
1151
|
-
block
|
|
1154
|
+
block[kStreamingLastParseLen] = throttled.parsedLen;
|
|
1152
1155
|
}
|
|
1153
1156
|
}
|
|
1154
1157
|
} else if (rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)) {
|
|
@@ -1253,23 +1256,26 @@ const streamOpenAICompletionsOnce = (
|
|
|
1253
1256
|
output.stopReason = "error";
|
|
1254
1257
|
output.errorMessage = EMPTY_OLLAMA_LENGTH_COMPLETION_MESSAGE;
|
|
1255
1258
|
}
|
|
1256
|
-
const
|
|
1257
|
-
if (
|
|
1258
|
-
throw
|
|
1259
|
+
const localAbortReason = abortTracker.getLocalAbortReason();
|
|
1260
|
+
if (localAbortReason) {
|
|
1261
|
+
throw localAbortReason;
|
|
1259
1262
|
}
|
|
1260
1263
|
if (abortTracker.wasCallerAbort()) {
|
|
1261
|
-
throw new
|
|
1264
|
+
throw new AIError.AbortError();
|
|
1262
1265
|
}
|
|
1263
1266
|
|
|
1264
1267
|
if (output.stopReason === "aborted") {
|
|
1265
|
-
throw new
|
|
1268
|
+
throw new AIError.AbortError();
|
|
1266
1269
|
}
|
|
1267
1270
|
if (output.stopReason === "error") {
|
|
1268
|
-
throw new
|
|
1271
|
+
throw new AIError.ProviderResponseError(output.errorMessage || "Provider returned an error stop reason", {
|
|
1272
|
+
provider: model.provider,
|
|
1273
|
+
kind: "runtime",
|
|
1274
|
+
});
|
|
1269
1275
|
}
|
|
1270
1276
|
|
|
1271
1277
|
output.errorMessage = undefined;
|
|
1272
|
-
output.duration =
|
|
1278
|
+
output.duration = performance.now() - startTime;
|
|
1273
1279
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
1274
1280
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
1275
1281
|
stream.end();
|
|
@@ -1280,19 +1286,22 @@ const streamOpenAICompletionsOnce = (
|
|
|
1280
1286
|
try {
|
|
1281
1287
|
finishOpenBlocksOnError();
|
|
1282
1288
|
} catch {}
|
|
1283
|
-
for (const block of output.content) delete (block as OpenAICompletionsContentBlockWithIndex).index;
|
|
1284
|
-
const firstEventTimeoutError = abortTracker.getLocalAbortReason();
|
|
1285
|
-
output.stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
1286
1289
|
const capturedErrorResponse = error instanceof OpenAIHttpError ? error.captured : undefined;
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1290
|
+
const result = await AIError.finalize(error, {
|
|
1291
|
+
api: model.api,
|
|
1292
|
+
provider: model.provider,
|
|
1293
|
+
abortTracker,
|
|
1294
|
+
rawRequestDump,
|
|
1295
|
+
capturedErrorResponse,
|
|
1296
|
+
});
|
|
1297
|
+
output.stopReason = result.stopReason;
|
|
1298
|
+
output.errorStatus = result.status;
|
|
1299
|
+
output.errorId = result.id;
|
|
1300
|
+
output.errorMessage = result.message;
|
|
1291
1301
|
// Some providers via OpenRouter include extra details here.
|
|
1292
1302
|
const rawMetadata = (error as { error?: { metadata?: { raw?: string } } })?.error?.metadata?.raw;
|
|
1293
1303
|
if (rawMetadata) output.errorMessage += `\n${rawMetadata}`;
|
|
1294
|
-
output.
|
|
1295
|
-
output.duration = Date.now() - startTime;
|
|
1304
|
+
output.duration = performance.now() - startTime;
|
|
1296
1305
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
1297
1306
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
1298
1307
|
stream.end();
|
|
@@ -1334,7 +1343,7 @@ function createRequestSetup(
|
|
|
1334
1343
|
azureChatCompletions: { apiVersion, deploymentName },
|
|
1335
1344
|
});
|
|
1336
1345
|
if (!setup.baseUrl) {
|
|
1337
|
-
throw new
|
|
1346
|
+
throw new AIError.ConfigurationError("OpenAI request setup did not resolve a base URL");
|
|
1338
1347
|
}
|
|
1339
1348
|
return setup as OpenAIRequestSetup & { baseUrl: string };
|
|
1340
1349
|
}
|
|
@@ -1775,13 +1784,14 @@ export function convertMessages(
|
|
|
1775
1784
|
const nonEmptyThinkingBlocks = thinkingBlocks.filter(b => b.thinking && b.thinking.trim().length > 0);
|
|
1776
1785
|
if (nonEmptyThinkingBlocks.length > 0) {
|
|
1777
1786
|
if (compat.requiresThinkingAsText) {
|
|
1778
|
-
|
|
1779
|
-
|
|
1787
|
+
const thinkingText = nonEmptyThinkingBlocks
|
|
1788
|
+
.map(b => renderDemotedThinking(model.id, b.thinking))
|
|
1789
|
+
.join("");
|
|
1780
1790
|
// `content` is a plain string at this point (set above) or null —
|
|
1781
|
-
// never an array. Prepend the thinking
|
|
1791
|
+
// never an array. Prepend the demoted thinking to the string form.
|
|
1782
1792
|
assistantMsg.content =
|
|
1783
1793
|
typeof assistantMsg.content === "string" && assistantMsg.content.length > 0
|
|
1784
|
-
? `${thinkingText}
|
|
1794
|
+
? `${thinkingText}${assistantMsg.content}`
|
|
1785
1795
|
: thinkingText;
|
|
1786
1796
|
} else if (compat.requiresReasoningContentForToolCalls) {
|
|
1787
1797
|
// Use the streamed signature when the backend accepts whichever
|
|
@@ -92,8 +92,11 @@ const systemMessageItemSchema = type({
|
|
|
92
92
|
|
|
93
93
|
const assistantMessageItemSchema = type({
|
|
94
94
|
"type?": "'message'",
|
|
95
|
+
"id?": "string",
|
|
95
96
|
role: "'assistant'",
|
|
96
97
|
"content?": type("string").or(outputContentBlockSchema.array()),
|
|
98
|
+
"status?": "'in_progress' | 'completed' | 'incomplete'",
|
|
99
|
+
"phase?": "'commentary' | 'final_answer' | null",
|
|
97
100
|
});
|
|
98
101
|
|
|
99
102
|
const reasoningItemSchema = type({
|
|
@@ -13,6 +13,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
13
13
|
import { type } from "arktype";
|
|
14
14
|
import { resolvePromptCacheKey } from "../auth-gateway/http";
|
|
15
15
|
import type { AuthGatewayStreamControl, AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
|
|
16
|
+
import * as AIError from "../error";
|
|
16
17
|
import type {
|
|
17
18
|
AssistantMessage,
|
|
18
19
|
AssistantMessageEventStream,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
type OpenAIResponsesTool,
|
|
33
34
|
openaiResponsesRequestSchema,
|
|
34
35
|
} from "./openai-responses-server-schema";
|
|
36
|
+
import { encodeTextSignatureV1, parseTextSignature } from "./openai-shared";
|
|
35
37
|
|
|
36
38
|
export type { ParsedRequest };
|
|
37
39
|
|
|
@@ -53,6 +55,20 @@ function asString(v: unknown): string | undefined {
|
|
|
53
55
|
return typeof v === "string" ? v : undefined;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
type AssistantItemPhase = "commentary" | "final_answer";
|
|
59
|
+
type MessageSignature = { id: string; phase?: AssistantItemPhase };
|
|
60
|
+
|
|
61
|
+
function parseAssistantItemPhase(value: unknown): AssistantItemPhase | undefined {
|
|
62
|
+
return value === "commentary" || value === "final_answer" ? value : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function messageTextSignature(id: unknown, phase: unknown): string | undefined {
|
|
66
|
+
const parsedPhase = parseAssistantItemPhase(phase);
|
|
67
|
+
if (typeof id === "string" && id.length > 0) return encodeTextSignatureV1(id, parsedPhase);
|
|
68
|
+
if (!parsedPhase) return undefined;
|
|
69
|
+
return encodeTextSignatureV1(makeMsgId(), parsedPhase);
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
// ─── id helpers ─────────────────────────────────────────────────────────────
|
|
57
73
|
|
|
58
74
|
function uuidNoDashes(): string {
|
|
@@ -146,20 +162,27 @@ type OutputBlockUnion =
|
|
|
146
162
|
| { type: "text"; text: string }
|
|
147
163
|
| { type: "refusal"; refusal: string };
|
|
148
164
|
|
|
149
|
-
function outputTextOf(
|
|
150
|
-
|
|
165
|
+
function outputTextOf(
|
|
166
|
+
blocks: OpenAIResponsesOutputContent[] | string | undefined,
|
|
167
|
+
message?: { id?: unknown; phase?: unknown },
|
|
168
|
+
): TextContent[] {
|
|
169
|
+
const textSignature = messageTextSignature(message?.id, message?.phase);
|
|
170
|
+
const textContent = (text: string): TextContent =>
|
|
171
|
+
textSignature ? { type: "text", text, textSignature } : { type: "text", text };
|
|
172
|
+
if (typeof blocks === "string") return blocks.length > 0 ? [textContent(blocks)] : [];
|
|
151
173
|
if (!blocks) return [];
|
|
152
|
-
const
|
|
174
|
+
const parts: string[] = [];
|
|
153
175
|
for (const raw of blocks) {
|
|
154
176
|
const block = raw as OutputBlockUnion;
|
|
155
177
|
if (block.type === "output_text" || block.type === "text") {
|
|
156
|
-
|
|
178
|
+
parts.push(block.text);
|
|
157
179
|
} else if (block.type === "refusal") {
|
|
158
180
|
// Preserve the refusal reason so history replay still carries it.
|
|
159
|
-
|
|
181
|
+
parts.push(`[refusal: ${block.refusal}]`);
|
|
160
182
|
}
|
|
161
183
|
}
|
|
162
|
-
|
|
184
|
+
const text = parts.join("");
|
|
185
|
+
return text.length > 0 ? [textContent(text)] : [];
|
|
163
186
|
}
|
|
164
187
|
|
|
165
188
|
// The schema accepts a much wider tool_choice union than the SDK type so the
|
|
@@ -266,7 +289,7 @@ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
|
266
289
|
|
|
267
290
|
const data = openaiResponsesRequestSchema(body);
|
|
268
291
|
if (data instanceof type.errors) {
|
|
269
|
-
throw new
|
|
292
|
+
throw new AIError.ValidationError(`openai-responses: ${data.summary}`);
|
|
270
293
|
}
|
|
271
294
|
|
|
272
295
|
const now = Date.now();
|
|
@@ -287,6 +310,8 @@ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
|
287
310
|
const msg = item as {
|
|
288
311
|
role?: string;
|
|
289
312
|
content?: OpenAIResponsesInputContent[] | OpenAIResponsesOutputContent[] | string;
|
|
313
|
+
id?: unknown;
|
|
314
|
+
phase?: unknown;
|
|
290
315
|
};
|
|
291
316
|
switch (msg.role) {
|
|
292
317
|
case "system": {
|
|
@@ -302,7 +327,10 @@ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
|
302
327
|
break;
|
|
303
328
|
}
|
|
304
329
|
case "assistant": {
|
|
305
|
-
const parts = outputTextOf(msg.content as OpenAIResponsesOutputContent[] | string | undefined
|
|
330
|
+
const parts = outputTextOf(msg.content as OpenAIResponsesOutputContent[] | string | undefined, {
|
|
331
|
+
id: msg.id,
|
|
332
|
+
phase: msg.phase,
|
|
333
|
+
});
|
|
306
334
|
messages.push({
|
|
307
335
|
role: "assistant",
|
|
308
336
|
content: parts,
|
|
@@ -345,7 +373,9 @@ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
|
345
373
|
const parsedArgs: unknown = JSON.parse(argsRaw);
|
|
346
374
|
args = isObj(parsedArgs) ? parsedArgs : {};
|
|
347
375
|
} catch {
|
|
348
|
-
throw new
|
|
376
|
+
throw new AIError.ValidationError(
|
|
377
|
+
`openai-responses: function_call ${call.call_id} has invalid JSON arguments`,
|
|
378
|
+
);
|
|
349
379
|
}
|
|
350
380
|
const toolCall: ToolCall = {
|
|
351
381
|
type: "toolCall",
|
|
@@ -500,6 +530,7 @@ type MessageOutputItem = {
|
|
|
500
530
|
role: "assistant";
|
|
501
531
|
status: "completed";
|
|
502
532
|
content: Array<{ type: "output_text"; text: string; annotations: never[] }>;
|
|
533
|
+
phase?: AssistantItemPhase;
|
|
503
534
|
};
|
|
504
535
|
|
|
505
536
|
type FunctionCallOutputItem = {
|
|
@@ -591,23 +622,32 @@ function wireCallId(id: string): string {
|
|
|
591
622
|
function buildOutputItems(message: AssistantMessage): OutputItem[] {
|
|
592
623
|
const out: OutputItem[] = [];
|
|
593
624
|
let pendingMessage: MessageOutputItem | null = null;
|
|
625
|
+
let pendingMessageSignature: { id: string; phase?: AssistantItemPhase } | undefined;
|
|
594
626
|
const flushMessage = () => {
|
|
595
627
|
if (pendingMessage) {
|
|
596
628
|
out.push(pendingMessage);
|
|
597
629
|
pendingMessage = null;
|
|
630
|
+
pendingMessageSignature = undefined;
|
|
598
631
|
}
|
|
599
632
|
};
|
|
600
633
|
|
|
601
634
|
for (const part of message.content) {
|
|
602
635
|
if (part.type === "text") {
|
|
636
|
+
const signature = parseTextSignature(part.textSignature);
|
|
637
|
+
const sameSignature =
|
|
638
|
+
!pendingMessage ||
|
|
639
|
+
(pendingMessageSignature?.id === signature?.id && pendingMessageSignature?.phase === signature?.phase);
|
|
640
|
+
if (!sameSignature) flushMessage();
|
|
603
641
|
if (!pendingMessage) {
|
|
604
642
|
pendingMessage = {
|
|
605
643
|
type: "message",
|
|
606
|
-
id: makeMsgId(),
|
|
644
|
+
id: signature?.id ?? makeMsgId(),
|
|
607
645
|
role: "assistant",
|
|
608
646
|
status: "completed",
|
|
609
647
|
content: [],
|
|
648
|
+
...(signature?.phase ? { phase: signature.phase } : {}),
|
|
610
649
|
};
|
|
650
|
+
pendingMessageSignature = signature;
|
|
611
651
|
}
|
|
612
652
|
pendingMessage.content.push({ type: "output_text", text: part.text, annotations: [] });
|
|
613
653
|
} else if (part.type === "thinking") {
|
|
@@ -616,7 +656,8 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
|
|
|
616
656
|
} else if (part.type === "toolCall") {
|
|
617
657
|
flushMessage();
|
|
618
658
|
if (part.customWireName) {
|
|
619
|
-
const
|
|
659
|
+
const input = part.arguments?.input;
|
|
660
|
+
const rawInput = typeof input === "string" ? input : "";
|
|
620
661
|
out.push({
|
|
621
662
|
type: "custom_tool_call",
|
|
622
663
|
id: part.thoughtSignature ?? makeCustomCallId(),
|
|
@@ -698,6 +739,7 @@ interface OpenMessage {
|
|
|
698
739
|
contentIndex: number;
|
|
699
740
|
currentPartText: string;
|
|
700
741
|
content: Array<{ type: "output_text"; text: string; annotations: never[] }>;
|
|
742
|
+
signature?: MessageSignature;
|
|
701
743
|
}
|
|
702
744
|
interface OpenReasoning {
|
|
703
745
|
kind: "reasoning";
|
|
@@ -765,15 +807,16 @@ export function encodeStream(
|
|
|
765
807
|
usage: null,
|
|
766
808
|
});
|
|
767
809
|
|
|
768
|
-
const openMessage = (): OpenMessage => {
|
|
810
|
+
const openMessage = (signature?: MessageSignature): OpenMessage => {
|
|
769
811
|
const itemOutputIndex = allocateOutputIndex();
|
|
770
|
-
const itemId = makeMsgId();
|
|
812
|
+
const itemId = signature?.id ?? makeMsgId();
|
|
771
813
|
const item = {
|
|
772
814
|
type: "message" as const,
|
|
773
815
|
id: itemId,
|
|
774
|
-
status: "in_progress",
|
|
816
|
+
status: "in_progress" as const,
|
|
775
817
|
role: "assistant" as const,
|
|
776
818
|
content: [] as Array<{ type: "output_text"; text: string; annotations: never[] }>,
|
|
819
|
+
...(signature?.phase ? { phase: signature.phase } : {}),
|
|
777
820
|
};
|
|
778
821
|
emit("response.output_item.added", { output_index: itemOutputIndex, item });
|
|
779
822
|
const next: OpenMessage = {
|
|
@@ -783,6 +826,7 @@ export function encodeStream(
|
|
|
783
826
|
contentIndex: 0,
|
|
784
827
|
currentPartText: "",
|
|
785
828
|
content: [],
|
|
829
|
+
...(signature ? { signature } : {}),
|
|
786
830
|
};
|
|
787
831
|
state.open = next;
|
|
788
832
|
return next;
|
|
@@ -904,20 +948,15 @@ export function encodeStream(
|
|
|
904
948
|
if (!state.open) return;
|
|
905
949
|
if (state.open.kind === "message") {
|
|
906
950
|
const item = {
|
|
907
|
-
type: "message",
|
|
951
|
+
type: "message" as const,
|
|
908
952
|
id: state.open.itemId,
|
|
909
|
-
status: "completed",
|
|
910
|
-
role: "assistant",
|
|
953
|
+
status: "completed" as const,
|
|
954
|
+
role: "assistant" as const,
|
|
911
955
|
content: state.open.content,
|
|
956
|
+
...(state.open.signature?.phase ? { phase: state.open.signature.phase } : {}),
|
|
912
957
|
};
|
|
913
958
|
emit("response.output_item.done", { output_index: state.open.outputIndex, item });
|
|
914
|
-
finishedItems.push(
|
|
915
|
-
type: "message",
|
|
916
|
-
id: state.open.itemId,
|
|
917
|
-
role: "assistant",
|
|
918
|
-
status: "completed",
|
|
919
|
-
content: state.open.content,
|
|
920
|
-
});
|
|
959
|
+
finishedItems.push(item);
|
|
921
960
|
state.open = null;
|
|
922
961
|
} else if (state.open.kind === "reasoning") {
|
|
923
962
|
const summary = [{ type: "summary_text" as const, text: state.open.reasoningText ?? "" }];
|
|
@@ -970,20 +1009,33 @@ export function encodeStream(
|
|
|
970
1009
|
}
|
|
971
1010
|
case "text_start": {
|
|
972
1011
|
let cur: OpenMessage;
|
|
1012
|
+
const textBlock = ev.partial.content[ev.contentIndex];
|
|
1013
|
+
const signature =
|
|
1014
|
+
textBlock?.type === "text" ? parseTextSignature(textBlock.textSignature) : undefined;
|
|
973
1015
|
if (state.open && state.open.kind === "message") {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1016
|
+
const sameSignature =
|
|
1017
|
+
(!signature && !state.open.signature) ||
|
|
1018
|
+
(signature !== undefined &&
|
|
1019
|
+
state.open.signature?.id === signature.id &&
|
|
1020
|
+
state.open.signature.phase === signature.phase);
|
|
1021
|
+
if (sameSignature) {
|
|
1022
|
+
// Continue same message item, new content part.
|
|
1023
|
+
cur = state.open;
|
|
1024
|
+
cur.currentPartText = "";
|
|
1025
|
+
} else {
|
|
1026
|
+
closeOpen();
|
|
1027
|
+
cur = openMessage(signature);
|
|
1028
|
+
}
|
|
977
1029
|
} else {
|
|
978
1030
|
if (state.open && state.open.kind !== "function_call") closeOpen();
|
|
979
|
-
cur = openMessage();
|
|
1031
|
+
cur = openMessage(signature);
|
|
980
1032
|
}
|
|
981
|
-
const
|
|
1033
|
+
const contentPart = { type: "output_text", text: "", annotations: [] as never[] };
|
|
982
1034
|
emit("response.content_part.added", {
|
|
983
1035
|
item_id: cur.itemId,
|
|
984
1036
|
output_index: cur.outputIndex,
|
|
985
1037
|
content_index: cur.contentIndex,
|
|
986
|
-
part,
|
|
1038
|
+
part: contentPart,
|
|
987
1039
|
});
|
|
988
1040
|
break;
|
|
989
1041
|
}
|
|
@@ -5849,7 +5849,7 @@ export interface ResponseCreateParamsBase {
|
|
|
5849
5849
|
* - [Conversation state](https://platform.openai.com/docs/guides/conversation-state)
|
|
5850
5850
|
* - [Function calling](https://platform.openai.com/docs/guides/function-calling)
|
|
5851
5851
|
*/
|
|
5852
|
-
input?:
|
|
5852
|
+
input?: ResponseInput;
|
|
5853
5853
|
/**
|
|
5854
5854
|
* A system (or developer) message inserted into the model's context.
|
|
5855
5855
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hostMatchesUrl } from "@oh-my-pi/pi-catalog/hosts";
|
|
2
|
-
import { $flag,
|
|
2
|
+
import { $flag, logger, structuredCloneJSON } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import * as AIError from "../error";
|
|
3
4
|
import { getEnvApiKey } from "../stream";
|
|
4
5
|
import type {
|
|
5
6
|
AssistantMessage,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
import { createAbortSourceTracker } from "../utils/abort";
|
|
25
26
|
import { withEmptyCompletionRetry } from "../utils/empty-completion-retry";
|
|
26
27
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
27
|
-
import {
|
|
28
|
+
import type { RawHttpRequestDump } from "../utils/http-inspector";
|
|
28
29
|
import {
|
|
29
30
|
getOpenAIStreamFirstEventTimeoutMs,
|
|
30
31
|
getOpenAIStreamIdleTimeoutMs,
|
|
@@ -97,6 +98,7 @@ export interface OpenAIResponsesOptions extends StreamOptions {
|
|
|
97
98
|
reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
98
99
|
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
99
100
|
serviceTier?: ServiceTier;
|
|
101
|
+
textVerbosity?: "low" | "medium" | "high";
|
|
100
102
|
toolChoice?: ToolChoice;
|
|
101
103
|
openrouterVariant?: string;
|
|
102
104
|
maxTokensExplicit?: boolean;
|
|
@@ -269,7 +271,7 @@ function buildOpenAIResponsesChainedParams(
|
|
|
269
271
|
? { ...params, input: params.input.slice(0, params.input.length - trailingScaffoldingItems) }
|
|
270
272
|
: params;
|
|
271
273
|
const deltaInput = chain.canAppend
|
|
272
|
-
? buildResponsesDeltaInput
|
|
274
|
+
? buildResponsesDeltaInput(chain.lastParams, chain.lastResponseItems, historyParams)
|
|
273
275
|
: null;
|
|
274
276
|
if (deltaInput && deltaInput.length > 0 && chain.lastResponseId) {
|
|
275
277
|
const scaffolding =
|
|
@@ -362,7 +364,7 @@ const streamOpenAIResponsesOnce = (
|
|
|
362
364
|
|
|
363
365
|
// Start async processing
|
|
364
366
|
(async () => {
|
|
365
|
-
const startTime =
|
|
367
|
+
const startTime = performance.now();
|
|
366
368
|
let firstTokenTime: number | undefined;
|
|
367
369
|
|
|
368
370
|
const output: AssistantMessage = createInitialResponsesAssistantMessage(model.api, model.provider, model.id);
|
|
@@ -370,7 +372,7 @@ const streamOpenAIResponsesOnce = (
|
|
|
370
372
|
let chainState: OpenAIResponsesChainState | undefined;
|
|
371
373
|
let sentPreviousResponseId: string | undefined;
|
|
372
374
|
const abortTracker = createAbortSourceTracker(options?.signal);
|
|
373
|
-
const firstEventTimeoutAbortError = new
|
|
375
|
+
const firstEventTimeoutAbortError = new AIError.StreamTimeoutError(OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE);
|
|
374
376
|
const { requestAbortController, requestSignal } = abortTracker;
|
|
375
377
|
const onSseEvent = options?.onSseEvent;
|
|
376
378
|
const rawSseObserver = onSseEvent
|
|
@@ -665,7 +667,7 @@ const streamOpenAIResponsesOnce = (
|
|
|
665
667
|
});
|
|
666
668
|
await processResponsesStream(timedOpenaiStream, output, stream, model, {
|
|
667
669
|
onFirstToken: () => {
|
|
668
|
-
if (!firstTokenTime) firstTokenTime =
|
|
670
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
669
671
|
},
|
|
670
672
|
onOutputItemDone: item => {
|
|
671
673
|
// `processResponsesStream` hands over a private clone already; no
|
|
@@ -678,12 +680,12 @@ const streamOpenAIResponsesOnce = (
|
|
|
678
680
|
requestServiceTier: options?.serviceTier,
|
|
679
681
|
});
|
|
680
682
|
|
|
681
|
-
const
|
|
682
|
-
if (
|
|
683
|
-
throw
|
|
683
|
+
const localAbortReason = abortTracker.getLocalAbortReason();
|
|
684
|
+
if (localAbortReason) {
|
|
685
|
+
throw localAbortReason;
|
|
684
686
|
}
|
|
685
687
|
if (abortTracker.wasCallerAbort()) {
|
|
686
|
-
throw new
|
|
688
|
+
throw new AIError.AbortError();
|
|
687
689
|
}
|
|
688
690
|
|
|
689
691
|
// Detect premature stream closure: the HTTP stream ended without the
|
|
@@ -692,11 +694,17 @@ const streamOpenAIResponsesOnce = (
|
|
|
692
694
|
// this guard the incomplete output is silently surfaced as a successful
|
|
693
695
|
// "stop".
|
|
694
696
|
if (!sawTerminalResponseEvent) {
|
|
695
|
-
throw new
|
|
697
|
+
throw new AIError.ProviderResponseError(
|
|
698
|
+
"OpenAI responses stream closed before a terminal response event was received",
|
|
699
|
+
{ provider: model.provider, kind: "incomplete-stream" },
|
|
700
|
+
);
|
|
696
701
|
}
|
|
697
702
|
|
|
698
703
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
699
|
-
throw new
|
|
704
|
+
throw new AIError.ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
|
|
705
|
+
provider: model.provider,
|
|
706
|
+
kind: "runtime",
|
|
707
|
+
});
|
|
700
708
|
}
|
|
701
709
|
|
|
702
710
|
output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, nativeOutputItems);
|
|
@@ -725,25 +733,28 @@ const streamOpenAIResponsesOnce = (
|
|
|
725
733
|
}
|
|
726
734
|
}
|
|
727
735
|
|
|
728
|
-
output.duration =
|
|
736
|
+
output.duration = performance.now() - startTime;
|
|
729
737
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
730
738
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
731
739
|
stream.end();
|
|
732
740
|
} catch (error) {
|
|
733
|
-
for (const block of output.content) delete (block as { index?: number }).index;
|
|
734
741
|
if (chainState) resetOpenAIResponsesChainState(chainState);
|
|
735
|
-
const firstEventTimeoutError = abortTracker.getLocalAbortReason();
|
|
736
|
-
output.stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
737
742
|
const capturedErrorResponse = error instanceof OpenAIHttpError ? error.captured : undefined;
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
743
|
+
const result = await AIError.finalize(error, {
|
|
744
|
+
api: model.api,
|
|
745
|
+
provider: model.provider,
|
|
746
|
+
abortTracker,
|
|
747
|
+
rawRequestDump,
|
|
748
|
+
capturedErrorResponse,
|
|
749
|
+
});
|
|
750
|
+
output.stopReason = result.stopReason;
|
|
751
|
+
output.errorStatus = result.status;
|
|
752
|
+
output.errorId = result.id;
|
|
753
|
+
output.errorMessage = result.message;
|
|
742
754
|
// Some providers via OpenRouter include extra details here.
|
|
743
755
|
const rawMetadata = (error as { error?: { metadata?: { raw?: string } } })?.error?.metadata?.raw;
|
|
744
756
|
if (rawMetadata) output.errorMessage += `\n${rawMetadata}`;
|
|
745
|
-
output.
|
|
746
|
-
output.duration = Date.now() - startTime;
|
|
757
|
+
output.duration = performance.now() - startTime;
|
|
747
758
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
748
759
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
749
760
|
stream.end();
|
|
@@ -762,6 +773,16 @@ const streamOpenAIResponsesOnce = (
|
|
|
762
773
|
export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (model, context, options) =>
|
|
763
774
|
withEmptyCompletionRetry(model, context, options, streamOpenAIResponsesOnce);
|
|
764
775
|
|
|
776
|
+
function isOfficialOpenAIResponsesEndpoint(model: Model<"openai-responses">): boolean {
|
|
777
|
+
if (model.provider !== "openai") return false;
|
|
778
|
+
if (!model.baseUrl) return true;
|
|
779
|
+
try {
|
|
780
|
+
return new URL(model.baseUrl).hostname === "api.openai.com";
|
|
781
|
+
} catch {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
765
786
|
export function buildParams(
|
|
766
787
|
model: Model<"openai-responses">,
|
|
767
788
|
context: Context,
|
|
@@ -849,6 +870,9 @@ export function buildParams(
|
|
|
849
870
|
});
|
|
850
871
|
|
|
851
872
|
applyCommonResponsesSamplingParams(params, { ...options, maxTokens: outputToken?.value }, model);
|
|
873
|
+
if (options?.textVerbosity && isOfficialOpenAIResponsesEndpoint(model)) {
|
|
874
|
+
params.text = { ...params.text, verbosity: options.textVerbosity };
|
|
875
|
+
}
|
|
852
876
|
// TODO: openai responses has no top-level `stop`/`stop_sequences`; surface via reasoning.stop?
|
|
853
877
|
// `StreamOptions.stopSequences` is intentionally dropped for this provider.
|
|
854
878
|
// TODO: openai responses has no top-level `frequency_penalty` field as of the current SDK;
|
|
@@ -912,7 +936,9 @@ export function buildParams(
|
|
|
912
936
|
* runtime path only consumes that metadata.
|
|
913
937
|
* @internal Exported for tests.
|
|
914
938
|
*/
|
|
915
|
-
export function supportsFreeformApplyPatch(
|
|
939
|
+
export function supportsFreeformApplyPatch(
|
|
940
|
+
model: Model<"openai-responses" | "azure-openai-responses" | "openai-codex-responses">,
|
|
941
|
+
): boolean {
|
|
916
942
|
return model.applyPatchToolType === "freeform";
|
|
917
943
|
}
|
|
918
944
|
|
|
@@ -946,7 +972,7 @@ export function mapOpenAIResponsesToolChoiceForTools(
|
|
|
946
972
|
export function convertTools(
|
|
947
973
|
tools: Tool[],
|
|
948
974
|
strictMode: boolean,
|
|
949
|
-
model: Model<"openai-responses">,
|
|
975
|
+
model: Model<"openai-responses" | "azure-openai-responses" | "openai-codex-responses">,
|
|
950
976
|
onQuarantine: (toolName: string, schemaPath: string) => void = (toolName, schemaPath) =>
|
|
951
977
|
logger.warn(
|
|
952
978
|
`Tool "${toolName}" omitted from the openai-responses request: its parameter schema is invalid for this provider at ${schemaPath} (an enum/const value cannot match its declared type). Other tools are unaffected.`,
|