@sayknow-cli/ai 0.3.16 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/package.json +23 -24
  2. package/src/auth-gateway/server.ts +164 -22
  3. package/src/auth-storage.ts +76 -43
  4. package/src/context-cap-policy.ts +59 -0
  5. package/src/index.ts +2 -0
  6. package/src/model-cache.ts +10 -0
  7. package/src/model-manager.ts +30 -17
  8. package/src/model-thinking.ts +14 -8
  9. package/src/providers/amazon-bedrock.ts +9 -1
  10. package/src/providers/anthropic-messages-server.ts +178 -17
  11. package/src/providers/anthropic.ts +199 -213
  12. package/src/providers/azure-openai-responses.ts +6 -1
  13. package/src/providers/google-gemini-cli.ts +26 -13
  14. package/src/providers/google-shared.ts +7 -1
  15. package/src/providers/ollama.ts +7 -1
  16. package/src/providers/openai-chat-server.ts +93 -5
  17. package/src/providers/openai-codex/response-handler.ts +11 -3
  18. package/src/providers/openai-codex-responses.ts +107 -20
  19. package/src/providers/openai-responses-server.ts +111 -36
  20. package/src/providers/openai-responses-shared.ts +71 -12
  21. package/src/providers/openai-responses.ts +26 -3
  22. package/src/providers/pi-native-client.ts +24 -12
  23. package/src/providers/pi-native-server.ts +275 -21
  24. package/src/types.ts +53 -4
  25. package/src/utils/discovery/codex.ts +3 -12
  26. package/src/utils/event-stream.ts +146 -58
  27. package/src/utils/fallback-transport.ts +269 -0
  28. package/src/utils/overflow.ts +72 -31
  29. package/src/utils/retry.ts +2 -2
  30. package/src/utils/validation.ts +6 -1
  31. package/src/utils.ts +120 -12
  32. package/dist/types/api-registry.d.ts +0 -30
  33. package/dist/types/auth-broker/client.d.ts +0 -67
  34. package/dist/types/auth-broker/index.d.ts +0 -5
  35. package/dist/types/auth-broker/refresher.d.ts +0 -25
  36. package/dist/types/auth-broker/remote-store.d.ts +0 -99
  37. package/dist/types/auth-broker/server.d.ts +0 -32
  38. package/dist/types/auth-broker/types.d.ts +0 -110
  39. package/dist/types/auth-broker/wire-schemas.d.ts +0 -443
  40. package/dist/types/auth-gateway/http.d.ts +0 -40
  41. package/dist/types/auth-gateway/index.d.ts +0 -3
  42. package/dist/types/auth-gateway/server.d.ts +0 -17
  43. package/dist/types/auth-gateway/types.d.ts +0 -115
  44. package/dist/types/auth-storage.d.ts +0 -695
  45. package/dist/types/cli.d.ts +0 -2
  46. package/dist/types/index.d.ts +0 -51
  47. package/dist/types/model-cache.d.ts +0 -17
  48. package/dist/types/model-manager.d.ts +0 -62
  49. package/dist/types/model-retirements.d.ts +0 -6
  50. package/dist/types/model-thinking.d.ts +0 -74
  51. package/dist/types/models.d.ts +0 -21
  52. package/dist/types/provider-details.d.ts +0 -24
  53. package/dist/types/provider-models/bundled-references.d.ts +0 -4
  54. package/dist/types/provider-models/descriptors.d.ts +0 -48
  55. package/dist/types/provider-models/google.d.ts +0 -20
  56. package/dist/types/provider-models/index.d.ts +0 -5
  57. package/dist/types/provider-models/ollama.d.ts +0 -7
  58. package/dist/types/provider-models/openai-compat.d.ts +0 -256
  59. package/dist/types/provider-models/special.d.ts +0 -19
  60. package/dist/types/providers/amazon-bedrock.d.ts +0 -60
  61. package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -450
  62. package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
  63. package/dist/types/providers/anthropic.d.ts +0 -208
  64. package/dist/types/providers/aws-credential-config.d.ts +0 -19
  65. package/dist/types/providers/aws-credentials.d.ts +0 -43
  66. package/dist/types/providers/aws-eventstream.d.ts +0 -38
  67. package/dist/types/providers/aws-sigv4.d.ts +0 -55
  68. package/dist/types/providers/azure-openai-responses.d.ts +0 -15
  69. package/dist/types/providers/composer-discipline.d.ts +0 -26
  70. package/dist/types/providers/cursor/client-version.d.ts +0 -10
  71. package/dist/types/providers/cursor/gen/agent_pb.d.ts +0 -13022
  72. package/dist/types/providers/cursor.d.ts +0 -45
  73. package/dist/types/providers/error-message.d.ts +0 -27
  74. package/dist/types/providers/github-copilot-headers.d.ts +0 -40
  75. package/dist/types/providers/gitlab-duo.d.ts +0 -27
  76. package/dist/types/providers/google-auth.d.ts +0 -24
  77. package/dist/types/providers/google-gemini-cli.d.ts +0 -75
  78. package/dist/types/providers/google-gemini-headers.d.ts +0 -43
  79. package/dist/types/providers/google-shared.d.ts +0 -179
  80. package/dist/types/providers/google-types.d.ts +0 -138
  81. package/dist/types/providers/google-vertex.d.ts +0 -7
  82. package/dist/types/providers/google.d.ts +0 -4
  83. package/dist/types/providers/grammar.d.ts +0 -1
  84. package/dist/types/providers/kimi.d.ts +0 -27
  85. package/dist/types/providers/mock.d.ts +0 -177
  86. package/dist/types/providers/ollama.d.ts +0 -41
  87. package/dist/types/providers/openai-anthropic-shim.d.ts +0 -31
  88. package/dist/types/providers/openai-bounded-rate-limits.d.ts +0 -3
  89. package/dist/types/providers/openai-chat-server-schema.d.ts +0 -815
  90. package/dist/types/providers/openai-chat-server.d.ts +0 -16
  91. package/dist/types/providers/openai-codex/constants.d.ts +0 -26
  92. package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -50
  93. package/dist/types/providers/openai-codex/response-handler.d.ts +0 -17
  94. package/dist/types/providers/openai-codex-responses.d.ts +0 -67
  95. package/dist/types/providers/openai-completions-compat.d.ts +0 -27
  96. package/dist/types/providers/openai-completions.d.ts +0 -33
  97. package/dist/types/providers/openai-request-transform.d.ts +0 -4
  98. package/dist/types/providers/openai-responses-server-schema.d.ts +0 -392
  99. package/dist/types/providers/openai-responses-server.d.ts +0 -17
  100. package/dist/types/providers/openai-responses-shared.d.ts +0 -104
  101. package/dist/types/providers/openai-responses.d.ts +0 -32
  102. package/dist/types/providers/pi-native-client.d.ts +0 -13
  103. package/dist/types/providers/pi-native-server.d.ts +0 -68
  104. package/dist/types/providers/register-builtins.d.ts +0 -31
  105. package/dist/types/providers/synthetic.d.ts +0 -26
  106. package/dist/types/providers/transform-messages.d.ts +0 -14
  107. package/dist/types/providers/vision-guard.d.ts +0 -8
  108. package/dist/types/rate-limit-utils.d.ts +0 -19
  109. package/dist/types/stream.d.ts +0 -43
  110. package/dist/types/types.d.ts +0 -831
  111. package/dist/types/usage/claude.d.ts +0 -3
  112. package/dist/types/usage/gemini.d.ts +0 -2
  113. package/dist/types/usage/github-copilot.d.ts +0 -7
  114. package/dist/types/usage/google-antigravity.d.ts +0 -2
  115. package/dist/types/usage/grok-cli.d.ts +0 -10
  116. package/dist/types/usage/kimi.d.ts +0 -2
  117. package/dist/types/usage/minimax-code.d.ts +0 -2
  118. package/dist/types/usage/openai-codex.d.ts +0 -3
  119. package/dist/types/usage/shared.d.ts +0 -1
  120. package/dist/types/usage/zai.d.ts +0 -2
  121. package/dist/types/usage.d.ts +0 -258
  122. package/dist/types/utils/abort.d.ts +0 -19
  123. package/dist/types/utils/anthropic-auth.d.ts +0 -31
  124. package/dist/types/utils/discovery/antigravity.d.ts +0 -67
  125. package/dist/types/utils/discovery/codex.d.ts +0 -38
  126. package/dist/types/utils/discovery/cursor.d.ts +0 -23
  127. package/dist/types/utils/discovery/gemini.d.ts +0 -25
  128. package/dist/types/utils/discovery/index.d.ts +0 -4
  129. package/dist/types/utils/discovery/openai-compatible.d.ts +0 -74
  130. package/dist/types/utils/event-stream.d.ts +0 -33
  131. package/dist/types/utils/fireworks-model-id.d.ts +0 -10
  132. package/dist/types/utils/foundry.d.ts +0 -1
  133. package/dist/types/utils/h2-fetch.d.ts +0 -22
  134. package/dist/types/utils/http-inspector.d.ts +0 -35
  135. package/dist/types/utils/idle-iterator.d.ts +0 -67
  136. package/dist/types/utils/json-parse.d.ts +0 -18
  137. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
  138. package/dist/types/utils/oauth/anthropic.d.ts +0 -22
  139. package/dist/types/utils/oauth/api-key-login.d.ts +0 -35
  140. package/dist/types/utils/oauth/api-key-validation.d.ts +0 -27
  141. package/dist/types/utils/oauth/callback-server.d.ts +0 -60
  142. package/dist/types/utils/oauth/cerebras.d.ts +0 -1
  143. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
  144. package/dist/types/utils/oauth/cursor.d.ts +0 -15
  145. package/dist/types/utils/oauth/deepinfra.d.ts +0 -1
  146. package/dist/types/utils/oauth/deepseek.d.ts +0 -10
  147. package/dist/types/utils/oauth/firepass.d.ts +0 -1
  148. package/dist/types/utils/oauth/fireworks.d.ts +0 -1
  149. package/dist/types/utils/oauth/fugu.d.ts +0 -1
  150. package/dist/types/utils/oauth/github-copilot.d.ts +0 -38
  151. package/dist/types/utils/oauth/gitlab-duo.d.ts +0 -3
  152. package/dist/types/utils/oauth/glm-zcode.d.ts +0 -71
  153. package/dist/types/utils/oauth/google-antigravity.d.ts +0 -11
  154. package/dist/types/utils/oauth/google-gemini-cli.d.ts +0 -10
  155. package/dist/types/utils/oauth/google-oauth-shared.d.ts +0 -28
  156. package/dist/types/utils/oauth/huggingface.d.ts +0 -19
  157. package/dist/types/utils/oauth/index.d.ts +0 -39
  158. package/dist/types/utils/oauth/kagi.d.ts +0 -17
  159. package/dist/types/utils/oauth/kilo.d.ts +0 -5
  160. package/dist/types/utils/oauth/kimi.d.ts +0 -21
  161. package/dist/types/utils/oauth/litellm.d.ts +0 -18
  162. package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
  163. package/dist/types/utils/oauth/minimax-code.d.ts +0 -28
  164. package/dist/types/utils/oauth/moonshot.d.ts +0 -1
  165. package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
  166. package/dist/types/utils/oauth/nvidia.d.ts +0 -18
  167. package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
  168. package/dist/types/utils/oauth/ollama.d.ts +0 -18
  169. package/dist/types/utils/oauth/openai-codex.d.ts +0 -21
  170. package/dist/types/utils/oauth/opencode.d.ts +0 -18
  171. package/dist/types/utils/oauth/parallel.d.ts +0 -17
  172. package/dist/types/utils/oauth/perplexity.d.ts +0 -9
  173. package/dist/types/utils/oauth/pkce.d.ts +0 -8
  174. package/dist/types/utils/oauth/qianfan.d.ts +0 -17
  175. package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
  176. package/dist/types/utils/oauth/synthetic.d.ts +0 -1
  177. package/dist/types/utils/oauth/tavily.d.ts +0 -17
  178. package/dist/types/utils/oauth/together.d.ts +0 -1
  179. package/dist/types/utils/oauth/types.d.ts +0 -45
  180. package/dist/types/utils/oauth/venice.d.ts +0 -18
  181. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
  182. package/dist/types/utils/oauth/vllm.d.ts +0 -16
  183. package/dist/types/utils/oauth/xai.d.ts +0 -30
  184. package/dist/types/utils/oauth/xiaomi.d.ts +0 -25
  185. package/dist/types/utils/oauth/zai.d.ts +0 -18
  186. package/dist/types/utils/oauth/zenmux.d.ts +0 -1
  187. package/dist/types/utils/overflow.d.ts +0 -64
  188. package/dist/types/utils/parse-bind.d.ts +0 -23
  189. package/dist/types/utils/provider-response.d.ts +0 -3
  190. package/dist/types/utils/retry-after.d.ts +0 -3
  191. package/dist/types/utils/retry-budget.d.ts +0 -1
  192. package/dist/types/utils/retry.d.ts +0 -26
  193. package/dist/types/utils/schema/adapt.d.ts +0 -24
  194. package/dist/types/utils/schema/compatibility.d.ts +0 -30
  195. package/dist/types/utils/schema/dereference.d.ts +0 -11
  196. package/dist/types/utils/schema/draft.d.ts +0 -10
  197. package/dist/types/utils/schema/equality.d.ts +0 -4
  198. package/dist/types/utils/schema/fields.d.ts +0 -49
  199. package/dist/types/utils/schema/index.d.ts +0 -14
  200. package/dist/types/utils/schema/json-schema-validator.d.ts +0 -12
  201. package/dist/types/utils/schema/meta-validator.d.ts +0 -2
  202. package/dist/types/utils/schema/normalize.d.ts +0 -93
  203. package/dist/types/utils/schema/root-combinator.d.ts +0 -12
  204. package/dist/types/utils/schema/spill.d.ts +0 -8
  205. package/dist/types/utils/schema/stamps.d.ts +0 -25
  206. package/dist/types/utils/schema/types.d.ts +0 -4
  207. package/dist/types/utils/schema/wire.d.ts +0 -54
  208. package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
  209. package/dist/types/utils/sse-debug.d.ts +0 -10
  210. package/dist/types/utils/tool-call-healing.d.ts +0 -71
  211. package/dist/types/utils/tool-choice-capability.d.ts +0 -41
  212. package/dist/types/utils/tool-choice.d.ts +0 -50
  213. package/dist/types/utils/validation.d.ts +0 -17
  214. package/dist/types/utils.d.ts +0 -35
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/ai",
4
- "version": "0.3.16",
4
+ "version": "0.4.0",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://sayknow-cli.com",
7
7
  "author": "jaybeyond",
