@sayknow-cli/ai 0.3.6 → 0.3.7

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.
Files changed (190) hide show
  1. package/dist/types/api-registry.d.ts +30 -0
  2. package/dist/types/auth-broker/client.d.ts +67 -0
  3. package/dist/types/auth-broker/index.d.ts +5 -0
  4. package/dist/types/auth-broker/refresher.d.ts +25 -0
  5. package/dist/types/auth-broker/remote-store.d.ts +98 -0
  6. package/dist/types/auth-broker/server.d.ts +32 -0
  7. package/dist/types/auth-broker/types.d.ts +110 -0
  8. package/dist/types/auth-broker/wire-schemas.d.ts +442 -0
  9. package/dist/types/auth-gateway/http.d.ts +40 -0
  10. package/dist/types/auth-gateway/index.d.ts +3 -0
  11. package/dist/types/auth-gateway/server.d.ts +17 -0
  12. package/dist/types/auth-gateway/types.d.ts +115 -0
  13. package/dist/types/auth-storage.d.ts +677 -0
  14. package/dist/types/cli.d.ts +2 -0
  15. package/dist/types/index.d.ts +51 -0
  16. package/dist/types/model-cache.d.ts +17 -0
  17. package/dist/types/model-manager.d.ts +62 -0
  18. package/dist/types/model-thinking.d.ts +74 -0
  19. package/dist/types/models.d.ts +12 -0
  20. package/dist/types/provider-details.d.ts +24 -0
  21. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  22. package/dist/types/provider-models/descriptors.d.ts +48 -0
  23. package/dist/types/provider-models/google.d.ts +20 -0
  24. package/dist/types/provider-models/index.d.ts +5 -0
  25. package/dist/types/provider-models/ollama.d.ts +7 -0
  26. package/dist/types/provider-models/openai-compat.d.ts +251 -0
  27. package/dist/types/provider-models/special.d.ts +19 -0
  28. package/dist/types/providers/amazon-bedrock.d.ts +60 -0
  29. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  30. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  31. package/dist/types/providers/anthropic.d.ts +208 -0
  32. package/dist/types/providers/aws-credentials.d.ts +43 -0
  33. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  34. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  35. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  36. package/dist/types/providers/composer-discipline.d.ts +26 -0
  37. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  38. package/dist/types/providers/cursor.d.ts +44 -0
  39. package/dist/types/providers/error-message.d.ts +27 -0
  40. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  41. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  42. package/dist/types/providers/google-auth.d.ts +24 -0
  43. package/dist/types/providers/google-gemini-cli.d.ts +75 -0
  44. package/dist/types/providers/google-gemini-headers.d.ts +40 -0
  45. package/dist/types/providers/google-shared.d.ts +173 -0
  46. package/dist/types/providers/google-types.d.ts +138 -0
  47. package/dist/types/providers/google-vertex.d.ts +7 -0
  48. package/dist/types/providers/google.d.ts +4 -0
  49. package/dist/types/providers/grammar.d.ts +1 -0
  50. package/dist/types/providers/kimi.d.ts +27 -0
  51. package/dist/types/providers/mock.d.ts +177 -0
  52. package/dist/types/providers/ollama.d.ts +41 -0
  53. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  54. package/dist/types/providers/openai-chat-server-schema.d.ts +815 -0
  55. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  56. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  57. package/dist/types/providers/openai-codex/request-transformer.d.ts +50 -0
  58. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  59. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  60. package/dist/types/providers/openai-completions-compat.d.ts +27 -0
  61. package/dist/types/providers/openai-completions.d.ts +33 -0
  62. package/dist/types/providers/openai-request-transform.d.ts +4 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +104 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +14 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +43 -0
  75. package/dist/types/types.d.ts +830 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/grok-cli.d.ts +10 -0
  81. package/dist/types/usage/kimi.d.ts +2 -0
  82. package/dist/types/usage/minimax-code.d.ts +2 -0
  83. package/dist/types/usage/openai-codex.d.ts +3 -0
  84. package/dist/types/usage/shared.d.ts +1 -0
  85. package/dist/types/usage/zai.d.ts +2 -0
  86. package/dist/types/usage.d.ts +258 -0
  87. package/dist/types/utils/abort.d.ts +19 -0
  88. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  89. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  90. package/dist/types/utils/discovery/codex.d.ts +38 -0
  91. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  92. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  93. package/dist/types/utils/discovery/index.d.ts +4 -0
  94. package/dist/types/utils/discovery/openai-compatible.d.ts +74 -0
  95. package/dist/types/utils/event-stream.d.ts +33 -0
  96. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  97. package/dist/types/utils/foundry.d.ts +1 -0
  98. package/dist/types/utils/h2-fetch.d.ts +22 -0
  99. package/dist/types/utils/http-inspector.d.ts +35 -0
  100. package/dist/types/utils/idle-iterator.d.ts +67 -0
  101. package/dist/types/utils/json-parse.d.ts +18 -0
  102. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  103. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  104. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  105. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  106. package/dist/types/utils/oauth/callback-server.d.ts +60 -0
  107. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  108. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  109. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  110. package/dist/types/utils/oauth/deepinfra.d.ts +1 -0
  111. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  112. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  113. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  114. package/dist/types/utils/oauth/fugu.d.ts +1 -0
  115. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  116. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  117. package/dist/types/utils/oauth/glm-zcode.d.ts +71 -0
  118. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  119. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  120. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  121. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  122. package/dist/types/utils/oauth/index.d.ts +39 -0
  123. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  124. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  125. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  126. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  127. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  128. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  129. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  130. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  131. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  132. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  133. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  134. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  135. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  136. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  137. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  138. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  139. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  140. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  141. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  142. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  143. package/dist/types/utils/oauth/together.d.ts +1 -0
  144. package/dist/types/utils/oauth/types.d.ts +45 -0
  145. package/dist/types/utils/oauth/venice.d.ts +18 -0
  146. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  147. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  148. package/dist/types/utils/oauth/xai.d.ts +30 -0
  149. package/dist/types/utils/oauth/xiaomi.d.ts +25 -0
  150. package/dist/types/utils/oauth/zai.d.ts +18 -0
  151. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  152. package/dist/types/utils/overflow.d.ts +64 -0
  153. package/dist/types/utils/parse-bind.d.ts +23 -0
  154. package/dist/types/utils/provider-response.d.ts +3 -0
  155. package/dist/types/utils/retry-after.d.ts +3 -0
  156. package/dist/types/utils/retry-budget.d.ts +1 -0
  157. package/dist/types/utils/retry.d.ts +26 -0
  158. package/dist/types/utils/schema/adapt.d.ts +24 -0
  159. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  160. package/dist/types/utils/schema/dereference.d.ts +11 -0
  161. package/dist/types/utils/schema/draft.d.ts +10 -0
  162. package/dist/types/utils/schema/equality.d.ts +4 -0
  163. package/dist/types/utils/schema/fields.d.ts +49 -0
  164. package/dist/types/utils/schema/index.d.ts +14 -0
  165. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  166. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  167. package/dist/types/utils/schema/normalize.d.ts +93 -0
  168. package/dist/types/utils/schema/root-combinator.d.ts +12 -0
  169. package/dist/types/utils/schema/spill.d.ts +8 -0
  170. package/dist/types/utils/schema/stamps.d.ts +25 -0
  171. package/dist/types/utils/schema/types.d.ts +4 -0
  172. package/dist/types/utils/schema/wire.d.ts +54 -0
  173. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  174. package/dist/types/utils/sse-debug.d.ts +10 -0
  175. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  176. package/dist/types/utils/tool-choice-capability.d.ts +41 -0
  177. package/dist/types/utils/tool-choice.d.ts +50 -0
  178. package/dist/types/utils/validation.d.ts +17 -0
  179. package/dist/types/utils.d.ts +35 -0
  180. package/package.json +25 -24
  181. package/src/providers/amazon-bedrock.ts +2 -2
  182. package/src/providers/anthropic-messages-server.ts +2 -1
  183. package/src/providers/anthropic.ts +107 -9
  184. package/src/providers/google-shared.ts +3 -3
  185. package/src/providers/openai-chat-server.ts +3 -2
  186. package/src/providers/openai-codex/request-transformer.ts +5 -0
  187. package/src/providers/openai-responses-server.ts +6 -3
  188. package/src/providers/openai-responses-shared.ts +3 -3
  189. package/src/rate-limit-utils.ts +9 -2
  190. package/src/utils.ts +28 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/ai",
