@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,309 @@
|
|
|
1
|
+
import turnAbortedGuidance from "../prompts/turn-aborted-guidance.md" with { type: "text" };
|
|
2
|
+
import type {
|
|
3
|
+
Api,
|
|
4
|
+
AssistantMessage,
|
|
5
|
+
DeveloperMessage,
|
|
6
|
+
Message,
|
|
7
|
+
Model,
|
|
8
|
+
ToolCall,
|
|
9
|
+
ToolResultMessage,
|
|
10
|
+
UserMessage,
|
|
11
|
+
} from "../types";
|
|
12
|
+
|
|
13
|
+
const enum ToolCallStatus {
|
|
14
|
+
/** Tool call has received a result (real or synthetic for orphan) */
|
|
15
|
+
Resolved = 1,
|
|
16
|
+
/** Tool call was from an aborted message; synthetic result injected, skip real results */
|
|
17
|
+
Aborted = 2,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Normalize tool call ID for cross-provider compatibility.
|
|
22
|
+
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
|
23
|
+
* Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
|
|
24
|
+
*
|
|
25
|
+
* For aborted/errored turns, this function:
|
|
26
|
+
* - Preserves tool call structure (unlike converting to text summaries)
|
|
27
|
+
* - Injects synthetic "aborted" tool results
|
|
28
|
+
* - Adds a <turn-aborted> guidance marker for the model
|
|
29
|
+
*/
|
|
30
|
+
export function transformMessages<TApi extends Api>(
|
|
31
|
+
messages: Message[],
|
|
32
|
+
model: Model<TApi>,
|
|
33
|
+
normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,
|
|
34
|
+
): Message[] {
|
|
35
|
+
// Build a map of original tool call IDs to normalized IDs
|
|
36
|
+
const toolCallIdMap = new Map<string, string>();
|
|
37
|
+
|
|
38
|
+
const latestAssistantIndex = messages.findLastIndex(msg => msg.role === "assistant");
|
|
39
|
+
// First pass: transform messages (thinking blocks, tool call ID normalization)
|
|
40
|
+
const transformed = messages.map((msg, index) => {
|
|
41
|
+
// User and developer messages pass through unchanged
|
|
42
|
+
if (msg.role === "user" || msg.role === "developer") {
|
|
43
|
+
return msg;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Handle toolResult messages - normalize toolCallId if we have a mapping
|
|
47
|
+
if (msg.role === "toolResult") {
|
|
48
|
+
const normalizedId = toolCallIdMap.get(msg.toolCallId);
|
|
49
|
+
if (normalizedId && normalizedId !== msg.toolCallId) {
|
|
50
|
+
return { ...msg, toolCallId: normalizedId };
|
|
51
|
+
}
|
|
52
|
+
return msg;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Assistant messages need transformation check
|
|
56
|
+
if (msg.role === "assistant") {
|
|
57
|
+
const assistantMsg = msg as AssistantMessage;
|
|
58
|
+
const isSameModel =
|
|
59
|
+
assistantMsg.provider === model.provider &&
|
|
60
|
+
assistantMsg.api === model.api &&
|
|
61
|
+
assistantMsg.model === model.id;
|
|
62
|
+
|
|
63
|
+
const mustPreserveLatestAnthropicThinking =
|
|
64
|
+
index === latestAssistantIndex &&
|
|
65
|
+
model.api === "anthropic-messages" &&
|
|
66
|
+
assistantMsg.api === "anthropic-messages";
|
|
67
|
+
// Aborted/errored messages may have partially-streamed thinking signatures.
|
|
68
|
+
// A partial signature is invalid and will be rejected by the API, so we must
|
|
69
|
+
// strip signatures from thinking blocks in these messages.
|
|
70
|
+
const hasInvalidSignatures = assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error";
|
|
71
|
+
|
|
72
|
+
const transformedContent = assistantMsg.content.flatMap(block => {
|
|
73
|
+
if (block.type === "thinking") {
|
|
74
|
+
// Strip signature from aborted/errored messages — it's likely incomplete
|
|
75
|
+
const sanitized =
|
|
76
|
+
hasInvalidSignatures && block.thinkingSignature ? { ...block, thinkingSignature: undefined } : block;
|
|
77
|
+
if (mustPreserveLatestAnthropicThinking) return sanitized;
|
|
78
|
+
// For same model: keep thinking blocks with signatures (needed for replay)
|
|
79
|
+
// even if the thinking text is empty (OpenAI encrypted reasoning)
|
|
80
|
+
if (isSameModel && sanitized.thinkingSignature) return sanitized;
|
|
81
|
+
// Skip empty thinking blocks, convert others to plain text
|
|
82
|
+
if (!sanitized.thinking || sanitized.thinking.trim() === "") return [];
|
|
83
|
+
if (isSameModel) return sanitized;
|
|
84
|
+
return {
|
|
85
|
+
type: "text" as const,
|
|
86
|
+
text: sanitized.thinking,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (block.type === "redactedThinking") {
|
|
91
|
+
if (mustPreserveLatestAnthropicThinking) return block;
|
|
92
|
+
if (isSameModel) return block;
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (block.type === "text") {
|
|
97
|
+
if (isSameModel) return block;
|
|
98
|
+
return {
|
|
99
|
+
type: "text" as const,
|
|
100
|
+
text: block.text,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (block.type === "toolCall") {
|
|
105
|
+
const toolCall = block as ToolCall;
|
|
106
|
+
let normalizedToolCall: ToolCall = toolCall;
|
|
107
|
+
|
|
108
|
+
if (!isSameModel && toolCall.thoughtSignature) {
|
|
109
|
+
normalizedToolCall = { ...toolCall };
|
|
110
|
+
delete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!isSameModel && normalizeToolCallId) {
|
|
114
|
+
const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
|
|
115
|
+
if (normalizedId !== toolCall.id) {
|
|
116
|
+
toolCallIdMap.set(toolCall.id, normalizedId);
|
|
117
|
+
normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return normalizedToolCall;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return block;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...assistantMsg,
|
|
129
|
+
content: transformedContent,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return msg;
|
|
133
|
+
});
|
|
134
|
+
const realToolResultIds = new Set(
|
|
135
|
+
transformed.filter((msg): msg is ToolResultMessage => msg.role === "toolResult").map(msg => msg.toolCallId),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Anthropic rejects `tool_result` blocks whose `tool_use_id` does not appear in a prior
|
|
139
|
+
// `tool_use` block. After handoff/compaction folds an assistant turn into a summary
|
|
140
|
+
// string, the user-side `toolResult` for that turn can survive while the originating
|
|
141
|
+
// `tool_use` disappears — leaving an orphan that triggers HTTP 400. Track the set of
|
|
142
|
+
// `tool_use` ids that survive transformation so the second pass can drop orphans cleanly.
|
|
143
|
+
const validToolUseIds = new Set<string>();
|
|
144
|
+
for (const msg of transformed) {
|
|
145
|
+
if (msg.role !== "assistant") continue;
|
|
146
|
+
for (const block of msg.content) {
|
|
147
|
+
if (block.type === "toolCall") validToolUseIds.add(block.id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Second pass: insert synthetic empty tool results for orphaned tool calls
|
|
152
|
+
// and preserve aborted/errored tool results when they were already persisted.
|
|
153
|
+
const result: Message[] = [];
|
|
154
|
+
let pendingToolCalls: ToolCall[] = [];
|
|
155
|
+
let pendingAbortedToolCalls = new Map<string, ToolCall>();
|
|
156
|
+
let pendingAbortedTimestamp: number | undefined;
|
|
157
|
+
// Track tool call status: whether resolved (has result) or aborted (synthetic result injected, skip later real results)
|
|
158
|
+
const toolCallStatus = new Map<string, ToolCallStatus>();
|
|
159
|
+
|
|
160
|
+
const flushPendingToolCalls = (timestamp: number): void => {
|
|
161
|
+
if (pendingToolCalls.length === 0) return;
|
|
162
|
+
for (const tc of pendingToolCalls) {
|
|
163
|
+
if (!toolCallStatus.has(tc.id) && !realToolResultIds.has(tc.id)) {
|
|
164
|
+
result.push({
|
|
165
|
+
role: "toolResult",
|
|
166
|
+
toolCallId: tc.id,
|
|
167
|
+
toolName: tc.name,
|
|
168
|
+
content: [{ type: "text", text: "No result provided" }],
|
|
169
|
+
isError: true,
|
|
170
|
+
timestamp,
|
|
171
|
+
} as ToolResultMessage);
|
|
172
|
+
toolCallStatus.set(tc.id, ToolCallStatus.Resolved);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
pendingToolCalls = [];
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const flushPendingAbortedToolCalls = (): void => {
|
|
179
|
+
if (pendingAbortedTimestamp === undefined) return;
|
|
180
|
+
for (const tc of pendingAbortedToolCalls.values()) {
|
|
181
|
+
if (!toolCallStatus.has(tc.id)) {
|
|
182
|
+
result.push({
|
|
183
|
+
role: "toolResult",
|
|
184
|
+
toolCallId: tc.id,
|
|
185
|
+
toolName: tc.name,
|
|
186
|
+
content: [{ type: "text", text: "aborted" }],
|
|
187
|
+
isError: true,
|
|
188
|
+
timestamp: pendingAbortedTimestamp,
|
|
189
|
+
} as ToolResultMessage);
|
|
190
|
+
toolCallStatus.set(tc.id, ToolCallStatus.Aborted);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
result.push({
|
|
194
|
+
role: "developer",
|
|
195
|
+
content: turnAbortedGuidance,
|
|
196
|
+
timestamp: pendingAbortedTimestamp + 1,
|
|
197
|
+
} as DeveloperMessage);
|
|
198
|
+
pendingAbortedToolCalls = new Map();
|
|
199
|
+
pendingAbortedTimestamp = undefined;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < transformed.length; i++) {
|
|
203
|
+
const msg = transformed[i];
|
|
204
|
+
const messageTimestamp = "timestamp" in msg && typeof msg.timestamp === "number" ? msg.timestamp : Date.now();
|
|
205
|
+
|
|
206
|
+
if (msg.role === "assistant") {
|
|
207
|
+
flushPendingToolCalls(messageTimestamp);
|
|
208
|
+
flushPendingAbortedToolCalls();
|
|
209
|
+
|
|
210
|
+
const assistantMsg = msg as AssistantMessage;
|
|
211
|
+
const toolCalls = assistantMsg.content.filter(b => b.type === "toolCall") as ToolCall[];
|
|
212
|
+
|
|
213
|
+
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
|
|
214
|
+
// Keep the assistant message with tool calls intact. If real tool results follow, preserve them;
|
|
215
|
+
// otherwise synthesize aborted results before the next turn boundary.
|
|
216
|
+
result.push(msg);
|
|
217
|
+
pendingAbortedToolCalls = new Map(toolCalls.map(toolCall => [toolCall.id, toolCall] as const));
|
|
218
|
+
pendingAbortedTimestamp = assistantMsg.timestamp;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (toolCalls.length > 0) {
|
|
223
|
+
pendingToolCalls = toolCalls;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
result.push(msg);
|
|
227
|
+
} else if (msg.role === "toolResult") {
|
|
228
|
+
if (pendingAbortedToolCalls.has(msg.toolCallId)) {
|
|
229
|
+
pendingAbortedToolCalls.delete(msg.toolCallId);
|
|
230
|
+
toolCallStatus.set(msg.toolCallId, ToolCallStatus.Resolved);
|
|
231
|
+
result.push(msg);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (toolCallStatus.get(msg.toolCallId) === ToolCallStatus.Aborted) continue;
|
|
236
|
+
|
|
237
|
+
if (!validToolUseIds.has(msg.toolCallId)) {
|
|
238
|
+
// Orphan `tool_result`: the originating `tool_use` is not present in the
|
|
239
|
+
// transformed history (typically because handoff/compaction folded the
|
|
240
|
+
// assistant message into a summary string while the user-side result
|
|
241
|
+
// survived). Sending the block as-is would 400 the request, so it must
|
|
242
|
+
// be dropped.
|
|
243
|
+
//
|
|
244
|
+
// If a pending tool-call window is still open (either normal or
|
|
245
|
+
// aborted), the orphan cannot be replaced with a developer note here:
|
|
246
|
+
//
|
|
247
|
+
// * Anthropic requires the next message after an assistant `tool_use`
|
|
248
|
+
// to be the matching `tool_result`. Inserting a developer message
|
|
249
|
+
// would break that contiguity.
|
|
250
|
+
// * `flushPendingAbortedToolCalls` synthesizes "aborted" results
|
|
251
|
+
// without checking whether a real result lands later in history
|
|
252
|
+
// (unlike `flushPendingToolCalls`, which is gated by
|
|
253
|
+
// `realToolResultIds`). Calling it here would convert a legitimate
|
|
254
|
+
// later `tool_result` into a synthetic "aborted" one via the
|
|
255
|
+
// `ToolCallStatus.Aborted` skip-guard.
|
|
256
|
+
//
|
|
257
|
+
// Drop the orphan silently in that case; the upcoming real
|
|
258
|
+
// `tool_result` will land normally on the next iteration.
|
|
259
|
+
if (pendingToolCalls.length > 0 || pendingAbortedToolCalls.size > 0) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// No pending tool-call window: safe to preserve the text payload so the
|
|
263
|
+
// model still sees what the tool returned.
|
|
264
|
+
//
|
|
265
|
+
// The note is emitted with `role: "user"` rather than `role: "developer"`
|
|
266
|
+
// because the developer role is elevated by some providers:
|
|
267
|
+
//
|
|
268
|
+
// * Ollama maps `developer` -> `system` (highest instruction priority).
|
|
269
|
+
// * OpenAI chat-completions reasoning models forward `developer` as
|
|
270
|
+
// `developer` (above-user instruction priority).
|
|
271
|
+
//
|
|
272
|
+
// Stale, model-untrusted tool output must not gain instruction priority
|
|
273
|
+
// above user/developer messages it lived alongside before compaction.
|
|
274
|
+
// `user` role is mapped to plain user content by every provider, so the
|
|
275
|
+
// content survives without ever being treated as an instruction the
|
|
276
|
+
// model should obey.
|
|
277
|
+
const textParts: string[] = [];
|
|
278
|
+
for (const part of msg.content) {
|
|
279
|
+
if (part.type === "text" && part.text.trim() !== "") textParts.push(part.text);
|
|
280
|
+
}
|
|
281
|
+
if (textParts.length > 0) {
|
|
282
|
+
const errorAttr = msg.isError ? ' is-error="true"' : "";
|
|
283
|
+
result.push({
|
|
284
|
+
role: "user",
|
|
285
|
+
content: `<stale-tool-result tool="${msg.toolName}" id="${msg.toolCallId}"${errorAttr}>\n${textParts.join("\n")}\n</stale-tool-result>`,
|
|
286
|
+
timestamp: messageTimestamp,
|
|
287
|
+
} as UserMessage);
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
toolCallStatus.set(msg.toolCallId, ToolCallStatus.Resolved);
|
|
293
|
+
result.push(msg);
|
|
294
|
+
} else if (msg.role === "user" || msg.role === "developer") {
|
|
295
|
+
flushPendingToolCalls(messageTimestamp);
|
|
296
|
+
flushPendingAbortedToolCalls();
|
|
297
|
+
result.push(msg);
|
|
298
|
+
} else {
|
|
299
|
+
flushPendingToolCalls(messageTimestamp);
|
|
300
|
+
flushPendingAbortedToolCalls();
|
|
301
|
+
result.push(msg);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
flushPendingToolCalls(Date.now());
|
|
306
|
+
flushPendingAbortedToolCalls();
|
|
307
|
+
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ImageContent, TextContent } from "../types";
|
|
2
|
+
|
|
3
|
+
export const NON_VISION_IMAGE_PLACEHOLDER = "[image omitted: model does not support vision]";
|
|
4
|
+
|
|
5
|
+
export function partitionVisionContent(
|
|
6
|
+
content: ReadonlyArray<TextContent | ImageContent>,
|
|
7
|
+
supportsImages: boolean,
|
|
8
|
+
): {
|
|
9
|
+
textBlocks: TextContent[];
|
|
10
|
+
imageBlocks: ImageContent[];
|
|
11
|
+
omittedImages: boolean;
|
|
12
|
+
} {
|
|
13
|
+
const textBlocks = content.filter((block): block is TextContent => block.type === "text");
|
|
14
|
+
const imageBlocks = content.filter((block): block is ImageContent => block.type === "image");
|
|
15
|
+
return {
|
|
16
|
+
textBlocks,
|
|
17
|
+
imageBlocks: supportsImages ? imageBlocks : [],
|
|
18
|
+
omittedImages: !supportsImages && imageBlocks.length > 0,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function joinTextWithImagePlaceholder(text: string, omittedImages: boolean): string {
|
|
23
|
+
const parts: string[] = [];
|
|
24
|
+
if (text.length > 0) {
|
|
25
|
+
parts.push(text);
|
|
26
|
+
}
|
|
27
|
+
if (omittedImages) {
|
|
28
|
+
parts.push(NON_VISION_IMAGE_PLACEHOLDER);
|
|
29
|
+
}
|
|
30
|
+
return parts.join("\n");
|
|
31
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limit reason classification and backoff calculation utilities.
|
|
3
|
+
* Ported from opencode-antigravity-auth plugin for consistency.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type RateLimitReason =
|
|
7
|
+
| "QUOTA_EXHAUSTED"
|
|
8
|
+
| "RATE_LIMIT_EXCEEDED"
|
|
9
|
+
| "MODEL_CAPACITY_EXHAUSTED"
|
|
10
|
+
| "SERVER_ERROR"
|
|
11
|
+
| "UNKNOWN";
|
|
12
|
+
|
|
13
|
+
const QUOTA_EXHAUSTED_BACKOFF_MS = 30 * 60 * 1000; // 30 min
|
|
14
|
+
const RATE_LIMIT_EXCEEDED_BACKOFF_MS = 30 * 1000; // 30s
|
|
15
|
+
const MODEL_CAPACITY_BASE_MS = 45 * 1000; // 45s base
|
|
16
|
+
const MODEL_CAPACITY_JITTER_MS = 30 * 1000; // ±15s
|
|
17
|
+
const SERVER_ERROR_BACKOFF_MS = 20 * 1000; // 20s
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Classify a rate-limit error message into a reason category.
|
|
21
|
+
* Priority order: MODEL_CAPACITY > RATE_LIMIT > QUOTA > SERVER_ERROR > UNKNOWN.
|
|
22
|
+
*
|
|
23
|
+
* "resource exhausted" maps to MODEL_CAPACITY (transient, short wait)
|
|
24
|
+
* "quota exceeded" maps to QUOTA_EXHAUSTED (long wait, switch account)
|
|
25
|
+
*/
|
|
26
|
+
export function parseRateLimitReason(errorMessage: string): RateLimitReason {
|
|
27
|
+
const lower = errorMessage.toLowerCase();
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
lower.includes("capacity") ||
|
|
31
|
+
lower.includes("overloaded") ||
|
|
32
|
+
lower.includes("529") ||
|
|
33
|
+
lower.includes("503") ||
|
|
34
|
+
lower.includes("resource exhausted")
|
|
35
|
+
) {
|
|
36
|
+
return "MODEL_CAPACITY_EXHAUSTED";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
lower.includes("per minute") ||
|
|
41
|
+
lower.includes("rate limit") ||
|
|
42
|
+
lower.includes("too many requests") ||
|
|
43
|
+
lower.includes("presque")
|
|
44
|
+
) {
|
|
45
|
+
return "RATE_LIMIT_EXCEEDED";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (lower.includes("exhausted") || lower.includes("quota") || lower.includes("usage limit")) {
|
|
49
|
+
return "QUOTA_EXHAUSTED";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (lower.includes("500") || lower.includes("internal error") || lower.includes("internal server error")) {
|
|
53
|
+
return "SERVER_ERROR";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return "UNKNOWN";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Calculate backoff delay in ms for a given rate limit reason.
|
|
61
|
+
* MODEL_CAPACITY gets jitter to prevent thundering herd.
|
|
62
|
+
*/
|
|
63
|
+
export function calculateRateLimitBackoffMs(reason: RateLimitReason): number {
|
|
64
|
+
switch (reason) {
|
|
65
|
+
case "QUOTA_EXHAUSTED":
|
|
66
|
+
return QUOTA_EXHAUSTED_BACKOFF_MS;
|
|
67
|
+
case "RATE_LIMIT_EXCEEDED":
|
|
68
|
+
return RATE_LIMIT_EXCEEDED_BACKOFF_MS;
|
|
69
|
+
case "MODEL_CAPACITY_EXHAUSTED":
|
|
70
|
+
return MODEL_CAPACITY_BASE_MS + Math.random() * MODEL_CAPACITY_JITTER_MS;
|
|
71
|
+
case "SERVER_ERROR":
|
|
72
|
+
return SERVER_ERROR_BACKOFF_MS;
|
|
73
|
+
default:
|
|
74
|
+
return QUOTA_EXHAUSTED_BACKOFF_MS; // conservative default
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Detect usage/quota limit errors in error messages (persistent, requires credential switch). */
|
|
79
|
+
const USAGE_LIMIT_PATTERN =
|
|
80
|
+
/usage.?limit|usage_limit_reached|usage_not_included|limit_reached|quota.?exceeded|resource.?exhausted/i;
|
|
81
|
+
|
|
82
|
+
export function isUsageLimitError(errorMessage: string): boolean {
|
|
83
|
+
return USAGE_LIMIT_PATTERN.test(errorMessage);
|
|
84
|
+
}
|