@genesislcap/foundation-ai 15.14.0 → 15.14.2

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.
@@ -4021,7 +4021,7 @@
4021
4021
  },
4022
4022
  {
4023
4023
  "kind": "Content",
4024
- "text": ";\n otherVendorAvailable?: boolean;\n };\n} | {\n reason: 'agent-handoff';\n summary: string;\n remainingTask: string;\n}"
4024
+ "text": ";\n otherVendorAvailable?: boolean;\n };\n providerRefused?: {\n vendorLabel: string;\n kind: 'spend' | 'auth';\n upstreamStatus?: number;\n upstreamType?: string;\n };\n} | {\n reason: 'agent-handoff';\n summary: string;\n remainingTask: string;\n}"
4025
4025
  },
4026
4026
  {
4027
4027
  "kind": "Content",
@@ -7493,6 +7493,29 @@
7493
7493
  "endIndex": 0
7494
7494
  }
7495
7495
  },
7496
+ {
7497
+ "kind": "Variable",
7498
+ "canonicalReference": "@genesislcap/foundation-ai!DEFAULT_PROVIDER_REFUSED_MESSAGE:var",
7499
+ "docComment": "/**\n * The single sentence shown to a user whose provider has refused — the transcript bubble the chat driver appends, and the string ai-service reuses for its own generation surfaces.\n *\n * **It names no cause, and that is the whole design.** Three constraints shaped it, and a rewrite has to clear all three:\n *\n * 1. **It must not sound like a cap.** The user can often see their remaining AI spend in the same UI, so *allowance / limit / quota / budget / usage / credit / balance* risk a visible self-contradiction: a message saying there is nothing left, next to a figure showing there is. \"isn't related to your account **or your usage**\" goes further than avoiding the words — it pre-empts the user checking that figure and concluding the message is wrong. 2. **It must not imply we cannot pay our bills.** *Billing / payment / funds / top up* all read as an unpaid invoice. Naming no cause means there is nothing to be embarrassed by and nothing for the user to contradict. 3. **It must not promise self-healing.** Clearing this needs a human to act out of band, so \"currently\" rather than \"temporarily\", and \"so it can be restored\" positively signals that someone must do something.\n *\n * It is also true of **both** {@link ProviderRefusalKind}s unchanged, which is what makes one sentence for two kinds honest rather than a fudge: for an expired key the provider *is* refusing, it *is* unrelated to the user's own account and usage, they *can* do nothing, and support *can* restore it.\n *\n * Vendor-neutral and deployment-neutral on purpose — the same bundle ships to white-labelled deployments where naming a vendor, or naming Genesis, would be wrong. A host that wants different wording sets `ChatDriverConfig.providerRefusedMessage`.\n *\n * @beta\n */\n",
7500
+ "excerptTokens": [
7501
+ {
7502
+ "kind": "Content",
7503
+ "text": "DEFAULT_PROVIDER_REFUSED_MESSAGE: "
7504
+ },
7505
+ {
7506
+ "kind": "Content",
7507
+ "text": "string"
7508
+ }
7509
+ ],
7510
+ "fileUrlPath": "src/transports/provider-refused.ts",
7511
+ "isReadonly": true,
7512
+ "releaseTag": "Beta",
7513
+ "name": "DEFAULT_PROVIDER_REFUSED_MESSAGE",
7514
+ "variableTypeTokenRange": {
7515
+ "startIndex": 1,
7516
+ "endIndex": 2
7517
+ }
7518
+ },
7496
7519
  {
7497
7520
  "kind": "TypeAlias",
7498
7521
  "canonicalReference": "@genesislcap/foundation-ai!FieldLike:type",
@@ -10217,6 +10240,339 @@
10217
10240
  }
10218
10241
  ]
10219
10242
  },