4
- "version": "0.3.6",
4
+ "version": "0.3.7",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/jaybeyond/Sayknow_CLI",
7
7
  "author": "jaybeyond",
@@ -27,7 +27,7 @@
27
27
  "api"
28
28
  ],
29
29
  "main": "./src/index.ts",
30
- "types": "./src/index.ts",
30
+ "types": "./dist/types/index.d.ts",
31
31
  "bin": {
32
32
  "pi-ai": "./src/cli.ts"
33
33
  },
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@anthropic-ai/sdk": "^0.94.0",
45
45
  "@bufbuild/protobuf": "^2.12.0",
46
- "@sayknow-cli/utils": "0.3.6",
46
+ "@sayknow-cli/utils": "0.3.7",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -57,87 +57,88 @@
57
57
  "files": [
58
58
  "src",
59
59
  "README.md",
60
- "CHANGELOG.md"
60
+ "CHANGELOG.md",
61
+ "dist/types"
61
62
  ],
62
63
  "exports": {
63
64
  ".": {
64
- "types": "./src/index.ts",
65
+ "types": "./dist/types/index.d.ts",
65
66
  "import": "./src/index.ts"
66
67
  },
67
68
  "./*": {
68
- "types": "./src/*.ts",
69
+ "types": "./dist/types/*.d.ts",
69
70
  "import": "./src/*.ts"
70
71
  },
71
72
  "./auth-broker": {
72
- "types": "./src/auth-broker/index.ts",
73
+ "types": "./dist/types/auth-broker/index.d.ts",
73
74
  "import": "./src/auth-broker/index.ts"
74
75
  },
75
76
  "./auth-broker/*": {
76
- "types": "./src/auth-broker/*.ts",
77
+ "types": "./dist/types/auth-broker/*.d.ts",
77
78
  "import": "./src/auth-broker/*.ts"
78
79
  },
79
80
  "./auth-gateway": {
80
- "types": "./src/auth-gateway/index.ts",
81
+ "types": "./dist/types/auth-gateway/index.d.ts",
81
82
  "import": "./src/auth-gateway/index.ts"
82
83
  },
83
84
  "./auth-gateway/*": {
84
- "types": "./src/auth-gateway/*.ts",
85
+ "types": "./dist/types/auth-gateway/*.d.ts",
85
86
  "import": "./src/auth-gateway/*.ts"
86
87
  },
87
88
  "./models.json": {
88
- "types": "./src/models.json.d.ts",
89
+ "types": "./dist/types/models.json.d.d.ts",
89
90
  "import": "./src/models.json"
90
91
  },
91
92
  "./provider-models": {
92
- "types": "./src/provider-models/index.ts",
93
+ "types": "./dist/types/provider-models/index.d.ts",
93
94
  "import": "./src/provider-models/index.ts"
94
95
  },
95
96
  "./provider-models/*": {
96
- "types": "./src/provider-models/*.ts",
97
+ "types": "./dist/types/provider-models/*.d.ts",
97
98
  "import": "./src/provider-models/*.ts"
98
99
  },
99
100
  "./providers/*": {
100
- "types": "./src/providers/*.ts",
101
+ "types": "./dist/types/providers/*.d.ts",
101
102
  "import": "./src/providers/*.ts"
102
103
  },
103
104
  "./providers/cursor/gen/*": {
104
- "types": "./src/providers/cursor/gen/*.ts",
105
+ "types": "./dist/types/providers/cursor/gen/*.d.ts",
105
106
  "import": "./src/providers/cursor/gen/*.ts"
106
107
  },
107
108
  "./providers/openai-codex/*": {
108
- "types": "./src/providers/openai-codex/*.ts",
109
+ "types": "./dist/types/providers/openai-codex/*.d.ts",
109
110
  "import": "./src/providers/openai-codex/*.ts"
110
111
  },
111
112
  "./usage/*": {
112
- "types": "./src/usage/*.ts",
113
+ "types": "./dist/types/usage/*.d.ts",
113
114
  "import": "./src/usage/*.ts"
114
115
  },
115
116
  "./utils/*": {
116
- "types": "./src/utils/*.ts",
117
+ "types": "./dist/types/utils/*.d.ts",
117
118
  "import": "./src/utils/*.ts"
118
119
  },
119
120
  "./utils/discovery": {
120
- "types": "./src/utils/discovery/index.ts",
121
+ "types": "./dist/types/utils/discovery/index.d.ts",
121
122
  "import": "./src/utils/discovery/index.ts"
122
123
  },
123
124
  "./utils/discovery/*": {
124
- "types": "./src/utils/discovery/*.ts",
125
+ "types": "./dist/types/utils/discovery/*.d.ts",
125
126
  "import": "./src/utils/discovery/*.ts"
126
127
  },
127
128
  "./utils/oauth": {
128
- "types": "./src/utils/oauth/index.ts",
129
+ "types": "./dist/types/utils/oauth/index.d.ts",
129
130
  "import": "./src/utils/oauth/index.ts"
130
131
  },
131
132
  "./utils/oauth/*": {
132
- "types": "./src/utils/oauth/*.ts",
133
+ "types": "./dist/types/utils/oauth/*.d.ts",
133
134
  "import": "./src/utils/oauth/*.ts"
134
135
  },
135
136
  "./utils/schema": {
136
- "types": "./src/utils/schema/index.ts",
137
+ "types": "./dist/types/utils/schema/index.d.ts",
137
138
  "import": "./src/utils/schema/index.ts"
138
139
  },
139
140
  "./utils/schema/*": {
140
- "types": "./src/utils/schema/*.ts",
141
+ "types": "./dist/types/utils/schema/*.d.ts",
141
142
  "import": "./src/utils/schema/*.ts"
142
143
  },
143
144
  "./*.js": "./src/*.ts"
@@ -28,7 +28,7 @@ import type {
28
28
  ToolChoice,
29
29
  ToolResultMessage,
30
30
  } from "../types";
31
- import { normalizeToolCallId, resolveCacheRetention } from "../utils";
31
+ import { normalizeToolCallId, resolveCacheRetention, sanitizeJsonStrings } from "../utils";
32
32
  import { AssistantMessageEventStream } from "../utils/event-stream";
33
33
  import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
34
34
  import { parseStreamingJson } from "../utils/json-parse";
@@ -701,7 +701,7 @@ function convertMessages(
701
701
  toolUse: {
702
702
  toolUseId: normalizeToolCallId(c.id),
703
703
  name: c.name,
704
- input: c.arguments,
704
+ input: sanitizeJsonStrings(c.arguments),
705
705
  },
706
706
  });
