@oh-my-pi/pi-ai 16.3.5 → 16.3.7
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 +29 -0
- package/dist/types/api-registry.d.ts +1 -1
- package/dist/types/auth-broker/client.d.ts +4 -2
- package/dist/types/auth-broker/discover.d.ts +1 -1
- package/dist/types/auth-broker/index.d.ts +7 -7
- package/dist/types/auth-broker/refresher.d.ts +1 -1
- package/dist/types/auth-broker/remote-store.d.ts +11 -6
- package/dist/types/auth-broker/server.d.ts +1 -1
- package/dist/types/auth-broker/snapshot-cache.d.ts +1 -1
- package/dist/types/auth-broker/types.d.ts +14 -2
- package/dist/types/auth-broker/wire-schemas.d.ts +46 -0
- package/dist/types/auth-gateway/index.d.ts +3 -3
- package/dist/types/auth-gateway/server.d.ts +3 -3
- package/dist/types/auth-gateway/types.d.ts +1 -1
- package/dist/types/auth-retry.d.ts +2 -2
- package/dist/types/auth-storage.d.ts +44 -6
- package/dist/types/dialect/anthropic.d.ts +1 -1
- package/dist/types/dialect/catalog.d.ts +1 -1
- package/dist/types/dialect/coercion.d.ts +1 -1
- package/dist/types/dialect/deepseek.d.ts +1 -1
- package/dist/types/dialect/demotion.d.ts +16 -14
- package/dist/types/dialect/examples.d.ts +1 -1
- package/dist/types/dialect/factory.d.ts +1 -1
- package/dist/types/dialect/gemini.d.ts +1 -1
- package/dist/types/dialect/gemma.d.ts +1 -1
- package/dist/types/dialect/glm.d.ts +1 -1
- package/dist/types/dialect/harmony.d.ts +1 -1
- package/dist/types/dialect/hermes.d.ts +1 -1
- package/dist/types/dialect/history.d.ts +2 -2
- package/dist/types/dialect/index.d.ts +11 -11
- package/dist/types/dialect/inventory.d.ts +1 -1
- package/dist/types/dialect/kimi.d.ts +1 -1
- package/dist/types/dialect/minimax.d.ts +1 -1
- package/dist/types/dialect/owned-stream.d.ts +2 -2
- package/dist/types/dialect/qwen3.d.ts +1 -1
- package/dist/types/dialect/rendering.d.ts +2 -2
- package/dist/types/dialect/thinking.d.ts +1 -1
- package/dist/types/dialect/types.d.ts +1 -1
- package/dist/types/dialect/xml.d.ts +1 -1
- package/dist/types/error/classes.d.ts +1 -1
- package/dist/types/error/finalize.d.ts +3 -3
- package/dist/types/error/flags.d.ts +1 -1
- package/dist/types/error/format.d.ts +1 -1
- package/dist/types/error/index.d.ts +13 -13
- package/dist/types/error/provider.d.ts +1 -1
- package/dist/types/error/rate-limit.d.ts +1 -1
- package/dist/types/index.d.ts +47 -47
- package/dist/types/provider-details.d.ts +1 -1
- package/dist/types/providers/amazon-bedrock.d.ts +1 -1
- package/dist/types/providers/anthropic-client.d.ts +3 -3
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +1 -1
- package/dist/types/providers/anthropic-messages-server.d.ts +2 -2
- package/dist/types/providers/anthropic-wire.d.ts +1 -1
- package/dist/types/providers/anthropic.d.ts +3 -3
- package/dist/types/providers/aws-credentials.d.ts +2 -2
- package/dist/types/providers/azure-openai-responses.d.ts +2 -2
- package/dist/types/providers/cursor.d.ts +18 -3
- package/dist/types/providers/devin.d.ts +1 -1
- package/dist/types/providers/error-message.d.ts +1 -1
- package/dist/types/providers/github-copilot-headers.d.ts +1 -1
- package/dist/types/providers/gitlab-duo-workflow.d.ts +2 -2
- package/dist/types/providers/gitlab-duo.d.ts +2 -2
- package/dist/types/providers/google-auth.d.ts +1 -1
- package/dist/types/providers/google-gemini-cli.d.ts +6 -4
- package/dist/types/providers/google-interactions.d.ts +3 -3
- package/dist/types/providers/google-shared.d.ts +7 -5
- package/dist/types/providers/google-vertex.d.ts +2 -2
- package/dist/types/providers/google.d.ts +2 -2
- package/dist/types/providers/kimi.d.ts +3 -3
- package/dist/types/providers/mock.d.ts +2 -2
- package/dist/types/providers/ollama.d.ts +1 -1
- package/dist/types/providers/openai-anthropic-shim.d.ts +2 -2
- package/dist/types/providers/openai-chat-server-schema.d.ts +1 -1
- package/dist/types/providers/openai-chat-server.d.ts +2 -2
- package/dist/types/providers/openai-codex/request-transformer.d.ts +1 -1
- package/dist/types/providers/openai-codex/response-handler.d.ts +1 -1
- package/dist/types/providers/openai-codex-responses.d.ts +3 -3
- package/dist/types/providers/openai-completions.d.ts +3 -3
- package/dist/types/providers/openai-reasoning-fallback.d.ts +1 -1
- package/dist/types/providers/openai-responses-server-schema.d.ts +1 -1
- package/dist/types/providers/openai-responses-server.d.ts +2 -2
- package/dist/types/providers/openai-responses.d.ts +5 -5
- package/dist/types/providers/openai-shared.d.ts +10 -9
- package/dist/types/providers/pi-native-client.d.ts +1 -1
- package/dist/types/providers/pi-native-server.d.ts +2 -2
- package/dist/types/providers/register-builtins.d.ts +3 -3
- package/dist/types/providers/synthetic.d.ts +3 -3
- package/dist/types/providers/transform-messages.d.ts +1 -1
- package/dist/types/providers/vision-guard.d.ts +1 -1
- package/dist/types/registry/alibaba-coding-plan.d.ts +1 -1
- package/dist/types/registry/anthropic.d.ts +1 -1
- package/dist/types/registry/api-key-login.d.ts +1 -1
- package/dist/types/registry/api-key-validation.d.ts +1 -1
- package/dist/types/registry/baseten.d.ts +2 -2
- package/dist/types/registry/cerebras.d.ts +2 -2
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +1 -1
- package/dist/types/registry/coreweave.d.ts +2 -2
- package/dist/types/registry/cursor.d.ts +1 -1
- package/dist/types/registry/deepseek.d.ts +1 -1
- package/dist/types/registry/devin.d.ts +1 -1
- package/dist/types/registry/firepass.d.ts +2 -2
- package/dist/types/registry/fireworks.d.ts +2 -2
- package/dist/types/registry/github-copilot.d.ts +1 -1
- package/dist/types/registry/gitlab-duo-workflow.d.ts +1 -1
- package/dist/types/registry/gitlab-duo.d.ts +1 -1
- package/dist/types/registry/google-antigravity.d.ts +1 -1
- package/dist/types/registry/google-gemini-cli.d.ts +1 -1
- package/dist/types/registry/huggingface.d.ts +2 -2
- package/dist/types/registry/index.d.ts +4 -4
- package/dist/types/registry/kagi.d.ts +1 -1
- package/dist/types/registry/kilo.d.ts +1 -1
- package/dist/types/registry/kimi-code.d.ts +1 -1
- package/dist/types/registry/litellm.d.ts +1 -1
- package/dist/types/registry/llama-cpp.d.ts +1 -1
- package/dist/types/registry/lm-studio.d.ts +1 -1
- package/dist/types/registry/minimax-code-cn.d.ts +1 -1
- package/dist/types/registry/minimax-code.d.ts +1 -1
- package/dist/types/registry/moonshot.d.ts +2 -2
- package/dist/types/registry/nanogpt.d.ts +2 -2
- package/dist/types/registry/nvidia.d.ts +1 -1
- package/dist/types/registry/oauth/anthropic.d.ts +3 -3
- package/dist/types/registry/oauth/callback-server.d.ts +1 -1
- package/dist/types/registry/oauth/cursor.d.ts +1 -1
- package/dist/types/registry/oauth/devin.d.ts +1 -1
- package/dist/types/registry/oauth/github-copilot.d.ts +2 -2
- package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +2 -2
- package/dist/types/registry/oauth/gitlab-duo.d.ts +1 -1
- package/dist/types/registry/oauth/google-antigravity.d.ts +1 -1
- package/dist/types/registry/oauth/google-gemini-cli.d.ts +1 -1
- package/dist/types/registry/oauth/google-oauth-shared.d.ts +2 -2
- package/dist/types/registry/oauth/index.d.ts +2 -2
- package/dist/types/registry/oauth/kimi.d.ts +1 -1
- package/dist/types/registry/oauth/minimax-code.d.ts +2 -2
- package/dist/types/registry/oauth/openai-codex.d.ts +1 -1
- package/dist/types/registry/oauth/opencode.d.ts +1 -1
- package/dist/types/registry/oauth/perplexity.d.ts +1 -1
- package/dist/types/registry/oauth/types.d.ts +2 -2
- package/dist/types/registry/oauth/wafer.d.ts +1 -1
- package/dist/types/registry/oauth/xai-oauth.d.ts +3 -3
- package/dist/types/registry/oauth/xiaomi.d.ts +1 -1
- package/dist/types/registry/ollama-cloud.d.ts +1 -1
- package/dist/types/registry/ollama.d.ts +1 -1
- package/dist/types/registry/openai-codex-device.d.ts +1 -1
- package/dist/types/registry/openai-codex.d.ts +1 -1
- package/dist/types/registry/opencode-go.d.ts +1 -1
- package/dist/types/registry/opencode-zen.d.ts +1 -1
- package/dist/types/registry/openrouter.d.ts +2 -2
- package/dist/types/registry/parallel.d.ts +1 -1
- package/dist/types/registry/perplexity.d.ts +2 -2
- package/dist/types/registry/qianfan.d.ts +2 -2
- package/dist/types/registry/qwen-portal.d.ts +1 -1
- package/dist/types/registry/registry.d.ts +69 -69
- package/dist/types/registry/sakana.d.ts +2 -2
- package/dist/types/registry/synthetic.d.ts +2 -2
- package/dist/types/registry/tavily.d.ts +1 -1
- package/dist/types/registry/together.d.ts +1 -1
- package/dist/types/registry/types.d.ts +1 -1
- package/dist/types/registry/umans.d.ts +2 -2
- package/dist/types/registry/venice.d.ts +2 -2
- package/dist/types/registry/vercel-ai-gateway.d.ts +1 -1
- package/dist/types/registry/vllm.d.ts +1 -1
- package/dist/types/registry/wafer-serverless.d.ts +1 -1
- package/dist/types/registry/xai-oauth.d.ts +1 -1
- package/dist/types/registry/xiaomi-token-plan-ams.d.ts +1 -1
- package/dist/types/registry/xiaomi-token-plan-cn.d.ts +1 -1
- package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +1 -1
- package/dist/types/registry/xiaomi.d.ts +1 -1
- package/dist/types/registry/zai.d.ts +2 -2
- package/dist/types/registry/zenmux.d.ts +2 -2
- package/dist/types/registry/zhipu-coding-plan.d.ts +2 -2
- package/dist/types/stream.d.ts +6 -6
- package/dist/types/types.d.ts +40 -21
- package/dist/types/usage/claude.d.ts +1 -1
- package/dist/types/usage/gemini.d.ts +1 -1
- package/dist/types/usage/github-copilot.d.ts +1 -1
- package/dist/types/usage/google-antigravity.d.ts +1 -1
- package/dist/types/usage/kimi.d.ts +1 -1
- package/dist/types/usage/minimax-code.d.ts +1 -1
- package/dist/types/usage/ollama.d.ts +1 -1
- package/dist/types/usage/openai-codex-reset.d.ts +1 -1
- package/dist/types/usage/openai-codex.d.ts +1 -1
- package/dist/types/usage/opencode-go.d.ts +1 -1
- package/dist/types/usage/zai.d.ts +1 -1
- package/dist/types/usage.d.ts +1 -1
- package/dist/types/utils/block-symbols.d.ts +15 -0
- package/dist/types/utils/empty-completion-retry.d.ts +2 -2
- package/dist/types/utils/event-stream.d.ts +1 -1
- package/dist/types/utils/harmony-leak.d.ts +1 -1
- package/dist/types/utils/http-inspector.d.ts +19 -0
- package/dist/types/utils/leaked-thinking-stream.d.ts +1 -1
- package/dist/types/utils/openai-http.d.ts +3 -9
- package/dist/types/utils/provider-response.d.ts +1 -1
- package/dist/types/utils/proxy.d.ts +1 -1
- package/dist/types/utils/request-debug.d.ts +1 -1
- package/dist/types/utils/retry.d.ts +1 -1
- package/dist/types/utils/schema/equality.d.ts +1 -1
- package/dist/types/utils/schema/index.d.ts +15 -15
- package/dist/types/utils/schema/normalize.d.ts +7 -2
- package/dist/types/utils/schema/spill.d.ts +1 -1
- package/dist/types/utils/schema/wire.d.ts +1 -1
- package/dist/types/utils/sse-debug.d.ts +1 -1
- package/dist/types/utils/thinking-loop.d.ts +2 -2
- package/dist/types/utils/tool-call-loop-guard.d.ts +1 -1
- package/dist/types/utils/tool-choice.d.ts +1 -1
- package/dist/types/utils/validation.d.ts +1 -1
- package/dist/types/utils.d.ts +14 -2
- package/package.json +4 -4
- package/src/auth-broker/client.ts +41 -12
- package/src/auth-broker/remote-store.ts +189 -9
- package/src/auth-broker/server.ts +109 -7
- package/src/auth-broker/types.ts +22 -1
- package/src/auth-broker/wire-schemas.ts +22 -0
- package/src/auth-storage.ts +292 -22
- package/src/dialect/demotion.ts +18 -19
- package/src/error/rate-limit.ts +1 -1
- package/src/providers/azure-openai-responses.ts +3 -2
- package/src/providers/cursor.ts +36 -0
- package/src/providers/google-gemini-cli.ts +12 -3
- package/src/providers/google-shared.ts +3 -1
- package/src/providers/mock.ts +10 -3
- package/src/providers/ollama.ts +2 -2
- package/src/providers/openai-codex-responses.ts +47 -39
- package/src/providers/openai-completions.ts +48 -9
- package/src/providers/openai-responses.ts +37 -23
- package/src/providers/openai-shared.ts +145 -26
- package/src/providers/transform-messages.ts +63 -9
- package/src/registry/oauth/wafer.ts +1 -1
- package/src/stream.ts +6 -0
- package/src/types.ts +45 -3
- package/src/usage/claude.ts +28 -8
- package/src/utils/block-symbols.ts +18 -0
- package/src/utils/http-inspector.ts +32 -4
- package/src/utils/openai-http.ts +6 -9
- package/src/utils/retry-after.ts +12 -1
- package/src/utils/schema/normalize.ts +131 -0
- package/src/utils.ts +78 -0
package/src/providers/cursor.ts
CHANGED
|
@@ -1111,6 +1111,17 @@ async function handleExecServerMessage(
|
|
|
1111
1111
|
case "grepArgs": {
|
|
1112
1112
|
const args = execMsg.message.value;
|
|
1113
1113
|
if (!args.toolCallId) args.toolCallId = crypto.randomUUID();
|
|
1114
|
+
// Cursor's model sometimes emits `grepArgs` with an empty `pattern` and a
|
|
1115
|
+
// non-empty `glob`, expecting grep to list files matching the glob. Reject
|
|
1116
|
+
// that up front with an actionable error so the model retries with a real
|
|
1117
|
+
// regex or switches to `ls`/`read`, instead of the local grep tool
|
|
1118
|
+
// surfacing a bare "Pattern must not be empty" (issue #4574) after the
|
|
1119
|
+
// synthesized block has already been persisted with a placeholder pattern.
|
|
1120
|
+
const emptyPatternError = emptyGrepPatternRejection(args.pattern, args.glob);
|
|
1121
|
+
if (emptyPatternError !== null) {
|
|
1122
|
+
sendExecClientMessage(h2Request, execMsg, "grepResult", buildGrepErrorResult(emptyPatternError));
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1114
1125
|
// Mirror the coding-agent bridge's arg mapping so live UI (from
|
|
1115
1126
|
// `tool_execution_start`) and rebuilt transcript (from this block)
|
|
1116
1127
|
// display identical args.
|
|
@@ -1835,6 +1846,31 @@ function buildGrepErrorResult(error: string) {
|
|
|
1835
1846
|
});
|
|
1836
1847
|
}
|
|
1837
1848
|
|
|
1849
|
+
/**
|
|
1850
|
+
* Reject a Cursor exec-channel `grepArgs` frame whose `pattern` is empty or
|
|
1851
|
+
* whitespace-only. Returns an actionable error message when the pattern is
|
|
1852
|
+
* unusable (with a `glob`-aware hint when the model likely meant to list
|
|
1853
|
+
* files), or `null` when the pattern is valid and grep should run.
|
|
1854
|
+
*
|
|
1855
|
+
* Exported for tests. Cursor's model sometimes sends `pattern=""` together
|
|
1856
|
+
* with a non-empty `glob`, expecting grep to enumerate matching files; the
|
|
1857
|
+
* downstream coding-agent `grep` tool rejects that with a bare "Pattern must
|
|
1858
|
+
* not be empty", which the TUI renders as `?` in the tool preview (issue
|
|
1859
|
+
* #4574). Handling it at the Cursor exec dispatch keeps the synthesized
|
|
1860
|
+
* `toolCall` block off the persisted assistant message and gives the model a
|
|
1861
|
+
* specific recovery hint.
|
|
1862
|
+
*/
|
|
1863
|
+
export function emptyGrepPatternRejection(pattern: string | undefined, glob: string | undefined): string | null {
|
|
1864
|
+
if (pattern && pattern.trim().length > 0) return null;
|
|
1865
|
+
if (glob && glob.length > 0) {
|
|
1866
|
+
return (
|
|
1867
|
+
`grep pattern is required (received an empty pattern). To list files matching "${glob}", ` +
|
|
1868
|
+
`pass a non-empty regex (e.g. ".") and set path to that glob, or use the ls/read tool instead.`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
return "grep pattern is required (received an empty pattern).";
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1838
1874
|
function buildDiagnosticsResultFromToolResult(path: string, toolResult: ToolResultMessage) {
|
|
1839
1875
|
const text = toolResultToText(toolResult);
|
|
1840
1876
|
if (toolResult.isError) {
|
|
@@ -263,6 +263,8 @@ export interface GoogleGeminiCliOptions extends StreamOptions {
|
|
|
263
263
|
*/
|
|
264
264
|
suppress?: { level: GoogleThinkingLevel } | { budget: number };
|
|
265
265
|
};
|
|
266
|
+
/** Request that Cloud Code Assist omit human-readable thought summaries while still allowing internal reasoning. */
|
|
267
|
+
hideThinkingSummary?: boolean;
|
|
266
268
|
/**
|
|
267
269
|
* Upstream wire model id override for collapsed effort-tier variants.
|
|
268
270
|
* Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
|
|
@@ -1002,8 +1004,8 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
1002
1004
|
}
|
|
1003
1005
|
|
|
1004
1006
|
const streamed = await streamResponse(currentResponse);
|
|
1005
|
-
if (streamed) {
|
|
1006
|
-
receivedContent =
|
|
1007
|
+
if (output.stopReason !== "stop" || streamed) {
|
|
1008
|
+
receivedContent = streamed;
|
|
1007
1009
|
break;
|
|
1008
1010
|
}
|
|
1009
1011
|
|
|
@@ -1012,6 +1014,13 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
1012
1014
|
}
|
|
1013
1015
|
}
|
|
1014
1016
|
|
|
1017
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
1018
|
+
throw new AIError.ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
|
|
1019
|
+
provider: model.provider,
|
|
1020
|
+
kind: "output",
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1015
1024
|
if (!receivedContent) {
|
|
1016
1025
|
throw new AIError.ProviderResponseError("Cloud Code Assist API returned an empty response", {
|
|
1017
1026
|
provider: model.provider,
|
|
@@ -1242,7 +1251,7 @@ export function buildRequest(
|
|
|
1242
1251
|
// Thinking config
|
|
1243
1252
|
if (options.thinking?.enabled && model.reasoning) {
|
|
1244
1253
|
generationConfig.thinkingConfig = {
|
|
1245
|
-
includeThoughts:
|
|
1254
|
+
includeThoughts: !options.hideThinkingSummary,
|
|
1246
1255
|
};
|
|
1247
1256
|
// Gemini 3 models use thinkingLevel, older models use thinkingBudget
|
|
1248
1257
|
if (options.thinking.level !== undefined) {
|
|
@@ -75,6 +75,8 @@ export interface GoogleSharedStreamOptions extends StreamOptions {
|
|
|
75
75
|
budgetTokens?: number;
|
|
76
76
|
level?: GoogleThinkingLevel;
|
|
77
77
|
};
|
|
78
|
+
/** Request that Google omit human-readable thought summaries while still allowing internal reasoning. */
|
|
79
|
+
hideThinkingSummary?: boolean;
|
|
78
80
|
/** Gemini/Vertex serving tier (`flex`/`priority`); other values are omitted. */
|
|
79
81
|
serviceTier?: ServiceTier;
|
|
80
82
|
/**
|
|
@@ -859,7 +861,7 @@ export function buildGoogleGenerateContentParams<T extends "google-generative-ai
|
|
|
859
861
|
}
|
|
860
862
|
|
|
861
863
|
if (options.thinking?.enabled && model.reasoning) {
|
|
862
|
-
const cfg: ThinkingConfig = { includeThoughts:
|
|
864
|
+
const cfg: ThinkingConfig = { includeThoughts: !options.hideThinkingSummary };
|
|
863
865
|
if (options.thinking.level !== undefined) {
|
|
864
866
|
// GoogleThinkingLevel mirrors the SDK's `ThinkingLevel` string enum values 1:1.
|
|
865
867
|
cfg.thinkingLevel = options.thinking.level as ThinkingLevel;
|
package/src/providers/mock.ts
CHANGED
|
@@ -446,10 +446,17 @@ function mergeUsage(partial?: Partial<Omit<Usage, "cost">> & { cost?: Partial<Us
|
|
|
446
446
|
if (costProvided) {
|
|
447
447
|
merged.cost = { ...base.cost, ...partial.cost } as Usage["cost"];
|
|
448
448
|
}
|
|
449
|
-
// Recompute totalTokens when not explicitly provided (canonical formula matches types.ts
|
|
450
|
-
// input + output + cacheRead + cacheWrite).
|
|
449
|
+
// Recompute totalTokens when not explicitly provided (canonical formula matches types.ts).
|
|
451
450
|
if (partial.totalTokens === undefined) {
|
|
452
|
-
|
|
451
|
+
const orchestration = merged.orchestration;
|
|
452
|
+
merged.totalTokens =
|
|
453
|
+
merged.input +
|
|
454
|
+
merged.output +
|
|
455
|
+
merged.cacheRead +
|
|
456
|
+
merged.cacheWrite +
|
|
457
|
+
(orchestration?.input ?? 0) +
|
|
458
|
+
(orchestration?.output ?? 0) +
|
|
459
|
+
(orchestration?.cacheRead ?? 0);
|
|
453
460
|
}
|
|
454
461
|
// Recompute cost.total when cost components were supplied without an explicit total.
|
|
455
462
|
if (costProvided && partial.cost?.total === undefined) {
|
package/src/providers/ollama.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
getOpenAIStreamFirstEventTimeoutMs,
|
|
24
24
|
getOpenAIStreamIdleTimeoutMs,
|
|
25
25
|
} from "../utils/idle-iterator";
|
|
26
|
-
import { toolWireSchema } from "../utils/schema
|
|
26
|
+
import { sanitizeSchemaForOllama, toolWireSchema } from "../utils/schema";
|
|
27
27
|
import {
|
|
28
28
|
getStreamMarkupHealingPattern,
|
|
29
29
|
type HealedToolCall,
|
|
@@ -280,7 +280,7 @@ function convertTools(tools: Tool[] | undefined): OllamaFunctionTool[] | undefin
|
|
|
280
280
|
function: {
|
|
281
281
|
name: tool.name,
|
|
282
282
|
description: tool.description,
|
|
283
|
-
parameters: toolWireSchema(tool),
|
|
283
|
+
parameters: sanitizeSchemaForOllama(toolWireSchema(tool)),
|
|
284
284
|
},
|
|
285
285
|
}));
|
|
286
286
|
}
|
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
getOpenAIResponsesHistoryItems,
|
|
44
44
|
getOpenAIResponsesHistoryPayload,
|
|
45
45
|
normalizeSystemPrompts,
|
|
46
|
+
sanitizeOpenAIResponsesAssistantFallbackItemsForReplay,
|
|
47
|
+
sanitizeOpenAIResponsesAssistantHistoryItemsForReplay,
|
|
46
48
|
} from "../utils";
|
|
47
49
|
import { clearStreamingPartialJson, kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-symbols";
|
|
48
50
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
@@ -286,7 +288,7 @@ interface CodexProviderSessionState extends ProviderSessionState {
|
|
|
286
288
|
|
|
287
289
|
interface CodexRequestContext {
|
|
288
290
|
apiKey: string;
|
|
289
|
-
accountId
|
|
291
|
+
accountId?: string;
|
|
290
292
|
baseUrl: string;
|
|
291
293
|
url: string;
|
|
292
294
|
requestHeaders: Record<string, string>;
|
|
@@ -834,7 +836,7 @@ async function buildCodexRequestContext(
|
|
|
834
836
|
throw new AIError.MissingApiKeyError(model.provider);
|
|
835
837
|
}
|
|
836
838
|
|
|
837
|
-
const accountId =
|
|
839
|
+
const accountId = getCodexAccountId(apiKey);
|
|
838
840
|
const baseUrl = model.baseUrl || CODEX_BASE_URL;
|
|
839
841
|
const url = resolveCodexResponsesUrl(baseUrl);
|
|
840
842
|
const promptCacheKey = normalizeOpenAIResponsesPromptCacheKey(options?.promptCacheKey ?? options?.sessionId);
|
|
@@ -853,7 +855,7 @@ async function buildCodexRequestContext(
|
|
|
853
855
|
|
|
854
856
|
const providerSessionState = getCodexProviderSessionState(options?.providerSessionState);
|
|
855
857
|
const responsesLite = shouldUseCodexResponsesLite(transformedBody, options?.responsesLite);
|
|
856
|
-
const sessionKey = getCodexWebSocketSessionKey(transportSessionId, model, accountId, baseUrl, responsesLite);
|
|
858
|
+
const sessionKey = getCodexWebSocketSessionKey(transportSessionId, model, accountId, apiKey, baseUrl, responsesLite);
|
|
857
859
|
const publicSessionKey = transportSessionId ? `${baseUrl}:${model.id}:${transportSessionId}` : undefined;
|
|
858
860
|
if (sessionKey && publicSessionKey) {
|
|
859
861
|
providerSessionState?.webSocketPublicToPrivate.set(publicSessionKey, sessionKey);
|
|
@@ -902,7 +904,7 @@ export async function buildTransformedCodexRequestBody(
|
|
|
902
904
|
// `{"detail":"Unsupported parameter: temperature"}` etc., so we drop
|
|
903
905
|
// everything from `StreamOptions` rather than forwarding any of them.
|
|
904
906
|
// (#3117 — codex-rs sends none of these either.)
|
|
905
|
-
applyOpenAIServiceTier(params, options?.serviceTier, model
|
|
907
|
+
applyOpenAIServiceTier(params, options?.serviceTier, model);
|
|
906
908
|
if (context.tools && context.tools.length > 0) {
|
|
907
909
|
params.tools = convertOpenAICodexResponsesTools(context.tools, model);
|
|
908
910
|
if (options?.toolChoice) {
|
|
@@ -1531,8 +1533,15 @@ class CodexStreamProcessor {
|
|
|
1531
1533
|
input_tokens?: number;
|
|
1532
1534
|
output_tokens?: number;
|
|
1533
1535
|
total_tokens?: number;
|
|
1534
|
-
input_tokens_details?: {
|
|
1535
|
-
|
|
1536
|
+
input_tokens_details?: {
|
|
1537
|
+
cached_tokens?: number;
|
|
1538
|
+
orchestration_input_tokens?: number;
|
|
1539
|
+
orchestration_input_cached_tokens?: number;
|
|
1540
|
+
};
|
|
1541
|
+
output_tokens_details?: {
|
|
1542
|
+
reasoning_tokens?: number;
|
|
1543
|
+
orchestration_output_tokens?: number;
|
|
1544
|
+
};
|
|
1536
1545
|
};
|
|
1537
1546
|
status?: string;
|
|
1538
1547
|
service_tier?: ServiceTier | "default";
|
|
@@ -2055,14 +2064,14 @@ export async function prewarmOpenAICodexResponses(
|
|
|
2055
2064
|
): Promise<void> {
|
|
2056
2065
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
2057
2066
|
if (!apiKey) return;
|
|
2058
|
-
const accountId =
|
|
2067
|
+
const accountId = getCodexAccountId(apiKey);
|
|
2059
2068
|
const baseUrl = model.baseUrl || CODEX_BASE_URL;
|
|
2060
2069
|
const url = resolveCodexResponsesUrl(baseUrl);
|
|
2061
2070
|
const transportSessionId = normalizeOpenAIResponsesPromptCacheKey(options?.sessionId);
|
|
2062
2071
|
const promptCacheKey = transportSessionId;
|
|
2063
2072
|
const providerSessionState = getCodexProviderSessionState(options?.providerSessionState);
|
|
2064
2073
|
const responsesLite = options?.responsesLite === true;
|
|
2065
|
-
const sessionKey = getCodexWebSocketSessionKey(transportSessionId, model, accountId, baseUrl, responsesLite);
|
|
2074
|
+
const sessionKey = getCodexWebSocketSessionKey(transportSessionId, model, accountId, apiKey, baseUrl, responsesLite);
|
|
2066
2075
|
const publicSessionKey = transportSessionId ? `${baseUrl}:${model.id}:${transportSessionId}` : undefined;
|
|
2067
2076
|
if (publicSessionKey && sessionKey) {
|
|
2068
2077
|
providerSessionState?.webSocketPublicToPrivate.set(publicSessionKey, sessionKey);
|
|
@@ -2095,15 +2104,17 @@ export async function prewarmOpenAICodexResponses(
|
|
|
2095
2104
|
function getCodexWebSocketSessionKey(
|
|
2096
2105
|
normalizedSessionId: string | undefined,
|
|
2097
2106
|
model: Model<"openai-codex-responses">,
|
|
2098
|
-
accountId: string,
|
|
2107
|
+
accountId: string | undefined,
|
|
2108
|
+
apiKey: string,
|
|
2099
2109
|
baseUrl: string,
|
|
2100
2110
|
responsesLite: boolean,
|
|
2101
2111
|
): string | undefined {
|
|
2102
2112
|
if (!normalizedSessionId) return undefined;
|
|
2113
|
+
const credentialKey = accountId ? `account:${accountId}` : `token:${Bun.hash(apiKey).toString(36)}`;
|
|
2103
2114
|
// Responses Lite is connection-scoped on the WebSocket upgrade, so lite and
|
|
2104
2115
|
// non-lite turns must never share a pooled socket or append state.
|
|
2105
2116
|
const liteSuffix = responsesLite ? ":lite" : "";
|
|
2106
|
-
return `${
|
|
2117
|
+
return `${credentialKey}:${baseUrl}:${model.id}:${normalizedSessionId}${liteSuffix}`;
|
|
2107
2118
|
}
|
|
2108
2119
|
|
|
2109
2120
|
function getCodexWebSocketSessionState(
|
|
@@ -2979,7 +2990,7 @@ async function getOrCreateCodexWebSocketConnection(
|
|
|
2979
2990
|
async function openCodexSseEventStream(
|
|
2980
2991
|
url: string,
|
|
2981
2992
|
requestHeaders: Record<string, string> | undefined,
|
|
2982
|
-
accountId: string,
|
|
2993
|
+
accountId: string | undefined,
|
|
2983
2994
|
apiKey: string,
|
|
2984
2995
|
sessionId: string | undefined,
|
|
2985
2996
|
body: RequestBody,
|
|
@@ -3044,7 +3055,7 @@ async function openCodexSseEventStream(
|
|
|
3044
3055
|
|
|
3045
3056
|
function createCodexHeaders(
|
|
3046
3057
|
initHeaders: Record<string, string> | undefined,
|
|
3047
|
-
accountId: string,
|
|
3058
|
+
accountId: string | undefined,
|
|
3048
3059
|
accessToken: string,
|
|
3049
3060
|
sessionId?: string,
|
|
3050
3061
|
transport: CodexTransport = "sse",
|
|
@@ -3054,7 +3065,7 @@ function createCodexHeaders(
|
|
|
3054
3065
|
const headers = new Headers(initHeaders ?? {});
|
|
3055
3066
|
headers.delete("x-api-key");
|
|
3056
3067
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
3057
|
-
headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
|
|
3068
|
+
if (accountId) headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
|
|
3058
3069
|
const betaHeader =
|
|
3059
3070
|
transport === "websocket"
|
|
3060
3071
|
? OPENAI_HEADER_VALUES.BETA_RESPONSES_WEBSOCKETS_V2
|
|
@@ -3129,17 +3140,6 @@ function resolveCodexResponsesUrl(baseUrl: string | undefined): string {
|
|
|
3129
3140
|
return `${normalized}/codex/responses`;
|
|
3130
3141
|
}
|
|
3131
3142
|
|
|
3132
|
-
function getAccountId(accessToken: string): string {
|
|
3133
|
-
const accountId = getCodexAccountId(accessToken);
|
|
3134
|
-
if (!accountId) {
|
|
3135
|
-
throw new AIError.OAuthError("Failed to extract accountId from token", {
|
|
3136
|
-
kind: "validation",
|
|
3137
|
-
provider: "openai",
|
|
3138
|
-
});
|
|
3139
|
-
}
|
|
3140
|
-
return accountId;
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
3143
|
function convertMessages(model: Model<"openai-codex-responses">, context: Context): ResponseInput {
|
|
3144
3144
|
const messages: ResponseInput = [];
|
|
3145
3145
|
|
|
@@ -3200,32 +3200,40 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
|
|
|
3200
3200
|
assistantMsg.api === model.api && assistantMsg.model === model.id
|
|
3201
3201
|
? getOpenAIResponsesHistoryPayload(assistantMsg.providerPayload, model.provider, assistantMsg.provider)
|
|
3202
3202
|
: undefined;
|
|
3203
|
-
const historyItems = providerPayload?.items as Array<
|
|
3203
|
+
const historyItems = providerPayload?.items as Array<Record<string, unknown>> | undefined;
|
|
3204
|
+
let suppressHiddenEmptyFallback = false;
|
|
3204
3205
|
if (historyItems) {
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3206
|
+
const sanitizedHistoryItems = sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(historyItems);
|
|
3207
|
+
if (sanitizedHistoryItems) {
|
|
3208
|
+
for (const item of sanitizedHistoryItems) {
|
|
3209
|
+
const maybe = item as { type?: string; call_id?: string };
|
|
3210
|
+
if (maybe.type === "custom_tool_call" && typeof maybe.call_id === "string") {
|
|
3211
|
+
customCallIds.add(maybe.call_id);
|
|
3212
|
+
}
|
|
3209
3213
|
}
|
|
3214
|
+
if (providerPayload?.dt) {
|
|
3215
|
+
messages.push(...sanitizedHistoryItems);
|
|
3216
|
+
} else {
|
|
3217
|
+
messages.splice(0, messages.length, ...sanitizedHistoryItems);
|
|
3218
|
+
// Keep customCallIds from the pre-splice state since historyItems may re-introduce them.
|
|
3219
|
+
}
|
|
3220
|
+
msgIndex += 1;
|
|
3221
|
+
continue;
|
|
3210
3222
|
}
|
|
3211
|
-
|
|
3212
|
-
messages.push(...historyItems);
|
|
3213
|
-
} else {
|
|
3214
|
-
messages.splice(0, messages.length, ...historyItems);
|
|
3215
|
-
// Keep customCallIds from the pre-splice state since historyItems may re-introduce them.
|
|
3216
|
-
}
|
|
3217
|
-
msgIndex += 1;
|
|
3218
|
-
continue;
|
|
3223
|
+
suppressHiddenEmptyFallback = true;
|
|
3219
3224
|
}
|
|
3220
3225
|
|
|
3221
|
-
const
|
|
3226
|
+
const convertedOutputItems = convertResponsesAssistantMessage(
|
|
3222
3227
|
msg as AssistantMessage,
|
|
3223
3228
|
model,
|
|
3224
3229
|
msgIndex,
|
|
3225
3230
|
knownCallIds,
|
|
3226
|
-
|
|
3231
|
+
!suppressHiddenEmptyFallback,
|
|
3227
3232
|
customCallIds,
|
|
3228
3233
|
);
|
|
3234
|
+
const outputItems = suppressHiddenEmptyFallback
|
|
3235
|
+
? sanitizeOpenAIResponsesAssistantFallbackItemsForReplay(convertedOutputItems)
|
|
3236
|
+
: convertedOutputItems;
|
|
3229
3237
|
if (outputItems.length > 0) {
|
|
3230
3238
|
messages.push(...outputItems);
|
|
3231
3239
|
}
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
} from "../types";
|
|
30
30
|
import { normalizeSystemPrompts } from "../utils";
|
|
31
31
|
import { createAbortSourceTracker } from "../utils/abort";
|
|
32
|
-
import { kStreamingLastParseLen } from "../utils/block-symbols";
|
|
32
|
+
import { isDemotedThinking, kStreamingLastParseLen } from "../utils/block-symbols";
|
|
33
33
|
import { hasVisibleAssistantContent, withEmptyCompletionRetry } from "../utils/empty-completion-retry";
|
|
34
34
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
35
35
|
import type { RawHttpRequestDump } from "../utils/http-inspector";
|
|
@@ -684,9 +684,10 @@ const streamOpenAICompletionsOnce = (
|
|
|
684
684
|
body: params,
|
|
685
685
|
signal: requestSignal,
|
|
686
686
|
fetch: options?.fetch,
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
|
|
687
|
+
// Transient 408/429/5xx get Retry-After-aware transport retries.
|
|
688
|
+
// The first-event watchdog above aborts `requestSignal`, which
|
|
689
|
+
// bounds every attempt and backoff sleep — retries cannot
|
|
690
|
+
// extend the deadline.
|
|
690
691
|
onSseEvent: rawSseObserver,
|
|
691
692
|
});
|
|
692
693
|
await notifyProviderResponse(options, response, model, requestId);
|
|
@@ -759,6 +760,16 @@ const streamOpenAICompletionsOnce = (
|
|
|
759
760
|
type OpenAIStreamBlock = TextContent | ThinkingContent | ToolCallStreamBlock;
|
|
760
761
|
const pendingToolCallBlocks: ToolCallStreamBlock[] = [];
|
|
761
762
|
const toolCallBlockByIndex = new Map<number, ToolCallStreamBlock>();
|
|
763
|
+
// Blocks born from an unkeyed multi-entry `tool_calls` array (no `id`,
|
|
764
|
+
// no `index`), tracked by array offset so continuation chunks that omit
|
|
765
|
+
// the entry name still route back to the sibling created earlier
|
|
766
|
+
// instead of collapsing onto `currentBlock`.
|
|
767
|
+
const unkeyedBatchBlocks: (ToolCallStreamBlock | undefined)[] = [];
|
|
768
|
+
const clearUnkeyedBatchSlot = (block: ToolCallStreamBlock): void => {
|
|
769
|
+
for (let index = 0; index < unkeyedBatchBlocks.length; index++) {
|
|
770
|
+
if (unkeyedBatchBlocks[index] === block) unkeyedBatchBlocks[index] = undefined;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
762
773
|
let currentBlock: OpenAIStreamBlock | undefined;
|
|
763
774
|
const blockIndex = (block: OpenAIStreamBlock | undefined): number => {
|
|
764
775
|
if (!block) return Math.max(0, output.content.length - 1);
|
|
@@ -791,6 +802,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
791
802
|
}
|
|
792
803
|
const pendingIndex = pendingToolCallBlocks.indexOf(block);
|
|
793
804
|
if (pendingIndex >= 0) pendingToolCallBlocks.splice(pendingIndex, 1);
|
|
805
|
+
clearUnkeyedBatchSlot(block);
|
|
794
806
|
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial: output });
|
|
795
807
|
};
|
|
796
808
|
const finishPendingToolCallBlocks = (): void => {
|
|
@@ -1092,14 +1104,27 @@ const streamOpenAICompletionsOnce = (
|
|
|
1092
1104
|
}
|
|
1093
1105
|
|
|
1094
1106
|
if (choice?.delta?.tool_calls && choice.delta.tool_calls.length > 0) {
|
|
1095
|
-
|
|
1107
|
+
const toolCalls = choice.delta.tool_calls;
|
|
1108
|
+
for (let toolCallOffset = 0; toolCallOffset < toolCalls.length; toolCallOffset++) {
|
|
1109
|
+
const toolCall = toolCalls[toolCallOffset]!;
|
|
1096
1110
|
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : undefined;
|
|
1111
|
+
const incomingName = toolCall.function?.name || "";
|
|
1112
|
+
// Multi-entry `tool_calls` arrays without `id`/`index` — either the
|
|
1113
|
+
// opening chunk that carries per-entry names, or a continuation whose
|
|
1114
|
+
// entries are argument-only. Either way, route by array offset so
|
|
1115
|
+
// sibling calls stay isolated.
|
|
1116
|
+
const unkeyedBatchedArrayEntry = toolCalls.length > 1 && streamIndex === undefined && !toolCall.id;
|
|
1097
1117
|
let block = streamIndex !== undefined ? toolCallBlockByIndex.get(streamIndex) : undefined;
|
|
1098
1118
|
if (!block && toolCall.id) {
|
|
1099
1119
|
block = pendingToolCallBlocks.find(candidate => candidate.id === toolCall.id);
|
|
1100
1120
|
}
|
|
1121
|
+
if (!block && unkeyedBatchedArrayEntry) {
|
|
1122
|
+
const offsetBlock = unkeyedBatchBlocks[toolCallOffset];
|
|
1123
|
+
if (offsetBlock && offsetBlock.partialArgs !== undefined) block = offsetBlock;
|
|
1124
|
+
}
|
|
1101
1125
|
if (
|
|
1102
1126
|
!block &&
|
|
1127
|
+
!unkeyedBatchedArrayEntry &&
|
|
1103
1128
|
currentBlock?.type === "toolCall" &&
|
|
1104
1129
|
(!toolCall.id || currentBlock.id === toolCall.id)
|
|
1105
1130
|
) {
|
|
@@ -1113,7 +1138,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
1113
1138
|
block = {
|
|
1114
1139
|
type: "toolCall",
|
|
1115
1140
|
id: toolCall.id || "",
|
|
1116
|
-
name:
|
|
1141
|
+
name: incomingName,
|
|
1117
1142
|
arguments: {},
|
|
1118
1143
|
partialArgs: "",
|
|
1119
1144
|
streamIndex,
|
|
@@ -1127,6 +1152,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
1127
1152
|
contentIndex: blockIndex(block),
|
|
1128
1153
|
partial: output,
|
|
1129
1154
|
});
|
|
1155
|
+
if (unkeyedBatchedArrayEntry) unkeyedBatchBlocks[toolCallOffset] = block;
|
|
1130
1156
|
} else {
|
|
1131
1157
|
// Resuming a pending call after interleaved text/thinking:
|
|
1132
1158
|
// close the text/thinking block we drifted into.
|
|
@@ -1141,7 +1167,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
1141
1167
|
}
|
|
1142
1168
|
|
|
1143
1169
|
if (toolCall.id) block.id = toolCall.id;
|
|
1144
|
-
if (
|
|
1170
|
+
if (incomingName) block.name = incomingName;
|
|
1145
1171
|
let delta = "";
|
|
1146
1172
|
// The OpenAI SDK types `function.arguments` as a JSON string, but MiniMax-compatible
|
|
1147
1173
|
// hosts stream a fully-formed object instead. Model both shapes so the branches below
|
|
@@ -1433,7 +1459,7 @@ function buildParams(
|
|
|
1433
1459
|
if (options?.frequencyPenalty !== undefined) {
|
|
1434
1460
|
params.frequency_penalty = options.frequencyPenalty;
|
|
1435
1461
|
}
|
|
1436
|
-
applyOpenAIServiceTier(params, options?.serviceTier, model
|
|
1462
|
+
applyOpenAIServiceTier(params, options?.serviceTier, model);
|
|
1437
1463
|
|
|
1438
1464
|
if (context.tools?.length) {
|
|
1439
1465
|
const builtTools = convertTools(context.tools, initialCompat, toolStrictModeOverride);
|
|
@@ -1778,7 +1804,20 @@ export function convertMessages(
|
|
|
1778
1804
|
// Always send assistant content as a plain string. Some OpenAI-compatible
|
|
1779
1805
|
// backends mirror array-of-text-block payloads back to the model literally,
|
|
1780
1806
|
// causing recursive nested content in subsequent turns.
|
|
1781
|
-
|
|
1807
|
+
// Join ordinary adjacent text blocks with no separator so bridge
|
|
1808
|
+
// stitching, imported transcripts, and streaming chunks keep their
|
|
1809
|
+
// original byte sequence. Demoted-thinking blocks (kDemotedThinking,
|
|
1810
|
+
// synthesized by transformMessages) are the one exception: bare
|
|
1811
|
+
// Anthropic-dialect reasoning would otherwise glue onto the first word
|
|
1812
|
+
// of the visible answer. Insert a paragraph break after them — only
|
|
1813
|
+
// when another block actually follows, so a trailing demoted block
|
|
1814
|
+
// never ships trailing whitespace.
|
|
1815
|
+
assistantMsg.content = nonEmptyTextBlocks
|
|
1816
|
+
.map((b, i) => {
|
|
1817
|
+
const text = b.text.toWellFormed();
|
|
1818
|
+
return isDemotedThinking(b) && i < nonEmptyTextBlocks.length - 1 ? `${text}\n` : text;
|
|
1819
|
+
})
|
|
1820
|
+
.join("");
|
|
1782
1821
|
}
|
|
1783
1822
|
|
|
1784
1823
|
// Handle thinking blocks
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
createOpenAIResponsesHistoryPayload,
|
|
21
21
|
normalizeSystemPrompts,
|
|
22
22
|
resolveCacheRetention,
|
|
23
|
-
|
|
23
|
+
sanitizeOpenAIResponsesAssistantHistoryItemsForReplay,
|
|
24
24
|
} from "../utils";
|
|
25
25
|
import { createAbortSourceTracker } from "../utils/abort";
|
|
26
26
|
import { withEmptyCompletionRetry } from "../utils/empty-completion-retry";
|
|
@@ -487,9 +487,9 @@ const streamOpenAIResponsesOnce = (
|
|
|
487
487
|
body: requestParams,
|
|
488
488
|
signal: requestSignal,
|
|
489
489
|
fetch: options?.fetch,
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
|
|
490
|
+
// Transient 408/429/5xx get Retry-After-aware transport
|
|
491
|
+
// retries; the first-event watchdog aborts `requestSignal`,
|
|
492
|
+
// so retries cannot extend the caller's deadline.
|
|
493
493
|
onSseEvent: rawSseObserver,
|
|
494
494
|
});
|
|
495
495
|
// Disarm the first-event watchdog as soon as headers arrive — a slow
|
|
@@ -687,22 +687,33 @@ const streamOpenAIResponsesOnce = (
|
|
|
687
687
|
}
|
|
688
688
|
|
|
689
689
|
output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, nativeOutputItems);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
690
|
+
const replayableResponseItems = sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(
|
|
691
|
+
structuredCloneJSON(nativeOutputItems),
|
|
692
|
+
);
|
|
693
|
+
if (replayableResponseItems) {
|
|
694
|
+
if (providerSessionState) providerSessionState.nativeHistoryReplayWarmed = true;
|
|
695
|
+
if (chainState) {
|
|
696
|
+
chainState.lastParams = structuredCloneJSON(activeParams);
|
|
697
|
+
if (output.responseId) {
|
|
698
|
+
chainState.lastResponseId = output.responseId;
|
|
699
|
+
chainState.lastResponseItems = replayableResponseItems;
|
|
700
|
+
chainState.canAppend = true;
|
|
701
|
+
// Only a successful CHAINED completion clears the stale counter — a
|
|
702
|
+
// full-context success must not mask categorical rejection.
|
|
703
|
+
if (sentPreviousResponseId) chainState.staleFailures = 0;
|
|
704
|
+
} else {
|
|
705
|
+
// Without a response id the append baseline cannot be trusted.
|
|
706
|
+
chainState.canAppend = false;
|
|
707
|
+
}
|
|
705
708
|
}
|
|
709
|
+
} else if (chainState) {
|
|
710
|
+
// Hidden-empty / fully sanitized successes cannot be used as an append
|
|
711
|
+
// baseline, but `lastParams` still records the successful wire controls
|
|
712
|
+
// without re-enabling `previous_response_id` chaining.
|
|
713
|
+
chainState.canAppend = false;
|
|
714
|
+
chainState.lastParams = structuredCloneJSON(activeParams);
|
|
715
|
+
chainState.lastResponseId = undefined;
|
|
716
|
+
chainState.lastResponseItems = undefined;
|
|
706
717
|
}
|
|
707
718
|
|
|
708
719
|
output.duration = performance.now() - startTime;
|
|
@@ -854,7 +865,7 @@ export function buildParams(
|
|
|
854
865
|
if (context.tools) {
|
|
855
866
|
const disableStrictTools =
|
|
856
867
|
disableStrictToolsOverride || isStrictToolsDisabledForScope(providerSessionState, strictToolsScope);
|
|
857
|
-
const strictMode = !disableStrictTools && model.compat.supportsStrictMode;
|
|
868
|
+
const strictMode = !disableStrictTools && model.compat.supportsStrictMode !== false;
|
|
858
869
|
params.tools = convertTools(context.tools, strictMode, model);
|
|
859
870
|
strictToolsApplied = params.tools.some(t => (t as { strict?: boolean }).strict === true);
|
|
860
871
|
if (options?.toolChoice) {
|
|
@@ -989,9 +1000,12 @@ export function convertTools(
|
|
|
989
1000
|
parameters,
|
|
990
1001
|
// `strict: false` and an omitted `strict` are NOT equivalent for every
|
|
991
1002
|
// OpenAI-compat backend — some over-fill optional args when the flag is
|
|
992
|
-
// absent (#4336). Preserve the author's explicit `false`
|
|
993
|
-
//
|
|
994
|
-
// strict-schema fallback
|
|
1003
|
+
// absent (#4336). Preserve the author's explicit `false` unless the
|
|
1004
|
+
// provider is explicitly known not to understand the field
|
|
1005
|
+
// (`supportsStrictMode: false`) or the strict-schema fallback is
|
|
1006
|
+
// active — both paths rely on a uniformly absent wire flag. Mirrors the
|
|
1007
|
+
// `supportsStrictMode !== false` gate used by openai-completions
|
|
1008
|
+
// (#4527).
|
|
995
1009
|
...(effectiveStrict
|
|
996
1010
|
? { strict: true }
|
|
997
1011
|
: !NO_STRICT && strictMode && tool.strict === false
|