@genesislcap/foundation-ai 15.40.0 → 15.41.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.
@@ -64,6 +64,46 @@ export declare interface AggregateUsage {
64
64
  /** Feature flag name for AI functionality (beta). Enable via ?feature.ai in URL or GENX_ENABLE_AI env var. */
65
65
  export declare const AI_FEATURE_FLAG = "ai";
66
66
 
67
+ /**
68
+ * Refusals that come from the app's gateway, not from its AI service. The Genesis router turns a
69
+ * request away before the service runs: with no session (401) or without the right the endpoint
70
+ * demands (403). Its body is the router's own NACK, with no `code` a proxy would send, so these are
71
+ * kept out of {@link AI_SERVICE_ERROR_CODES}.
72
+ *
73
+ * @beta
74
+ */
75
+ export declare const AI_SERVICE_ACCESS_REFUSALS: Readonly<{
76
+ /** 401: the request had no session. */
77
+ readonly SIGNED_OUT: "SIGNED_OUT";
78
+ /** 403: the user lacks the right the endpoint demands. */
79
+ readonly ACCESS_DENIED: "ACCESS_DENIED";
80
+ }>;
81
+
82
+ /**
83
+ * Every code an AI service may put in an error body. A code not listed here keeps the old,
84
+ * status-based handling: an unknown code must not become a silent dead end, or a retry loop.
85
+ *
86
+ * @beta
87
+ */
88
+ export declare const AI_SERVICE_ERROR_CODES: Readonly<{
89
+ /** No API key is configured for the vendor. */
90
+ readonly NO_API_KEY: "NO_API_KEY";
91
+ /** The configured key has characters an API key cannot have. */
92
+ readonly BAD_API_KEY: "BAD_API_KEY";
93
+ /** An encrypted key could not be decrypted. */
94
+ readonly KEY_UNREADABLE: "KEY_UNREADABLE";
95
+ /** The request asked for a model the service does not allow. */
96
+ readonly MODEL_NOT_ALLOWED: "MODEL_NOT_ALLOWED";
97
+ /** The service refused the shape of the request. */
98
+ readonly BAD_REQUEST: "BAD_REQUEST";
99
+ /** The request named fallback models, which the service does not accept. */
100
+ readonly FALLBACKS_NOT_SUPPORTED: "FALLBACKS_NOT_SUPPORTED";
101
+ /** The request body is larger, or nested deeper, than the service accepts. */
102
+ readonly REQUEST_TOO_LARGE: "REQUEST_TOO_LARGE";
103
+ /** The service could not reach the vendor. The only code that is worth retrying. */
104
+ readonly VENDOR_UNREACHABLE: "VENDOR_UNREACHABLE";
105
+ }>;
106
+
67
107
  /**
68
108
  * Every tier id, in ascending capability order.
69
109
  *
@@ -231,6 +271,76 @@ export declare interface AIProviderRegistryStatusEntry {
231
271
  */
232
272
  export declare type AIProviderType = 'openai' | 'chrome' | 'gemini' | 'anthropic' | 'none';
233
273
 
274
+ /**
275
+ * One of {@link AI_SERVICE_ACCESS_REFUSALS}.
276
+ *
277
+ * @beta
278
+ */
279
+ export declare type AIServiceAccessRefusal = (typeof AI_SERVICE_ACCESS_REFUSALS)[keyof typeof AI_SERVICE_ACCESS_REFUSALS];
280
+
281
+ /**
282
+ * The access refusal behind a 401 or 403 from the app's router, which carries no `code`.
283
+ *
284
+ * Recognised by a positive signal only, like the rest of this module's family: the router's NACK,
285
+ * or an empty body. A vendor's own 401/403, a coded one, and anything else (an HTML page from a
286
+ * proxy in between, say) keep their own handling.
287
+ *
288
+ * @param bodyText - the raw response body, so an empty one can be told from one that isn't JSON.
289
+ * Left out, the body is unknown rather than empty, and only the router's NACK counts.
290
+ * @beta
291
+ */
292
+ export declare function aiServiceAccessRefusalOf(status: number, payload: unknown, bodyText?: string): AIServiceAccessRefusal | undefined;
293
+
294
+ /**
295
+ * Thrown when the AI service answers with one of {@link AI_SERVICE_ERROR_CODES}, or the app's router
296
+ * refuses the request with one of {@link AI_SERVICE_ACCESS_REFUSALS}.
297
+ *
298
+ * All but `VENDOR_UNREACHABLE` are terminal: sending the same request again gets the same answer
299
+ * until someone changes the configuration, or the user's session or rights.
300
+ *
301
+ * @beta
302
+ */
303
+ export declare class AIServiceError extends Error {
304
+ /** Vendor label of the transport that was refused (e.g. `'Gemini'`). */
305
+ readonly vendorLabel: string;
306
+ readonly code: AIServiceRefusal;
307
+ /** HTTP status, or the err frame's status on the framed path. */
308
+ readonly status: number;
309
+ /** The service's own message, for the console. Never shown to the user. */
310
+ readonly detail?: string;
311
+ constructor(
312
+ /** Vendor label of the transport that was refused (e.g. `'Gemini'`). */
313
+ vendorLabel: string, code: AIServiceRefusal,
314
+ /** HTTP status, or the err frame's status on the framed path. */
315
+ status: number,
316
+ /** The service's own message, for the console. Never shown to the user. */
317
+ detail?: string);
318
+ /** Whether sending the request again cannot help. */
319
+ get terminal(): boolean;
320
+ }
321
+
322
+ /**
323
+ * One of {@link AI_SERVICE_ERROR_CODES}.
324
+ *
325
+ * @beta
326
+ */
327
+ export declare type AIServiceErrorCode = (typeof AI_SERVICE_ERROR_CODES)[keyof typeof AI_SERVICE_ERROR_CODES];
328
+
329
+ /**
330
+ * The code, when it is one of {@link AI_SERVICE_ERROR_CODES}; otherwise `undefined`.
331
+ *
332
+ * @beta
333
+ */
334
+ export declare function aiServiceErrorCodeOf(code: unknown): AIServiceErrorCode | undefined;
335
+
336
+ /**
337
+ * Everything the chat can tell the user about a refused request: a code the service sent, or an
338
+ * access refusal from in front of it.
339
+ *
340
+ * @beta
341
+ */
342
+ export declare type AIServiceRefusal = AIServiceErrorCode | AIServiceAccessRefusal;
343
+
234
344
  /**
235
345
  * AI status for display in the header indicator.
236
346
  *
@@ -2055,6 +2165,8 @@ export declare type ChatToolHandlers<TSubAgent extends {
2055
2165
  * - `{ ok: false, reason }` — the run ended without completing (see
2056
2166
  * {@link SubAgentFailureReason}). The parent handler decides how to
2057
2167
  * recover — typically by returning the issue info to its own agent.
2168
+ * With `reason: 'ai_service_error'` it also carries the refusal's `code`
2169
+ * and whether it is `terminal`.
2058
2170
  *
2059
2171
  * Sub-agents run with tool use forced, so they can no longer end on a
2060
2172
  * free-text turn; the previous string fallback has been removed.
@@ -2063,10 +2175,22 @@ export declare type ChatToolHandlers<TSubAgent extends {
2063
2175
  ok: true;
2064
2176
  result: T;
2065
2177
  reason?: never;
2178
+ code?: never;
2179
+ terminal?: never;
2066
2180
  } | {
2067
2181
  ok: false;
2068
2182
  result?: never;
2069
- reason: SubAgentFailureReason;
2183
+ reason: Exclude<SubAgentFailureReason, 'ai_service_error'>;
2184
+ code?: never;
2185
+ terminal?: never;
2186
+ } | {
2187
+ ok: false;
2188
+ result?: never;
2189
+ reason: 'ai_service_error';
2190
+ /** The refusal's code. */
2191
+ code: AIServiceRefusal;
2192
+ /** Whether re-invoking it with the same request gets the same refusal. */
2193
+ terminal: boolean;
2070
2194
  }>;