707
707
  break;
@@ -13,6 +13,7 @@ import type {
13
13
  ToolResultMessage,
14
14
  UserMessage,
15
15
  } from "../types";
16
+ import { sanitizeJsonStrings } from "../utils";
16
17
  import {
17
18
  type AnthropicAssistantContentBlock,
18
19
  type AnthropicMessage,
@@ -433,7 +434,7 @@ function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>
433
434
  blocks.push({ type: "redacted_thinking", data: c.data });
434
435
  break;
435
436
  case "toolCall":
436
- blocks.push({ type: "tool_use", id: c.id, name: c.name, input: c.arguments ?? {} });
437
+ blocks.push({ type: "tool_use", id: c.id, name: c.name, input: sanitizeJsonStrings(c.arguments ?? {}) });
437
438
  break;
438
439
  }
439
440
  }
@@ -21,6 +21,7 @@ import {
21
21
  } from "@sayknow-cli/utils";
22
22
  import { hasOpus47ApiRestrictions, mapEffortToAnthropicAdaptiveEffort } from "../model-thinking";
23
23
  import { calculateCost } from "../models";
24
+ import { isUsageLimitError } from "../rate-limit-utils";
24
25
  import { getEnvApiKey, OUTPUT_FALLBACK_BUFFER } from "../stream";
