@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.
- package/package.json +24 -25
- package/src/auth-gateway/server.ts +164 -22
- package/src/auth-storage.ts +76 -43
- package/src/context-cap-policy.ts +59 -0
- package/src/index.ts +2 -0
- package/src/model-cache.ts +10 -0
- package/src/model-manager.ts +30 -17
- package/src/model-thinking.ts +14 -8
- package/src/providers/amazon-bedrock.ts +9 -1
- package/src/providers/anthropic-messages-server.ts +178 -17
- package/src/providers/anthropic.ts +199 -213
- package/src/providers/azure-openai-responses.ts +6 -1
- package/src/providers/google-gemini-cli.ts +26 -13
- package/src/providers/google-shared.ts +7 -1
- package/src/providers/ollama.ts +7 -1
- package/src/providers/openai-chat-server.ts +93 -5
- package/src/providers/openai-codex/response-handler.ts +11 -3
- package/src/providers/openai-codex-responses.ts +107 -20
- package/src/providers/openai-responses-server.ts +111 -36
- package/src/providers/openai-responses-shared.ts +71 -12
- package/src/providers/openai-responses.ts +26 -3
- package/src/providers/pi-native-client.ts +24 -12
- package/src/providers/pi-native-server.ts +275 -21
- package/src/types.ts +53 -4
- package/src/utils/discovery/codex.ts +3 -12
- package/src/utils/event-stream.ts +146 -58
- package/src/utils/fallback-transport.ts +269 -0
- package/src/utils/overflow.ts +72 -31
- package/src/utils/retry.ts +2 -2
- package/src/utils/validation.ts +6 -1
- package/src/utils.ts +120 -12
- package/dist/types/api-registry.d.ts +0 -30
- package/dist/types/auth-broker/client.d.ts +0 -67
- package/dist/types/auth-broker/index.d.ts +0 -5
- package/dist/types/auth-broker/refresher.d.ts +0 -25
- package/dist/types/auth-broker/remote-store.d.ts +0 -99
- package/dist/types/auth-broker/server.d.ts +0 -32
- package/dist/types/auth-broker/types.d.ts +0 -110
- package/dist/types/auth-broker/wire-schemas.d.ts +0 -443
- package/dist/types/auth-gateway/http.d.ts +0 -40
- package/dist/types/auth-gateway/index.d.ts +0 -3
- package/dist/types/auth-gateway/server.d.ts +0 -17
- package/dist/types/auth-gateway/types.d.ts +0 -115
- package/dist/types/auth-storage.d.ts +0 -695
- package/dist/types/cli.d.ts +0 -2
- package/dist/types/index.d.ts +0 -51
- package/dist/types/model-cache.d.ts +0 -17
- package/dist/types/model-manager.d.ts +0 -62
- package/dist/types/model-retirements.d.ts +0 -6
- package/dist/types/model-thinking.d.ts +0 -74
- package/dist/types/models.d.ts +0 -21
- package/dist/types/provider-details.d.ts +0 -24
- package/dist/types/provider-models/bundled-references.d.ts +0 -4
- package/dist/types/provider-models/descriptors.d.ts +0 -48
- package/dist/types/provider-models/google.d.ts +0 -20
- package/dist/types/provider-models/index.d.ts +0 -5
- package/dist/types/provider-models/ollama.d.ts +0 -7
- package/dist/types/provider-models/openai-compat.d.ts +0 -256
- package/dist/types/provider-models/special.d.ts +0 -19
- package/dist/types/providers/amazon-bedrock.d.ts +0 -60
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -450
- package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
- package/dist/types/providers/anthropic.d.ts +0 -208
- package/dist/types/providers/aws-credential-config.d.ts +0 -19
- package/dist/types/providers/aws-credentials.d.ts +0 -43
- package/dist/types/providers/aws-eventstream.d.ts +0 -38
- package/dist/types/providers/aws-sigv4.d.ts +0 -55
- package/dist/types/providers/azure-openai-responses.d.ts +0 -15
- package/dist/types/providers/composer-discipline.d.ts +0 -26
- package/dist/types/providers/cursor/client-version.d.ts +0 -10
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +0 -13022
- package/dist/types/providers/cursor.d.ts +0 -45
- package/dist/types/providers/error-message.d.ts +0 -27
- package/dist/types/providers/github-copilot-headers.d.ts +0 -40
- package/dist/types/providers/gitlab-duo.d.ts +0 -27
- package/dist/types/providers/google-auth.d.ts +0 -24
- package/dist/types/providers/google-gemini-cli.d.ts +0 -75
- package/dist/types/providers/google-gemini-headers.d.ts +0 -43
- package/dist/types/providers/google-shared.d.ts +0 -179
- package/dist/types/providers/google-types.d.ts +0 -138
- package/dist/types/providers/google-vertex.d.ts +0 -7
- package/dist/types/providers/google.d.ts +0 -4
- package/dist/types/providers/grammar.d.ts +0 -1
- package/dist/types/providers/kimi.d.ts +0 -27
- package/dist/types/providers/mock.d.ts +0 -177
- package/dist/types/providers/ollama.d.ts +0 -41
- package/dist/types/providers/openai-anthropic-shim.d.ts +0 -31
- package/dist/types/providers/openai-bounded-rate-limits.d.ts +0 -3
- package/dist/types/providers/openai-chat-server-schema.d.ts +0 -815
- package/dist/types/providers/openai-chat-server.d.ts +0 -16
- package/dist/types/providers/openai-codex/constants.d.ts +0 -26
- package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -50
- package/dist/types/providers/openai-codex/response-handler.d.ts +0 -17
- package/dist/types/providers/openai-codex-responses.d.ts +0 -67
- package/dist/types/providers/openai-completions-compat.d.ts +0 -27
- package/dist/types/providers/openai-completions.d.ts +0 -33
- package/dist/types/providers/openai-request-transform.d.ts +0 -4
- package/dist/types/providers/openai-responses-server-schema.d.ts +0 -392
- package/dist/types/providers/openai-responses-server.d.ts +0 -17
- package/dist/types/providers/openai-responses-shared.d.ts +0 -104
- package/dist/types/providers/openai-responses.d.ts +0 -32
- package/dist/types/providers/pi-native-client.d.ts +0 -13
- package/dist/types/providers/pi-native-server.d.ts +0 -68
- package/dist/types/providers/register-builtins.d.ts +0 -31
- package/dist/types/providers/synthetic.d.ts +0 -26
- package/dist/types/providers/transform-messages.d.ts +0 -14
- package/dist/types/providers/vision-guard.d.ts +0 -8
- package/dist/types/rate-limit-utils.d.ts +0 -19
- package/dist/types/stream.d.ts +0 -43
- package/dist/types/types.d.ts +0 -831
- package/dist/types/usage/claude.d.ts +0 -3
- package/dist/types/usage/gemini.d.ts +0 -2
- package/dist/types/usage/github-copilot.d.ts +0 -7
- package/dist/types/usage/google-antigravity.d.ts +0 -2
- package/dist/types/usage/grok-cli.d.ts +0 -10
- package/dist/types/usage/kimi.d.ts +0 -2
- package/dist/types/usage/minimax-code.d.ts +0 -2
- package/dist/types/usage/openai-codex.d.ts +0 -3
- package/dist/types/usage/shared.d.ts +0 -1
- package/dist/types/usage/zai.d.ts +0 -2
- package/dist/types/usage.d.ts +0 -258
- package/dist/types/utils/abort.d.ts +0 -19
- package/dist/types/utils/anthropic-auth.d.ts +0 -31
- package/dist/types/utils/discovery/antigravity.d.ts +0 -67
- package/dist/types/utils/discovery/codex.d.ts +0 -38
- package/dist/types/utils/discovery/cursor.d.ts +0 -23
- package/dist/types/utils/discovery/gemini.d.ts +0 -25
- package/dist/types/utils/discovery/index.d.ts +0 -4
- package/dist/types/utils/discovery/openai-compatible.d.ts +0 -74
- package/dist/types/utils/event-stream.d.ts +0 -33
- package/dist/types/utils/fireworks-model-id.d.ts +0 -10
- package/dist/types/utils/foundry.d.ts +0 -1
- package/dist/types/utils/h2-fetch.d.ts +0 -22
- package/dist/types/utils/http-inspector.d.ts +0 -35
- package/dist/types/utils/idle-iterator.d.ts +0 -67
- package/dist/types/utils/json-parse.d.ts +0 -18
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
- package/dist/types/utils/oauth/anthropic.d.ts +0 -22
- package/dist/types/utils/oauth/api-key-login.d.ts +0 -35
- package/dist/types/utils/oauth/api-key-validation.d.ts +0 -27
- package/dist/types/utils/oauth/callback-server.d.ts +0 -60
- package/dist/types/utils/oauth/cerebras.d.ts +0 -1
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/cursor.d.ts +0 -15
- package/dist/types/utils/oauth/deepinfra.d.ts +0 -1
- package/dist/types/utils/oauth/deepseek.d.ts +0 -10
- package/dist/types/utils/oauth/firepass.d.ts +0 -1
- package/dist/types/utils/oauth/fireworks.d.ts +0 -1
- package/dist/types/utils/oauth/fugu.d.ts +0 -1
- package/dist/types/utils/oauth/github-copilot.d.ts +0 -38
- package/dist/types/utils/oauth/gitlab-duo.d.ts +0 -3
- package/dist/types/utils/oauth/glm-zcode.d.ts +0 -71
- package/dist/types/utils/oauth/google-antigravity.d.ts +0 -11
- package/dist/types/utils/oauth/google-gemini-cli.d.ts +0 -10
- package/dist/types/utils/oauth/google-oauth-shared.d.ts +0 -28
- package/dist/types/utils/oauth/huggingface.d.ts +0 -19
- package/dist/types/utils/oauth/index.d.ts +0 -39
- package/dist/types/utils/oauth/kagi.d.ts +0 -17
- package/dist/types/utils/oauth/kilo.d.ts +0 -5
- package/dist/types/utils/oauth/kimi.d.ts +0 -21
- package/dist/types/utils/oauth/litellm.d.ts +0 -18
- package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
- package/dist/types/utils/oauth/minimax-code.d.ts +0 -28
- package/dist/types/utils/oauth/moonshot.d.ts +0 -1
- package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
- package/dist/types/utils/oauth/nvidia.d.ts +0 -18
- package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
- package/dist/types/utils/oauth/ollama.d.ts +0 -18
- package/dist/types/utils/oauth/openai-codex.d.ts +0 -21
- package/dist/types/utils/oauth/opencode.d.ts +0 -18
- package/dist/types/utils/oauth/parallel.d.ts +0 -17
- package/dist/types/utils/oauth/perplexity.d.ts +0 -9
- package/dist/types/utils/oauth/pkce.d.ts +0 -8
- package/dist/types/utils/oauth/qianfan.d.ts +0 -17
- package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
- package/dist/types/utils/oauth/synthetic.d.ts +0 -1
- package/dist/types/utils/oauth/tavily.d.ts +0 -17
- package/dist/types/utils/oauth/together.d.ts +0 -1
- package/dist/types/utils/oauth/types.d.ts +0 -45
- package/dist/types/utils/oauth/venice.d.ts +0 -18
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/vllm.d.ts +0 -16
- package/dist/types/utils/oauth/xai.d.ts +0 -30
- package/dist/types/utils/oauth/xiaomi.d.ts +0 -25
- package/dist/types/utils/oauth/zai.d.ts +0 -18
- package/dist/types/utils/oauth/zenmux.d.ts +0 -1
- package/dist/types/utils/overflow.d.ts +0 -64
- package/dist/types/utils/parse-bind.d.ts +0 -23
- package/dist/types/utils/provider-response.d.ts +0 -3
- package/dist/types/utils/retry-after.d.ts +0 -3
- package/dist/types/utils/retry-budget.d.ts +0 -1
- package/dist/types/utils/retry.d.ts +0 -26
- package/dist/types/utils/schema/adapt.d.ts +0 -24
- package/dist/types/utils/schema/compatibility.d.ts +0 -30
- package/dist/types/utils/schema/dereference.d.ts +0 -11
- package/dist/types/utils/schema/draft.d.ts +0 -10
- package/dist/types/utils/schema/equality.d.ts +0 -4
- package/dist/types/utils/schema/fields.d.ts +0 -49
- package/dist/types/utils/schema/index.d.ts +0 -14
- package/dist/types/utils/schema/json-schema-validator.d.ts +0 -12
- package/dist/types/utils/schema/meta-validator.d.ts +0 -2
- package/dist/types/utils/schema/normalize.d.ts +0 -93
- package/dist/types/utils/schema/root-combinator.d.ts +0 -12
- package/dist/types/utils/schema/spill.d.ts +0 -8
- package/dist/types/utils/schema/stamps.d.ts +0 -25
- package/dist/types/utils/schema/types.d.ts +0 -4
- package/dist/types/utils/schema/wire.d.ts +0 -54
- package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
- package/dist/types/utils/sse-debug.d.ts +0 -10
- package/dist/types/utils/tool-call-healing.d.ts +0 -71
- package/dist/types/utils/tool-choice-capability.d.ts +0 -41
- package/dist/types/utils/tool-choice.d.ts +0 -50
- package/dist/types/utils/validation.d.ts +0 -17
- 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
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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 {
|
|
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
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
|
943
|
-
*
|
|
944
|
-
*
|
|
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
|
|
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;
|