2071
2195
  /**
2072
2196
  * Signal explicit completion from within a sub-agent tool handler.
@@ -2113,6 +2237,22 @@ export declare type ChatToolHandlers<TSubAgent extends {
2113
2237
  * restores the content); non-idempotent tools must use `replaceWith`.
2114
2238
  */
2115
2239
  condenseWhen: (policy: CondensePolicy) => void;
2240
+ /**
2241
+ * The current TURN's abort signal: one object for every tool call made while answering
2242
+ * one user message, a new one for the next, and aborted when the user stops the turn or
2243
+ * the view is disposed. One message includes an orchestrated handoff's continuation and a
2244
+ * sub-agent the message delegates to: both see the same object, and a stop reaches both.
2245
+ * A continuation after the user stopped the message is not part of it, and gets a new one.
2246
+ *
2247
+ * Two uses. A long-running handler can listen for it to stop early. And a handler that
2248
+ * must remember something for the rest of the turn — a write the user denied, say — can
2249
+ * key that state on this object (a `WeakMap`), which cannot collide the way a turn number
2250
+ * would when several chat views share one set of handlers.
2251
+ *
2252
+ * Optional: a host that builds its own context may not supply it, and a handler relying on
2253
+ * it must decide what its absence means.
2254
+ */
2255
+ turnSignal?: AbortSignal;
2116
2256
  }) => Promise<unknown>>;
