@genesislcap/ai-assistant 15.13.0 → 15.13.1-GENC-1506.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +27 -0
- package/dist/ai-assistant.d.ts +80 -13
- package/dist/chat-driver.cjs +253 -11
- package/dist/chat-driver.cjs.map +3 -3
- package/dist/chat-driver.mjs +250 -11
- package/dist/chat-driver.mjs.map +3 -3
- package/dist/custom-elements.json +444 -353
- package/dist/dts/chat-driver-node.d.ts +1 -1
- package/dist/dts/chat-driver-node.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +49 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +31 -13
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/esm/chat-driver-node.js +9 -1
- package/dist/esm/components/chat-driver/chat-driver.js +130 -18
- package/dist/esm/components/chat-driver/chat-driver.test.js +94 -1
- package/dist/esm/main/blocked-state.test.js +56 -5
- package/dist/esm/main/main.js +106 -27
- package/dist/esm/state/debug-event-log.js +1 -1
- package/dist/esm/state/debug-event-log.test.js +2 -0
- package/docs/migration-GENC-1464.md +19 -8
- package/docs/migration-GENC-1506.md +177 -0
- package/package.json +17 -17
- package/src/chat-driver-node.ts +10 -0
- package/src/components/chat-driver/chat-driver.test.ts +140 -0
- package/src/components/chat-driver/chat-driver.ts +162 -4
- package/src/main/blocked-state.test.ts +73 -5
- package/src/main/main.ts +98 -25
- package/src/state/debug-event-log.test.ts +2 -0
- package/src/state/debug-event-log.ts +1 -1
|
@@ -5923,6 +5923,33 @@
|
|
|
5923
5923
|
"endIndex": 3
|
|
5924
5924
|
}
|
|
5925
5925
|
},
|
|
5926
|
+
{
|
|
5927
|
+
"kind": "PropertySignature",
|
|
5928
|
+
"canonicalReference": "@genesislcap/ai-assistant!ChatDriverConfig#providerRefusedMessage:member",
|
|
5929
|
+
"docComment": "/**\n * Overrides the transcript sentence shown when the upstream PROVIDER refuses the account (GENC-1506) — see `DEFAULT_PROVIDER_REFUSED_MESSAGE`.\n *\n * Separate from `budgetExhaustedMessage` because the two conditions are separate: ours is a spend cap a host administrator can raise, this is the vendor declining to serve us at all. A host that white-labels one will usually want to white-label both, but conflating them into one field would force identical copy on two situations with different remedies.\n *\n * Rarely needed: unlike the budget default, the shipped sentence names no vendor and no Genesis, so it is already safe for a white-labelled deployment.\n */\n",
|
|
5930
|
+
"excerptTokens": [
|
|
5931
|
+
{
|
|
5932
|
+
"kind": "Content",
|
|
5933
|
+
"text": "providerRefusedMessage?: "
|
|
5934
|
+
},
|
|
5935
|
+
{
|
|
5936
|
+
"kind": "Content",
|
|
5937
|
+
"text": "string"
|
|
5938
|
+
},
|
|
5939
|
+
{
|
|
5940
|
+
"kind": "Content",
|
|
5941
|
+
"text": ";"
|
|
5942
|
+
}
|
|
5943
|
+
],
|
|
5944
|
+
"isReadonly": false,
|
|
5945
|
+
"isOptional": true,
|
|
5946
|
+
"releaseTag": "Beta",
|
|
5947
|
+
"name": "providerRefusedMessage",
|
|
5948
|
+
"propertyTypeTokenRange": {
|
|
5949
|
+
"startIndex": 1,
|
|
5950
|
+
"endIndex": 2
|
|
5951
|
+
}
|
|
5952
|
+
},
|
|
5926
5953
|
{
|
|
5927
5954
|
"kind": "PropertySignature",
|
|
5928
5955
|
"canonicalReference": "@genesislcap/ai-assistant!ChatDriverConfig#sessionKey:member",
|
package/dist/ai-assistant.d.ts
CHANGED
|
@@ -1524,6 +1524,24 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1524
1524
|
* and the bubble is composed per wall by {@link ChatDriver.budgetExhaustedBubble}.
|
|
1525
1525
|
*/
|
|
1526
1526
|
private readonly budgetExhaustedMessageOverride?;
|
|
1527
|
+
/**
|
|
1528
|
+
* Host override for the provider-refusal sentence, from
|
|
1529
|
+
* `ChatDriverConfig.providerRefusedMessage`. `undefined` means no override, so the shipped default
|
|
1530
|
+
* is used verbatim.
|
|
1531
|
+
*/
|
|
1532
|
+
private readonly providerRefusedMessageOverride?;
|
|
1533
|
+
/**
|
|
1534
|
+
* A provider refusal was observed this turn, so the tool loop must not call the model again
|
|
1535
|
+
* (GENC-1506).
|
|
1536
|
+
*
|
|
1537
|
+
* Exists for the SUB-AGENT path only, exactly like `budgetExhaustedThisTurn`: this driver's own
|
|
1538
|
+
* refusal returns straight out of the catch, whereas a child's refusal reaches the parent as a tool
|
|
1539
|
+
* result, and without this flag the loop would issue another call into the same wall — N batched
|
|
1540
|
+
* children costing N doomed calls plus a doomed parent one.
|
|
1541
|
+
*/
|
|
1542
|
+
private providerRefusedThisTurn;
|
|
1543
|
+
/** The refusal detail latched off a sub-agent's failure, for the parent's own result. */
|
|
1544
|
+
private providerRefusedDetail?;
|
|
1527
1545
|
/**
|
|
1528
1546
|
* Set the moment a budget wall is observed anywhere in this turn — this
|
|
1529
1547
|
* driver's own 402, or a sub-agent's (which surfaces here only as a
|
|
@@ -1629,6 +1647,24 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1629
1647
|
*/
|
|
1630
1648
|
private budgetExhaustedBubble;
|
|
1631
1649
|
reportBudgetExhausted(e: BudgetExhaustedError, pendingUserMessage?: ChatMessage): ChatDriverResult;
|
|
1650
|
+
/**
|
|
1651
|
+
* The sentence shown when the upstream provider refuses (GENC-1506).
|
|
1652
|
+
*
|
|
1653
|
+
* Deliberately far simpler than `budgetExhaustedBubble`: no vendor name, no figures, no
|
|
1654
|
+
* switch-provider advice, and no branching on `kind`. The copy is cause-free by design — a user who
|
|
1655
|
+
* can see their own remaining spend must not be told about a limit, and we must not imply the bill
|
|
1656
|
+
* has gone unpaid — so there is nothing here to compose. A host override wins verbatim.
|
|
1657
|
+
*/
|
|
1658
|
+
private providerRefusedBubble;
|
|
1659
|
+
/**
|
|
1660
|
+
* Terminal provider-refusal outcome. Mirrors `reportBudgetExhausted` so the two walls behave
|
|
1661
|
+
* identically from the caller's side, while keeping their diagnostics distinct.
|
|
1662
|
+
*
|
|
1663
|
+
* `kind` reaches the debug log and the result but never the transcript: it is what lets an operator
|
|
1664
|
+
* tell "top up the account" from "rotate the key", and with cause-free user copy this is the only
|
|
1665
|
+
* place that distinction survives.
|
|
1666
|
+
*/
|
|
1667
|
+
private reportProviderRefused;
|
|
1632
1668
|
/** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
|
|
1633
1669
|
private static failureReasonOf;
|
|
1634
1670
|
/**
|
|
@@ -1980,6 +2016,19 @@ export declare interface ChatDriverConfig {
|
|
|
1980
2016
|
* reversible change.
|
|
1981
2017
|
*/
|
|
1982
2018
|
budgetExhaustedMessage?: string;
|
|
2019
|
+
/**
|
|
2020
|
+
* Overrides the transcript sentence shown when the upstream PROVIDER refuses the account
|
|
2021
|
+
* (GENC-1506) — see `DEFAULT_PROVIDER_REFUSED_MESSAGE`.
|
|
2022
|
+
*
|
|
2023
|
+
* Separate from `budgetExhaustedMessage` because the two conditions are separate: ours is a spend
|
|
2024
|
+
* cap a host administrator can raise, this is the vendor declining to serve us at all. A host that
|
|
2025
|
+
* white-labels one will usually want to white-label both, but conflating them into one field would
|
|
2026
|
+
* force identical copy on two situations with different remedies.
|
|
2027
|
+
*
|
|
2028
|
+
* Rarely needed: unlike the budget default, the shipped sentence names no vendor and no Genesis, so
|
|
2029
|
+
* it is already safe for a white-labelled deployment.
|
|
2030
|
+
*/
|
|
2031
|
+
providerRefusedMessage?: string;
|
|
1983
2032
|
}
|
|
1984
2033
|
|
|
1985
2034
|
export { ChatFallback }
|
|
@@ -2314,9 +2363,9 @@ export declare interface FallbackAgentConfig extends BaseAgentConfig {
|
|
|
2314
2363
|
*
|
|
2315
2364
|
* @internal
|
|
2316
2365
|
*/
|
|
2317
|
-
export declare function formatBlockedReason(budget?: Extract<ChatDriverResult, {
|
|
2366
|
+
export declare function formatBlockedReason(budget?: Partial<Extract<ChatDriverResult, {
|
|
2318
2367
|
reason: 'done';
|
|
2319
|
-
}>['budget']
|
|
2368
|
+
}>['budget']>, vendor?: AIProviderType): string | undefined;
|
|
2320
2369
|
|
|
2321
2370
|
/**
|
|
2322
2371
|
* Foundation AI Assistant component.
|
|
@@ -2778,11 +2827,18 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2778
2827
|
*/
|
|
2779
2828
|
private get relevantBlockedVendors();
|
|
2780
2829
|
/**
|
|
2781
|
-
* Whether the blocked banner has anything to say — a vendor-agnostic block,
|
|
2782
|
-
*
|
|
2783
|
-
*
|
|
2784
|
-
*
|
|
2785
|
-
*
|
|
2830
|
+
* Whether the blocked banner has anything to say — a vendor-agnostic block, or
|
|
2831
|
+
* a partial wall that the NEXT send would actually hit.
|
|
2832
|
+
*
|
|
2833
|
+
* Partial exhaustion is announced only while the ACTIVE vendor (the registry
|
|
2834
|
+
* default the next send resolves to) is among the walled ones. A user who has
|
|
2835
|
+
* switched to a vendor with headroom has already acted on the advice —
|
|
2836
|
+
* keeping the banner up past that point nagged about a vendor they just left
|
|
2837
|
+
* (GENC-1464 tester feedback), while switching BACK to the walled vendor
|
|
2838
|
+
* brings the banner straight back. Two conservative fallbacks keep it
|
|
2839
|
+
* fail-informative: while the statuses are still loading the active vendor is
|
|
2840
|
+
* unknowable, so any reachable-and-walled vendor still announces; and a
|
|
2841
|
+
* vendor-agnostic block announces always, exactly as before.
|
|
2786
2842
|
*
|
|
2787
2843
|
* Reads the reachability-filtered list for the same reason the copy does — a
|
|
2788
2844
|
* host that has swapped its registry away from the walled vendor has nothing
|
|
@@ -2809,6 +2865,15 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2809
2865
|
* @internal
|
|
2810
2866
|
*/
|
|
2811
2867
|
get suggestionsBlocked(): boolean;
|
|
2868
|
+
/**
|
|
2869
|
+
* The vendor the NEXT send resolves to — the registry default's provider, as
|
|
2870
|
+
* reported through `providerStatuses`. `undefined` while the statuses are
|
|
2871
|
+
* still loading (or when the default reports the `'none'` sentinel), which
|
|
2872
|
+
* every consumer treats conservatively.
|
|
2873
|
+
*
|
|
2874
|
+
* @internal
|
|
2875
|
+
*/
|
|
2876
|
+
private get activeVendor();
|
|
2812
2877
|
/**
|
|
2813
2878
|
* Explanation shown in the blocked banner, or `null` for the element's default
|
|
2814
2879
|
* copy. Only meaningful while {@link FoundationAiAssistant.blocked} is true;
|
|
@@ -2957,12 +3022,14 @@ export declare class FoundationAiAssistant extends GenesisElement {
|
|
|
2957
3022
|
* vendors watched all of it vanish the moment a second one walled — replaced by
|
|
2958
3023
|
* generic copy, at the exact moment the situation got worse.
|
|
2959
3024
|
*
|
|
2960
|
-
* The compact
|
|
2961
|
-
*
|
|
2962
|
-
*
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
2965
|
-
*
|
|
3025
|
+
* The compact list is the DEFAULT shape for several walls, now carrying each
|
|
3026
|
+
* vendor's figures: "AI usage limits are reached for Gemini ($62.34 of $50.00)
|
|
3027
|
+
* and Anthropic ($31.10 of $30.00)." A driver latch stores no copy of its own
|
|
3028
|
+
* (it stores the figures, which this composes from), so that shape is what the
|
|
3029
|
+
* real budget flow produces. As soon as any walled vendor carries HOST copy,
|
|
3030
|
+
* which cannot be folded into a list item, the branch lists one statement per
|
|
3031
|
+
* vendor instead — falling back to composed copy for the ones that have none, so
|
|
3032
|
+
* the sentence set stays complete rather than silently naming a subset.
|
|
2966
3033
|
*
|
|
2967
3034
|
* @internal
|
|
2968
3035
|
*/
|
package/dist/chat-driver.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(chat_driver_node_exports, {
|
|
|
26
26
|
AnthropicProvider: () => AnthropicProvider,
|
|
27
27
|
AnthropicTransport: () => AnthropicTransport,
|
|
28
28
|
ChatDriver: () => ChatDriver,
|
|
29
|
+
DEFAULT_PROVIDER_REFUSED_MESSAGE: () => DEFAULT_PROVIDER_REFUSED_MESSAGE,
|
|
29
30
|
GEMINI_CACHED_INPUT_MULTIPLIER: () => GEMINI_CACHED_INPUT_MULTIPLIER,
|
|
30
31
|
GEMINI_LONG_CONTEXT_THRESHOLD: () => GEMINI_LONG_CONTEXT_THRESHOLD,
|
|
31
32
|
GeminiProvider: () => GeminiProvider,
|
|
@@ -33,6 +34,8 @@ __export(chat_driver_node_exports, {
|
|
|
33
34
|
MutableAIProviderRegistry: () => MutableAIProviderRegistry,
|
|
34
35
|
NOOP_ACTIVITY_BUS: () => NOOP_ACTIVITY_BUS,
|
|
35
36
|
OrchestratingDriver: () => OrchestratingDriver,
|
|
37
|
+
PROVIDER_REFUSED_CODE: () => PROVIDER_REFUSED_CODE,
|
|
38
|
+
ProviderRefusedError: () => ProviderRefusedError,
|
|
36
39
|
REQUEST_CONTINUATION_TOOL: () => REQUEST_CONTINUATION_TOOL,
|
|
37
40
|
SUPPORTED_ANTHROPIC_MODEL_IDS: () => SUPPORTED_ANTHROPIC_MODEL_IDS,
|
|
38
41
|
SUPPORTED_GEMINI_MODEL_IDS: () => SUPPORTED_GEMINI_MODEL_IDS,
|
|
@@ -1105,6 +1108,78 @@ function budgetExhaustedFrom(vendorLabel, payload, fallbackDetail) {
|
|
|
1105
1108
|
});
|
|
1106
1109
|
}
|
|
1107
1110
|
|
|
1111
|
+
// ../../foundation-ai/dist/esm/transports/provider-refused.js
|
|
1112
|
+
var PROVIDER_REFUSED_CODE = "PROVIDER_REFUSED";
|
|
1113
|
+
var DEFAULT_PROVIDER_REFUSED_MESSAGE = "AI requests are currently being refused by the provider. This isn't related to your account or your usage and needs no action from you \u2014 please contact your support team so it can be restored.";
|
|
1114
|
+
var ProviderRefusedError = class extends Error {
|
|
1115
|
+
constructor(vendorLabel, kind, upstreamStatus, upstreamType, detail) {
|
|
1116
|
+
super(`${vendorLabel} refused the request (${kind})` + (upstreamStatus != null ? ` \u2014 HTTP ${upstreamStatus}` : "") + (upstreamType ? ` ${upstreamType}` : "") + (detail ? `: ${detail}` : "") + ". Retrying will not clear this \u2014 the provider account must be fixed.");
|
|
1117
|
+
this.vendorLabel = vendorLabel;
|
|
1118
|
+
this.kind = kind;
|
|
1119
|
+
this.upstreamStatus = upstreamStatus;
|
|
1120
|
+
this.upstreamType = upstreamType;
|
|
1121
|
+
this.detail = detail;
|
|
1122
|
+
this.name = "ProviderRefusedError";
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
var ANTHROPIC_SPEND_PHRASES = Object.freeze([
|
|
1126
|
+
/credit balance/i,
|
|
1127
|
+
/usage limit/i,
|
|
1128
|
+
/spend limit/i,
|
|
1129
|
+
/insufficient (credit|funds|balance)/i
|
|
1130
|
+
]);
|
|
1131
|
+
var ANTHROPIC_AUTH_TYPES = Object.freeze([
|
|
1132
|
+
"authentication_error",
|
|
1133
|
+
"permission_error"
|
|
1134
|
+
]);
|
|
1135
|
+
function anthropicErrorBody(payload) {
|
|
1136
|
+
var _a;
|
|
1137
|
+
const outer = typeof payload === "object" && payload !== null ? payload : {};
|
|
1138
|
+
for (const candidate of [
|
|
1139
|
+
outer.error,
|
|
1140
|
+
(_a = outer.details) === null || _a === void 0 ? void 0 : _a.error
|
|
1141
|
+
]) {
|
|
1142
|
+
if (typeof candidate === "object" && candidate !== null) {
|
|
1143
|
+
const { type, message } = candidate;
|
|
1144
|
+
return {
|
|
1145
|
+
type: typeof type === "string" ? type : void 0,
|
|
1146
|
+
message: typeof message === "string" ? message : void 0
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return void 0;
|
|
1151
|
+
}
|
|
1152
|
+
function providerRefusalOf(vendor, code, payload) {
|
|
1153
|
+
if (code === PROVIDER_REFUSED_CODE) {
|
|
1154
|
+
const kind = payload === null || payload === void 0 ? void 0 : payload.kind;
|
|
1155
|
+
return kind === "auth" ? "auth" : "spend";
|
|
1156
|
+
}
|
|
1157
|
+
if (vendor !== "anthropic")
|
|
1158
|
+
return void 0;
|
|
1159
|
+
const body = anthropicErrorBody(payload);
|
|
1160
|
+
if (!(body === null || body === void 0 ? void 0 : body.type))
|
|
1161
|
+
return void 0;
|
|
1162
|
+
if (ANTHROPIC_AUTH_TYPES.includes(body.type))
|
|
1163
|
+
return "auth";
|
|
1164
|
+
if (body.type === "billing_error")
|
|
1165
|
+
return "spend";
|
|
1166
|
+
if (body.type === "invalid_request_error" && body.message) {
|
|
1167
|
+
const message = body.message;
|
|
1168
|
+
if (ANTHROPIC_SPEND_PHRASES.some((phrase) => phrase.test(message)))
|
|
1169
|
+
return "spend";
|
|
1170
|
+
}
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
function providerRefusedFrom(vendorLabel, kind, upstreamStatus, payload, fallbackDetail) {
|
|
1174
|
+
var _a;
|
|
1175
|
+
const body = anthropicErrorBody(payload);
|
|
1176
|
+
return new ProviderRefusedError(vendorLabel, kind, upstreamStatus, body === null || body === void 0 ? void 0 : body.type, (_a = body === null || body === void 0 ? void 0 : body.message) !== null && _a !== void 0 ? _a : fallbackDetail);
|
|
1177
|
+
}
|
|
1178
|
+
function vendorOfTransportLabel(vendorLabel) {
|
|
1179
|
+
const entry = Object.entries(VENDOR_LABELS).find(([, label]) => label === vendorLabel);
|
|
1180
|
+
return entry === null || entry === void 0 ? void 0 : entry[0];
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1108
1183
|
// ../../foundation-ai/dist/esm/types/config.types.js
|
|
1109
1184
|
var SUPPORTED_GEMINI_MODEL_IDS = [
|
|
1110
1185
|
"gemini-2.5-pro",
|
|
@@ -1523,13 +1598,70 @@ function readFramedBody(stream, onChunk, vendorLabel, stallTimeout) {
|
|
|
1523
1598
|
});
|
|
1524
1599
|
}
|
|
1525
1600
|
|
|
1601
|
+
// ../../foundation-ai/dist/esm/transports/retry-hints.js
|
|
1602
|
+
var MAX_SERVER_REQUESTED_BACKOFF_MS = 6e4;
|
|
1603
|
+
var SHOULD_RETRY_HEADER = "x-should-retry";
|
|
1604
|
+
var MS_PER_SECOND = 1e3;
|
|
1605
|
+
function shouldNotRetry(headers) {
|
|
1606
|
+
var _a;
|
|
1607
|
+
const value = (_a = headers === null || headers === void 0 ? void 0 : headers.get) === null || _a === void 0 ? void 0 : _a.call(headers, SHOULD_RETRY_HEADER);
|
|
1608
|
+
return typeof value === "string" && value.trim().toLowerCase() === "false";
|
|
1609
|
+
}
|
|
1610
|
+
function retryAfterMs(headers) {
|
|
1611
|
+
var _a;
|
|
1612
|
+
const raw = (_a = headers === null || headers === void 0 ? void 0 : headers.get) === null || _a === void 0 ? void 0 : _a.call(headers, "retry-after");
|
|
1613
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
1614
|
+
return void 0;
|
|
1615
|
+
const seconds = Number(raw.trim());
|
|
1616
|
+
if (Number.isFinite(seconds))
|
|
1617
|
+
return Math.max(0, seconds * MS_PER_SECOND);
|
|
1618
|
+
const at = Date.parse(raw);
|
|
1619
|
+
if (Number.isNaN(at))
|
|
1620
|
+
return void 0;
|
|
1621
|
+
return Math.max(0, at - Date.now());
|
|
1622
|
+
}
|
|
1623
|
+
function retryInfoMs(payload) {
|
|
1624
|
+
const outer = typeof payload === "object" && payload !== null ? payload : {};
|
|
1625
|
+
const framed = typeof outer.details === "object" && outer.details !== null ? outer.details : void 0;
|
|
1626
|
+
for (const holder of [outer.error, framed === null || framed === void 0 ? void 0 : framed.error]) {
|
|
1627
|
+
const details = holder === null || holder === void 0 ? void 0 : holder.details;
|
|
1628
|
+
if (!Array.isArray(details))
|
|
1629
|
+
continue;
|
|
1630
|
+
for (const entry of details) {
|
|
1631
|
+
const type = entry === null || entry === void 0 ? void 0 : entry["@type"];
|
|
1632
|
+
if (typeof type !== "string" || !type.endsWith("google.rpc.RetryInfo"))
|
|
1633
|
+
continue;
|
|
1634
|
+
const delay = entry.retryDelay;
|
|
1635
|
+
if (typeof delay !== "string")
|
|
1636
|
+
continue;
|
|
1637
|
+
const seconds = Number(delay.replace(/s$/, ""));
|
|
1638
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
1639
|
+
return seconds * MS_PER_SECOND;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
return void 0;
|
|
1643
|
+
}
|
|
1644
|
+
function resolveBackoffMs(attempt, baseMs, serverMs) {
|
|
1645
|
+
const ladder = baseMs * Math.pow(2, attempt);
|
|
1646
|
+
if (serverMs == null || !Number.isFinite(serverMs))
|
|
1647
|
+
return ladder;
|
|
1648
|
+
return Math.max(ladder, Math.min(serverMs, MAX_SERVER_REQUESTED_BACKOFF_MS));
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1526
1651
|
// ../../foundation-ai/dist/esm/transports/post-with-retry.js
|
|
1527
1652
|
var MAX_RETRIES = 5;
|
|
1528
1653
|
function postWithRetry(options) {
|
|
1529
1654
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1530
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1655
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1531
1656
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal } = options;
|
|
1532
|
-
const
|
|
1657
|
+
const vendor = vendorOfTransportLabel(vendorLabel);
|
|
1658
|
+
const backoff = (attempt, serverMs) => {
|
|
1659
|
+
const waitMs = resolveBackoffMs(attempt, backoffBaseMs, serverMs);
|
|
1660
|
+
if (serverMs != null && waitMs > backoffBaseMs * Math.pow(2, attempt)) {
|
|
1661
|
+
logger.warn(`${vendorLabel}Transport: honouring the server's requested retry delay (${Math.round(waitMs)}ms, asked ${Math.round(serverMs)}ms)`);
|
|
1662
|
+
}
|
|
1663
|
+
return abortableDelay(waitMs, signal);
|
|
1664
|
+
};
|
|
1533
1665
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
|
|
1534
1666
|
const timeoutController = new AbortController();
|
|
1535
1667
|
const startedAt = performance.now();
|
|
@@ -1563,24 +1695,33 @@ function postWithRetry(options) {
|
|
|
1563
1695
|
if (!response.ok) {
|
|
1564
1696
|
const errText = yield response.text();
|
|
1565
1697
|
const parsed = parseJsonOrUndefined(errText);
|
|
1698
|
+
const refusal = providerRefusalOf(vendor, codeOf(parsed), parsed);
|
|
1699
|
+
if (refusal) {
|
|
1700
|
+
throw providerRefusedFrom(vendorLabel, refusal, response.status, parsed, errText || void 0);
|
|
1701
|
+
}
|
|
1566
1702
|
if (isBudgetRejection(response.status, codeOf(parsed))) {
|
|
1567
1703
|
throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
|
|
1568
1704
|
}
|
|
1569
|
-
|
|
1705
|
+
const refusedRetry = shouldNotRetry(response.headers);
|
|
1706
|
+
if (retryableStatuses.includes(response.status) && !refusedRetry && attempt < MAX_RETRIES) {
|
|
1570
1707
|
logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1571
1708
|
clearTimeout(timeoutId);
|
|
1572
|
-
yield backoff(attempt);
|
|
1709
|
+
yield backoff(attempt, (_d = retryAfterMs(response.headers)) !== null && _d !== void 0 ? _d : retryInfoMs(parsed));
|
|
1573
1710
|
continue;
|
|
1574
1711
|
}
|
|
1575
1712
|
throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
|
|
1576
1713
|
}
|
|
1577
|
-
if (((
|
|
1714
|
+
if (((_f = (_e = response.headers) === null || _e === void 0 ? void 0 : _e.get("content-type")) === null || _f === void 0 ? void 0 : _f.toLowerCase().includes("application/x-ndjson")) && response.body) {
|
|
1578
1715
|
rearmStallTimer();
|
|
1579
1716
|
const frame = yield readFramedBody(response.body, rearmStallTimer, vendorLabel, stallTimeout);
|
|
1580
1717
|
if (frame.t === "ok")
|
|
1581
1718
|
return frame.body;
|
|
1582
1719
|
if (frame.code === "UPSTREAM_STALLED") {
|
|
1583
|
-
throw new DOMException(`${vendorLabel} request stalled: ${(
|
|
1720
|
+
throw new DOMException(`${vendorLabel} request stalled: ${(_g = frame.error) !== null && _g !== void 0 ? _g : "no upstream data"}`, "TimeoutError");
|
|
1721
|
+
}
|
|
1722
|
+
const framedRefusal = providerRefusalOf(vendor, frame.code, frame);
|
|
1723
|
+
if (framedRefusal) {
|
|
1724
|
+
throw providerRefusedFrom(vendorLabel, framedRefusal, frame.status, frame, frame.error);
|
|
1584
1725
|
}
|
|
1585
1726
|
if (isBudgetRejection(frame.status, frame.code)) {
|
|
1586
1727
|
throw budgetExhaustedFrom(vendorLabel, frame);
|
|
@@ -1588,10 +1729,10 @@ function postWithRetry(options) {
|
|
|
1588
1729
|
if (retryableStatuses.includes(frame.status) && attempt < MAX_RETRIES) {
|
|
1589
1730
|
logger.warn(`${vendorLabel}Transport: retryable status ${frame.status} (err frame), retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1590
1731
|
clearTimeout(timeoutId);
|
|
1591
|
-
yield backoff(attempt);
|
|
1732
|
+
yield backoff(attempt, retryInfoMs(frame));
|
|
1592
1733
|
continue;
|
|
1593
1734
|
}
|
|
1594
|
-
throw new Error(`${vendorLabel} request error ${frame.status}: ${(
|
|
1735
|
+
throw new Error(`${vendorLabel} request error ${frame.status}: ${(_h = frame.error) !== null && _h !== void 0 ? _h : JSON.stringify(frame.details)}`);
|
|
1595
1736
|
}
|
|
1596
1737
|
return yield response.json();
|
|
1597
1738
|
} catch (e) {
|
|
@@ -3671,6 +3812,12 @@ function accumulate(messages, into) {
|
|
|
3671
3812
|
var TOOL_FOLD_SYMBOL = Symbol("toolFold");
|
|
3672
3813
|
|
|
3673
3814
|
// src/components/chat-driver/chat-driver.ts
|
|
3815
|
+
var providerRefusedDetailOf = (e) => ({
|
|
3816
|
+
vendorLabel: e.vendorLabel,
|
|
3817
|
+
kind: e.kind,
|
|
3818
|
+
...e.upstreamStatus != null ? { upstreamStatus: e.upstreamStatus } : {},
|
|
3819
|
+
...e.upstreamType ? { upstreamType: e.upstreamType } : {}
|
|
3820
|
+
});
|
|
3674
3821
|
var budgetDetailOf = (e) => {
|
|
3675
3822
|
const vendor = vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor);
|
|
3676
3823
|
if (e.budgetUsd == null && e.spentUsd == null && !vendor) return void 0;
|
|
@@ -3866,6 +4013,16 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3866
4013
|
* picked up on the next turn.
|
|
3867
4014
|
*/
|
|
3868
4015
|
this.resolvedStatusCache = /* @__PURE__ */ new Map();
|
|
4016
|
+
/**
|
|
4017
|
+
* A provider refusal was observed this turn, so the tool loop must not call the model again
|
|
4018
|
+
* (GENC-1506).
|
|
4019
|
+
*
|
|
4020
|
+
* Exists for the SUB-AGENT path only, exactly like `budgetExhaustedThisTurn`: this driver's own
|
|
4021
|
+
* refusal returns straight out of the catch, whereas a child's refusal reaches the parent as a tool
|
|
4022
|
+
* result, and without this flag the loop would issue another call into the same wall — N batched
|
|
4023
|
+
* children costing N doomed calls plus a doomed parent one.
|
|
4024
|
+
*/
|
|
4025
|
+
this.providerRefusedThisTurn = false;
|
|
3869
4026
|
/**
|
|
3870
4027
|
* Set the moment a budget wall is observed anywhere in this turn — this
|
|
3871
4028
|
* driver's own 402, or a sub-agent's (which surfaces here only as a
|
|
@@ -3893,13 +4050,15 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3893
4050
|
maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
3894
4051
|
sessionKey = "",
|
|
3895
4052
|
activityBus = NOOP_ACTIVITY_BUS,
|
|
3896
|
-
budgetExhaustedMessage
|
|
4053
|
+
budgetExhaustedMessage,
|
|
4054
|
+
providerRefusedMessage
|
|
3897
4055
|
} = config;
|
|
3898
4056
|
this.maxToolIterations = maxToolIterations;
|
|
3899
4057
|
this.condenseBatchCalls = condenseBatchCalls;
|
|
3900
4058
|
this.sessionKey = sessionKey;
|
|
3901
4059
|
this.activityBus = activityBus;
|
|
3902
4060
|
this.budgetExhaustedMessageOverride = budgetExhaustedMessage;
|
|
4061
|
+
this.providerRefusedMessageOverride = providerRefusedMessage;
|
|
3903
4062
|
if (typeof toolHandlers === "function") {
|
|
3904
4063
|
this.toolHandlersFactory = toolHandlers;
|
|
3905
4064
|
this.toolHandlers = {};
|
|
@@ -4006,9 +4165,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4006
4165
|
* (rather than set to `undefined`) so a happy-path result stays byte-identical to
|
|
4007
4166
|
* the historical `{ reason: 'done' }`.
|
|
4008
4167
|
*/
|
|
4009
|
-
turnDone(failureReason, budget) {
|
|
4168
|
+
turnDone(failureReason, budget, providerRefused) {
|
|
4010
4169
|
if (!failureReason) return { reason: "done" };
|
|
4011
|
-
return
|
|
4170
|
+
return {
|
|
4171
|
+
reason: "done",
|
|
4172
|
+
failureReason,
|
|
4173
|
+
...budget ? { budget } : {},
|
|
4174
|
+
...providerRefused ? { providerRefused } : {}
|
|
4175
|
+
};
|
|
4012
4176
|
}
|
|
4013
4177
|
/**
|
|
4014
4178
|
* Terminal budget outcome for a wall hit **outside** the tool loop — today,
|
|
@@ -4063,6 +4227,42 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4063
4227
|
this.appendToHistory({ role: "assistant", content: this.budgetExhaustedBubble(e) });
|
|
4064
4228
|
return this.turnDone("budget-exhausted", budgetDetailOf(e));
|
|
4065
4229
|
}
|
|
4230
|
+
/**
|
|
4231
|
+
* The sentence shown when the upstream provider refuses (GENC-1506).
|
|
4232
|
+
*
|
|
4233
|
+
* Deliberately far simpler than `budgetExhaustedBubble`: no vendor name, no figures, no
|
|
4234
|
+
* switch-provider advice, and no branching on `kind`. The copy is cause-free by design — a user who
|
|
4235
|
+
* can see their own remaining spend must not be told about a limit, and we must not imply the bill
|
|
4236
|
+
* has gone unpaid — so there is nothing here to compose. A host override wins verbatim.
|
|
4237
|
+
*/
|
|
4238
|
+
providerRefusedBubble() {
|
|
4239
|
+
return this.providerRefusedMessageOverride ?? DEFAULT_PROVIDER_REFUSED_MESSAGE;
|
|
4240
|
+
}
|
|
4241
|
+
/**
|
|
4242
|
+
* Terminal provider-refusal outcome. Mirrors `reportBudgetExhausted` so the two walls behave
|
|
4243
|
+
* identically from the caller's side, while keeping their diagnostics distinct.
|
|
4244
|
+
*
|
|
4245
|
+
* `kind` reaches the debug log and the result but never the transcript: it is what lets an operator
|
|
4246
|
+
* tell "top up the account" from "rotate the key", and with cause-free user copy this is the only
|
|
4247
|
+
* place that distinction survives.
|
|
4248
|
+
*/
|
|
4249
|
+
reportProviderRefused(e) {
|
|
4250
|
+
const detail = providerRefusedDetailOf(e);
|
|
4251
|
+
this.providerRefusedThisTurn = true;
|
|
4252
|
+
this.providerRefusedDetail = detail;
|
|
4253
|
+
logger2.error("ChatDriver: provider refused the request", e);
|
|
4254
|
+
recordTurnError(this.sessionKey, "provider-refused", {
|
|
4255
|
+
agent: this.activeAgentName,
|
|
4256
|
+
provider: this.lastResolvedProviderName,
|
|
4257
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? this.lastResolvedProvider,
|
|
4258
|
+
kind: e.kind,
|
|
4259
|
+
upstreamStatus: e.upstreamStatus,
|
|
4260
|
+
upstreamType: e.upstreamType,
|
|
4261
|
+
isSubAgent: this.isSubAgent
|
|
4262
|
+
});
|
|
4263
|
+
this.appendToHistory({ role: "assistant", content: this.providerRefusedBubble() });
|
|
4264
|
+
return this.turnDone("provider-refused", void 0, detail);
|
|
4265
|
+
}
|
|
4066
4266
|
/** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
|
|
4067
4267
|
static failureReasonOf(result) {
|
|
4068
4268
|
return result.reason === "done" ? result.failureReason : void 0;
|
|
@@ -4747,6 +4947,8 @@ Output format (strict):
|
|
|
4747
4947
|
this.budgetExhaustedThisTurn = false;
|
|
4748
4948
|
this.budgetWallDetail = void 0;
|
|
4749
4949
|
this.budgetWallViaSubAgent = false;
|
|
4950
|
+
this.providerRefusedThisTurn = false;
|
|
4951
|
+
this.providerRefusedDetail = void 0;
|
|
4750
4952
|
this.appendToHistory({ role: "user", content: userInput, attachments });
|
|
4751
4953
|
this.turnStartedAt = Date.now();
|
|
4752
4954
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
@@ -5006,6 +5208,8 @@ Output format (strict):
|
|
|
5006
5208
|
this.budgetExhaustedThisTurn = false;
|
|
5007
5209
|
this.budgetWallDetail = void 0;
|
|
5008
5210
|
this.budgetWallViaSubAgent = false;
|
|
5211
|
+
this.providerRefusedThisTurn = false;
|
|
5212
|
+
this.providerRefusedDetail = void 0;
|
|
5009
5213
|
this.turnStartedAt = Date.now();
|
|
5010
5214
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
5011
5215
|
phase: "continueFromHistory",
|
|
@@ -5195,6 +5399,22 @@ Output format (strict):
|
|
|
5195
5399
|
if (this.turnController.signal.aborted) {
|
|
5196
5400
|
return this.completeAbortedTurn();
|
|
5197
5401
|
}
|
|
5402
|
+
if (this.providerRefusedThisTurn) {
|
|
5403
|
+
logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the provider wall");
|
|
5404
|
+
recordTurnError(this.sessionKey, "provider-refused", {
|
|
5405
|
+
agent: this.activeAgentName,
|
|
5406
|
+
provider: this.lastResolvedProviderName,
|
|
5407
|
+
kind: this.providerRefusedDetail?.kind,
|
|
5408
|
+
via: "sub-agent",
|
|
5409
|
+
isSubAgent: this.isSubAgent
|
|
5410
|
+
});
|
|
5411
|
+
if (this.isSubAgent) {
|
|
5412
|
+
this.failSubAgent("provider_refused");
|
|
5413
|
+
} else {
|
|
5414
|
+
this.appendToHistory({ role: "assistant", content: this.providerRefusedBubble() });
|
|
5415
|
+
}
|
|
5416
|
+
return this.turnDone("provider-refused", void 0, this.providerRefusedDetail);
|
|
5417
|
+
}
|
|
5198
5418
|
if (this.budgetExhaustedThisTurn) {
|
|
5199
5419
|
logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the AI budget wall");
|
|
5200
5420
|
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
@@ -5422,6 +5642,25 @@ ${tailBody}
|
|
|
5422
5642
|
}
|
|
5423
5643
|
return this.turnDone("response-truncated");
|
|
5424
5644
|
}
|
|
5645
|
+
if (e instanceof ProviderRefusedError) {
|
|
5646
|
+
if (this.isSubAgent) {
|
|
5647
|
+
logger2.error("ChatDriver: provider refused the request", e);
|
|
5648
|
+
recordTurnError(this.sessionKey, "provider-refused", {
|
|
5649
|
+
agent: this.activeAgentName,
|
|
5650
|
+
provider: this.lastResolvedProviderName,
|
|
5651
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? this.lastResolvedProvider,
|
|
5652
|
+
kind: e.kind,
|
|
5653
|
+
upstreamStatus: e.upstreamStatus,
|
|
5654
|
+
upstreamType: e.upstreamType,
|
|
5655
|
+
isSubAgent: true
|
|
5656
|
+
});
|
|
5657
|
+
this.providerRefusedThisTurn = true;
|
|
5658
|
+
this.providerRefusedDetail = providerRefusedDetailOf(e);
|
|
5659
|
+
this.failSubAgent("provider_refused");
|
|
5660
|
+
return this.turnDone("provider-refused", void 0, this.providerRefusedDetail);
|
|
5661
|
+
}
|
|
5662
|
+
return this.reportProviderRefused(e);
|
|
5663
|
+
}
|
|
5425
5664
|
if (e instanceof BudgetExhaustedError) {
|
|
5426
5665
|
this.budgetExhaustedThisTurn = true;
|
|
5427
5666
|
this.budgetWallDetail = budgetDetailOf(e);
|
|
@@ -6779,6 +7018,7 @@ function withFreshMetaSnapshot(stored, fresh) {
|
|
|
6779
7018
|
AnthropicProvider,
|
|
6780
7019
|
AnthropicTransport,
|
|
6781
7020
|
ChatDriver,
|
|
7021
|
+
DEFAULT_PROVIDER_REFUSED_MESSAGE,
|
|
6782
7022
|
GEMINI_CACHED_INPUT_MULTIPLIER,
|
|
6783
7023
|
GEMINI_LONG_CONTEXT_THRESHOLD,
|
|
6784
7024
|
GeminiProvider,
|
|
@@ -6786,6 +7026,8 @@ function withFreshMetaSnapshot(stored, fresh) {
|
|
|
6786
7026
|
MutableAIProviderRegistry,
|
|
6787
7027
|
NOOP_ACTIVITY_BUS,
|
|
6788
7028
|
OrchestratingDriver,
|
|
7029
|
+
PROVIDER_REFUSED_CODE,
|
|
7030
|
+
ProviderRefusedError,
|
|
6789
7031
|
REQUEST_CONTINUATION_TOOL,
|
|
6790
7032
|
SUPPORTED_ANTHROPIC_MODEL_IDS,
|
|
6791
7033
|
SUPPORTED_GEMINI_MODEL_IDS,
|