@genesislcap/foundation-ai 14.494.0 → 14.496.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.
@@ -1,5 +1,4 @@
1
1
  import { __awaiter } from "tslib";
2
- import { DI, Registration } from '@microsoft/fast-foundation';
3
2
  import { AnthropicProvider } from './providers/anthropic-provider';
4
3
  import { ChromeProvider } from './providers/chrome-provider';
5
4
  import { DefaultAIProvider } from './providers/default-provider';
@@ -300,85 +299,3 @@ export class MutableAIProviderRegistry {
300
299
  listener();
301
300
  }
302
301
  }
303
- /**
304
- * The DI token for the {@link (AIProviderRegistry:interface)}. When no host
305
- * registers a concrete registry, the container resolves the built-in empty
306
- * registry (a single no-op provider) so consumers degrade to inert rather
307
- * than throwing.
308
- *
309
- * Prefer the {@link registerAIProviders} helper over registering this token
310
- * directly.
311
- *
312
- * @beta
313
- */
314
- export const AIProviderRegistry = DI.createInterface((x) => x.singleton(EmptyAIProviderRegistry));
315
- /**
316
- * Registers one or more named AI providers as an {@link (AIProviderRegistry:interface)}
317
- * on the given DI container.
318
- *
319
- * @remarks
320
- * - With a single provider, the default is inferred — `options.default` may be omitted.
321
- * - With multiple providers, `options.default` is required to avoid implicit ordering.
322
- * - Throws when `providers` is empty, when the named default isn't present, or
323
- * when multiple providers are passed without an explicit default.
324
- *
325
- * @example
326
- * ```ts
327
- * // Single provider — default inferred
328
- * registerAIProviders(container, { openai: createAIProvider(openAiConfig) });
329
- *
330
- * // Multiple providers — explicit default
331
- * registerAIProviders(
332
- * container,
333
- * { fast: chromeProvider, deep: anthropicProvider },
334
- * { default: 'deep' },
335
- * );
336
- * ```
337
- *
338
- * @returns the constructed {@link MutableAIProviderRegistry}, so a host that
339
- * wants to switch providers at runtime can keep the handle and call
340
- * {@link MutableAIProviderRegistry.set | set} /
341
- * {@link MutableAIProviderRegistry.setDefault | setDefault} /
342
- * {@link MutableAIProviderRegistry.update | update} on it later. Callers that
343
- * register once and never switch can ignore the return value.
344
- *
345
- * @beta
346
- */
347
- export function registerAIProviders(container, providers, options = {}) {
348
- const entries = Object.entries(providers);
349
- if (entries.length === 0) {
350
- throw new Error('registerAIProviders: at least one provider is required.');
351
- }
352
- let defaultName;
353
- if (options.default !== undefined) {
354
- if (!Object.prototype.hasOwnProperty.call(providers, options.default)) {
355
- throw new Error(`registerAIProviders: default "${options.default}" is not one of the registered providers (${entries.map(([k]) => k).join(', ')}).`);
356
- }
357
- defaultName = options.default;
358
- }
359
- else if (entries.length === 1) {
360
- defaultName = entries[0][0];
361
- }
362
- else {
363
- throw new Error(`registerAIProviders: multiple providers registered (${entries.map(([k]) => k).join(', ')}) — must specify { default } to disambiguate.`);
364
- }
365
- const registry = new MutableAIProviderRegistry(new Map(entries), defaultName);
366
- registerAIProviderRegistry(container, registry);
367
- return registry;
368
- }
369
- /**
370
- * Registers a host-supplied {@link (AIProviderRegistry:interface)} instance on
371
- * the DI container under the {@link (AIProviderRegistry:variable)} token.
372
- *
373
- * @remarks
374
- * A thin wrapper over FAST's `Registration.instance` so a host can register its
375
- * own pre-built registry — typically a {@link MutableAIProviderRegistry} it
376
- * owns and mutates for runtime provider switching — **without importing FAST
377
- * primitives** itself. {@link registerAIProviders} delegates to this; reach for
378
- * it directly when you construct the registry yourself.
379
- *
380
- * @beta
381
- */
382
- export function registerAIProviderRegistry(container, registry) {
383
- container.register(Registration.instance(AIProviderRegistry, registry));
384
- }
package/dist/esm/index.js CHANGED
@@ -1,4 +1,9 @@
1
- export { AIProviderRegistry, createAIProvider, isObservableAIProviderRegistry, MutableAIProviderRegistry, registerAIProviderRegistry, registerAIProviders, resolveAIConfig, } from './ai-provider';
1
+ export { createAIProvider, isObservableAIProviderRegistry, MutableAIProviderRegistry, resolveAIConfig, } from './ai-provider';
2
+ // FAST DI wiring lives in its own module so `@microsoft/fast-foundation` (→ `fast-element`, which
3
+ // touches `document` at eval) is only pulled when a host actually uses the container. Keeps the
4
+ // registry/driver path loadable in bare Node. `AIProviderRegistry` carries both the interface
5
+ // (type) and the DI token (value) — both merged in `ai-provider-di.ts`. See `ai-provider-di.ts`.
6
+ export { AIProviderRegistry, registerAIProviderRegistry, registerAIProviders, } from './ai-provider-di';
2
7
  export { AnthropicProvider } from './providers/anthropic-provider';
