@sayknow-cli/ai 0.3.16 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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
package/src/model-cache.ts
CHANGED
|
@@ -66,6 +66,16 @@ function getDb(dbPath?: string): Database {
|
|
|
66
66
|
return db;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** Close the shared cache only when it owns the exact requested database path. */
|
|
70
|
+
export function closeModelCache(dbPath?: string): boolean {
|
|
71
|
+
const resolvedPath = dbPath ?? getModelDbPath();
|
|
72
|
+
if (!sharedDb || sharedDbPath !== resolvedPath) return false;
|
|
73
|
+
sharedDb.close();
|
|
74
|
+
sharedDb = null;
|
|
75
|
+
sharedDbPath = null;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
function migrateCacheSchema(db: Database): void {
|
|
70
80
|
const columns = db.prepare("PRAGMA table_info(model_cache)").all() as TableInfoRow[];
|
|
71
81
|
if (!columns.some(column => column.name === "static_fingerprint")) {
|
package/src/model-manager.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyFinalCodexGpt56ContextCap } from "./context-cap-policy";
|
|
1
2
|
import { readModelCache, writeModelCache } from "./model-cache";
|
|
2
3
|
import { isRetiredModel, isRetiredModelKey } from "./model-retirements";
|
|
3
4
|
import { applyGeneratedModelPolicies, enrichModelThinking } from "./model-thinking";
|
|
@@ -40,6 +41,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
40
41
|
modelsDev?: ModelsDevFallback<TApi, TModelsDevPayload>;
|
|
41
42
|
/** Clock override for deterministic tests. */
|
|
42
43
|
now?: () => number;
|
|
44
|
+
/** Optional guard that must permit cache publication. Default: writes are permitted. */
|
|
45
|
+
canPublishCache?: () => boolean;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
@@ -98,7 +101,7 @@ function passModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
|
|
|
98
101
|
out.push(enrichModelThinking(item as Model<TApi>));
|
|
99
102
|
}
|
|
100
103
|
applyGeneratedModelPolicies(out as Model<Api>[]);
|
|
101
|
-
return out;
|
|
104
|
+
return applyFinalCodexGpt56ContextCap(out);
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
/**
|
|
@@ -147,7 +150,9 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
147
150
|
return { models: cachedModels, stale: false };
|
|
148
151
|
}
|
|
149
152
|
const repairedModels = mergeDynamicModels(staticModels, cachedModels);
|
|
150
|
-
|
|
153
|
+
if (options.canPublishCache?.() ?? true) {
|
|
154
|
+
writeModelCache(options.providerId, now(), repairedModels, true, staticFingerprint, dbPath);
|
|
155
|
+
}
|
|
151
156
|
return { models: repairedModels, stale: false };
|
|
152
157
|
}
|
|
153
158
|
|
|
@@ -161,27 +166,35 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
161
166
|
const cacheModels = dynamicFetchSucceeded ? [] : normalizeModelList<TApi>(cache?.models ?? []);
|
|
162
167
|
const dynamicModels = fetchedDynamicModels ?? [];
|
|
163
168
|
const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
|
|
164
|
-
const models = mergeDynamicModels(mergedWithCache, dynamicModels);
|
|
169
|
+
const models = applyFinalCodexGpt56ContextCap(mergeDynamicModels(mergedWithCache, dynamicModels));
|
|
165
170
|
const dynamicAuthoritative = !hasDynamicFetcher || dynamicFetchSucceeded || shouldUseFreshCacheAsAuthoritative;
|
|
166
171
|
if (shouldFetchFromNetwork) {
|
|
167
172
|
if (dynamicFetchSucceeded) {
|
|
168
|
-
const snapshotModels =
|
|
169
|
-
|
|
173
|
+
const snapshotModels = applyFinalCodexGpt56ContextCap(
|
|
174
|
+
mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels),
|
|
175
|
+
);
|
|
176
|
+
if (options.canPublishCache?.() ?? true) {
|
|
177
|
+
writeModelCache(options.providerId, now(), snapshotModels, true, staticFingerprint, dbPath);
|
|
178
|
+
}
|
|
170
179
|
} else {
|
|
171
180
|
// Dynamic fetch failed — update cache with a non-authoritative snapshot so
|
|
172
181
|
// stale state remains visible while retry backoff still applies.
|
|
173
182
|
const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
if (options.canPublishCache?.() ?? true) {
|
|
184
|
+
writeModelCache(
|
|
185
|
+
options.providerId,
|
|
186
|
+
now(),
|
|
187
|
+
applyFinalCodexGpt56ContextCap(
|
|
188
|
+
mergeDynamicModels(
|
|
189
|
+
mergeModelSources(staticModels, modelsDevModels),
|
|
190
|
+
normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
|
|
191
|
+
),
|
|
192
|
+
),
|
|
193
|
+
false,
|
|
194
|
+
staticFingerprint,
|
|
195
|
+
dbPath,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
185
198
|
}
|
|
186
199
|
}
|
|
187
200
|
return {
|
|
@@ -393,7 +406,7 @@ function normalizeModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
|
|
|
393
406
|
models.push(enrichModelThinking(item as Model<TApi>));
|
|
394
407
|
}
|
|
395
408
|
}
|
|
396
|
-
return models;
|
|
409
|
+
return applyFinalCodexGpt56ContextCap(models);
|
|
397
410
|
}
|
|
398
411
|
|
|
399
412
|
function isModelLike(value: unknown): value is Model<Api> {
|
package/src/model-thinking.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CODEX_GPT_5_6_CONTEXT_CAP, isCodexGpt56Tier, isCodexProductTransport } from "./context-cap-policy";
|
|
1
2
|
import { resolveOpenAICompat } from "./providers/openai-completions-compat";
|
|
2
3
|
import type { Api, Model as ApiModel, ThinkingConfig } from "./types";
|
|
3
4
|
import { isClaudeForcedToolChoiceIncapableModelId } from "./utils/tool-choice-capability";
|
|
@@ -49,6 +50,7 @@ const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, E
|
|
|
49
50
|
const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
|
|
50
51
|
const GPT_5_6_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max];
|
|
51
52
|
const GPT_5_5_DEFAULT_EFFORT = Effort.XHigh;
|
|
53
|
+
const KIMI_K3_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
|
|
52
54
|
|
|
53
55
|
const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
|
|
54
56
|
const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic";
|
|
@@ -477,15 +479,13 @@ function applyGpt55ContextWindow(model: ApiModel<Api>, parsedModel: OpenAIModel)
|
|
|
477
479
|
}
|
|
478
480
|
return false;
|
|
479
481
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
function applyGpt56ContextWindow(model: ApiModel<Api>, parsedModel: OpenAIModel): boolean {
|
|
483
|
-
if (!semverGte(parsedModel.version, "5.6") || !GPT_5_6_TIER_VARIANTS.has(parsedModel.variant)) {
|
|
482
|
+
function applyGpt56ContextWindow(model: ApiModel<Api>): boolean {
|
|
483
|
+
if (!isCodexGpt56Tier(model) || !isCodexProductTransport(model)) {
|
|
484
484
|
return false;
|
|
485
485
|
}
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
model.contextWindow =
|
|
486
|
+
// Codex product metadata is bounded by the currently enforced prompt cap.
|
|
487
|
+
// Smaller observed limits remain authoritative; first-party OpenAI is untouched.
|
|
488
|
+
model.contextWindow = Math.min(model.contextWindow, CODEX_GPT_5_6_CONTEXT_CAP.ceiling);
|
|
489
489
|
return true;
|
|
490
490
|
}
|
|
491
491
|
|
|
@@ -493,7 +493,7 @@ function applyOpenAICatalogPolicy(model: ApiModel<Api>, parsedModel: OpenAIModel
|
|
|
493
493
|
if (applyGpt55ContextWindow(model, parsedModel)) {
|
|
494
494
|
return;
|
|
495
495
|
}
|
|
496
|
-
if (applyGpt56ContextWindow(model
|
|
496
|
+
if (applyGpt56ContextWindow(model)) {
|
|
497
497
|
return;
|
|
498
498
|
}
|
|
499
499
|
// OpenAI code backend models: 400K figure includes output budget; input window is 272K.
|
|
@@ -517,6 +517,9 @@ function applyOpenAICatalogPolicy(model: ApiModel<Api>, parsedModel: OpenAIModel
|
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
function inferDefaultEffort<TApi extends Api>(model: ApiModel<TApi>, parsedModel: ParsedModel): Effort | undefined {
|
|
520
|
+
if (model.provider === "kimi-code" && model.id === "k3") {
|
|
521
|
+
return Effort.High;
|
|
522
|
+
}
|
|
520
523
|
if (
|
|
521
524
|
parsedModel.family === "openai" &&
|
|
522
525
|
model.provider === "openai-codex" &&
|
|
@@ -592,6 +595,9 @@ function expandEffortRange(thinking: ThinkingConfig): readonly Effort[] {
|
|
|
592
595
|
}
|
|
593
596
|
|
|
594
597
|
function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model: ApiModel<TApi>): readonly Effort[] {
|
|
598
|
+
if (model.provider === "kimi-code" && model.id === "k3") {
|
|
599
|
+
return KIMI_K3_EFFORTS;
|
|
600
|
+
}
|
|
595
601
|
switch (parsedModel.family) {
|
|
596
602
|
case "openai":
|
|
597
603
|
return inferOpenAISupportedEfforts(parsedModel);
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
} from "../types";
|
|
31
31
|
import { normalizeToolCallId, resolveCacheRetention, sanitizeJsonStrings } from "../utils";
|
|
32
32
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
33
|
+
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
33
34
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
34
35
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
35
36
|
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
@@ -317,7 +318,12 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
|
|
|
317
318
|
new Error(`Bedrock HTTP ${response.status}: ${errBody.slice(0, 1000)}`),
|
|
318
319
|
response.status,
|
|
319
320
|
);
|
|
320
|
-
if (
|
|
321
|
+
if (
|
|
322
|
+
firstTokenTime === undefined &&
|
|
323
|
+
!fallbackRan &&
|
|
324
|
+
!options.fallbackManaged &&
|
|
325
|
+
isForcedToolChoiceUnsupportedError(error, true)
|
|
326
|
+
) {
|
|
321
327
|
response = await retryWithoutForcedToolChoice(error.message);
|
|
322
328
|
} else {
|
|
323
329
|
throw error;
|
|
@@ -349,6 +355,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
|
|
|
349
355
|
firstTokenTime === undefined &&
|
|
350
356
|
sentForcedToolChoice &&
|
|
351
357
|
!fallbackRan &&
|
|
358
|
+
!options.fallbackManaged &&
|
|
352
359
|
isForcedToolChoiceUnsupportedError(error, true)
|
|
353
360
|
) {
|
|
354
361
|
response = await retryWithoutForcedToolChoice(error.message);
|
|
@@ -432,6 +439,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
|
|
|
432
439
|
}
|
|
433
440
|
output.stopReason = options.signal?.aborted ? "aborted" : "error";
|
|
434
441
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
442
|
+
output.transportFailure = transportFailureFacts(error);
|
|
435
443
|
const baseMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
436
444
|
// Enrich error with thinking block diagnostics for signature-related failures
|
|
437
445
|
let diagnostics = "";
|
|
@@ -417,6 +417,62 @@ function mapStopReasonOut(reason: StopReason): "end_turn" | "max_tokens" | "tool
|
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
/**
|
|
421
|
+
* True when `signature` is one of OUR serialized OpenAI Responses reasoning-item
|
|
422
|
+
* envelopes (produced by the Responses/Codex decoders as
|
|
423
|
+
* `JSON.stringify(reasoningItem)` with `type: "reasoning"`). Such an envelope is
|
|
424
|
+
* NOT a valid Anthropic thinking signature and may embed raw chain-of-thought in
|
|
425
|
+
* `content[]`, so it must never be forwarded on Anthropic egress. Genuine opaque
|
|
426
|
+
* Anthropic signatures and any non-envelope string return false (fail safe).
|
|
427
|
+
*/
|
|
428
|
+
function isSerializedResponsesReasoningItem(signature: string): boolean {
|
|
429
|
+
try {
|
|
430
|
+
const parsed: unknown = JSON.parse(signature);
|
|
431
|
+
return (
|
|
432
|
+
typeof parsed === "object" &&
|
|
433
|
+
parsed !== null &&
|
|
434
|
+
!Array.isArray(parsed) &&
|
|
435
|
+
(parsed as { type?: unknown }).type === "reasoning"
|
|
436
|
+
);
|
|
437
|
+
} catch {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* A thinking-block signature safe to emit on Anthropic egress: cross-protocol
|
|
444
|
+
* serialized Responses reasoning envelopes are omitted (they can carry raw CoT);
|
|
445
|
+
* genuine opaque Anthropic signatures round-trip unchanged.
|
|
446
|
+
*/
|
|
447
|
+
function safeThinkingSignature(signature: string | undefined): string | undefined {
|
|
448
|
+
if (!signature) return undefined;
|
|
449
|
+
if (isSerializedResponsesReasoningItem(signature)) return undefined;
|
|
450
|
+
return signature;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function isResponsesFamilyApi(api: AssistantMessage["api"]): boolean {
|
|
454
|
+
return api === "openai-responses" || api === "openai-codex-responses";
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function safeThinkingText(content: ThinkingContent, api: AssistantMessage["api"]): string | undefined {
|
|
458
|
+
if (isResponsesFamilyApi(api) && content.provenance === undefined) return undefined;
|
|
459
|
+
if (content.provenance === "raw") return undefined;
|
|
460
|
+
if (content.provenance === "mixed") return content.summaryText;
|
|
461
|
+
if (content.provenance === "summary") return content.summaryText ?? content.thinking;
|
|
462
|
+
return content.thinking;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function hasRawOrMixedThinking(partial: AssistantMessage, contentIndex: number): boolean {
|
|
466
|
+
const content = partial.content[contentIndex];
|
|
467
|
+
return content?.type === "thinking" && (content.provenance === "raw" || content.provenance === "mixed");
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Responses-family reasoning is untrusted until output_item.done assigns provenance. */
|
|
471
|
+
function hasUnfinalizedResponsesThinking(partial: AssistantMessage, contentIndex: number): boolean {
|
|
472
|
+
const content = partial.content[contentIndex];
|
|
473
|
+
return content?.type !== "thinking" || (isResponsesFamilyApi(partial.api) && content.provenance === undefined);
|
|
474
|
+
}
|
|
475
|
+
|
|
420
476
|
function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>[] {
|
|
421
477
|
const blocks: Record<string, unknown>[] = [];
|
|
422
478
|
for (const c of message.content) {
|
|
@@ -425,8 +481,11 @@ function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>
|
|
|
425
481
|
blocks.push({ type: "text", text: c.text });
|
|
426
482
|
break;
|
|
427
483
|
case "thinking": {
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
484
|
+
const thinking = safeThinkingText(c, message.api);
|
|
485
|
+
if (thinking === undefined) break;
|
|
486
|
+
const b: Record<string, unknown> = { type: "thinking", thinking };
|
|
487
|
+
const sig = safeThinkingSignature(c.thinkingSignature);
|
|
488
|
+
if (sig) b.signature = sig;
|
|
430
489
|
blocks.push(b);
|
|
431
490
|
break;
|
|
432
491
|
}
|
|
@@ -495,6 +554,12 @@ export function encodeStream(
|
|
|
495
554
|
const messageId = newMessageId();
|
|
496
555
|
let started = false;
|
|
497
556
|
const open = new Map<number, OpenBlock>();
|
|
557
|
+
// contentIndexes that already streamed a reasoning summary delta, so a
|
|
558
|
+
// final-only reasoning_summary_end does not duplicate streamed summary text.
|
|
559
|
+
const summaryDeltaSeen = new Set<number>();
|
|
560
|
+
// Responses assigns reasoning provenance only at output_item.done. Keep its
|
|
561
|
+
// pre-classification bytes out of this public compatibility stream.
|
|
562
|
+
const pendingThinkingDeltas = new Map<number, string[]>();
|
|
498
563
|
|
|
499
564
|
const ensureStart = (partial: AssistantMessage) => {
|
|
500
565
|
if (started) return;
|
|
@@ -524,6 +589,30 @@ export function encodeStream(
|
|
|
524
589
|
open.delete(index);
|
|
525
590
|
};
|
|
526
591
|
|
|
592
|
+
const openThinking = (partial: AssistantMessage, index: number) => {
|
|
593
|
+
if (open.has(index)) return;
|
|
594
|
+
ensureStart(partial);
|
|
595
|
+
open.set(index, { index, kind: "thinking" });
|
|
596
|
+
controller.enqueue(
|
|
597
|
+
sseFrame("content_block_start", {
|
|
598
|
+
type: "content_block_start",
|
|
599
|
+
index,
|
|
600
|
+
content_block: { type: "thinking", thinking: "" },
|
|
601
|
+
}),
|
|
602
|
+
);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const writeThinkingDelta = (index: number, thinking: string) => {
|
|
606
|
+
if (thinking.length === 0) return;
|
|
607
|
+
controller.enqueue(
|
|
608
|
+
sseFrame("content_block_delta", {
|
|
609
|
+
type: "content_block_delta",
|
|
610
|
+
index,
|
|
611
|
+
delta: { type: "thinking_delta", thinking },
|
|
612
|
+
}),
|
|
613
|
+
);
|
|
614
|
+
};
|
|
615
|
+
|
|
527
616
|
try {
|
|
528
617
|
for await (const ev of events) {
|
|
529
618
|
switch (ev.type) {
|
|
@@ -555,18 +644,73 @@ export function encodeStream(
|
|
|
555
644
|
closeBlock(ev.contentIndex);
|
|
556
645
|
break;
|
|
557
646
|
case "thinking_start": {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
647
|
+
if (hasRawOrMixedThinking(ev.partial, ev.contentIndex)) break;
|
|
648
|
+
if (hasUnfinalizedResponsesThinking(ev.partial, ev.contentIndex)) {
|
|
649
|
+
pendingThinkingDeltas.set(ev.contentIndex, []);
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
openThinking(ev.partial, ev.contentIndex);
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
case "thinking_delta": {
|
|
656
|
+
if (hasRawOrMixedThinking(ev.partial, ev.contentIndex)) break;
|
|
657
|
+
if (hasUnfinalizedResponsesThinking(ev.partial, ev.contentIndex)) {
|
|
658
|
+
const deltas = pendingThinkingDeltas.get(ev.contentIndex) ?? [];
|
|
659
|
+
deltas.push(ev.delta);
|
|
660
|
+
pendingThinkingDeltas.set(ev.contentIndex, deltas);
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
writeThinkingDelta(ev.contentIndex, ev.delta);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
case "thinking_end": {
|
|
667
|
+
const unfinalized = hasUnfinalizedResponsesThinking(ev.partial, ev.contentIndex);
|
|
668
|
+
const rawOrMixed = hasRawOrMixedThinking(ev.partial, ev.contentIndex);
|
|
669
|
+
const pending = pendingThinkingDeltas.get(ev.contentIndex);
|
|
670
|
+
pendingThinkingDeltas.delete(ev.contentIndex);
|
|
671
|
+
if (rawOrMixed || unfinalized) {
|
|
672
|
+
closeBlock(ev.contentIndex);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
if (pending) {
|
|
676
|
+
openThinking(ev.partial, ev.contentIndex);
|
|
677
|
+
for (const delta of pending) writeThinkingDelta(ev.contentIndex, delta);
|
|
678
|
+
}
|
|
679
|
+
const c = ev.partial.content[ev.contentIndex];
|
|
680
|
+
const sig = c?.type === "thinking" ? safeThinkingSignature(c.thinkingSignature) : undefined;
|
|
681
|
+
if (sig) {
|
|
682
|
+
controller.enqueue(
|
|
683
|
+
sseFrame("content_block_delta", {
|
|
684
|
+
type: "content_block_delta",
|
|
685
|
+
index: ev.contentIndex,
|
|
686
|
+
delta: { type: "signature_delta", signature: sig },
|
|
687
|
+
}),
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
closeBlock(ev.contentIndex);
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
case "reasoning_summary_start": {
|
|
694
|
+
// Provider-displayable summary reasoning surfaces as this format's
|
|
695
|
+
// native thinking channel. Open the thinking block if a thinking_start
|
|
696
|
+
// did not already (summary-only streams emit no thinking_start).
|
|
697
|
+
if (!open.has(ev.contentIndex)) {
|
|
698
|
+
ensureStart(ev.partial);
|
|
699
|
+
open.set(ev.contentIndex, { index: ev.contentIndex, kind: "thinking" });
|
|
700
|
+
controller.enqueue(
|
|
701
|
+
sseFrame("content_block_start", {
|
|
702
|
+
type: "content_block_start",
|
|
703
|
+
index: ev.contentIndex,
|
|
704
|
+
content_block: { type: "thinking", thinking: "" },
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
567
708
|
break;
|
|
568
709
|
}
|
|
569
|
-
case "
|
|
710
|
+
case "reasoning_summary_delta":
|
|
711
|
+
// Only a non-whitespace delta counts as a delivered summary; a bare
|
|
712
|
+
// separator ("\n\n") must not suppress a later final-only end content.
|
|
713
|
+
if (ev.delta.trim().length > 0) summaryDeltaSeen.add(ev.contentIndex);
|
|
570
714
|
controller.enqueue(
|
|
571
715
|
sseFrame("content_block_delta", {
|
|
572
716
|
type: "content_block_delta",
|
|
@@ -575,18 +719,31 @@ export function encodeStream(
|
|
|
575
719
|
}),
|
|
576
720
|
);
|
|
577
721
|
break;
|
|
578
|
-
case "
|
|
579
|
-
|
|
580
|
-
|
|
722
|
+
case "reasoning_summary_end": {
|
|
723
|
+
// Final-only summary: text arrives only on the end event with no prior
|
|
724
|
+
// deltas. Ensure the thinking block is open, then surface the content as
|
|
725
|
+
// a thinking_delta (skip when deltas already streamed to avoid dup). The
|
|
726
|
+
// thinking block is closed by the subsequent thinking_end.
|
|
727
|
+
if (ev.content.length > 0 && !summaryDeltaSeen.has(ev.contentIndex)) {
|
|
728
|
+
if (!open.has(ev.contentIndex)) {
|
|
729
|
+
ensureStart(ev.partial);
|
|
730
|
+
open.set(ev.contentIndex, { index: ev.contentIndex, kind: "thinking" });
|
|
731
|
+
controller.enqueue(
|
|
732
|
+
sseFrame("content_block_start", {
|
|
733
|
+
type: "content_block_start",
|
|
734
|
+
index: ev.contentIndex,
|
|
735
|
+
content_block: { type: "thinking", thinking: "" },
|
|
736
|
+
}),
|
|
737
|
+
);
|
|
738
|
+
}
|
|
581
739
|
controller.enqueue(
|
|
582
740
|
sseFrame("content_block_delta", {
|
|
583
741
|
type: "content_block_delta",
|
|
584
742
|
index: ev.contentIndex,
|
|
585
|
-
delta: { type: "
|
|
743
|
+
delta: { type: "thinking_delta", thinking: ev.content },
|
|
586
744
|
}),
|
|
587
745
|
);
|
|
588
746
|
}
|
|
589
|
-
closeBlock(ev.contentIndex);
|
|
590
747
|
break;
|
|
591
748
|
}
|
|
592
749
|
case "toolcall_start": {
|
|
@@ -621,6 +778,7 @@ export function encodeStream(
|
|
|
621
778
|
break;
|
|
622
779
|
case "done": {
|
|
623
780
|
for (const idx of [...open.keys()]) closeBlock(idx);
|
|
781
|
+
pendingThinkingDeltas.clear();
|
|
624
782
|
controller.enqueue(
|
|
625
783
|
sseFrame("message_delta", {
|
|
626
784
|
type: "message_delta",
|
|
@@ -636,6 +794,7 @@ export function encodeStream(
|
|
|
636
794
|
}
|
|
637
795
|
case "error": {
|
|
638
796
|
const msg = ev.error.errorMessage ?? "stream error";
|
|
797
|
+
pendingThinkingDeltas.clear();
|
|
639
798
|
controller.enqueue(
|
|
640
799
|
sseFrame("error", { type: "error", error: { type: "api_error", message: msg } }),
|
|
641
800
|
);
|
|
@@ -645,10 +804,12 @@ export function encodeStream(
|
|
|
645
804
|
}
|
|
646
805
|
}
|
|
647
806
|
// stream ended without explicit done; close gracefully
|
|
807
|
+
pendingThinkingDeltas.clear();
|
|
648
808
|
for (const idx of [...open.keys()]) closeBlock(idx);
|
|
649
809
|
controller.enqueue(sseFrame("message_stop", { type: "message_stop" }));
|
|
650
810
|
controller.close();
|
|
651
811
|
} catch (err) {
|
|
812
|
+
pendingThinkingDeltas.clear();
|
|
652
813
|
controller.enqueue(
|
|
653
814
|
sseFrame("error", {
|
|
654
815
|
type: "error",
|