@sayknow-cli/ai 0.3.15 → 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 +24 -25
  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
@@ -4,19 +4,18 @@
4
4
  * Where the OpenAI / Anthropic / Responses route modules translate foreign
5
5
  * wire shapes through pi-ai's canonical {@link Context}, this module accepts
6
6
  * the canonical shape *directly* — for clients that already speak pi-ai
7
- * (containerized skc and roboskc's sidecar auth-gateway).
7
+ * (containerized SKC deployments and sidecar auth gateways).
8
8
  * Skipping the wire-format → Context → wire-format round-trip cuts
9
9
  * per-request CPU but, more importantly, avoids the quantization that those
10
10
  * translations impose on first-class pi-ai fields (service tier, cache
11
11
  * markers, thinking budgets, tool-choice variants, …).
12
12
  *
13
- * The streaming wire is {@link AssistantMessageEvent} serialized verbatim and
14
- * SSE-framed. Same type pi-ai already produces internally; the client feeds
15
- * each parsed event straight into `AssistantMessageEventStream.push()` with
16
- * no translation. Including `partial: AssistantMessage` on every delta is
17
- * O(N²) in turn length on the wire — acceptable for the loopback / sidecar
18
- * topology this transport is designed for; provider latency dominates the
19
- * actual cost.
13
+ * The streaming wire is {@link AssistantMessageEvent} serialized as SSE. Public
14
+ * projections omit private raw reasoning and serialized Responses reasoning
15
+ * signatures while preserving provider-displayable summaries and genuine opaque
16
+ * signatures. Including `partial: AssistantMessage` on every delta is O(N²) in
17
+ * turn length on the wire — acceptable for the loopback / sidecar topology this
18
+ * transport is designed for; provider latency dominates the actual cost.
20
19
  *
21
20
  * Endpoint contract:
22
21
  * POST /v1/pi/stream
@@ -25,7 +24,14 @@
25
24
  * 200 JSON (stream=false): { message: AssistantMessage }
26
25
  * 4xx/5xx: { error: { type, message } }
27
26
  */
28
- import type { AssistantMessageEventStream, Context, SimpleStreamOptions } from "../types";
27
+ import type {
28
+ AssistantMessage,
29
+ AssistantMessageEvent,
30
+ AssistantMessageEventStream,
31
+ Context,
32
+ SimpleStreamOptions,
33
+ ThinkingContent,
34
+ } from "../types";
29
35
 
30
36
  export interface PiNativeParsedRequest {
31
37
  modelId: string;
@@ -56,6 +62,7 @@ const ALLOWED_OPTION_KEYS: ReadonlySet<keyof SimpleStreamOptions> = new Set([
56
62
  "headers",
57
63
  "initiatorOverride",
58
64
  "maxRetryDelayMs",
65
+ "fallbackManaged",
59
66
  "metadata",
60
67
  "sessionId",
61
68
  "streamFirstEventTimeoutMs",
@@ -147,25 +154,272 @@ export function parseRequest(body: unknown, _headers?: Headers): PiNativeParsedR
147
154
  const SSE_ENCODER = new TextEncoder();
148
155
  const SSE_DONE = SSE_ENCODER.encode("data: [DONE]\n\n");
149
156
 
157
+ function isSerializedResponsesReasoningItem(signature: string): boolean {
158
+ try {
159
+ const parsed: unknown = JSON.parse(signature);
160
+ return (
161
+ typeof parsed === "object" &&
162
+ parsed !== null &&
163
+ !Array.isArray(parsed) &&
164
+ (parsed as { type?: unknown }).type === "reasoning"
165
+ );
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+
150
171
  /**
151
- * Ship every {@link AssistantMessageEvent} verbatim, SSE-framed.
152
- *
153
- * No per-event re-shaping: the pi-native client is pi-ai itself, so the
154
- * canonical event type IS the wire type. Including the rolling
155
- * `partial: AssistantMessage` on every delta is quadratic in turn length
156
- * on the wire, but for the loopback / sidecar topology this transport
157
- * targets (containerized skc → host gateway, roboskc slot → skc-auth-gateway
158
- * sidecar) the bandwidth cost is negligible compared to provider latency —
159
- * and the client gets to feed the events straight into its existing
160
- * `AssistantMessageEventStream.push()` plumbing with zero translation.
172
+ * Clone a thinking block for public transport. Raw reasoning is private: omit
173
+ * raw-only blocks, retain only the displayable summary for mixed blocks, and
174
+ * never forward a serialized Responses reasoning item as a signature.
175
+ */
176
+ function isResponsesFamilyApi(api: AssistantMessage["api"]): boolean {
177
+ return api === "openai-responses" || api === "openai-codex-responses";
178
+ }
179
+
180
+ function sanitizeThinking(content: ThinkingContent, api: AssistantMessage["api"]): ThinkingContent | undefined {
181
+ if (isResponsesFamilyApi(api) && content.provenance === undefined) return undefined;
182
+ if (content.provenance === "raw") return undefined;
183
+
184
+ let thinking: string;
185
+ if (content.provenance === "mixed") {
186
+ if (content.summaryText === undefined) return undefined;
187
+ thinking = content.summaryText;
188
+ } else {
189
+ thinking = content.provenance === "summary" ? (content.summaryText ?? content.thinking) : content.thinking;
190
+ }
191
+ const signature =
192
+ content.thinkingSignature && isSerializedResponsesReasoningItem(content.thinkingSignature)
193
+ ? undefined
194
+ : content.thinkingSignature;
195
+ const { rawText: _rawText, thinkingSignature: _thinkingSignature, ...rest } = content;
196
+ return signature === undefined ? { ...rest, thinking } : { ...rest, thinking, thinkingSignature: signature };
197
+ }
198
+
199
+ function sanitizeMessage(message: AssistantMessage): AssistantMessage {
200
+ let changed = false;
201
+ const content: AssistantMessage["content"] = [];
202
+ for (const part of message.content) {
203
+ if (part.type !== "thinking") {
204
+ content.push(part);
205
+ continue;
206
+ }
207
+ const needsSanitizing =
208
+ (isResponsesFamilyApi(message.api) && part.provenance === undefined) ||
209
+ part.provenance !== undefined ||
210
+ part.rawText !== undefined ||
211
+ (part.thinkingSignature !== undefined && isSerializedResponsesReasoningItem(part.thinkingSignature));
212
+ if (!needsSanitizing) {
213
+ content.push(part);
214
+ continue;
215
+ }
216
+ const sanitized = sanitizeThinking(part, message.api);
217
+ changed = true;
218
+ if (sanitized !== undefined) content.push(sanitized);
219
+ }
220
+ return changed ? { ...message, content } : message;
221
+ }
222
+
223
+ function hasRawOrMixedThinking(partial: AssistantMessage, contentIndex: number): boolean {
224
+ const content = partial.content[contentIndex];
225
+ return content?.type === "thinking" && (content.provenance === "raw" || content.provenance === "mixed");
226
+ }
227
+
228
+ type ThinkingEvent = Extract<AssistantMessageEvent, { type: "thinking_start" | "thinking_delta" | "thinking_end" }>;
229
+
230
+ interface BufferedThinkingEvent {
231
+ event: ThinkingEvent;
232
+ sequence: number;
233
+ }
234
+
235
+ function isFinalSafeThinking(partial: AssistantMessage, contentIndex: number): boolean {
236
+ const content = partial.content[contentIndex];
237
+ return (
238
+ content?.type === "thinking" &&
239
+ (!isResponsesFamilyApi(partial.api) || content.provenance !== undefined) &&
240
+ !hasRawOrMixedThinking(partial, contentIndex)
241
+ );
242
+ }
243
+
244
+ function maskBufferedThinking(message: AssistantMessage, contentIndexes: ReadonlySet<number>): AssistantMessage {
245
+ let changed = false;
246
+ const content = message.content.map((part, contentIndex) => {
247
+ if (!contentIndexes.has(contentIndex) || part.type !== "thinking") return part;
248
+ changed = true;
249
+ return { type: "thinking" as const, thinking: "", ...(part.itemId ? { itemId: part.itemId } : {}) };
250
+ });
251
+ return changed ? { ...message, content } : message;
252
+ }
253
+
254
+ function maskBufferedThinkingInEvent(
255
+ event: AssistantMessageEvent,
256
+ contentIndexes: ReadonlySet<number>,
257
+ ): AssistantMessageEvent {
258
+ if (contentIndexes.size === 0) return event;
259
+ switch (event.type) {
260
+ case "done":
261
+ case "error":
262
+ case "toolChoiceIncapability":
263
+ return event;
264
+ case "start":
265
+ case "text_start":
266
+ case "text_delta":
267
+ case "text_end":
268
+ case "thinking_start":
269
+ case "thinking_delta":
270
+ case "thinking_end":
271
+ case "reasoning_summary_start":
272
+ case "reasoning_summary_delta":
273
+ case "reasoning_summary_end":
274
+ case "toolcall_start":
275
+ case "toolcall_delta":
276
+ case "toolcall_end":
277
+ return { ...event, partial: maskBufferedThinking(event.partial, contentIndexes) };
278
+ }
279
+ }
280
+
281
+ function withSummaryPartial<
282
+ T extends Extract<
283
+ AssistantMessageEvent,
284
+ { type: "reasoning_summary_start" | "reasoning_summary_delta" | "reasoning_summary_end" }
285
+ >,
286
+ >(event: T, contentIndexes: ReadonlySet<number>, summaryText: string): T {
287
+ const partial = maskBufferedThinking(event.partial, contentIndexes);
288
+ const content = [...partial.content];
289
+ const original = event.partial.content[event.contentIndex];
290
+ content[event.contentIndex] = {
291
+ type: "thinking",
292
+ thinking: summaryText,
293
+ provenance: "summary",
294
+ summaryText,
295
+ ...(original?.type === "thinking" && original.itemId ? { itemId: original.itemId } : {}),
296
+ };
297
+ return { ...event, partial: { ...partial, content } };
298
+ }
299
+
300
+ function sanitizeEvent(event: AssistantMessageEvent): AssistantMessageEvent | undefined {
301
+ switch (event.type) {
302
+ case "done":
303
+ return { ...event, message: sanitizeMessage(event.message) };
304
+ case "error":
305
+ return { ...event, error: sanitizeMessage(event.error) };
306
+ case "toolChoiceIncapability":
307
+ return event;
308
+ case "thinking_start":
309
+ case "thinking_delta":
310
+ case "thinking_end":
311
+ return hasRawOrMixedThinking(event.partial, event.contentIndex)
312
+ ? undefined
313
+ : { ...event, partial: sanitizeMessage(event.partial) };
314
+ case "start":
315
+ case "text_start":
316
+ case "text_delta":
317
+ case "text_end":
318
+ case "reasoning_summary_start":
319
+ case "reasoning_summary_delta":
320
+ case "reasoning_summary_end":
321
+ case "toolcall_start":
322
+ case "toolcall_delta":
323
+ case "toolcall_end":
324
+ return { ...event, partial: sanitizeMessage(event.partial) };
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Ship only public-safe {@link AssistantMessageEvent} projections. Unknown
330
+ * thinking blocks remain buffered until their terminal partial establishes that
331
+ * the provider-native block is safe; raw and mixed blocks never reach SSE.
161
332
  */
162
333
  export function encodeStream(events: AssistantMessageEventStream): ReadableStream<Uint8Array> {
163
334
  return new ReadableStream<Uint8Array>({
164
335
  async start(controller) {
336
+ const bufferedThinking = new Map<number, BufferedThinkingEvent[]>();
337
+ const summaryTextByIndex = new Map<number, string>();
338
+ let sequence = 0;
339
+ const write = (event: AssistantMessageEvent): void => {
340
+ const sanitized = sanitizeEvent(event);
341
+ if (sanitized !== undefined) {
342
+ controller.enqueue(SSE_ENCODER.encode(`data: ${JSON.stringify(sanitized)}\n\n`));
343
+ }
344
+ };
345
+ const emit = (event: AssistantMessageEvent): void => {
346
+ write(maskBufferedThinkingInEvent(event, new Set(bufferedThinking.keys())));
347
+ };
348
+ const flush = (buffered: BufferedThinkingEvent[]): void => {
349
+ for (const { event } of buffered.sort((a, b) => a.sequence - b.sequence)) emit(event);
350
+ };
351
+ const resolveBufferedThinking = (final: AssistantMessage): void => {
352
+ const ready: BufferedThinkingEvent[] = [];
353
+ for (const [contentIndex, buffered] of bufferedThinking) {
354
+ if (isFinalSafeThinking(final, contentIndex)) ready.push(...buffered);
355
+ }
356
+ bufferedThinking.clear();
357
+ flush(ready);
358
+ };
165
359
  try {
166
360
  for await (const event of events) {
167
- controller.enqueue(SSE_ENCODER.encode(`data: ${JSON.stringify(event)}\n\n`));
168
- if (event.type === "done" || event.type === "error") break;
361
+ switch (event.type) {
362
+ case "thinking_start":
363
+ case "thinking_delta": {
364
+ if (hasRawOrMixedThinking(event.partial, event.contentIndex)) {
365
+ bufferedThinking.delete(event.contentIndex);
366
+ break;
367
+ }
368
+ const buffered = bufferedThinking.get(event.contentIndex) ?? [];
369
+ buffered.push({ event, sequence: sequence++ });
370
+ bufferedThinking.set(event.contentIndex, buffered);
371
+ break;
372
+ }
373
+ case "thinking_end": {
374
+ const buffered = bufferedThinking.get(event.contentIndex) ?? [];
375
+ bufferedThinking.delete(event.contentIndex);
376
+ if (!isFinalSafeThinking(event.partial, event.contentIndex)) break;
377
+ buffered.push({ event, sequence: sequence++ });
378
+ flush(buffered);
379
+ break;
380
+ }
381
+ case "done":
382
+ resolveBufferedThinking(event.message);
383
+ summaryTextByIndex.clear();
384
+ emit(event);
385
+ controller.enqueue(SSE_DONE);
386
+ controller.close();
387
+ return;
388
+ case "error":
389
+ resolveBufferedThinking(event.error);
390
+ summaryTextByIndex.clear();
391
+ emit(event);
392
+ controller.enqueue(SSE_DONE);
393
+ controller.close();
394
+ return;
395
+ case "reasoning_summary_start": {
396
+ summaryTextByIndex.set(event.contentIndex, "");
397
+ write(withSummaryPartial(event, new Set(bufferedThinking.keys()), ""));
398
+ break;
399
+ }
400
+ case "reasoning_summary_delta": {
401
+ const summaryText = `${summaryTextByIndex.get(event.contentIndex) ?? ""}${event.delta}`;
402
+ summaryTextByIndex.set(event.contentIndex, summaryText);
403
+ write(withSummaryPartial(event, new Set(bufferedThinking.keys()), summaryText));
404
+ break;
405
+ }
406
+ case "reasoning_summary_end": {
407
+ const summaryText = event.content || summaryTextByIndex.get(event.contentIndex) || "";
408
+ summaryTextByIndex.delete(event.contentIndex);
409
+ write(withSummaryPartial(event, new Set(bufferedThinking.keys()), summaryText));
410
+ break;
411
+ }
412
+ case "start":
413
+ case "text_start":
414
+ case "text_delta":
415
+ case "text_end":
416
+ case "toolcall_start":
417
+ case "toolcall_delta":
418
+ case "toolcall_end":
419
+ case "toolChoiceIncapability":
420
+ emit(event);
421
+ break;
422
+ }
169
423
  }
170
424
  controller.enqueue(SSE_DONE);
171
425
  controller.close();
package/src/types.ts CHANGED
@@ -28,6 +28,7 @@ import type { OpenAICodexResponsesOptions } from "./providers/openai-codex-respo
28
28
  import type { OpenAICompletionsOptions } from "./providers/openai-completions";
29
29
  import type { OpenAIResponsesOptions } from "./providers/openai-responses";
30
30
  import type { AssistantMessageEventStream } from "./utils/event-stream";
31
+ import type { FallbackAttemptToken, TransportFailureFacts } from "./utils/fallback-transport";
31
32
 
32
33
  export type { AssistantMessageEventStream } from "./utils/event-stream";
33
34
 
@@ -77,6 +78,23 @@ export type ThinkingControlMode =
77
78
  | "anthropic-adaptive"
78
79
  | "anthropic-budget-effort";
79
80
 
81
+ /** Canonical runtime vocabulary for provider thinking transports. */
82
+ export const THINKING_CONTROL_MODES = [
83
+ "effort",
84
+ "budget",
85
+ "google-level",
86
+ "anthropic-adaptive",
87
+ "anthropic-budget-effort",
88
+ ] as const satisfies readonly ThinkingControlMode[];
89
+
90
+ type _CheckThinkingControlModes = [
91
+ Exclude<ThinkingControlMode, (typeof THINKING_CONTROL_MODES)[number]>,
92
+ Exclude<(typeof THINKING_CONTROL_MODES)[number], ThinkingControlMode>,
93
+ ] extends [never, never]
94
+ ? true
95
+ : false;
96
+ true satisfies _CheckThinkingControlModes;
97
+
80
98
  /** Per-model thinking capabilities used to clamp and map user-facing effort levels. */
81
99
  export interface ThinkingConfig {
82
100
  /** Least intensive supported user-facing effort level. */
@@ -306,6 +324,10 @@ export interface StreamOptions {
306
324
  maxTokens?: number;
307
325
  signal?: AbortSignal;
308
326
  apiKey?: string;
327
+ /** Disables all transport-level replay; the fallback controller owns retries. */
328
+ fallbackManaged?: boolean;
329
+ /** Opaque token returned by beginAttempt for a managed transport invocation. */
330
+ fallbackAttempt?: FallbackAttemptToken;
309
331
  /**
310
332
  * Called when a provider returns 401 before any replay-unsafe assistant
311
333
  * event has been emitted. Returning a different key retries the provider
@@ -466,6 +488,9 @@ export interface ThinkingContent {
466
488
  thinking: string;
467
489
  thinkingSignature?: string; // e.g., for OpenAI responses, the reasoning item ID
468
490
  itemId?: string; // item.id from output_item.added, used to match output_item.done
491
+ readonly provenance?: "summary" | "raw" | "mixed";
492
+ readonly summaryText?: string;
493
+ readonly rawText?: string;
469
494
  }
470
495
 
471
496
  export interface RedactedThinkingContent {
@@ -598,6 +623,8 @@ export interface AssistantMessage {
598
623
  errorKind?: AssistantErrorKind;
599
624
  /** HTTP status surfaced by the provider when the request failed. Populated by every provider's catch block alongside `errorMessage` so consumers (auth retry, telemetry, UI) can branch without regex-scraping the message. */
600
625
  errorStatus?: number;
626
+ /** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
627
+ transportFailure?: TransportFailureFacts;
601
628
  /**
602
629
  * Stable identifiers for request features the provider silently dropped
603
630
  * during this turn (e.g. `"priority"`). Set when a server-side rejection
@@ -682,10 +709,17 @@ export type TSchema = ZodType | TJsonSchema;
682
709
  /** Resolve parameter types for tool execution / handlers. */
683
710
  export type Static<S> = S extends ZodType ? z.infer<S> : S extends { static: infer T } ? T : unknown;
684
711
 
712
+ export type RawArgumentValidationResult =
713
+ | { outcome: "passthrough" }
714
+ | { outcome: "accept"; arguments: ToolCall["arguments"] }
715
+ | { outcome: "reject" };
716
+
685
717
  export interface Tool<TParameters extends TSchema = TSchema> {
686
718
  name: string;
687
719
  description: string;
688
720
  parameters: TParameters;
721
+ /** Optional pre-coercion adapter for narrowly scoped raw argument recovery or rejection. */
722
+ rawArgumentValidation?: (arguments_: ToolCall["arguments"]) => RawArgumentValidationResult;
689
723
  /** If true, tool is strictly typed and validated against the parameters schema before execution */
690
724
  strict?: boolean;
691
725
  /**
@@ -705,6 +739,13 @@ export interface Tool<TParameters extends TSchema = TSchema> {
705
739
  * calls route correctly. Absent for regular JSON function tools.
706
740
  */
707
741
  customWireName?: string;
742
+ /**
743
+ * Optional safe projection for tool arguments or results. Extensions use this
744
+ * only for explicitly opt-in, display-safe summaries.
745
+ */
746
+ safeSummary?: (kind: "args" | "result", value: unknown) => string | undefined;
747
+ /** Allowlisted argument/result field names for a safe fallback summary. */
748
+ safeSummaryFields?: { args?: string[]; result?: string[] };
708
749
  }
709
750
 
710
751
  export interface Context {
@@ -721,6 +762,9 @@ export type AssistantMessageEvent =
721
762
  | { type: "thinking_start"; contentIndex: number; partial: AssistantMessage }
722
763
  | { type: "thinking_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
723
764
  | { type: "thinking_end"; contentIndex: number; content: string; partial: AssistantMessage }
765
+ | { type: "reasoning_summary_start"; contentIndex: number; partial: AssistantMessage }
766
+ | { type: "reasoning_summary_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
767
+ | { type: "reasoning_summary_end"; contentIndex: number; content: string; partial: AssistantMessage }
724
768
  | { type: "toolcall_start"; contentIndex: number; partial: AssistantMessage }
725
769
  | { type: "toolcall_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
726
770
  | { type: "toolcall_end"; contentIndex: number; toolCall: ToolCall; partial: AssistantMessage }
@@ -865,6 +909,12 @@ export interface AnthropicCompat extends ToolChoiceCompat {
865
909
  supportsForcedToolChoice?: boolean;
866
910
  /** Whether long prompt-cache retention (`ttl: "1h"`) is supported. Default: true for canonical Anthropic API. */
867
911
  supportsLongCacheRetention?: boolean;
912
+ /**
913
+ * Prompt-cache transport accepted by this Anthropic-compatible endpoint.
914
+ * Canonical Anthropic defaults to `"automatic"`; noncanonical endpoints default
915
+ * to `"none"` and must explicitly opt into generated `"explicit"` markers.
916
+ */
917
+ promptCacheMode?: "none" | "explicit" | "automatic";
868
918
  }
869
919
 
870
920
  /**
@@ -939,10 +989,9 @@ export interface Model<TApi extends Api = any> {
939
989
  * (or compatible) host; `headers.Authorization` (or `apiKey` resolved by
940
990
  * the registry) carries the gateway bearer.
941
991
  *
942
- * Used by containerized skc installs (e.g. roboskc slots) to route every
943
- * LLM call through a sidecar gateway that holds the real provider
944
- * credentials. The model's other metadata (pricing, context window,
945
- * thinking config, …) still resolves locally; only the streaming
992
+ * Used by containerized SKC installs to route every LLM call through a
993
+ * sidecar gateway that holds the real provider credentials. The model's other
994
+ * metadata (pricing, context window, thinking config, …) still resolves locally; only the streaming
946
995
  * dispatch is redirected.
947
996
  */
948
997
  transport?: "pi-native";
@@ -1,10 +1,10 @@
1
1
  import * as z from "zod/v4";
2
+ import { resolveCodexGpt56DiscoveryContext } from "../../context-cap-policy";
2
3
  import { CODEX_BASE_URL, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "../../providers/openai-codex/constants";
3
4
  import type { Model } from "../../types";
4
5
  import { isRecord } from "../../utils";
5
6
 
6
7
  const DEFAULT_MODEL_LIST_PATHS = ["/codex/models", "/models"] as const;
7
- const DEFAULT_CONTEXT_WINDOW = 272_000;
8
8
  const DEFAULT_MAX_TOKENS = 128_000;
9
9
  const DEFAULT_CODEX_CLIENT_VERSION = "0.99.0";
10
10
  const NPM_CODEX_LATEST_URL = "https://registry.npmjs.org/@openai%2Fcodex/latest";
@@ -258,7 +258,8 @@ function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCo
258
258
  }
259
259
 
260
260
  const name = toNonEmptyString(payload.display_name) ?? slug;
261
- const contextWindow = toPositiveInt(payload.context_window) ?? DEFAULT_CONTEXT_WINDOW;
261
+ const modelIdentity = { id: slug, api: "openai-codex-responses", provider: "openai-codex" } as const;
262
+ const contextWindow = resolveCodexGpt56DiscoveryContext(modelIdentity, payload.context_window);
262
263
  const maxTokens = Math.min(DEFAULT_MAX_TOKENS, contextWindow);
263
264
  const reasoning = supportsReasoning(payload.default_reasoning_level, payload.supported_reasoning_levels);
264
265
  const input = normalizeInputModalities(payload.input_modalities);
@@ -346,16 +347,6 @@ function toNonEmptyString(value: unknown): string | null {
346
347
  return trimmed.length > 0 ? trimmed : null;
347
348
  }
348
349
 
349
- function toPositiveInt(value: unknown): number | null {
350
- if (typeof value !== "number" || !Number.isFinite(value)) {
351
- return null;
352
- }
353
- if (value <= 0) {
354
- return null;
355
- }
356
- return Math.trunc(value);
357
- }
358
-
359
350
  function toFiniteNumber(value: unknown): number | null {
360
351
  if (typeof value !== "number" || !Number.isFinite(value)) {
361
352
  return null;