@gajae-code/ai 0.13.2 → 0.14.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 (81) hide show
  1. package/CHANGELOG.md +61 -2
  2. package/dist/types/auth-broker/client.d.ts +9 -1
  3. package/dist/types/auth-broker/redact.d.ts +7 -0
  4. package/dist/types/auth-broker/remote-store.d.ts +50 -9
  5. package/dist/types/auth-broker/types.d.ts +14 -0
  6. package/dist/types/auth-broker/wire-schemas.d.ts +25 -0
  7. package/dist/types/auth-storage.d.ts +200 -6
  8. package/dist/types/core.d.ts +1 -0
  9. package/dist/types/model-cache.d.ts +4 -1
  10. package/dist/types/model-manager.d.ts +11 -0
  11. package/dist/types/provider-models/openai-compat.d.ts +5 -0
  12. package/dist/types/provider-models/special.d.ts +3 -0
  13. package/dist/types/providers/anthropic.d.ts +31 -0
  14. package/dist/types/providers/cursor.d.ts +9 -1
  15. package/dist/types/providers/kiro-codewhisperer.d.ts +8 -0
  16. package/dist/types/providers/mock.d.ts +8 -0
  17. package/dist/types/providers/register-builtins.d.ts +1 -0
  18. package/dist/types/providers/transform-messages.d.ts +18 -0
  19. package/dist/types/types.d.ts +34 -8
  20. package/dist/types/usage/grok-cli.d.ts +5 -0
  21. package/dist/types/usage.d.ts +6 -0
  22. package/dist/types/utils/discovery/openai-compatible.d.ts +5 -0
  23. package/dist/types/utils/event-stream.d.ts +4 -2
  24. package/dist/types/utils/fallback-transport.d.ts +10 -0
  25. package/dist/types/utils/http-inspector.d.ts +1 -0
  26. package/dist/types/utils/idle-iterator.d.ts +13 -1
  27. package/dist/types/utils/json-parse.d.ts +19 -0
  28. package/dist/types/utils/oauth/callback-server.d.ts +13 -0
  29. package/dist/types/utils/oauth/kiro.d.ts +71 -0
  30. package/dist/types/utils/oauth/types.d.ts +1 -1
  31. package/dist/types/utils/parse-bind.d.ts +8 -5
  32. package/dist/types/utils/tool-call-healing.d.ts +7 -0
  33. package/dist/types/utils/tool-choice-capability.d.ts +11 -0
  34. package/package.json +3 -2
  35. package/src/auth-broker/client.ts +30 -0
  36. package/src/auth-broker/redact.ts +15 -0
  37. package/src/auth-broker/refresher.ts +4 -2
  38. package/src/auth-broker/remote-store.ts +693 -70
  39. package/src/auth-broker/server.ts +57 -12
  40. package/src/auth-broker/types.ts +16 -0
  41. package/src/auth-broker/wire-schemas.ts +21 -0
  42. package/src/auth-gateway/server.ts +84 -19
  43. package/src/auth-storage.ts +985 -41
  44. package/src/core.ts +1 -0
  45. package/src/model-cache.ts +23 -4
  46. package/src/model-manager.ts +70 -11
  47. package/src/model-thinking.ts +45 -1
  48. package/src/models.json +9604 -1932
  49. package/src/openai-completions-compat.ts +2 -1
  50. package/src/provider-models/descriptors.ts +7 -1
  51. package/src/provider-models/openai-compat.ts +52 -28
  52. package/src/provider-models/special.ts +12 -0
  53. package/src/providers/amazon-bedrock.ts +2 -1
  54. package/src/providers/anthropic.ts +831 -27
  55. package/src/providers/cursor.ts +83 -3
  56. package/src/providers/kiro-codewhisperer.ts +572 -0
  57. package/src/providers/mock.ts +15 -2
  58. package/src/providers/ollama.ts +9 -2
  59. package/src/providers/openai-codex-responses.ts +16 -9
  60. package/src/providers/openai-completions.ts +6 -1
  61. package/src/providers/openai-responses-shared.ts +180 -18
  62. package/src/providers/register-builtins.ts +24 -2
  63. package/src/providers/transform-messages.ts +64 -1
  64. package/src/stream.ts +25 -2
  65. package/src/types.ts +36 -7
  66. package/src/usage/grok-cli.ts +86 -1
  67. package/src/usage.ts +7 -0
  68. package/src/utils/discovery/openai-compatible.ts +89 -4
  69. package/src/utils/event-stream.ts +11 -2
  70. package/src/utils/fallback-transport.ts +44 -2
  71. package/src/utils/http-inspector.ts +1 -0
  72. package/src/utils/idle-iterator.ts +29 -6
  73. package/src/utils/json-parse.ts +80 -0
  74. package/src/utils/oauth/callback-server.ts +31 -1
  75. package/src/utils/oauth/index.ts +14 -1
  76. package/src/utils/oauth/kiro.ts +448 -0
  77. package/src/utils/oauth/synthetic.ts +2 -3
  78. package/src/utils/oauth/types.ts +1 -0
  79. package/src/utils/parse-bind.ts +27 -0
  80. package/src/utils/tool-call-healing.ts +13 -2
  81. package/src/utils/tool-choice-capability.ts +386 -6