25
26
  import type {
26
27
  Api,
@@ -52,6 +53,7 @@ import {
52
53
  normalizeSystemPrompts,
53
54
  normalizeToolCallId,
54
55
  resolveCacheRetention,
56
+ sanitizeJsonStrings,
55
57
  } from "../utils";
56
58
  import { createAbortSourceTracker } from "../utils/abort";
57
59
  import { AssistantMessageEventStream } from "../utils/event-stream";
@@ -62,6 +64,7 @@ import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse";
62
64
  import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
63
65
  import { notifyProviderResponse } from "../utils/provider-response";
64
66
  import { isCopilotTransientModelError } from "../utils/retry";
67
+ import { getRetryAfterMsFromHeaders } from "../utils/retry-after";
65
68
  import { resolveRetryBudget } from "../utils/retry-budget";
66
69
  import {
67
70
  COMBINATOR_KEYS,
@@ -724,6 +727,7 @@ export type AnthropicClientOptionsArgs = {
724
727
  onSseEvent?: AnthropicOptions["onSseEvent"];
725
728
  fetch?: FetchImpl;
726
729
  requestMaxRetries?: number;
730
+ maxRetryDelayMs?: number;
727
731
  };
728
732
 
729
733
  export type AnthropicClientOptionsResult = {
@@ -877,6 +881,101 @@ function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]):
877
881
  return merged;
878
882
  }
879
883
 
