@aryee337/aery-ai 0.2.28 → 0.2.29
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 +2914 -0
- package/README.md +614 -813
- package/package.json +140 -105
- package/src/api-registry.ts +96 -0
- package/src/auth-broker/client.ts +358 -0
- package/src/auth-broker/index.ts +5 -0
- package/src/auth-broker/refresher.ts +117 -0
- package/src/auth-broker/remote-store.ts +623 -0
- package/src/auth-broker/server.ts +644 -0
- package/src/auth-broker/types.ts +127 -0
- package/src/auth-broker/wire-schemas.ts +200 -0
- package/src/auth-gateway/http.ts +194 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +818 -0
- package/src/auth-gateway/types.ts +143 -0
- package/src/auth-storage.ts +4422 -0
- package/src/index.ts +54 -0
- package/src/model-cache.ts +129 -0
- package/src/model-manager.ts +469 -0
- package/src/model-thinking.ts +782 -0
- package/src/models.json +83530 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +90 -0
- package/src/provider-models/bundled-references.ts +38 -0
- package/src/provider-models/descriptors.ts +355 -0
- package/src/provider-models/google.ts +88 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +153 -0
- package/src/provider-models/openai-compat.ts +2817 -0
- package/src/provider-models/special.ts +67 -0
- package/src/providers/aery-native-client.ts +228 -0
- package/src/providers/aery-native-server.ts +212 -0
- package/src/providers/amazon-bedrock.ts +873 -0
- package/src/providers/anthropic-client.ts +318 -0
- package/src/providers/anthropic-messages-server-schema.ts +243 -0
- package/src/providers/anthropic-messages-server.ts +683 -0
- package/src/providers/anthropic-wire.ts +268 -0
- package/src/providers/anthropic.ts +3094 -0
- package/src/providers/aws-credentials.ts +501 -0
- package/src/providers/aws-eventstream.ts +185 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +361 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2621 -0
- package/src/providers/error-message.ts +21 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +372 -0
- package/src/providers/google-auth.ts +252 -0
- package/src/providers/google-gemini-cli.ts +809 -0
- package/src/providers/google-gemini-headers.ts +41 -0
- package/src/providers/google-shared.ts +917 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.ts +91 -0
- package/src/providers/google.ts +41 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +52 -0
- package/src/providers/mock.ts +496 -0
- package/src/providers/ollama.ts +644 -0
- package/src/providers/openai-anthropic-shim.ts +138 -0
- package/src/providers/openai-chat-server-schema.ts +252 -0
- package/src/providers/openai-chat-server.ts +647 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +161 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +3018 -0
- package/src/providers/openai-completions-compat.ts +300 -0
- package/src/providers/openai-completions.ts +1979 -0
- package/src/providers/openai-responses-server-schema.ts +290 -0
- package/src/providers/openai-responses-server.ts +1183 -0
- package/src/providers/openai-responses-shared.ts +873 -0
- package/src/providers/openai-responses.ts +679 -0
- package/src/providers/register-builtins.ts +436 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +382 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/providers/xai-responses.ts +82 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +1065 -0
- package/src/types.ts +944 -0
- package/src/usage/claude.ts +482 -0
- package/src/usage/gemini.ts +250 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +201 -0
- package/src/usage/kimi.ts +271 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +503 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +185 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/abortable-iterator.ts +69 -0
- package/src/utils/anthropic-auth.ts +93 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +4 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +142 -0
- package/src/utils/fireworks-model-id.ts +30 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/http-inspector.ts +176 -0
- package/src/utils/idle-iterator.ts +267 -0
- package/src/utils/json-parse.ts +182 -0
- package/src/utils/oauth/__tests__/xai-oauth.test.ts +107 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +273 -0
- package/src/utils/oauth/api-key-login.ts +87 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +16 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/deepseek.ts +53 -0
- package/src/utils/oauth/firepass.ts +24 -0
- package/src/utils/oauth/fireworks.ts +15 -0
- package/src/utils/oauth/github-copilot.ts +362 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +200 -0
- package/src/utils/oauth/google-gemini-cli.ts +256 -0
- package/src/utils/oauth/google-oauth-shared.ts +110 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +484 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +254 -0
- package/src/utils/oauth/litellm.ts +47 -0
- package/src/utils/oauth/lm-studio.ts +38 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +23 -0
- package/src/utils/oauth/nanogpt.ts +15 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +203 -0
- package/src/utils/oauth/ollama-cloud.ts +28 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +299 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/openrouter.ts +20 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +206 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +15 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +16 -0
- package/src/utils/oauth/types.ts +99 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/wafer.ts +50 -0
- package/src/utils/oauth/xai-oauth.ts +342 -0
- package/src/utils/oauth/xiaomi.ts +139 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -0
- package/src/utils/oauth/zhipu.ts +60 -0
- package/src/utils/overflow.ts +137 -0
- package/src/utils/parse-bind.ts +54 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/request-debug.ts +336 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +54 -0
- package/src/utils/schema/CONSTRAINTS.md +164 -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 +191 -0
- package/src/utils/schema/index.ts +13 -0
- package/src/utils/schema/json-schema-validator.ts +577 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +1588 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +97 -0
- package/src/utils/schema/types.ts +10 -0
- package/src/utils/schema/wire.ts +293 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sdk-stream-timeout.ts +43 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/stream-markup-healing.ts +612 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1024 -0
- package/src/utils.ts +166 -0
- package/dist/api-registry.d.ts +0 -20
- package/dist/api-registry.d.ts.map +0 -1
- package/dist/api-registry.js +0 -44
- package/dist/api-registry.js.map +0 -1
- package/dist/bedrock-provider.d.ts +0 -5
- package/dist/bedrock-provider.d.ts.map +0 -1
- package/dist/bedrock-provider.js +0 -6
- package/dist/bedrock-provider.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -130
- package/dist/cli.js.map +0 -1
- package/dist/env-api-keys.d.ts +0 -18
- package/dist/env-api-keys.d.ts.map +0 -1
- package/dist/env-api-keys.js +0 -178
- package/dist/env-api-keys.js.map +0 -1
- package/dist/image-models.d.ts +0 -10
- package/dist/image-models.d.ts.map +0 -1
- package/dist/image-models.generated.d.ts +0 -440
- package/dist/image-models.generated.d.ts.map +0 -1
- package/dist/image-models.generated.js +0 -442
- package/dist/image-models.generated.js.map +0 -1
- package/dist/image-models.js +0 -23
- package/dist/image-models.js.map +0 -1
- package/dist/images-api-registry.d.ts +0 -14
- package/dist/images-api-registry.d.ts.map +0 -1
- package/dist/images-api-registry.js +0 -22
- package/dist/images-api-registry.js.map +0 -1
- package/dist/images.d.ts +0 -4
- package/dist/images.d.ts.map +0 -1
- package/dist/images.js +0 -14
- package/dist/images.js.map +0 -1
- package/dist/index.d.ts +0 -32
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -20
- package/dist/index.js.map +0 -1
- package/dist/models.d.ts +0 -18
- package/dist/models.d.ts.map +0 -1
- package/dist/models.generated.d.ts +0 -17707
- package/dist/models.generated.d.ts.map +0 -1
- package/dist/models.generated.js +0 -16561
- package/dist/models.generated.js.map +0 -1
- package/dist/models.js +0 -71
- package/dist/models.js.map +0 -1
- package/dist/oauth.d.ts +0 -2
- package/dist/oauth.d.ts.map +0 -1
- package/dist/oauth.js +0 -2
- package/dist/oauth.js.map +0 -1
- package/dist/providers/aery-error-formatting.d.ts +0 -13
- package/dist/providers/aery-error-formatting.d.ts.map +0 -1
- package/dist/providers/aery-error-formatting.js +0 -112
- package/dist/providers/aery-error-formatting.js.map +0 -1
- package/dist/providers/amazon-bedrock.d.ts +0 -38
- package/dist/providers/amazon-bedrock.d.ts.map +0 -1
- package/dist/providers/amazon-bedrock.js +0 -763
- package/dist/providers/amazon-bedrock.js.map +0 -1
- package/dist/providers/anthropic.d.ts +0 -71
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js +0 -949
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/azure-openai-responses.d.ts +0 -15
- package/dist/providers/azure-openai-responses.d.ts.map +0 -1
- package/dist/providers/azure-openai-responses.js +0 -225
- package/dist/providers/azure-openai-responses.js.map +0 -1
- package/dist/providers/cloudflare.d.ts +0 -13
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js +0 -26
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/faux.d.ts +0 -56
- package/dist/providers/faux.d.ts.map +0 -1
- package/dist/providers/faux.js +0 -368
- package/dist/providers/faux.js.map +0 -1
- package/dist/providers/github-copilot-headers.d.ts +0 -8
- package/dist/providers/github-copilot-headers.d.ts.map +0 -1
- package/dist/providers/github-copilot-headers.js +0 -29
- package/dist/providers/github-copilot-headers.js.map +0 -1
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -779
- package/dist/providers/google-gemini-cli.js.map +0 -1
- package/dist/providers/google-shared.d.ts +0 -70
- package/dist/providers/google-shared.d.ts.map +0 -1
- package/dist/providers/google-shared.js +0 -329
- package/dist/providers/google-shared.js.map +0 -1
- package/dist/providers/google-vertex.d.ts +0 -15
- package/dist/providers/google-vertex.d.ts.map +0 -1
- package/dist/providers/google-vertex.js +0 -442
- package/dist/providers/google-vertex.js.map +0 -1
- package/dist/providers/google.d.ts +0 -13
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js +0 -400
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/images/openrouter.d.ts +0 -3
- package/dist/providers/images/openrouter.d.ts.map +0 -1
- package/dist/providers/images/openrouter.js +0 -129
- package/dist/providers/images/openrouter.js.map +0 -1
- package/dist/providers/images/register-builtins.d.ts +0 -4
- package/dist/providers/images/register-builtins.d.ts.map +0 -1
- package/dist/providers/images/register-builtins.js +0 -34
- package/dist/providers/images/register-builtins.js.map +0 -1
- package/dist/providers/mistral.d.ts +0 -25
- package/dist/providers/mistral.d.ts.map +0 -1
- package/dist/providers/mistral.js +0 -535
- package/dist/providers/mistral.js.map +0 -1
- package/dist/providers/openai-codex-responses.d.ts +0 -30
- package/dist/providers/openai-codex-responses.d.ts.map +0 -1
- package/dist/providers/openai-codex-responses.js +0 -1090
- package/dist/providers/openai-codex-responses.js.map +0 -1
- package/dist/providers/openai-completions.d.ts +0 -19
- package/dist/providers/openai-completions.d.ts.map +0 -1
- package/dist/providers/openai-completions.js +0 -950
- package/dist/providers/openai-completions.js.map +0 -1
- package/dist/providers/openai-prompt-cache.d.ts +0 -3
- package/dist/providers/openai-prompt-cache.d.ts.map +0 -1
- package/dist/providers/openai-prompt-cache.js +0 -10
- package/dist/providers/openai-prompt-cache.js.map +0 -1
- package/dist/providers/openai-responses-shared.d.ts +0 -18
- package/dist/providers/openai-responses-shared.d.ts.map +0 -1
- package/dist/providers/openai-responses-shared.js +0 -492
- package/dist/providers/openai-responses-shared.js.map +0 -1
- package/dist/providers/openai-responses.d.ts +0 -13
- package/dist/providers/openai-responses.d.ts.map +0 -1
- package/dist/providers/openai-responses.js +0 -237
- package/dist/providers/openai-responses.js.map +0 -1
- package/dist/providers/register-builtins.d.ts +0 -38
- package/dist/providers/register-builtins.d.ts.map +0 -1
- package/dist/providers/register-builtins.js +0 -278
- package/dist/providers/register-builtins.js.map +0 -1
- package/dist/providers/simple-options.d.ts +0 -8
- package/dist/providers/simple-options.d.ts.map +0 -1
- package/dist/providers/simple-options.js +0 -41
- package/dist/providers/simple-options.js.map +0 -1
- package/dist/providers/transform-messages.d.ts +0 -8
- package/dist/providers/transform-messages.d.ts.map +0 -1
- package/dist/providers/transform-messages.js +0 -184
- package/dist/providers/transform-messages.js.map +0 -1
- package/dist/session-resources.d.ts +0 -4
- package/dist/session-resources.d.ts.map +0 -1
- package/dist/session-resources.js +0 -22
- package/dist/session-resources.js.map +0 -1
- package/dist/stream.d.ts +0 -8
- package/dist/stream.d.ts.map +0 -1
- package/dist/stream.js +0 -27
- package/dist/stream.js.map +0 -1
- package/dist/types.d.ts +0 -498
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/diagnostics.d.ts +0 -19
- package/dist/utils/diagnostics.d.ts.map +0 -1
- package/dist/utils/diagnostics.js +0 -25
- package/dist/utils/diagnostics.js.map +0 -1
- package/dist/utils/event-stream.d.ts +0 -21
- package/dist/utils/event-stream.d.ts.map +0 -1
- package/dist/utils/event-stream.js +0 -81
- package/dist/utils/event-stream.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -3
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -14
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/headers.d.ts +0 -2
- package/dist/utils/headers.d.ts.map +0 -1
- package/dist/utils/headers.js +0 -8
- package/dist/utils/headers.js.map +0 -1
- package/dist/utils/json-parse.d.ts +0 -16
- package/dist/utils/json-parse.d.ts.map +0 -1
- package/dist/utils/json-parse.js +0 -113
- package/dist/utils/json-parse.js.map +0 -1
- package/dist/utils/node-http-proxy.d.ts +0 -10
- package/dist/utils/node-http-proxy.d.ts.map +0 -1
- package/dist/utils/node-http-proxy.js +0 -97
- package/dist/utils/node-http-proxy.js.map +0 -1
- package/dist/utils/oauth/anthropic.d.ts +0 -25
- package/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/dist/utils/oauth/anthropic.js +0 -335
- package/dist/utils/oauth/anthropic.js.map +0 -1
- package/dist/utils/oauth/device-code.d.ts +0 -19
- package/dist/utils/oauth/device-code.d.ts.map +0 -1
- package/dist/utils/oauth/device-code.js +0 -55
- package/dist/utils/oauth/device-code.js.map +0 -1
- package/dist/utils/oauth/github-copilot.d.ts +0 -30
- package/dist/utils/oauth/github-copilot.d.ts.map +0 -1
- package/dist/utils/oauth/github-copilot.js +0 -268
- package/dist/utils/oauth/github-copilot.js.map +0 -1
- package/dist/utils/oauth/google-antigravity.d.ts +0 -26
- package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
- package/dist/utils/oauth/google-antigravity.js +0 -377
- package/dist/utils/oauth/google-antigravity.js.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
- package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.js +0 -482
- package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
- package/dist/utils/oauth/index.d.ts +0 -63
- package/dist/utils/oauth/index.d.ts.map +0 -1
- package/dist/utils/oauth/index.js +0 -131
- package/dist/utils/oauth/index.js.map +0 -1
- package/dist/utils/oauth/oauth-page.d.ts +0 -3
- package/dist/utils/oauth/oauth-page.d.ts.map +0 -1
- package/dist/utils/oauth/oauth-page.js +0 -105
- package/dist/utils/oauth/oauth-page.js.map +0 -1
- package/dist/utils/oauth/openai-codex.d.ts +0 -34
- package/dist/utils/oauth/openai-codex.d.ts.map +0 -1
- package/dist/utils/oauth/openai-codex.js +0 -385
- package/dist/utils/oauth/openai-codex.js.map +0 -1
- package/dist/utils/oauth/pkce.d.ts +0 -13
- package/dist/utils/oauth/pkce.d.ts.map +0 -1
- package/dist/utils/oauth/pkce.js +0 -31
- package/dist/utils/oauth/pkce.js.map +0 -1
- package/dist/utils/oauth/types.d.ts +0 -64
- package/dist/utils/oauth/types.d.ts.map +0 -1
- package/dist/utils/oauth/types.js +0 -2
- package/dist/utils/oauth/types.js.map +0 -1
- package/dist/utils/overflow.d.ts +0 -56
- package/dist/utils/overflow.d.ts.map +0 -1
- package/dist/utils/overflow.js +0 -151
- package/dist/utils/overflow.js.map +0 -1
- package/dist/utils/sanitize-unicode.d.ts +0 -22
- package/dist/utils/sanitize-unicode.d.ts.map +0 -1
- package/dist/utils/sanitize-unicode.js +0 -26
- package/dist/utils/sanitize-unicode.js.map +0 -1
- package/dist/utils/typebox-helpers.d.ts +0 -17
- package/dist/utils/typebox-helpers.d.ts.map +0 -1
- package/dist/utils/typebox-helpers.js +0 -21
- package/dist/utils/typebox-helpers.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -18
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -281
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import { $env, extractHttpStatusFromError, structuredCloneJSON } from "@aryee337/aery-utils";
|
|
2
|
+
import OpenAI, { APIConnectionTimeoutError as OpenAIConnectionTimeoutError } from "openai";
|
|
3
|
+
import type {
|
|
4
|
+
Tool as OpenAITool,
|
|
5
|
+
ResponseCreateParamsStreaming,
|
|
6
|
+
ResponseInput,
|
|
7
|
+
} from "openai/resources/responses/responses";
|
|
8
|
+
import { getEnvApiKey } from "../stream";
|
|
9
|
+
import type {
|
|
10
|
+
AssistantMessage,
|
|
11
|
+
CacheRetention,
|
|
12
|
+
Context,
|
|
13
|
+
FetchImpl,
|
|
14
|
+
MessageAttribution,
|
|
15
|
+
Model,
|
|
16
|
+
OpenAICompat,
|
|
17
|
+
ProviderSessionState,
|
|
18
|
+
ServiceTier,
|
|
19
|
+
StreamFunction,
|
|
20
|
+
StreamOptions,
|
|
21
|
+
Tool,
|
|
22
|
+
ToolChoice,
|
|
23
|
+
} from "../types";
|
|
24
|
+
import {
|
|
25
|
+
createOpenAIResponsesHistoryPayload,
|
|
26
|
+
getOpenAIResponsesHistoryItems,
|
|
27
|
+
getOpenAIResponsesHistoryPayload,
|
|
28
|
+
normalizeSystemPrompts,
|
|
29
|
+
resolveCacheRetention,
|
|
30
|
+
sanitizeOpenAIResponsesHistoryItemsForReplay,
|
|
31
|
+
} from "../utils";
|
|
32
|
+
import { createAbortSourceTracker } from "../utils/abort";
|
|
33
|
+
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
34
|
+
import { finalizeErrorMessage, type RawHttpRequestDump, rewriteCopilotError } from "../utils/http-inspector";
|
|
35
|
+
import {
|
|
36
|
+
getOpenAIStreamFirstEventTimeoutMs,
|
|
37
|
+
getOpenAIStreamIdleTimeoutMs,
|
|
38
|
+
iterateWithIdleTimeout,
|
|
39
|
+
} from "../utils/idle-iterator";
|
|
40
|
+
import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
41
|
+
import { notifyProviderResponse } from "../utils/provider-response";
|
|
42
|
+
import { callWithCopilotModelRetry } from "../utils/retry";
|
|
43
|
+
import { adaptSchemaForStrict, NO_STRICT, sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
|
|
44
|
+
import { createSdkStreamRequestOptions } from "../utils/sdk-stream-timeout";
|
|
45
|
+
import { wrapFetchForSseDebug } from "../utils/sse-debug";
|
|
46
|
+
import { mapToOpenAIResponsesToolChoice, type OpenAIResponsesToolChoice } from "../utils/tool-choice";
|
|
47
|
+
import {
|
|
48
|
+
buildCopilotDynamicHeaders,
|
|
49
|
+
hasCopilotVisionInput,
|
|
50
|
+
resolveGitHubCopilotBaseUrl,
|
|
51
|
+
} from "./github-copilot-headers";
|
|
52
|
+
import { compactGrammarDefinition } from "./grammar";
|
|
53
|
+
import {
|
|
54
|
+
appendResponsesToolResultMessages,
|
|
55
|
+
applyCommonResponsesSamplingParams,
|
|
56
|
+
applyResponsesReasoningParams,
|
|
57
|
+
collectCustomCallIds,
|
|
58
|
+
collectKnownCallIds,
|
|
59
|
+
convertResponsesAssistantMessage,
|
|
60
|
+
convertResponsesInputContent,
|
|
61
|
+
createInitialResponsesAssistantMessage,
|
|
62
|
+
isOpenAIResponsesProgressEvent,
|
|
63
|
+
normalizeResponsesToolCallIdForTransform,
|
|
64
|
+
processResponsesStream,
|
|
65
|
+
repairOrphanResponsesToolOutputs,
|
|
66
|
+
} from "./openai-responses-shared";
|
|
67
|
+
import { transformMessages } from "./transform-messages";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get prompt cache retention based on cacheRetention and base URL.
|
|
71
|
+
* Only applies to direct OpenAI API calls (api.openai.com).
|
|
72
|
+
*/
|
|
73
|
+
function getPromptCacheRetention(baseUrl: string, cacheRetention: CacheRetention): "24h" | undefined {
|
|
74
|
+
if (cacheRetention !== "long") {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (baseUrl.includes("api.openai.com")) {
|
|
78
|
+
return "24h";
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function normalizeOpenAIResponsesPromptCacheKey(sessionId: string | undefined): string | undefined {
|
|
84
|
+
if (!sessionId || sessionId.length === 0) return undefined;
|
|
85
|
+
const wellFormed = sessionId.toWellFormed();
|
|
86
|
+
if (wellFormed.length <= 64) return wellFormed;
|
|
87
|
+
return `pc_${Bun.hash(wellFormed).toString(36)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// OpenAI Responses-specific options
|
|
91
|
+
export interface OpenAIResponsesOptions extends StreamOptions {
|
|
92
|
+
reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
93
|
+
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
94
|
+
serviceTier?: ServiceTier;
|
|
95
|
+
toolChoice?: ToolChoice;
|
|
96
|
+
/**
|
|
97
|
+
* Enforce strict tool call/result pairing when building Responses API inputs.
|
|
98
|
+
* Azure OpenAI and GitHub Copilot Responses paths require tool results to match prior tool calls.
|
|
99
|
+
*/
|
|
100
|
+
strictResponsesPairing?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Pass `include: ["reasoning.encrypted_content"]` on requests when the
|
|
103
|
+
* model supports reasoning. Default: true (preserves current behavior).
|
|
104
|
+
* Set to false when the upstream Responses endpoint rejects replayed
|
|
105
|
+
* encrypted reasoning (e.g., xAI Grok under SuperGrok OAuth).
|
|
106
|
+
*/
|
|
107
|
+
includeEncryptedReasoning?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Strip `type: "reasoning"` items from replayed conversation history
|
|
110
|
+
* before they hit the wire. Default: false (preserves current behavior).
|
|
111
|
+
* Set to true when the upstream rejects replayed reasoning wrappers.
|
|
112
|
+
*/
|
|
113
|
+
filterReasoningHistory?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Suppress the `reasoning.effort` wire param when set, even if
|
|
116
|
+
* `options.reasoning` is requested. Default: false. xAI Grok models
|
|
117
|
+
* outside the effort-capable allowlist 400 with "Model X does not
|
|
118
|
+
* support parameter reasoningEffort" — the xAI Responses adapter sets
|
|
119
|
+
* this when the target model is not in GROK_EFFORT_CAPABLE_PREFIXES.
|
|
120
|
+
*/
|
|
121
|
+
omitReasoningEffort?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Extra request headers merged onto the underlying client's
|
|
124
|
+
* defaultHeaders. Used by adapter wrappers to inject provider-specific
|
|
125
|
+
* routing or cache hints.
|
|
126
|
+
*/
|
|
127
|
+
headers?: Record<string, string>;
|
|
128
|
+
/**
|
|
129
|
+
* Extra body fields merged into the Responses request payload. Used by
|
|
130
|
+
* adapter wrappers to inject provider-specific body keys (e.g.,
|
|
131
|
+
* prompt_cache_key for prompt-cache routing).
|
|
132
|
+
*/
|
|
133
|
+
extraBody?: Record<string, unknown>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX = "openai-responses:";
|
|
137
|
+
const OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE =
|
|
138
|
+
"OpenAI responses stream timed out while waiting for the first event";
|
|
139
|
+
|
|
140
|
+
interface OpenAIResponsesProviderSessionState extends ProviderSessionState {
|
|
141
|
+
nativeHistoryReplayWarmed: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createOpenAIResponsesProviderSessionState(): OpenAIResponsesProviderSessionState {
|
|
145
|
+
const state: OpenAIResponsesProviderSessionState = {
|
|
146
|
+
nativeHistoryReplayWarmed: false,
|
|
147
|
+
close: () => {
|
|
148
|
+
state.nativeHistoryReplayWarmed = false;
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
return state;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getOpenAIResponsesProviderSessionStateKey(model: Model<"openai-responses">): string {
|
|
155
|
+
return `${OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX}${model.provider}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getOpenAIResponsesProviderSessionState(
|
|
159
|
+
model: Model<"openai-responses">,
|
|
160
|
+
providerSessionState: Map<string, ProviderSessionState> | undefined,
|
|
161
|
+
): OpenAIResponsesProviderSessionState | undefined {
|
|
162
|
+
if (!providerSessionState) return undefined;
|
|
163
|
+
const key = getOpenAIResponsesProviderSessionStateKey(model);
|
|
164
|
+
const existing = providerSessionState.get(key) as OpenAIResponsesProviderSessionState | undefined;
|
|
165
|
+
if (existing) return existing;
|
|
166
|
+
const created = createOpenAIResponsesProviderSessionState();
|
|
167
|
+
providerSessionState.set(key, created);
|
|
168
|
+
return created;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function canReplayOpenAIResponsesNativeHistory(
|
|
172
|
+
providerSessionState: OpenAIResponsesProviderSessionState | undefined,
|
|
173
|
+
): boolean {
|
|
174
|
+
return providerSessionState?.nativeHistoryReplayWarmed ?? true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type OpenAIResponsesSamplingParams = ResponseCreateParamsStreaming & {
|
|
178
|
+
top_p?: number;
|
|
179
|
+
top_k?: number;
|
|
180
|
+
min_p?: number;
|
|
181
|
+
presence_penalty?: number;
|
|
182
|
+
repetition_penalty?: number;
|
|
183
|
+
stream_options?: { include_obfuscation?: boolean };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate function for OpenAI Responses API
|
|
188
|
+
*/
|
|
189
|
+
export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
190
|
+
model: Model<"openai-responses">,
|
|
191
|
+
context: Context,
|
|
192
|
+
options?: OpenAIResponsesOptions,
|
|
193
|
+
): AssistantMessageEventStream => {
|
|
194
|
+
const stream = new AssistantMessageEventStream();
|
|
195
|
+
|
|
196
|
+
// Start async processing
|
|
197
|
+
(async () => {
|
|
198
|
+
const startTime = Date.now();
|
|
199
|
+
let firstTokenTime: number | undefined;
|
|
200
|
+
|
|
201
|
+
const output: AssistantMessage = createInitialResponsesAssistantMessage(
|
|
202
|
+
"openai-responses",
|
|
203
|
+
model.provider,
|
|
204
|
+
model.id,
|
|
205
|
+
);
|
|
206
|
+
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
207
|
+
const abortTracker = createAbortSourceTracker(options?.signal);
|
|
208
|
+
const firstEventTimeoutAbortError = new Error(OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE);
|
|
209
|
+
const { requestAbortController, requestSignal } = abortTracker;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
// Keep request routing on `sessionId` while allowing callers to pin a
|
|
213
|
+
// stable prompt-cache key independently. Side-channel calls use this to
|
|
214
|
+
// avoid perturbing provider conversation state without cold-starting the cache.
|
|
215
|
+
const routingSessionId = getOpenAIResponsesRoutingSessionId(options);
|
|
216
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
217
|
+
const { client, copilotPremiumRequests, baseUrl } = createClient(
|
|
218
|
+
model,
|
|
219
|
+
context,
|
|
220
|
+
apiKey,
|
|
221
|
+
options?.headers,
|
|
222
|
+
options?.initiatorOverride,
|
|
223
|
+
routingSessionId,
|
|
224
|
+
options?.onSseEvent,
|
|
225
|
+
options?.fetch,
|
|
226
|
+
);
|
|
227
|
+
const premiumRequestsTotal = copilotPremiumRequests;
|
|
228
|
+
const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
|
|
229
|
+
const { params } = buildParams(model, context, options, providerSessionState, baseUrl);
|
|
230
|
+
const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs();
|
|
231
|
+
const firstEventTimeoutMs =
|
|
232
|
+
options?.streamFirstEventTimeoutMs ?? getOpenAIStreamFirstEventTimeoutMs(idleTimeoutMs);
|
|
233
|
+
const requestTimeoutMs =
|
|
234
|
+
firstEventTimeoutMs !== undefined && firstEventTimeoutMs > 0 ? firstEventTimeoutMs : undefined;
|
|
235
|
+
options?.onPayload?.(params);
|
|
236
|
+
rawRequestDump = {
|
|
237
|
+
provider: model.provider,
|
|
238
|
+
api: output.api,
|
|
239
|
+
model: model.id,
|
|
240
|
+
method: "POST",
|
|
241
|
+
url: `${baseUrl ?? "https://api.openai.com/v1"}/responses`,
|
|
242
|
+
body: params,
|
|
243
|
+
};
|
|
244
|
+
const openaiStream = await callWithCopilotModelRetry(
|
|
245
|
+
async () => {
|
|
246
|
+
const requestOptions = createSdkStreamRequestOptions(requestSignal, requestTimeoutMs);
|
|
247
|
+
let requestTimeout: NodeJS.Timeout | undefined;
|
|
248
|
+
if (requestTimeoutMs !== undefined) {
|
|
249
|
+
requestTimeout = setTimeout(
|
|
250
|
+
() => abortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
251
|
+
requestTimeoutMs,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const { data, response, request_id } = await client.responses
|
|
256
|
+
.create(params, requestOptions)
|
|
257
|
+
.withResponse();
|
|
258
|
+
await notifyProviderResponse(options, response, model, request_id);
|
|
259
|
+
return data;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error instanceof OpenAIConnectionTimeoutError && !abortTracker.wasCallerAbort()) {
|
|
262
|
+
throw firstEventTimeoutAbortError;
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
} finally {
|
|
266
|
+
if (requestTimeout !== undefined) clearTimeout(requestTimeout);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{ provider: model.provider, signal: requestSignal },
|
|
270
|
+
);
|
|
271
|
+
if (premiumRequestsTotal !== undefined) output.usage.premiumRequests = premiumRequestsTotal;
|
|
272
|
+
stream.push({ type: "start", partial: output });
|
|
273
|
+
|
|
274
|
+
const nativeOutputItems: Array<Record<string, unknown>> = [];
|
|
275
|
+
await processResponsesStream(
|
|
276
|
+
iterateWithIdleTimeout(openaiStream, {
|
|
277
|
+
idleTimeoutMs,
|
|
278
|
+
firstItemTimeoutMs: firstEventTimeoutMs,
|
|
279
|
+
firstItemErrorMessage: OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE,
|
|
280
|
+
errorMessage: "OpenAI responses stream stalled while waiting for the next event",
|
|
281
|
+
onFirstItemTimeout: () => abortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
282
|
+
onIdle: () => requestAbortController.abort(),
|
|
283
|
+
abortSignal: options?.signal,
|
|
284
|
+
isProgressItem: isOpenAIResponsesProgressEvent,
|
|
285
|
+
}),
|
|
286
|
+
output,
|
|
287
|
+
stream,
|
|
288
|
+
model,
|
|
289
|
+
{
|
|
290
|
+
onFirstToken: () => {
|
|
291
|
+
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
292
|
+
},
|
|
293
|
+
onOutputItemDone: item => {
|
|
294
|
+
nativeOutputItems.push(structuredCloneJSON<unknown>(item) as unknown as Record<string, unknown>);
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
if (premiumRequestsTotal !== undefined) output.usage.premiumRequests = premiumRequestsTotal;
|
|
299
|
+
|
|
300
|
+
const firstEventTimeoutError = abortTracker.getLocalAbortReason();
|
|
301
|
+
if (firstEventTimeoutError) {
|
|
302
|
+
throw firstEventTimeoutError;
|
|
303
|
+
}
|
|
304
|
+
if (abortTracker.wasCallerAbort()) {
|
|
305
|
+
throw new Error("Request was aborted");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
309
|
+
throw new Error(output.errorMessage ?? "An unknown error occurred");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, nativeOutputItems);
|
|
313
|
+
if (providerSessionState) providerSessionState.nativeHistoryReplayWarmed = true;
|
|
314
|
+
|
|
315
|
+
output.duration = Date.now() - startTime;
|
|
316
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
317
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
318
|
+
stream.end();
|
|
319
|
+
} catch (error) {
|
|
320
|
+
for (const block of output.content) delete (block as { index?: number }).index;
|
|
321
|
+
const firstEventTimeoutError = abortTracker.getLocalAbortReason();
|
|
322
|
+
output.stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
323
|
+
output.errorStatus = extractHttpStatusFromError(error);
|
|
324
|
+
output.errorMessage = firstEventTimeoutError?.message ?? (await finalizeErrorMessage(error, rawRequestDump));
|
|
325
|
+
output.errorMessage = rewriteCopilotError(output.errorMessage, error, model.provider);
|
|
326
|
+
output.duration = Date.now() - startTime;
|
|
327
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
328
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
329
|
+
stream.end();
|
|
330
|
+
}
|
|
331
|
+
})();
|
|
332
|
+
|
|
333
|
+
return stream;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
function createClient(
|
|
337
|
+
model: Model<"openai-responses">,
|
|
338
|
+
context: Context,
|
|
339
|
+
apiKey?: string,
|
|
340
|
+
extraHeaders?: Record<string, string>,
|
|
341
|
+
initiatorOverride?: MessageAttribution,
|
|
342
|
+
sessionId?: string,
|
|
343
|
+
onSseEvent?: OpenAIResponsesOptions["onSseEvent"],
|
|
344
|
+
fetchOverride?: FetchImpl,
|
|
345
|
+
): {
|
|
346
|
+
client: OpenAI;
|
|
347
|
+
copilotPremiumRequests: number | undefined;
|
|
348
|
+
baseUrl: string | undefined;
|
|
349
|
+
} {
|
|
350
|
+
if (!apiKey) {
|
|
351
|
+
if (!$env.OPENAI_API_KEY) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
"OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.",
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
apiKey = $env.OPENAI_API_KEY;
|
|
357
|
+
}
|
|
358
|
+
const rawApiKey = apiKey;
|
|
359
|
+
|
|
360
|
+
const headers = { ...(model.headers ?? {}), ...(extraHeaders ?? {}) };
|
|
361
|
+
let copilotPremiumRequests: number | undefined;
|
|
362
|
+
|
|
363
|
+
let baseUrl = model.baseUrl;
|
|
364
|
+
if (model.provider === "github-copilot") {
|
|
365
|
+
apiKey = parseGitHubCopilotApiKey(rawApiKey).accessToken;
|
|
366
|
+
const hasImages = hasCopilotVisionInput(context.messages);
|
|
367
|
+
const copilot = buildCopilotDynamicHeaders({
|
|
368
|
+
messages: context.messages,
|
|
369
|
+
hasImages,
|
|
370
|
+
premiumMultiplier: model.premiumMultiplier,
|
|
371
|
+
headers,
|
|
372
|
+
initiatorOverride,
|
|
373
|
+
});
|
|
374
|
+
Object.assign(headers, copilot.headers);
|
|
375
|
+
copilotPremiumRequests = copilot.premiumRequests;
|
|
376
|
+
baseUrl = resolveGitHubCopilotBaseUrl(model.baseUrl, rawApiKey) ?? model.baseUrl;
|
|
377
|
+
}
|
|
378
|
+
if (sessionId && model.provider === "openai" && (baseUrl ?? "").toLowerCase().includes("api.openai.com")) {
|
|
379
|
+
headers.session_id ??= sessionId;
|
|
380
|
+
headers["x-client-request-id"] ??= sessionId;
|
|
381
|
+
}
|
|
382
|
+
const baseFetch = fetchOverride ?? fetch;
|
|
383
|
+
return {
|
|
384
|
+
client: new OpenAI({
|
|
385
|
+
apiKey,
|
|
386
|
+
baseURL: baseUrl,
|
|
387
|
+
dangerouslyAllowBrowser: true,
|
|
388
|
+
maxRetries: 5,
|
|
389
|
+
defaultHeaders: headers,
|
|
390
|
+
fetch: onSseEvent ? wrapFetchForSseDebug(baseFetch, event => onSseEvent(event, model)) : baseFetch,
|
|
391
|
+
}),
|
|
392
|
+
copilotPremiumRequests,
|
|
393
|
+
baseUrl,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getOpenAIResponsesPromptCacheKey(
|
|
398
|
+
options: Pick<OpenAIResponsesOptions, "cacheRetention" | "promptCacheKey" | "sessionId"> | undefined,
|
|
399
|
+
): string | undefined {
|
|
400
|
+
if (resolveCacheRetention(options?.cacheRetention) === "none") return undefined;
|
|
401
|
+
return normalizeOpenAIResponsesPromptCacheKey(options?.promptCacheKey ?? options?.sessionId);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function getOpenAIResponsesCacheSessionId(
|
|
405
|
+
options: Pick<OpenAIResponsesOptions, "cacheRetention" | "sessionId" | "promptCacheKey"> | undefined,
|
|
406
|
+
): string | undefined {
|
|
407
|
+
return getOpenAIResponsesPromptCacheKey(options);
|
|
408
|
+
}
|
|
409
|
+
function getOpenAIResponsesRoutingSessionId(
|
|
410
|
+
options: Pick<OpenAIResponsesOptions, "cacheRetention" | "sessionId"> | undefined,
|
|
411
|
+
): string | undefined {
|
|
412
|
+
if (resolveCacheRetention(options?.cacheRetention) === "none") return undefined;
|
|
413
|
+
return normalizeOpenAIResponsesPromptCacheKey(options?.sessionId);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function buildParams(
|
|
417
|
+
model: Model<"openai-responses">,
|
|
418
|
+
context: Context,
|
|
419
|
+
options: OpenAIResponsesOptions | undefined,
|
|
420
|
+
providerSessionState: OpenAIResponsesProviderSessionState | undefined,
|
|
421
|
+
resolvedBaseUrl?: string,
|
|
422
|
+
): { conversationMessages: ResponseInput; params: OpenAIResponsesSamplingParams } {
|
|
423
|
+
const strictResponsesPairing =
|
|
424
|
+
options?.strictResponsesPairing ??
|
|
425
|
+
(isAzureOpenAIBaseUrl(model.baseUrl ?? "") || model.provider === "github-copilot");
|
|
426
|
+
const conversationMessages = convertConversationMessages(
|
|
427
|
+
model,
|
|
428
|
+
context,
|
|
429
|
+
strictResponsesPairing,
|
|
430
|
+
providerSessionState,
|
|
431
|
+
options,
|
|
432
|
+
);
|
|
433
|
+
const messages: ResponseInput = [...conversationMessages];
|
|
434
|
+
|
|
435
|
+
const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
|
436
|
+
let systemInstructions: string | undefined;
|
|
437
|
+
if (systemPrompts.length > 0) {
|
|
438
|
+
const needsDeveloperRole = model.reasoning && supportsDeveloperRole(resolvedBaseUrl ?? model);
|
|
439
|
+
if (needsDeveloperRole) {
|
|
440
|
+
// Reasoning models on known OpenAI-compatible endpoints require the
|
|
441
|
+
// `developer` role. Send all system prompts inline in `input`.
|
|
442
|
+
messages.unshift(
|
|
443
|
+
...systemPrompts.map(systemPrompt => ({ role: "developer" as const, content: systemPrompt })),
|
|
444
|
+
);
|
|
445
|
+
} else {
|
|
446
|
+
// All other endpoints (including third-party /v1/responses proxies) use
|
|
447
|
+
// the canonical top-level `instructions` field so that proxies that
|
|
448
|
+
// reject `input[{role:"system"}]` work out of the box.
|
|
449
|
+
systemInstructions = systemPrompts.join("\n\n");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
454
|
+
const promptCacheKey = getOpenAIResponsesPromptCacheKey(options);
|
|
455
|
+
const params: OpenAIResponsesSamplingParams = {
|
|
456
|
+
model: model.id,
|
|
457
|
+
input: messages,
|
|
458
|
+
instructions: systemInstructions,
|
|
459
|
+
stream: true,
|
|
460
|
+
prompt_cache_key: promptCacheKey,
|
|
461
|
+
prompt_cache_retention: promptCacheKey ? getPromptCacheRetention(model.baseUrl, cacheRetention) : undefined,
|
|
462
|
+
store: false,
|
|
463
|
+
stream_options: model.provider === "openai" ? { include_obfuscation: false } : undefined,
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
applyCommonResponsesSamplingParams(params, options, model.provider);
|
|
467
|
+
// TODO: openai responses has no top-level `stop`/`stop_sequences`; surface via reasoning.stop?
|
|
468
|
+
// `StreamOptions.stopSequences` is intentionally dropped for this provider.
|
|
469
|
+
// TODO: openai responses has no top-level `frequency_penalty` field as of the current SDK;
|
|
470
|
+
// `StreamOptions.frequencyPenalty` is intentionally dropped for this provider.
|
|
471
|
+
|
|
472
|
+
if (context.tools) {
|
|
473
|
+
params.tools = convertTools(context.tools, supportsStrictMode(model), model);
|
|
474
|
+
if (options?.toolChoice) {
|
|
475
|
+
params.tool_choice = mapOpenAIResponsesToolChoiceForTools(options.toolChoice, context.tools, model);
|
|
476
|
+
}
|
|
477
|
+
// The apply_patch spec §1 marks only `apply_patch` itself as
|
|
478
|
+
// `supports_parallel_tool_calls = false`. OpenAI's Responses API
|
|
479
|
+
// exposes `parallel_tool_calls` as a request-scoped flag, not a
|
|
480
|
+
// per-tool one, so when a custom grammar tool is in the list we
|
|
481
|
+
// disable parallelism for the whole turn. Slightly coarser than
|
|
482
|
+
// the spec requires — but the platform API offers no finer knob.
|
|
483
|
+
if (params.tools.some(t => (t as { type?: string }).type === "custom")) {
|
|
484
|
+
params.parallel_tool_calls = false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
applyResponsesReasoningParams(
|
|
489
|
+
params,
|
|
490
|
+
model,
|
|
491
|
+
options,
|
|
492
|
+
messages,
|
|
493
|
+
effort =>
|
|
494
|
+
mapReasoningEffort(
|
|
495
|
+
effort as NonNullable<OpenAIResponsesOptions["reasoning"]>,
|
|
496
|
+
model.compat?.reasoningEffortMap,
|
|
497
|
+
),
|
|
498
|
+
options?.includeEncryptedReasoning ?? true,
|
|
499
|
+
options?.omitReasoningEffort ?? false,
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
if (options?.extraBody) {
|
|
503
|
+
Object.assign(params, options.extraBody);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return { conversationMessages, params };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function mapReasoningEffort(
|
|
510
|
+
effort: NonNullable<OpenAIResponsesOptions["reasoning"]>,
|
|
511
|
+
reasoningEffortMap: OpenAICompat["reasoningEffortMap"] | undefined,
|
|
512
|
+
): string {
|
|
513
|
+
return reasoningEffortMap?.[effort] ?? effort;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function isAzureOpenAIBaseUrl(baseUrl: string): boolean {
|
|
517
|
+
return baseUrl.includes(".openai.azure.com") || baseUrl.includes("azure.com/openai");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function supportsStrictMode(model: Model<"openai-responses">): boolean {
|
|
521
|
+
if (model.provider === "openai" || model.provider === "azure" || model.provider === "github-copilot") return true;
|
|
522
|
+
|
|
523
|
+
const baseUrl = model.baseUrl.toLowerCase();
|
|
524
|
+
return (
|
|
525
|
+
baseUrl.includes("api.openai.com") ||
|
|
526
|
+
baseUrl.includes(".openai.azure.com") ||
|
|
527
|
+
baseUrl.includes("models.inference.ai.azure.com")
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function supportsDeveloperRole(modelOrBaseUrl: Pick<Model, "provider" | "baseUrl"> | string): boolean {
|
|
532
|
+
const baseUrl =
|
|
533
|
+
typeof modelOrBaseUrl === "string" ? modelOrBaseUrl.toLowerCase() : (modelOrBaseUrl.baseUrl ?? "").toLowerCase();
|
|
534
|
+
return (
|
|
535
|
+
baseUrl.includes("api.openai.com") ||
|
|
536
|
+
baseUrl.includes(".openai.azure.com") ||
|
|
537
|
+
baseUrl.includes("azure.com/openai") ||
|
|
538
|
+
baseUrl.includes("models.inference.ai.azure.com") ||
|
|
539
|
+
baseUrl.includes("githubcopilot.com") ||
|
|
540
|
+
baseUrl.includes("copilot-api.")
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function convertConversationMessages(
|
|
545
|
+
model: Model<"openai-responses">,
|
|
546
|
+
context: Context,
|
|
547
|
+
strictResponsesPairing: boolean,
|
|
548
|
+
providerSessionState: OpenAIResponsesProviderSessionState | undefined,
|
|
549
|
+
options?: OpenAIResponsesOptions,
|
|
550
|
+
): ResponseInput {
|
|
551
|
+
const filterReasoning = <T extends { type?: string }>(items: T[]): T[] =>
|
|
552
|
+
options?.filterReasoningHistory ? items.filter(i => i?.type !== "reasoning") : items;
|
|
553
|
+
const messages: ResponseInput = [];
|
|
554
|
+
let knownCallIds = new Set<string>();
|
|
555
|
+
const customCallIds = new Set<string>();
|
|
556
|
+
const shouldReplayNativeHistory = canReplayOpenAIResponsesNativeHistory(providerSessionState);
|
|
557
|
+
const transformedMessages = transformMessages(context.messages, model, normalizeResponsesToolCallIdForTransform);
|
|
558
|
+
|
|
559
|
+
let msgIndex = 0;
|
|
560
|
+
for (const msg of transformedMessages) {
|
|
561
|
+
if (msg.role === "user" || msg.role === "developer") {
|
|
562
|
+
const providerPayload = (msg as { providerPayload?: AssistantMessage["providerPayload"] }).providerPayload;
|
|
563
|
+
const historyItems = getOpenAIResponsesHistoryItems(providerPayload, model.provider);
|
|
564
|
+
const shouldReplayPayloadItems =
|
|
565
|
+
shouldReplayNativeHistory ||
|
|
566
|
+
(historyItems?.some(item => {
|
|
567
|
+
if (!item || typeof item !== "object") return false;
|
|
568
|
+
const candidate = item as { type?: unknown };
|
|
569
|
+
return candidate.type === "compaction" || candidate.type === "compaction_summary";
|
|
570
|
+
}) ??
|
|
571
|
+
false);
|
|
572
|
+
if (historyItems && shouldReplayPayloadItems) {
|
|
573
|
+
messages.push(...sanitizeOpenAIResponsesHistoryItemsForReplay(filterReasoning(historyItems)));
|
|
574
|
+
knownCallIds = collectKnownCallIds(messages);
|
|
575
|
+
for (const id of collectCustomCallIds(messages)) customCallIds.add(id);
|
|
576
|
+
msgIndex++;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const content = convertResponsesInputContent(msg.content, model.input.includes("image"));
|
|
580
|
+
if (!content) continue;
|
|
581
|
+
messages.push({ role: "user", content });
|
|
582
|
+
} else if (msg.role === "assistant") {
|
|
583
|
+
const assistantMsg = msg as AssistantMessage;
|
|
584
|
+
const providerPayload = shouldReplayNativeHistory
|
|
585
|
+
? getOpenAIResponsesHistoryPayload(assistantMsg.providerPayload, model.provider, assistantMsg.provider)
|
|
586
|
+
: undefined;
|
|
587
|
+
const historyItems = providerPayload?.items;
|
|
588
|
+
if (historyItems) {
|
|
589
|
+
const sanitizedHistoryItems = sanitizeOpenAIResponsesHistoryItemsForReplay(filterReasoning(historyItems));
|
|
590
|
+
if (providerPayload?.dt) {
|
|
591
|
+
messages.push(...sanitizedHistoryItems);
|
|
592
|
+
} else {
|
|
593
|
+
messages.splice(0, messages.length, ...sanitizedHistoryItems);
|
|
594
|
+
}
|
|
595
|
+
knownCallIds = collectKnownCallIds(messages);
|
|
596
|
+
for (const id of collectCustomCallIds(messages)) customCallIds.add(id);
|
|
597
|
+
msgIndex++;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const outputItems = convertResponsesAssistantMessage(
|
|
602
|
+
assistantMsg,
|
|
603
|
+
model,
|
|
604
|
+
msgIndex,
|
|
605
|
+
knownCallIds,
|
|
606
|
+
shouldReplayNativeHistory,
|
|
607
|
+
customCallIds,
|
|
608
|
+
);
|
|
609
|
+
if (outputItems.length === 0) continue;
|
|
610
|
+
messages.push(...outputItems);
|
|
611
|
+
} else if (msg.role === "toolResult") {
|
|
612
|
+
appendResponsesToolResultMessages(messages, msg, model, strictResponsesPairing, knownCallIds, customCallIds);
|
|
613
|
+
}
|
|
614
|
+
msgIndex++;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return repairOrphanResponsesToolOutputs(messages);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Whether this model should get the OpenAI custom-tool grammar variant
|
|
622
|
+
* for `apply_patch`. The generated model catalog sets
|
|
623
|
+
* `model.applyPatchToolType` for first-party GPT-5 Responses models; this
|
|
624
|
+
* runtime path only consumes that metadata.
|
|
625
|
+
* @internal Exported for tests.
|
|
626
|
+
*/
|
|
627
|
+
export function supportsFreeformApplyPatch(model: Model<"openai-responses">): boolean {
|
|
628
|
+
return model.applyPatchToolType === "freeform";
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** @internal Exported for tests. */
|
|
632
|
+
export function mapOpenAIResponsesToolChoiceForTools(
|
|
633
|
+
choice: ToolChoice | undefined,
|
|
634
|
+
tools: Tool[],
|
|
635
|
+
model: Model<"openai-responses">,
|
|
636
|
+
): OpenAIResponsesToolChoice {
|
|
637
|
+
const mapped = mapToOpenAIResponsesToolChoice(choice);
|
|
638
|
+
if (!mapped || typeof mapped === "string" || mapped.type !== "function" || !supportsFreeformApplyPatch(model)) {
|
|
639
|
+
return mapped;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const customTool = tools.find(
|
|
643
|
+
tool => tool.customFormat && (tool.name === mapped.name || tool.customWireName === mapped.name),
|
|
644
|
+
);
|
|
645
|
+
return customTool ? { type: "custom", name: customTool.customWireName ?? customTool.name } : mapped;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** @internal Exported for tests. */
|
|
649
|
+
export function convertTools(tools: Tool[], strictMode: boolean, model: Model<"openai-responses">): OpenAITool[] {
|
|
650
|
+
const allowFreeform = supportsFreeformApplyPatch(model);
|
|
651
|
+
return tools.map(tool => {
|
|
652
|
+
if (allowFreeform && tool.customFormat) {
|
|
653
|
+
return {
|
|
654
|
+
type: "custom",
|
|
655
|
+
// Tool advertises its wire-level name (e.g. `apply_patch`) — the
|
|
656
|
+
// agent-loop dispatcher will match incoming calls by either the
|
|
657
|
+
// internal `name` or `customWireName`.
|
|
658
|
+
name: tool.customWireName ?? tool.name,
|
|
659
|
+
description: tool.description || "",
|
|
660
|
+
format: {
|
|
661
|
+
type: "grammar",
|
|
662
|
+
syntax: tool.customFormat.syntax,
|
|
663
|
+
definition: compactGrammarDefinition(tool.customFormat.syntax, tool.customFormat.definition),
|
|
664
|
+
},
|
|
665
|
+
} as unknown as OpenAITool;
|
|
666
|
+
}
|
|
667
|
+
const strict = !NO_STRICT && strictMode && tool.strict !== false;
|
|
668
|
+
const baseParameters = toolWireSchema(tool);
|
|
669
|
+
const responseParameters = sanitizeSchemaForOpenAIResponses(baseParameters);
|
|
670
|
+
const { schema: parameters, strict: effectiveStrict } = adaptSchemaForStrict(responseParameters, strict);
|
|
671
|
+
return {
|
|
672
|
+
type: "function",
|
|
673
|
+
name: tool.name,
|
|
674
|
+
description: tool.description || "",
|
|
675
|
+
parameters,
|
|
676
|
+
...(effectiveStrict && { strict: true }),
|
|
677
|
+
} as OpenAITool;
|
|
678
|
+
});
|
|
679
|
+
}
|