package/src/core.ts CHANGED
@@ -23,6 +23,7 @@ export {
23
23
  PROVIDER_RUNTIME_DESCRIPTORS,
24
24
  type ProviderRuntimeDescriptor,
25
25
  } from "./providers/register-builtins";
26
+ export { hasAdjacentPrivateThinkingBlocks } from "./providers/transform-messages";
26
27
  export * from "./rate-limit-utils";
27
28
  export * from "./stream";
28
29
  export * from "./types";
@@ -6,7 +6,7 @@ import { Database } from "bun:sqlite";
6
6
  import { getModelDbPath } from "@gajae-code/utils/dirs";
7
7
  import type { Api, Model } from "./types";
8
8
 
9
- const CACHE_SCHEMA_VERSION = 3;
9
+ const CACHE_SCHEMA_VERSION = 5;
10
10
 
11
11
  interface CacheRow {
12
12
  provider_id: string;
@@ -14,6 +14,8 @@ interface CacheRow {
14
14
  updated_at: number;
15
15
  authoritative: number;
16
16
  static_fingerprint: string;
17
+ dynamic_model_ids: string | null;
18
+ dynamic_model_provenance: string | null;
17
19
  models: string;
18
20
  }
19
21
 
@@ -33,6 +35,9 @@ interface CacheEntry<TApi extends Api = Api> {
33
35
  * match — the cache already incorporates the same static state.
34
36
  */
35
37
  staticFingerprint: string;
38
+ /** IDs returned by the authoritative dynamic provider catalog, when retained. */
39
+ dynamicModelIds: string[] | undefined;
40
+ dynamicModelProvenance: string | undefined;
36
41
  }
37
42
 
38
43
  let sharedDb: Database | null = null;
@@ -56,6 +61,8 @@ function getDb(dbPath?: string): Database {
56
61
  updated_at INTEGER NOT NULL,
57
62
  authoritative INTEGER NOT NULL DEFAULT 0,
58
63
  static_fingerprint TEXT NOT NULL DEFAULT '',
64
+ dynamic_model_ids TEXT,
65
+ dynamic_model_provenance TEXT,
59
66
  models TEXT NOT NULL
60
67
  )
61
68
  `);
@@ -81,7 +88,13 @@ function migrateCacheSchema(db: Database): void {
81
88
  if (!columns.some(column => column.name === "static_fingerprint")) {
82
89
  db.run("ALTER TABLE model_cache ADD COLUMN static_fingerprint TEXT NOT NULL DEFAULT ''");
83
90
  }
84
- db.run("UPDATE model_cache SET version = ? WHERE version = 2", [CACHE_SCHEMA_VERSION]);
91
+ if (!columns.some(column => column.name === "dynamic_model_ids")) {
92
+ db.run("ALTER TABLE model_cache ADD COLUMN dynamic_model_ids TEXT");
93
+ }
94
+ if (!columns.some(column => column.name === "dynamic_model_provenance")) {
95
+ db.run("ALTER TABLE model_cache ADD COLUMN dynamic_model_provenance TEXT");
96
+ }
97
+ db.run("UPDATE model_cache SET version = ? WHERE version IN (2, 3, 4)", [CACHE_SCHEMA_VERSION]);
85
98
  }
86
99
 
87
100
  export function readModelCache<TApi extends Api>(
@@ -105,6 +118,8 @@ export function readModelCache<TApi extends Api>(
105
118
  authoritative: row.authoritative === 1,
106
119
  updatedAt: row.updated_at,
107
120
  staticFingerprint: row.static_fingerprint ?? "",
121
+ dynamicModelIds: row.dynamic_model_ids === null ? undefined : (JSON.parse(row.dynamic_model_ids) as string[]),
122
+ dynamicModelProvenance: row.dynamic_model_provenance ?? undefined,
108
123
  };
109
124
  } catch {
110
125
  return null;
@@ -118,18 +133,22 @@ export function writeModelCache<TApi extends Api>(
118
133
  authoritative: boolean,
119
134
  staticFingerprint: string,
120
135
  dbPath?: string,
136
+ dynamicModelIds?: readonly string[],
137
+ dynamicModelProvenance?: string,
121
138
  ): void {
122
139
  try {
123
140
  const db = getDb(dbPath);
124
141
  db.run(
125
- `INSERT OR REPLACE INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, models)
126
- VALUES (?, ?, ?, ?, ?, ?)`,
142
+ `INSERT OR REPLACE INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, dynamic_model_ids, dynamic_model_provenance, models)
143
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
127
144
  [
128
145
  providerId,
129
146
  CACHE_SCHEMA_VERSION,
130
147
  updatedAt,
131
148
  authoritative ? 1 : 0,
132
149
  staticFingerprint,
150
+ dynamicModelIds === undefined ? null : JSON.stringify(dynamicModelIds),
151
+ dynamicModelProvenance ?? null,
133
152
  JSON.stringify(models),
134
153
  ],
