@vib-rato/ai 0.16.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 +3354 -0
- package/README.md +1194 -0
- package/dist/types/adapter-internals/provider-safety-stop.d.ts +45 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth-broker/client.d.ts +80 -0
- package/dist/types/auth-broker/index.d.ts +5 -0
- package/dist/types/auth-broker/redact.d.ts +7 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +153 -0
- package/dist/types/auth-broker/server.d.ts +32 -0
- package/dist/types/auth-broker/types.d.ts +132 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +555 -0
- package/dist/types/auth-gateway/http.d.ts +40 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +70 -0
- package/dist/types/auth-gateway/types.d.ts +129 -0
- package/dist/types/auth-storage.d.ts +1074 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/codex-tools.d.ts +4 -0
- package/dist/types/context-cap-policy.d.ts +68 -0
- package/dist/types/core.d.ts +35 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/model-cache.d.ts +24 -0
- package/dist/types/model-manager.d.ts +77 -0
- package/dist/types/model-pricing.d.ts +3 -0
- package/dist/types/model-retirements.d.ts +6 -0
- package/dist/types/model-thinking.d.ts +100 -0
- package/dist/types/models.d.ts +21 -0
- package/dist/types/openai-completions-compat.d.ts +34 -0
- package/dist/types/provider-details.d.ts +24 -0
- package/dist/types/provider-models/bundled-references.d.ts +4 -0
- package/dist/types/provider-models/descriptors.d.ts +48 -0
- package/dist/types/provider-models/google.d.ts +20 -0
- package/dist/types/provider-models/index.d.ts +5 -0
- package/dist/types/provider-models/ollama.d.ts +7 -0
- package/dist/types/provider-models/openai-compat.d.ts +293 -0
- package/dist/types/provider-models/special.d.ts +29 -0
- package/dist/types/providers/amazon-bedrock.d.ts +60 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
- package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
- package/dist/types/providers/anthropic.d.ts +280 -0
- package/dist/types/providers/aws-credential-config.d.ts +19 -0
- package/dist/types/providers/aws-credentials.d.ts +43 -0
- package/dist/types/providers/aws-eventstream.d.ts +38 -0
- package/dist/types/providers/aws-sigv4.d.ts +55 -0
- package/dist/types/providers/azure-openai-responses.d.ts +22 -0
- package/dist/types/providers/composer-discipline.d.ts +32 -0
- package/dist/types/providers/cursor/client-version.d.ts +10 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +16769 -0
- package/dist/types/providers/cursor-pi-args.d.ts +119 -0
- package/dist/types/providers/cursor.d.ts +72 -0
- package/dist/types/providers/dashscope-token-plan-headers.d.ts +57 -0
- package/dist/types/providers/error-message.d.ts +27 -0
- package/dist/types/providers/github-copilot-headers.d.ts +40 -0
- package/dist/types/providers/gitlab-duo.d.ts +27 -0
- package/dist/types/providers/google-auth.d.ts +26 -0
- package/dist/types/providers/google-gemini-cli.d.ts +75 -0
- package/dist/types/providers/google-gemini-headers.d.ts +43 -0
- package/dist/types/providers/google-shared.d.ts +183 -0
- package/dist/types/providers/google-types.d.ts +138 -0
- package/dist/types/providers/google-vertex.d.ts +11 -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/kiro-api-key.d.ts +50 -0
- package/dist/types/providers/kiro-codewhisperer.d.ts +11 -0
- package/dist/types/providers/mock.d.ts +189 -0
- package/dist/types/providers/ollama.d.ts +41 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
- package/dist/types/providers/openai-bounded-rate-limits.d.ts +3 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +1733 -0
- package/dist/types/providers/openai-chat-server.d.ts +16 -0
- package/dist/types/providers/openai-codex/constants.d.ts +26 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +50 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +18 -0
- package/dist/types/providers/openai-codex-responses.d.ts +71 -0
- package/dist/types/providers/openai-completions-compat.d.ts +6 -0
- package/dist/types/providers/openai-completions.d.ts +35 -0
- package/dist/types/providers/openai-opencodex-responses.d.ts +10 -0
- package/dist/types/providers/openai-request-transform.d.ts +4 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
- package/dist/types/providers/openai-responses-server.d.ts +17 -0
- package/dist/types/providers/openai-responses-shared.d.ts +106 -0
- package/dist/types/providers/openai-responses.d.ts +37 -0
- package/dist/types/providers/pi-native-client.d.ts +29 -0
- package/dist/types/providers/pi-native-server.d.ts +60 -0
- package/dist/types/providers/register-builtins.d.ts +59 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +33 -0
- package/dist/types/providers/vision-guard.d.ts +8 -0
- package/dist/types/rate-limit-utils.d.ts +19 -0
- package/dist/types/stream.d.ts +45 -0
- package/dist/types/types.d.ts +1073 -0
- package/dist/types/usage/claude.d.ts +3 -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 +2 -0
- package/dist/types/usage/grok-cli.d.ts +17 -0
- package/dist/types/usage/kimi.d.ts +4 -0
- package/dist/types/usage/minimax-code.d.ts +2 -0
- package/dist/types/usage/openai-codex.d.ts +3 -0
- package/dist/types/usage/shared.d.ts +1 -0
- package/dist/types/usage/zai.d.ts +2 -0
- package/dist/types/usage.d.ts +264 -0
- package/dist/types/utils/abort.d.ts +19 -0
- package/dist/types/utils/anthropic-auth.d.ts +39 -0
- package/dist/types/utils/block-symbols.d.ts +6 -0
- package/dist/types/utils/discovery/antigravity.d.ts +67 -0
- package/dist/types/utils/discovery/codex.d.ts +38 -0
- package/dist/types/utils/discovery/cursor.d.ts +49 -0
- package/dist/types/utils/discovery/gemini.d.ts +25 -0
- package/dist/types/utils/discovery/index.d.ts +4 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +81 -0
- package/dist/types/utils/event-stream.d.ts +41 -0
- package/dist/types/utils/fallback-transport.d.ts +110 -0
- package/dist/types/utils/fireworks-model-id.d.ts +10 -0
- package/dist/types/utils/foundry.d.ts +11 -0
- package/dist/types/utils/h2-fetch.d.ts +22 -0
- package/dist/types/utils/http-inspector.d.ts +59 -0
- package/dist/types/utils/idle-iterator.d.ts +122 -0
- package/dist/types/utils/json-parse.d.ts +98 -0
- package/dist/types/utils/oauth/alibaba-token-plan.d.ts +19 -0
- package/dist/types/utils/oauth/anthropic.d.ts +41 -0
- package/dist/types/utils/oauth/api-key-login.d.ts +38 -0
- package/dist/types/utils/oauth/api-key-validation.d.ts +39 -0
- package/dist/types/utils/oauth/bizrouter.d.ts +1 -0
- package/dist/types/utils/oauth/callback-server.d.ts +80 -0
- package/dist/types/utils/oauth/cerebras.d.ts +1 -0
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
- package/dist/types/utils/oauth/commandcode.d.ts +1 -0
- package/dist/types/utils/oauth/cursor.d.ts +15 -0
- package/dist/types/utils/oauth/deepinfra.d.ts +1 -0
- package/dist/types/utils/oauth/deepseek.d.ts +10 -0
- package/dist/types/utils/oauth/firepass.d.ts +1 -0
- package/dist/types/utils/oauth/fireworks.d.ts +1 -0
- package/dist/types/utils/oauth/fugu.d.ts +1 -0
- package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
- package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
- package/dist/types/utils/oauth/glm-zcode.d.ts +71 -0
- package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
- package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
- package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
- package/dist/types/utils/oauth/huggingface.d.ts +19 -0
- package/dist/types/utils/oauth/index.d.ts +39 -0
- package/dist/types/utils/oauth/kagi.d.ts +17 -0
- package/dist/types/utils/oauth/kilo.d.ts +5 -0
- package/dist/types/utils/oauth/kimi.d.ts +17 -0
- package/dist/types/utils/oauth/kiro.d.ts +71 -0
- package/dist/types/utils/oauth/litellm.d.ts +18 -0
- package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
- package/dist/types/utils/oauth/mara.d.ts +1 -0
- package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
- package/dist/types/utils/oauth/moonshot.d.ts +1 -0
- package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
- package/dist/types/utils/oauth/nvidia.d.ts +18 -0
- package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
- package/dist/types/utils/oauth/ollama.d.ts +18 -0
- package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
- package/dist/types/utils/oauth/opencode.d.ts +18 -0
- package/dist/types/utils/oauth/opengateway.d.ts +1 -0
- package/dist/types/utils/oauth/openrouter.d.ts +1 -0
- package/dist/types/utils/oauth/parallel.d.ts +17 -0
- package/dist/types/utils/oauth/perplexity.d.ts +4 -0
- package/dist/types/utils/oauth/pkce.d.ts +8 -0
- package/dist/types/utils/oauth/qianfan.d.ts +17 -0
- package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
- package/dist/types/utils/oauth/sglang.d.ts +16 -0
- package/dist/types/utils/oauth/synthetic.d.ts +1 -0
- package/dist/types/utils/oauth/tavily.d.ts +17 -0
- package/dist/types/utils/oauth/together.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +56 -0
- package/dist/types/utils/oauth/venice.d.ts +18 -0
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
- package/dist/types/utils/oauth/vllm.d.ts +16 -0
- package/dist/types/utils/oauth/xai.d.ts +30 -0
- package/dist/types/utils/oauth/xiaomi.d.ts +25 -0
- package/dist/types/utils/oauth/zai.d.ts +18 -0
- package/dist/types/utils/oauth/zenmux.d.ts +1 -0
- package/dist/types/utils/overflow.d.ts +14 -0
- package/dist/types/utils/parse-bind.d.ts +26 -0
- package/dist/types/utils/provider-response.d.ts +6 -0
- package/dist/types/utils/provider-safety-stop.d.ts +8 -0
- package/dist/types/utils/proxy.d.ts +7 -0
- package/dist/types/utils/retry-after.d.ts +3 -0
- package/dist/types/utils/retry-budget.d.ts +1 -0
- package/dist/types/utils/retry.d.ts +29 -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 +49 -0
- package/dist/types/utils/schema/index.d.ts +14 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
- package/dist/types/utils/schema/meta-validator.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +93 -0
- package/dist/types/utils/schema/root-combinator.d.ts +12 -0
- package/dist/types/utils/schema/spill.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +25 -0
- package/dist/types/utils/schema/types.d.ts +4 -0
- package/dist/types/utils/schema/wire.d.ts +54 -0
- package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
- package/dist/types/utils/sse-debug.d.ts +10 -0
- package/dist/types/utils/tool-call-healing.d.ts +80 -0
- package/dist/types/utils/tool-choice-capability.d.ts +57 -0
- package/dist/types/utils/tool-choice.d.ts +50 -0
- package/dist/types/utils/validation.d.ts +17 -0
- package/dist/types/utils.d.ts +117 -0
- package/package.json +152 -0
- package/src/adapter-internals/provider-safety-stop.d.ts +45 -0
- package/src/adapter-internals/provider-safety-stop.ts +156 -0
- package/src/api-registry.d.ts +30 -0
- package/src/api-registry.ts +96 -0
- package/src/auth-broker/client.ts +444 -0
- package/src/auth-broker/index.ts +5 -0
- package/src/auth-broker/redact.ts +39 -0
- package/src/auth-broker/refresher.ts +130 -0
- package/src/auth-broker/remote-store.ts +1576 -0
- package/src/auth-broker/server.ts +764 -0
- package/src/auth-broker/types.ts +164 -0
- package/src/auth-broker/wire-schemas.ts +261 -0
- package/src/auth-gateway/http.ts +198 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +1315 -0
- package/src/auth-gateway/types.ts +160 -0
- package/src/auth-storage.ts +7312 -0
- package/src/cli.ts +269 -0
- package/src/codex-tools.d.ts +4 -0
- package/src/codex-tools.ts +24 -0
- package/src/context-cap-policy.d.ts +68 -0
- package/src/context-cap-policy.ts +123 -0
- package/src/core.ts +44 -0
- package/src/index.ts +61 -0
- package/src/model-cache.ts +236 -0
- package/src/model-manager.ts +744 -0
- package/src/model-pricing.d.ts +3 -0
- package/src/model-pricing.ts +68 -0
- package/src/model-retirements.d.ts +6 -0
- package/src/model-retirements.ts +19 -0
- package/src/model-thinking.d.ts +100 -0
- package/src/model-thinking.ts +1054 -0
- package/src/models.d.ts +21 -0
- package/src/models.json +94672 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +126 -0
- package/src/openai-completions-compat.d.ts +34 -0
- package/src/openai-completions-compat.ts +383 -0
- package/src/prompts/composer-bash-policy-recovery.md +1 -0
- package/src/prompts/cursor-composer-bash-policy-recovery.md +1 -0
- package/src/prompts/cursor-composer-edit-discipline.md +7 -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 +392 -0
- package/src/provider-models/google.ts +92 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +159 -0
- package/src/provider-models/openai-compat.ts +2920 -0
- package/src/provider-models/special.ts +185 -0
- package/src/providers/amazon-bedrock.d.ts +60 -0
- package/src/providers/amazon-bedrock.ts +939 -0
- package/src/providers/anthropic-messages-server-schema.ts +229 -0
- package/src/providers/anthropic-messages-server.ts +839 -0
- package/src/providers/anthropic.d.ts +280 -0
- package/src/providers/anthropic.ts +4421 -0
- package/src/providers/aws-credential-config.d.ts +19 -0
- package/src/providers/aws-credential-config.ts +179 -0
- package/src/providers/aws-credentials.d.ts +43 -0
- package/src/providers/aws-credentials.ts +457 -0
- package/src/providers/aws-eventstream.d.ts +38 -0
- package/src/providers/aws-eventstream.ts +185 -0
- package/src/providers/aws-sigv4.d.ts +55 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.d.ts +22 -0
- package/src/providers/azure-openai-responses.ts +447 -0
- package/src/providers/composer-discipline.d.ts +32 -0
- package/src/providers/composer-discipline.ts +95 -0
- package/src/providers/cursor/client-version.d.ts +10 -0
- package/src/providers/cursor/client-version.ts +10 -0
- package/src/providers/cursor/exec-modern.d.ts +98 -0
- package/src/providers/cursor/exec-modern.ts +497 -0
- package/src/providers/cursor/gen/agent_pb.d.ts +16769 -0
- package/src/providers/cursor/gen/agent_pb.ts +19780 -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.d.ts +119 -0
- package/src/providers/cursor-pi-args.ts +187 -0
- package/src/providers/cursor.d.ts +72 -0
- package/src/providers/cursor.ts +3396 -0
- package/src/providers/dashscope-token-plan-headers.d.ts +57 -0
- package/src/providers/dashscope-token-plan-headers.ts +84 -0
- package/src/providers/error-message.d.ts +27 -0
- package/src/providers/error-message.ts +21 -0
- package/src/providers/github-copilot-headers.d.ts +40 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.d.ts +27 -0
- package/src/providers/gitlab-duo.ts +393 -0
- package/src/providers/google-auth.d.ts +26 -0
- package/src/providers/google-auth.ts +262 -0
- package/src/providers/google-gemini-cli.d.ts +75 -0
- package/src/providers/google-gemini-cli.ts +969 -0
- package/src/providers/google-gemini-headers.d.ts +43 -0
- package/src/providers/google-gemini-headers.ts +100 -0
- package/src/providers/google-shared.d.ts +183 -0
- package/src/providers/google-shared.ts +1105 -0
- package/src/providers/google-types.d.ts +138 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.d.ts +11 -0
- package/src/providers/google-vertex.ts +124 -0
- package/src/providers/google.d.ts +4 -0
- package/src/providers/google.ts +41 -0
- package/src/providers/grammar.d.ts +1 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.d.ts +27 -0
- package/src/providers/kimi.ts +52 -0
- package/src/providers/kiro-api-key.d.ts +50 -0
- package/src/providers/kiro-api-key.ts +786 -0
- package/src/providers/kiro-codewhisperer.d.ts +11 -0
- package/src/providers/kiro-codewhisperer.ts +600 -0
- package/src/providers/mock.ts +526 -0
- package/src/providers/ollama.d.ts +41 -0
- package/src/providers/ollama.ts +645 -0
- package/src/providers/openai-anthropic-shim.d.ts +31 -0
- package/src/providers/openai-anthropic-shim.ts +156 -0
- package/src/providers/openai-bounded-rate-limits.d.ts +3 -0
- package/src/providers/openai-bounded-rate-limits.ts +57 -0
- package/src/providers/openai-chat-server-schema.ts +254 -0
- package/src/providers/openai-chat-server.ts +724 -0
- package/src/providers/openai-codex/constants.d.ts +26 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.d.ts +50 -0
- package/src/providers/openai-codex/request-transformer.ts +219 -0
- package/src/providers/openai-codex/response-handler.d.ts +18 -0
- package/src/providers/openai-codex/response-handler.ts +111 -0
- package/src/providers/openai-codex-responses.d.ts +71 -0
- package/src/providers/openai-codex-responses.ts +3288 -0
- package/src/providers/openai-completions-compat.d.ts +6 -0
- package/src/providers/openai-completions-compat.ts +6 -0
- package/src/providers/openai-completions.d.ts +35 -0
- package/src/providers/openai-completions.ts +2294 -0
- package/src/providers/openai-opencodex-responses.ts +174 -0
- package/src/providers/openai-request-transform.d.ts +4 -0
- package/src/providers/openai-request-transform.ts +136 -0
- package/src/providers/openai-responses-server-schema.ts +290 -0
- package/src/providers/openai-responses-server.ts +1268 -0
- package/src/providers/openai-responses-shared.d.ts +106 -0
- package/src/providers/openai-responses-shared.ts +1253 -0
- package/src/providers/openai-responses.d.ts +37 -0
- package/src/providers/openai-responses.ts +989 -0
- package/src/providers/pi-native-client.d.ts +29 -0
- package/src/providers/pi-native-client.ts +243 -0
- package/src/providers/pi-native-server.ts +488 -0
- package/src/providers/register-builtins.d.ts +59 -0
- package/src/providers/register-builtins.ts +544 -0
- package/src/providers/synthetic.d.ts +26 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.d.ts +33 -0
- package/src/providers/transform-messages.ts +408 -0
- package/src/providers/vision-guard.d.ts +8 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/rate-limit-utils.d.ts +19 -0
- package/src/rate-limit-utils.ts +102 -0
- package/src/stream.d.ts +45 -0
- package/src/stream.ts +1306 -0
- package/src/types.d.ts +1073 -0
- package/src/types.ts +1305 -0
- package/src/usage/claude.ts +449 -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/grok-cli.ts +259 -0
- package/src/usage/kimi.ts +285 -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 +190 -0
- package/src/utils/abort.d.ts +19 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/anthropic-auth.ts +95 -0
- package/src/utils/block-symbols.d.ts +6 -0
- package/src/utils/block-symbols.ts +11 -0
- package/src/utils/discovery/antigravity.ts +275 -0
- package/src/utils/discovery/codex.ts +362 -0
- package/src/utils/discovery/cursor.ts +388 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +4 -0
- package/src/utils/discovery/openai-compatible.ts +379 -0
- package/src/utils/event-stream.d.ts +41 -0
- package/src/utils/event-stream.ts +269 -0
- package/src/utils/fallback-transport.d.ts +110 -0
- package/src/utils/fallback-transport.ts +411 -0
- package/src/utils/fireworks-model-id.d.ts +10 -0
- package/src/utils/fireworks-model-id.ts +30 -0
- package/src/utils/foundry.d.ts +11 -0
- package/src/utils/foundry.ts +18 -0
- package/src/utils/h2-fetch.ts +60 -0
- package/src/utils/http-inspector.d.ts +59 -0
- package/src/utils/http-inspector.ts +380 -0
- package/src/utils/idle-iterator.d.ts +122 -0
- package/src/utils/idle-iterator.ts +410 -0
- package/src/utils/json-parse.d.ts +98 -0
- package/src/utils/json-parse.ts +607 -0
- package/src/utils/oauth/alibaba-token-plan.ts +60 -0
- package/src/utils/oauth/anthropic.ts +233 -0
- package/src/utils/oauth/api-key-login.ts +98 -0
- package/src/utils/oauth/api-key-validation.ts +344 -0
- package/src/utils/oauth/bizrouter.ts +15 -0
- package/src/utils/oauth/callback-server.d.ts +80 -0
- package/src/utils/oauth/callback-server.ts +359 -0
- package/src/utils/oauth/cerebras.ts +16 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/commandcode.ts +17 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/deepinfra.ts +15 -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/fugu.ts +15 -0
- package/src/utils/oauth/github-copilot.d.ts +38 -0
- package/src/utils/oauth/github-copilot.ts +362 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/glm-zcode.d.ts +71 -0
- package/src/utils/oauth/glm-zcode.ts +433 -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 +558 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.d.ts +17 -0
- package/src/utils/oauth/kimi.ts +275 -0
- package/src/utils/oauth/kiro.ts +448 -0
- package/src/utils/oauth/litellm.ts +47 -0
- package/src/utils/oauth/lm-studio.ts +38 -0
- package/src/utils/oauth/mara.ts +16 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +16 -0
- package/src/utils/oauth/nanogpt.ts +15 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -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/opengateway.ts +15 -0
- package/src/utils/oauth/openrouter.ts +16 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +225 -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/sglang.ts +42 -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.d.ts +56 -0
- package/src/utils/oauth/types.ts +122 -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 +42 -0
- package/src/utils/oauth/xai.ts +246 -0
- package/src/utils/oauth/xiaomi.ts +199 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -0
- package/src/utils/overflow.ts +275 -0
- package/src/utils/parse-bind.ts +81 -0
- package/src/utils/provider-response.d.ts +6 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/provider-safety-stop.ts +8 -0
- package/src/utils/proxy.d.ts +7 -0
- package/src/utils/proxy.ts +652 -0
- package/src/utils/retry-after.d.ts +3 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry-budget.d.ts +1 -0
- package/src/utils/retry-budget.ts +4 -0
- package/src/utils/retry.d.ts +29 -0
- package/src/utils/retry.ts +67 -0
- package/src/utils/schema/CONSTRAINTS.md +164 -0
- package/src/utils/schema/adapt.d.ts +24 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.d.ts +30 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.d.ts +11 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.d.ts +10 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.d.ts +4 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.d.ts +49 -0
- package/src/utils/schema/fields.ts +190 -0
- package/src/utils/schema/index.d.ts +14 -0
- package/src/utils/schema/index.ts +14 -0
- package/src/utils/schema/json-schema-validator.d.ts +12 -0
- package/src/utils/schema/json-schema-validator.ts +577 -0
- package/src/utils/schema/meta-validator.d.ts +2 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.d.ts +93 -0
- package/src/utils/schema/normalize.ts +1588 -0
- package/src/utils/schema/root-combinator.d.ts +12 -0
- package/src/utils/schema/root-combinator.ts +143 -0
- package/src/utils/schema/spill.d.ts +8 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.d.ts +25 -0
- package/src/utils/schema/stamps.ts +97 -0
- package/src/utils/schema/types.d.ts +4 -0
- package/src/utils/schema/types.ts +11 -0
- package/src/utils/schema/wire.d.ts +54 -0
- package/src/utils/schema/wire.ts +213 -0
- package/src/utils/schema/zod-decontaminate.d.ts +31 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sse-debug.d.ts +10 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/tool-call-healing.d.ts +80 -0
- package/src/utils/tool-call-healing.ts +298 -0
- package/src/utils/tool-choice-capability.d.ts +57 -0
- package/src/utils/tool-choice-capability.ts +633 -0
- package/src/utils/tool-choice.d.ts +50 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1080 -0
- package/src/utils.d.ts +117 -0
- package/src/utils.ts +523 -0
|
@@ -0,0 +1,1268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Responses HTTP wire-format ↔ vib Context bridge for the auth-gateway.
|
|
3
|
+
*
|
|
4
|
+
* Inbound: parses `POST /v1/responses` request bodies into a {@link ParsedRequest}.
|
|
5
|
+
* Outbound: encodes vib's {@link AssistantMessage} (and event stream) back into
|
|
6
|
+
* the documented `response.*` SSE taxonomy or the non-streaming JSON shape.
|
|
7
|
+
*
|
|
8
|
+
* Spec: https://platform.openai.com/docs/api-reference/responses
|
|
9
|
+
* Inverse direction (source-of-truth for item shapes): ../../providers/openai-responses.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { logger } from "@vib-rato/utils";
|
|
13
|
+
import { resolvePromptCacheKey } from "../auth-gateway/http";
|
|
14
|
+
import type { AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
|
|
15
|
+
import type {
|
|
16
|
+
AssistantMessage,
|
|
17
|
+
AssistantMessageEventStream,
|
|
18
|
+
Context,
|
|
19
|
+
Message,
|
|
20
|
+
TextContent,
|
|
21
|
+
ThinkingContent,
|
|
22
|
+
Tool,
|
|
23
|
+
ToolCall,
|
|
24
|
+
} from "../types";
|
|
25
|
+
import { sanitizeJsonStrings } from "../utils";
|
|
26
|
+
import {
|
|
27
|
+
type OpenAIResponsesFunctionCallItem,
|
|
28
|
+
type OpenAIResponsesFunctionCallOutputItem,
|
|
29
|
+
type OpenAIResponsesInputContent,
|
|
30
|
+
type OpenAIResponsesOutputContent,
|
|
31
|
+
type OpenAIResponsesReasoningItem,
|
|
32
|
+
type OpenAIResponsesTool,
|
|
33
|
+
openaiResponsesRequestSchema,
|
|
34
|
+
} from "./openai-responses-server-schema";
|
|
35
|
+
|
|
36
|
+
export type { ParsedRequest };
|
|
37
|
+
|
|
38
|
+
// ─── narrow guards ──────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
function isReasoningEffort(value: unknown): value is NonNullable<ParsedRequest["options"]["reasoning"]> {
|
|
41
|
+
return (
|
|
42
|
+
value === "minimal" ||
|
|
43
|
+
value === "low" ||
|
|
44
|
+
value === "medium" ||
|
|
45
|
+
value === "high" ||
|
|
46
|
+
value === "xhigh" ||
|
|
47
|
+
value === "max"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isServiceTier(value: unknown): value is NonNullable<ParsedRequest["options"]["serviceTier"]> {
|
|
52
|
+
return value === "auto" || value === "default" || value === "flex" || value === "scale" || value === "priority";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isObj(v: unknown): v is Record<string, unknown> {
|
|
56
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function asString(v: unknown): string | undefined {
|
|
60
|
+
return typeof v === "string" ? v : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── id helpers ─────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
function uuidNoDashes(): string {
|
|
66
|
+
return crypto.randomUUID().replace(/-/g, "");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeRespId(): string {
|
|
70
|
+
return `resp_${uuidNoDashes()}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function makeMsgId(): string {
|
|
74
|
+
return `msg_${uuidNoDashes()}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeReasoningId(): string {
|
|
78
|
+
return `rs_${uuidNoDashes()}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function makeFuncCallId(): string {
|
|
82
|
+
return `fc_${uuidNoDashes()}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function makeCustomCallId(): string {
|
|
86
|
+
return `ctc_${uuidNoDashes()}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── once-only warnings ─────────────────────────────────────────────────────
|
|
90
|
+
// Module-scoped so we don't spam logs once per turn.
|
|
91
|
+
|
|
92
|
+
let warnedImageNotSupported = false;
|
|
93
|
+
let warnedFileNotSupported = false;
|
|
94
|
+
let warnedReasoningSummaryLevel = false;
|
|
95
|
+
|
|
96
|
+
// ─── inbound parser helpers ─────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function reasoningContentFromItem(
|
|
99
|
+
item: OpenAIResponsesReasoningItem,
|
|
100
|
+
): Pick<ThinkingContent, "thinking" | "provenance" | "summaryText" | "rawText"> {
|
|
101
|
+
// `summary[]` is provider-displayable; `content[]` is raw reasoning. Keep
|
|
102
|
+
// these channels distinct so a Responses gateway round-trip cannot relabel
|
|
103
|
+
// raw CoT as a summary merely because summary text is absent.
|
|
104
|
+
const summaryText = (item.summary ?? []).map(part => part.text).join("");
|
|
105
|
+
const rawText = (item.content ?? []).map(part => part.text).join("");
|
|
106
|
+
if (summaryText && rawText) {
|
|
107
|
+
return { thinking: summaryText, provenance: "mixed", summaryText, rawText };
|
|
108
|
+
}
|
|
109
|
+
if (summaryText) return { thinking: summaryText, provenance: "summary", summaryText };
|
|
110
|
+
if (rawText) return { thinking: rawText, provenance: "raw", rawText };
|
|
111
|
+
return { thinking: "" };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type InputBlockUnion =
|
|
115
|
+
| { type: "input_text"; text: string }
|
|
116
|
+
| { type: "text"; text: string }
|
|
117
|
+
| { type: "input_image"; detail?: "auto" | "low" | "high"; image_url?: string; file_id?: string }
|
|
118
|
+
| { type: "input_file"; file_id?: string; filename?: string; file_data?: string };
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Walk an input message's content array and produce pi-ai's `TextContent[]`.
|
|
122
|
+
* `input_image`/`input_file` blocks become bracketed text placeholders since
|
|
123
|
+
* pi-ai's `ImageContent` only carries inline base64 data and we have no
|
|
124
|
+
* resolver for OpenAI `image_url` / `file_id` references. Logs once per kind.
|
|
125
|
+
*/
|
|
126
|
+
function inputContentParts(blocks: OpenAIResponsesInputContent[] | string | undefined): string | TextContent[] {
|
|
127
|
+
if (typeof blocks === "string") return blocks;
|
|
128
|
+
if (!blocks) return [];
|
|
129
|
+
const parts: TextContent[] = [];
|
|
130
|
+
for (const raw of blocks) {
|
|
131
|
+
const block = raw as InputBlockUnion;
|
|
132
|
+
if (block.type === "input_text" || block.type === "text") {
|
|
133
|
+
parts.push({ type: "text", text: block.text });
|
|
134
|
+
} else if (block.type === "input_image") {
|
|
135
|
+
if (!warnedImageNotSupported) {
|
|
136
|
+
warnedImageNotSupported = true;
|
|
137
|
+
logger.warn("openai-responses-server: input_image dropped (no pi-ai bridge for image_url/file_id)", {
|
|
138
|
+
hasUrl: typeof block.image_url === "string",
|
|
139
|
+
hasFileId: typeof block.file_id === "string",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const ref = block.image_url ?? block.file_id ?? "?";
|
|
143
|
+
parts.push({ type: "text", text: `[image: ${ref}]` });
|
|
144
|
+
} else if (block.type === "input_file") {
|
|
145
|
+
if (!warnedFileNotSupported) {
|
|
146
|
+
warnedFileNotSupported = true;
|
|
147
|
+
logger.warn("openai-responses-server: input_file dropped (no pi-ai bridge for file_id/file_data)", {
|
|
148
|
+
hasFileId: typeof block.file_id === "string",
|
|
149
|
+
hasFileData: typeof block.file_data === "string",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const ref = block.file_id ?? block.filename ?? "?";
|
|
153
|
+
parts.push({ type: "text", text: `[file: ${ref}]` });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return parts.length === 1 ? parts[0].text : parts;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type OutputBlockUnion =
|
|
160
|
+
| { type: "output_text"; text: string }
|
|
161
|
+
| { type: "text"; text: string }
|
|
162
|
+
| { type: "refusal"; refusal: string };
|
|
163
|
+
|
|
164
|
+
function outputTextOf(blocks: OpenAIResponsesOutputContent[] | string | undefined): TextContent[] {
|
|
165
|
+
if (typeof blocks === "string") return blocks.length > 0 ? [{ type: "text", text: blocks }] : [];
|
|
166
|
+
if (!blocks) return [];
|
|
167
|
+
const out: TextContent[] = [];
|
|
168
|
+
for (const raw of blocks) {
|
|
169
|
+
const block = raw as OutputBlockUnion;
|
|
170
|
+
if (block.type === "output_text" || block.type === "text") {
|
|
171
|
+
out.push({ type: "text", text: block.text });
|
|
172
|
+
} else if (block.type === "refusal") {
|
|
173
|
+
// Preserve the refusal reason so history replay still carries it.
|
|
174
|
+
out.push({ type: "text", text: `[refusal: ${block.refusal}]` });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// The schema accepts a much wider tool_choice union than the SDK type so the
|
|
181
|
+
// walker narrows against the local schema shape.
|
|
182
|
+
type ParsedToolChoice =
|
|
183
|
+
| "auto"
|
|
184
|
+
| "none"
|
|
185
|
+
| "required"
|
|
186
|
+
| { type: "function"; name: string }
|
|
187
|
+
| { type: "custom"; name: string }
|
|
188
|
+
| {
|
|
189
|
+
type:
|
|
190
|
+
| "web_search_preview"
|
|
191
|
+
| "file_search"
|
|
192
|
+
| "computer_use_preview"
|
|
193
|
+
| "code_interpreter"
|
|
194
|
+
| "image_generation"
|
|
195
|
+
| "mcp";
|
|
196
|
+
}
|
|
197
|
+
| { type: "allowed_tools"; mode: "auto" | "required"; tools: Array<{ type: string; name?: string }> };
|
|
198
|
+
|
|
199
|
+
function mapToolChoice(value: ParsedToolChoice | undefined): ParsedRequest["options"]["toolChoice"] {
|
|
200
|
+
if (value === undefined) return undefined;
|
|
201
|
+
if (value === "auto" || value === "none" || value === "required") return value;
|
|
202
|
+
if ("type" in value) {
|
|
203
|
+
// `custom` (OpenAI code backend apply_patch) and `function` both resolve to the same
|
|
204
|
+
// pi-ai shape: pi-ai's dispatcher matches `Tool.name` AND `customWireName`,
|
|
205
|
+
// so passing the wire name works for either.
|
|
206
|
+
if (value.type === "function" || value.type === "custom") return { name: value.name };
|
|
207
|
+
// Hosted tools + allowed_tools — we don't surface these to pi-ai; fall
|
|
208
|
+
// back to letting the model pick a tool freely.
|
|
209
|
+
return "auto";
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildTools(tools: Array<OpenAIResponsesTool | { type: string }> | undefined): Tool[] | undefined {
|
|
215
|
+
if (!tools) return undefined;
|
|
216
|
+
const out: Tool[] = [];
|
|
217
|
+
for (const t of tools) {
|
|
218
|
+
// Skip non-function tools (web_search, file_search, …).
|
|
219
|
+
if (t.type !== "function") continue;
|
|
220
|
+
const fn = t as Extract<OpenAIResponsesTool, { type: "function" }>;
|
|
221
|
+
const tool: Tool = {
|
|
222
|
+
name: fn.name,
|
|
223
|
+
description: fn.description ?? "",
|
|
224
|
+
parameters: (fn.parameters ?? {}) as Tool["parameters"],
|
|
225
|
+
};
|
|
226
|
+
if (fn.strict !== undefined && fn.strict !== null) tool.strict = fn.strict;
|
|
227
|
+
out.push(tool);
|
|
228
|
+
}
|
|
229
|
+
return out.length > 0 ? out : undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function ensureAssistantPlaceholder(messages: Message[], modelId: string, now: number): AssistantMessage {
|
|
233
|
+
const last = messages[messages.length - 1];
|
|
234
|
+
if (last && last.role === "assistant") return last;
|
|
235
|
+
const placeholder: AssistantMessage = {
|
|
236
|
+
role: "assistant",
|
|
237
|
+
content: [],
|
|
238
|
+
api: "openai-responses",
|
|
239
|
+
provider: "openai",
|
|
240
|
+
model: modelId,
|
|
241
|
+
usage: {
|
|
242
|
+
input: 0,
|
|
243
|
+
output: 0,
|
|
244
|
+
cacheRead: 0,
|
|
245
|
+
cacheWrite: 0,
|
|
246
|
+
totalTokens: 0,
|
|
247
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
248
|
+
},
|
|
249
|
+
stopReason: "stop",
|
|
250
|
+
timestamp: now,
|
|
251
|
+
};
|
|
252
|
+
messages.push(placeholder);
|
|
253
|
+
return placeholder;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Flatten a function_call_output array form (text + refusal) into a single string. */
|
|
257
|
+
function flattenFunctionOutputArray(blocks: readonly unknown[]): string {
|
|
258
|
+
const parts: string[] = [];
|
|
259
|
+
for (const raw of blocks) {
|
|
260
|
+
if (!isObj(raw)) continue;
|
|
261
|
+
const t = raw.type;
|
|
262
|
+
if (t === "output_text" || t === "text") {
|
|
263
|
+
const text = asString(raw.text);
|
|
264
|
+
if (text) parts.push(text);
|
|
265
|
+
} else if (t === "refusal") {
|
|
266
|
+
const refusal = asString(raw.refusal);
|
|
267
|
+
if (refusal) parts.push(`[refusal: ${refusal}]`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return parts.join("");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── parseRequest ───────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
276
|
+
// Header capture is centralized in `auth-gateway/server.ts` (the
|
|
277
|
+
// allow-listed set lands on `options.headers` automatically). We also
|
|
278
|
+
// consult `headers` here to populate `options.promptCacheKey` when the
|
|
279
|
+
// client signals a cache identity outside the body — see the
|
|
280
|
+
// `resolvePromptCacheKey` call further down.
|
|
281
|
+
|
|
282
|
+
const parsed = openaiResponsesRequestSchema.safeParse(body);
|
|
283
|
+
if (!parsed.success) {
|
|
284
|
+
throw new Error(`openai-responses: ${parsed.error.message}`);
|
|
285
|
+
}
|
|
286
|
+
const data = parsed.data;
|
|
287
|
+
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
const messages: Message[] = [];
|
|
290
|
+
const systemPrompt: string[] = [];
|
|
291
|
+
|
|
292
|
+
if (typeof data.instructions === "string" && data.instructions.length > 0) {
|
|
293
|
+
systemPrompt.push(data.instructions);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (typeof data.input === "string") {
|
|
297
|
+
messages.push({ role: "user", content: data.input, timestamp: now });
|
|
298
|
+
} else if (data.input) {
|
|
299
|
+
for (const item of data.input) {
|
|
300
|
+
// Items may omit `type` and rely on `role` (the convenience shape).
|
|
301
|
+
const effectiveType = item.type ?? ("role" in item ? "message" : undefined);
|
|
302
|
+
if (effectiveType === "message") {
|
|
303
|
+
const msg = item as {
|
|
304
|
+
role?: string;
|
|
305
|
+
content?: OpenAIResponsesInputContent[] | OpenAIResponsesOutputContent[] | string;
|
|
306
|
+
};
|
|
307
|
+
switch (msg.role) {
|
|
308
|
+
case "system": {
|
|
309
|
+
const text = inputContentParts(msg.content as OpenAIResponsesInputContent[] | string | undefined);
|
|
310
|
+
const flat = typeof text === "string" ? text : text.map(p => p.text).join("");
|
|
311
|
+
if (flat.length > 0) systemPrompt.push(flat);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case "user":
|
|
315
|
+
case "developer": {
|
|
316
|
+
const content = inputContentParts(msg.content as OpenAIResponsesInputContent[] | string | undefined);
|
|
317
|
+
messages.push({ role: msg.role, content, timestamp: now });
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case "assistant": {
|
|
321
|
+
const parts = outputTextOf(msg.content as OpenAIResponsesOutputContent[] | string | undefined);
|
|
322
|
+
messages.push({
|
|
323
|
+
role: "assistant",
|
|
324
|
+
content: parts,
|
|
325
|
+
api: "openai-responses",
|
|
326
|
+
provider: "openai",
|
|
327
|
+
model: data.model,
|
|
328
|
+
usage: {
|
|
329
|
+
input: 0,
|
|
330
|
+
output: 0,
|
|
331
|
+
cacheRead: 0,
|
|
332
|
+
cacheWrite: 0,
|
|
333
|
+
totalTokens: 0,
|
|
334
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
335
|
+
},
|
|
336
|
+
stopReason: "stop",
|
|
337
|
+
timestamp: now,
|
|
338
|
+
});
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (effectiveType === "reasoning") {
|
|
345
|
+
const reasoning = item as OpenAIResponsesReasoningItem;
|
|
346
|
+
const content = reasoningContentFromItem(reasoning);
|
|
347
|
+
const thinking: ThinkingContent = {
|
|
348
|
+
type: "thinking",
|
|
349
|
+
...content,
|
|
350
|
+
thinkingSignature: JSON.stringify(reasoning),
|
|
351
|
+
...(reasoning.id ? { itemId: reasoning.id } : {}),
|
|
352
|
+
};
|
|
353
|
+
ensureAssistantPlaceholder(messages, data.model, now).content.push(thinking);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (effectiveType === "function_call") {
|
|
357
|
+
const call = item as OpenAIResponsesFunctionCallItem;
|
|
358
|
+
const argsRaw = call.arguments ?? "{}";
|
|
359
|
+
let args: Record<string, unknown>;
|
|
360
|
+
try {
|
|
361
|
+
const parsedArgs: unknown = JSON.parse(argsRaw);
|
|
362
|
+
args = isObj(parsedArgs) ? parsedArgs : {};
|
|
363
|
+
} catch {
|
|
364
|
+
throw new Error(`openai-responses: function_call ${call.call_id} has invalid JSON arguments`);
|
|
365
|
+
}
|
|
366
|
+
const toolCall: ToolCall = {
|
|
367
|
+
type: "toolCall",
|
|
368
|
+
id: call.call_id,
|
|
369
|
+
name: call.name,
|
|
370
|
+
arguments: args,
|
|
371
|
+
...(call.id ? { thoughtSignature: call.id } : {}),
|
|
372
|
+
};
|
|
373
|
+
ensureAssistantPlaceholder(messages, data.model, now).content.push(toolCall);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (effectiveType === "custom_tool_call") {
|
|
377
|
+
const call = item as { id?: string; call_id: string; name: string; input: string };
|
|
378
|
+
// Custom tools carry a raw input string. We stash it in `arguments.input`
|
|
379
|
+
// matching pi-ai's openai-responses-shared convention, and tag the call
|
|
380
|
+
// with `customWireName` so encoders re-emit it as `custom_tool_call`.
|
|
381
|
+
const toolCall: ToolCall = {
|
|
382
|
+
type: "toolCall",
|
|
383
|
+
id: call.call_id,
|
|
384
|
+
name: call.name,
|
|
385
|
+
arguments: { input: call.input ?? "" },
|
|
386
|
+
customWireName: call.name,
|
|
387
|
+
...(call.id ? { thoughtSignature: call.id } : {}),
|
|
388
|
+
};
|
|
389
|
+
ensureAssistantPlaceholder(messages, data.model, now).content.push(toolCall);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (effectiveType === "function_call_output") {
|
|
393
|
+
const output = item as OpenAIResponsesFunctionCallOutputItem;
|
|
394
|
+
const toolName = findToolNameById(messages, output.call_id);
|
|
395
|
+
const text =
|
|
396
|
+
typeof output.output === "string"
|
|
397
|
+
? output.output
|
|
398
|
+
: Array.isArray(output.output)
|
|
399
|
+
? flattenFunctionOutputArray(output.output)
|
|
400
|
+
: "";
|
|
401
|
+
messages.push({
|
|
402
|
+
role: "toolResult",
|
|
403
|
+
toolCallId: output.call_id,
|
|
404
|
+
toolName,
|
|
405
|
+
content: [{ type: "text", text }],
|
|
406
|
+
isError: false,
|
|
407
|
+
timestamp: now,
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (effectiveType === "custom_tool_call_output") {
|
|
412
|
+
const output = item as { call_id: string; output: string };
|
|
413
|
+
const toolName = findToolNameById(messages, output.call_id);
|
|
414
|
+
messages.push({
|
|
415
|
+
role: "toolResult",
|
|
416
|
+
toolCallId: output.call_id,
|
|
417
|
+
toolName,
|
|
418
|
+
content: [{ type: "text", text: output.output ?? "" }],
|
|
419
|
+
isError: false,
|
|
420
|
+
timestamp: now,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
// Other item types are tolerated but not bridged.
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const tools = buildTools(data.tools);
|
|
428
|
+
const context: Context = {
|
|
429
|
+
...(systemPrompt.length > 0 ? { systemPrompt } : {}),
|
|
430
|
+
messages,
|
|
431
|
+
...(tools ? { tools } : {}),
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const options: ParsedRequest["options"] = {};
|
|
435
|
+
if (data.max_output_tokens !== undefined) options.maxOutputTokens = data.max_output_tokens;
|
|
436
|
+
if (data.temperature !== undefined) options.temperature = data.temperature;
|
|
437
|
+
if (data.top_p !== undefined) options.topP = data.top_p;
|
|
438
|
+
if (data.stop !== undefined && data.stop !== null) {
|
|
439
|
+
options.stopSequences = typeof data.stop === "string" ? [data.stop] : data.stop;
|
|
440
|
+
}
|
|
441
|
+
const toolChoice = mapToolChoice(data.tool_choice as ParsedToolChoice | undefined);
|
|
442
|
+
if (toolChoice !== undefined) options.toolChoice = toolChoice;
|
|
443
|
+
if (data.reasoning?.effort && isReasoningEffort(data.reasoning.effort)) {
|
|
444
|
+
options.reasoning = data.reasoning.effort;
|
|
445
|
+
}
|
|
446
|
+
// OpenAI summary: `none` → suppress; `auto`/`concise`/`detailed` → request
|
|
447
|
+
// visible summary. pi-ai has no per-level plumbing — log once and let the
|
|
448
|
+
// provider default kick in.
|
|
449
|
+
if (data.reasoning?.summary === "none") {
|
|
450
|
+
options.hideThinkingSummary = true;
|
|
451
|
+
} else if (
|
|
452
|
+
data.reasoning?.summary === "auto" ||
|
|
453
|
+
data.reasoning?.summary === "concise" ||
|
|
454
|
+
data.reasoning?.summary === "detailed"
|
|
455
|
+
) {
|
|
456
|
+
if (!warnedReasoningSummaryLevel) {
|
|
457
|
+
warnedReasoningSummaryLevel = true;
|
|
458
|
+
logger.debug("openai-responses-server: reasoning.summary level not differentiated", {
|
|
459
|
+
level: data.reasoning.summary,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (data.service_tier !== undefined && isServiceTier(data.service_tier)) {
|
|
464
|
+
options.serviceTier = data.service_tier;
|
|
465
|
+
}
|
|
466
|
+
if (data.presence_penalty !== undefined) options.presencePenalty = data.presence_penalty;
|
|
467
|
+
if (data.frequency_penalty !== undefined) options.frequencyPenalty = data.frequency_penalty;
|
|
468
|
+
if (data.parallel_tool_calls !== undefined) options.parallelToolCalls = data.parallel_tool_calls;
|
|
469
|
+
const cacheKey = resolvePromptCacheKey(body, headers);
|
|
470
|
+
if (cacheKey !== undefined) options.promptCacheKey = cacheKey;
|
|
471
|
+
if (data.previous_response_id !== undefined) options.previousResponseId = data.previous_response_id;
|
|
472
|
+
if (data.user !== undefined) options.user = data.user;
|
|
473
|
+
if (isObj(data.metadata)) options.metadata = data.metadata;
|
|
474
|
+
// `store` is a stateful-storage hint that vib's gateway doesn't honour;
|
|
475
|
+
// silently accepted by the schema. No typed slot — drop.
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
modelId: data.model,
|
|
479
|
+
context,
|
|
480
|
+
stream: data.stream === true,
|
|
481
|
+
options,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function findToolNameById(messages: Message[], callId: string): string {
|
|
486
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
487
|
+
const m = messages[i];
|
|
488
|
+
if (m.role !== "assistant") continue;
|
|
489
|
+
for (const c of m.content) {
|
|
490
|
+
if (c.type === "toolCall" && c.id === callId) return c.name;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return "";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ─── formatError ────────────────────────────────────────────────────────────
|
|
497
|
+
|
|
498
|
+
export function formatError(status: number, type: string, message: string): Response {
|
|
499
|
+
return new Response(JSON.stringify({ error: { message, type } }), {
|
|
500
|
+
status,
|
|
501
|
+
headers: { "Content-Type": "application/json" },
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ─── output item builders (shared by streaming + non-streaming encoders) ────
|
|
506
|
+
|
|
507
|
+
type ReasoningOutputItem = {
|
|
508
|
+
type: "reasoning";
|
|
509
|
+
id: string;
|
|
510
|
+
summary: Array<{ type: "summary_text"; text: string }>;
|
|
511
|
+
} & Record<string, unknown>;
|
|
512
|
+
|
|
513
|
+
type MessageOutputItem = {
|
|
514
|
+
type: "message";
|
|
515
|
+
id: string;
|
|
516
|
+
role: "assistant";
|
|
517
|
+
status: "completed";
|
|
518
|
+
content: Array<{ type: "output_text"; text: string; annotations: never[] }>;
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
type FunctionCallOutputItem = {
|
|
522
|
+
type: "function_call";
|
|
523
|
+
id: string;
|
|
524
|
+
call_id: string;
|
|
525
|
+
name: string;
|
|
526
|
+
arguments: string;
|
|
527
|
+
status: "completed";
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
type CustomToolCallOutputItem = {
|
|
531
|
+
type: "custom_tool_call";
|
|
532
|
+
id: string;
|
|
533
|
+
call_id: string;
|
|
534
|
+
name: string;
|
|
535
|
+
input: string;
|
|
536
|
+
status: "completed";
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
type OutputItem = ReasoningOutputItem | MessageOutputItem | FunctionCallOutputItem | CustomToolCallOutputItem;
|
|
540
|
+
|
|
541
|
+
type ResponseStatus = "completed" | "in_progress" | "failed" | "incomplete";
|
|
542
|
+
|
|
543
|
+
function responseStatusForStopReason(message: AssistantMessage): ResponseStatus {
|
|
544
|
+
if (message.stopReason === "length") return "incomplete";
|
|
545
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") return "failed";
|
|
546
|
+
return "completed";
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Privacy boundary for the public Responses envelope: a reasoning item's
|
|
551
|
+
* `summary_text` must carry ONLY provider-displayable summary text, never raw
|
|
552
|
+
* chain-of-thought. A summary is published ONLY for blocks explicitly marked
|
|
553
|
+
* `provenance: "summary" | "mixed"` (the #2304 provenance path), sourced from
|
|
554
|
+
* `summaryText`. Raw-provenance AND unmarked blocks are omitted: unmarked
|
|
555
|
+
* `thinking` can be raw CoT from providers that stream unmarked reasoning (e.g.
|
|
556
|
+
* openai-completions / ollama) which the auth gateway re-encodes into the
|
|
557
|
+
* Responses wire format, so falling open to `part.thinking` would leak raw CoT.
|
|
558
|
+
*/
|
|
559
|
+
function envelopeSummaryText(part: ThinkingContent): string | undefined {
|
|
560
|
+
if (part.provenance === "summary" || part.provenance === "mixed") return part.summaryText;
|
|
561
|
+
return undefined;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function envelopeSummaryParts(part: ThinkingContent): Array<{ type: "summary_text"; text: string }> {
|
|
565
|
+
const text = envelopeSummaryText(part);
|
|
566
|
+
return text ? [{ type: "summary_text", text }] : [];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function normalizeSummaryParts(value: unknown): Array<{ type: "summary_text"; text: string }> {
|
|
570
|
+
// A serialized signature's `summary` is, by the Responses protocol, provider-
|
|
571
|
+
// displayable summary text (raw reasoning lives in content[]/encrypted_content,
|
|
572
|
+
// which is stripped). Coerce to the canonical shape, keeping only well-formed
|
|
573
|
+
// summary_text entries. This is NOT the unsafe `part.thinking` fallback.
|
|
574
|
+
if (!Array.isArray(value)) return [];
|
|
575
|
+
const out: Array<{ type: "summary_text"; text: string }> = [];
|
|
576
|
+
for (const entry of value) {
|
|
577
|
+
if (isObj(entry) && entry.type === "summary_text" && typeof entry.text === "string") {
|
|
578
|
+
out.push({ type: "summary_text", text: entry.text });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return out;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function buildReasoningItem(part: ThinkingContent): ReasoningOutputItem {
|
|
585
|
+
const baseId = part.itemId ?? makeReasoningId();
|
|
586
|
+
if (part.thinkingSignature) {
|
|
587
|
+
try {
|
|
588
|
+
const sigParsed: unknown = JSON.parse(part.thinkingSignature);
|
|
589
|
+
if (isObj(sigParsed) && sigParsed.type === "reasoning") {
|
|
590
|
+
const id = part.itemId ?? asString(sigParsed.id) ?? makeReasoningId();
|
|
591
|
+
// Preserve any extra fields (encrypted_content, …) the original carried,
|
|
592
|
+
// but normalize the summary into the canonical `{type, text}[]` shape.
|
|
593
|
+
const merged: Record<string, unknown> = { ...sigParsed, type: "reasoning", id };
|
|
594
|
+
merged.summary =
|
|
595
|
+
part.provenance === "summary" || part.provenance === "mixed"
|
|
596
|
+
? envelopeSummaryParts(part)
|
|
597
|
+
: normalizeSummaryParts(sigParsed.summary);
|
|
598
|
+
// Strip any `content[]` (raw `reasoning_text`) the serialized signature
|
|
599
|
+
// carried: raw chain-of-thought must never surface in the public final
|
|
600
|
+
// envelope (#2304 CoT boundary). Opaque top-level `encrypted_content`
|
|
601
|
+
// (when present) is a separate field and is preserved by the spread above.
|
|
602
|
+
delete merged.content;
|
|
603
|
+
return merged as ReasoningOutputItem;
|
|
604
|
+
}
|
|
605
|
+
} catch {
|
|
606
|
+
// Not a serialized Responses reasoning item; fall through to fresh build.
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
type: "reasoning",
|
|
611
|
+
id: baseId,
|
|
612
|
+
summary: envelopeSummaryParts(part),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function reasoningItemId(part: ThinkingContent): string {
|
|
617
|
+
if (part.itemId) return part.itemId;
|
|
618
|
+
if (part.thinkingSignature) {
|
|
619
|
+
try {
|
|
620
|
+
const sigParsed: unknown = JSON.parse(part.thinkingSignature);
|
|
621
|
+
if (isObj(sigParsed)) {
|
|
622
|
+
const id = asString(sigParsed.id);
|
|
623
|
+
if (id) return id;
|
|
624
|
+
}
|
|
625
|
+
} catch {
|
|
626
|
+
// Not a serialized Responses reasoning item.
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return makeReasoningId();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Walk the assistant content array and group consecutive TextContent into a
|
|
634
|
+
* single message item; each ThinkingContent / ToolCall is its own item.
|
|
635
|
+
*/
|
|
636
|
+
function buildOutputItems(message: AssistantMessage): OutputItem[] {
|
|
637
|
+
const out: OutputItem[] = [];
|
|
638
|
+
let pendingMessage: MessageOutputItem | null = null;
|
|
639
|
+
const flushMessage = () => {
|
|
640
|
+
if (pendingMessage) {
|
|
641
|
+
out.push(pendingMessage);
|
|
642
|
+
pendingMessage = null;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
for (const part of message.content) {
|
|
647
|
+
if (part.type === "text") {
|
|
648
|
+
if (!pendingMessage) {
|
|
649
|
+
pendingMessage = {
|
|
650
|
+
type: "message",
|
|
651
|
+
id: makeMsgId(),
|
|
652
|
+
role: "assistant",
|
|
653
|
+
status: "completed",
|
|
654
|
+
content: [],
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
pendingMessage.content.push({ type: "output_text", text: part.text, annotations: [] });
|
|
658
|
+
} else if (part.type === "thinking") {
|
|
659
|
+
flushMessage();
|
|
660
|
+
out.push(buildReasoningItem(part));
|
|
661
|
+
} else if (part.type === "toolCall") {
|
|
662
|
+
flushMessage();
|
|
663
|
+
if (part.customWireName) {
|
|
664
|
+
const rawInput =
|
|
665
|
+
typeof part.arguments?.input === "string" ? (part.arguments.input as string).toWellFormed() : "";
|
|
666
|
+
out.push({
|
|
667
|
+
type: "custom_tool_call",
|
|
668
|
+
id: part.thoughtSignature ?? makeCustomCallId(),
|
|
669
|
+
call_id: part.id,
|
|
670
|
+
name: part.customWireName,
|
|
671
|
+
input: rawInput,
|
|
672
|
+
status: "completed",
|
|
673
|
+
});
|
|
674
|
+
} else {
|
|
675
|
+
out.push({
|
|
676
|
+
type: "function_call",
|
|
677
|
+
id: part.thoughtSignature ?? makeFuncCallId(),
|
|
678
|
+
call_id: part.id,
|
|
679
|
+
name: part.name,
|
|
680
|
+
arguments: JSON.stringify(sanitizeJsonStrings(part.arguments ?? {})),
|
|
681
|
+
status: "completed",
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// RedactedThinking / Image are silently dropped — no direct Responses wire representation.
|
|
686
|
+
}
|
|
687
|
+
flushMessage();
|
|
688
|
+
return out;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function buildUsage(message: AssistantMessage): Record<string, unknown> {
|
|
692
|
+
const u = message.usage;
|
|
693
|
+
const inputTokens = u.input + u.cacheRead + u.cacheWrite;
|
|
694
|
+
return {
|
|
695
|
+
input_tokens: inputTokens,
|
|
696
|
+
input_tokens_details: { cached_tokens: u.cacheRead },
|
|
697
|
+
output_tokens: u.output,
|
|
698
|
+
output_tokens_details: { reasoning_tokens: u.reasoningTokens ?? 0 },
|
|
699
|
+
total_tokens: inputTokens + u.output,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function buildResponseEnvelope(
|
|
704
|
+
message: AssistantMessage,
|
|
705
|
+
requestedModelId: string,
|
|
706
|
+
id: string,
|
|
707
|
+
status: ResponseStatus,
|
|
708
|
+
items: OutputItem[] | [],
|
|
709
|
+
usage: Record<string, unknown> | null,
|
|
710
|
+
): Record<string, unknown> {
|
|
711
|
+
return {
|
|
712
|
+
id,
|
|
713
|
+
object: "response",
|
|
714
|
+
created_at: Math.floor(message.timestamp / 1000),
|
|
715
|
+
status,
|
|
716
|
+
model: requestedModelId,
|
|
717
|
+
output: items,
|
|
718
|
+
usage,
|
|
719
|
+
...(status === "incomplete" ? { incomplete_details: { reason: "max_output_tokens" } } : {}),
|
|
720
|
+
...(status === "failed" ? { error: { message: message.errorMessage ?? "response failed" } } : {}),
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// ─── encodeResponse (non-streaming) ─────────────────────────────────────────
|
|
725
|
+
|
|
726
|
+
export function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown> {
|
|
727
|
+
const items = buildOutputItems(message);
|
|
728
|
+
return buildResponseEnvelope(
|
|
729
|
+
message,
|
|
730
|
+
requestedModelId,
|
|
731
|
+
makeRespId(),
|
|
732
|
+
responseStatusForStopReason(message),
|
|
733
|
+
items,
|
|
734
|
+
buildUsage(message),
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// ─── encodeStream ───────────────────────────────────────────────────────────
|
|
739
|
+
|
|
740
|
+
interface OpenMessage {
|
|
741
|
+
kind: "message";
|
|
742
|
+
itemId: string;
|
|
743
|
+
outputIndex: number;
|
|
744
|
+
contentIndex: number;
|
|
745
|
+
currentPartText: string;
|
|
746
|
+
content: Array<{ type: "output_text"; text: string; annotations: never[] }>;
|
|
747
|
+
}
|
|
748
|
+
interface OpenReasoning {
|
|
749
|
+
kind: "reasoning";
|
|
750
|
+
itemId: string;
|
|
751
|
+
outputIndex: number;
|
|
752
|
+
summaryText: string;
|
|
753
|
+
summaryPartText: string;
|
|
754
|
+
}
|
|
755
|
+
interface OpenFunctionCall {
|
|
756
|
+
kind: "function_call";
|
|
757
|
+
itemId: string;
|
|
758
|
+
outputIndex: number;
|
|
759
|
+
callId: string;
|
|
760
|
+
name: string;
|
|
761
|
+
argsText: string;
|
|
762
|
+
/** Set when the underlying ToolCall is a custom-tool emission. */
|
|
763
|
+
customWireName?: string;
|
|
764
|
+
}
|
|
765
|
+
type OpenItem = OpenMessage | OpenReasoning | OpenFunctionCall;
|
|
766
|
+
|
|
767
|
+
function sseEvent(name: string, data: unknown): string {
|
|
768
|
+
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function encodeStream(
|
|
772
|
+
events: AssistantMessageEventStream,
|
|
773
|
+
requestedModelId: string,
|
|
774
|
+
): ReadableStream<Uint8Array> {
|
|
775
|
+
const encoder = new TextEncoder();
|
|
776
|
+
const responseId = makeRespId();
|
|
777
|
+
let sequenceNumber = 0;
|
|
778
|
+
const seq = () => sequenceNumber++;
|
|
779
|
+
|
|
780
|
+
return new ReadableStream<Uint8Array>({
|
|
781
|
+
async start(controller) {
|
|
782
|
+
const emit = (name: string, data: Record<string, unknown>) => {
|
|
783
|
+
controller.enqueue(encoder.encode(sseEvent(name, { type: name, sequence_number: seq(), ...data })));
|
|
784
|
+
};
|
|
785
|
+
const emitDone = () => controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
786
|
+
|
|
787
|
+
let createdAt = Math.floor(Date.now() / 1000);
|
|
788
|
+
let outputIndex = 0;
|
|
789
|
+
const state: { open: OpenItem | null } = { open: null };
|
|
790
|
+
const finishedItems: OutputItem[] = [];
|
|
791
|
+
|
|
792
|
+
const responseSnapshot = (status: ResponseStatus, output: OutputItem[] | []) => ({
|
|
793
|
+
id: responseId,
|
|
794
|
+
object: "response",
|
|
795
|
+
created_at: createdAt,
|
|
796
|
+
status,
|
|
797
|
+
model: requestedModelId,
|
|
798
|
+
output,
|
|
799
|
+
usage: null,
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const openMessage = (): OpenMessage => {
|
|
803
|
+
const itemId = makeMsgId();
|
|
804
|
+
const item = {
|
|
805
|
+
type: "message" as const,
|
|
806
|
+
id: itemId,
|
|
807
|
+
status: "in_progress",
|
|
808
|
+
role: "assistant" as const,
|
|
809
|
+
content: [] as Array<{ type: "output_text"; text: string; annotations: never[] }>,
|
|
810
|
+
};
|
|
811
|
+
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
812
|
+
const next: OpenMessage = {
|
|
813
|
+
kind: "message",
|
|
814
|
+
itemId,
|
|
815
|
+
outputIndex,
|
|
816
|
+
contentIndex: 0,
|
|
817
|
+
currentPartText: "",
|
|
818
|
+
content: [],
|
|
819
|
+
};
|
|
820
|
+
state.open = next;
|
|
821
|
+
return next;
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
const openReasoning = (partial: AssistantMessage, contentIndex: number): OpenReasoning => {
|
|
825
|
+
const part = partial.content[contentIndex];
|
|
826
|
+
const itemId = part && part.type === "thinking" ? reasoningItemId(part) : makeReasoningId();
|
|
827
|
+
const item = {
|
|
828
|
+
type: "reasoning" as const,
|
|
829
|
+
id: itemId,
|
|
830
|
+
summary: [] as Array<{ type: "summary_text"; text: string }>,
|
|
831
|
+
};
|
|
832
|
+
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
833
|
+
const next: OpenReasoning = {
|
|
834
|
+
kind: "reasoning",
|
|
835
|
+
itemId,
|
|
836
|
+
outputIndex,
|
|
837
|
+
summaryText: "",
|
|
838
|
+
summaryPartText: "",
|
|
839
|
+
};
|
|
840
|
+
state.open = next;
|
|
841
|
+
return next;
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const openToolCall = (partial: AssistantMessage, contentIndex: number): OpenFunctionCall => {
|
|
845
|
+
const part = partial.content[contentIndex];
|
|
846
|
+
const tc = part && part.type === "toolCall" ? part : undefined;
|
|
847
|
+
const customWireName: string | undefined =
|
|
848
|
+
tc && typeof tc.customWireName === "string" && tc.customWireName.length > 0
|
|
849
|
+
? tc.customWireName
|
|
850
|
+
: undefined;
|
|
851
|
+
const isCustom = customWireName !== undefined;
|
|
852
|
+
const itemId = tc?.thoughtSignature ?? (isCustom ? makeCustomCallId() : makeFuncCallId());
|
|
853
|
+
const callId = tc?.id ?? "";
|
|
854
|
+
const name = customWireName ?? tc?.name ?? "";
|
|
855
|
+
const item = isCustom
|
|
856
|
+
? {
|
|
857
|
+
type: "custom_tool_call" as const,
|
|
858
|
+
id: itemId,
|
|
859
|
+
call_id: callId,
|
|
860
|
+
name,
|
|
861
|
+
input: "",
|
|
862
|
+
status: "in_progress",
|
|
863
|
+
}
|
|
864
|
+
: {
|
|
865
|
+
type: "function_call" as const,
|
|
866
|
+
id: itemId,
|
|
867
|
+
call_id: callId,
|
|
868
|
+
name,
|
|
869
|
+
arguments: "",
|
|
870
|
+
status: "in_progress",
|
|
871
|
+
};
|
|
872
|
+
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
873
|
+
const next: OpenFunctionCall = {
|
|
874
|
+
kind: "function_call",
|
|
875
|
+
itemId,
|
|
876
|
+
outputIndex,
|
|
877
|
+
callId,
|
|
878
|
+
name,
|
|
879
|
+
argsText: "",
|
|
880
|
+
...(isCustom ? { customWireName } : {}),
|
|
881
|
+
};
|
|
882
|
+
state.open = next;
|
|
883
|
+
return next;
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const closeOpen = () => {
|
|
887
|
+
if (!state.open) return;
|
|
888
|
+
if (state.open.kind === "message") {
|
|
889
|
+
const item = {
|
|
890
|
+
type: "message",
|
|
891
|
+
id: state.open.itemId,
|
|
892
|
+
status: "completed",
|
|
893
|
+
role: "assistant",
|
|
894
|
+
content: state.open.content,
|
|
895
|
+
};
|
|
896
|
+
emit("response.output_item.done", { output_index: state.open.outputIndex, item });
|
|
897
|
+
finishedItems.push({
|
|
898
|
+
type: "message",
|
|
899
|
+
id: state.open.itemId,
|
|
900
|
+
role: "assistant",
|
|
901
|
+
status: "completed",
|
|
902
|
+
content: state.open.content,
|
|
903
|
+
});
|
|
904
|
+
} else if (state.open.kind === "reasoning") {
|
|
905
|
+
const summary = state.open.summaryText
|
|
906
|
+
? [{ type: "summary_text" as const, text: state.open.summaryText }]
|
|
907
|
+
: [];
|
|
908
|
+
// Final reasoning envelope carries the displayable summary ONLY. Raw
|
|
909
|
+
// chain-of-thought is streamed live via response.reasoning_text.delta
|
|
910
|
+
// (the internal raw channel) and is deliberately NOT persisted into the
|
|
911
|
+
// terminal item's content[] — the public final envelope must never carry
|
|
912
|
+
// raw CoT (#2304 CoT boundary).
|
|
913
|
+
const item: ReasoningOutputItem = {
|
|
914
|
+
type: "reasoning",
|
|
915
|
+
id: state.open.itemId,
|
|
916
|
+
summary,
|
|
917
|
+
};
|
|
918
|
+
emit("response.output_item.done", { output_index: state.open.outputIndex, item });
|
|
919
|
+
finishedItems.push(item);
|
|
920
|
+
} else {
|
|
921
|
+
const text = state.open.argsText ?? "";
|
|
922
|
+
if (state.open.customWireName) {
|
|
923
|
+
const item = {
|
|
924
|
+
type: "custom_tool_call",
|
|
925
|
+
id: state.open.itemId,
|
|
926
|
+
call_id: state.open.callId ?? "",
|
|
927
|
+
name: state.open.customWireName,
|
|
928
|
+
input: text,
|
|
929
|
+
status: "completed",
|
|
930
|
+
};
|
|
931
|
+
emit("response.output_item.done", { output_index: state.open.outputIndex, item });
|
|
932
|
+
finishedItems.push({
|
|
933
|
+
type: "custom_tool_call",
|
|
934
|
+
id: state.open.itemId,
|
|
935
|
+
call_id: state.open.callId ?? "",
|
|
936
|
+
name: state.open.customWireName,
|
|
937
|
+
input: text,
|
|
938
|
+
status: "completed",
|
|
939
|
+
});
|
|
940
|
+
} else {
|
|
941
|
+
const item = {
|
|
942
|
+
type: "function_call",
|
|
943
|
+
id: state.open.itemId,
|
|
944
|
+
call_id: state.open.callId ?? "",
|
|
945
|
+
name: state.open.name ?? "",
|
|
946
|
+
arguments: text,
|
|
947
|
+
status: "completed",
|
|
948
|
+
};
|
|
949
|
+
emit("response.output_item.done", { output_index: state.open.outputIndex, item });
|
|
950
|
+
finishedItems.push({
|
|
951
|
+
type: "function_call",
|
|
952
|
+
id: state.open.itemId,
|
|
953
|
+
call_id: state.open.callId ?? "",
|
|
954
|
+
name: state.open.name ?? "",
|
|
955
|
+
arguments: text,
|
|
956
|
+
status: "completed",
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
outputIndex++;
|
|
961
|
+
state.open = null;
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
try {
|
|
965
|
+
let finalMessage: AssistantMessage | null = null;
|
|
966
|
+
let failureMessage: AssistantMessage | null = null;
|
|
967
|
+
|
|
968
|
+
for await (const ev of events) {
|
|
969
|
+
switch (ev.type) {
|
|
970
|
+
case "start": {
|
|
971
|
+
createdAt = Math.floor((ev.partial.timestamp || Date.now()) / 1000);
|
|
972
|
+
// response.created — initial envelope.
|
|
973
|
+
controller.enqueue(
|
|
974
|
+
encoder.encode(
|
|
975
|
+
sseEvent("response.created", {
|
|
976
|
+
type: "response.created",
|
|
977
|
+
sequence_number: seq(),
|
|
978
|
+
response: responseSnapshot("in_progress", []),
|
|
979
|
+
}),
|
|
980
|
+
),
|
|
981
|
+
);
|
|
982
|
+
// response.in_progress — mirrors real OpenAI; some clients gate
|
|
983
|
+
// on it before reading items.
|
|
984
|
+
controller.enqueue(
|
|
985
|
+
encoder.encode(
|
|
986
|
+
sseEvent("response.in_progress", {
|
|
987
|
+
type: "response.in_progress",
|
|
988
|
+
sequence_number: seq(),
|
|
989
|
+
response: responseSnapshot("in_progress", []),
|
|
990
|
+
}),
|
|
991
|
+
),
|
|
992
|
+
);
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
case "text_start": {
|
|
996
|
+
let cur: OpenMessage;
|
|
997
|
+
if (state.open && state.open.kind === "message") {
|
|
998
|
+
// continue same message item, new content part
|
|
999
|
+
cur = state.open;
|
|
1000
|
+
cur.currentPartText = "";
|
|
1001
|
+
} else {
|
|
1002
|
+
if (state.open) closeOpen();
|
|
1003
|
+
cur = openMessage();
|
|
1004
|
+
}
|
|
1005
|
+
const part = { type: "output_text", text: "", annotations: [] as never[] };
|
|
1006
|
+
emit("response.content_part.added", {
|
|
1007
|
+
item_id: cur.itemId,
|
|
1008
|
+
output_index: cur.outputIndex,
|
|
1009
|
+
content_index: cur.contentIndex,
|
|
1010
|
+
part,
|
|
1011
|
+
});
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
case "text_delta": {
|
|
1015
|
+
if (state.open?.kind !== "message") break;
|
|
1016
|
+
const cur: OpenMessage = state.open;
|
|
1017
|
+
cur.currentPartText += ev.delta;
|
|
1018
|
+
emit("response.output_text.delta", {
|
|
1019
|
+
item_id: cur.itemId,
|
|
1020
|
+
output_index: cur.outputIndex,
|
|
1021
|
+
content_index: cur.contentIndex,
|
|
1022
|
+
delta: ev.delta,
|
|
1023
|
+
logprobs: [],
|
|
1024
|
+
});
|
|
1025
|
+
// TODO: when pi-ai surfaces output_text annotations
|
|
1026
|
+
// (web_search citations, …), emit
|
|
1027
|
+
// `response.output_text.annotation.added` here.
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
case "text_end": {
|
|
1031
|
+
if (state.open?.kind !== "message") break;
|
|
1032
|
+
const cur: OpenMessage = state.open;
|
|
1033
|
+
const text = ev.content ?? cur.currentPartText;
|
|
1034
|
+
emit("response.output_text.done", {
|
|
1035
|
+
item_id: cur.itemId,
|
|
1036
|
+
output_index: cur.outputIndex,
|
|
1037
|
+
content_index: cur.contentIndex,
|
|
1038
|
+
text,
|
|
1039
|
+
logprobs: [],
|
|
1040
|
+
});
|
|
1041
|
+
cur.content.push({ type: "output_text", text, annotations: [] });
|
|
1042
|
+
emit("response.content_part.done", {
|
|
1043
|
+
item_id: cur.itemId,
|
|
1044
|
+
output_index: cur.outputIndex,
|
|
1045
|
+
content_index: cur.contentIndex,
|
|
1046
|
+
part: { type: "output_text", text, annotations: [] },
|
|
1047
|
+
});
|
|
1048
|
+
cur.contentIndex += 1;
|
|
1049
|
+
cur.currentPartText = "";
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
case "thinking_start": {
|
|
1053
|
+
if (state.open) closeOpen();
|
|
1054
|
+
openReasoning(ev.partial, ev.contentIndex);
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
case "thinking_delta": {
|
|
1058
|
+
// Raw reasoning is private. The public Responses gateway emits only
|
|
1059
|
+
// provider-displayable reasoning_summary_* events.
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
case "thinking_end": {
|
|
1063
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1064
|
+
// Raw reasoning is intentionally omitted from every public gateway
|
|
1065
|
+
// frame. Only reasoning_summary_* events populate the terminal item.
|
|
1066
|
+
closeOpen();
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
case "reasoning_summary_start": {
|
|
1070
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1071
|
+
const cur: OpenReasoning = state.open;
|
|
1072
|
+
cur.summaryPartText = "";
|
|
1073
|
+
emit("response.reasoning_summary_part.added", {
|
|
1074
|
+
item_id: cur.itemId,
|
|
1075
|
+
output_index: cur.outputIndex,
|
|
1076
|
+
summary_index: 0,
|
|
1077
|
+
part: { type: "summary_text", text: "" },
|
|
1078
|
+
});
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
case "reasoning_summary_delta": {
|
|
1082
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1083
|
+
const cur: OpenReasoning = state.open;
|
|
1084
|
+
cur.summaryPartText += ev.delta;
|
|
1085
|
+
cur.summaryText += ev.delta;
|
|
1086
|
+
emit("response.reasoning_summary_text.delta", {
|
|
1087
|
+
item_id: cur.itemId,
|
|
1088
|
+
output_index: cur.outputIndex,
|
|
1089
|
+
summary_index: 0,
|
|
1090
|
+
delta: ev.delta,
|
|
1091
|
+
});
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
case "reasoning_summary_end": {
|
|
1095
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1096
|
+
const cur: OpenReasoning = state.open;
|
|
1097
|
+
const text = ev.content ?? cur.summaryPartText;
|
|
1098
|
+
// A separator-only accumulated summary (e.g. a part.done "\n\n" before any
|
|
1099
|
+
// real text) is treated as empty so the real end content wins.
|
|
1100
|
+
if (!cur.summaryText.trim()) cur.summaryText = text;
|
|
1101
|
+
emit("response.reasoning_summary_text.done", {
|
|
1102
|
+
item_id: cur.itemId,
|
|
1103
|
+
output_index: cur.outputIndex,
|
|
1104
|
+
summary_index: 0,
|
|
1105
|
+
text,
|
|
1106
|
+
});
|
|
1107
|
+
emit("response.reasoning_summary_part.done", {
|
|
1108
|
+
item_id: cur.itemId,
|
|
1109
|
+
output_index: cur.outputIndex,
|
|
1110
|
+
summary_index: 0,
|
|
1111
|
+
part: { type: "summary_text", text },
|
|
1112
|
+
});
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
case "toolcall_start": {
|
|
1116
|
+
if (state.open) closeOpen();
|
|
1117
|
+
openToolCall(ev.partial, ev.contentIndex);
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
case "toolcall_delta": {
|
|
1121
|
+
if (state.open?.kind !== "function_call") break;
|
|
1122
|
+
const cur: OpenFunctionCall = state.open;
|
|
1123
|
+
cur.argsText += ev.delta;
|
|
1124
|
+
if (cur.customWireName) {
|
|
1125
|
+
emit("response.custom_tool_call_input.delta", {
|
|
1126
|
+
item_id: cur.itemId,
|
|
1127
|
+
output_index: cur.outputIndex,
|
|
1128
|
+
delta: ev.delta,
|
|
1129
|
+
});
|
|
1130
|
+
} else {
|
|
1131
|
+
emit("response.function_call_arguments.delta", {
|
|
1132
|
+
item_id: cur.itemId,
|
|
1133
|
+
output_index: cur.outputIndex,
|
|
1134
|
+
delta: ev.delta,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
case "toolcall_end": {
|
|
1140
|
+
if (state.open?.kind !== "function_call") break;
|
|
1141
|
+
const cur: OpenFunctionCall = state.open;
|
|
1142
|
+
// Promote possibly-late info from the canonical ToolCall.
|
|
1143
|
+
const tc = ev.toolCall;
|
|
1144
|
+
if (tc.customWireName && !cur.customWireName) cur.customWireName = tc.customWireName;
|
|
1145
|
+
if (tc.thoughtSignature) cur.itemId = tc.thoughtSignature;
|
|
1146
|
+
cur.callId = tc.id;
|
|
1147
|
+
cur.name = cur.customWireName ?? tc.name;
|
|
1148
|
+
if (cur.customWireName) {
|
|
1149
|
+
// Custom tool: raw input string. Streamed deltas accumulated
|
|
1150
|
+
// the wire-level body; fall back to `arguments.input` from
|
|
1151
|
+
// the finalized ToolCall when nothing streamed (rare).
|
|
1152
|
+
const rawInput =
|
|
1153
|
+
cur.argsText ||
|
|
1154
|
+
(typeof tc.arguments?.input === "string" ? (tc.arguments.input as string) : "");
|
|
1155
|
+
cur.argsText = rawInput;
|
|
1156
|
+
emit("response.custom_tool_call_input.done", {
|
|
1157
|
+
item_id: cur.itemId,
|
|
1158
|
+
output_index: cur.outputIndex,
|
|
1159
|
+
input: rawInput,
|
|
1160
|
+
name: cur.name,
|
|
1161
|
+
});
|
|
1162
|
+
} else {
|
|
1163
|
+
// Standard JSON tool: arguments object on the vib side, the
|
|
1164
|
+
// wire wants the JSON string the model emitted (= streamed deltas).
|
|
1165
|
+
const argsJson =
|
|
1166
|
+
cur.argsText.toWellFormed() || JSON.stringify(sanitizeJsonStrings(tc.arguments ?? {}));
|
|
1167
|
+
cur.argsText = argsJson;
|
|
1168
|
+
emit("response.function_call_arguments.done", {
|
|
1169
|
+
item_id: cur.itemId,
|
|
1170
|
+
output_index: cur.outputIndex,
|
|
1171
|
+
arguments: argsJson,
|
|
1172
|
+
name: cur.name,
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
closeOpen();
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
case "done": {
|
|
1179
|
+
finalMessage = ev.message;
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
case "error": {
|
|
1183
|
+
failureMessage = ev.error;
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
if (failureMessage) {
|
|
1190
|
+
if (state.open) closeOpen();
|
|
1191
|
+
controller.enqueue(
|
|
1192
|
+
encoder.encode(
|
|
1193
|
+
sseEvent("response.failed", {
|
|
1194
|
+
type: "response.failed",
|
|
1195
|
+
sequence_number: seq(),
|
|
1196
|
+
response: {
|
|
1197
|
+
...responseSnapshot("failed", finishedItems),
|
|
1198
|
+
error: { message: failureMessage.errorMessage ?? "stream failed" },
|
|
1199
|
+
},
|
|
1200
|
+
}),
|
|
1201
|
+
),
|
|
1202
|
+
);
|
|
1203
|
+
emitDone();
|
|
1204
|
+
controller.close();
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if (state.open) closeOpen();
|
|
1209
|
+
const message = finalMessage ?? ((await events.result().catch(() => null)) as AssistantMessage | null);
|
|
1210
|
+
|
|
1211
|
+
// Build the canonical output from the final message so non-streaming
|
|
1212
|
+
// readers see the exact same shape they'd get from encodeResponse().
|
|
1213
|
+
const items = message ? buildOutputItems(message) : finishedItems;
|
|
1214
|
+
const usage = message ? buildUsage(message) : null;
|
|
1215
|
+
const status = message ? responseStatusForStopReason(message) : "completed";
|
|
1216
|
+
const terminalEvent =
|
|
1217
|
+
status === "incomplete"
|
|
1218
|
+
? "response.incomplete"
|
|
1219
|
+
: status === "failed"
|
|
1220
|
+
? "response.failed"
|
|
1221
|
+
: "response.completed";
|
|
1222
|
+
controller.enqueue(
|
|
1223
|
+
encoder.encode(
|
|
1224
|
+
sseEvent(terminalEvent, {
|
|
1225
|
+
type: terminalEvent,
|
|
1226
|
+
sequence_number: seq(),
|
|
1227
|
+
response: {
|
|
1228
|
+
id: responseId,
|
|
1229
|
+
object: "response",
|
|
1230
|
+
created_at: createdAt,
|
|
1231
|
+
status,
|
|
1232
|
+
model: requestedModelId,
|
|
1233
|
+
output: items,
|
|
1234
|
+
usage,
|
|
1235
|
+
...(status === "incomplete" ? { incomplete_details: { reason: "max_output_tokens" } } : {}),
|
|
1236
|
+
...(status === "failed"
|
|
1237
|
+
? { error: { message: message?.errorMessage ?? "response failed" } }
|
|
1238
|
+
: {}),
|
|
1239
|
+
},
|
|
1240
|
+
}),
|
|
1241
|
+
),
|
|
1242
|
+
);
|
|
1243
|
+
emitDone();
|
|
1244
|
+
controller.close();
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
controller.enqueue(
|
|
1247
|
+
encoder.encode(
|
|
1248
|
+
sseEvent("response.failed", {
|
|
1249
|
+
type: "response.failed",
|
|
1250
|
+
sequence_number: seq(),
|
|
1251
|
+
response: {
|
|
1252
|
+
id: responseId,
|
|
1253
|
+
object: "response",
|
|
1254
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
1255
|
+
status: "failed",
|
|
1256
|
+
model: requestedModelId,
|
|
1257
|
+
output: [],
|
|
1258
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
1259
|
+
},
|
|
1260
|
+
}),
|
|
1261
|
+
),
|
|
1262
|
+
);
|
|
1263
|
+
emitDone();
|
|
1264
|
+
controller.close();
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
});
|
|
1268
|
+
}
|