10243
+ {
10244
+ "kind": "Variable",
10245
+ "canonicalReference": "@genesislcap/foundation-ai!PROVIDER_REFUSED_CODE:var",
10246
+ "docComment": "/**\n * The proxy's machine-readable code for \"the upstream vendor refused this account\".\n *\n * Deliberately distinct from `BUDGET_EXCEEDED`: the ai-service proxy stamps this when it relays a provider refusal, so the client can tell a relayed upstream fault from the proxy's own budget verdict without inspecting the body at all.\n *\n * Exported from the package index — unlike its `BUDGET_EXCEEDED_CODE` sibling, which is internal — because the SERVER side of the contract lives in another repo (genesis-create's ai-service stamps this code onto its proxy responses). A hand-copied string literal there would drift on the first rename, and the drift would be silent: the client would simply stop classifying.\n *\n * @beta\n */\n",
10247
+ "excerptTokens": [
10248
+ {
10249
+ "kind": "Content",
10250
+ "text": "PROVIDER_REFUSED_CODE = "
10251
+ },
10252
+ {
10253
+ "kind": "Content",
10254
+ "text": "\"PROVIDER_REFUSED\""
10255
+ }
10256
+ ],
10257
+ "fileUrlPath": "src/transports/provider-refused.ts",
10258
+ "initializerTokenRange": {
10259
+ "startIndex": 1,
10260
+ "endIndex": 2
10261
+ },
10262
+ "isReadonly": true,
10263
+ "releaseTag": "Beta",
10264
+ "name": "PROVIDER_REFUSED_CODE",
10265
+ "variableTypeTokenRange": {
10266
+ "startIndex": 0,
10267
+ "endIndex": 0
10268
+ }
10269
+ },
10270
+ {
10271
+ "kind": "TypeAlias",
10272
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusalKind:type",
10273
+ "docComment": "/**\n * Which kind of refusal it was. **One error type, two kinds** — because the user-facing outcome is identical (one sentence, see {@link DEFAULT_PROVIDER_REFUSED_MESSAGE}) while the operator's fix is completely different.\n *\n * - `spend` — the account cannot spend: credit balance at zero, a workspace/organisation usage cap reached, or a billing/payment-information problem. Someone has to pay or raise a cap. - `auth` — the credential cannot be used: revoked, expired or malformed key (`401`), or a key without access to the requested model (`403`). Someone has to rotate a key or grant access.\n *\n * The alternative designs were both worse. A single type named for spend would *lie* about an expired key, and a debug timeline reading `provider-spend-refused` for an auth fault misleads the next engineer — which is precisely the trap the first draft of this feature fell into by naming itself `ProviderCreditExhaustedError` before a usage cap turned out to be in the same family. Two separate error types would duplicate a branch, a message and a wire code to express one outcome.\n *\n * @beta\n */\n",
10274
+ "excerptTokens": [
10275
+ {
10276
+ "kind": "Content",
10277
+ "text": "export type ProviderRefusalKind = "
10278
+ },
10279
+ {
10280
+ "kind": "Content",
10281
+ "text": "'spend' | 'auth'"
10282
+ },
10283
+ {
10284
+ "kind": "Content",
10285
+ "text": ";"
10286
+ }
10287
+ ],
10288
+ "fileUrlPath": "src/transports/provider-refused.ts",
10289
+ "releaseTag": "Beta",
10290
+ "name": "ProviderRefusalKind",
10291
+ "typeTokenRange": {
10292
+ "startIndex": 1,
10293
+ "endIndex": 2
10294
+ }
10295
+ },
10296
+ {
10297
+ "kind": "Class",
10298
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError:class",
10299
+ "docComment": "/**\n * Thrown when the upstream provider refuses the request because of the state of the **account** rather than anything about the request — see {@link ProviderRefusalKind}.\n *\n * Terminal and non-transient, in the same family as `ResponseTruncatedError` and `BudgetExhaustedError`: retrying cannot clear it, because nothing about the request is wrong. So both the transport retry ladder and the driver's transient-retry catch step aside for it and the failure surfaces immediately as the `'provider-refused'` `TurnFailureReason`.\n *\n * @beta\n */\n",
10300
+ "excerptTokens": [
10301
+ {
10302
+ "kind": "Content",
10303
+ "text": "export declare class ProviderRefusedError extends "
10304
+ },
10305
+ {
10306
+ "kind": "Reference",
10307
+ "text": "Error",
10308
+ "canonicalReference": "!Error:interface"
10309
+ },
10310
+ {
10311
+ "kind": "Content",
10312
+ "text": " "
10313
+ }
10314
+ ],
10315
+ "fileUrlPath": "src/transports/provider-refused.ts",
10316
+ "releaseTag": "Beta",
10317
+ "isAbstract": false,
10318
+ "name": "ProviderRefusedError",
10319
+ "preserveMemberOrder": false,
10320
+ "members": [
10321
+ {
10322
+ "kind": "Constructor",
10323
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError:constructor(1)",
10324
+ "docComment": "/**\n * Constructs a new instance of the `ProviderRefusedError` class\n */\n",
10325
+ "excerptTokens": [
10326
+ {
10327
+ "kind": "Content",
10328
+ "text": "constructor(\n vendorLabel: "
10329
+ },
10330
+ {
10331
+ "kind": "Content",
10332
+ "text": "string"
10333
+ },
10334
+ {
10335
+ "kind": "Content",
10336
+ "text": ", \n kind: "
10337
+ },
10338
+ {
10339
+ "kind": "Reference",
10340
+ "text": "ProviderRefusalKind",
10341
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusalKind:type"
10342
+ },
10343
+ {
10344
+ "kind": "Content",
10345
+ "text": ", \n upstreamStatus?: "
10346
+ },
10347
+ {
10348
+ "kind": "Content",
10349
+ "text": "number"
10350
+ },
10351
+ {
10352
+ "kind": "Content",
10353
+ "text": ", \n upstreamType?: "
10354
+ },
10355
+ {
10356
+ "kind": "Content",
10357
+ "text": "string"
10358
+ },
10359
+ {
10360
+ "kind": "Content",
10361
+ "text": ", \n detail?: "
10362
+ },
10363
+ {
10364
+ "kind": "Content",
10365
+ "text": "string"
10366
+ },
10367
+ {
10368
+ "kind": "Content",
10369
+ "text": ");"
10370
+ }
10371
+ ],
10372
+ "releaseTag": "Beta",
10373
+ "isProtected": false,
10374
+ "overloadIndex": 1,
10375
+ "parameters": [
10376
+ {
10377
+ "parameterName": "vendorLabel",
10378
+ "parameterTypeTokenRange": {
10379
+ "startIndex": 1,
10380
+ "endIndex": 2
10381
+ },
10382
+ "isOptional": false
10383
+ },
10384
+ {
10385
+ "parameterName": "kind",
10386
+ "parameterTypeTokenRange": {
10387
+ "startIndex": 3,
10388
+ "endIndex": 4
10389
+ },
10390
+ "isOptional": false
10391
+ },
10392
+ {
10393
+ "parameterName": "upstreamStatus",
10394
+ "parameterTypeTokenRange": {
10395
+ "startIndex": 5,
10396
+ "endIndex": 6
10397
+ },
10398
+ "isOptional": true
10399
+ },
10400
+ {
10401
+ "parameterName": "upstreamType",
10402
+ "parameterTypeTokenRange": {
10403
+ "startIndex": 7,
10404
+ "endIndex": 8
10405
+ },
10406
+ "isOptional": true
10407
+ },
10408
+ {
10409
+ "parameterName": "detail",
10410
+ "parameterTypeTokenRange": {
10411
+ "startIndex": 9,
10412
+ "endIndex": 10
10413
+ },
10414
+ "isOptional": true
10415
+ }
10416
+ ]
10417
+ },
10418
+ {
10419
+ "kind": "Property",
10420
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError#detail:member",
10421
+ "docComment": "/**\n * The provider's human-readable message, verbatim, for the log. Never shown to the user.\n */\n",
10422
+ "excerptTokens": [
10423
+ {
10424
+ "kind": "Content",
10425
+ "text": "readonly detail?: "
10426
+ },
10427
+ {
10428
+ "kind": "Content",
10429
+ "text": "string"
10430
+ },
10431
+ {
10432
+ "kind": "Content",
10433
+ "text": ";"
10434
+ }
10435
+ ],
10436
+ "isReadonly": true,
10437
+ "isOptional": true,
10438
+ "releaseTag": "Beta",
10439
+ "name": "detail",
10440
+ "propertyTypeTokenRange": {
10441
+ "startIndex": 1,
10442
+ "endIndex": 2
10443
+ },
10444
+ "isStatic": false,
10445
+ "isProtected": false,
10446
+ "isAbstract": false
10447
+ },
10448
+ {
10449
+ "kind": "Property",
10450
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError#kind:member",
10451
+ "docComment": "/**\n * Which kind of refusal — drives the log and the operator's fix, never the user's message.\n */\n",
10452
+ "excerptTokens": [
10453
+ {
10454
+ "kind": "Content",
10455
+ "text": "readonly kind: "
10456
+ },
10457
+ {
10458
+ "kind": "Reference",
10459
+ "text": "ProviderRefusalKind",
10460
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusalKind:type"
10461
+ },
10462
+ {
10463
+ "kind": "Content",
10464
+ "text": ";"
10465
+ }
10466
+ ],
10467
+ "isReadonly": true,
10468
+ "isOptional": false,
10469
+ "releaseTag": "Beta",
10470
+ "name": "kind",
10471
+ "propertyTypeTokenRange": {
10472
+ "startIndex": 1,
10473
+ "endIndex": 2
10474
+ },
10475
+ "isStatic": false,
10476
+ "isProtected": false,
10477
+ "isAbstract": false
10478
+ },
10479
+ {
10480
+ "kind": "Property",
10481
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError#upstreamStatus:member",
10482
+ "docComment": "/**\n * The provider's own HTTP status, when known.\n *\n * Diagnostic only, and deliberately not part of the classification contract: the observed spend refusals arrive as `400` (not the `402 billing_error` the docs describe), so a consumer that branched on this would be encoding one snapshot of provider behaviour.\n */\n",
10483
+ "excerptTokens": [
10484
+ {
10485
+ "kind": "Content",
10486
+ "text": "readonly upstreamStatus?: "
10487
+ },
10488
+ {
10489
+ "kind": "Content",
10490
+ "text": "number"
10491
+ },
10492
+ {
10493
+ "kind": "Content",
10494
+ "text": ";"
10495
+ }
10496
+ ],
10497
+ "isReadonly": true,
10498
+ "isOptional": true,
10499
+ "releaseTag": "Beta",
10500
+ "name": "upstreamStatus",
10501
+ "propertyTypeTokenRange": {
10502
+ "startIndex": 1,
10503
+ "endIndex": 2
10504
+ },
10505
+ "isStatic": false,
10506
+ "isProtected": false,
10507
+ "isAbstract": false
10508
+ },
10509
+ {
10510
+ "kind": "Property",
10511
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError#upstreamType:member",
10512
+ "docComment": "/**\n * The provider's own `error.type` (e.g. `'invalid_request_error'`, `'authentication_error'`).\n */\n",
10513
+ "excerptTokens": [
10514
+ {
10515
+ "kind": "Content",
10516
+ "text": "readonly upstreamType?: "
10517
+ },
10518
+ {
10519
+ "kind": "Content",
10520
+ "text": "string"
10521
+ },
10522
+ {
10523
+ "kind": "Content",
10524
+ "text": ";"
10525
+ }
10526
+ ],
10527
+ "isReadonly": true,
10528
+ "isOptional": true,
10529
+ "releaseTag": "Beta",
10530
+ "name": "upstreamType",
10531
+ "propertyTypeTokenRange": {
10532
+ "startIndex": 1,
10533
+ "endIndex": 2
10534
+ },
10535
+ "isStatic": false,
10536
+ "isProtected": false,
10537
+ "isAbstract": false
10538
+ },
10539
+ {
10540
+ "kind": "Property",
10541
+ "canonicalReference": "@genesislcap/foundation-ai!ProviderRefusedError#vendorLabel:member",
10542
+ "docComment": "/**\n * Vendor label of the transport that was refused (e.g. `'Anthropic'`).\n */\n",
10543
+ "excerptTokens": [
10544
+ {
10545
+ "kind": "Content",
10546
+ "text": "readonly vendorLabel: "
10547
+ },
10548
+ {
10549
+ "kind": "Content",
10550
+ "text": "string"
10551
+ },
10552
+ {
10553
+ "kind": "Content",
10554
+ "text": ";"
10555
+ }
10556
+ ],
10557
+ "isReadonly": true,
10558
+ "isOptional": false,
10559
+ "releaseTag": "Beta",
10560
+ "name": "vendorLabel",
10561
+ "propertyTypeTokenRange": {
10562
+ "startIndex": 1,
10563
+ "endIndex": 2
10564
+ },
10565
+ "isStatic": false,
10566
+ "isProtected": false,
10567
+ "isAbstract": false
10568
+ }
10569
+ ],
10570
+ "extendsTokenRange": {
10571
+ "startIndex": 1,
10572
+ "endIndex": 2
10573
+ },
10574
+ "implementsTokenRanges": []
10575
+ },
10220
10576
  {
10221
10577
  "kind": "Function",
10222
10578
  "canonicalReference": "@genesislcap/foundation-ai!registerAIProviderRegistry:function(1)",
@@ -10944,7 +11300,7 @@
10944
11300
  },