135
154
  );
@@ -43,6 +43,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
43
43
  now?: () => number;
44
44
  /** Optional guard that must permit cache publication. Default: writes are permitted. */
45
45
  canPublishCache?: () => boolean;
46
+ /** Credential-and-endpoint identity required to reuse dynamic catalog IDs. */
47
+ cacheDynamicModelProvenance?: string;
46
48
  }
47
49
 
48
50
  /**
@@ -56,8 +58,17 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
56
58
  export interface ModelResolutionResult<TApi extends Api = Api> {
57
59
  models: Model<TApi>[];
58
60
  stale: boolean;
61
+ /** Whether the cache row consulted for this resolution was still within its TTL. */
62
+ cacheFresh: boolean;
63
+ /** Whether the consulted cache row was authoritative. */
64
+ cacheAuthoritative: boolean;
59
65
  /** Whether this resolution successfully fetched dynamic models. */
60
66
  fetched: boolean;
67
+ /**
68
+ * IDs returned by a current authoritative dynamic provider catalog. This is
69
+ * deliberately distinct from `models`, which merges static and cached data.
70
+ */
71
+ dynamicModelIds?: readonly string[];
61
72
  }
62
73
 
63
74
  /**
@@ -125,14 +136,19 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
125
136
  const cache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
126
137
  const dynamicFetcher = options.fetchDynamicModels;
127
138
  const hasDynamicFetcher = typeof dynamicFetcher === "function";
128
- const hasAuthoritativeCache = (cache?.authoritative ?? false) || !hasDynamicFetcher;
139
+ const cacheDynamicModelIdsCurrent =
140
+ cache?.dynamicModelIds !== undefined &&
141
+ cache.dynamicModelProvenance !== undefined &&
142
+ cache.dynamicModelProvenance === options.cacheDynamicModelProvenance;
143
+ const cacheProvenanceMismatch = cache?.dynamicModelIds !== undefined && !cacheDynamicModelIdsCurrent;
144
+ const hasAuthoritativeCache =
145
+ !hasDynamicFetcher ||
146
+ ((cache?.authoritative ?? false) && (cache?.dynamicModelIds === undefined || cacheDynamicModelIdsCurrent));
129
147
  const cacheAgeMs = cache ? now() - cache.updatedAt : Number.POSITIVE_INFINITY;
130
- const shouldFetchFromNetwork = shouldFetchRemoteSources(
131
- strategy,
132
- cache?.fresh ?? false,
133
- hasAuthoritativeCache,
134
- cacheAgeMs,
135
- );
148
+ const shouldFetchFromNetwork =
149
+ cacheProvenanceMismatch && strategy !== "offline"
150
+ ? true
151
+ : shouldFetchRemoteSources(strategy, cache?.fresh ?? false, hasAuthoritativeCache, cacheAgeMs);
136
152
  const staticFingerprint = fingerprintStatic(staticModels);
137
153
 
138
154
  // Cold-start fast path: when a fresh, authoritative cache exists, the network
@@ -149,13 +165,38 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
149
165
  ) {
150
166
  const cachedModels = passModelList<TApi>(cache.models);
151
167
  if (!hasStaticTransportDrift(staticModels, cachedModels)) {
152
- return { models: cachedModels, stale: false, fetched: false };
168
+ return {
169
+ models: cachedModels,
170
+ stale: false,
171
+ cacheFresh: true,
172
+ cacheAuthoritative: true,
173
+ fetched: false,
174
+ dynamicModelIds:
175
+ strategy === "online-if-uncached" && cacheDynamicModelIdsCurrent ? cache.dynamicModelIds : undefined,
176
+ };
153
177
  }
154
178
  const repairedModels = mergeDynamicModels(staticModels, cachedModels);
155
179
  if (options.canPublishCache?.() ?? true) {
156
- writeModelCache(options.providerId, now(), repairedModels, true, staticFingerprint, dbPath);
180
+ writeModelCache(
181
+ options.providerId,
182
+ now(),
183
+ repairedModels,
184
+ true,
185
+ staticFingerprint,
186
+ dbPath,
187
+ cache.dynamicModelIds,
188
+ cache.dynamicModelProvenance,
189
+ );
157
190
  }
158
- return { models: repairedModels, stale: false, fetched: false };
191
+ return {
192
+ models: repairedModels,
193
+ stale: false,
194
+ cacheFresh: true,
195
+ cacheAuthoritative: true,
196
+ fetched: false,
197
+ dynamicModelIds:
198
+ strategy === "online-if-uncached" && cacheDynamicModelIdsCurrent ? cache.dynamicModelIds : undefined,
199
+ };
159
200
  }
160
201
 
161
202
  const [fetchedModelsDevModels, fetchedDynamicModels] = shouldFetchFromNetwork
@@ -176,7 +217,16 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
176
217
  mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels),
177
218
  );
178
219
  if (options.canPublishCache?.() ?? true) {
179
- writeModelCache(options.providerId, now(), snapshotModels, true, staticFingerprint, dbPath);
220
+ writeModelCache(
221
+ options.providerId,
222
+ now(),
223
+ snapshotModels,
224
+ true,
225
+ staticFingerprint,
226
+ dbPath,
227
+ dynamicModels.map(model => model.id),
228
+ options.cacheDynamicModelProvenance,
229
+ );
180
230
  }
181
231
  } else {
182
232
  // Dynamic fetch failed — update cache with a non-authoritative snapshot so
@@ -202,7 +252,16 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
202
252
  return {
203
253
  models,
204
254
  stale: !dynamicAuthoritative,
255
+ cacheFresh: cache?.fresh ?? false,
256
+ cacheAuthoritative: cache?.authoritative ?? false,
205
257
  fetched: shouldFetchFromNetwork && dynamicFetchSucceeded,
258
+ dynamicModelIds: dynamicFetchSucceeded
259
+ ? dynamicModels.map(model => model.id)
260
+ : shouldUseFreshCacheAsAuthoritative
261
+ ? cacheDynamicModelIdsCurrent
262
+ ? cache?.dynamicModelIds
263
+ : undefined
264
+ : undefined,
206
265
  };
207
266
  }
208
267
 
@@ -53,11 +53,16 @@ const DEFAULT_REASONING_EFFORTS_WITH_XHIGH_AND_MAX: readonly Effort[] = [
53
53
  ];
54
54
  const GEMINI_3_PRO_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High];
55
55
  const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
56
+ // Gemini 3.7 Flash dropped `minimal`; the official API returns an error for it.
57
+ // https://ai.google.dev/gemini-api/docs/models/gemini-3.7-flash
58
+ const GEMINI_3_7_FLASH_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High];
56
59
  const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
57
60
  const GPT_5_6_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max];
58
61
  const GPT_5_5_DEFAULT_EFFORT = Effort.XHigh;
59
62
  const KIMI_K3_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
60
63
  const DEEPSEEK_V4_FLASH_0731_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
64
+ const GROK_4_5_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High];
65
+ const GROK_4_6_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
61
66
 
62
67
  const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
63
68
  const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic";
@@ -206,10 +211,16 @@ export function refreshModelThinking<TApi extends Api>(model: ApiModel<TApi>): A
206
211
  export function applyGeneratedModelPolicies(models: ApiModel<Api>[]): void {
207
212
  for (let index = 0; index < models.length; index++) {
208
213
  const source = models[index]!;
214
+ if (source.provider === "xai" && (source.id === "grok-4.5" || source.id === "grok-4.6")) {
215
+ source.reasoning = true;
216
+ }
209
217
  if (source.provider === "alibaba-token-plan" && source.id === "deepseek-v4-flash-0731") {
210
218
  source.reasoning = true;
211
219
  source.name = "DeepSeek V4 Flash 0731";
212
220
  }
221
+ if (source.id.split("/").at(-1)?.toLowerCase() === "muse-spark-1.2") {
222
+ source.reasoning = true;
223
+ }
213
224
  const model = refreshModelThinking(source);
214
225
  applyGeneratedModelPolicy(model);
215
226
  models[index] = model;
@@ -460,6 +471,17 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
460
471
  if (model.provider === "zai" && model.id === "glm-5.2") {
461
472
  model.contextWindow = 1_000_000;
462
473
  }
474
+ // GLM-5.3 always thinks and exposes only low/high/max reasoning_effort.
475
+ // https://z.ai/blog/glm-5.3#api-changes-in-glm-5-3
476
+ if (model.provider === "zai" && model.id === "glm-5.3") {
477
+ model.thinking = {
478
+ mode: "effort",
479
+ minLevel: Effort.Low,
480
+ maxLevel: Effort.Max,
481
+ defaultLevel: Effort.Max,
482
+ levels: [Effort.Low, Effort.High, Effort.Max],
483
+ };
484
+ }
463
485
  if (model.provider === "alibaba-token-plan" && model.id === "deepseek-v4-flash-0731") {
464
486
  model.contextWindow = 1_000_000;
465
487
  model.maxTokens = 384_000;
@@ -471,6 +493,9 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
471
493
  requiresReasoningContentForToolCalls: true,
472
494
  };
473
495
  }
496
+ if (model.provider === "xai" && (model.id === "grok-4.5" || model.id === "grok-4.6")) {
497
+ model.maxTokens = Math.min(model.maxTokens, 64_000);
498
+ }
474
499
  // MiniMax-M3's official Token Plan routes expose a 1M context window.
475
500
  // Scope the correction to the four first-class regional MiniMax routes
476
501
  // (canonical id plus the Anthropic Token Plan `[1m]` id); unrelated
@@ -664,6 +689,12 @@ function expandEffortRange(thinking: ThinkingConfig): readonly Effort[] {
664
689
  }
665
690
 
666
691
  function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model: ApiModel<TApi>): readonly Effort[] {
692
+ if (model.provider === "xai" && model.id === "grok-4.5") {
693
+ return GROK_4_5_EFFORTS;
694
+ }
695
+ if (model.provider === "xai" && model.id === "grok-4.6") {
696
+ return GROK_4_6_EFFORTS;
697
+ }
667
698
  if (model.provider === "kimi-code" && model.id === "k3") {
668
699
  return KIMI_K3_EFFORTS;
669
700
  }
@@ -699,7 +730,13 @@ function inferGeminiSupportedEfforts(model: GeminiModel): readonly Effort[] {
699
730
  if (!semverGte(model.version, "3.0")) {
700
731
  return DEFAULT_REASONING_EFFORTS;
701
732
  }
702
- return model.kind === "pro" ? GEMINI_3_PRO_EFFORTS : GEMINI_3_FLASH_EFFORTS;
733
+ if (model.kind === "pro") {
734
+ return GEMINI_3_PRO_EFFORTS;
735
+ }
736
+ if (semverGte(model.version, "3.7")) {
737
+ return GEMINI_3_7_FLASH_EFFORTS;
738
+ }
739
+ return GEMINI_3_FLASH_EFFORTS;
703
740
  }
704
741
 
705
742
  function inferAnthropicSupportedEfforts<TApi extends Api>(
@@ -730,6 +767,13 @@ function inferAnthropicSupportedEfforts<TApi extends Api>(
730
767
  }
731
768
 
732
769
  function inferFallbackEfforts<TApi extends Api>(model: ApiModel<TApi>): readonly Effort[] {
770
+ // Meta documents Muse Spark 1.2 as accepting the full minimal..xhigh
771
+ // reasoning range. Keep that capability provider-independent so runtime
772
+ // model discovery/merge cannot downgrade the bundled OpenRouter entry to
773
+ // the generic openai-completions ceiling of `high`.
774
+ if (model.id.split("/").at(-1)?.toLowerCase() === "muse-spark-1.2") {
775
+ return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
776
+ }
733
777
  if (model.api === "anthropic-messages") {
734
778
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
735
779
  }