@linxiraos/pi-ai 1.0.0
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 +5066 -0
- package/README.md +1195 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
- package/dist/types/auth-broker/client.d.ts +93 -0
- package/dist/types/auth-broker/discover.d.ts +39 -0
- package/dist/types/auth-broker/index.d.ts +7 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +136 -0
- package/dist/types/auth-broker/server.d.ts +48 -0
- package/dist/types/auth-broker/snapshot-cache.d.ts +17 -0
- package/dist/types/auth-broker/types.d.ts +152 -0
- package/dist/types/auth-broker/wire-schema-resource.d.ts +53 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
- package/dist/types/auth-gateway/http.d.ts +56 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +36 -0
- package/dist/types/auth-gateway/types.d.ts +127 -0
- package/dist/types/auth-retry.d.ts +150 -0
- package/dist/types/auth-storage.d.ts +1258 -0
- package/dist/types/dialect/anthropic.d.ts +15 -0
- package/dist/types/dialect/catalog.d.ts +3 -0
- package/dist/types/dialect/coercion.d.ts +23 -0
- package/dist/types/dialect/deepseek.d.ts +14 -0
- package/dist/types/dialect/demotion.d.ts +30 -0
- package/dist/types/dialect/examples.d.ts +18 -0
- package/dist/types/dialect/factory.d.ts +3 -0
- package/dist/types/dialect/fenced-thinking.d.ts +53 -0
- package/dist/types/dialect/gemini.d.ts +17 -0
- package/dist/types/dialect/gemma.d.ts +15 -0
- package/dist/types/dialect/glm.d.ts +9 -0
- package/dist/types/dialect/harmony.d.ts +8 -0
- package/dist/types/dialect/hermes.d.ts +9 -0
- package/dist/types/dialect/history.d.ts +3 -0
- package/dist/types/dialect/index.d.ts +11 -0
- package/dist/types/dialect/inventory.d.ts +9 -0
- package/dist/types/dialect/kimi.d.ts +14 -0
- package/dist/types/dialect/minimax.d.ts +3 -0
- package/dist/types/dialect/owned-stream.d.ts +4 -0
- package/dist/types/dialect/qwen3.d.ts +9 -0
- package/dist/types/dialect/rendering.d.ts +54 -0
- package/dist/types/dialect/thinking.d.ts +6 -0
- package/dist/types/dialect/types.d.ts +68 -0
- package/dist/types/dialect/xml.d.ts +9 -0
- package/dist/types/error/abort.d.ts +14 -0
- package/dist/types/error/auth-classify.d.ts +20 -0
- package/dist/types/error/auth.d.ts +27 -0
- package/dist/types/error/aws.d.ts +27 -0
- package/dist/types/error/classes.d.ts +106 -0
- package/dist/types/error/finalize.d.ts +39 -0
- package/dist/types/error/flags.d.ts +94 -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 +14 -0
- package/dist/types/error/oauth.d.ts +43 -0
- package/dist/types/error/provider.d.ts +42 -0
- package/dist/types/error/rate-limit.d.ts +80 -0
- package/dist/types/error/retryable.d.ts +27 -0
- package/dist/types/error/validation.d.ts +32 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/provider-details.d.ts +24 -0
- package/dist/types/providers/amazon-bedrock.d.ts +39 -0
- package/dist/types/providers/anthropic-client.d.ts +106 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +937 -0
- package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
- package/dist/types/providers/anthropic-wire.d.ts +345 -0
- package/dist/types/providers/anthropic.d.ts +261 -0
- package/dist/types/providers/aws-credentials.d.ts +48 -0
- package/dist/types/providers/aws-eventstream.d.ts +39 -0
- package/dist/types/providers/aws-sigv4.d.ts +55 -0
- package/dist/types/providers/azure-openai-responses.d.ts +16 -0
- package/dist/types/providers/bedrock-mantle.d.ts +13 -0
- package/dist/types/providers/claude-code-fingerprint.d.ts +19 -0
- package/dist/types/providers/cowork-fetch.d.ts +3 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor-pi-args.d.ts +105 -0
- package/dist/types/providers/cursor.d.ts +221 -0
- package/dist/types/providers/devin.d.ts +12 -0
- package/dist/types/providers/error-message.d.ts +25 -0
- package/dist/types/providers/github-copilot-headers.d.ts +40 -0
- package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
- package/dist/types/providers/gitlab-duo.d.ts +27 -0
- package/dist/types/providers/google-auth.d.ts +24 -0
- package/dist/types/providers/google-gemini-cli.d.ts +120 -0
- package/dist/types/providers/google-shared.d.ts +203 -0
- package/dist/types/providers/google-types.d.ts +155 -0
- package/dist/types/providers/google-vertex.d.ts +7 -0
- package/dist/types/providers/google.d.ts +4 -0
- package/dist/types/providers/grammar.d.ts +1 -0
- package/dist/types/providers/kimi.d.ts +27 -0
- package/dist/types/providers/mock.d.ts +179 -0
- package/dist/types/providers/ollama.d.ts +8 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +35 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +1311 -0
- package/dist/types/providers/openai-chat-server.d.ts +16 -0
- package/dist/types/providers/openai-chat-wire.d.ts +669 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +109 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +26 -0
- package/dist/types/providers/openai-codex-responses.d.ts +238 -0
- package/dist/types/providers/openai-completions.d.ts +48 -0
- package/dist/types/providers/openai-reasoning-fallback.d.ts +25 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +1314 -0
- package/dist/types/providers/openai-responses-server.d.ts +17 -0
- package/dist/types/providers/openai-responses-wire.d.ts +6099 -0
- package/dist/types/providers/openai-responses.d.ts +131 -0
- package/dist/types/providers/openai-shared.d.ts +624 -0
- package/dist/types/providers/pi-native-client.d.ts +13 -0
- package/dist/types/providers/pi-native-server.d.ts +69 -0
- package/dist/types/providers/register-builtins.d.ts +37 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +32 -0
- package/dist/types/providers/vision-guard.d.ts +20 -0
- package/dist/types/registry/aiand.d.ts +7 -0
- package/dist/types/registry/aimlapi.d.ts +4 -0
- package/dist/types/registry/alibaba-coding-plan.d.ts +8 -0
- package/dist/types/registry/alibaba-token-plan.d.ts +18 -0
- package/dist/types/registry/amazon-bedrock.d.ts +5 -0
- package/dist/types/registry/anthropic.d.ts +10 -0
- package/dist/types/registry/api-key-login.d.ts +42 -0
- package/dist/types/registry/api-key-validation.d.ts +43 -0
- package/dist/types/registry/aws.d.ts +13 -0
- package/dist/types/registry/azure.d.ts +4 -0
- package/dist/types/registry/baseten.d.ts +7 -0
- package/dist/types/registry/bedrock-mantle.d.ts +22 -0
- package/dist/types/registry/cerebras.d.ts +7 -0
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
- package/dist/types/registry/coreweave.d.ts +7 -0
- package/dist/types/registry/cursor.d.ts +7 -0
- package/dist/types/registry/deepseek.d.ts +8 -0
- package/dist/types/registry/derived.d.ts +5 -0
- package/dist/types/registry/devin.d.ts +8 -0
- package/dist/types/registry/exa.d.ts +8 -0
- package/dist/types/registry/firepass.d.ts +16 -0
- package/dist/types/registry/fireworks.d.ts +7 -0
- package/dist/types/registry/github-copilot.d.ts +7 -0
- package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
- package/dist/types/registry/gitlab-duo.d.ts +9 -0
- package/dist/types/registry/gmi-cloud.d.ts +7 -0
- package/dist/types/registry/google-antigravity.d.ts +9 -0
- package/dist/types/registry/google-gemini-cli.d.ts +9 -0
- package/dist/types/registry/google-vertex.d.ts +5 -0
- package/dist/types/registry/google.d.ts +4 -0
- package/dist/types/registry/groq.d.ts +4 -0
- package/dist/types/registry/huggingface.d.ts +7 -0
- package/dist/types/registry/index.d.ts +4 -0
- package/dist/types/registry/kagi.d.ts +14 -0
- package/dist/types/registry/kilo.d.ts +7 -0
- package/dist/types/registry/kimi-code.d.ts +7 -0
- package/dist/types/registry/litellm.d.ts +13 -0
- package/dist/types/registry/llama-cpp.d.ts +8 -0
- package/dist/types/registry/lm-studio.d.ts +8 -0
- package/dist/types/registry/meta.d.ts +7 -0
- package/dist/types/registry/minimax-code-cn.d.ts +6 -0
- package/dist/types/registry/minimax-code.d.ts +6 -0
- package/dist/types/registry/minimax.d.ts +4 -0
- package/dist/types/registry/mistral.d.ts +4 -0
- package/dist/types/registry/moonshot.d.ts +7 -0
- package/dist/types/registry/nanogpt.d.ts +7 -0
- package/dist/types/registry/novita.d.ts +6 -0
- package/dist/types/registry/nvidia.d.ts +7 -0
- package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
- package/dist/types/registry/oauth/anthropic.d.ts +24 -0
- package/dist/types/registry/oauth/callback-server.d.ts +74 -0
- package/dist/types/registry/oauth/cursor.d.ts +16 -0
- package/dist/types/registry/oauth/device-code.d.ts +25 -0
- package/dist/types/registry/oauth/devin.d.ts +5 -0
- package/dist/types/registry/oauth/github-copilot.d.ts +30 -0
- package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
- package/dist/types/registry/oauth/gitlab-duo.d.ts +3 -0
- package/dist/types/registry/oauth/google-antigravity.d.ts +11 -0
- package/dist/types/registry/oauth/google-gemini-cli.d.ts +22 -0
- package/dist/types/registry/oauth/google-oauth-shared.d.ts +56 -0
- package/dist/types/registry/oauth/index.d.ts +45 -0
- package/dist/types/registry/oauth/kimi.d.ts +21 -0
- package/dist/types/registry/oauth/minimax-code.d.ts +27 -0
- package/dist/types/registry/oauth/openai-codex.d.ts +33 -0
- package/dist/types/registry/oauth/opencode.d.ts +18 -0
- package/dist/types/registry/oauth/perplexity.d.ts +9 -0
- package/dist/types/registry/oauth/pkce.d.ts +8 -0
- package/dist/types/registry/oauth/types.d.ts +87 -0
- package/dist/types/registry/oauth/wafer.d.ts +1 -0
- package/dist/types/registry/oauth/xai-oauth.d.ts +46 -0
- package/dist/types/registry/oauth/xiaomi.d.ts +25 -0
- package/dist/types/registry/oauth/zai.d.ts +25 -0
- package/dist/types/registry/ollama-cloud.d.ts +7 -0
- package/dist/types/registry/ollama.d.ts +12 -0
- package/dist/types/registry/openai-codex-device.d.ts +8 -0
- package/dist/types/registry/openai-codex.d.ts +9 -0
- package/dist/types/registry/openai.d.ts +4 -0
- package/dist/types/registry/opencode-go.d.ts +6 -0
- package/dist/types/registry/opencode-zen.d.ts +6 -0
- package/dist/types/registry/openrouter.d.ts +13 -0
- package/dist/types/registry/parallel.d.ts +14 -0
- package/dist/types/registry/perplexity.d.ts +7 -0
- package/dist/types/registry/qianfan.d.ts +7 -0
- package/dist/types/registry/qwen-portal.d.ts +7 -0
- package/dist/types/registry/registry.d.ts +367 -0
- package/dist/types/registry/sakana.d.ts +7 -0
- package/dist/types/registry/siliconflow-cn.d.ts +7 -0
- package/dist/types/registry/siliconflow.d.ts +7 -0
- package/dist/types/registry/synthetic.d.ts +6 -0
- package/dist/types/registry/tavily.d.ts +14 -0
- package/dist/types/registry/together.d.ts +6 -0
- package/dist/types/registry/types.d.ts +75 -0
- package/dist/types/registry/umans.d.ts +7 -0
- package/dist/types/registry/venice.d.ts +13 -0
- package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
- package/dist/types/registry/vllm.d.ts +7 -0
- package/dist/types/registry/wafer-serverless.d.ts +6 -0
- package/dist/types/registry/xai-oauth.d.ts +7 -0
- package/dist/types/registry/xai.d.ts +7 -0
- package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
- package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
- package/dist/types/registry/xiaomi.d.ts +6 -0
- package/dist/types/registry/zai.d.ts +15 -0
- package/dist/types/registry/zenmux.d.ts +7 -0
- package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
- package/dist/types/stream.d.ts +46 -0
- package/dist/types/types.d.ts +1064 -0
- package/dist/types/usage/alibaba-token-plan.d.ts +3 -0
- package/dist/types/usage/claude.d.ts +4 -0
- package/dist/types/usage/cursor.d.ts +4 -0
- package/dist/types/usage/gemini.d.ts +2 -0
- package/dist/types/usage/github-copilot.d.ts +7 -0
- package/dist/types/usage/google-antigravity.d.ts +15 -0
- package/dist/types/usage/kimi.d.ts +2 -0
- package/dist/types/usage/minimax-code.d.ts +3 -0
- package/dist/types/usage/ollama.d.ts +5 -0
- package/dist/types/usage/openai-codex-base-url.d.ts +18 -0
- package/dist/types/usage/openai-codex-reset.d.ts +88 -0
- package/dist/types/usage/openai-codex.d.ts +10 -0
- package/dist/types/usage/opencode-go.d.ts +2 -0
- package/dist/types/usage/shared.d.ts +1 -0
- package/dist/types/usage/synthetic.d.ts +2 -0
- package/dist/types/usage/umans.d.ts +2 -0
- package/dist/types/usage/xai-oauth.d.ts +12 -0
- package/dist/types/usage/zai.d.ts +3 -0
- package/dist/types/usage.d.ts +527 -0
- package/dist/types/utils/abort.d.ts +25 -0
- package/dist/types/utils/anthropic-auth.d.ts +35 -0
- package/dist/types/utils/aws-profile.d.ts +17 -0
- package/dist/types/utils/block-symbols.d.ts +62 -0
- package/dist/types/utils/deterministic-id.d.ts +16 -0
- package/dist/types/utils/empty-completion-retry.d.ts +21 -0
- package/dist/types/utils/event-stream.d.ts +39 -0
- package/dist/types/utils/foundry.d.ts +1 -0
- package/dist/types/utils/google-validation.d.ts +2 -0
- package/dist/types/utils/harmony-leak.d.ts +135 -0
- package/dist/types/utils/http-inspector.d.ts +49 -0
- package/dist/types/utils/idle-iterator.d.ts +149 -0
- package/dist/types/utils/leaked-thinking-stream.d.ts +33 -0
- package/dist/types/utils/openai-http.d.ts +48 -0
- package/dist/types/utils/openrouter-headers.d.ts +1 -0
- package/dist/types/utils/parse-bind.d.ts +23 -0
- package/dist/types/utils/provider-response.d.ts +3 -0
- package/dist/types/utils/proxy.d.ts +39 -0
- package/dist/types/utils/request-debug.d.ts +29 -0
- package/dist/types/utils/retry-after.d.ts +4 -0
- package/dist/types/utils/retry.d.ts +14 -0
- package/dist/types/utils/schema/adapt.d.ts +24 -0
- package/dist/types/utils/schema/compatibility.d.ts +30 -0
- package/dist/types/utils/schema/dereference.d.ts +11 -0
- package/dist/types/utils/schema/draft.d.ts +10 -0
- package/dist/types/utils/schema/equality.d.ts +4 -0
- package/dist/types/utils/schema/fields.d.ts +54 -0
- package/dist/types/utils/schema/index.d.ts +14 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +20 -0
- package/dist/types/utils/schema/meta-validator.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +153 -0
- package/dist/types/utils/schema/spill.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +17 -0
- package/dist/types/utils/schema/strict-tool-validation.d.ts +16 -0
- package/dist/types/utils/schema/types.d.ts +4 -0
- package/dist/types/utils/schema/typescript.d.ts +24 -0
- package/dist/types/utils/schema/wire.d.ts +52 -0
- package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
- package/dist/types/utils/sse-debug.d.ts +5 -0
- package/dist/types/utils/stream-markup-healing.d.ts +87 -0
- package/dist/types/utils/thinking-loop.d.ts +102 -0
- package/dist/types/utils/tool-call-loop-guard.d.ts +26 -0
- package/dist/types/utils/tool-choice.d.ts +52 -0
- package/dist/types/utils/validation.d.ts +28 -0
- package/dist/types/utils.d.ts +57 -0
- package/package.json +138 -0
- package/src/api-registry.ts +109 -0
- package/src/auth/sqlite-credential-store.ts +2066 -0
- package/src/auth-broker/client.ts +471 -0
- package/src/auth-broker/discover.ts +310 -0
- package/src/auth-broker/index.ts +7 -0
- package/src/auth-broker/refresher.ts +117 -0
- package/src/auth-broker/remote-store.ts +1332 -0
- package/src/auth-broker/server.ts +898 -0
- package/src/auth-broker/snapshot-cache.ts +200 -0
- package/src/auth-broker/types.ts +193 -0
- package/src/auth-broker/wire-schema-resource.ts +487 -0
- package/src/auth-broker/wire-schemas.ts +43 -0
- package/src/auth-gateway/http.ts +227 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +836 -0
- package/src/auth-gateway/types.ts +153 -0
- package/src/auth-retry.ts +401 -0
- package/src/auth-storage.ts +6540 -0
- package/src/dialect/anthropic.md +31 -0
- package/src/dialect/anthropic.ts +608 -0
- package/src/dialect/catalog.ts +29 -0
- package/src/dialect/coercion.ts +136 -0
- package/src/dialect/deepseek.md +24 -0
- package/src/dialect/deepseek.ts +609 -0
- package/src/dialect/demotion.ts +40 -0
- package/src/dialect/examples.ts +71 -0
- package/src/dialect/factory.ts +34 -0
- package/src/dialect/fenced-thinking.ts +184 -0
- package/src/dialect/gemini.md +44 -0
- package/src/dialect/gemini.ts +583 -0
- package/src/dialect/gemma.md +33 -0
- package/src/dialect/gemma.ts +387 -0
- package/src/dialect/glm.md +32 -0
- package/src/dialect/glm.ts +579 -0
- package/src/dialect/harmony.md +31 -0
- package/src/dialect/harmony.ts +345 -0
- package/src/dialect/hermes.md +25 -0
- package/src/dialect/hermes.ts +206 -0
- package/src/dialect/history.ts +81 -0
- package/src/dialect/index.ts +15 -0
- package/src/dialect/inventory.ts +30 -0
- package/src/dialect/kimi.md +24 -0
- package/src/dialect/kimi.ts +340 -0
- package/src/dialect/minimax.md +31 -0
- package/src/dialect/minimax.ts +95 -0
- package/src/dialect/owned-stream.ts +481 -0
- package/src/dialect/prompt-template.md +12 -0
- package/src/dialect/qwen3.md +28 -0
- package/src/dialect/qwen3.ts +240 -0
- package/src/dialect/rendering.ts +304 -0
- package/src/dialect/thinking.ts +292 -0
- package/src/dialect/types.ts +56 -0
- package/src/dialect/xml.md +22 -0
- package/src/dialect/xml.ts +90 -0
- package/src/error/abort.ts +18 -0
- package/src/error/auth-classify.ts +47 -0
- package/src/error/auth.ts +48 -0
- package/src/error/aws.ts +35 -0
- package/src/error/classes.ts +281 -0
- package/src/error/finalize.ts +69 -0
- package/src/error/flags.ts +602 -0
- package/src/error/format.ts +45 -0
- package/src/error/gateway.ts +96 -0
- package/src/error/index.ts +14 -0
- package/src/error/oauth.ts +58 -0
- package/src/error/provider.ts +63 -0
- package/src/error/rate-limit.ts +303 -0
- package/src/error/retryable.ts +70 -0
- package/src/error/validation.ts +44 -0
- package/src/index.ts +51 -0
- package/src/provider-details.ts +90 -0
- package/src/providers/amazon-bedrock.ts +1064 -0
- package/src/providers/anthropic-client.ts +317 -0
- package/src/providers/anthropic-messages-server-schema.ts +252 -0
- package/src/providers/anthropic-messages-server.ts +818 -0
- package/src/providers/anthropic-wire.ts +359 -0
- package/src/providers/anthropic.ts +4539 -0
- package/src/providers/aws-credentials.ts +772 -0
- package/src/providers/aws-eventstream.ts +181 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +438 -0
- package/src/providers/bedrock-mantle.ts +110 -0
- package/src/providers/claude-code-fingerprint.ts +20 -0
- package/src/providers/cowork-fetch.ts +201 -0
- package/src/providers/cursor/exec-modern.ts +496 -0
- package/src/providers/cursor/proto/agent.proto +4533 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor-pi-args.ts +165 -0
- package/src/providers/cursor.ts +4689 -0
- package/src/providers/devin/proto/buf/validate/validate.proto +468 -0
- package/src/providers/devin/proto/buf.gen.yaml +33 -0
- package/src/providers/devin/proto/buf.yaml +17 -0
- package/src/providers/devin/proto/cel/expr/checked.proto +103 -0
- package/src/providers/devin/proto/cel/expr/eval.proto +38 -0
- package/src/providers/devin/proto/cel/expr/explain.proto +15 -0
- package/src/providers/devin/proto/cel/expr/syntax.proto +113 -0
- package/src/providers/devin/proto/cel/expr/value.proto +41 -0
- package/src/providers/devin/proto/connectext/grpc/status/v1/status.proto +11 -0
- package/src/providers/devin/proto/errorspb/errors.proto +56 -0
- package/src/providers/devin/proto/errorspb/hintdetail.proto +7 -0
- package/src/providers/devin/proto/errorspb/markers.proto +10 -0
- package/src/providers/devin/proto/errorspb/tags.proto +12 -0
- package/src/providers/devin/proto/errorspb/testing.proto +6 -0
- package/src/providers/devin/proto/exa/analytics_pb/analytics.proto +188 -0
- package/src/providers/devin/proto/exa/api_server_pb/api_server.proto +2461 -0
- package/src/providers/devin/proto/exa/auth_pb/auth.proto +19 -0
- package/src/providers/devin/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +79 -0
- package/src/providers/devin/proto/exa/browser_preview_pb/browser_preview.proto +32 -0
- package/src/providers/devin/proto/exa/bug_checker_pb/bug_checker.proto +22 -0
- package/src/providers/devin/proto/exa/cascade_plugins_pb/cascade_plugins.proto +262 -0
- package/src/providers/devin/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
- package/src/providers/devin/proto/exa/chat_pb/chat.proto +449 -0
- package/src/providers/devin/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
- package/src/providers/devin/proto/exa/codeium_common_pb/codeium_common.proto +4157 -0
- package/src/providers/devin/proto/exa/context_module_pb/context_module.proto +175 -0
- package/src/providers/devin/proto/exa/cortex_pb/cortex.proto +3268 -0
- package/src/providers/devin/proto/exa/dev_pb/dev.proto +26 -0
- package/src/providers/devin/proto/exa/diff_action_pb/diff_action.proto +75 -0
- package/src/providers/devin/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +103 -0
- package/src/providers/devin/proto/exa/eval_pb/eval.proto +1315 -0
- package/src/providers/devin/proto/exa/extension_server_pb/extension_server.proto +556 -0
- package/src/providers/devin/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
- package/src/providers/devin/proto/exa/index_pb/index.proto +461 -0
- package/src/providers/devin/proto/exa/knowledge_base_pb/knowledge_base.proto +144 -0
- package/src/providers/devin/proto/exa/language_server_pb/language_server.proto +2385 -0
- package/src/providers/devin/proto/exa/model_management_pb/model_management.proto +186 -0
- package/src/providers/devin/proto/exa/opensearch_clients_pb/opensearch_clients.proto +503 -0
- package/src/providers/devin/proto/exa/product_analytics_pb/product_analytics.proto +37 -0
- package/src/providers/devin/proto/exa/prompt_pb/prompt.proto +92 -0
- package/src/providers/devin/proto/exa/reactive_component_pb/reactive_component.proto +96 -0
- package/src/providers/devin/proto/exa/seat_management_pb/seat_management.proto +2680 -0
- package/src/providers/devin/proto/exa/tokenizer_pb/tokenizer.proto +37 -0
- package/src/providers/devin/proto/exa/trainer_pb/config.proto +647 -0
- package/src/providers/devin/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
- package/src/providers/devin/proto/exa/trust_pb/trust.proto +157 -0
- package/src/providers/devin/proto/exa/user_analytics_pb/user_analytics.proto +519 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/base/api_base.proto +28 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/datastore/datastore_v3.proto +484 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/log/log_service.proto +136 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/remote_api/remote_api.proto +42 -0
- package/src/providers/devin/proto/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto +61 -0
- package/src/providers/devin/proto/grpc/binlog/v1/binarylog.proto +84 -0
- package/src/providers/devin/proto/io/prometheus/client/metrics.proto +98 -0
- package/src/providers/devin.ts +679 -0
- package/src/providers/error-message.ts +23 -0
- package/src/providers/github-copilot-headers.ts +141 -0
- package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
- package/src/providers/gitlab-duo-workflow.ts +3135 -0
- package/src/providers/gitlab-duo.ts +399 -0
- package/src/providers/google-auth.ts +330 -0
- package/src/providers/google-gemini-cli.ts +1370 -0
- package/src/providers/google-shared.ts +1122 -0
- package/src/providers/google-types.ts +180 -0
- package/src/providers/google-vertex.ts +135 -0
- package/src/providers/google.ts +47 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +51 -0
- package/src/providers/mock.ts +514 -0
- package/src/providers/ollama.ts +776 -0
- package/src/providers/openai-anthropic-shim.ts +166 -0
- package/src/providers/openai-chat-server-schema.ts +243 -0
- package/src/providers/openai-chat-server.ts +752 -0
- package/src/providers/openai-chat-wire.ts +859 -0
- package/src/providers/openai-codex/request-transformer.ts +491 -0
- package/src/providers/openai-codex/response-handler.ts +102 -0
- package/src/providers/openai-codex-responses.ts +4716 -0
- package/src/providers/openai-completions.ts +2389 -0
- package/src/providers/openai-reasoning-fallback.ts +269 -0
- package/src/providers/openai-responses-server-schema.ts +397 -0
- package/src/providers/openai-responses-server.ts +1466 -0
- package/src/providers/openai-responses-wire.ts +6416 -0
- package/src/providers/openai-responses.ts +1393 -0
- package/src/providers/openai-shared.ts +3500 -0
- package/src/providers/pi-native-client.ts +275 -0
- package/src/providers/pi-native-server.ts +245 -0
- package/src/providers/register-builtins.ts +503 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +1083 -0
- package/src/providers/vision-guard.ts +54 -0
- package/src/registry/aiand.ts +22 -0
- package/src/registry/aimlapi.ts +6 -0
- package/src/registry/alibaba-coding-plan.ts +104 -0
- package/src/registry/alibaba-token-plan.ts +125 -0
- package/src/registry/amazon-bedrock.ts +22 -0
- package/src/registry/anthropic.ts +26 -0
- package/src/registry/api-key-login.ts +115 -0
- package/src/registry/api-key-validation.ts +145 -0
- package/src/registry/aws.ts +57 -0
- package/src/registry/azure.ts +6 -0
- package/src/registry/baseten.ts +22 -0
- package/src/registry/bedrock-mantle.ts +34 -0
- package/src/registry/cerebras.ts +23 -0
- package/src/registry/cloudflare-ai-gateway.ts +45 -0
- package/src/registry/coreweave.ts +40 -0
- package/src/registry/cursor.ts +20 -0
- package/src/registry/deepseek.ts +46 -0
- package/src/registry/derived.ts +9 -0
- package/src/registry/devin.ts +15 -0
- package/src/registry/exa.ts +19 -0
- package/src/registry/firepass.ts +32 -0
- package/src/registry/fireworks.ts +28 -0
- package/src/registry/github-copilot.ts +22 -0
- package/src/registry/gitlab-duo-workflow.ts +20 -0
- package/src/registry/gitlab-duo.ts +19 -0
- package/src/registry/gmi-cloud.ts +22 -0
- package/src/registry/google-antigravity.ts +22 -0
- package/src/registry/google-gemini-cli.ts +22 -0
- package/src/registry/google-vertex.ts +38 -0
- package/src/registry/google.ts +6 -0
- package/src/registry/groq.ts +6 -0
- package/src/registry/huggingface.ts +29 -0
- package/src/registry/index.ts +4 -0
- package/src/registry/kagi.ts +46 -0
- package/src/registry/kilo.ts +114 -0
- package/src/registry/kimi-code.ts +17 -0
- package/src/registry/litellm.ts +45 -0
- package/src/registry/llama-cpp.ts +35 -0
- package/src/registry/lm-studio.ts +31 -0
- package/src/registry/meta.ts +22 -0
- package/src/registry/minimax-code-cn.ts +12 -0
- package/src/registry/minimax-code.ts +12 -0
- package/src/registry/minimax.ts +6 -0
- package/src/registry/mistral.ts +6 -0
- package/src/registry/moonshot.ts +28 -0
- package/src/registry/nanogpt.ts +22 -0
- package/src/registry/novita.ts +25 -0
- package/src/registry/nvidia.ts +61 -0
- package/src/registry/oauth/anthropic-constants.ts +12 -0
- package/src/registry/oauth/anthropic.ts +346 -0
- package/src/registry/oauth/callback-server.ts +438 -0
- package/src/registry/oauth/cursor.ts +187 -0
- package/src/registry/oauth/device-code.ts +92 -0
- package/src/registry/oauth/devin.ts +124 -0
- package/src/registry/oauth/github-copilot.ts +369 -0
- package/src/registry/oauth/gitlab-duo-workflow.ts +146 -0
- package/src/registry/oauth/gitlab-duo.ts +222 -0
- package/src/registry/oauth/google-antigravity.ts +225 -0
- package/src/registry/oauth/google-gemini-cli.ts +297 -0
- package/src/registry/oauth/google-oauth-shared.ts +211 -0
- package/src/registry/oauth/index.ts +187 -0
- package/src/registry/oauth/kimi.ts +297 -0
- package/src/registry/oauth/minimax-code.ts +53 -0
- package/src/registry/oauth/oauth.html +317 -0
- package/src/registry/oauth/openai-codex.ts +384 -0
- package/src/registry/oauth/opencode.ts +50 -0
- package/src/registry/oauth/perplexity.ts +228 -0
- package/src/registry/oauth/pkce.ts +18 -0
- package/src/registry/oauth/types.ts +96 -0
- package/src/registry/oauth/wafer.ts +24 -0
- package/src/registry/oauth/xai-oauth.ts +559 -0
- package/src/registry/oauth/xiaomi.ts +211 -0
- package/src/registry/oauth/zai.ts +285 -0
- package/src/registry/ollama-cloud.ts +36 -0
- package/src/registry/ollama.ts +43 -0
- package/src/registry/openai-codex-device.ts +18 -0
- package/src/registry/openai-codex.ts +19 -0
- package/src/registry/openai.ts +6 -0
- package/src/registry/opencode-go.ts +12 -0
- package/src/registry/opencode-zen.ts +12 -0
- package/src/registry/openrouter.ts +28 -0
- package/src/registry/parallel.ts +45 -0
- package/src/registry/perplexity.ts +13 -0
- package/src/registry/qianfan.ts +27 -0
- package/src/registry/qwen-portal.ts +50 -0
- package/src/registry/registry.ts +182 -0
- package/src/registry/sakana.ts +22 -0
- package/src/registry/siliconflow-cn.ts +22 -0
- package/src/registry/siliconflow.ts +22 -0
- package/src/registry/synthetic.ts +21 -0
- package/src/registry/tavily.ts +45 -0
- package/src/registry/together.ts +22 -0
- package/src/registry/types.ts +86 -0
- package/src/registry/umans.ts +23 -0
- package/src/registry/venice.ts +33 -0
- package/src/registry/vercel-ai-gateway.ts +38 -0
- package/src/registry/vllm.ts +34 -0
- package/src/registry/wafer-serverless.ts +12 -0
- package/src/registry/xai-oauth.ts +17 -0
- package/src/registry/xai.ts +22 -0
- package/src/registry/xiaomi-token-plan-ams.ts +12 -0
- package/src/registry/xiaomi-token-plan-cn.ts +12 -0
- package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
- package/src/registry/xiaomi.ts +12 -0
- package/src/registry/zai.ts +41 -0
- package/src/registry/zenmux.ts +22 -0
- package/src/registry/zhipu-coding-plan.ts +27 -0
- package/src/stream.ts +1944 -0
- package/src/types.ts +1243 -0
- package/src/usage/alibaba-token-plan.ts +230 -0
- package/src/usage/claude.ts +830 -0
- package/src/usage/cursor.ts +335 -0
- package/src/usage/gemini.ts +258 -0
- package/src/usage/github-copilot.ts +424 -0
- package/src/usage/google-antigravity.ts +497 -0
- package/src/usage/kimi.ts +277 -0
- package/src/usage/minimax-code.ts +291 -0
- package/src/usage/ollama.ts +41 -0
- package/src/usage/openai-codex-base-url.ts +35 -0
- package/src/usage/openai-codex-reset.ts +205 -0
- package/src/usage/openai-codex.ts +627 -0
- package/src/usage/opencode-go.ts +89 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/synthetic.ts +180 -0
- package/src/usage/umans.ts +192 -0
- package/src/usage/xai-oauth.ts +414 -0
- package/src/usage/zai.ts +370 -0
- package/src/usage.ts +411 -0
- package/src/utils/abort.ts +67 -0
- package/src/utils/anthropic-auth.ts +93 -0
- package/src/utils/aws-profile.ts +88 -0
- package/src/utils/block-symbols.ts +78 -0
- package/src/utils/deterministic-id.ts +20 -0
- package/src/utils/empty-completion-retry.ts +161 -0
- package/src/utils/event-stream.ts +202 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/google-validation.ts +25 -0
- package/src/utils/harmony-leak.ts +500 -0
- package/src/utils/http-inspector.ts +196 -0
- package/src/utils/idle-iterator.ts +531 -0
- package/src/utils/leaked-thinking-stream.ts +483 -0
- package/src/utils/openai-http.ts +119 -0
- package/src/utils/openrouter-headers.ts +12 -0
- package/src/utils/parse-bind.ts +56 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/proxy.ts +314 -0
- package/src/utils/request-debug.ts +351 -0
- package/src/utils/retry-after.ts +121 -0
- package/src/utils/retry.ts +77 -0
- package/src/utils/schema/CONSTRAINTS.md +168 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.ts +210 -0
- package/src/utils/schema/index.ts +14 -0
- package/src/utils/schema/json-schema-validator.ts +595 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +2314 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +109 -0
- package/src/utils/schema/strict-tool-validation.ts +117 -0
- package/src/utils/schema/types.ts +10 -0
- package/src/utils/schema/typescript.ts +212 -0
- package/src/utils/schema/wire.ts +662 -0
- package/src/utils/sdk-stream-timeout.ts +43 -0
- package/src/utils/sse-debug.ts +18 -0
- package/src/utils/stream-markup-healing.ts +247 -0
- package/src/utils/thinking-loop.ts +552 -0
- package/src/utils/tool-call-loop-guard.ts +107 -0
- package/src/utils/tool-choice.ts +101 -0
- package/src/utils/validation.ts +1932 -0
- package/src/utils.ts +492 -0
|
@@ -0,0 +1,1393 @@
|
|
|
1
|
+
import { scheduler } from "node:timers/promises";
|
|
2
|
+
import { hostMatchesUrl } from "@linxiraos/pi-catalog/hosts";
|
|
3
|
+
import { $flag, logger, structuredCloneJSON } from "@linxiraos/pi-utils";
|
|
4
|
+
import * as AIError from "../error";
|
|
5
|
+
import { getEnvApiKey } from "../stream";
|
|
6
|
+
import type {
|
|
7
|
+
AssistantMessage,
|
|
8
|
+
CacheRetention,
|
|
9
|
+
Context,
|
|
10
|
+
Model,
|
|
11
|
+
OpenAICompat,
|
|
12
|
+
ProviderSessionState,
|
|
13
|
+
RawSseEvent,
|
|
14
|
+
ServiceTier,
|
|
15
|
+
StreamFunction,
|
|
16
|
+
StreamOptions,
|
|
17
|
+
Tool,
|
|
18
|
+
ToolChoice,
|
|
19
|
+
} from "../types";
|
|
20
|
+
import {
|
|
21
|
+
createOpenAIResponsesHistoryPayload,
|
|
22
|
+
normalizeSystemPrompts,
|
|
23
|
+
resolveCacheRetention,
|
|
24
|
+
sanitizeOpenAIResponsesAssistantHistoryItemsForReplay,
|
|
25
|
+
} from "../utils";
|
|
26
|
+
import { createAbortSourceTracker } from "../utils/abort";
|
|
27
|
+
import { withEmptyCompletionRetry } from "../utils/empty-completion-retry";
|
|
28
|
+
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
29
|
+
import type { RawHttpRequestDump } from "../utils/http-inspector";
|
|
30
|
+
import {
|
|
31
|
+
getOpenAIStreamFirstEventTimeoutMs,
|
|
32
|
+
getOpenAIStreamIdleTimeoutMs,
|
|
33
|
+
iterateWithIdleTimeout,
|
|
34
|
+
} from "../utils/idle-iterator";
|
|
35
|
+
import { OpenAIHttpError, postOpenAIStream } from "../utils/openai-http";
|
|
36
|
+
import { notifyProviderResponse } from "../utils/provider-response";
|
|
37
|
+
import { callWithCopilotModelRetry } from "../utils/retry";
|
|
38
|
+
import {
|
|
39
|
+
adaptSchemaForStrict,
|
|
40
|
+
findStrictToolSchemaViolation,
|
|
41
|
+
NO_STRICT,
|
|
42
|
+
normalizeSchemaForMoonshot,
|
|
43
|
+
sanitizeSchemaForOpenAIResponses,
|
|
44
|
+
toolWireSchema,
|
|
45
|
+
} from "../utils/schema";
|
|
46
|
+
import {
|
|
47
|
+
isForcedToolChoice,
|
|
48
|
+
mapToOpenAIResponsesToolChoice,
|
|
49
|
+
type OpenAIResponsesToolChoice,
|
|
50
|
+
} from "../utils/tool-choice";
|
|
51
|
+
import { compactGrammarDefinition } from "./grammar";
|
|
52
|
+
import {
|
|
53
|
+
applyOpenAIReasoningEffortFallback,
|
|
54
|
+
clearOpenAIReasoningEffortFallbackState,
|
|
55
|
+
createOpenAIReasoningEffortFallbackKey,
|
|
56
|
+
createOpenAIReasoningEffortFallbackState,
|
|
57
|
+
getOpenAIReasoningEffortFallback,
|
|
58
|
+
type OpenAIReasoningEffortFallback,
|
|
59
|
+
type OpenAIReasoningEffortFallbackState,
|
|
60
|
+
rememberOpenAIReasoningEffortFallback,
|
|
61
|
+
resolveOpenAIReasoningEffortFallback,
|
|
62
|
+
} from "./openai-reasoning-fallback";
|
|
63
|
+
import type {
|
|
64
|
+
Tool as OpenAITool,
|
|
65
|
+
ResponseCreateParamsStreaming,
|
|
66
|
+
ResponseInput,
|
|
67
|
+
ResponseInputContent,
|
|
68
|
+
ResponseStreamEvent,
|
|
69
|
+
} from "./openai-responses-wire";
|
|
70
|
+
import {
|
|
71
|
+
applyCommonResponsesSamplingParams,
|
|
72
|
+
applyOpenAIExtraBody,
|
|
73
|
+
applyOpenAIGatewayRouting,
|
|
74
|
+
applyResponsesCompatPolicy,
|
|
75
|
+
applyVercelResponsesCacheControls,
|
|
76
|
+
applyWireModelIdTransform,
|
|
77
|
+
buildResponsesDeltaInput,
|
|
78
|
+
buildResponsesInput,
|
|
79
|
+
clearOpenAIStrictToolsState,
|
|
80
|
+
createInitialResponsesAssistantMessage,
|
|
81
|
+
createOpenAIStrictToolsState,
|
|
82
|
+
disableStrictToolsForScope,
|
|
83
|
+
getOpenAIPromptCacheKey,
|
|
84
|
+
getOpenAIResponsesRoutingSessionId,
|
|
85
|
+
getOpenAIStrictToolsScope,
|
|
86
|
+
getOpenRouterResponsesSessionId,
|
|
87
|
+
isCompiledGrammarTooLargeStrictError,
|
|
88
|
+
isOpenAIResponsesProgressEvent,
|
|
89
|
+
isOpenRouterAnthropicModel,
|
|
90
|
+
isStrictToolsDisabledForScope,
|
|
91
|
+
type OpenAIPromptCacheOptions,
|
|
92
|
+
type OpenAIStrictToolsScope,
|
|
93
|
+
type OpenAIStrictToolsState,
|
|
94
|
+
processResponsesStream,
|
|
95
|
+
resolveOpenAICompatPolicy,
|
|
96
|
+
resolveOpenAIOutputTokenParam,
|
|
97
|
+
resolveOpenAIRequestSetup,
|
|
98
|
+
resolveOpenAIResponsesOutputClamp,
|
|
99
|
+
shouldRetryWithoutStrictTools,
|
|
100
|
+
} from "./openai-shared";
|
|
101
|
+
|
|
102
|
+
// OpenAI Responses-specific options
|
|
103
|
+
export interface OpenAIResponsesOptions extends StreamOptions {
|
|
104
|
+
reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
105
|
+
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
106
|
+
serviceTier?: ServiceTier;
|
|
107
|
+
textVerbosity?: "low" | "medium" | "high";
|
|
108
|
+
toolChoice?: ToolChoice;
|
|
109
|
+
openrouterVariant?: string;
|
|
110
|
+
maxTokensExplicit?: boolean;
|
|
111
|
+
disableReasoning?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Stateful turns: chain via `previous_response_id` + delta input instead of
|
|
114
|
+
* replaying the full transcript. Forces `store: true` (the platform only
|
|
115
|
+
* resolves stored responses). Defaults ON against the official OpenAI API
|
|
116
|
+
* and OFF for other Responses endpoints; `PI_OPENAI_STATEFUL` overrides the
|
|
117
|
+
* default, and `false` here vetoes everything. Requires `sessionId` +
|
|
118
|
+
* `providerSessionState`. Falls back to a full replay whenever history
|
|
119
|
+
* mutates or the server reports a stale id.
|
|
120
|
+
*/
|
|
121
|
+
statefulResponses?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Override catalog compat for strict tool call/result pairing when building
|
|
124
|
+
* Responses API inputs. Default behavior is catalog compat; this is only for
|
|
125
|
+
* debugging/adapter wrappers.
|
|
126
|
+
*/
|
|
127
|
+
strictResponsesPairing?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Override catalog compat for `include: ["reasoning.encrypted_content"]`.
|
|
130
|
+
* Default behavior is catalog compat; this is only for debugging/adapter wrappers.
|
|
131
|
+
*/
|
|
132
|
+
includeEncryptedReasoning?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Override catalog compat for stripping `type: "reasoning"` items from
|
|
135
|
+
* replayed conversation history before request encoding. Default behavior is
|
|
136
|
+
* catalog compat; this is only for debugging/adapter wrappers.
|
|
137
|
+
*/
|
|
138
|
+
filterReasoningHistory?: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Override catalog compat for suppressing the `reasoning.effort` wire param.
|
|
141
|
+
* Default behavior is catalog compat; this is only for debugging/adapter wrappers.
|
|
142
|
+
*/
|
|
143
|
+
omitReasoningEffort?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Extra request headers merged onto the model/copilot defaults. Used by
|
|
146
|
+
* adapter wrappers to inject provider-specific
|
|
147
|
+
* routing or cache hints.
|
|
148
|
+
*/
|
|
149
|
+
headers?: Record<string, string>;
|
|
150
|
+
/**
|
|
151
|
+
* Extra body fields merged into the Responses request payload. Used by
|
|
152
|
+
* adapter wrappers to inject provider-specific body keys (e.g.,
|
|
153
|
+
* prompt_cache_key for prompt-cache routing).
|
|
154
|
+
*/
|
|
155
|
+
extraBody?: Record<string, unknown>;
|
|
156
|
+
/** Opt-in GPT-5.6+ prompt-cache policy. Unsupported explicit mode fails locally. */
|
|
157
|
+
promptCache?: OpenAIPromptCacheOptions;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX = "openai-responses:";
|
|
161
|
+
const OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE =
|
|
162
|
+
"OpenAI responses stream timed out while waiting for the first event";
|
|
163
|
+
/** Consecutive stale-previous-response failures before chaining is disabled for the session. */
|
|
164
|
+
const OPENAI_RESPONSES_CHAIN_STALE_FAILURE_LIMIT = 3;
|
|
165
|
+
const OPENAI_RESPONSES_MAX_TRANSIENT_STREAM_RETRIES = 1;
|
|
166
|
+
const OPENAI_RESPONSES_TRANSIENT_STREAM_RETRY_DELAY_MS = 500;
|
|
167
|
+
|
|
168
|
+
function isOpenAIResponsesReplayUnsafeEvent(event: ResponseStreamEvent): boolean {
|
|
169
|
+
switch (event.type) {
|
|
170
|
+
case "response.output_text.delta":
|
|
171
|
+
case "response.refusal.delta":
|
|
172
|
+
case "response.reasoning_summary_text.delta":
|
|
173
|
+
case "response.reasoning_text.delta":
|
|
174
|
+
case "response.function_call_arguments.delta":
|
|
175
|
+
case "response.custom_tool_call_input.delta":
|
|
176
|
+
return typeof event.delta === "string" && event.delta.length > 0;
|
|
177
|
+
case "response.reasoning_summary_part.done":
|
|
178
|
+
return true;
|
|
179
|
+
case "response.output_item.done":
|
|
180
|
+
return true;
|
|
181
|
+
default:
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isRetryableOpenAIResponsesStreamFailure(error: unknown): boolean {
|
|
187
|
+
return (
|
|
188
|
+
AIError.isTransientStreamParseError(error) ||
|
|
189
|
+
(error instanceof AIError.ProviderResponseError && error.kind === "incomplete-stream")
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface OpenAIResponsesProviderSessionState
|
|
194
|
+
extends ProviderSessionState,
|
|
195
|
+
OpenAIStrictToolsState,
|
|
196
|
+
OpenAIReasoningEffortFallbackState {
|
|
197
|
+
nativeHistoryReplayWarmed: boolean;
|
|
198
|
+
/** Stateful `previous_response_id` chain baselines, keyed by baseUrl/model/session. */
|
|
199
|
+
chains: Map<string, OpenAIResponsesChainState>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface OpenAIResponsesChainState {
|
|
203
|
+
/**
|
|
204
|
+
* Wire params of the last successful turn; never carries
|
|
205
|
+
* `previous_response_id`.
|
|
206
|
+
*/
|
|
207
|
+
lastParams?: OpenAIResponsesSamplingParams;
|
|
208
|
+
lastPromptCacheBreakpointPolicy?: "latest-stable-message" | "none";
|
|
209
|
+
lastResponseId?: string;
|
|
210
|
+
/** Output items of the last response, in replay-sanitized form (matches next-turn input). */
|
|
211
|
+
lastResponseItems?: ResponseInput;
|
|
212
|
+
canAppend: boolean;
|
|
213
|
+
/** Consecutive stale-previous-response failures; reset on a successful chained completion. */
|
|
214
|
+
staleFailures: number;
|
|
215
|
+
/** Set once chaining is judged unsupported for this session (circuit breaker). */
|
|
216
|
+
disabled: boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createOpenAIResponsesProviderSessionState(): OpenAIResponsesProviderSessionState {
|
|
220
|
+
const strictToolsState = createOpenAIStrictToolsState();
|
|
221
|
+
const reasoningEffortFallbackState = createOpenAIReasoningEffortFallbackState();
|
|
222
|
+
const state: OpenAIResponsesProviderSessionState = {
|
|
223
|
+
...strictToolsState,
|
|
224
|
+
...reasoningEffortFallbackState,
|
|
225
|
+
nativeHistoryReplayWarmed: false,
|
|
226
|
+
chains: new Map(),
|
|
227
|
+
close: () => {
|
|
228
|
+
state.nativeHistoryReplayWarmed = false;
|
|
229
|
+
state.chains.clear();
|
|
230
|
+
clearOpenAIStrictToolsState(state);
|
|
231
|
+
clearOpenAIReasoningEffortFallbackState(state);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
return state;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getOpenAIResponsesProviderSessionState(
|
|
238
|
+
model: Model<"openai-responses">,
|
|
239
|
+
providerSessionState: Map<string, ProviderSessionState> | undefined,
|
|
240
|
+
): OpenAIResponsesProviderSessionState | undefined {
|
|
241
|
+
if (!providerSessionState) return undefined;
|
|
242
|
+
const key = `${OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX}${model.provider}`;
|
|
243
|
+
const existing = providerSessionState.get(key) as OpenAIResponsesProviderSessionState | undefined;
|
|
244
|
+
if (existing) return existing;
|
|
245
|
+
const created = createOpenAIResponsesProviderSessionState();
|
|
246
|
+
providerSessionState.set(key, created);
|
|
247
|
+
return created;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isOpenAIResponsesStatefulEnabled(
|
|
251
|
+
options: OpenAIResponsesOptions | undefined,
|
|
252
|
+
baseUrl: string | undefined,
|
|
253
|
+
): boolean {
|
|
254
|
+
if (options?.statefulResponses === false) return false;
|
|
255
|
+
if (options?.statefulResponses === true) return true;
|
|
256
|
+
// Default ON only against the official OpenAI API: chaining forces
|
|
257
|
+
// `store: true`, and third-party /v1/responses proxies routinely ignore or
|
|
258
|
+
// reject `previous_response_id`. An unset baseUrl means the default
|
|
259
|
+
// endpoint (api.openai.com).
|
|
260
|
+
return $flag("PI_OPENAI_STATEFUL", !baseUrl || hostMatchesUrl(baseUrl, "openai"));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getOpenAIResponsesChainState(
|
|
264
|
+
providerSessionState: OpenAIResponsesProviderSessionState,
|
|
265
|
+
model: Model<"openai-responses">,
|
|
266
|
+
resolvedBaseUrl: string | undefined,
|
|
267
|
+
sessionId: string,
|
|
268
|
+
): OpenAIResponsesChainState {
|
|
269
|
+
const key = `${resolvedBaseUrl ?? model.baseUrl ?? ""}\u0000${model.id}\u0000${sessionId}`;
|
|
270
|
+
const existing = providerSessionState.chains.get(key);
|
|
271
|
+
if (existing) return existing;
|
|
272
|
+
const created: OpenAIResponsesChainState = { canAppend: false, staleFailures: 0, disabled: false };
|
|
273
|
+
providerSessionState.chains.set(key, created);
|
|
274
|
+
return created;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resetOpenAIResponsesChainState(state: OpenAIResponsesChainState): void {
|
|
278
|
+
state.canAppend = false;
|
|
279
|
+
state.lastParams = undefined;
|
|
280
|
+
state.lastResponseId = undefined;
|
|
281
|
+
state.lastResponseItems = undefined;
|
|
282
|
+
state.lastPromptCacheBreakpointPolicy = undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
interface OpenAIResponsesChainedParams {
|
|
286
|
+
params: OpenAIResponsesSamplingParams;
|
|
287
|
+
/** Set iff the params carry previous_response_id (delta request). */
|
|
288
|
+
previousResponseId?: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Shape the next turn's request: when the session's append baseline is intact
|
|
293
|
+
* (same options, strict history prefix), chain via `previous_response_id` +
|
|
294
|
+
* delta-only `input`; otherwise break the chain and replay the full transcript.
|
|
295
|
+
*
|
|
296
|
+
* The prefix check runs on the wire form of the conversation arguments, so
|
|
297
|
+
* history mutations or option changes force a full replay.
|
|
298
|
+
*/
|
|
299
|
+
function buildOpenAIResponsesChainedParams(
|
|
300
|
+
params: OpenAIResponsesSamplingParams,
|
|
301
|
+
chain: OpenAIResponsesChainState,
|
|
302
|
+
): OpenAIResponsesChainedParams {
|
|
303
|
+
const deltaInput = chain.canAppend
|
|
304
|
+
? buildResponsesDeltaInput(chain.lastParams, chain.lastResponseItems, params)
|
|
305
|
+
: null;
|
|
306
|
+
if (deltaInput && deltaInput.length > 0 && chain.lastResponseId) {
|
|
307
|
+
return {
|
|
308
|
+
params: { ...params, previous_response_id: chain.lastResponseId, input: deltaInput },
|
|
309
|
+
previousResponseId: chain.lastResponseId,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (chain.canAppend) {
|
|
313
|
+
// History mutated or options changed — break the chain and replay in full.
|
|
314
|
+
resetOpenAIResponsesChainState(chain);
|
|
315
|
+
}
|
|
316
|
+
return { params };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function isOpenAIResponsesStalePreviousResponseError(error: unknown): boolean {
|
|
320
|
+
if (!(error instanceof Error)) return false;
|
|
321
|
+
if ((error as { code?: string }).code === "previous_response_not_found") return true;
|
|
322
|
+
// "unsupported" covers endpoints that reject the parameter outright
|
|
323
|
+
// (e.g. "Unsupported parameter: previous_response_id").
|
|
324
|
+
return (
|
|
325
|
+
/previous[ _]?response/i.test(error.message) &&
|
|
326
|
+
/not[ _]?found|invalid|expired|stale|unsupported/i.test(error.message)
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function registerOpenAIResponsesChainStaleFailure(chain: OpenAIResponsesChainState, error: unknown): void {
|
|
331
|
+
resetOpenAIResponsesChainState(chain);
|
|
332
|
+
chain.staleFailures += 1;
|
|
333
|
+
if (chain.staleFailures >= OPENAI_RESPONSES_CHAIN_STALE_FAILURE_LIMIT) {
|
|
334
|
+
chain.disabled = true;
|
|
335
|
+
}
|
|
336
|
+
logger.debug("OpenAI responses previous_response_id rejected; falling back to full context", {
|
|
337
|
+
error: error instanceof Error ? error.message : String(error),
|
|
338
|
+
consecutiveFailures: chain.staleFailures,
|
|
339
|
+
disabled: chain.disabled,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* One-shot ZDR signal: the org will never resolve a stored response, so skip
|
|
345
|
+
* the staleFailures counter and disable chaining immediately for this session.
|
|
346
|
+
*/
|
|
347
|
+
function markOpenAIResponsesChainZeroDataRetention(chain: OpenAIResponsesChainState, error: unknown): void {
|
|
348
|
+
resetOpenAIResponsesChainState(chain);
|
|
349
|
+
chain.disabled = true;
|
|
350
|
+
chain.staleFailures = OPENAI_RESPONSES_CHAIN_STALE_FAILURE_LIMIT;
|
|
351
|
+
logger.debug("OpenAI responses chaining disabled (Zero Data Retention)", {
|
|
352
|
+
error: error instanceof Error ? error.message : String(error),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
type OpenRouterAnthropicCacheControl = { type: "ephemeral"; ttl?: "1h" };
|
|
357
|
+
|
|
358
|
+
type OpenAIResponsesSamplingParams = ResponseCreateParamsStreaming & {
|
|
359
|
+
top_p?: number;
|
|
360
|
+
top_k?: number;
|
|
361
|
+
min_p?: number;
|
|
362
|
+
presence_penalty?: number;
|
|
363
|
+
repetition_penalty?: number;
|
|
364
|
+
session_id?: string;
|
|
365
|
+
stream_options?: { include_obfuscation?: boolean };
|
|
366
|
+
provider?: OpenAICompat["openRouterRouting"];
|
|
367
|
+
reasoning?: { effort?: string } | { enabled: false };
|
|
368
|
+
cache_control?: OpenRouterAnthropicCacheControl;
|
|
369
|
+
caching?: "auto";
|
|
370
|
+
cache_anchor_items?: number;
|
|
371
|
+
cache_ttl?: "5m" | "1h";
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
function maybeAddOpenRouterAnthropicCacheControl(
|
|
375
|
+
params: OpenAIResponsesSamplingParams,
|
|
376
|
+
model: Model<"openai-responses">,
|
|
377
|
+
cacheRetention: CacheRetention,
|
|
378
|
+
): void {
|
|
379
|
+
if (cacheRetention === "none" || !isOpenRouterAnthropicModel(model)) return;
|
|
380
|
+
if (params.cache_control != null) return;
|
|
381
|
+
params.cache_control = cacheRetention === "long" ? { type: "ephemeral", ttl: "1h" } : { type: "ephemeral" };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Generate function for OpenAI Responses API
|
|
386
|
+
*/
|
|
387
|
+
const streamOpenAIResponsesOnce = (
|
|
388
|
+
model: Model<"openai-responses">,
|
|
389
|
+
context: Context,
|
|
390
|
+
options?: OpenAIResponsesOptions,
|
|
391
|
+
): AssistantMessageEventStream => {
|
|
392
|
+
const stream = new AssistantMessageEventStream();
|
|
393
|
+
|
|
394
|
+
// Start async processing
|
|
395
|
+
(async () => {
|
|
396
|
+
const startTime = performance.now();
|
|
397
|
+
let firstTokenTime: number | undefined;
|
|
398
|
+
|
|
399
|
+
const output: AssistantMessage = createInitialResponsesAssistantMessage(model.api, model.provider, model.id);
|
|
400
|
+
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
401
|
+
let chainState: OpenAIResponsesChainState | undefined;
|
|
402
|
+
let sentPreviousResponseId: string | undefined;
|
|
403
|
+
const abortTracker = createAbortSourceTracker(options?.signal);
|
|
404
|
+
const firstEventTimeoutAbortError = new AIError.StreamTimeoutError(OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE);
|
|
405
|
+
const { requestAbortController, requestSignal } = abortTracker;
|
|
406
|
+
const onSseEvent = options?.onSseEvent;
|
|
407
|
+
const rawSseObserver = onSseEvent
|
|
408
|
+
? (event: RawSseEvent) => {
|
|
409
|
+
if (!event.event && event.data && event.data !== "[DONE]") {
|
|
410
|
+
try {
|
|
411
|
+
const parsed = JSON.parse(event.data);
|
|
412
|
+
const resolvedEvent =
|
|
413
|
+
typeof parsed.type === "string"
|
|
414
|
+
? parsed.type
|
|
415
|
+
: typeof parsed.object === "string"
|
|
416
|
+
? parsed.object
|
|
417
|
+
: null;
|
|
418
|
+
if (resolvedEvent) {
|
|
419
|
+
event.event = resolvedEvent;
|
|
420
|
+
event.raw = [`event: ${resolvedEvent}`, ...event.raw];
|
|
421
|
+
}
|
|
422
|
+
} catch {}
|
|
423
|
+
}
|
|
424
|
+
onSseEvent(event, model);
|
|
425
|
+
}
|
|
426
|
+
: undefined;
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
// Keep request routing on `sessionId` while allowing callers to pin a
|
|
430
|
+
// stable prompt-cache key independently. Side-channel calls use this to
|
|
431
|
+
// avoid perturbing provider conversation state without cold-starting the cache.
|
|
432
|
+
const routingSessionId = getOpenAIResponsesRoutingSessionId(options);
|
|
433
|
+
const promptCacheSessionId = getOpenAIPromptCacheKey(options);
|
|
434
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
435
|
+
const { headers, copilotPremiumRequests, baseUrl } = resolveOpenAIRequestSetup(model, {
|
|
436
|
+
apiKey,
|
|
437
|
+
extraHeaders: options?.headers,
|
|
438
|
+
initiatorOverride: options?.initiatorOverride,
|
|
439
|
+
messages: context.messages,
|
|
440
|
+
openAISessionId: routingSessionId,
|
|
441
|
+
promptCacheSessionId,
|
|
442
|
+
});
|
|
443
|
+
const premiumRequestsTotal = copilotPremiumRequests;
|
|
444
|
+
const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
|
|
445
|
+
const strictToolsScope = getOpenAIStrictToolsScope(model, baseUrl);
|
|
446
|
+
const promptCacheBreakpointPolicy =
|
|
447
|
+
resolveCacheRetention(options?.cacheRetention) !== "none" && options?.promptCache?.mode === "explicit"
|
|
448
|
+
? (options.promptCache.breakpoint ?? "latest-stable-message")
|
|
449
|
+
: undefined;
|
|
450
|
+
if (isOpenAIResponsesStatefulEnabled(options, baseUrl) && routingSessionId && providerSessionState) {
|
|
451
|
+
chainState = getOpenAIResponsesChainState(providerSessionState, model, baseUrl, routingSessionId);
|
|
452
|
+
if (chainState.canAppend && chainState.lastPromptCacheBreakpointPolicy !== promptCacheBreakpointPolicy) {
|
|
453
|
+
resetOpenAIResponsesChainState(chainState);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const builtParams = buildParams(
|
|
457
|
+
model,
|
|
458
|
+
context,
|
|
459
|
+
options,
|
|
460
|
+
providerSessionState,
|
|
461
|
+
strictToolsScope,
|
|
462
|
+
false,
|
|
463
|
+
chainState?.canAppend ? chainState.lastParams?.input : undefined,
|
|
464
|
+
);
|
|
465
|
+
const params = builtParams.params;
|
|
466
|
+
let activeParams = params;
|
|
467
|
+
const resolvedBaseUrl = (baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
468
|
+
const requestReasoningEffortFallbacks = new Map<string, OpenAIReasoningEffortFallback>();
|
|
469
|
+
const attemptedReasoningEffortFallbacks = new Set<string>();
|
|
470
|
+
let pendingReasoningEffortFallback: { key: string; fallback: OpenAIReasoningEffortFallback } | undefined;
|
|
471
|
+
let activeReasoningEffortFallbackKey: string | undefined;
|
|
472
|
+
let activeRequestParams: OpenAIResponsesSamplingParams | undefined;
|
|
473
|
+
const applyReasoningEffortFallbackForRequest = (requestParams: OpenAIResponsesSamplingParams): string => {
|
|
474
|
+
const fallbackKey = createOpenAIReasoningEffortFallbackKey(
|
|
475
|
+
"responses",
|
|
476
|
+
resolvedBaseUrl,
|
|
477
|
+
typeof requestParams.model === "string" ? requestParams.model : model.id,
|
|
478
|
+
);
|
|
479
|
+
const requestReasoningEffortFallback = requestReasoningEffortFallbacks.has(fallbackKey)
|
|
480
|
+
? requestReasoningEffortFallbacks.get(fallbackKey)
|
|
481
|
+
: getOpenAIReasoningEffortFallback(providerSessionState, fallbackKey);
|
|
482
|
+
if (requestReasoningEffortFallback !== undefined) {
|
|
483
|
+
applyOpenAIReasoningEffortFallback(requestParams, requestReasoningEffortFallback);
|
|
484
|
+
}
|
|
485
|
+
return fallbackKey;
|
|
486
|
+
};
|
|
487
|
+
if (chainState && !chainState.disabled) {
|
|
488
|
+
// Platform `previous_response_id` chaining only resolves stored responses.
|
|
489
|
+
params.store = true;
|
|
490
|
+
}
|
|
491
|
+
applyReasoningEffortFallbackForRequest(params);
|
|
492
|
+
let chained: OpenAIResponsesChainedParams =
|
|
493
|
+
chainState && !chainState.disabled ? buildOpenAIResponsesChainedParams(params, chainState) : { params };
|
|
494
|
+
sentPreviousResponseId = chained.previousResponseId;
|
|
495
|
+
const idleTimeoutMs =
|
|
496
|
+
options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs(model.compat.streamIdleTimeoutMs);
|
|
497
|
+
const firstEventTimeoutMs =
|
|
498
|
+
options?.streamFirstEventTimeoutMs ??
|
|
499
|
+
getOpenAIStreamFirstEventTimeoutMs(idleTimeoutMs, model.compat.streamFirstEventTimeoutMs);
|
|
500
|
+
const requestTimeoutMs =
|
|
501
|
+
firstEventTimeoutMs !== undefined && firstEventTimeoutMs > 0 ? firstEventTimeoutMs : undefined;
|
|
502
|
+
const requestUrl = `${resolvedBaseUrl}/responses`;
|
|
503
|
+
const applyPayloadReplacement = async (requestParams: OpenAIResponsesSamplingParams) => {
|
|
504
|
+
const replacementPayload = await options?.onPayload?.(requestParams, model);
|
|
505
|
+
const payload =
|
|
506
|
+
replacementPayload !== undefined ? (replacementPayload as OpenAIResponsesSamplingParams) : requestParams;
|
|
507
|
+
applyReasoningEffortFallbackForRequest(payload);
|
|
508
|
+
return payload;
|
|
509
|
+
};
|
|
510
|
+
chained = { ...chained, params: await applyPayloadReplacement(chained.params) };
|
|
511
|
+
const activeRawRequestDump: RawHttpRequestDump = {
|
|
512
|
+
provider: model.provider,
|
|
513
|
+
api: output.api,
|
|
514
|
+
model: model.id,
|
|
515
|
+
method: "POST",
|
|
516
|
+
url: requestUrl,
|
|
517
|
+
body: chained.params,
|
|
518
|
+
};
|
|
519
|
+
rawRequestDump = activeRawRequestDump;
|
|
520
|
+
const openResponsesStream = (requestParams: OpenAIResponsesSamplingParams) => {
|
|
521
|
+
activeReasoningEffortFallbackKey = createOpenAIReasoningEffortFallbackKey(
|
|
522
|
+
"responses",
|
|
523
|
+
resolvedBaseUrl,
|
|
524
|
+
typeof requestParams.model === "string" ? requestParams.model : model.id,
|
|
525
|
+
);
|
|
526
|
+
activeRequestParams = requestParams;
|
|
527
|
+
return callWithCopilotModelRetry(
|
|
528
|
+
async () => {
|
|
529
|
+
let requestTimeout: NodeJS.Timeout | undefined;
|
|
530
|
+
if (requestTimeoutMs !== undefined) {
|
|
531
|
+
requestTimeout = setTimeout(
|
|
532
|
+
() => abortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
533
|
+
requestTimeoutMs,
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
const headersWithTimeout = { ...headers };
|
|
538
|
+
if (requestTimeoutMs !== undefined) {
|
|
539
|
+
headersWithTimeout["X-Stainless-Timeout"] = Math.floor(requestTimeoutMs / 1000).toString();
|
|
540
|
+
}
|
|
541
|
+
const { events, response, requestId } = await postOpenAIStream<ResponseStreamEvent>({
|
|
542
|
+
url: requestUrl,
|
|
543
|
+
headers: headersWithTimeout,
|
|
544
|
+
body: requestParams,
|
|
545
|
+
signal: requestSignal,
|
|
546
|
+
fetch: options?.fetch,
|
|
547
|
+
// Transient 408/429/5xx get Retry-After-aware transport
|
|
548
|
+
// retries; the first-event watchdog aborts `requestSignal`,
|
|
549
|
+
// so retries cannot extend the caller's deadline.
|
|
550
|
+
onSseEvent: rawSseObserver,
|
|
551
|
+
});
|
|
552
|
+
// Disarm the first-event watchdog as soon as headers arrive — a slow
|
|
553
|
+
// onResponse callback must not abort an already-connected stream.
|
|
554
|
+
if (requestTimeout !== undefined) {
|
|
555
|
+
clearTimeout(requestTimeout);
|
|
556
|
+
requestTimeout = undefined;
|
|
557
|
+
}
|
|
558
|
+
await notifyProviderResponse(options, response, model, requestId);
|
|
559
|
+
return events;
|
|
560
|
+
} finally {
|
|
561
|
+
if (requestTimeout !== undefined) clearTimeout(requestTimeout);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
{ provider: model.provider, signal: requestSignal },
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
let strictRetryAvailable = true;
|
|
568
|
+
let activeStrictToolsApplied = builtParams.strictToolsApplied;
|
|
569
|
+
let forceDisableStrictTools = false;
|
|
570
|
+
const openResponsesStreamWithFallbacks = async (): Promise<AsyncIterable<ResponseStreamEvent>> => {
|
|
571
|
+
let openaiStream: AsyncIterable<ResponseStreamEvent>;
|
|
572
|
+
while (true) {
|
|
573
|
+
try {
|
|
574
|
+
openaiStream = await openResponsesStream(chained.params);
|
|
575
|
+
if (pendingReasoningEffortFallback) {
|
|
576
|
+
rememberOpenAIReasoningEffortFallback(
|
|
577
|
+
providerSessionState,
|
|
578
|
+
pendingReasoningEffortFallback.key,
|
|
579
|
+
pendingReasoningEffortFallback.fallback,
|
|
580
|
+
);
|
|
581
|
+
pendingReasoningEffortFallback = undefined;
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
} catch (error) {
|
|
585
|
+
const capturedErrorResponse = error instanceof OpenAIHttpError ? error.captured : undefined;
|
|
586
|
+
const reasoningEffortFallback =
|
|
587
|
+
activeReasoningEffortFallbackKey && activeRequestParams && !requestSignal.aborted
|
|
588
|
+
? resolveOpenAIReasoningEffortFallback(error, capturedErrorResponse, activeRequestParams, {
|
|
589
|
+
explicitDisable: options?.disableReasoning === true && options.reasoning === undefined,
|
|
590
|
+
})
|
|
591
|
+
: undefined;
|
|
592
|
+
if (reasoningEffortFallback !== undefined && activeReasoningEffortFallbackKey) {
|
|
593
|
+
const retryMarker = `${activeReasoningEffortFallbackKey}:${String(reasoningEffortFallback)}`;
|
|
594
|
+
if (attemptedReasoningEffortFallbacks.has(retryMarker)) throw error;
|
|
595
|
+
attemptedReasoningEffortFallbacks.add(retryMarker);
|
|
596
|
+
requestReasoningEffortFallbacks.set(activeReasoningEffortFallbackKey, reasoningEffortFallback);
|
|
597
|
+
applyOpenAIReasoningEffortFallback(chained.params, reasoningEffortFallback);
|
|
598
|
+
applyOpenAIReasoningEffortFallback(activeParams, reasoningEffortFallback);
|
|
599
|
+
activeRawRequestDump.body = chained.params;
|
|
600
|
+
pendingReasoningEffortFallback = {
|
|
601
|
+
key: activeReasoningEffortFallbackKey,
|
|
602
|
+
fallback: reasoningEffortFallback,
|
|
603
|
+
};
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const compiledGrammarTooLarge =
|
|
607
|
+
isOpenRouterAnthropicModel(model) &&
|
|
608
|
+
isCompiledGrammarTooLargeStrictError(error, capturedErrorResponse);
|
|
609
|
+
const canRetryWithoutStrictTools =
|
|
610
|
+
strictRetryAvailable &&
|
|
611
|
+
!requestSignal.aborted &&
|
|
612
|
+
(compiledGrammarTooLarge ||
|
|
613
|
+
shouldRetryWithoutStrictTools(error, capturedErrorResponse, {
|
|
614
|
+
model,
|
|
615
|
+
strictToolsApplied: activeStrictToolsApplied,
|
|
616
|
+
tools: context.tools,
|
|
617
|
+
}));
|
|
618
|
+
if (canRetryWithoutStrictTools) {
|
|
619
|
+
strictRetryAvailable = false;
|
|
620
|
+
forceDisableStrictTools = true;
|
|
621
|
+
disableStrictToolsForScope(providerSessionState, strictToolsScope);
|
|
622
|
+
const fallbackBuilt = buildParams(
|
|
623
|
+
model,
|
|
624
|
+
context,
|
|
625
|
+
options,
|
|
626
|
+
providerSessionState,
|
|
627
|
+
strictToolsScope,
|
|
628
|
+
true,
|
|
629
|
+
chainState?.canAppend ? chainState.lastParams?.input : undefined,
|
|
630
|
+
);
|
|
631
|
+
const fallbackParams = fallbackBuilt.params;
|
|
632
|
+
if (chainState && !chainState.disabled) fallbackParams.store = true;
|
|
633
|
+
let fallbackChained: OpenAIResponsesChainedParams =
|
|
634
|
+
chainState && !chainState.disabled
|
|
635
|
+
? buildOpenAIResponsesChainedParams(fallbackParams, chainState)
|
|
636
|
+
: { params: fallbackParams };
|
|
637
|
+
sentPreviousResponseId = fallbackChained.previousResponseId;
|
|
638
|
+
fallbackChained = {
|
|
639
|
+
...fallbackChained,
|
|
640
|
+
params: await applyPayloadReplacement(fallbackChained.params),
|
|
641
|
+
};
|
|
642
|
+
chained = fallbackChained;
|
|
643
|
+
activeRawRequestDump.body = chained.params;
|
|
644
|
+
activeParams = fallbackParams;
|
|
645
|
+
activeStrictToolsApplied = fallbackBuilt.strictToolsApplied;
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (!chainState || !sentPreviousResponseId || requestSignal.aborted) {
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
const zdrRejection =
|
|
652
|
+
error instanceof Error &&
|
|
653
|
+
/previous[ _]?response/i.test(error.message) &&
|
|
654
|
+
/zero[ _-]?data[ _-]?retention/i.test(error.message);
|
|
655
|
+
const isPromptBlocked =
|
|
656
|
+
error instanceof Error &&
|
|
657
|
+
((error as { code?: string }).code === "invalid_prompt" ||
|
|
658
|
+
/invalid_prompt|Request blocked/i.test(error.message));
|
|
659
|
+
if (!zdrRejection && !isPromptBlocked && !isOpenAIResponsesStalePreviousResponseError(error)) {
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
662
|
+
// Server rejected the chain baseline: reset, count the failure (or
|
|
663
|
+
// disable categorically on ZDR), and retry once with the full
|
|
664
|
+
// transcript. Structurally cannot loop — the retry carries no
|
|
665
|
+
// previous_response_id.
|
|
666
|
+
if (zdrRejection) {
|
|
667
|
+
markOpenAIResponsesChainZeroDataRetention(chainState, error);
|
|
668
|
+
// ZDR orgs cannot store responses; the retry uses `store: false`.
|
|
669
|
+
} else {
|
|
670
|
+
registerOpenAIResponsesChainStaleFailure(chainState, error);
|
|
671
|
+
}
|
|
672
|
+
sentPreviousResponseId = undefined;
|
|
673
|
+
const currentBuilt = buildParams(
|
|
674
|
+
model,
|
|
675
|
+
context,
|
|
676
|
+
options,
|
|
677
|
+
providerSessionState,
|
|
678
|
+
strictToolsScope,
|
|
679
|
+
forceDisableStrictTools,
|
|
680
|
+
);
|
|
681
|
+
const currentParams = currentBuilt.params;
|
|
682
|
+
// Only ZDR forces `store: false` (the org never persists responses). A
|
|
683
|
+
// non-ZDR stale baseline is transient, so keep storing: the full-context
|
|
684
|
+
// retry must be chainable next turn, and the consecutive stale-failure
|
|
685
|
+
// breaker only trips when each retry stores and the next turn re-chains.
|
|
686
|
+
currentParams.store = !zdrRejection;
|
|
687
|
+
const retryParams = await applyPayloadReplacement(currentParams);
|
|
688
|
+
chained = { params: retryParams };
|
|
689
|
+
activeRawRequestDump.body = retryParams;
|
|
690
|
+
activeParams = currentParams;
|
|
691
|
+
activeStrictToolsApplied = currentBuilt.strictToolsApplied;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return openaiStream;
|
|
695
|
+
};
|
|
696
|
+
let openaiStream = await openResponsesStreamWithFallbacks();
|
|
697
|
+
if (premiumRequestsTotal !== undefined) output.usage.premiumRequests = premiumRequestsTotal;
|
|
698
|
+
stream.push({ type: "start", partial: output });
|
|
699
|
+
|
|
700
|
+
const nativeOutputItems: Array<Record<string, unknown>> = [];
|
|
701
|
+
let transientStreamRetryAttempt = 0;
|
|
702
|
+
while (true) {
|
|
703
|
+
let sawReplayUnsafeOutput = false;
|
|
704
|
+
let sawTerminalResponseEvent = false;
|
|
705
|
+
const attemptStream = new AssistantMessageEventStream();
|
|
706
|
+
let forwardAttemptLive = false;
|
|
707
|
+
const forwardAttemptEvents = () => {
|
|
708
|
+
for (const event of attemptStream.queue) stream.push(event);
|
|
709
|
+
attemptStream.queue.length = 0;
|
|
710
|
+
};
|
|
711
|
+
nativeOutputItems.length = 0;
|
|
712
|
+
const timedOpenaiStream = iterateWithIdleTimeout(openaiStream, {
|
|
713
|
+
idleTimeoutMs,
|
|
714
|
+
firstItemTimeoutMs: firstEventTimeoutMs,
|
|
715
|
+
firstItemErrorMessage: OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE,
|
|
716
|
+
errorMessage: "OpenAI responses stream stalled while waiting for the next event",
|
|
717
|
+
onFirstItemTimeout: () => abortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
718
|
+
onIdle: () => requestAbortController.abort(),
|
|
719
|
+
abortSignal: options?.signal,
|
|
720
|
+
isProgressItem: isOpenAIResponsesProgressEvent,
|
|
721
|
+
});
|
|
722
|
+
const observedOpenaiStream = (async function* (): AsyncGenerator<ResponseStreamEvent> {
|
|
723
|
+
for await (const event of timedOpenaiStream) {
|
|
724
|
+
if (isOpenAIResponsesReplayUnsafeEvent(event)) {
|
|
725
|
+
sawReplayUnsafeOutput = true;
|
|
726
|
+
if (!forwardAttemptLive) {
|
|
727
|
+
forwardAttemptEvents();
|
|
728
|
+
forwardAttemptLive = true;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
yield event;
|
|
732
|
+
if (forwardAttemptLive) forwardAttemptEvents();
|
|
733
|
+
}
|
|
734
|
+
})();
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
await processResponsesStream(observedOpenaiStream, output, attemptStream, model, {
|
|
738
|
+
onFirstToken: () => {
|
|
739
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
740
|
+
},
|
|
741
|
+
onOutputItemDone: item => {
|
|
742
|
+
// `processResponsesStream` hands over a private clone already; no
|
|
743
|
+
// second deep copy needed (reasoning items carry multi-KB blobs).
|
|
744
|
+
nativeOutputItems.push(item as unknown as Record<string, unknown>);
|
|
745
|
+
},
|
|
746
|
+
onCompleted: () => {
|
|
747
|
+
sawTerminalResponseEvent = true;
|
|
748
|
+
},
|
|
749
|
+
requestServiceTier: options?.serviceTier,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
const localAbortReason = abortTracker.getLocalAbortReason();
|
|
753
|
+
if (localAbortReason) throw localAbortReason;
|
|
754
|
+
if (abortTracker.wasCallerAbort()) throw new AIError.AbortError();
|
|
755
|
+
|
|
756
|
+
// Detect premature stream closure: the HTTP stream ended without the
|
|
757
|
+
// provider sending a recognized terminal response event.
|
|
758
|
+
if (!sawTerminalResponseEvent) {
|
|
759
|
+
throw new AIError.ProviderResponseError(
|
|
760
|
+
"OpenAI responses stream closed before a terminal response event was received",
|
|
761
|
+
{ provider: model.provider, kind: "incomplete-stream" },
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
766
|
+
throw new AIError.ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
|
|
767
|
+
provider: model.provider,
|
|
768
|
+
kind: "runtime",
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
forwardAttemptEvents();
|
|
772
|
+
break;
|
|
773
|
+
} catch (error) {
|
|
774
|
+
const streamFailure = abortTracker.getLocalAbortReason() ?? error;
|
|
775
|
+
const canRetry =
|
|
776
|
+
!sawReplayUnsafeOutput &&
|
|
777
|
+
!requestSignal.aborted &&
|
|
778
|
+
!abortTracker.wasCallerAbort() &&
|
|
779
|
+
transientStreamRetryAttempt < OPENAI_RESPONSES_MAX_TRANSIENT_STREAM_RETRIES &&
|
|
780
|
+
isRetryableOpenAIResponsesStreamFailure(streamFailure);
|
|
781
|
+
if (!canRetry) {
|
|
782
|
+
forwardAttemptEvents();
|
|
783
|
+
throw streamFailure;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
transientStreamRetryAttempt++;
|
|
787
|
+
logger.debug("OpenAI responses stream ended before replay-unsafe output; retrying", {
|
|
788
|
+
provider: model.provider,
|
|
789
|
+
model: model.id,
|
|
790
|
+
attempt: transientStreamRetryAttempt,
|
|
791
|
+
error: streamFailure instanceof Error ? streamFailure.message : String(streamFailure),
|
|
792
|
+
});
|
|
793
|
+
const retryOutput = createInitialResponsesAssistantMessage(model.api, model.provider, model.id);
|
|
794
|
+
output.content.length = 0;
|
|
795
|
+
output.responseId = undefined;
|
|
796
|
+
output.upstreamProvider = undefined;
|
|
797
|
+
output.errorMessage = undefined;
|
|
798
|
+
output.errorStatus = undefined;
|
|
799
|
+
output.errorId = undefined;
|
|
800
|
+
output.stopDetails = undefined;
|
|
801
|
+
output.providerPayload = undefined;
|
|
802
|
+
output.usage = retryOutput.usage;
|
|
803
|
+
if (premiumRequestsTotal !== undefined) output.usage.premiumRequests = premiumRequestsTotal;
|
|
804
|
+
output.stopReason = "stop";
|
|
805
|
+
output.duration = undefined;
|
|
806
|
+
output.ttft = undefined;
|
|
807
|
+
firstTokenTime = undefined;
|
|
808
|
+
nativeOutputItems.length = 0;
|
|
809
|
+
|
|
810
|
+
if (options?.providerRetryWait) {
|
|
811
|
+
await options.providerRetryWait(OPENAI_RESPONSES_TRANSIENT_STREAM_RETRY_DELAY_MS, options.signal);
|
|
812
|
+
} else {
|
|
813
|
+
await scheduler.wait(OPENAI_RESPONSES_TRANSIENT_STREAM_RETRY_DELAY_MS, { signal: options?.signal });
|
|
814
|
+
}
|
|
815
|
+
if (abortTracker.wasCallerAbort()) throw new AIError.AbortError();
|
|
816
|
+
openaiStream = await openResponsesStreamWithFallbacks();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, nativeOutputItems);
|
|
821
|
+
const replayableResponseItems = sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(
|
|
822
|
+
structuredCloneJSON(nativeOutputItems),
|
|
823
|
+
);
|
|
824
|
+
if (replayableResponseItems) {
|
|
825
|
+
if (providerSessionState) providerSessionState.nativeHistoryReplayWarmed = true;
|
|
826
|
+
if (chainState) {
|
|
827
|
+
chainState.lastParams = structuredCloneJSON(activeParams);
|
|
828
|
+
chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
|
|
829
|
+
if (output.responseId) {
|
|
830
|
+
chainState.lastResponseId = output.responseId;
|
|
831
|
+
chainState.lastResponseItems = replayableResponseItems;
|
|
832
|
+
chainState.canAppend = true;
|
|
833
|
+
// Only a successful CHAINED completion clears the stale counter — a
|
|
834
|
+
// full-context success must not mask categorical rejection.
|
|
835
|
+
if (sentPreviousResponseId) chainState.staleFailures = 0;
|
|
836
|
+
} else {
|
|
837
|
+
// Without a response id the append baseline cannot be trusted.
|
|
838
|
+
chainState.canAppend = false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
} else if (chainState) {
|
|
842
|
+
// Hidden-empty / fully sanitized successes cannot be used as an append
|
|
843
|
+
// baseline, but `lastParams` still records the successful wire controls
|
|
844
|
+
// without re-enabling `previous_response_id` chaining.
|
|
845
|
+
chainState.canAppend = false;
|
|
846
|
+
chainState.lastParams = structuredCloneJSON(activeParams);
|
|
847
|
+
chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
|
|
848
|
+
chainState.lastResponseId = undefined;
|
|
849
|
+
chainState.lastResponseItems = undefined;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
output.duration = performance.now() - startTime;
|
|
853
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
854
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
855
|
+
stream.end();
|
|
856
|
+
} catch (error) {
|
|
857
|
+
if (chainState) resetOpenAIResponsesChainState(chainState);
|
|
858
|
+
const capturedErrorResponse = error instanceof OpenAIHttpError ? error.captured : undefined;
|
|
859
|
+
const result = await AIError.finalize(error, {
|
|
860
|
+
api: model.api,
|
|
861
|
+
provider: model.provider,
|
|
862
|
+
abortTracker,
|
|
863
|
+
rawRequestDump,
|
|
864
|
+
capturedErrorResponse,
|
|
865
|
+
});
|
|
866
|
+
output.stopReason = result.stopReason;
|
|
867
|
+
output.errorStatus = result.status;
|
|
868
|
+
output.errorId = result.id;
|
|
869
|
+
output.errorMessage = result.message;
|
|
870
|
+
// Some providers via OpenRouter include extra details here.
|
|
871
|
+
const rawMetadata = (error as { error?: { metadata?: { raw?: string } } })?.error?.metadata?.raw;
|
|
872
|
+
if (rawMetadata) output.errorMessage += `\n${rawMetadata}`;
|
|
873
|
+
output.duration = performance.now() - startTime;
|
|
874
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
875
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
876
|
+
stream.end();
|
|
877
|
+
}
|
|
878
|
+
})();
|
|
879
|
+
|
|
880
|
+
return stream;
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Public entry: wrap the single-attempt Responses streamer with bounded
|
|
885
|
+
* empty-completion retries — a `response.completed` carrying no content/usage
|
|
886
|
+
* would otherwise stall the agent loop. Shared with the OpenAI-completions and
|
|
887
|
+
* Anthropic providers via `withEmptyCompletionRetry`.
|
|
888
|
+
*/
|
|
889
|
+
export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (model, context, options) =>
|
|
890
|
+
withEmptyCompletionRetry(model, context, options, streamOpenAIResponsesOnce);
|
|
891
|
+
|
|
892
|
+
function isOfficialOpenAIResponsesEndpoint(model: Model<"openai-responses">): boolean {
|
|
893
|
+
if (model.provider !== "openai") return false;
|
|
894
|
+
if (!model.baseUrl) return true;
|
|
895
|
+
try {
|
|
896
|
+
return new URL(model.baseUrl).hostname === "api.openai.com";
|
|
897
|
+
} catch {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function isResponsesPromptCacheableContentBlock(block: unknown): block is ResponseInputContent {
|
|
903
|
+
if (typeof block !== "object" || block === null || !("type" in block)) return false;
|
|
904
|
+
return block.type === "input_text" || block.type === "input_image" || block.type === "input_file";
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
type ResponsesPromptCacheableMessage = {
|
|
908
|
+
role: "assistant" | "developer" | "system" | "user";
|
|
909
|
+
content: ResponseInputContent[];
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
function isResponsesPromptCacheableMessage(item: unknown): item is ResponsesPromptCacheableMessage {
|
|
913
|
+
if (typeof item !== "object" || item === null || !("role" in item) || !("content" in item)) return false;
|
|
914
|
+
if (item.role !== "assistant" && item.role !== "developer" && item.role !== "system" && item.role !== "user")
|
|
915
|
+
return false;
|
|
916
|
+
return Array.isArray(item.content) && item.content.every(isResponsesPromptCacheableContentBlock);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
type ResponsesStringInstruction = {
|
|
920
|
+
role: "developer" | "system";
|
|
921
|
+
content: string;
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
function isStableStringResponsesInstruction(item: unknown): item is ResponsesStringInstruction {
|
|
925
|
+
if (typeof item !== "object" || item === null || !("role" in item) || !("content" in item)) return false;
|
|
926
|
+
return (
|
|
927
|
+
(item.role === "developer" || item.role === "system") &&
|
|
928
|
+
typeof item.content === "string" &&
|
|
929
|
+
item.content.length > 0
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function matchesResponsesCacheBaseline(
|
|
934
|
+
baseline: ResponsesPromptCacheableMessage,
|
|
935
|
+
current: ResponsesPromptCacheableMessage,
|
|
936
|
+
): boolean {
|
|
937
|
+
if (baseline.role !== current.role || baseline.content.length !== current.content.length) return false;
|
|
938
|
+
for (let index = 0; index < baseline.content.length; index++) {
|
|
939
|
+
const baselineBlock = baseline.content[index];
|
|
940
|
+
const currentBlock = current.content[index];
|
|
941
|
+
if (!baselineBlock || !currentBlock) return false;
|
|
942
|
+
const breakpoint = baselineBlock.prompt_cache_breakpoint;
|
|
943
|
+
if (breakpoint) {
|
|
944
|
+
if (!Bun.deepEquals(baselineBlock, { ...currentBlock, prompt_cache_breakpoint: breakpoint })) return false;
|
|
945
|
+
} else if (!Bun.deepEquals(baselineBlock, currentBlock)) {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function restoreResponsesCacheBreakpointsFromBaseline(
|
|
953
|
+
input: ResponseInput | undefined,
|
|
954
|
+
baseline: ResponseInput | undefined,
|
|
955
|
+
): boolean {
|
|
956
|
+
if (!input || !baseline) return false;
|
|
957
|
+
let restored = false;
|
|
958
|
+
for (let i = 0; i < baseline.length && i < input.length; i++) {
|
|
959
|
+
const baselineMessage = baseline[i];
|
|
960
|
+
const message = input[i];
|
|
961
|
+
if (!isResponsesPromptCacheableMessage(baselineMessage)) continue;
|
|
962
|
+
|
|
963
|
+
if (isStableStringResponsesInstruction(message)) {
|
|
964
|
+
const [baselineBlock] = baselineMessage.content;
|
|
965
|
+
if (
|
|
966
|
+
baselineMessage.role === message.role &&
|
|
967
|
+
baselineBlock?.type === "input_text" &&
|
|
968
|
+
baselineBlock.text === message.content &&
|
|
969
|
+
baselineBlock.prompt_cache_breakpoint
|
|
970
|
+
) {
|
|
971
|
+
Object.assign(message, {
|
|
972
|
+
content: [
|
|
973
|
+
{
|
|
974
|
+
type: "input_text",
|
|
975
|
+
text: message.content,
|
|
976
|
+
prompt_cache_breakpoint: baselineBlock.prompt_cache_breakpoint,
|
|
977
|
+
},
|
|
978
|
+
],
|
|
979
|
+
});
|
|
980
|
+
restored = true;
|
|
981
|
+
}
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (!isResponsesPromptCacheableMessage(message) || !matchesResponsesCacheBaseline(baselineMessage, message))
|
|
986
|
+
continue;
|
|
987
|
+
for (let j = 0; j < baselineMessage.content.length; j++) {
|
|
988
|
+
const baselineBlock = baselineMessage.content[j];
|
|
989
|
+
const block = message.content[j];
|
|
990
|
+
if (!baselineBlock?.prompt_cache_breakpoint || !block) continue;
|
|
991
|
+
Object.assign(block, { prompt_cache_breakpoint: baselineBlock.prompt_cache_breakpoint });
|
|
992
|
+
restored = true;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return restored;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function hasResponsesCacheBreakpoint(input: ResponseInput | undefined): boolean {
|
|
999
|
+
return (
|
|
1000
|
+
input?.some(
|
|
1001
|
+
message =>
|
|
1002
|
+
isResponsesPromptCacheableMessage(message) &&
|
|
1003
|
+
message.content.some(block => block.prompt_cache_breakpoint !== undefined),
|
|
1004
|
+
) ?? false
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function markLatestStableResponsesCacheBreakpoint(
|
|
1009
|
+
input: ResponseInput | undefined,
|
|
1010
|
+
statefulBaseline?: ResponseInput,
|
|
1011
|
+
): boolean {
|
|
1012
|
+
if (!input) return false;
|
|
1013
|
+
// Stateful appends use a strict wire-prefix comparison. Retain the exact
|
|
1014
|
+
// marker from that prefix rather than recomputing a newer boundary.
|
|
1015
|
+
if (statefulBaseline) {
|
|
1016
|
+
if (restoreResponsesCacheBreakpointsFromBaseline(input, statefulBaseline)) return true;
|
|
1017
|
+
// A prior marker whose content no longer matches means chaining will
|
|
1018
|
+
// reset to a full replay. Recompute a fresh boundary for that replay.
|
|
1019
|
+
// Markerless baselines stay markerless so appends do not mutate them.
|
|
1020
|
+
if (!hasResponsesCacheBreakpoint(statefulBaseline)) return false;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
let latestInputMessage = -1;
|
|
1024
|
+
for (let i = input.length - 1; i >= 0; i--) {
|
|
1025
|
+
const message = input[i];
|
|
1026
|
+
if (!("role" in message)) continue;
|
|
1027
|
+
if (message.role === "user" || message.role === "developer") {
|
|
1028
|
+
latestInputMessage = i;
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (latestInputMessage <= 0) return false;
|
|
1033
|
+
|
|
1034
|
+
for (let i = latestInputMessage - 1; i >= 0; i--) {
|
|
1035
|
+
const message = input[i];
|
|
1036
|
+
if (isStableStringResponsesInstruction(message)) {
|
|
1037
|
+
const text = message.content;
|
|
1038
|
+
Object.assign(message, {
|
|
1039
|
+
content: [
|
|
1040
|
+
{
|
|
1041
|
+
type: "input_text",
|
|
1042
|
+
text,
|
|
1043
|
+
prompt_cache_breakpoint: { mode: "explicit" },
|
|
1044
|
+
},
|
|
1045
|
+
],
|
|
1046
|
+
});
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
if (!isResponsesPromptCacheableMessage(message)) continue;
|
|
1050
|
+
for (let j = message.content.length - 1; j >= 0; j--) {
|
|
1051
|
+
const block = message.content[j];
|
|
1052
|
+
if (!isResponsesPromptCacheableContentBlock(block)) continue;
|
|
1053
|
+
Object.assign(block, { prompt_cache_breakpoint: { mode: "explicit" } });
|
|
1054
|
+
return true;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function applyOpenAIResponsesPromptCachePolicy(
|
|
1061
|
+
params: OpenAIResponsesSamplingParams,
|
|
1062
|
+
model: Model<"openai-responses">,
|
|
1063
|
+
options: OpenAIResponsesOptions | undefined,
|
|
1064
|
+
statefulCacheBaseline?: ResponseInput,
|
|
1065
|
+
): void {
|
|
1066
|
+
const promptCache = options?.promptCache;
|
|
1067
|
+
if (!promptCache || resolveCacheRetention(options?.cacheRetention) === "none") return;
|
|
1068
|
+
if (!model.compat.supportsPromptCacheBreakpoints) {
|
|
1069
|
+
if (promptCache.mode === "explicit") {
|
|
1070
|
+
throw new AIError.ConfigurationError(
|
|
1071
|
+
`OpenAI explicit prompt caching is unsupported for ${model.provider}/${model.id}; enable compat.supportsPromptCacheBreakpoints only for a compatible endpoint.`,
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
params.prompt_cache_options = {
|
|
1078
|
+
mode: promptCache.mode,
|
|
1079
|
+
ttl: promptCache.ttl ?? model.compat.promptCacheBreakpointTtl,
|
|
1080
|
+
};
|
|
1081
|
+
if (promptCache.mode === "explicit" && promptCache.breakpoint !== "none")
|
|
1082
|
+
markLatestStableResponsesCacheBreakpoint(params.input, statefulCacheBaseline);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export function buildParams(
|
|
1086
|
+
model: Model<"openai-responses">,
|
|
1087
|
+
context: Context,
|
|
1088
|
+
options: OpenAIResponsesOptions | undefined,
|
|
1089
|
+
providerSessionState: OpenAIResponsesProviderSessionState | undefined,
|
|
1090
|
+
strictToolsScope?: OpenAIStrictToolsScope,
|
|
1091
|
+
disableStrictToolsOverride = false,
|
|
1092
|
+
statefulCacheBaseline?: ResponseInput,
|
|
1093
|
+
): { params: OpenAIResponsesSamplingParams; strictToolsApplied: boolean } {
|
|
1094
|
+
const policy = resolveOpenAICompatPolicy(model, {
|
|
1095
|
+
endpoint: "responses",
|
|
1096
|
+
reasoning: options?.reasoning,
|
|
1097
|
+
disableReasoning: options?.disableReasoning,
|
|
1098
|
+
toolChoice: options?.toolChoice,
|
|
1099
|
+
strictResponsesPairing: options?.strictResponsesPairing,
|
|
1100
|
+
includeEncryptedReasoning: options?.includeEncryptedReasoning,
|
|
1101
|
+
filterReasoningHistory: options?.filterReasoningHistory,
|
|
1102
|
+
omitReasoningEffort: options?.omitReasoningEffort,
|
|
1103
|
+
});
|
|
1104
|
+
const strictResponsesPairing = policy.tools.strictResponsesPairing;
|
|
1105
|
+
const shouldReplayNativeHistory = providerSessionState?.nativeHistoryReplayWarmed ?? true;
|
|
1106
|
+
const messages = buildResponsesInput({
|
|
1107
|
+
model,
|
|
1108
|
+
context,
|
|
1109
|
+
strictResponsesPairing,
|
|
1110
|
+
supportsImageDetailOriginal: model.compat.supportsImageDetailOriginal,
|
|
1111
|
+
nativeHistory: {
|
|
1112
|
+
replay: shouldReplayNativeHistory,
|
|
1113
|
+
filterReasoning: policy.reasoning.filterReasoningHistory,
|
|
1114
|
+
},
|
|
1115
|
+
includeThinkingSignatures: shouldReplayNativeHistory && !policy.reasoning.filterReasoningHistory,
|
|
1116
|
+
repairOrphanOutputs: true,
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
|
1120
|
+
let systemInstructions: string | undefined;
|
|
1121
|
+
if (systemPrompts.length > 0) {
|
|
1122
|
+
const needsDeveloperRole = policy.messages.systemRole === "developer";
|
|
1123
|
+
if (needsDeveloperRole) {
|
|
1124
|
+
// Reasoning models on known OpenAI-compatible endpoints require the
|
|
1125
|
+
// `developer` role. Send all system prompts inline in `input`.
|
|
1126
|
+
messages.unshift(
|
|
1127
|
+
...systemPrompts.map(systemPrompt => ({ role: "developer" as const, content: systemPrompt })),
|
|
1128
|
+
);
|
|
1129
|
+
} else {
|
|
1130
|
+
// All other endpoints (including third-party /v1/responses proxies) use
|
|
1131
|
+
// the canonical top-level `instructions` field so that proxies that
|
|
1132
|
+
// reject `input[{role:"system"}]` work out of the box.
|
|
1133
|
+
systemInstructions = systemPrompts.join("\n\n");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
1138
|
+
const promptCacheKey = getOpenAIPromptCacheKey(options);
|
|
1139
|
+
const modelId = applyWireModelIdTransform(
|
|
1140
|
+
model.requestModelId ?? model.id,
|
|
1141
|
+
model.compat.wireModelIdMode,
|
|
1142
|
+
options?.openrouterVariant,
|
|
1143
|
+
);
|
|
1144
|
+
const params: OpenAIResponsesSamplingParams = {
|
|
1145
|
+
model: modelId,
|
|
1146
|
+
input: messages,
|
|
1147
|
+
instructions: systemInstructions,
|
|
1148
|
+
stream: true,
|
|
1149
|
+
prompt_cache_key: promptCacheKey,
|
|
1150
|
+
prompt_cache_retention: promptCacheKey
|
|
1151
|
+
? cacheRetention === "long" && model.compat.supportsLongPromptCacheRetention
|
|
1152
|
+
? "24h"
|
|
1153
|
+
: undefined
|
|
1154
|
+
: undefined,
|
|
1155
|
+
// Gateway routing: OpenRouter-only Responses wire field for sticky upstream
|
|
1156
|
+
// routing + observability grouping; no equivalent on direct OpenAI.
|
|
1157
|
+
session_id: model.compat.isOpenRouterHost ? getOpenRouterResponsesSessionId(options) : undefined,
|
|
1158
|
+
store: false,
|
|
1159
|
+
stream_options: model.compat.supportsObfuscationOptOut ? { include_obfuscation: false } : undefined,
|
|
1160
|
+
};
|
|
1161
|
+
if (options?.include?.length) params.include = Array.from(new Set(options.include));
|
|
1162
|
+
maybeAddOpenRouterAnthropicCacheControl(params, model, cacheRetention);
|
|
1163
|
+
const outputToken = resolveOpenAIOutputTokenParam({
|
|
1164
|
+
field: "max_output_tokens",
|
|
1165
|
+
maxTokens: options?.maxTokens,
|
|
1166
|
+
maxTokensExplicit: options?.maxTokensExplicit ?? options?.maxTokens !== undefined,
|
|
1167
|
+
modelMaxTokens: model.maxTokens,
|
|
1168
|
+
omitMaxOutputTokens: model.omitMaxOutputTokens ?? false,
|
|
1169
|
+
isOpenRouterHost: model.compat.isOpenRouterHost,
|
|
1170
|
+
alwaysSendMaxTokens: model.compat.alwaysSendMaxTokens,
|
|
1171
|
+
providerOutputClamp: resolveOpenAIResponsesOutputClamp(model),
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
applyCommonResponsesSamplingParams(params, { ...options, maxTokens: outputToken?.value }, model);
|
|
1175
|
+
if (options?.textVerbosity && isOfficialOpenAIResponsesEndpoint(model)) {
|
|
1176
|
+
params.text = { ...params.text, verbosity: options.textVerbosity };
|
|
1177
|
+
}
|
|
1178
|
+
// TODO: openai responses has no top-level `stop`/`stop_sequences`; surface via reasoning.stop?
|
|
1179
|
+
// `StreamOptions.stopSequences` is intentionally dropped for this provider.
|
|
1180
|
+
// TODO: openai responses has no top-level `frequency_penalty` field as of the current SDK;
|
|
1181
|
+
// `StreamOptions.frequencyPenalty` is intentionally dropped for this provider.
|
|
1182
|
+
|
|
1183
|
+
let strictToolsApplied = false;
|
|
1184
|
+
if (context.tools) {
|
|
1185
|
+
const disableStrictTools =
|
|
1186
|
+
disableStrictToolsOverride || isStrictToolsDisabledForScope(providerSessionState, strictToolsScope);
|
|
1187
|
+
const strictMode = !disableStrictTools && model.compat.supportsStrictMode !== false;
|
|
1188
|
+
params.tools = convertTools(context.tools, strictMode, model);
|
|
1189
|
+
strictToolsApplied = params.tools.some(t => (t as { strict?: boolean }).strict === true);
|
|
1190
|
+
if (options?.toolChoice) {
|
|
1191
|
+
// Map tool_choice against the tools that survived quarantine, not the
|
|
1192
|
+
// original list: a forced choice for a dropped tool — or "required" when
|
|
1193
|
+
// every tool was dropped — would otherwise send a tool_choice with no
|
|
1194
|
+
// matching tool, which the provider rejects just like the bad schema did (#2652).
|
|
1195
|
+
const emittedNames = new Set(
|
|
1196
|
+
params.tools.map(t => (t as { name?: string }).name).filter((n): n is string => n !== undefined),
|
|
1197
|
+
);
|
|
1198
|
+
const emittedComputer = params.tools.some(tool => tool.type === "computer");
|
|
1199
|
+
const survivingTools =
|
|
1200
|
+
params.tools.length === context.tools.length
|
|
1201
|
+
? context.tools
|
|
1202
|
+
: context.tools.filter(
|
|
1203
|
+
t =>
|
|
1204
|
+
emittedNames.has(t.customWireName ?? t.name) ||
|
|
1205
|
+
(t.native?.type === "computer" && emittedComputer),
|
|
1206
|
+
);
|
|
1207
|
+
const toolChoice = mapOpenAIResponsesToolChoiceForTools(options.toolChoice, survivingTools, model);
|
|
1208
|
+
if (toolChoice !== undefined && params.tools.length > 0) {
|
|
1209
|
+
if (
|
|
1210
|
+
typeof toolChoice === "object" &&
|
|
1211
|
+
toolChoice.type === "function" &&
|
|
1212
|
+
!model.compat.supportsNamedToolChoice
|
|
1213
|
+
) {
|
|
1214
|
+
// String-only hosts cannot receive the named object. Restrict the
|
|
1215
|
+
// catalogue first so "required" still forces the requested tool.
|
|
1216
|
+
params.tools = params.tools.filter(tool => tool.type === "function" && tool.name === toolChoice.name);
|
|
1217
|
+
params.tool_choice = "required";
|
|
1218
|
+
} else {
|
|
1219
|
+
params.tool_choice = toolChoice;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const reasoningPolicy = resolveOpenAICompatPolicy(model, {
|
|
1226
|
+
endpoint: "responses",
|
|
1227
|
+
reasoning: options?.reasoning,
|
|
1228
|
+
disableReasoning: options?.disableReasoning,
|
|
1229
|
+
toolChoice: params.tool_choice,
|
|
1230
|
+
strictResponsesPairing: options?.strictResponsesPairing,
|
|
1231
|
+
includeEncryptedReasoning: options?.includeEncryptedReasoning,
|
|
1232
|
+
filterReasoningHistory: options?.filterReasoningHistory,
|
|
1233
|
+
omitReasoningEffort: options?.omitReasoningEffort,
|
|
1234
|
+
});
|
|
1235
|
+
const reasoningSummary =
|
|
1236
|
+
model.provider === "xai-oauth"
|
|
1237
|
+
? options?.reasoning === undefined
|
|
1238
|
+
? undefined
|
|
1239
|
+
: null
|
|
1240
|
+
: options?.reasoningSummary;
|
|
1241
|
+
applyResponsesCompatPolicy(params, reasoningPolicy, {
|
|
1242
|
+
reasoningSummary,
|
|
1243
|
+
mapEffort: effort =>
|
|
1244
|
+
model.compat.reasoningEffortMap?.[effort as NonNullable<OpenAIResponsesOptions["reasoning"]>] ??
|
|
1245
|
+
model.thinking?.effortMap?.[effort as NonNullable<OpenAIResponsesOptions["reasoning"]>] ??
|
|
1246
|
+
effort,
|
|
1247
|
+
});
|
|
1248
|
+
// Catalog pro aliases (`gpt-5.6-*-pro`): merge AFTER the compat policy so the
|
|
1249
|
+
// mode survives every policy branch (disabled/omitted effort included) while
|
|
1250
|
+
// keeping whatever effort/summary the policy produced — mode and effort are
|
|
1251
|
+
// independent wire fields.
|
|
1252
|
+
if (model.reasoningMode) {
|
|
1253
|
+
params.reasoning = { ...params.reasoning, mode: model.reasoningMode };
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (model.compat.isVercelGatewayHost) {
|
|
1257
|
+
applyVercelResponsesCacheControls(params, model.compat, cacheRetention);
|
|
1258
|
+
} else {
|
|
1259
|
+
applyOpenAIGatewayRouting(params, model.compat);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
applyOpenAIExtraBody(params, options?.extraBody);
|
|
1263
|
+
applyOpenAIResponsesPromptCachePolicy(params, model, options, statefulCacheBaseline);
|
|
1264
|
+
|
|
1265
|
+
return { params, strictToolsApplied };
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Whether this model should get the OpenAI custom-tool grammar variant
|
|
1270
|
+
* for `apply_patch`. The generated model catalog sets
|
|
1271
|
+
* `model.applyPatchToolType` for first-party GPT-5 Responses models; this
|
|
1272
|
+
* runtime path only consumes that metadata.
|
|
1273
|
+
* @internal Exported for tests.
|
|
1274
|
+
*/
|
|
1275
|
+
export function supportsFreeformApplyPatch(
|
|
1276
|
+
model: Model<"openai-responses" | "azure-openai-responses" | "openai-codex-responses">,
|
|
1277
|
+
): boolean {
|
|
1278
|
+
return model.applyPatchToolType === "freeform";
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/** @internal Exported for tests. */
|
|
1282
|
+
export function mapOpenAIResponsesToolChoiceForTools(
|
|
1283
|
+
choice: ToolChoice | undefined,
|
|
1284
|
+
tools: Tool[],
|
|
1285
|
+
model: Model<"openai-responses">,
|
|
1286
|
+
): OpenAIResponsesToolChoice {
|
|
1287
|
+
if (!model.compat.supportsToolChoice) return undefined;
|
|
1288
|
+
if (isForcedToolChoice(choice) && !model.compat.supportsForcedToolChoice) {
|
|
1289
|
+
return "auto";
|
|
1290
|
+
}
|
|
1291
|
+
if (typeof choice !== "string" && choice?.type === "computer") {
|
|
1292
|
+
const computer = tools.find(tool => tool.native?.type === "computer");
|
|
1293
|
+
if (!computer) return undefined;
|
|
1294
|
+
return model.supportsComputerUse === true ? { type: "computer" } : { type: "function", name: computer.name };
|
|
1295
|
+
}
|
|
1296
|
+
const mapped = mapToOpenAIResponsesToolChoice(choice);
|
|
1297
|
+
if (!mapped || typeof mapped === "string" || mapped.type !== "function") {
|
|
1298
|
+
return mapped;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const directTool = tools.find(tool => tool.name === mapped.name);
|
|
1302
|
+
const customTool = supportsFreeformApplyPatch(model)
|
|
1303
|
+
? tools.find(tool => tool.customFormat && (tool.name === mapped.name || tool.customWireName === mapped.name))
|
|
1304
|
+
: undefined;
|
|
1305
|
+
const offeredTool = customTool ?? directTool;
|
|
1306
|
+
if (offeredTool?.native?.type === "computer") {
|
|
1307
|
+
return model.supportsComputerUse === true ? { type: "computer" } : { type: "function", name: offeredTool.name };
|
|
1308
|
+
}
|
|
1309
|
+
if (!offeredTool) {
|
|
1310
|
+
return undefined;
|
|
1311
|
+
}
|
|
1312
|
+
return customTool ? { type: "custom", name: customTool.customWireName ?? customTool.name } : mapped;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/** @internal Exported for tests. */
|
|
1316
|
+
export function convertTools(
|
|
1317
|
+
tools: Tool[],
|
|
1318
|
+
strictMode: boolean,
|
|
1319
|
+
model: Model<"openai-responses" | "azure-openai-responses" | "openai-codex-responses">,
|
|
1320
|
+
onQuarantine: (toolName: string, schemaPath: string) => void = (toolName, schemaPath) =>
|
|
1321
|
+
logger.warn(
|
|
1322
|
+
`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.`,
|
|
1323
|
+
),
|
|
1324
|
+
): OpenAITool[] {
|
|
1325
|
+
const allowFreeform = supportsFreeformApplyPatch(model);
|
|
1326
|
+
const out: OpenAITool[] = [];
|
|
1327
|
+
for (const tool of tools) {
|
|
1328
|
+
if (tool.native?.type === "computer" && model.supportsComputerUse === true) {
|
|
1329
|
+
out.push({ type: "computer" });
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
// Models without native computer support fall through and receive the
|
|
1333
|
+
// tool as a plain function tool (name/description/schema below), so
|
|
1334
|
+
// function-calling models can still drive the desktop.
|
|
1335
|
+
if (allowFreeform && tool.customFormat) {
|
|
1336
|
+
out.push({
|
|
1337
|
+
type: "custom",
|
|
1338
|
+
// Tool advertises its wire-level name (e.g. `apply_patch`) — the
|
|
1339
|
+
// agent-loop dispatcher will match incoming calls by either the
|
|
1340
|
+
// internal `name` or `customWireName`.
|
|
1341
|
+
name: tool.customWireName ?? tool.name,
|
|
1342
|
+
description: tool.description || "",
|
|
1343
|
+
format: {
|
|
1344
|
+
type: "grammar",
|
|
1345
|
+
syntax: tool.customFormat.syntax,
|
|
1346
|
+
definition: compactGrammarDefinition(tool.customFormat.syntax, tool.customFormat.definition),
|
|
1347
|
+
},
|
|
1348
|
+
} as unknown as OpenAITool);
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const strict = !NO_STRICT && strictMode && tool.strict !== false;
|
|
1352
|
+
const baseParameters = toolWireSchema(tool);
|
|
1353
|
+
// MFJS must run AFTER the Responses sanitizer: the sanitizer normalizes
|
|
1354
|
+
// `{}` → `true` (issue #1179), and Moonshot's validator rejects boolean
|
|
1355
|
+
// subschemas ("property schema … must be an object"), so the Moonshot
|
|
1356
|
+
// pass re-coerces them last.
|
|
1357
|
+
const sanitized = sanitizeSchemaForOpenAIResponses(baseParameters);
|
|
1358
|
+
const responseParameters =
|
|
1359
|
+
model.compat.toolSchemaFlavor === "moonshot-mfjs"
|
|
1360
|
+
? (normalizeSchemaForMoonshot(sanitized) as Record<string, unknown>)
|
|
1361
|
+
: sanitized;
|
|
1362
|
+
const { schema: parameters, strict: effectiveStrict } = adaptSchemaForStrict(responseParameters, strict);
|
|
1363
|
+
// Quarantine a tool whose emitted schema carries a provider-rejecting
|
|
1364
|
+
// enum/const-vs-type contradiction: dropping just that tool keeps the rest
|
|
1365
|
+
// of the request valid instead of letting one bad MCP schema 400 the whole
|
|
1366
|
+
// turn (#2652). Other tools and built-ins are unaffected.
|
|
1367
|
+
const violation = findStrictToolSchemaViolation(parameters);
|
|
1368
|
+
if (violation) {
|
|
1369
|
+
onQuarantine(tool.name, violation);
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
out.push({
|
|
1373
|
+
type: "function",
|
|
1374
|
+
name: tool.name,
|
|
1375
|
+
description: tool.description || "",
|
|
1376
|
+
parameters,
|
|
1377
|
+
// `strict: false` and an omitted `strict` are NOT equivalent for every
|
|
1378
|
+
// OpenAI-compat backend — some over-fill optional args when the flag is
|
|
1379
|
+
// absent (#4336). Preserve the author's explicit `false` unless the
|
|
1380
|
+
// provider is explicitly known not to understand the field
|
|
1381
|
+
// (`supportsStrictMode: false`) or the strict-schema fallback is
|
|
1382
|
+
// active — both paths rely on a uniformly absent wire flag. Mirrors the
|
|
1383
|
+
// `supportsStrictMode !== false` gate used by openai-completions
|
|
1384
|
+
// (#4527).
|
|
1385
|
+
...(effectiveStrict
|
|
1386
|
+
? { strict: true }
|
|
1387
|
+
: !NO_STRICT && strictMode && tool.strict === false
|
|
1388
|
+
? { strict: false }
|
|
1389
|
+
: {}),
|
|
1390
|
+
} as OpenAITool);
|
|
1391
|
+
}
|
|
1392
|
+
return out;
|
|
1393
|
+
}
|