884
+ const ANTHROPIC_RETRY_DELAY_CAP_MS = 60_000;
885
+ const ANTHROPIC_RATE_LIMIT_HEADER_PREFIX = "anthropic-ratelimit-";
886
+
887
+ function getSafeAnthropicHeaderEvidence(headers: Headers): string[] {
888
+ const evidence: string[] = [];
889
+ for (const [name, value] of headers) {
890
+ const lowerName = name.toLowerCase();
891
+ if (
892
+ lowerName === "retry-after" ||
893
+ lowerName === "retry-after-ms" ||
894
+ lowerName.startsWith(ANTHROPIC_RATE_LIMIT_HEADER_PREFIX)
895
+ ) {
896
+ evidence.push(`${lowerName}=${value}`);
897
+ }
898
+ }
899
+ return evidence.sort();
900
+ }
901
+
902
+ function getStringProperty(source: unknown, key: string): string | undefined {
903
+ if (!isRecord(source)) return undefined;
904
+ const value = source[key];
905
+ return typeof value === "string" ? value : undefined;
906
+ }
907
+
908
+ function appendAnthropicRateLimitEvidence(bodyText: string, headers: Headers): string {
909
+ const evidence = getSafeAnthropicHeaderEvidence(headers);
910
+ if (evidence.length === 0) return bodyText;
911
+
912
+ const suffix = ` Anthropic rate-limit evidence: ${evidence.join(", ")}`;
913
+ try {
914
+ const parsed = JSON.parse(bodyText) as unknown;
915
+ if (isRecord(parsed)) {
916
+ const error = parsed.error;
917
+ if (isRecord(error) && typeof error.message === "string" && !error.message.includes(suffix)) {
918
+ return JSON.stringify({ ...parsed, error: { ...error, message: `${error.message}${suffix}` } });
919
+ }
920
+ if (typeof parsed.message === "string" && !parsed.message.includes(suffix)) {
921
+ return JSON.stringify({ ...parsed, message: `${parsed.message}${suffix}` });
922
+ }
923
+ }
924
+ } catch {}
925
+
926
+ return bodyText.includes(suffix) ? bodyText : `${bodyText}${suffix}`;
927
+ }
928
+
929
+ function isAnthropicUsageExhaustionResponse(
930
+ bodyText: string,
931
+ headers: Headers,
932
+ retryAfterMs: number | undefined,
933
+ retryDelayCapMs: number,
934
+ ): boolean {
935
+ const overageReason = headers.get("anthropic-ratelimit-unified-overage-disabled-reason")?.toLowerCase();
936
+ if (overageReason === "out_of_credits") return true;
937
+ if (retryAfterMs !== undefined && retryAfterMs > retryDelayCapMs) return true;
938
+
939
+ try {
940
+ const parsed = JSON.parse(bodyText) as unknown;
941
+ const error = isRecord(parsed) ? parsed.error : undefined;
942
+ const type = getStringProperty(error, "type") ?? getStringProperty(parsed, "type");
943
+ const message = getStringProperty(error, "message") ?? getStringProperty(parsed, "message") ?? bodyText;
944
+ return (
945
+ /rate_limit_error/i.test(type ?? "") &&
946
+ (/request would exceed your account.?s rate limit/i.test(message) || /out_of_credits/i.test(message))
947
+ );
948
+ } catch {
949
+ return /request would exceed your account.?s rate limit|out_of_credits/i.test(bodyText);
950
+ }
951
+ }
952
+
953
+ function wrapAnthropicFetchForBoundedRateLimits(baseFetch: FetchImpl, maxRetryDelayMs: number | undefined): FetchImpl {
954
+ const retryDelayCapMs = maxRetryDelayMs ?? ANTHROPIC_RETRY_DELAY_CAP_MS;
955
+ return Object.assign(
956
+ async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
957
+ const response = await baseFetch(input, init);
958
+ if (response.status !== 429 || retryDelayCapMs === 0) return response;
959
+
960
+ const headers = new Headers(response.headers);
961
+ const retryAfterMs = getRetryAfterMsFromHeaders(headers);
962
+ const bodyText = await response
963
+ .clone()
964
+ .text()
965
+ .catch(() => "");
966
+ if (!isAnthropicUsageExhaustionResponse(bodyText, headers, retryAfterMs, retryDelayCapMs)) return response;
967
+
968
+ headers.set("x-should-retry", "false");
969
+ return new Response(appendAnthropicRateLimitEvidence(bodyText, headers), {
970
+ status: response.status,
971
+ statusText: response.statusText,
972
+ headers,
973
+ });
974
+ },
975
+ baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
976
+ );
977
+ }
978
+
880
979
  // The Anthropic SDK logs malformed SSE frames directly before rethrowing them.
881
980
  // We surface the resulting provider error ourselves, so keep the SDK quiet.
882
981
  const ANTHROPIC_SDK_LOG_LEVEL = "off" as const;
@@ -1042,6 +1141,7 @@ export function isProviderRetryableError(error: unknown, provider?: string): boo
1042
1141
  if (!(error instanceof Error)) return false;
1043
1142
  if (provider === "github-copilot" && isCopilotTransientModelError(error)) return true;