@@ -27,7 +27,7 @@
27
27
  "api"
28
28
  ],
29
29
  "main": "./src/index.ts",
30
- "types": "./dist/types/index.d.ts",
30
+ "types": "./src/index.ts",
31
31
  "bin": {
32
32
  "pi-ai": "./src/cli.ts"
33
33
  },
@@ -57,88 +57,87 @@
57
57
  "files": [
58
58
  "src",
59
59
  "README.md",
60
- "CHANGELOG.md",
61
- "dist/types"
60
+ "CHANGELOG.md"
62
61
  ],
63
62
  "exports": {
64
63
  ".": {
65
- "types": "./dist/types/index.d.ts",
64
+ "types": "./src/index.ts",
66
65
  "import": "./src/index.ts"
67
66
  },
68
67
  "./*": {
69
- "types": "./dist/types/*.d.ts",
68
+ "types": "./src/*.ts",
70
69
  "import": "./src/*.ts"
71
70
  },
72
71
  "./auth-broker": {
73
- "types": "./dist/types/auth-broker/index.d.ts",
72
+ "types": "./src/auth-broker/index.ts",
74
73
  "import": "./src/auth-broker/index.ts"
75
74
  },
76
75
  "./auth-broker/*": {
77
- "types": "./dist/types/auth-broker/*.d.ts",
76
+ "types": "./src/auth-broker/*.ts",
78
77
  "import": "./src/auth-broker/*.ts"
79
78
  },
80
79
  "./auth-gateway": {
81
- "types": "./dist/types/auth-gateway/index.d.ts",
80
+ "types": "./src/auth-gateway/index.ts",
82
81
  "import": "./src/auth-gateway/index.ts"
83
82
  },
84
83
  "./auth-gateway/*": {
85
- "types": "./dist/types/auth-gateway/*.d.ts",
84
+ "types": "./src/auth-gateway/*.ts",
86
85
  "import": "./src/auth-gateway/*.ts"
87
86
  },
88
87
  "./models.json": {
89
- "types": "./dist/types/models.json.d.d.ts",
88
+ "types": "./src/models.json.d.ts",
90
89
  "import": "./src/models.json"
91
90
  },
92
91
  "./provider-models": {
93
- "types": "./dist/types/provider-models/index.d.ts",
92
+ "types": "./src/provider-models/index.ts",
94
93
  "import": "./src/provider-models/index.ts"
95
94
  },
96
95
  "./provider-models/*": {
97
- "types": "./dist/types/provider-models/*.d.ts",
96
+ "types": "./src/provider-models/*.ts",
98
97
  "import": "./src/provider-models/*.ts"
99
98
  },
100
99
  "./providers/*": {
101
- "types": "./dist/types/providers/*.d.ts",
100
+ "types": "./src/providers/*.ts",
102
101
  "import": "./src/providers/*.ts"
103
102
  },
104
103
  "./providers/cursor/gen/*": {
105
- "types": "./dist/types/providers/cursor/gen/*.d.ts",
104
+ "types": "./src/providers/cursor/gen/*.ts",
106
105
  "import": "./src/providers/cursor/gen/*.ts"
107
106
  },
108
107
  "./providers/openai-codex/*": {
109
- "types": "./dist/types/providers/openai-codex/*.d.ts",
108
+ "types": "./src/providers/openai-codex/*.ts",
110
109
  "import": "./src/providers/openai-codex/*.ts"
111
110
  },
112
111
  "./usage/*": {
113
- "types": "./dist/types/usage/*.d.ts",
112
+ "types": "./src/usage/*.ts",
114
113
  "import": "./src/usage/*.ts"
115
114
  },
116
115
  "./utils/*": {
117
- "types": "./dist/types/utils/*.d.ts",
116
+ "types": "./src/utils/*.ts",
118
117
  "import": "./src/utils/*.ts"
119
118
  },
120
119
  "./utils/discovery": {
121
- "types": "./dist/types/utils/discovery/index.d.ts",
120
+ "types": "./src/utils/discovery/index.ts",
122
121
  "import": "./src/utils/discovery/index.ts"
123
122
  },
124
123
  "./utils/discovery/*": {
125
- "types": "./dist/types/utils/discovery/*.d.ts",
124
+ "types": "./src/utils/discovery/*.ts",
126
125
  "import": "./src/utils/discovery/*.ts"
127
126
  },
128
127
  "./utils/oauth": {
129
- "types": "./dist/types/utils/oauth/index.d.ts",
128
+ "types": "./src/utils/oauth/index.ts",
130
129
  "import": "./src/utils/oauth/index.ts"
131
130
  },
132
131
  "./utils/oauth/*": {
133
- "types": "./dist/types/utils/oauth/*.d.ts",
132
+ "types": "./src/utils/oauth/*.ts",
134
133
  "import": "./src/utils/oauth/*.ts"
135
134
  },
136
135
  "./utils/schema": {
137
- "types": "./dist/types/utils/schema/index.d.ts",
136
+ "types": "./src/utils/schema/index.ts",
138
137
  "import": "./src/utils/schema/index.ts"
139
138
  },
140
139
  "./utils/schema/*": {
141
- "types": "./dist/types/utils/schema/*.d.ts",
140
+ "types": "./src/utils/schema/*.ts",
142
141
  "import": "./src/utils/schema/*.ts"
143
142
  },
144
143
  "./*.js": "./src/*.ts"
@@ -17,6 +17,7 @@
17
17
  * POST /v1/messages → Anthropic messages in/out
18
18
  * POST /v1/responses → OpenAI Responses in/out
19
19
  */
20
+
20
21
  import { logger } from "@sayknow-cli/utils";
21
22
  import type { AuthStorage } from "../auth-storage";
22
23
  import { Effort } from "../model-thinking";
@@ -26,6 +27,7 @@ import * as openaiResponses from "../providers/openai-responses-server";
26
27
  import * as piNative from "../providers/pi-native-server";
27
28
  import { streamSimple } from "../stream";
28
29
  import type { Api, AssistantMessageEventStream, Context, Model, SimpleStreamOptions } from "../types";
30
+ import { beginAttempt, classifyFallbackTrigger } from "../utils/fallback-transport";
29
31
  import { parseBind } from "../utils/parse-bind";
30
32
  import {
31
33
  captureRequestHeaders,
@@ -260,6 +262,90 @@ async function refreshGatewayApiKeyAfterAuthError(
260
262
  return storage.getApiKey(provider, undefined, { modelId: model.id, signal });
261
263
  }
262
264
 
265
+ /**
266
+ * Records a managed gateway failure against the credential selected for this
267
+ * request. This deliberately never returns a replacement key: the outer
268
+ * fallback controller owns the next attempt and is the only component allowed
269
+ * to make another upstream request.
270
+ */
271
+ async function markManagedGatewayCredentialFailure(
272
+ storage: AuthStorage,
273
+ model: Model<Api>,
274
+ apiKey: string,
275
+ error: unknown,
276
+ signal: AbortSignal,
277
+ format: string,
278
+ peer: string,
279
+ ): Promise<void> {
280
+ const trigger = classifyFallbackTrigger(error);
281
+ try {
282
+ if (trigger.class === "auth") {
283
+ await storage.invalidateCredentialMatching(model.provider, apiKey, signal);
284
+ } else if (trigger.class === "quota" || trigger.class === "rate_limit") {
285
+ await storage.markUsageLimitReached(model.provider, undefined, {
286
+ retryAfterMs: trigger.retryAfterMs,
287
+ signal,
288
+ });
289
+ } else {
290
+ return;
291
+ }
292
+ logger.debug("auth-gateway recorded managed credential failure", {
293
+ format,
294
+ provider: model.provider,
295
+ peer,
296
+ trigger: trigger.class,
297
+ });
298
+ } catch (markError) {
299
+ // Credential bookkeeping must not replace the upstream failure returned to
300
+ // the fallback controller.
301
+ logger.warn("auth-gateway failed to record managed credential failure", {
302
+ format,
303
+ provider: model.provider,
304
+ peer,
305
+ error: markError instanceof Error ? markError.message : String(markError),
306
+ });
307
+ }
308
+ }
309
+
310
+ function observeManagedGatewayFailure(
311
+ events: AssistantMessageEventStream,
312
+ markFailure: (error: unknown) => Promise<void>,
313
+ ): AssistantMessageEventStream {
314
+ let marked = false;
315
+ const markOnce = async (error: unknown): Promise<void> => {
316
+ if (marked) return;
317
+ marked = true;
318
+ await markFailure(error);
319
+ };
320
+ async function* observed() {
321
+ try {
322
+ for await (const event of events) {
323
+ if (event.type === "error") {
324
+ await markOnce(event.error.transportFailure ?? { kind: "transport", status: event.error.errorStatus });
325
+ }
326
+ yield event;
327
+ }
328
+ } catch (error) {
329
+ await markOnce(error);
330
+ throw error;
331
+ }
332
+ }
333
+ const result = observed() as unknown as AssistantMessageEventStream;
334
+ result.result = async () => {
335
+ try {
336
+ const message = await events.result();
337
+ if (message.stopReason === "error") {
338
+ await markOnce(message.transportFailure ?? { kind: "transport", status: message.errorStatus });
339
+ }
340
+ return message;
341
+ } catch (error) {
342
+ await markOnce(error);
343
+ throw error;
344
+ }
345
+ };
346
+ return result;
347
+ }
348
+
263
349
  function clientClosedResponse(route: { module: FormatModule }): Response {
264
350
  return route.module.formatError(499, "request_aborted", "client closed request");
265
351
  }
@@ -361,17 +447,21 @@ async function handleFormatEndpoint(
361
447
 
362
448
  const streamOpts = buildStreamOptions(parsed, model.api, controller.signal);
363
449
  streamOpts.apiKey = apiKey;
364
- streamOpts.onAuthError = (provider, oldKey, error) =>
365
- refreshGatewayApiKeyAfterAuthError(
366
- bootOpts.storage,
367
- model,
368
- provider,
369
- oldKey,
370
- error,
371
- controller.signal,
372
- route.label,
373
- peer,
374
- );
450
+ if (streamOpts.fallbackManaged) {
451
+ streamOpts.fallbackAttempt = beginAttempt(model.id, "auth-gateway");
452
+ } else {
453
+ streamOpts.onAuthError = (provider, oldKey, error) =>
454
+ refreshGatewayApiKeyAfterAuthError(
455
+ bootOpts.storage,
456
+ model,
457
+ provider,
458
+ oldKey,
459
+ error,
460
+ controller.signal,
461
+ route.label,
462
+ peer,
463
+ );
464
+ }
375
465
 
376
466
  logger.info("auth-gateway request", {
377
467
  format: route.label,
@@ -387,10 +477,34 @@ async function handleFormatEndpoint(
387
477
  if (controller.signal.aborted) return clientClosedResponse(route);
388
478
  events = streamSimple(model, parsed.context, streamOpts);
389
479
  } catch (error) {
480
+ if (streamOpts.fallbackManaged) {
481
+ await markManagedGatewayCredentialFailure(
482
+ bootOpts.storage,
483
+ model,
484
+ apiKey,
485
+ error,
486
+ controller.signal,
487
+ route.label,
488
+ peer,
489
+ );
490
+ }
390
491
  const classified = classifyGatewayError(error);
391
492
  logger.warn("auth-gateway streamSimple threw", { format: route.label, error: classified.message, peer });
392
493
  return route.module.formatError(classified.status, classified.type, classified.message);
393
494
  }
495
+ if (streamOpts.fallbackManaged) {
496
+ events = observeManagedGatewayFailure(events, error =>
497
+ markManagedGatewayCredentialFailure(
498
+ bootOpts.storage,
499
+ model,
500
+ apiKey,
501
+ error,
502
+ controller.signal,
503
+ route.label,
504
+ peer,
505
+ ),
506
+ );
507
+ }
394
508
 
395
509
  if (!parsed.stream) {
396
510
  try {
@@ -509,17 +623,21 @@ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, pe
509
623
  // only inject server-controlled fields. The OpenAI code backend temperature/topP strip
510
624
  // matches `buildStreamOptions` — OpenAI code backend rejects them with a 400.
511
625
  const streamOpts: SimpleStreamOptions = { ...parsed.options, apiKey, signal: controller.signal };
512
- streamOpts.onAuthError = (provider, oldKey, error) =>
513
- refreshGatewayApiKeyAfterAuthError(
514
- bootOpts.storage,
515
- model,
516
- provider,
517
- oldKey,
518
- error,
519
- controller.signal,
520
- "pi-native",
521
- peer,
522
- );
626
+ if (streamOpts.fallbackManaged) {
627
+ streamOpts.fallbackAttempt = beginAttempt(model.id, "auth-gateway-pi-native");
628
+ } else {
629
+ streamOpts.onAuthError = (provider, oldKey, error) =>
630
+ refreshGatewayApiKeyAfterAuthError(
631
+ bootOpts.storage,
632
+ model,
633
+ provider,
634
+ oldKey,
635
+ error,
636
+ controller.signal,
637
+ "pi-native",
638
+ peer,
639
+ );
640
+ }
523
641
  if (model.api === "openai-codex-responses") {
524
642
  delete streamOpts.temperature;
525
643
  delete streamOpts.topP;
@@ -547,10 +665,34 @@ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, pe
547
665
  if (controller.signal.aborted) return aborted();
548
666
  events = streamSimple(model, parsed.context, streamOpts);
549
667
  } catch (error) {
668
+ if (streamOpts.fallbackManaged) {
669
+ await markManagedGatewayCredentialFailure(
670
+ bootOpts.storage,
671
+ model,
672
+ apiKey,
673
+ error,
674
+ controller.signal,
675
+ "pi-native",
676
+ peer,
677
+ );
678
+ }
550
679
  const classified = classifyGatewayError(error);
551
680
  logger.warn("auth-gateway streamSimple threw", { format: "pi-native", error: classified.message, peer });
552
681
  return piNative.formatError(classified.status, classified.type, classified.message);
553
682
  }
683
+ if (streamOpts.fallbackManaged) {
684
+ events = observeManagedGatewayFailure(events, error =>
685
+ markManagedGatewayCredentialFailure(
686
+ bootOpts.storage,
687
+ model,
688
+ apiKey,
689
+ error,
690
+ controller.signal,
691
+ "pi-native",
692
+ peer,
693
+ ),
694
+ );
695
+ }
554
696
 
555
697
  if (!parsed.stream) {
556
698
  try {
@@ -775,7 +775,9 @@ export class AuthStorage {
775
775
  */
776
776
  static async create(dbPath: string, options: AuthStorageOptions = {}): Promise<AuthStorage> {
777
777
  const store = await SqliteAuthCredentialStore.open(dbPath);
778
- return new AuthStorage(store, options);
778
+ const storage = new AuthStorage(store, options);
779
+ await storage.reload();
780
+ return storage;
779
781
  }
780
782
 
781
783
  /**
@@ -854,6 +856,7 @@ export class AuthStorage {
854
856
  */
855
857
  setRuntimeApiKey(provider: string, apiKey: string): void {
856
858
  this.#runtimeOverrides.set(provider, apiKey);
859
+ this.#bumpGeneration("set-runtime-api-key");
857
860
  }
858
861
 
859
862
  /**
@@ -877,7 +880,12 @@ export class AuthStorage {
877
880
  * Remove a runtime API key override.
878
881
  */
879
882
  removeRuntimeApiKey(provider: string): void {
880
- this.#runtimeOverrides.delete(provider);
883
+ if (this.#runtimeOverrides.delete(provider)) this.#bumpGeneration("remove-runtime-api-key");
884
+ }
885
+
886
+ /** Whether a provider is currently authenticated by a runtime API-key override. */
887
+ hasRuntimeApiKey(provider: string): boolean {
888
+ return Boolean(this.#runtimeOverrides.get(provider));
881
889
  }
882
890
 
883
891
  /**
@@ -892,13 +900,14 @@ export class AuthStorage {
892
900
  */
893
901
  setConfigApiKey(provider: string, apiKey: string): void {
894
902
  this.#configOverrides.set(provider, apiKey);
903
+ this.#bumpGeneration("set-config-api-key");
895
904
  }
896
905
 
897
906
  /**
898
907
  * Remove a single config-sourced API key override.
899
908
  */
900
909
  removeConfigApiKey(provider: string): void {
901
- this.#configOverrides.delete(provider);
910
+ if (this.#configOverrides.delete(provider)) this.#bumpGeneration("remove-config-api-key");
902
911
  }
903
912
 
904
913
  /**
@@ -906,7 +915,9 @@ export class AuthStorage {
906
915
  * re-parsing `models.yml` so removed entries actually disappear.
907
916
  */
908
917
  clearConfigApiKeys(): void {
918
+ if (this.#configOverrides.size === 0) return;
909
919
  this.#configOverrides.clear();
920
+ this.#bumpGeneration("clear-config-api-keys");
910
921
  }
911
922
 
912
923
  /**
@@ -2067,7 +2078,11 @@ export class AuthStorage {
2067
2078
  });
2068
2079
  }
2069
2080
 
2070
- async #fetchUsageUncached(request: UsageRequestDescriptor, timeoutMs?: number): Promise<UsageReport | null> {
2081
+ async #fetchUsageUncached(
2082
+ request: UsageRequestDescriptor,
2083
+ timeoutMs?: number,
2084
+ logDetails: boolean = true,
2085
+ ): Promise<UsageReport | null> {
2071
2086
  const resolver = this.#usageProviderResolver;
2072
2087
  if (!resolver) return null;
2073
2088
 
@@ -2105,10 +2120,12 @@ export class AuthStorage {
2105
2120
  credential: refreshedCredential,
2106
2121
  };
2107
2122
  } catch (error) {
2108
- this.#usageLogger?.debug("Usage credential refresh failed, using original credential", {
2109
- provider: request.provider,
2110
- error: String(error),
2111
- });
2123
+ if (logDetails) {
2124
+ this.#usageLogger?.debug("Usage credential refresh failed, using original credential", {
2125
+ provider: request.provider,
2126
+ error: String(error),
2127
+ });
2128
+ }
2112
2129
  }
2113
2130
  }
2114
2131
  }
@@ -2118,18 +2135,24 @@ export class AuthStorage {
2118
2135
  try {
2119
2136
  return await providerImpl.fetchUsage(params, {
2120
2137
  fetch: this.#usageFetch,
2121
- logger: this.#usageLogger,
2138
+ logger: logDetails ? this.#usageLogger : undefined,
2122
2139
  });
2123
2140
  } catch (error) {
2124
- logger.debug("AuthStorage usage fetch failed", {
2125
- provider: request.provider,
2126
- error: String(error),
2127
- });
2141
+ if (logDetails) {
2142
+ logger.debug("AuthStorage usage fetch failed", {
2143
+ provider: request.provider,
2144
+ error: String(error),
2145
+ });
2146
+ }
2128
2147
  return null;
2129
2148
  }
2130
2149
  }
2131
2150
 
2132
- async #fetchUsageCached(request: UsageRequestDescriptor, timeoutMs?: number): Promise<UsageReport | null> {
2151
+ async #fetchUsageCached(
2152
+ request: UsageRequestDescriptor,
2153
+ timeoutMs?: number,
2154
+ logDetails: boolean = true,
2155
+ ): Promise<UsageReport | null> {
2133
2156
  const cacheKey = this.#buildUsageReportCacheKey(request);
2134
2157
  const now = Date.now();
2135
2158
  const cached = this.#usageCache.get<UsageReport | null>(cacheKey);
@@ -2142,7 +2165,7 @@ export class AuthStorage {
2142
2165
  if (inFlight) return inFlight;
2143
2166
 
2144
2167
  const promise = (async () => {
2145
- const report = await this.#fetchUsageUncached(request, timeoutMs);
2168
+ const report = await this.#fetchUsageUncached(request, timeoutMs, logDetails);
2146
2169
  const ttlJitter = USAGE_REPORT_TTL_MS * (Math.random() * 0.5 - 0.25);
2147
2170
  if (report !== null) {
2148
2171
  // Success: stagger per-credential cache expiry so all accounts don't
@@ -2385,6 +2408,8 @@ export class AuthStorage {
2385
2408
  baseUrlResolver?: (provider: Provider) => string | undefined;
2386
2409
  /** Caller's cancel signal; only rejects this caller, never the shared upstream fetch. */
2387
2410
  signal?: AbortSignal;
2411
+ /** Disable provider/account/error logging for secret-safe control surfaces. */
2412
+ logDetails?: boolean;
2388
2413
  }): Promise<UsageReport[] | null> {
2389
2414
  // Caller override > store-level hook > local per-credential fan-out.
2390
2415
  // `RemoteAuthCredentialStore` implements the store hook so a gateway
@@ -2413,9 +2438,11 @@ export class AuthStorage {
2413
2438
  const requests = this.#collectUsageRequests(options);
2414
2439
  if (requests.length === 0) return [];
2415
2440
 
2416
- this.#usageLogger?.debug("Usage fetch requested", {
2417
- providers: [...new Set(requests.map(request => request.provider))].sort(),
2418
- });
2441
+ if (options?.logDetails !== false) {
2442
+ this.#usageLogger?.debug("Usage fetch requested", {
2443
+ providers: [...new Set(requests.map(request => request.provider))].sort(),
2444
+ });
2445
+ }
2419
2446
 
2420
2447
  // Per-credential caching with jitter lives in #fetchUsageCached, so we
2421
2448
  // don't store the aggregated result here — doing so locks the widget to
@@ -2428,39 +2455,45 @@ export class AuthStorage {
2428
2455
  if (inFlight) return inFlight;
2429
2456
 
2430
2457
  const promise = (async () => {
2431
- for (const request of requests) {
2432
- this.#usageLogger?.debug("Usage fetch queued", {
2433
- provider: request.provider,
2434
- credentialType: request.credential.type,
2435
- baseUrl: request.baseUrl,
2436
- accountId: request.credential.accountId,
2437
- email: request.credential.email,
2438
- });
2458
+ if (options?.logDetails !== false) {
2459
+ for (const request of requests) {
2460
+ this.#usageLogger?.debug("Usage fetch queued", {
2461
+ provider: request.provider,
2462
+ credentialType: request.credential.type,
2463
+ baseUrl: request.baseUrl,
2464
+ accountId: request.credential.accountId,
2465
+ email: request.credential.email,
2466
+ });
2467
+ }
2439
2468
  }
2440
2469
 
2441
2470
  const results = await Promise.all(
2442
- requests.map(request => this.#fetchUsageCached(request, this.#usageRequestTimeoutMs)),
2471
+ requests.map(request =>
2472
+ this.#fetchUsageCached(request, this.#usageRequestTimeoutMs, options?.logDetails !== false),
2473
+ ),
2443
2474
  );
2444
2475
  const reports = results.filter((report): report is UsageReport => report !== null);
2445
2476
  const deduped = this.#dedupeUsageReports(reports);
2446
2477
  // no outer cache write — see comment above.
2447
2478
  const resolved = deduped;
2448
- this.#usageLogger?.debug("Usage fetch resolved", {
2449
- reports: resolved.map(report => {
2450
- const accountLabel =
2451
- this.#getUsageReportMetadataValue(report, "email") ??
2452
- this.#getUsageReportMetadataValue(report, "accountId") ??
2453
- this.#getUsageReportMetadataValue(report, "account") ??
2454
- this.#getUsageReportMetadataValue(report, "user") ??
2455
- this.#getUsageReportMetadataValue(report, "username") ??
2456
- this.#getUsageReportScopeAccountId(report);
2457
- return {
2458
- provider: report.provider,
2459
- limits: report.limits.length,
2460
- account: accountLabel,
2461
- };
2462
- }),
2463
- });
2479
+ if (options?.logDetails !== false) {
2480
+ this.#usageLogger?.debug("Usage fetch resolved", {
2481
+ reports: resolved.map(report => {
2482
+ const accountLabel =
2483
+ this.#getUsageReportMetadataValue(report, "email") ??
2484
+ this.#getUsageReportMetadataValue(report, "accountId") ??
2485
+ this.#getUsageReportMetadataValue(report, "account") ??
2486
+ this.#getUsageReportMetadataValue(report, "user") ??
2487
+ this.#getUsageReportMetadataValue(report, "username") ??
2488
+ this.#getUsageReportScopeAccountId(report);
2489
+ return {
2490
+ provider: report.provider,
2491
+ limits: report.limits.length,
2492
+ account: accountLabel,
2493
+ };
2494
+ }),
2495
+ });
2496
+ }
2464
2497
  return resolved;
2465
2498
  })().finally(() => {
2466
2499
  this.#usageReportsInFlight.delete(cacheKey);
@@ -0,0 +1,59 @@
1
+ import type { Api, Model } from "./types";
2
+
3
+ export interface CodexGpt56ContextCapPolicy {
4
+ fallback: number;
5
+ ceiling: number;
6
+ }
7
+
8
+ export const CODEX_GPT_5_6_CONTEXT_CAP: CodexGpt56ContextCapPolicy = {
9
+ fallback: 272_000,
10
+ ceiling: 272_000,
11
+ };
12
+
13
+ const CODEX_GPT_5_6_MODEL_IDS: ReadonlySet<string> = new Set([
14
+ "gpt-5.6",
15
+ "gpt-5.6-sol",
16
+ "gpt-5.6-terra",
17
+ "gpt-5.6-luna",
18
+ ]);
19
+
20
+ export function isCodexProductTransport(model: Pick<Model<Api>, "api" | "provider">): boolean {
21
+ return model.provider === "openai-codex" || model.api === "openai-codex-responses";
22
+ }
23
+
24
+ export function isCodexGpt56Tier(model: Pick<Model<Api>, "id">): boolean {
25
+ return CODEX_GPT_5_6_MODEL_IDS.has(model.id.toLowerCase());
26
+ }
27
+
28
+ export function resolveCodexGpt56DiscoveryContext(
29
+ model: Pick<Model<Api>, "api" | "id" | "provider">,
30
+ rawContextWindow: unknown,
31
+ policy: CodexGpt56ContextCapPolicy = CODEX_GPT_5_6_CONTEXT_CAP,
32
+ ): number {
33
+ const observed = isPositiveFiniteNumber(rawContextWindow) ? rawContextWindow : policy.fallback;
34
+ if (!isCodexGpt56Tier(model) || !isCodexProductTransport(model)) {
35
+ return observed;
36
+ }
37
+ return Math.min(observed, policy.ceiling);
38
+ }
39
+
40
+ export function applyFinalCodexGpt56ContextCap<TApi extends Api>(
41
+ models: readonly Model<TApi>[],
42
+ policy: CodexGpt56ContextCapPolicy = CODEX_GPT_5_6_CONTEXT_CAP,
43
+ ): Model<TApi>[] {
44
+ return models.map(model => {
45
+ if (
46
+ !isCodexGpt56Tier(model as Model<Api>) ||
47
+ !isCodexProductTransport(model as Model<Api>) ||
48
+ !isPositiveFiniteNumber(model.contextWindow) ||
49
+ model.contextWindow <= policy.ceiling
50
+ ) {
51
+ return model;
52
+ }
53
+ return { ...model, contextWindow: policy.ceiling };
54
+ });
55
+ }
56
+
57
+ function isPositiveFiniteNumber(value: unknown): value is number {
58
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
59
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./auth-broker";
4
4
  export { type AuthGatewayBootOptions, type ModelResolver, startAuthGateway } from "./auth-gateway/server";
5
5
  export * from "./auth-gateway/types";
6
6
  export * from "./auth-storage";
7
+ export * from "./context-cap-policy";
7
8
  export * from "./model-cache";
8
9
  export * from "./model-manager";
9
10
  export * from "./model-thinking";
@@ -41,6 +42,7 @@ export * from "./usage/zai";
41
42
  export * from "./utils/anthropic-auth";
42
43
  export * from "./utils/discovery";
43
44
  export * from "./utils/event-stream";
45
+ export * from "./utils/fallback-transport";
44
46
  export * from "./utils/h2-fetch";
45
47
  export * from "./utils/oauth";
46
48
  export type {