10945
11301
  {
10946
11302
  "kind": "Content",
10947
- "text": "'max_iterations' | 'malformed_tool_call' | 'empty_response' | 'unknown_tool_limit' | 'timeout' | 'response_truncated' | 'refusal' | 'budget_exhausted'"
11303
+ "text": "'max_iterations' | 'malformed_tool_call' | 'empty_response' | 'unknown_tool_limit' | 'timeout' | 'response_truncated' | 'refusal' | 'budget_exhausted'\n/**\n * The upstream vendor refused the account (GENC-1506). **Terminal for the PARENT turn too**, on the\n * same reasoning as `budget_exhausted`: the parent cannot recover by calling the model again,\n * because the very next call hits the same wall. N batched sub-agent calls would otherwise cost N\n * doomed children AND a doomed parent call.\n */\n | 'provider_refused'"
10948
11304
  },
10949
11305
  {
10950
11306
  "kind": "Content",
@@ -11483,7 +11839,7 @@
11483
11839
  {
11484
11840
  "kind": "TypeAlias",
11485
11841
  "canonicalReference": "@genesislcap/foundation-ai!TurnFailureReason:type",
11486
- "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. - `refusal` — a safety-classifier decline (e.g. Fable 5 `stop_reason: 'refusal'`), which returns empty content; deterministic, so it is not retried. - `budget-exhausted` — the AI-spend budget is used up and the proxy refused the request (`BudgetExhaustedError`, HTTP 402). Terminal, not retried: the budget must be raised out-of-band before any call can succeed.\n *\n * @beta\n */\n",
11842
+ "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. - `refusal` — a safety-classifier decline (e.g. Fable 5 `stop_reason: 'refusal'`), which returns empty content; deterministic, so it is not retried. - `budget-exhausted` — the AI-spend budget is used up and the proxy refused the request (`BudgetExhaustedError`, HTTP 402). Terminal, not retried: the budget must be raised out-of-band before any call can succeed. - `provider-refused` — the upstream VENDOR refused this account, not this request (`ProviderRefusedError`): its credit is gone, a usage cap is reached, or the credential is dead. Terminal and not retried. Distinct from `budget-exhausted` because the fix belongs to a different person — ours is \"raise the cap\", this is \"fix the provider account\".\n *\n * @beta\n */\n",
11487
11843
  "excerptTokens": [
11488
11844
  {
11489
11845
  "kind": "Content",
@@ -11491,7 +11847,7 @@
11491
11847
  },
11492
11848
  {
11493
11849
  "kind": "Content",
11494
- "text": "'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations' | 'response-truncated' | 'refusal' | 'budget-exhausted'"
11850
+ "text": "'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations' | 'response-truncated' | 'refusal' | 'budget-exhausted' | 'provider-refused'"
11495
11851
  },
11496
11852
  {
11497
11853
  "kind": "Content",
@@ -949,6 +949,30 @@ export declare type ChatDriverResult = {
949
949
  */
950
950
  otherVendorAvailable?: boolean;
951
951
  };
952
+ /**
953
+ * What the provider's refusal reported, present **only** when
954
+ * `failureReason === 'provider-refused'` (GENC-1506).
955
+ *
956
+ * Carried for the caller's LOG, not for its UI. The user-facing sentence is deliberately
957
+ * cause-free (see `DEFAULT_PROVIDER_REFUSED_MESSAGE`), which makes this the only place a
958
+ * consumer can tell "the account cannot spend" from "the credential is dead" — and those need
959
+ * different people to fix them. genesis-create's ai-service is the consumer this exists for:
960
+ * its generation surfaces re-throw across a WebSocket, so without this the kind would be lost
961
+ * at the driver boundary and the server's own Sentry alert could not say which fault it was.
962
+ *
963
+ * Omitted entirely (not set to `undefined`) on every other outcome, so a clean turn's shape
964
+ * stays byte-identical to the historical `{ reason: 'done' }`.
965
+ */
966
+ providerRefused?: {
967
+ /** Vendor label of the transport that was refused (e.g. `'Anthropic'`). */
968
+ vendorLabel: string;
969
+ /** `'spend'` (account cannot pay) or `'auth'` (credential cannot be used). */
970
+ kind: 'spend' | 'auth';
971
+ /** The provider's own HTTP status, when known. Diagnostic only — never branch on it. */
972
+ upstreamStatus?: number;
973
+ /** The provider's own `error.type`, when it sent one. */
974
+ upstreamType?: string;
975
+ };
952
976
  } | {
953
977
  reason: 'agent-handoff';
954
978
  summary: string;
@@ -2015,6 +2039,38 @@ export declare interface CriteriaInterpretContext {
2015
2039
  */
2016
2040
  export declare const DEFAULT_BUDGET_EXHAUSTED_MESSAGE = "You've reached your AI usage limit. Contact your administrator to raise it.";
2017
2041
 
2042
+ /**
2043
+ * The single sentence shown to a user whose provider has refused — the transcript bubble the chat
2044
+ * driver appends, and the string ai-service reuses for its own generation surfaces.
2045
+ *
2046
+ * **It names no cause, and that is the whole design.** Three constraints shaped it, and a rewrite
2047
+ * has to clear all three:
2048
+ *
2049
+ * 1. **It must not sound like a cap.** The user can often see their remaining AI spend in the same
2050
+ * UI, so *allowance / limit / quota / budget / usage / credit / balance* risk a visible
2051
+ * self-contradiction: a message saying there is nothing left, next to a figure showing there is.
2052
+ * "isn't related to your account **or your usage**" goes further than avoiding the words — it
2053
+ * pre-empts the user checking that figure and concluding the message is wrong.
2054
+ * 2. **It must not imply we cannot pay our bills.** *Billing / payment / funds / top up* all read as
2055
+ * an unpaid invoice. Naming no cause means there is nothing to be embarrassed by and nothing for
2056
+ * the user to contradict.
2057
+ * 3. **It must not promise self-healing.** Clearing this needs a human to act out of band, so
2058
+ * "currently" rather than "temporarily", and "so it can be restored" positively signals that
2059
+ * someone must do something.
2060
+ *
2061
+ * It is also true of **both** {@link ProviderRefusalKind}s unchanged, which is what makes one
2062
+ * sentence for two kinds honest rather than a fudge: for an expired key the provider *is* refusing,
2063
+ * it *is* unrelated to the user's own account and usage, they *can* do nothing, and support *can*
2064
+ * restore it.
2065
+ *
2066
+ * Vendor-neutral and deployment-neutral on purpose — the same bundle ships to white-labelled
2067
+ * deployments where naming a vendor, or naming Genesis, would be wrong. A host that wants different
2068
+ * wording sets `ChatDriverConfig.providerRefusedMessage`.
2069
+ *
2070
+ * @beta
2071
+ */
2072
+ export declare const DEFAULT_PROVIDER_REFUSED_MESSAGE: string;
2073
+
2018
2074
  /**
2019
2075
  * A field descriptor accepted by criteria interpretation utilities.
2020
2076
  * Can be a plain string name or an object with common field metadata properties.
@@ -2586,6 +2642,89 @@ export declare interface ObservableAIProviderRegistry extends AIProviderRegistry
2586
2642
  subscribe(listener: () => void): () => void;
2587
2643
  }
2588
2644
 
2645
+ /**
2646
+ * The proxy's machine-readable code for "the upstream vendor refused this account".
2647
+ *
2648
+ * Deliberately distinct from `BUDGET_EXCEEDED`: the ai-service proxy stamps this when it relays a
2649
+ * provider refusal, so the client can tell a relayed upstream fault from the proxy's own budget
2650
+ * verdict without inspecting the body at all.
2651
+ *
2652
+ * Exported from the package index — unlike its `BUDGET_EXCEEDED_CODE` sibling, which is internal —
2653
+ * because the SERVER side of the contract lives in another repo (genesis-create's ai-service stamps
2654
+ * this code onto its proxy responses). A hand-copied string literal there would drift on the first
2655
+ * rename, and the drift would be silent: the client would simply stop classifying.
2656
+ *
2657
+ * @beta
2658
+ */
2659
+ export declare const PROVIDER_REFUSED_CODE = "PROVIDER_REFUSED";
2660
+
2661
+ /**
2662
+ * Which kind of refusal it was. **One error type, two kinds** — because the user-facing outcome is
2663
+ * identical (one sentence, see {@link DEFAULT_PROVIDER_REFUSED_MESSAGE}) while the operator's fix is
2664
+ * completely different.
2665
+ *
2666
+ * - `spend` — the account cannot spend: credit balance at zero, a workspace/organisation usage cap
2667
+ * reached, or a billing/payment-information problem. Someone has to pay or raise a cap.
2668
+ * - `auth` — the credential cannot be used: revoked, expired or malformed key (`401`), or a key
2669
+ * without access to the requested model (`403`). Someone has to rotate a key or grant access.
2670
+ *
2671
+ * The alternative designs were both worse. A single type named for spend would *lie* about an
2672
+ * expired key, and a debug timeline reading `provider-spend-refused` for an auth fault misleads the
2673
+ * next engineer — which is precisely the trap the first draft of this feature fell into by naming
2674
+ * itself `ProviderCreditExhaustedError` before a usage cap turned out to be in the same family. Two
2675
+ * separate error types would duplicate a branch, a message and a wire code to express one outcome.
2676
+ *
2677
+ * @beta
2678
+ */
2679
+ export declare type ProviderRefusalKind = 'spend' | 'auth';
2680
+
2681
+ /**
2682
+ * Thrown when the upstream provider refuses the request because of the state of the **account**
2683
+ * rather than anything about the request — see {@link ProviderRefusalKind}.
2684
+ *
2685
+ * Terminal and non-transient, in the same family as `ResponseTruncatedError` and
2686
+ * `BudgetExhaustedError`: retrying cannot clear it, because nothing about the request is wrong. So
2687
+ * both the transport retry ladder and the driver's transient-retry catch step aside for it and the
2688
+ * failure surfaces immediately as the `'provider-refused'` `TurnFailureReason`.
2689
+ *
2690
+ * @beta
2691
+ */
2692
+ export declare class ProviderRefusedError extends Error {
2693
+ /** Vendor label of the transport that was refused (e.g. `'Anthropic'`). */
2694
+ readonly vendorLabel: string;
2695
+ /** Which kind of refusal — drives the log and the operator's fix, never the user's message. */
2696
+ readonly kind: ProviderRefusalKind;
2697
+ /**
2698
+ * The provider's own HTTP status, when known.
2699
+ *
2700
+ * Diagnostic only, and deliberately not part of the classification contract: the observed spend
2701
+ * refusals arrive as `400` (not the `402 billing_error` the docs describe), so a consumer that
2702
+ * branched on this would be encoding one snapshot of provider behaviour.
2703
+ */
2704
+ readonly upstreamStatus?: number;
2705
+ /** The provider's own `error.type` (e.g. `'invalid_request_error'`, `'authentication_error'`). */
2706
+ readonly upstreamType?: string;
2707
+ /** The provider's human-readable message, verbatim, for the log. Never shown to the user. */
2708
+ readonly detail?: string;
2709
+ constructor(
2710
+ /** Vendor label of the transport that was refused (e.g. `'Anthropic'`). */
2711
+ vendorLabel: string,
2712
+ /** Which kind of refusal — drives the log and the operator's fix, never the user's message. */
2713
+ kind: ProviderRefusalKind,
2714
+ /**
2715
+ * The provider's own HTTP status, when known.
2716
+ *
2717
+ * Diagnostic only, and deliberately not part of the classification contract: the observed spend
2718
+ * refusals arrive as `400` (not the `402 billing_error` the docs describe), so a consumer that
2719
+ * branched on this would be encoding one snapshot of provider behaviour.
2720
+ */
2721
+ upstreamStatus?: number,
2722
+ /** The provider's own `error.type` (e.g. `'invalid_request_error'`, `'authentication_error'`). */
2723
+ upstreamType?: string,
2724
+ /** The provider's human-readable message, verbatim, for the log. Never shown to the user. */
2725
+ detail?: string);
2726
+ }
2727
+
2589
2728
  /**
2590
2729
  * Registers a host-supplied {@link (AIProviderRegistry:interface)} instance on
2591
2730
  * the DI container under the {@link (AIProviderRegistry:variable)} token.
@@ -2801,7 +2940,14 @@ declare interface StructuredPromptOptions {
2801
2940
  *
2802
2941
  * @beta
2803
2942
  */
2804
- export declare type SubAgentFailureReason = 'max_iterations' | 'malformed_tool_call' | 'empty_response' | 'unknown_tool_limit' | 'timeout' | 'response_truncated' | 'refusal' | 'budget_exhausted';
2943
+ export declare type SubAgentFailureReason = 'max_iterations' | 'malformed_tool_call' | 'empty_response' | 'unknown_tool_limit' | 'timeout' | 'response_truncated' | 'refusal' | 'budget_exhausted'
2944
+ /**
2945
+ * The upstream vendor refused the account (GENC-1506). **Terminal for the PARENT turn too**, on the
2946
+ * same reasoning as `budget_exhausted`: the parent cannot recover by calling the model again,
2947
+ * because the very next call hits the same wall. N batched sub-agent calls would otherwise cost N
2948
+ * doomed children AND a doomed parent call.
2949
+ */
2950
+ | 'provider_refused';
2805
2951
 
2806
2952
  /**
2807
2953
  * Options passed to `requestSubAgent` at call time.
@@ -2925,10 +3071,15 @@ export declare interface TokenRates {
2925
3071
  * - `budget-exhausted` — the AI-spend budget is used up and the proxy refused the request
2926
3072
  * (`BudgetExhaustedError`, HTTP 402). Terminal, not retried: the
2927
3073
  * budget must be raised out-of-band before any call can succeed.
3074
+ * - `provider-refused` — the upstream VENDOR refused this account, not this request
3075
+ * (`ProviderRefusedError`): its credit is gone, a usage cap is reached,
3076
+ * or the credential is dead. Terminal and not retried. Distinct from
3077
+ * `budget-exhausted` because the fix belongs to a different person —
3078
+ * ours is "raise the cap", this is "fix the provider account".
2928
3079
  *
2929
3080
  * @beta
2930
3081
  */
2931
- export declare type TurnFailureReason = 'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations' | 'response-truncated' | 'refusal' | 'budget-exhausted';
3082
+ export declare type TurnFailureReason = 'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations' | 'response-truncated' | 'refusal' | 'budget-exhausted' | 'provider-refused';
2932
3083
 
2933
3084
  /**
2934
3085
  * 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.14.0",
4
+ "version": "15.14.2",
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.14.0",
56
- "@genesislcap/genx": "15.14.0",
57
- "@genesislcap/rollup-builder": "15.14.0",
58
- "@genesislcap/ts-builder": "15.14.0",
59
- "@genesislcap/uvu-playwright-builder": "15.14.0",
60
- "@genesislcap/vite-builder": "15.14.0",
61
- "@genesislcap/webpack-builder": "15.14.0"
55
+ "@genesislcap/foundation-testing": "15.14.2",
56
+ "@genesislcap/genx": "15.14.2",
57
+ "@genesislcap/rollup-builder": "15.14.2",
58
+ "@genesislcap/ts-builder": "15.14.2",
59
+ "@genesislcap/uvu-playwright-builder": "15.14.2",
60
+ "@genesislcap/vite-builder": "15.14.2",
61
+ "@genesislcap/webpack-builder": "15.14.2"
62
62
  },
63
63
  "dependencies": {
64
- "@genesislcap/foundation-logger": "15.14.0",
65
- "@genesislcap/foundation-utils": "15.14.0",
64
+ "@genesislcap/foundation-logger": "15.14.2",
65
+ "@genesislcap/foundation-utils": "15.14.2",
66
66
  "@microsoft/fast-foundation": "2.50.0"
67
67
  },
68
68
  "repository": {
@@ -73,5 +73,5 @@
73
73
  "publishConfig": {
74
74
  "access": "public"
75
75
  },
76
- "gitHead": "d84656a0872b2274be6f6b20a87a28504b48ed2a"
76
+ "gitHead": "bd1cb4a789548d9c7c6777f8adb7bce3f43c3317"
77
77
  }