1044
1143
  const msg = error.message.toLowerCase();
1144
+ if (isUsageLimitError(error.message)) return false;
1045
1145
  if (
1046
1146
  isUnexpectedSocketCloseMessage(msg) ||
1047
1147
  /rate.?limit|too many requests|overloaded|service.?unavailable|internal_error|stream error.*received from peer|1302|timed?\s*out while waiting for the first event|timeout waiting for first/i.test(
@@ -1166,6 +1266,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1166
1266
  onSseEvent: options?.onSseEvent,
1167
1267
  fetch: options?.fetch,
1168
1268
  requestMaxRetries: options?.requestMaxRetries,
1269
+ maxRetryDelayMs: options?.maxRetryDelayMs,
1169
1270
  });
1170
1271
  client = created.client;
1171
1272
  isOAuthToken = created.isOAuthToken;
@@ -1723,11 +1824,8 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
1723
1824
  const foundryCustomHeaders = resolveAnthropicCustomHeaders(model);
1724
1825
  const tlsFetchOptions = buildClaudeCodeTlsFetchOptions(model, baseUrl);
1725
1826
  const baseFetch = args.fetch ?? fetch;
1726
- const debugFetch = onSseEvent
1727
- ? wrapFetchForSseDebug(baseFetch, event => onSseEvent(event, model))
1728
- : args.fetch
1729
- ? baseFetch
1730
- : undefined;
1827
+ const boundedFetch = wrapAnthropicFetchForBoundedRateLimits(baseFetch, args.maxRetryDelayMs);
1828
+ const debugFetch = onSseEvent ? wrapFetchForSseDebug(boundedFetch, event => onSseEvent(event, model)) : boundedFetch;
1731
1829
  if (model.provider === "github-copilot") {
1732
1830
  const copilotApiKey = parseGitHubCopilotApiKey(apiKey).accessToken;
1733
1831
  const betaFeatures = [...extraBetas];
@@ -1755,7 +1853,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
1755
1853
  dangerouslyAllowBrowser: true,
1756
1854
  defaultHeaders,
1757
1855
  logLevel: ANTHROPIC_SDK_LOG_LEVEL,
1758
- ...(debugFetch ? { fetch: debugFetch } : {}),
1856
+ fetch: debugFetch,
1759
1857
  ...(tlsFetchOptions ? { fetchOptions: tlsFetchOptions } : {}),
1760
1858
  };
1761
1859
  }
@@ -1789,7 +1887,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
1789
1887
  dangerouslyAllowBrowser: true,
1790
1888
  defaultHeaders,
1791
1889
  logLevel: ANTHROPIC_SDK_LOG_LEVEL,
1792
- ...(debugFetch ? { fetch: debugFetch } : {}),
1890
+ fetch: debugFetch,
1793
1891
  };
1794
1892
  }
1795
1893
 
@@ -1802,7 +1900,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
1802
1900
  dangerouslyAllowBrowser: true,
1803
1901
  defaultHeaders,
1804
1902
  logLevel: ANTHROPIC_SDK_LOG_LEVEL,
1805
- ...(debugFetch ? { fetch: debugFetch } : {}),
1903
+ fetch: debugFetch,
1806
1904
  ...(tlsFetchOptions ? { fetchOptions: tlsFetchOptions } : {}),
1807
1905
  };
1808
1906
  }
@@ -2394,7 +2492,7 @@ export function convertAnthropicMessages(
2394
2492
  type: "tool_use",
2395
2493
  id: block.id,
2396
2494
  name: isOAuthToken ? applyClaudeToolPrefix(block.name) : block.name,
2397
- input: block.arguments ?? {},
2495
+ input: sanitizeJsonStrings(block.arguments ?? {}),
2398
2496
  });
2399
2497
  }
2400
2498
  }
@@ -18,7 +18,7 @@ import type {
18
18
  Tool,
19
19
  ToolCall,
20
20
  } from "../types";
21
- import { normalizeSystemPrompts } from "../utils";
21
+ import { normalizeSystemPrompts, sanitizeJsonStrings } from "../utils";
22
22
  import { AssistantMessageEventStream } from "../utils/event-stream";