2117
2257
 
2118
2258
  /**
@@ -2543,6 +2683,15 @@ export declare interface CriteriaInterpretContext {
2543
2683
  fields?: FieldLike[];
2544
2684
  }
2545
2685
 
2686
+ /**
2687
+ * What the user sees for each code and access refusal. Written for the app's user, with a hint for
2688
+ * whoever runs it, and never quoting the server: its text goes to the console only. A host can
2689
+ * replace any of them (`aiServiceErrorMessages` on the assistant).
2690
+ *
2691
+ * @beta
2692
+ */
2693
+ export declare const DEFAULT_AI_SERVICE_ERROR_MESSAGES: Readonly<Record<AIServiceRefusal, string>>;
2694
+
2546
2695
  /**
2547
2696
  * The platform's default tier table.
2548
2697
  *
@@ -2972,6 +3121,13 @@ export declare interface InteractionRequestOptions {
2972
3121
  * means no timeout — the interaction waits indefinitely.
2973
3122
  */
2974
3123
  timeoutMs?: number;
3124
+ /**
3125
+ * Closes the interaction when it aborts, exactly as an unanswered one closes at `timeoutMs`: the
3126
+ * widget goes read-only and `requestInteraction` resolves with `status: 'timeout'`. A tool
3127
+ * handler passes its `turnSignal`, so a user's Stop closes a widget left open instead of leaving
3128
+ * the turn parked on it, and nothing the user clicks afterwards is taken as an answer.
3129
+ */
3130
+ signal?: AbortSignal;
2975
3131
  /**
2976
3132
  * How the host frames this widget in the transcript — see
2977
3133
  * {@link InteractionPresentation}. Defaults to `'label'` (the historical
@@ -3079,6 +3235,14 @@ export declare function isObservableAIProviderRegistry(r: AIProviderRegistry): r
3079
3235
  */
3080
3236
  export declare function isSpeechRecognitionAvailable(): boolean;
3081
3237
 
3238
+ /**
3239
+ * Whether sending the same request again gets the same refusal. Every code but `VENDOR_UNREACHABLE`
3240
+ * does, until someone changes the configuration or the user's session or rights.
3241
+ *
3242
+ * @beta
3243
+ */
3244
+ export declare const isTerminalAIServiceRefusal: (code: AIServiceRefusal) => boolean;
3245
+
3082
3246
  /**
3083
3247
  * Thrown when Gemini returns a `MALFORMED_FUNCTION_CALL` finish reason,
3084
3248
  * typically because the model tried to batch multiple tool calls using
@@ -3542,9 +3706,10 @@ declare interface StructuredPromptOptions {
3542
3706
  * So a handler that receives it will not be called a second time this turn, and
3543
3707
  * the parent turn reports `'budget-exhausted'` in its own right.
3544
3708
  *
3545
- * Recovery guidance differs sharply by reason — see `docs/sub_agent.md`. Only
3546
- * `budget_exhausted` and `refusal` are terminal; retrying either just reproduces
3547
- * it. The rest are worth a retry or a question to the user.
3709
+ * Recovery guidance differs sharply by reason — see `docs/sub_agent.md`.
3710
+ * `budget_exhausted`, `refusal`, `provider_refused`, `context_exhausted` and a
3711
+ * `terminal` `ai_service_error` are terminal: retrying any of them with the same
3712
+ * request just reproduces it. The rest are worth a retry or a question to the user.
3548
3713
  *
3549
3714
  * @beta
3550
3715
  */
@@ -3557,13 +3722,29 @@ export declare type SubAgentFailureReason = 'max_iterations' | 'malformed_tool_c
3557
3722
  */
3558
3723
  | 'provider_refused'