3
8
  export { GeminiProvider } from './providers/gemini-provider';
4
9
  export { AnthropicTransport, ResponseTruncatedError } from './transports/anthropic-transport';
@@ -30,6 +30,8 @@ function toAnthropicToolChoice(choice) {
30
30
  * Source: https://docs.claude.com/en/docs/about-claude/models/overview
31
31
  */
32
32
  const ANTHROPIC_CONTEXT_LIMITS = {
33
+ 'claude-fable-5': 1000000,
34
+ 'claude-opus-4-8': 1000000,
33
35
  'claude-opus-4-7': 1000000,
34
36
  'claude-sonnet-5': 1000000,
35
37
  'claude-sonnet-4-6': 1000000,
@@ -41,6 +43,8 @@ const ANTHROPIC_CONTEXT_LIMITS = {
41
43
  * and surfaces the `input` field as the structured response.
42
44
  */
43
45
  const STRUCTURED_OUTPUT_TOOL_NAME = 'emit_structured_response';
46
+ /** Beta flag enabling the server-side `fallbacks` request parameter (refusal rescue). */
47
+ const SERVER_SIDE_FALLBACK_BETA = 'server-side-fallback-2026-06-01';
44
48
  function assertSupportedAnthropicModel(model) {
45
49
  if (!SUPPORTED_ANTHROPIC_MODEL_IDS.includes(model)) {
46
50
  throw new Error(`AnthropicTransport: unsupported model "${model}". Use one of: ${SUPPORTED_ANTHROPIC_MODEL_IDS.join(', ')}.`);
@@ -53,36 +57,60 @@ function estimatedAnthropicRatesUsdPerMillion(model) {
53
57
  if (model === 'claude-haiku-4-5-20251001') {
54
58
  return { promptPerMillion: 1, candidatePerMillion: 5 };
55
59
  }
60
+ // Fable 5 — Anthropic's most capable widely-released model; priced above Opus tier.
61
+ if (model === 'claude-fable-5') {
62
+ return { promptPerMillion: 10, candidatePerMillion: 50 };
63
+ }
56
64
  // Sonnet 5 and Sonnet 4.6 share the standard Sonnet tier ($3 / $15 per MTok). Sonnet 5's
57
65
  // introductory rate ($2 / $10 through 2026-08-31) is deliberately NOT used here — standard rates.
58
66
  if (model === 'claude-sonnet-5' || model === 'claude-sonnet-4-6') {
59
67
  return { promptPerMillion: 3, candidatePerMillion: 15 };
60
68
  }
61
- // Opus 4.7
69
+ // Opus 4.7 / 4.8 — same $5 / $25 per MTok.
62
70
  return { promptPerMillion: 5, candidatePerMillion: 25 };
63
71
  }
64
72
  /**
65
73
  * Models that reject non-default sampling parameters (`temperature`/`top_p`/`top_k`) with a 400 —
66
- * the Opus 4.7+ generation. Sonnet 4.6 and Haiku 4.5 still accept them.
74
+ * the Opus 4.7+ / Sonnet 5 / Fable 5 generation. Sonnet 4.6 and Haiku 4.5 still accept them.
67
75
  */
68
76
  function rejectsSamplingParams(model) {
69
- return model === 'claude-opus-4-7' || model === 'claude-sonnet-5';
77
+ return (model === 'claude-fable-5' ||
78
+ model === 'claude-opus-4-8' ||
79
+ model === 'claude-opus-4-7' ||
80
+ model === 'claude-sonnet-5');
81
+ }
82
+ /**
83
+ * Whether the model enforces structured output natively via decode-time `output_config.format`
84
+ * (json_schema). True for Fable 5, Opus 4.8, Sonnet 5, and Haiku 4.5; false for Opus 4.7 and
85
+ * Sonnet 4.6, which fall back to the forced-tool approach on the one-shot path. `output_config`
86
+ * composes with a live `tools` array, so no tool-vs-schema gating is needed on this provider.
87
+ */
88
+ function supportsNativeStructuredOutput(model) {
89
+ return (model === 'claude-fable-5' ||
90
+ model === 'claude-opus-4-8' ||
91
+ model === 'claude-sonnet-5' ||
92
+ model === 'claude-haiku-4-5-20251001');
70
93
  }
71
94
  /**
72
- * Extended-thinking posture for a request. Sonnet 5 always runs adaptive thinking with
95
+ * Extended-thinking posture for a request. Sonnet 5 and Fable 5 run adaptive thinking with
73
96
  * `display:'summarized'` — regardless of `tool_choice`. First-party has no forced-tool-vs-thinking
74
97
  * restriction (that's Bedrock-only), and "adaptive" self-regulates (the model thinks little on
75
98
  * trivial/mechanical turns on its own), so there is no reason to special-case forced tool calls.
76
99
  * `display:'summarized'` means the reasoning summary is always returned (billed regardless; the
77
100
  * host's toggle decides visibility).
78
101
  *
79
- * Every other model returns undefined → `thinking` is omitted it runs WITHOUT thinking on the
80
- * chat path. So the older "forced tool + thinking is incompatible" restriction never applies to
81
- * them (you can't hit it when thinking is off). If thinking is ever enabled for a non-Sonnet-5
82
- * model, revisit the forced-tool interaction for that model then.
102
+ * Fable 5 runs thinking *unconditionally* (it cannot be disabled an explicit
103
+ * `{type:'disabled'}` is a 400), so it must never be sent `disabled`; `{type:'adaptive'}`
104
+ * is the accepted posture and is what we return.
105
+ *
106
+ * Every other model (Opus 4.8/4.7, Sonnet 4.6, Haiku) returns undefined → `thinking` is omitted
107
+ * → it runs WITHOUT thinking on the chat path (Opus 4.8 adaptive thinking is opt-in). So the older
108
+ * "forced tool + thinking is incompatible" restriction never applies to them (you can't hit it when
109
+ * thinking is off). If thinking is ever enabled for one of those, revisit the forced-tool
110
+ * interaction for that model then.
83
111
  */
84
112
  function anthropicThinking(model) {
85
- if (model !== 'claude-sonnet-5')
113
+ if (model !== 'claude-sonnet-5' && model !== 'claude-fable-5')
86
114
  return undefined;
87
115
  return { type: 'adaptive', display: 'summarized' };
88
116
  }
@@ -178,8 +206,11 @@ export class AnthropicTransport {
178
206
  else if (model === 'claude-sonnet-4-6') {
179
207
  logger.warn('AnthropicTransport: using claude-sonnet-4-6 — higher cost than Haiku; use for stronger reasoning or agent tasks.');
180
208
  }
181
- else if (model === 'claude-opus-4-7') {
182
- logger.warn('AnthropicTransport: using claude-opus-4-7 — significantly higher cost; reserve for tasks where Sonnet reliability is insufficient.');
209
+ else if (model === 'claude-opus-4-7' || model === 'claude-opus-4-8') {
210
+ logger.warn(`AnthropicTransport: using ${model} — significantly higher cost; reserve for tasks where Sonnet reliability is insufficient.`);
211
+ }
212
+ else if (model === 'claude-fable-5') {
213
+ logger.warn('AnthropicTransport: using claude-fable-5 — Anthropic\'s most capable model, priced above Opus tier ($10 / $50 per MTok) with thinking always on. Reserve for the hardest reasoning/long-horizon work. Requires ≥30-day data retention (unavailable under ZDR) and may return stop_reason "refusal"; pair with an Opus 4.8 fallback.');
183
214
  }
184
215
  this.timeout = (_b = config.timeout) !== null && _b !== void 0 ? _b : DEFAULT_TIMEOUT;
185
216
  this.stallTimeout = (_c = config.stallTimeout) !== null && _c !== void 0 ? _c : DEFAULT_STALL_TIMEOUT;
@@ -213,19 +244,6 @@ export class AnthropicTransport {
213
244
  var _a, _b, _c;
214
245
  const { systemPrompt, userPrompt, responseSchema } = options;
215
246
  const messages = [{ role: 'user', content: userPrompt }];
216
- // Anthropic has no native JSON-schema response format. The supported pattern
217
- // is to define a tool whose input_schema is the desired schema, then force
218
- // the model to call it via tool_choice. The tool's `input` is the structured
219
- // payload we surface back to the caller as a JSON string.
220
- const tools = responseSchema
221
- ? [
222
- {
223
- name: STRUCTURED_OUTPUT_TOOL_NAME,
224
- description: 'Emit the structured response that matches the required schema.',
225
- input_schema: responseSchema,
226
- },
227
- ]
228
- : undefined;
229
247
  const body = {
230
248
  model: this.model,
231
249
  max_tokens: this.maxTokens,
@@ -233,19 +251,38 @@ export class AnthropicTransport {
233
251
  };
234
252
  if (systemPrompt)
235
253
  body.system = systemPrompt;
236
- if (tools) {
237
- body.tools = tools;
254
+ // Prefer native decode-time enforcement (`output_config.format`) where the model supports it —
255
+ // the answer comes back as ordinary text conforming to the schema. On models without native
256
+ // support (Opus 4.7, Sonnet 4.6), fall back to the legacy pattern: a tool whose `input_schema`
257
+ // is the schema, forced via `tool_choice`, with the structured payload read from its `tool_use`.
258
+ const useNative = responseSchema != null && supportsNativeStructuredOutput(this.model);
259
+ const useForcedTool = responseSchema != null && !useNative;
260
+ if (useNative) {
261
+ body.output_config = {
262
+ format: { type: 'json_schema', schema: responseSchema },
263
+ };
264
+ }
265
+ else if (useForcedTool) {
266
+ body.tools = [
267
+ {
268
+ name: STRUCTURED_OUTPUT_TOOL_NAME,
269
+ description: 'Emit the structured response that matches the required schema.',
270
+ input_schema: responseSchema,
271
+ },
272
+ ];
238
273
  body.tool_choice = { type: 'tool', name: STRUCTURED_OUTPUT_TOOL_NAME };
239
274
  }
240
- // Sonnet 5 runs adaptive thinking by default; disable it for structured/one-shot prompts — the
241
- // reasoning would be billed but discarded (only text / the forced tool_use block is read back).
275
+ // Sonnet 5 runs adaptive thinking by default; disable it for one-shot prompts — the reasoning
276
+ // would be billed but discarded. Fable 5 runs thinking unconditionally (cannot be disabled a
277
+ // 400), so it is left on; its summary is simply unused here.
242
278
  if (this.model === 'claude-sonnet-5')
243
279
  body.thinking = { type: 'disabled' };
244
280
  const response = yield this.post(body);
245
- if (responseSchema) {
281
+ if (useForcedTool) {
246
282
  const toolUse = ((_a = response.content) !== null && _a !== void 0 ? _a : []).find((b) => b.type === 'tool_use' && b.name === STRUCTURED_OUTPUT_TOOL_NAME);
247
283
  return toolUse ? JSON.stringify((_b = toolUse.input) !== null && _b !== void 0 ? _b : {}) : '';
248
284
  }
285
+ // Native structured output and plain prompts both return the answer as text.
249
286
  return ((_c = response.content) !== null && _c !== void 0 ? _c : [])
250
287
  .filter((b) => b.type === 'text')
251
288
  .map((b) => b.text)
@@ -255,7 +292,7 @@ export class AnthropicTransport {
255
292
  // ── ChatTransport (multi-turn chat) ────────────────────────────────────
256
293
  sendChatMessage(history, userMessage, options) {
257
294
  return __awaiter(this, void 0, void 0, function* () {
258
- var _a, _b;
295
+ var _a, _b, _c;
259
296
  const messages = this.toAnthropicMessages(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
260
297
  const body = {
261
298
  model: this.model,
@@ -294,6 +331,22 @@ export class AnthropicTransport {
294
331
  maxTemp: ANTHROPIC_MAX_TEMPERATURE,
295
332
  });
296
333
  }
334
+ // Structured output: constrain the final text answer to the caller's JSON schema via
335
+ // decode-time `output_config.format`. Composes with a live `tools` array — the model still
336
+ // calls tools through the turn and conforms its closing answer to the schema. Applied only on
337
+ // models that support it natively; elsewhere the schema is dropped here (the caller keeps a
338
+ // prompt-instruction + validator fallback). No beta header needed.
339
+ if ((options === null || options === void 0 ? void 0 : options.responseSchema) && supportsNativeStructuredOutput(this.model)) {
340
+ body.output_config = {
341
+ format: { type: 'json_schema', schema: options.responseSchema },
342
+ };
343
+ }
344
+ // Refusal fallback chain (e.g. Fable 5 → Opus 4.8). Sent as the server-side `fallbacks`
345
+ // param; `post` adds the required beta header when this is present. A refused turn is
346
+ // re-run on the next model in one round trip.
347
+ if ((_c = options === null || options === void 0 ? void 0 : options.fallbacks) === null || _c === void 0 ? void 0 : _c.length) {
348
+ body.fallbacks = options.fallbacks.map((f) => f.maxTokens != null ? { model: f.model, max_tokens: f.maxTokens } : { model: f.model });
349
+ }
297
350
  // Place prompt-cache breakpoints per the resolved policy (no-op for `'default'`/absent).
298
351
  if (options === null || options === void 0 ? void 0 : options.cachePolicy) {
299
352
  this.applyCacheControl(body, options.cachePolicy);
@@ -553,6 +606,14 @@ export class AnthropicTransport {
553
606
  if (response.stop_reason === 'max_tokens') {
554
607
  base.responseMeta = { finishReason: 'max_tokens' };
555
608
  }
609
+ // A `refusal` stop means safety classifiers declined the request (Fable 5 and other
610
+ // recent models). Content is empty (pre-output) or partial (mid-stream) — return it
611
+ // rather than crash on an assumed non-empty block, and flag the reason so the driver
612
+ // (or a server-side `fallbacks` chain) can respond deterministically instead of
613
+ // retrying into the same wall. Usage/cost is already logged above.
614
+ if (response.stop_reason === 'refusal') {
615
+ base.responseMeta = { finishReason: 'refusal' };
616
+ }
556
617
  return base;
557
618
  }
558
619
  buildEndpoint() {
@@ -584,10 +645,17 @@ export class AnthropicTransport {
584
645
  }
585
646
  post(body, signal) {
586
647
  return __awaiter(this, void 0, void 0, function* () {
648
+ var _a;
587
649
  const { url, headers, credentials } = this.buildEndpoint();
650
+ // The server-side `fallbacks` param is gated behind a beta flag; add it per-request only
651
+ // when a fallback chain is set (the param without the header is a 400, and vice-versa is fine).
652
+ const requestHeaders = ((_a = body.fallbacks) === null || _a === void 0 ? void 0 : _a.length)
653
+ ? Object.assign(Object.assign({}, headers), { 'anthropic-beta': [headers['anthropic-beta'], SERVER_SIDE_FALLBACK_BETA]
654
+ .filter(Boolean)
655
+ .join(',') }) : headers;
588
656
  return (yield postWithRetry({
589
657
  url,
590
- headers,
658
+ headers: requestHeaders,
591
659
  body,
592
660
  credentials,
593
661
  vendorLabel: 'Anthropic',
@@ -258,7 +258,18 @@ export class GeminiTransport {
258
258
  // Names of the tools offered this turn — used to validate a repaired
259
259
  // malformed call against the real tool surface before accepting it.
260
260
  const offeredToolNames = new Set(((_b = options === null || options === void 0 ? void 0 : options.tools) !== null && _b !== void 0 ? _b : []).map((t) => t.name));
261
- const response = yield this.post({ model: this.model, contents, tools, systemInstruction, toolConfig, generationConfig }, options === null || options === void 0 ? void 0 : options.signal);
261
+ // Structured output (native JSON mode). Gemini 3 can combine it with function calling;
262
+ // Gemini 2.x cannot, so on 2.x apply the schema only on a pure structured turn (no tools) and
263
+ // otherwise drop it (the caller keeps a prompt-instruction + validator fallback). Passed as a
264
+ // top-level `responseSchema` that `toDirectPayload` maps to `generationConfig.responseMimeType`
265
+ // + `responseSchema` on the direct path (the proxy forwards it).
266
+ const schemaCoexistsWithTools = this.model.startsWith('gemini-3');
267
+ const applyResponseSchema = (options === null || options === void 0 ? void 0 : options.responseSchema) != null && (!tools || schemaCoexistsWithTools);
268
+ const response = yield this.post(Object.assign({ model: this.model, contents,
269
+ tools,
270
+ systemInstruction,
271
+ toolConfig,
272
+ generationConfig }, (applyResponseSchema ? { responseSchema: options.responseSchema } : {})), options === null || options === void 0 ? void 0 : options.signal);
262
273
  return this.fromGeminiResponse(response, offeredToolNames);
263
274
  });
264
275
  }
@@ -9,6 +9,8 @@ export const SUPPORTED_GEMINI_MODEL_IDS = [
9
9
  ];
10
10
  /** @beta */
11
11
  export const SUPPORTED_ANTHROPIC_MODEL_IDS = [
12
+ 'claude-fable-5',
13
+ 'claude-opus-4-8',
12
14
  'claude-opus-4-7',
13
15
  'claude-sonnet-5',
14
16
  'claude-sonnet-4-6',
@@ -719,7 +719,7 @@
719
719
  "text": "export interface AIProviderRegistry "
720
720
  }
721
721
  ],
722
- "fileUrlPath": "src/ai-provider.ts",
722
+ "fileUrlPath": "src/ai-provider-di.ts",
723
723
  "releaseTag": "Beta",
724
724
  "name": "AIProviderRegistry",
725
725
  "preserveMemberOrder": false,
@@ -995,7 +995,7 @@
995
995
  "text": ">"
996
996
  }
997
997
  ],
998
- "fileUrlPath": "src/ai-provider.ts",
998
+ "fileUrlPath": "src/ai-provider-di.ts",
999
999
  "isReadonly": true,
1000
1000
  "releaseTag": "Beta",
1001
1001
  "name": "AIProviderRegistry",
@@ -1441,7 +1441,7 @@
1441
1441
  },
1442
1442
  {
1443
1443
  "kind": "Content",
1444
- "text": "'claude-opus-4-7' | 'claude-sonnet-5' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001'"
1444
+ "text": "'claude-fable-5' | 'claude-opus-4-8' | 'claude-opus-4-7' | 'claude-sonnet-5' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001'"
1445
1445
  },
1446
1446
  {
1447
1447
  "kind": "Content",
@@ -3138,7 +3138,16 @@
3138
3138
  },
3139
3139
  {
3140
3140
  "kind": "Content",
3141
- "text": "{\n reason: 'done';\n} | {\n reason: 'agent-handoff';\n summary: string;\n remainingTask: string;\n}"
3141
+ "text": "{\n reason: 'done';\n failureReason?: "
3142
+ },
3143
+ {
3144
+ "kind": "Reference",
3145
+ "text": "TurnFailureReason",
3146
+ "canonicalReference": "@genesislcap/foundation-ai!TurnFailureReason:type"
3147
+ },
3148
+ {
3149
+ "kind": "Content",
3150
+ "text": ";\n} | {\n reason: 'agent-handoff';\n summary: string;\n remainingTask: string;\n}"
3142
3151
  },
3143
3152
  {
3144
3153
  "kind": "Content",
@@ -3150,9 +3159,81 @@
3150
3159
  "name": "ChatDriverResult",
3151
3160
  "typeTokenRange": {
3152
3161
  "startIndex": 1,
3153
- "endIndex": 2
3162
+ "endIndex": 4
3154
3163
  }
3155
3164
  },
3165
+ {
3166
+ "kind": "Interface",
3167
+ "canonicalReference": "@genesislcap/foundation-ai!ChatFallback:interface",
3168
+ "docComment": "/**\n * One entry in a {@link ChatRequestOptions.fallbacks} chain.\n *\n * @beta\n */\n",
3169
+ "excerptTokens": [
3170
+ {
3171
+ "kind": "Content",
3172
+ "text": "export interface ChatFallback "
3173
+ }
3174
+ ],
3175
+ "fileUrlPath": "src/types/chat.types.ts",
3176
+ "releaseTag": "Beta",
3177
+ "name": "ChatFallback",
3178
+ "preserveMemberOrder": false,
3179
+ "members": [
3180
+ {
3181
+ "kind": "PropertySignature",
3182
+ "canonicalReference": "@genesislcap/foundation-ai!ChatFallback#maxTokens:member",
3183
+ "docComment": "/**\n * Optional per-hop `max_tokens` cap for this fallback attempt.\n */\n",
3184
+ "excerptTokens": [
3185
+ {
3186
+ "kind": "Content",
3187
+ "text": "maxTokens?: "
3188
+ },
3189
+ {
3190
+ "kind": "Content",
3191
+ "text": "number"
3192
+ },
3193
+ {
3194
+ "kind": "Content",
3195
+ "text": ";"
3196
+ }
3197
+ ],
3198
+ "isReadonly": false,
3199
+ "isOptional": true,
3200
+ "releaseTag": "Beta",
3201
+ "name": "maxTokens",
3202
+ "propertyTypeTokenRange": {
3203
+ "startIndex": 1,
3204
+ "endIndex": 2
3205
+ }
3206
+ },
3207
+ {
3208
+ "kind": "PropertySignature",
3209
+ "canonicalReference": "@genesislcap/foundation-ai!ChatFallback#model:member",
3210
+ "docComment": "/**\n * Model id to fall back to (e.g. `'claude-opus-4-8'`).\n */\n",
3211
+ "excerptTokens": [
3212
+ {
3213
+ "kind": "Content",
3214
+ "text": "model: "
3215
+ },
3216
+ {
3217
+ "kind": "Content",
3218
+ "text": "string"
3219
+ },
3220
+ {
3221
+ "kind": "Content",
3222
+ "text": ";"
3223
+ }
3224
+ ],
3225
+ "isReadonly": false,
3226
+ "isOptional": false,
3227
+ "releaseTag": "Beta",
3228
+ "name": "model",
3229
+ "propertyTypeTokenRange": {
3230
+ "startIndex": 1,
3231
+ "endIndex": 2
3232
+ }
3233
+ }
3234
+ ],
3235
+ "extendsTokenRanges": []
3236
+ },
3156
3237
  {
3157
3238
  "kind": "TypeAlias",
3158
3239
  "canonicalReference": "@genesislcap/foundation-ai!ChatInputDuringExecutionMode:type",
@@ -4014,6 +4095,65 @@
4014
4095
  "endIndex": 2
4015
4096
  }
4016
4097
  },
4098
+ {
4099
+ "kind": "PropertySignature",
4100
+ "canonicalReference": "@genesislcap/foundation-ai!ChatRequestOptions#fallbacks:member",
4101
+ "docComment": "/**\n * Provider-neutral refusal-fallback chain: if the model declines the request (`stop_reason: 'refusal'`, e.g. Fable 5 safety classifiers), the provider re-runs the same request on the next listed model and returns its answer. Ordered most- to least-preferred; each entry may cap its own `maxTokens`. Providers that support it apply it server-side (Anthropic: `fallbacks` + the `server-side-fallback` beta); others ignore it. Typical use: Fable 5 with an Opus 4.8 fallback.\n *\n * @beta\n */\n",
4102
+ "excerptTokens": [
4103
+ {
4104
+ "kind": "Content",
4105
+ "text": "fallbacks?: "
4106
+ },
4107
+ {
4108
+ "kind": "Reference",
4109
+ "text": "ChatFallback",
4110
+ "canonicalReference": "@genesislcap/foundation-ai!ChatFallback:interface"
4111
+ },
4112
+ {
4113
+ "kind": "Content",
4114
+ "text": "[]"
4115
+ },
4116
+ {
4117
+ "kind": "Content",
4118
+ "text": ";"
4119
+ }
4120
+ ],
4121
+ "isReadonly": false,
4122
+ "isOptional": true,
4123
+ "releaseTag": "Beta",
4124
+ "name": "fallbacks",
4125
+ "propertyTypeTokenRange": {
4126
+ "startIndex": 1,
4127
+ "endIndex": 3
4128
+ }
4129
+ },
4130
+ {
4131
+ "kind": "PropertySignature",
4132
+ "canonicalReference": "@genesislcap/foundation-ai!ChatRequestOptions#responseSchema:member",
4133
+ "docComment": "/**\n * Structured-output schema (JSON Schema) for this turn. When set, the model's final (non-tool) answer is constrained to it instead of free text. Composes with `tools` — the model may still call tools this turn, then conform its closing answer to the schema. Providers apply it natively where the model supports it (Anthropic `output_config.format`, Gemini JSON mode) and drop it otherwise (caller keeps a prompt-instruction + validator fallback). Keep to the portable JSON-Schema subset providers share (`additionalProperties: false`, explicit `required`, enums, `anyOf` for nullables — no numeric/string constraints or recursion).\n *\n * @beta\n */\n",
4134
+ "excerptTokens": [
4135
+ {
4136
+ "kind": "Content",
4137
+ "text": "responseSchema?: "
4138
+ },
4139
+ {
4140
+ "kind": "Content",
4141
+ "text": "object"
4142
+ },
4143
+ {
4144
+ "kind": "Content",
4145
+ "text": ";"
4146
+ }
4147
+ ],
4148
+ "isReadonly": false,
4149
+ "isOptional": true,
4150
+ "releaseTag": "Beta",
4151
+ "name": "responseSchema",
4152
+ "propertyTypeTokenRange": {
4153
+ "startIndex": 1,
4154
+ "endIndex": 2
4155
+ }
4156
+ },
4017
4157
  {
4018
4158
  "kind": "PropertySignature",
4019
4159
  "canonicalReference": "@genesislcap/foundation-ai!ChatRequestOptions#signal:member",
@@ -8669,7 +8809,7 @@
8669
8809
  "text": ";"
8670
8810
  }
8671
8811
  ],
8672
- "fileUrlPath": "src/ai-provider.ts",
8812
+ "fileUrlPath": "src/ai-provider-di.ts",
8673
8813
  "returnTypeTokenRange": {
8674
8814
  "startIndex": 5,
8675
8815
  "endIndex": 6
@@ -8755,7 +8895,7 @@
8755
8895
  "text": ";"
8756
8896
  }
8757
8897
  ],
8758
- "fileUrlPath": "src/ai-provider.ts",
8898
+ "fileUrlPath": "src/ai-provider-di.ts",
8759
8899
  "returnTypeTokenRange": {
8760
8900
  "startIndex": 10,
8761
8901
  "endIndex": 11
@@ -8800,7 +8940,7 @@
8800
8940
  "text": "export interface RegisterAIProvidersOptions "
8801
8941
  }
8802
8942
  ],
8803
- "fileUrlPath": "src/ai-provider.ts",
8943
+ "fileUrlPath": "src/ai-provider-di.ts",
8804
8944
  "releaseTag": "Beta",
8805
8945
  "name": "RegisterAIProvidersOptions",
8806
8946
  "preserveMemberOrder": false,
@@ -9597,6 +9737,32 @@
9597
9737
  "startIndex": 1,
9598
9738
  "endIndex": 4
9599
9739
  }
9740
+ },
9741
+ {
9742
+ "kind": "TypeAlias",
9743
+ "canonicalReference": "@genesislcap/foundation-ai!TurnFailureReason:type",
9744
+ "docComment": "/**\n * Why a driver turn ended in failure — the typed taxonomy the tool loop already records onto its debug-log timeline (`turn.error` / `turn.retry` details), surfaced here so callers of a turn can read the outcome structurally instead of scraping prose. Distinct from {@link SubAgentFailureReason} (underscored, its own `timeout` member) because the two enums serialise into different log surfaces and READMEs; a transport request timeout on the main turn is recorded as `exception`.\n *\n * - `exception` — an uncaught error escaped the tool loop (catch-all, includes a transport request timeout). - `malformed-function-call` — the provider returned an unparseable tool call. - `empty-response` — the model returned no content and no tool calls. - `unknown-tool-limit` — the model repeatedly called tools it couldn't dispatch, whether hallucinated or stale (real earlier, retired now). - `max-iterations` — the tool loop hit its iteration cap. - `response-truncated` — a turn stopped at the provider's output-token cap with an incomplete tool call; deterministic, so it bails without retry.\n *\n * @beta\n */\n",
9745
+ "excerptTokens": [
9746
+ {
9747
+ "kind": "Content",
9748
+ "text": "export type TurnFailureReason = "
9749
+ },
9750
+ {
9751
+ "kind": "Content",
9752
+ "text": "'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations' | 'response-truncated'"
9753
+ },
9754
+ {
9755
+ "kind": "Content",
9756
+ "text": ";"
9757
+ }
9758
+ ],
9759
+ "fileUrlPath": "src/types/chat.types.ts",
9760
+ "releaseTag": "Beta",
9761
+ "name": "TurnFailureReason",
9762
+ "typeTokenRange": {
9763
+ "startIndex": 1,
9764
+ "endIndex": 2
9765
+ }
9600
9766
  }
9601
9767
  ]
9602
9768
  }