23
23
  import { finalizeErrorMessage, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
24
24
  import { normalizeSchemaForCCA, normalizeSchemaForGoogle, toolWireSchema } from "../utils/schema";
@@ -237,7 +237,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
237
237
  const part: Part = {
238
238
  functionCall: {
239
239
  name: block.name,
240
- args: block.arguments ?? {},
240
+ args: sanitizeJsonStrings(block.arguments ?? {}) as Record<string, unknown>,
241
241
  ...(requiresToolCallId(model.id) ? { id: block.id } : {}),
242
242
  },
243
243
  };
@@ -467,7 +467,7 @@ export function pushToolCallEvents(
467
467
  stream.push({
468
468
  type: "toolcall_delta",
469
469
  contentIndex,
470
- delta: JSON.stringify(toolCall.arguments),
470
+ delta: JSON.stringify(sanitizeJsonStrings(toolCall.arguments)),
471
471
  partial: output,
472
472
  });
473
473
  stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
@@ -19,6 +19,7 @@ import type {
19
19
  ToolResultMessage,
20
20
  TSchema,
21
21
  } from "../types";
22
+ import { sanitizeJsonStrings } from "../utils";
22
23
  import {
23
24
  type OpenAIChatContentPart,
24
25
  type OpenAIChatMessage,
@@ -453,9 +454,9 @@ function isOnlyRaw(args: Record<string, unknown>): boolean {
453
454
 
454
455
  function stringifyArgs(args: Record<string, unknown>): string {
455
456
  // `__raw` is our fallback marker for un-parseable inbound args; preserve it verbatim on the way out.
456
- if (typeof args.__raw === "string" && isOnlyRaw(args)) return args.__raw;
457
+ if (typeof args.__raw === "string" && isOnlyRaw(args)) return args.__raw.toWellFormed();
457
458
  try {
458
- return JSON.stringify(args);
459
+ return JSON.stringify(sanitizeJsonStrings(args));
459
460
  } catch {
460
461
  return "{}";
461
462
  }
@@ -1,6 +1,7 @@
1
1
  import type { Effort } from "../../model-thinking";
2
2
  import { requireSupportedEffort } from "../../model-thinking";
3
3
  import type { Api, Model } from "../../types";
4
+ import { sanitizeJsonStrings } from "../../utils";
4
5
 
5
6
  export interface ReasoningConfig {
6
7
  effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
@@ -109,6 +110,10 @@ function normalizeInputTextPartFields(input: InputItem[] | undefined): InputItem
109
110
  }
110
111
  if (normalizedItem.type === "message") {
111
112
  normalizedItem.content = normalizeTextPartFields(normalizedItem.content, `input[${itemIndex}].content`);
113
+ } else if (normalizedItem.type === "function_call" && "arguments" in itemRecord) {
114
+ itemRecord.arguments = sanitizeJsonStrings(itemRecord.arguments);
115
+ } else if (normalizedItem.type === "custom_tool_call" && typeof itemRecord.input === "string") {
116
+ itemRecord.input = itemRecord.input.toWellFormed();
112
117
  }
113
118
  return normalizedItem;
114
119
  });
@@ -22,6 +22,7 @@ import type {
22
22
  Tool,
23
23
  ToolCall,
24
24
  } from "../types";
25
+ import { sanitizeJsonStrings } from "../utils";
25
26
  import {
26
27
  type OpenAIResponsesFunctionCallItem,
27
28
  type OpenAIResponsesFunctionCallOutputItem,
@@ -612,7 +613,8 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
612
613
  } else if (part.type === "toolCall") {
613
614
  flushMessage();
614
615
  if (part.customWireName) {
615
- const rawInput = typeof part.arguments?.input === "string" ? (part.arguments.input as string) : "";
616
+ const rawInput =
617
+ typeof part.arguments?.input === "string" ? (part.arguments.input as string).toWellFormed() : "";
616
618
  out.push({
617
619
  type: "custom_tool_call",
618
620
  id: part.thoughtSignature ?? makeCustomCallId(),
@@ -627,7 +629,7 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
627
629
  id: part.thoughtSignature ?? makeFuncCallId(),
628
630
  call_id: part.id,
629
631
  name: part.name,
630
- arguments: JSON.stringify(part.arguments ?? {}),
632
+ arguments: JSON.stringify(sanitizeJsonStrings(part.arguments ?? {})),
631
633
  status: "completed",
632
634
  });
633
635
  }
@@ -1085,7 +1087,8 @@ export function encodeStream(
1085
1087
  } else {
1086
1088
  // Standard JSON tool: arguments object on the skc side, the
1087
1089
  // wire wants the JSON string the model emitted (= streamed deltas).
1088
- const argsJson = cur.argsText || JSON.stringify(tc.arguments ?? {});
1090
+ const argsJson =
1091
+ cur.argsText.toWellFormed() || JSON.stringify(sanitizeJsonStrings(tc.arguments ?? {}));
1089
1092
  cur.argsText = argsJson;
1090
1093
  emit("response.function_call_arguments.done", {
1091
1094
  item_id: cur.itemId,
@@ -28,7 +28,7 @@ import {
28
28
  type ToolCall,
29
29
  type ToolResultMessage,
30
30
  } from "../types";
31
- import { normalizeResponsesToolCallId } from "../utils";
31
+ import { normalizeResponsesToolCallId, sanitizeJsonStrings } from "../utils";
32
32
  import type { AssistantMessageEventStream } from "../utils/event-stream";
33
33
  import { isCompleteJson, parseStreamingJson } from "../utils/json-parse";
34
34
  import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
@@ -275,7 +275,7 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
275
275
  }
276
276
  knownCallIds.add(normalized.callId);
277
277
  if (block.customWireName) {
278
- const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input : "";
278
+ const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input.toWellFormed() : "";
279
279
  customCallIds?.add(normalized.callId);
280
280
  outputItems.push({
281
281
  type: "custom_tool_call",
@@ -291,7 +291,7 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
291
291
  id: itemId,
292
292
  call_id: normalized.callId,
293
293
  name: block.name,
294
- arguments: JSON.stringify(block.arguments),
294
+ arguments: JSON.stringify(sanitizeJsonStrings(block.arguments ?? {})),
295
295
  });
296
296
  }
297
297
 
@@ -36,6 +36,14 @@ export function parseRateLimitReason(errorMessage: string): RateLimitReason {
36
36
  return "MODEL_CAPACITY_EXHAUSTED";
37
37
  }
38
38
 
39
+ if (
40
+ lower.includes("out_of_credits") ||
41
+ lower.includes("request would exceed your account's rate limit") ||
42
+ lower.includes("request would exceed your accounts rate limit")
43
+ ) {
44
+ return "QUOTA_EXHAUSTED";
45
+ }
46
+
39
47
  if (
40
48
  lower.includes("per minute") ||
41
49
  lower.includes("rate limit") ||
@@ -86,8 +94,7 @@ export function calculateRateLimitBackoffMs(reason: RateLimitReason): number {
86
94
 
87
95
  /** Detect usage/quota limit errors in error messages (persistent, requires credential switch). */
88
96
  const USAGE_LIMIT_PATTERN =
89
- /usage.?limit|usage_limit_reached|usage_not_included|limit_reached|model.?limit|model_limit_reached|message.?limit|message_limit_reached|limit for this model|quota.?exceeded|resource has been exhausted[^\n]*(?:quota|limit)/i;
90
-
97
+ /usage.?limit|usage_limit_reached|usage_not_included|limit_reached|model.?limit|model_limit_reached|message.?limit|message_limit_reached|limit for this model|quota.?exceeded|out_of_credits|request would exceed your account.?s rate limit|resource has been exhausted[^\n]*(?:quota|limit)/i;
91
98
  export function isUsageLimitError(errorMessage: string): boolean {
92
99
  return USAGE_LIMIT_PATTERN.test(errorMessage);
93
100
  }
package/src/utils.ts CHANGED
@@ -11,6 +11,34 @@ export function normalizeSystemPrompts(systemPrompt: readonly string[] | string
11
11
  return prompts.map(prompt => prompt.toWellFormed()).filter(prompt => prompt.length > 0);
12
12
  }
13
13
 
14
+ export function sanitizeJsonStrings(value: unknown): unknown {
15
+ return sanitizeJsonStringsInner(value, new WeakMap<object, unknown>());
16
+ }
17
+
18
+ function sanitizeJsonStringsInner(value: unknown, seen: WeakMap<object, unknown>): unknown {
19
+ if (typeof value === "string") return value.toWellFormed();
20
+ if (!value || typeof value !== "object") return value;
21
+
22
+ const cached = seen.get(value);
23
+ if (cached !== undefined) return cached;
24
+
25
+ if (Array.isArray(value)) {
26
+ const sanitized: unknown[] = [];
27
+ seen.set(value, sanitized);
28
+ for (const item of value) {
29
+ sanitized.push(sanitizeJsonStringsInner(item, seen));
30
+ }
31
+ return sanitized;
32
+ }
33
+
34
+ const sanitized: Record<string, unknown> = {};
35
+ seen.set(value, sanitized);
36
+ for (const [key, nestedValue] of Object.entries(value)) {
37
+ sanitized[key.toWellFormed()] = sanitizeJsonStringsInner(nestedValue, seen);
38
+ }
39
+ return sanitized;
40
+ }
41
+
14
42
  export function toNumber(value: unknown): number | undefined {
15
43
  if (typeof value === "number" && Number.isFinite(value)) return value;
16
44
  if (typeof value === "string" && value.trim()) {