3559
3724
  /**
3560
- * The sub-agent was stopped because continuing would have overrun the context
3561
- * window (GENC-1567). **Terminal for the PARENT turn too**, on the same
3562
- * reasoning as `budget_exhausted` and `provider_refused`: the parent shares the
3563
- * conversation whose size caused it, so calling the model again hits the same
3564
- * wall.
3725
+ * The sub-agent was stopped because continuing would have overrun the context window
3726
+ * (GENC-1567). Re-invoking it with the same conversation stops it the same way. The parent's turn
3727
+ * goes on, but it shares the conversation whose size caused this, so its own next model call is
3728
+ * likely to stop on the same guard.
3729
+ */
3730
+ | 'context_exhausted'
3731
+ /**
3732
+ * The app's AI service refused the sub-agent's request (GENC-1611). The outcome carries the
3733
+ * refusal's `code` and whether it is `terminal`: re-invoking the sub-agent with the same request
3734
+ * gets the same refusal, so recover without calling it again. Only a `VENDOR_UNREACHABLE` refusal
3735
+ * is not terminal. Even so, a refusal from your own vendor that is about the service (for example
3736
+ * `VENDOR_UNREACHABLE`) still ends this turn; retry on the user's next message, not in the
3737
+ * handler.
3738
+ *
3739
+ * **Terminal for the PARENT turn too only when** the refusal is about the service rather than the
3740
+ * child's own request (a missing or unusable key, no session or no right, the vendor unreachable)
3741
+ * and the child called the parent's own vendor, unless a completion or a handoff from the same
3742
+ * batch goes on past it. Otherwise the parent is told and its turn goes on. The user is shown a
3743
+ * key, access, model (`MODEL_NOT_ALLOWED`) or reachability refusal's sentence once per user
3744
+ * message; `REQUEST_TOO_LARGE`, `BAD_REQUEST` and `FALLBACKS_NOT_SUPPORTED` are left to the parent
3745
+ * and the console.
3565
3746
  */
3566
- | 'context_exhausted';
3747
+ | 'ai_service_error';
3567
3748
 
3568
3749
  /**
3569
3750
  * Options passed to `requestSubAgent` at call time.
@@ -3723,7 +3904,13 @@ export declare type TurnFailureReason = 'exception' | 'malformed-function-call'
3723
3904
  * the edge rather than issuing a request the provider would reject, so the
3724
3905
  * transcript stays intact and compaction is still possible.
3725
3906
  */
3726
- | 'context-exhausted';
3907
+ | 'context-exhausted'
3908
+ /**
3909
+ * The app's AI service refused the request with one of `AI_SERVICE_ERROR_CODES` (GENC-1611): a
3910
+ * configuration error such as a missing API key, or the vendor still unreachable after the
3911
+ * retries. The transcript says what is wrong in words for the user.
3912
+ */
3913
+ | 'ai-service-error';
3727
3914
 
3728
3915
  /**
3729
3916
  * Display name for each concrete AI vendor: the **single source** every
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/foundation-ai",
3
3
  "description": "Genesis Foundation AI - Provider-agnostic AI configuration and shared utilities",
4
- "version": "15.40.0",
4
+ "version": "15.41.0",
5
5
  "sideEffects": false,
6
6
  "license": "SEE LICENSE IN license.txt",
7
7
  "main": "dist/esm/index.js",
@@ -52,17 +52,17 @@
52
52
  }
53
53
  },
54
54
  "devDependencies": {
55
- "@genesislcap/foundation-testing": "15.40.0",
56
- "@genesislcap/genx": "15.40.0",
57
- "@genesislcap/rollup-builder": "15.40.0",
58
- "@genesislcap/ts-builder": "15.40.0",
59
- "@genesislcap/uvu-playwright-builder": "15.40.0",
60
- "@genesislcap/vite-builder": "15.40.0",
61
- "@genesislcap/webpack-builder": "15.40.0"
55
+ "@genesislcap/foundation-testing": "15.41.0",
56
+ "@genesislcap/genx": "15.41.0",
57
+ "@genesislcap/rollup-builder": "15.41.0",
58
+ "@genesislcap/ts-builder": "15.41.0",
59
+ "@genesislcap/uvu-playwright-builder": "15.41.0",
60
+ "@genesislcap/vite-builder": "15.41.0",
61
+ "@genesislcap/webpack-builder": "15.41.0"
62
62
  },
63
63
  "dependencies": {
64
- "@genesislcap/foundation-logger": "15.40.0",
65
- "@genesislcap/foundation-utils": "15.40.0",
64
+ "@genesislcap/foundation-logger": "15.41.0",
65
+ "@genesislcap/foundation-utils": "15.41.0",
66
66
  "@microsoft/fast-foundation": "2.50.0"
67
67
  },
68
68
  "repository": {