@gajae-code/ai 0.1.1
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 +2644 -0
- package/README.md +1181 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth-broker/client.d.ts +66 -0
- package/dist/types/auth-broker/index.d.ts +5 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +96 -0
- package/dist/types/auth-broker/server.d.ts +32 -0
- package/dist/types/auth-broker/types.d.ts +105 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
- package/dist/types/auth-gateway/http.d.ts +39 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +17 -0
- package/dist/types/auth-gateway/types.d.ts +115 -0
- package/dist/types/auth-storage.d.ts +641 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/index.d.ts +49 -0
- package/dist/types/model-cache.d.ts +17 -0
- package/dist/types/model-manager.d.ts +62 -0
- package/dist/types/model-thinking.d.ts +71 -0
- package/dist/types/models.d.ts +12 -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 +237 -0
- package/dist/types/provider-models/special.d.ts +16 -0
- package/dist/types/providers/amazon-bedrock.d.ts +36 -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 +188 -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 +15 -0
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
- package/dist/types/providers/cursor.d.ts +42 -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 +24 -0
- package/dist/types/providers/google-gemini-cli.d.ts +72 -0
- package/dist/types/providers/google-gemini-headers.d.ts +18 -0
- package/dist/types/providers/google-shared.d.ts +163 -0
- package/dist/types/providers/google-types.d.ts +138 -0
- package/dist/types/providers/google-vertex.d.ts +7 -0
- package/dist/types/providers/google.d.ts +4 -0
- package/dist/types/providers/grammar.d.ts +1 -0
- package/dist/types/providers/kimi.d.ts +27 -0
- package/dist/types/providers/mock.d.ts +175 -0
- package/dist/types/providers/ollama.d.ts +6 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +814 -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 +49 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
- package/dist/types/providers/openai-codex-responses.d.ts +67 -0
- package/dist/types/providers/openai-completions-compat.d.ts +25 -0
- package/dist/types/providers/openai-completions.d.ts +33 -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 +89 -0
- package/dist/types/providers/openai-responses.d.ts +32 -0
- package/dist/types/providers/pi-native-client.d.ts +13 -0
- package/dist/types/providers/pi-native-server.d.ts +68 -0
- package/dist/types/providers/register-builtins.d.ts +31 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +12 -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 +24 -0
- package/dist/types/types.d.ts +746 -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/kimi.d.ts +2 -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 +258 -0
- package/dist/types/utils/abort.d.ts +19 -0
- package/dist/types/utils/anthropic-auth.d.ts +31 -0
- package/dist/types/utils/discovery/antigravity.d.ts +61 -0
- package/dist/types/utils/discovery/codex.d.ts +38 -0
- package/dist/types/utils/discovery/cursor.d.ts +23 -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 +72 -0
- package/dist/types/utils/event-stream.d.ts +28 -0
- package/dist/types/utils/fireworks-model-id.d.ts +10 -0
- package/dist/types/utils/foundry.d.ts +1 -0
- package/dist/types/utils/h2-fetch.d.ts +22 -0
- package/dist/types/utils/http-inspector.d.ts +31 -0
- package/dist/types/utils/idle-iterator.d.ts +67 -0
- package/dist/types/utils/json-parse.d.ts +10 -0
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
- package/dist/types/utils/oauth/anthropic.d.ts +22 -0
- package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
- package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
- package/dist/types/utils/oauth/callback-server.d.ts +57 -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/cursor.d.ts +15 -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/github-copilot.d.ts +38 -0
- package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -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 +38 -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 +21 -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/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/parallel.d.ts +17 -0
- package/dist/types/utils/oauth/perplexity.d.ts +9 -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/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 +44 -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/xiaomi.d.ts +19 -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 +54 -0
- package/dist/types/utils/parse-bind.d.ts +23 -0
- package/dist/types/utils/provider-response.d.ts +3 -0
- package/dist/types/utils/retry-after.d.ts +3 -0
- package/dist/types/utils/retry.d.ts +26 -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 +13 -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/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 +71 -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 +28 -0
- package/package.json +146 -0
- 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 +127 -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 +717 -0
- package/src/auth-gateway/types.ts +134 -0
- package/src/auth-storage.ts +4104 -0
- package/src/cli.ts +262 -0
- package/src/index.ts +54 -0
- package/src/model-cache.ts +129 -0
- package/src/model-manager.ts +450 -0
- package/src/model-thinking.ts +691 -0
- package/src/models.json +73853 -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 +308 -0
- package/src/provider-models/google.ts +91 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +153 -0
- package/src/provider-models/openai-compat.ts +2275 -0
- package/src/provider-models/special.ts +67 -0
- package/src/providers/amazon-bedrock.ts +849 -0
- package/src/providers/anthropic-messages-server-schema.ts +229 -0
- package/src/providers/anthropic-messages-server.ts +677 -0
- package/src/providers/anthropic.ts +2696 -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 +337 -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 +2561 -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 +795 -0
- package/src/providers/google-gemini-headers.ts +41 -0
- package/src/providers/google-shared.ts +902 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.ts +88 -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 +500 -0
- package/src/providers/ollama.ts +544 -0
- package/src/providers/openai-anthropic-shim.ts +138 -0
- package/src/providers/openai-chat-server-schema.ts +243 -0
- package/src/providers/openai-chat-server.ts +628 -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 +2598 -0
- package/src/providers/openai-completions-compat.ts +279 -0
- package/src/providers/openai-completions.ts +1853 -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 +800 -0
- package/src/providers/openai-responses.ts +621 -0
- package/src/providers/pi-native-client.ts +228 -0
- package/src/providers/pi-native-server.ts +210 -0
- package/src/providers/register-builtins.ts +412 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +309 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +895 -0
- package/src/types.ts +884 -0
- package/src/usage/claude.ts +431 -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 +183 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/anthropic-auth.ts +87 -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/h2-fetch.ts +60 -0
- package/src/utils/http-inspector.ts +176 -0
- package/src/utils/idle-iterator.ts +250 -0
- package/src/utils/json-parse.ts +148 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +200 -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 +444 -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 +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/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 +16 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +16 -0
- package/src/utils/oauth/types.ts +94 -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/xiaomi.ts +137 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -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/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 +190 -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 +11 -0
- package/src/utils/schema/wire.ts +213 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/tool-call-healing.ts +271 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1019 -0
- package/src/utils.ts +166 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
import { structuredCloneJSON } from "@gajae-code/utils";
|
|
2
|
+
import type OpenAI from "openai";
|
|
3
|
+
import type {
|
|
4
|
+
ResponseCustomToolCall,
|
|
5
|
+
ResponseFunctionToolCall,
|
|
6
|
+
ResponseInput,
|
|
7
|
+
ResponseInputContent,
|
|
8
|
+
ResponseInputImage,
|
|
9
|
+
ResponseInputText,
|
|
10
|
+
ResponseOutputItem,
|
|
11
|
+
ResponseOutputMessage,
|
|
12
|
+
ResponseReasoningItem,
|
|
13
|
+
} from "openai/resources/responses/responses";
|
|
14
|
+
import { calculateCost } from "../models";
|
|
15
|
+
import {
|
|
16
|
+
type Api,
|
|
17
|
+
type AssistantMessage,
|
|
18
|
+
type ImageContent,
|
|
19
|
+
type Model,
|
|
20
|
+
resolveServiceTier,
|
|
21
|
+
type ServiceTier,
|
|
22
|
+
type StopReason,
|
|
23
|
+
type StreamOptions,
|
|
24
|
+
shouldSendServiceTier,
|
|
25
|
+
type TextContent,
|
|
26
|
+
type TextSignatureV1,
|
|
27
|
+
type ThinkingContent,
|
|
28
|
+
type ToolCall,
|
|
29
|
+
type ToolResultMessage,
|
|
30
|
+
} from "../types";
|
|
31
|
+
import { normalizeResponsesToolCallId } from "../utils";
|
|
32
|
+
import type { AssistantMessageEventStream } from "../utils/event-stream";
|
|
33
|
+
import { parseStreamingJson } from "../utils/json-parse";
|
|
34
|
+
import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
|
|
35
|
+
|
|
36
|
+
export function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase"]): string {
|
|
37
|
+
const payload: TextSignatureV1 = { v: 1, id };
|
|
38
|
+
if (phase) payload.phase = phase;
|
|
39
|
+
return JSON.stringify(payload);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function parseTextSignature(
|
|
43
|
+
signature: string | undefined,
|
|
44
|
+
): { id: string; phase?: TextSignatureV1["phase"] } | undefined {
|
|
45
|
+
if (!signature) return undefined;
|
|
46
|
+
if (signature.startsWith("{")) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(signature) as Partial<TextSignatureV1>;
|
|
49
|
+
if (parsed.v === 1 && typeof parsed.id === "string") {
|
|
50
|
+
if (parsed.phase === "commentary" || parsed.phase === "final_answer") {
|
|
51
|
+
return { id: parsed.id, phase: parsed.phase };
|
|
52
|
+
}
|
|
53
|
+
return { id: parsed.id };
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Fall through to legacy plain-string handling.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { id: signature };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function encodeResponsesToolCallId(callId: string, itemId: string | null | undefined): string {
|
|
63
|
+
const stableItemId = itemId && itemId.length > 0 ? itemId : `fc_${Bun.hash(callId).toString(36)}`;
|
|
64
|
+
return `${callId}|${stableItemId}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function normalizeResponsesToolCallIdForTransform(
|
|
68
|
+
id: string,
|
|
69
|
+
model?: Model<Api>,
|
|
70
|
+
source?: AssistantMessage,
|
|
71
|
+
): string {
|
|
72
|
+
if (!id.includes("|")) return id;
|
|
73
|
+
const isForeignToolCall =
|
|
74
|
+
source != null && model != null && (source.provider !== model.provider || source.api !== model.api);
|
|
75
|
+
if (isForeignToolCall) {
|
|
76
|
+
const [callId, itemId] = id.split("|");
|
|
77
|
+
const normalizeIdPart = (part: string): string => {
|
|
78
|
+
const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
79
|
+
const truncated = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
|
|
80
|
+
return truncated.replace(/_+$/, "");
|
|
81
|
+
};
|
|
82
|
+
const normalizedCallId = normalizeIdPart(callId);
|
|
83
|
+
let normalizedItemId = `fc_${Bun.hash(itemId).toString(36)}`;
|
|
84
|
+
if (normalizedItemId.length > 64) normalizedItemId = normalizedItemId.slice(0, 64);
|
|
85
|
+
return `${normalizedCallId}|${normalizedItemId}`;
|
|
86
|
+
}
|
|
87
|
+
const normalized = normalizeResponsesToolCallId(id);
|
|
88
|
+
return `${normalized.callId}|${normalized.itemId}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function collectKnownCallIds(messages: ResponseInput): Set<string> {
|
|
92
|
+
const knownCallIds = new Set<string>();
|
|
93
|
+
for (const item of messages) {
|
|
94
|
+
if (item.type === "function_call" && typeof item.call_id === "string") {
|
|
95
|
+
knownCallIds.add(item.call_id);
|
|
96
|
+
} else if (
|
|
97
|
+
(item as { type?: string }).type === "custom_tool_call" &&
|
|
98
|
+
typeof (item as { call_id?: string }).call_id === "string"
|
|
99
|
+
) {
|
|
100
|
+
knownCallIds.add((item as { call_id: string }).call_id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return knownCallIds;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Scan replay items for call_ids that were originally custom tool calls. */
|
|
107
|
+
export function collectCustomCallIds(messages: ResponseInput): Set<string> {
|
|
108
|
+
const customCallIds = new Set<string>();
|
|
109
|
+
for (const item of messages) {
|
|
110
|
+
if (
|
|
111
|
+
(item as { type?: string }).type === "custom_tool_call" &&
|
|
112
|
+
typeof (item as { call_id?: string }).call_id === "string"
|
|
113
|
+
) {
|
|
114
|
+
customCallIds.add((item as { call_id: string }).call_id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return customCallIds;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert orphan `function_call_output` / `custom_tool_call_output` items —
|
|
122
|
+
* those whose `call_id` has no matching preceding `function_call` /
|
|
123
|
+
* `custom_tool_call` in the same input — into assistant text notes.
|
|
124
|
+
*
|
|
125
|
+
* The Responses API rejects unpaired outputs with
|
|
126
|
+
* `400 No tool call found for function call output with call_id …`. Orphans
|
|
127
|
+
* sneak in through two paths today:
|
|
128
|
+
*
|
|
129
|
+
* - A previous turn's `providerPayload` snapshot replaces the input array via
|
|
130
|
+
* the `dt: false` splice (see {@link convertConversationMessages}), wiping
|
|
131
|
+
* the matching `function_call` while leaving the matching
|
|
132
|
+
* `function_call_output` queued in a later `toolResult`.
|
|
133
|
+
* - A locally-rejected tool call (argument-validation failure, hook reject,
|
|
134
|
+
* aborted turn before the call streamed) produces a tool result without a
|
|
135
|
+
* `function_call` ever landing in any persisted provider payload.
|
|
136
|
+
*
|
|
137
|
+
* Dropping the result loses information the model needs to recover; sending
|
|
138
|
+
* it as-is 400s the request. Folding it into an assistant `message` preserves
|
|
139
|
+
* the payload (call_id + truncated output) while staying within the Responses
|
|
140
|
+
* input grammar. Matches the behavior of {@link transformRequestBody} in the
|
|
141
|
+
* OpenAI code backend provider — issue #1351 / regression of #472.
|
|
142
|
+
*/
|
|
143
|
+
export function repairOrphanResponsesToolOutputs(input: ResponseInput): ResponseInput {
|
|
144
|
+
const knownCallIds = new Set<string>();
|
|
145
|
+
for (const item of input) {
|
|
146
|
+
const t = (item as { type?: string }).type;
|
|
147
|
+
const callId = (item as { call_id?: unknown }).call_id;
|
|
148
|
+
if (typeof callId !== "string") continue;
|
|
149
|
+
if (t === "function_call" || t === "custom_tool_call") knownCallIds.add(callId);
|
|
150
|
+
}
|
|
151
|
+
let hasOrphan = false;
|
|
152
|
+
for (const item of input) {
|
|
153
|
+
const t = (item as { type?: string }).type;
|
|
154
|
+
if (t !== "function_call_output" && t !== "custom_tool_call_output") continue;
|
|
155
|
+
const callId = (item as { call_id?: unknown }).call_id;
|
|
156
|
+
if (typeof callId === "string" && !knownCallIds.has(callId)) {
|
|
157
|
+
hasOrphan = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!hasOrphan) return input;
|
|
162
|
+
return input.map(item => {
|
|
163
|
+
const t = (item as { type?: string }).type;
|
|
164
|
+
if (t !== "function_call_output" && t !== "custom_tool_call_output") return item;
|
|
165
|
+
const record = item as { call_id?: unknown; output?: unknown; name?: unknown };
|
|
166
|
+
const callId = record.call_id;
|
|
167
|
+
if (typeof callId !== "string" || knownCallIds.has(callId)) return item;
|
|
168
|
+
const toolName = typeof record.name === "string" && record.name.length > 0 ? record.name : "tool";
|
|
169
|
+
const rawOutput = record.output;
|
|
170
|
+
let text: string;
|
|
171
|
+
if (typeof rawOutput === "string") text = rawOutput;
|
|
172
|
+
else if (rawOutput == null) text = "";
|
|
173
|
+
else {
|
|
174
|
+
try {
|
|
175
|
+
text = JSON.stringify(rawOutput);
|
|
176
|
+
} catch {
|
|
177
|
+
text = String(rawOutput);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const ORPHAN_OUTPUT_LIMIT = 16_000;
|
|
181
|
+
if (text.length > ORPHAN_OUTPUT_LIMIT) text = `${text.slice(0, ORPHAN_OUTPUT_LIMIT)}\n...[truncated]`;
|
|
182
|
+
return {
|
|
183
|
+
type: "message",
|
|
184
|
+
role: "assistant",
|
|
185
|
+
content: `[Orphan ${toolName} result; call_id=${callId}]: ${text}`,
|
|
186
|
+
} as ResponseInput[number];
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function convertResponsesInputContent(
|
|
191
|
+
content: string | Array<TextContent | ImageContent>,
|
|
192
|
+
supportsImages: boolean,
|
|
193
|
+
): ResponseInputContent[] | undefined {
|
|
194
|
+
if (typeof content === "string") {
|
|
195
|
+
if (content.trim().length === 0) return undefined;
|
|
196
|
+
return [{ type: "input_text", text: content.toWellFormed() } satisfies ResponseInputText];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { textBlocks, imageBlocks, omittedImages } = partitionVisionContent(content, supportsImages);
|
|
200
|
+
const normalizedContent: ResponseInputContent[] = [];
|
|
201
|
+
for (const item of textBlocks) {
|
|
202
|
+
const text = item.text.toWellFormed();
|
|
203
|
+
if (text.trim().length === 0) continue;
|
|
204
|
+
normalizedContent.push({
|
|
205
|
+
type: "input_text",
|
|
206
|
+
text,
|
|
207
|
+
} satisfies ResponseInputText);
|
|
208
|
+
}
|
|
209
|
+
for (const item of imageBlocks) {
|
|
210
|
+
normalizedContent.push({
|
|
211
|
+
type: "input_image",
|
|
212
|
+
detail: "auto",
|
|
213
|
+
image_url: `data:${item.mimeType};base64,${item.data}`,
|
|
214
|
+
} satisfies ResponseInputImage);
|
|
215
|
+
}
|
|
216
|
+
if (omittedImages) {
|
|
217
|
+
normalizedContent.push({
|
|
218
|
+
type: "input_text",
|
|
219
|
+
text: NON_VISION_IMAGE_PLACEHOLDER,
|
|
220
|
+
} satisfies ResponseInputText);
|
|
221
|
+
}
|
|
222
|
+
return normalizedContent.length > 0 ? normalizedContent : undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function convertResponsesAssistantMessage<TApi extends Api>(
|
|
226
|
+
assistantMsg: AssistantMessage,
|
|
227
|
+
model: Model<TApi>,
|
|
228
|
+
msgIndex: number,
|
|
229
|
+
knownCallIds: Set<string>,
|
|
230
|
+
includeThinkingSignatures = true,
|
|
231
|
+
customCallIds?: Set<string>,
|
|
232
|
+
): ResponseInput {
|
|
233
|
+
const outputItems: ResponseInput = [];
|
|
234
|
+
const isDifferentModel =
|
|
235
|
+
assistantMsg.model !== model.id && assistantMsg.provider === model.provider && assistantMsg.api === model.api;
|
|
236
|
+
|
|
237
|
+
for (const block of assistantMsg.content) {
|
|
238
|
+
if (block.type === "thinking" && assistantMsg.stopReason !== "error") {
|
|
239
|
+
if (!includeThinkingSignatures) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (block.thinkingSignature) {
|
|
243
|
+
outputItems.push(JSON.parse(block.thinkingSignature) as ResponseReasoningItem);
|
|
244
|
+
}
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (block.type === "text") {
|
|
249
|
+
const parsedSignature = parseTextSignature(block.textSignature);
|
|
250
|
+
let msgId = parsedSignature?.id;
|
|
251
|
+
if (!msgId) {
|
|
252
|
+
msgId = `msg_${msgIndex}`;
|
|
253
|
+
} else if (msgId.length > 64) {
|
|
254
|
+
msgId = `msg_${Bun.hash(msgId).toString(36)}`;
|
|
255
|
+
}
|
|
256
|
+
outputItems.push({
|
|
257
|
+
type: "message",
|
|
258
|
+
role: "assistant",
|
|
259
|
+
content: [{ type: "output_text", text: block.text.toWellFormed(), annotations: [] }],
|
|
260
|
+
status: "completed",
|
|
261
|
+
id: msgId,
|
|
262
|
+
phase: parsedSignature?.phase,
|
|
263
|
+
} satisfies ResponseOutputMessage);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (block.type !== "toolCall") {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const normalized = normalizeResponsesToolCallId(block.id, block.customWireName ? "ctc" : "fc");
|
|
272
|
+
let itemId: string | undefined = normalized.itemId;
|
|
273
|
+
if (isDifferentModel && (itemId?.startsWith("fc_") || itemId?.startsWith("fcr_") || itemId?.startsWith("ctc_"))) {
|
|
274
|
+
itemId = undefined;
|
|
275
|
+
}
|
|
276
|
+
knownCallIds.add(normalized.callId);
|
|
277
|
+
if (block.customWireName) {
|
|
278
|
+
const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input : "";
|
|
279
|
+
customCallIds?.add(normalized.callId);
|
|
280
|
+
outputItems.push({
|
|
281
|
+
type: "custom_tool_call",
|
|
282
|
+
id: itemId,
|
|
283
|
+
call_id: normalized.callId,
|
|
284
|
+
name: block.customWireName,
|
|
285
|
+
input: rawInput,
|
|
286
|
+
} as ResponseInput[number]);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
outputItems.push({
|
|
290
|
+
type: "function_call",
|
|
291
|
+
id: itemId,
|
|
292
|
+
call_id: normalized.callId,
|
|
293
|
+
name: block.name,
|
|
294
|
+
arguments: JSON.stringify(block.arguments),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return outputItems;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function appendResponsesToolResultMessages<TApi extends Api>(
|
|
302
|
+
messages: ResponseInput,
|
|
303
|
+
toolResult: ToolResultMessage,
|
|
304
|
+
model: Model<TApi>,
|
|
305
|
+
strictResponsesPairing: boolean,
|
|
306
|
+
knownCallIds: ReadonlySet<string>,
|
|
307
|
+
customCallIds?: ReadonlySet<string>,
|
|
308
|
+
): void {
|
|
309
|
+
const supportsImages = model.input.includes("image");
|
|
310
|
+
const textResult = toolResult.content
|
|
311
|
+
.filter((block): block is TextContent => block.type === "text")
|
|
312
|
+
.map(block => block.text)
|
|
313
|
+
.join("\n");
|
|
314
|
+
const hasImages = toolResult.content.some((block): block is ImageContent => block.type === "image");
|
|
315
|
+
const omittedImages = hasImages && !supportsImages;
|
|
316
|
+
const normalized = normalizeResponsesToolCallId(toolResult.toolCallId);
|
|
317
|
+
if (strictResponsesPairing && !knownCallIds.has(normalized.callId)) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const output = (
|
|
322
|
+
omittedImages
|
|
323
|
+
? joinTextWithImagePlaceholder(textResult, true)
|
|
324
|
+
: textResult.length > 0
|
|
325
|
+
? textResult
|
|
326
|
+
: "(see attached image)"
|
|
327
|
+
).toWellFormed();
|
|
328
|
+
if (customCallIds?.has(normalized.callId)) {
|
|
329
|
+
messages.push({
|
|
330
|
+
type: "custom_tool_call_output",
|
|
331
|
+
call_id: normalized.callId,
|
|
332
|
+
output,
|
|
333
|
+
} as ResponseInput[number]);
|
|
334
|
+
} else {
|
|
335
|
+
messages.push({
|
|
336
|
+
type: "function_call_output",
|
|
337
|
+
call_id: normalized.callId,
|
|
338
|
+
output,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!hasImages || !supportsImages) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const contentParts: ResponseInputContent[] = [
|
|
347
|
+
{ type: "input_text", text: "Attached image(s) from tool result:" } satisfies ResponseInputText,
|
|
348
|
+
];
|
|
349
|
+
for (const block of toolResult.content) {
|
|
350
|
+
if (block.type === "image") {
|
|
351
|
+
contentParts.push({
|
|
352
|
+
type: "input_image",
|
|
353
|
+
detail: "auto",
|
|
354
|
+
image_url: `data:${block.mimeType};base64,${block.data}`,
|
|
355
|
+
} satisfies ResponseInputImage);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
messages.push({ role: "user", content: contentParts });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface ProcessResponsesStreamOptions {
|
|
362
|
+
onFirstToken?: () => void;
|
|
363
|
+
onOutputItemDone?: (item: ResponseOutputItem) => void;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export async function processResponsesStream<TApi extends Api>(
|
|
367
|
+
openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>,
|
|
368
|
+
output: AssistantMessage,
|
|
369
|
+
stream: AssistantMessageEventStream,
|
|
370
|
+
model: Model<TApi>,
|
|
371
|
+
options?: ProcessResponsesStreamOptions,
|
|
372
|
+
): Promise<void> {
|
|
373
|
+
let currentItem:
|
|
374
|
+
| ResponseReasoningItem
|
|
375
|
+
| ResponseOutputMessage
|
|
376
|
+
| ResponseFunctionToolCall
|
|
377
|
+
| ResponseCustomToolCall
|
|
378
|
+
| null = null;
|
|
379
|
+
let currentBlock: ThinkingContent | TextContent | (ToolCall & { partialJson: string }) | null = null;
|
|
380
|
+
const blocks = output.content;
|
|
381
|
+
const blockIndex = () => blocks.length - 1;
|
|
382
|
+
let sawFirstToken = false;
|
|
383
|
+
|
|
384
|
+
for await (const event of openaiStream) {
|
|
385
|
+
if (event.type === "response.created") {
|
|
386
|
+
output.responseId = event.response.id;
|
|
387
|
+
} else if (event.type === "response.output_item.added") {
|
|
388
|
+
if (!sawFirstToken) {
|
|
389
|
+
sawFirstToken = true;
|
|
390
|
+
options?.onFirstToken?.();
|
|
391
|
+
}
|
|
392
|
+
const item = event.item;
|
|
393
|
+
if (item.type === "reasoning") {
|
|
394
|
+
currentItem = item;
|
|
395
|
+
currentBlock = { type: "thinking", thinking: "", itemId: item.id };
|
|
396
|
+
output.content.push(currentBlock);
|
|
397
|
+
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
|
|
398
|
+
} else if (item.type === "message") {
|
|
399
|
+
currentItem = item;
|
|
400
|
+
currentBlock = { type: "text", text: "" };
|
|
401
|
+
output.content.push(currentBlock);
|
|
402
|
+
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
403
|
+
} else if (item.type === "function_call") {
|
|
404
|
+
currentItem = item;
|
|
405
|
+
currentBlock = {
|
|
406
|
+
type: "toolCall",
|
|
407
|
+
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
408
|
+
name: item.name,
|
|
409
|
+
arguments: {},
|
|
410
|
+
partialJson: item.arguments || "",
|
|
411
|
+
};
|
|
412
|
+
output.content.push(currentBlock);
|
|
413
|
+
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
|
|
414
|
+
} else if (item.type === "custom_tool_call") {
|
|
415
|
+
currentItem = item;
|
|
416
|
+
currentBlock = {
|
|
417
|
+
type: "toolCall",
|
|
418
|
+
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
419
|
+
// Preserve the raw wire name (e.g. `apply_patch`). The agent-loop
|
|
420
|
+
// dispatcher matches it against both `Tool.name` and
|
|
421
|
+
// `Tool.customWireName`, so this stays wire-accurate through
|
|
422
|
+
// history replay while still routing to the right handler.
|
|
423
|
+
name: item.name,
|
|
424
|
+
arguments: { input: item.input ?? "" },
|
|
425
|
+
customWireName: item.name,
|
|
426
|
+
// Custom tools stream a raw string, but we reuse `partialJson` as the
|
|
427
|
+
// accumulation buffer so later code that inspects the field still works.
|
|
428
|
+
partialJson: item.input ?? "",
|
|
429
|
+
};
|
|
430
|
+
output.content.push(currentBlock);
|
|
431
|
+
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
|
|
432
|
+
}
|
|
433
|
+
} else if (event.type === "response.reasoning_summary_part.added") {
|
|
434
|
+
if (currentItem?.type === "reasoning") {
|
|
435
|
+
currentItem.summary = currentItem.summary || [];
|
|
436
|
+
currentItem.summary.push(event.part);
|
|
437
|
+
}
|
|
438
|
+
} else if (event.type === "response.reasoning_summary_text.delta") {
|
|
439
|
+
if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
|
|
440
|
+
currentItem.summary = currentItem.summary || [];
|
|
441
|
+
const lastPart = currentItem.summary[currentItem.summary.length - 1];
|
|
442
|
+
if (lastPart) {
|
|
443
|
+
currentBlock.thinking += event.delta;
|
|
444
|
+
lastPart.text += event.delta;
|
|
445
|
+
stream.push({
|
|
446
|
+
type: "thinking_delta",
|
|
447
|
+
contentIndex: blockIndex(),
|
|
448
|
+
delta: event.delta,
|
|
449
|
+
partial: output,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else if (event.type === "response.reasoning_summary_part.done") {
|
|
454
|
+
if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
|
|
455
|
+
currentItem.summary = currentItem.summary || [];
|
|
456
|
+
const lastPart = currentItem.summary[currentItem.summary.length - 1];
|
|
457
|
+
if (lastPart) {
|
|
458
|
+
currentBlock.thinking += "\n\n";
|
|
459
|
+
lastPart.text += "\n\n";
|
|
460
|
+
stream.push({
|
|
461
|
+
type: "thinking_delta",
|
|
462
|
+
contentIndex: blockIndex(),
|
|
463
|
+
delta: "\n\n",
|
|
464
|
+
partial: output,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} else if (event.type === "response.reasoning_text.delta") {
|
|
469
|
+
// Raw reasoning text delta from local providers that stream thinking
|
|
470
|
+
// directly rather than via the OpenAI summary tracking protocol.
|
|
471
|
+
if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
|
|
472
|
+
currentBlock.thinking += event.delta;
|
|
473
|
+
stream.push({
|
|
474
|
+
type: "thinking_delta",
|
|
475
|
+
contentIndex: blockIndex(),
|
|
476
|
+
delta: event.delta,
|
|
477
|
+
partial: output,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
} else if (event.type === "response.content_part.added") {
|
|
481
|
+
if (currentItem?.type === "message") {
|
|
482
|
+
currentItem.content = currentItem.content || [];
|
|
483
|
+
if (event.part.type === "output_text" || event.part.type === "refusal") {
|
|
484
|
+
currentItem.content.push(event.part);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} else if (event.type === "response.output_text.delta") {
|
|
488
|
+
if (currentItem?.type === "message" && currentBlock?.type === "text") {
|
|
489
|
+
const lastPart = currentItem.content?.[currentItem.content.length - 1];
|
|
490
|
+
if (lastPart?.type === "output_text") {
|
|
491
|
+
currentBlock.text += event.delta;
|
|
492
|
+
lastPart.text += event.delta;
|
|
493
|
+
stream.push({
|
|
494
|
+
type: "text_delta",
|
|
495
|
+
contentIndex: blockIndex(),
|
|
496
|
+
delta: event.delta,
|
|
497
|
+
partial: output,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} else if (event.type === "response.refusal.delta") {
|
|
502
|
+
if (currentItem?.type === "message" && currentBlock?.type === "text") {
|
|
503
|
+
const lastPart = currentItem.content?.[currentItem.content.length - 1];
|
|
504
|
+
if (lastPart?.type === "refusal") {
|
|
505
|
+
currentBlock.text += event.delta;
|
|
506
|
+
lastPart.refusal += event.delta;
|
|
507
|
+
stream.push({
|
|
508
|
+
type: "text_delta",
|
|
509
|
+
contentIndex: blockIndex(),
|
|
510
|
+
delta: event.delta,
|
|
511
|
+
partial: output,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} else if (event.type === "response.function_call_arguments.delta") {
|
|
516
|
+
if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
|
|
517
|
+
currentBlock.partialJson += event.delta;
|
|
518
|
+
currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
|
|
519
|
+
stream.push({
|
|
520
|
+
type: "toolcall_delta",
|
|
521
|
+
contentIndex: blockIndex(),
|
|
522
|
+
delta: event.delta,
|
|
523
|
+
partial: output,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
} else if (event.type === "response.function_call_arguments.done") {
|
|
527
|
+
if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
|
|
528
|
+
currentBlock.partialJson = event.arguments;
|
|
529
|
+
currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
|
|
530
|
+
}
|
|
531
|
+
} else if (event.type === "response.custom_tool_call_input.delta") {
|
|
532
|
+
if (currentItem?.type === "custom_tool_call" && currentBlock?.type === "toolCall") {
|
|
533
|
+
currentBlock.partialJson += event.delta;
|
|
534
|
+
currentBlock.arguments = { input: currentBlock.partialJson };
|
|
535
|
+
stream.push({
|
|
536
|
+
type: "toolcall_delta",
|
|
537
|
+
contentIndex: blockIndex(),
|
|
538
|
+
delta: event.delta,
|
|
539
|
+
partial: output,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
} else if (event.type === "response.custom_tool_call_input.done") {
|
|
543
|
+
if (currentItem?.type === "custom_tool_call" && currentBlock?.type === "toolCall") {
|
|
544
|
+
currentBlock.partialJson = event.input;
|
|
545
|
+
currentBlock.arguments = { input: event.input };
|
|
546
|
+
}
|
|
547
|
+
} else if (event.type === "response.output_item.done") {
|
|
548
|
+
const item = structuredCloneJSON(event.item);
|
|
549
|
+
options?.onOutputItemDone?.(item);
|
|
550
|
+
if (item.type === "reasoning") {
|
|
551
|
+
const thinking =
|
|
552
|
+
item.summary?.length > 0
|
|
553
|
+
? item.summary.map(part => part.text).join("\n\n")
|
|
554
|
+
: item.content?.[0]?.type === "reasoning_text"
|
|
555
|
+
? (item.content[0].text ?? "")
|
|
556
|
+
: "";
|
|
557
|
+
const reasoningBlock = output.content.find(
|
|
558
|
+
b => b.type === "thinking" && (b as ThinkingContent).itemId === item.id,
|
|
559
|
+
) as ThinkingContent | undefined;
|
|
560
|
+
if (reasoningBlock) {
|
|
561
|
+
reasoningBlock.thinking = thinking;
|
|
562
|
+
reasoningBlock.thinkingSignature = JSON.stringify(item);
|
|
563
|
+
const reasoningBlockIndex = output.content.indexOf(reasoningBlock);
|
|
564
|
+
stream.push({
|
|
565
|
+
type: "thinking_end",
|
|
566
|
+
contentIndex: reasoningBlockIndex,
|
|
567
|
+
content: thinking,
|
|
568
|
+
partial: output,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
if ((currentBlock as ThinkingContent | null)?.itemId === item.id) currentBlock = null;
|
|
572
|
+
} else if (item.type === "message" && currentBlock?.type === "text") {
|
|
573
|
+
currentBlock.text = item.content
|
|
574
|
+
.map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? "")))
|
|
575
|
+
.join("");
|
|
576
|
+
currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
|
|
577
|
+
stream.push({
|
|
578
|
+
type: "text_end",
|
|
579
|
+
contentIndex: blockIndex(),
|
|
580
|
+
content: currentBlock.text,
|
|
581
|
+
partial: output,
|
|
582
|
+
});
|
|
583
|
+
currentBlock = null;
|
|
584
|
+
} else if (item.type === "function_call") {
|
|
585
|
+
const args =
|
|
586
|
+
currentBlock?.type === "toolCall" && currentBlock.partialJson
|
|
587
|
+
? parseStreamingJson(currentBlock.partialJson)
|
|
588
|
+
: parseStreamingJson(item.arguments || "{}");
|
|
589
|
+
const toolCall: ToolCall = {
|
|
590
|
+
type: "toolCall",
|
|
591
|
+
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
592
|
+
name: item.name,
|
|
593
|
+
arguments: args,
|
|
594
|
+
};
|
|
595
|
+
currentBlock = null;
|
|
596
|
+
stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
|
|
597
|
+
} else if (item.type === "custom_tool_call") {
|
|
598
|
+
const rawInput =
|
|
599
|
+
currentBlock?.type === "toolCall" && currentBlock.partialJson
|
|
600
|
+
? currentBlock.partialJson
|
|
601
|
+
: (item.input ?? "");
|
|
602
|
+
const toolCall: ToolCall = {
|
|
603
|
+
type: "toolCall",
|
|
604
|
+
id: encodeResponsesToolCallId(item.call_id, item.id),
|
|
605
|
+
name: item.name,
|
|
606
|
+
arguments: { input: rawInput },
|
|
607
|
+
customWireName: item.name,
|
|
608
|
+
};
|
|
609
|
+
currentBlock = null;
|
|
610
|
+
stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
|
|
611
|
+
}
|
|
612
|
+
} else if (event.type === "response.completed") {
|
|
613
|
+
const response = event.response;
|
|
614
|
+
if (response?.id) {
|
|
615
|
+
output.responseId = response.id;
|
|
616
|
+
}
|
|
617
|
+
populateResponsesUsageFromResponse(output, response?.usage);
|
|
618
|
+
calculateCost(model, output.usage);
|
|
619
|
+
output.stopReason = mapOpenAIResponsesStopReason(response?.status);
|
|
620
|
+
if (response?.status === "failed" || response?.status === "cancelled") {
|
|
621
|
+
const error = response?.error ?? (response as any)?.status_details?.error;
|
|
622
|
+
const details = response?.incomplete_details;
|
|
623
|
+
const statusDetailsReason = (response as any)?.status_details?.reason;
|
|
624
|
+
const message = error
|
|
625
|
+
? `${error.code || "unknown"}: ${error.message || "no message"}`
|
|
626
|
+
: details?.reason
|
|
627
|
+
? `incomplete: ${details.reason}`
|
|
628
|
+
: typeof statusDetailsReason === "string" && statusDetailsReason.length > 0
|
|
629
|
+
? `status_details: ${statusDetailsReason}`
|
|
630
|
+
: "Unknown error (no error details in response)";
|
|
631
|
+
throw new Error(message);
|
|
632
|
+
}
|
|
633
|
+
if (output.content.some(block => block.type === "toolCall") && output.stopReason === "stop") {
|
|
634
|
+
output.stopReason = "toolUse";
|
|
635
|
+
}
|
|
636
|
+
} else if (event.type === "error") {
|
|
637
|
+
throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
|
|
638
|
+
} else if (event.type === "response.failed") {
|
|
639
|
+
const error = event.response?.error ?? (event.response as any)?.status_details?.error;
|
|
640
|
+
const details = event.response?.incomplete_details;
|
|
641
|
+
const message = error
|
|
642
|
+
? `${error.code || "unknown"}: ${error.message || "no message"}`
|
|
643
|
+
: details?.reason
|
|
644
|
+
? `incomplete: ${details.reason}`
|
|
645
|
+
: "Unknown error (no error details in response)";
|
|
646
|
+
throw new Error(message);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export function mapOpenAIResponsesStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason {
|
|
652
|
+
if (!status) return "stop";
|
|
653
|
+
switch (status) {
|
|
654
|
+
case "completed":
|
|
655
|
+
return "stop";
|
|
656
|
+
case "incomplete":
|
|
657
|
+
return "length";
|
|
658
|
+
case "failed":
|
|
659
|
+
case "cancelled":
|
|
660
|
+
return "error";
|
|
661
|
+
case "in_progress":
|
|
662
|
+
case "queued":
|
|
663
|
+
return "stop";
|
|
664
|
+
default: {
|
|
665
|
+
const exhaustive: never = status;
|
|
666
|
+
throw new Error(`Unhandled stop reason: ${exhaustive}`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Initial empty `AssistantMessage` that streaming providers accumulate into. */
|
|
672
|
+
export function createInitialResponsesAssistantMessage(api: Api, provider: string, modelId: string): AssistantMessage {
|
|
673
|
+
return {
|
|
674
|
+
role: "assistant",
|
|
675
|
+
content: [],
|
|
676
|
+
api,
|
|
677
|
+
provider,
|
|
678
|
+
model: modelId,
|
|
679
|
+
usage: {
|
|
680
|
+
input: 0,
|
|
681
|
+
output: 0,
|
|
682
|
+
cacheRead: 0,
|
|
683
|
+
cacheWrite: 0,
|
|
684
|
+
totalTokens: 0,
|
|
685
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
686
|
+
},
|
|
687
|
+
stopReason: "stop",
|
|
688
|
+
timestamp: Date.now(),
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** Extension fields we add on top of `ResponseCreateParamsStreaming` across the Responses-family providers. */
|
|
693
|
+
export type ResponsesSamplingParamsExtras = {
|
|
694
|
+
top_p?: number;
|
|
695
|
+
top_k?: number;
|
|
696
|
+
min_p?: number;
|
|
697
|
+
presence_penalty?: number;
|
|
698
|
+
repetition_penalty?: number;
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
type CommonResponsesParams = OpenAI.Responses.ResponseCreateParamsStreaming & ResponsesSamplingParamsExtras;
|
|
702
|
+
|
|
703
|
+
type CommonSamplingOptions = Pick<
|
|
704
|
+
StreamOptions,
|
|
705
|
+
"temperature" | "topP" | "topK" | "minP" | "presencePenalty" | "repetitionPenalty" | "maxTokens"
|
|
706
|
+
> & { serviceTier?: ServiceTier };
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Apply the common `StreamOptions` → Responses sampling-parameter mapping (max output tokens,
|
|
710
|
+
* temperature, top-p/k, min-p, presence/repetition penalties, service tier). Mutates `params`.
|
|
711
|
+
*/
|
|
712
|
+
export function applyCommonResponsesSamplingParams<P extends CommonResponsesParams>(
|
|
713
|
+
params: P,
|
|
714
|
+
options: CommonSamplingOptions | undefined,
|
|
715
|
+
provider: string,
|
|
716
|
+
): void {
|
|
717
|
+
if (options?.maxTokens) params.max_output_tokens = options.maxTokens;
|
|
718
|
+
if (options?.temperature !== undefined) params.temperature = options.temperature;
|
|
719
|
+
if (options?.topP !== undefined) params.top_p = options.topP;
|
|
720
|
+
if (options?.topK !== undefined) params.top_k = options.topK;
|
|
721
|
+
if (options?.minP !== undefined) params.min_p = options.minP;
|
|
722
|
+
if (options?.presencePenalty !== undefined) params.presence_penalty = options.presencePenalty;
|
|
723
|
+
if (options?.repetitionPenalty !== undefined) params.repetition_penalty = options.repetitionPenalty;
|
|
724
|
+
if (shouldSendServiceTier(options?.serviceTier, provider)) {
|
|
725
|
+
const resolved = resolveServiceTier(options?.serviceTier, provider);
|
|
726
|
+
if (resolved === "flex" || resolved === "scale" || resolved === "priority") {
|
|
727
|
+
params.service_tier = resolved;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
type ReasoningOptions = {
|
|
733
|
+
reasoning?: string;
|
|
734
|
+
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Apply reasoning-related Responses parameters: enable encrypted reasoning content for replay,
|
|
739
|
+
* set effort/summary when requested, and otherwise inject the GPT-5 "Juice: 0" no-reasoning hack.
|
|
740
|
+
* Mutates `params` and may push a developer message into `messages`.
|
|
741
|
+
*/
|
|
742
|
+
export function applyResponsesReasoningParams<P extends OpenAI.Responses.ResponseCreateParamsStreaming>(
|
|
743
|
+
params: P,
|
|
744
|
+
model: Model<Api>,
|
|
745
|
+
options: ReasoningOptions | undefined,
|
|
746
|
+
messages: ResponseInput,
|
|
747
|
+
mapEffort?: (effort: string) => string,
|
|
748
|
+
): void {
|
|
749
|
+
if (!model.reasoning) return;
|
|
750
|
+
// Always request encrypted reasoning content so reasoning items can be replayed in
|
|
751
|
+
// multi-turn conversations when store is false (items aren't persisted server-side, so
|
|
752
|
+
// we must include the full content). See: https://github.com/can1357/gajae-code/issues/41
|
|
753
|
+
params.include = ["reasoning.encrypted_content"];
|
|
754
|
+
|
|
755
|
+
if (options?.reasoning || options?.reasoningSummary !== undefined) {
|
|
756
|
+
const requested = options?.reasoning || "medium";
|
|
757
|
+
type ReasoningParam = NonNullable<OpenAI.Responses.ResponseCreateParamsStreaming["reasoning"]>;
|
|
758
|
+
const reasoningParams: ReasoningParam = {
|
|
759
|
+
effort: (mapEffort ? mapEffort(requested) : requested) as ReasoningParam["effort"],
|
|
760
|
+
};
|
|
761
|
+
if (options?.reasoningSummary !== null) {
|
|
762
|
+
reasoningParams.summary = options?.reasoningSummary || "auto";
|
|
763
|
+
}
|
|
764
|
+
params.reasoning = reasoningParams as P["reasoning"];
|
|
765
|
+
} else if (model.name.toLowerCase().startsWith("gpt-5")) {
|
|
766
|
+
// Jesus Christ, see https://community.openai.com/t/need-reasoning-false-option-for-gpt-5/1351588/7
|
|
767
|
+
messages.push({
|
|
768
|
+
role: "developer",
|
|
769
|
+
content: [{ type: "input_text", text: "# Juice: 0 !important" }],
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/** Populate `output.usage` from a Responses-API `response.usage` payload. Does not invoke `calculateCost`. */
|
|
775
|
+
export function populateResponsesUsageFromResponse(
|
|
776
|
+
output: AssistantMessage,
|
|
777
|
+
usage:
|
|
778
|
+
| {
|
|
779
|
+
input_tokens?: number | null;
|
|
780
|
+
output_tokens?: number | null;
|
|
781
|
+
total_tokens?: number | null;
|
|
782
|
+
input_tokens_details?: { cached_tokens?: number | null } | null;
|
|
783
|
+
output_tokens_details?: { reasoning_tokens?: number | null } | null;
|
|
784
|
+
}
|
|
785
|
+
| null
|
|
786
|
+
| undefined,
|
|
787
|
+
): void {
|
|
788
|
+
if (!usage) return;
|
|
789
|
+
const cachedTokens = usage.input_tokens_details?.cached_tokens || 0;
|
|
790
|
+
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens || 0;
|
|
791
|
+
output.usage = {
|
|
792
|
+
input: (usage.input_tokens || 0) - cachedTokens,
|
|
793
|
+
output: usage.output_tokens || 0,
|
|
794
|
+
cacheRead: cachedTokens,
|
|
795
|
+
cacheWrite: 0,
|
|
796
|
+
totalTokens: usage.total_tokens || 0,
|
|
797
|
+
...(reasoningTokens > 0 ? { reasoningTokens } : {}),
|
|
798
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
799
|
+
};
|
|
800
|
+
}
|