@genesislcap/ai-assistant 15.14.1 → 15.14.2-FUI-2603.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.
- package/dist/ai-assistant.api.json +37 -1
- package/dist/ai-assistant.d.ts +55 -0
- package/dist/chat-driver.cjs +285 -9
- package/dist/chat-driver.cjs.map +3 -3
- package/dist/chat-driver.mjs +282 -9
- package/dist/chat-driver.mjs.map +3 -3
- package/dist/custom-elements.json +101 -3
- 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 +54 -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/esm/chat-driver-node.js +9 -1
- package/dist/esm/components/chat-driver/chat-driver.js +154 -22
- package/dist/esm/components/chat-driver/chat-driver.test.js +210 -1
- 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-1506.md +209 -0
- package/package.json +17 -17
- package/src/chat-driver-node.ts +10 -0
- package/src/components/chat-driver/chat-driver.test.ts +294 -0
- package/src/components/chat-driver/chat-driver.ts +215 -9
- package/src/state/debug-event-log.test.ts +2 -0
- package/src/state/debug-event-log.ts +1 -1
|
@@ -4963,6 +4963,15 @@
|
|
|
4963
4963
|
"text": "BudgetDetail",
|
|
4964
4964
|
"canonicalReference": "@genesislcap/ai-assistant!~BudgetDetail:type"
|
|
4965
4965
|
},
|
|
4966
|
+
{
|
|
4967
|
+
"kind": "Content",
|
|
4968
|
+
"text": ";\n providerRefused?: "
|
|
4969
|
+
},
|
|
4970
|
+
{
|
|
4971
|
+
"kind": "Reference",
|
|
4972
|
+
"text": "ProviderRefusedDetail",
|
|
4973
|
+
"canonicalReference": "@genesislcap/ai-assistant!~ProviderRefusedDetail:type"
|
|
4974
|
+
},
|
|
4966
4975
|
{
|
|
4967
4976
|
"kind": "Content",
|
|
4968
4977
|
"text": ";\n } | undefined"
|
|
@@ -4975,7 +4984,7 @@
|
|
|
4975
4984
|
"isStatic": false,
|
|
4976
4985
|
"returnTypeTokenRange": {
|
|
4977
4986
|
"startIndex": 1,
|
|
4978
|
-
"endIndex":
|
|
4987
|
+
"endIndex": 8
|
|
4979
4988
|
},
|
|
4980
4989
|
"releaseTag": "Beta",
|
|
4981
4990
|
"isProtected": false,
|
|
@@ -5923,6 +5932,33 @@
|
|
|
5923
5932
|
"endIndex": 3
|
|
5924
5933
|
}
|
|
5925
5934
|
},
|
|
5935
|
+
{
|
|
5936
|
+
"kind": "PropertySignature",
|
|
5937
|
+
"canonicalReference": "@genesislcap/ai-assistant!ChatDriverConfig#providerRefusedMessage:member",
|
|
5938
|
+
"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",
|
|
5939
|
+
"excerptTokens": [
|
|
5940
|
+
{
|
|
5941
|
+
"kind": "Content",
|
|
5942
|
+
"text": "providerRefusedMessage?: "
|
|
5943
|
+
},
|
|
5944
|
+
{
|
|
5945
|
+
"kind": "Content",
|
|
5946
|
+
"text": "string"
|
|
5947
|
+
},
|
|
5948
|
+
{
|
|
5949
|
+
"kind": "Content",
|
|
5950
|
+
"text": ";"
|
|
5951
|
+
}
|
|
5952
|
+
],
|
|
5953
|
+
"isReadonly": false,
|
|
5954
|
+
"isOptional": true,
|
|
5955
|
+
"releaseTag": "Beta",
|
|
5956
|
+
"name": "providerRefusedMessage",
|
|
5957
|
+
"propertyTypeTokenRange": {
|
|
5958
|
+
"startIndex": 1,
|
|
5959
|
+
"endIndex": 2
|
|
5960
|
+
}
|
|
5961
|
+
},
|
|
5926
5962
|
{
|
|
5927
5963
|
"kind": "PropertySignature",
|
|
5928
5964
|
"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
|
/**
|
|
@@ -1712,6 +1748,7 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1712
1748
|
getSubAgentFailure(): {
|
|
1713
1749
|
reason: SubAgentFailureReason;
|
|
1714
1750
|
budget?: BudgetDetail;
|
|
1751
|
+
providerRefused?: ProviderRefusedDetail;
|
|
1715
1752
|
} | undefined;
|
|
1716
1753
|
/**
|
|
1717
1754
|
* Record a sub-agent failure reason (first one wins). No-op for top-level
|
|
@@ -1980,6 +2017,19 @@ export declare interface ChatDriverConfig {
|
|
|
1980
2017
|
* reversible change.
|
|
1981
2018
|
*/
|
|
1982
2019
|
budgetExhaustedMessage?: string;
|
|
2020
|
+
/**
|
|
2021
|
+
* Overrides the transcript sentence shown when the upstream PROVIDER refuses the account
|
|
2022
|
+
* (GENC-1506) — see `DEFAULT_PROVIDER_REFUSED_MESSAGE`.
|
|
2023
|
+
*
|
|
2024
|
+
* Separate from `budgetExhaustedMessage` because the two conditions are separate: ours is a spend
|
|
2025
|
+
* cap a host administrator can raise, this is the vendor declining to serve us at all. A host that
|
|
2026
|
+
* white-labels one will usually want to white-label both, but conflating them into one field would
|
|
2027
|
+
* force identical copy on two situations with different remedies.
|
|
2028
|
+
*
|
|
2029
|
+
* Rarely needed: unlike the budget default, the shipped sentence names no vendor and no Genesis, so
|
|
2030
|
+
* it is already safe for a white-labelled deployment.
|
|
2031
|
+
*/
|
|
2032
|
+
providerRefusedMessage?: string;
|
|
1983
2033
|
}
|
|
1984
2034
|
|
|
1985
2035
|
export { ChatFallback }
|
|
@@ -4410,6 +4460,11 @@ export declare type PopoutMode = 'expand' | 'collapse';
|
|
|
4410
4460
|
*/
|
|
4411
4461
|
export declare type ProviderInput = string | ((ctx: SystemPromptContext) => string | Promise<string>);
|
|
4412
4462
|
|
|
4463
|
+
/** The `providerRefused` payload on a `'provider-refused'` {@link ChatDriverResult} (GENC-1506). */
|
|
4464
|
+
declare type ProviderRefusedDetail = NonNullable<Extract<ChatDriverResult, {
|
|
4465
|
+
reason: 'done';
|
|
4466
|
+
}>['providerRefused']>;
|
|
4467
|
+
|
|
4413
4468
|
/** Name reserved for the cross-agent handoff tool — injected by OrchestratingDriver. */
|
|
4414
4469
|
export declare const REQUEST_CONTINUATION_TOOL = "request_continuation";
|
|
4415
4470
|
|
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,94 @@ 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 statedKind(payload) {
|
|
1153
|
+
const outer = typeof payload === "object" && payload !== null ? payload : {};
|
|
1154
|
+
const nested = typeof outer.details === "object" && outer.details !== null ? outer.details : {};
|
|
1155
|
+
for (const candidate of [outer.kind, nested.kind]) {
|
|
1156
|
+
if (candidate === "auth" || candidate === "spend")
|
|
1157
|
+
return candidate;
|
|
1158
|
+
}
|
|
1159
|
+
return void 0;
|
|
1160
|
+
}
|
|
1161
|
+
function kindFromEnvelopeType(payload) {
|
|
1162
|
+
const body = anthropicErrorBody(payload);
|
|
1163
|
+
if (!(body === null || body === void 0 ? void 0 : body.type))
|
|
1164
|
+
return void 0;
|
|
1165
|
+
if (ANTHROPIC_AUTH_TYPES.includes(body.type))
|
|
1166
|
+
return "auth";
|
|
1167
|
+
if (body.type === "billing_error")
|
|
1168
|
+
return "spend";
|
|
1169
|
+
return void 0;
|
|
1170
|
+
}
|
|
1171
|
+
function providerRefusalOf(vendor, code, payload) {
|
|
1172
|
+
var _a, _b;
|
|
1173
|
+
if (code === PROVIDER_REFUSED_CODE) {
|
|
1174
|
+
return (_b = (_a = statedKind(payload)) !== null && _a !== void 0 ? _a : kindFromEnvelopeType(payload)) !== null && _b !== void 0 ? _b : "spend";
|
|
1175
|
+
}
|
|
1176
|
+
if (vendor !== "anthropic")
|
|
1177
|
+
return void 0;
|
|
1178
|
+
const structural = kindFromEnvelopeType(payload);
|
|
1179
|
+
if (structural)
|
|
1180
|
+
return structural;
|
|
1181
|
+
const body = anthropicErrorBody(payload);
|
|
1182
|
+
if ((body === null || body === void 0 ? void 0 : body.type) === "invalid_request_error" && body.message) {
|
|
1183
|
+
const message = body.message;
|
|
1184
|
+
if (ANTHROPIC_SPEND_PHRASES.some((phrase) => phrase.test(message)))
|
|
1185
|
+
return "spend";
|
|
1186
|
+
}
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1189
|
+
function providerRefusedFrom(vendorLabel, kind, upstreamStatus, payload, fallbackDetail) {
|
|
1190
|
+
var _a;
|
|
1191
|
+
const body = anthropicErrorBody(payload);
|
|
1192
|
+
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);
|
|
1193
|
+
}
|
|
1194
|
+
function vendorOfTransportLabel(vendorLabel) {
|
|
1195
|
+
const entry = Object.entries(VENDOR_LABELS).find(([, label]) => label === vendorLabel);
|
|
1196
|
+
return entry === null || entry === void 0 ? void 0 : entry[0];
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1108
1199
|
// ../../foundation-ai/dist/esm/types/config.types.js
|
|
1109
1200
|
var SUPPORTED_GEMINI_MODEL_IDS = [
|
|
1110
1201
|
"gemini-2.5-pro",
|
|
@@ -1523,13 +1614,81 @@ function readFramedBody(stream, onChunk, vendorLabel, stallTimeout) {
|
|
|
1523
1614
|
});
|
|
1524
1615
|
}
|
|
1525
1616
|
|
|
1617
|
+
// ../../foundation-ai/dist/esm/transports/retry-hints.js
|
|
1618
|
+
var MAX_SERVER_REQUESTED_BACKOFF_MS = 6e4;
|
|
1619
|
+
var MAX_TOTAL_SERVER_REQUESTED_BACKOFF_MS = 12e4;
|
|
1620
|
+
var SHOULD_RETRY_HEADER = "x-should-retry";
|
|
1621
|
+
var MS_PER_SECOND = 1e3;
|
|
1622
|
+
function shouldNotRetry(headers) {
|
|
1623
|
+
var _a;
|
|
1624
|
+
const value = (_a = headers === null || headers === void 0 ? void 0 : headers.get) === null || _a === void 0 ? void 0 : _a.call(headers, SHOULD_RETRY_HEADER);
|
|
1625
|
+
return typeof value === "string" && value.trim().toLowerCase() === "false";
|
|
1626
|
+
}
|
|
1627
|
+
function retryAfterMs(headers) {
|
|
1628
|
+
var _a;
|
|
1629
|
+
const raw = (_a = headers === null || headers === void 0 ? void 0 : headers.get) === null || _a === void 0 ? void 0 : _a.call(headers, "retry-after");
|
|
1630
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
1631
|
+
return void 0;
|
|
1632
|
+
const seconds = Number(raw.trim());
|
|
1633
|
+
if (Number.isFinite(seconds))
|
|
1634
|
+
return Math.max(0, seconds * MS_PER_SECOND);
|
|
1635
|
+
const at = Date.parse(raw);
|
|
1636
|
+
if (Number.isNaN(at))
|
|
1637
|
+
return void 0;
|
|
1638
|
+
return Math.max(0, at - Date.now());
|
|
1639
|
+
}
|
|
1640
|
+
function retryInfoMs(payload) {
|
|
1641
|
+
const outer = typeof payload === "object" && payload !== null ? payload : {};
|
|
1642
|
+
const framed = typeof outer.details === "object" && outer.details !== null ? outer.details : void 0;
|
|
1643
|
+
for (const holder of [outer.error, framed === null || framed === void 0 ? void 0 : framed.error]) {
|
|
1644
|
+
const details = holder === null || holder === void 0 ? void 0 : holder.details;
|
|
1645
|
+
if (!Array.isArray(details))
|
|
1646
|
+
continue;
|
|
1647
|
+
for (const entry of details) {
|
|
1648
|
+
const type = entry === null || entry === void 0 ? void 0 : entry["@type"];
|
|
1649
|
+
if (typeof type !== "string" || !type.endsWith("google.rpc.RetryInfo"))
|
|
1650
|
+
continue;
|
|
1651
|
+
const delay = entry === null || entry === void 0 ? void 0 : entry.retryDelay;
|
|
1652
|
+
if (typeof delay !== "string")
|
|
1653
|
+
continue;
|
|
1654
|
+
const seconds = Number(delay.replace(/s$/, ""));
|
|
1655
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
1656
|
+
return seconds * MS_PER_SECOND;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return void 0;
|
|
1660
|
+
}
|
|
1661
|
+
function resolveBackoffMs(attempt, baseMs, serverMs, serverAllowanceMs = Number.POSITIVE_INFINITY) {
|
|
1662
|
+
const ladder = baseMs * Math.pow(2, attempt);
|
|
1663
|
+
if (serverMs == null || !Number.isFinite(serverMs))
|
|
1664
|
+
return ladder;
|
|
1665
|
+
const honoured = Math.min(serverMs, MAX_SERVER_REQUESTED_BACKOFF_MS, Math.max(0, serverAllowanceMs));
|
|
1666
|
+
return Math.max(ladder, honoured);
|
|
1667
|
+
}
|
|
1668
|
+
function serverBackoffSpentMs(resolvedMs, ladderMs) {
|
|
1669
|
+
return Math.max(0, resolvedMs - ladderMs);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1526
1672
|
// ../../foundation-ai/dist/esm/transports/post-with-retry.js
|
|
1527
1673
|
var MAX_RETRIES = 5;
|
|
1528
1674
|
function postWithRetry(options) {
|
|
1529
1675
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1530
1676
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1531
1677
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal } = options;
|
|
1532
|
-
const
|
|
1678
|
+
const vendor = vendorOfTransportLabel(vendorLabel);
|
|
1679
|
+
let serverBackoffSpent = 0;
|
|
1680
|
+
const backoff = (attempt, serverMs) => {
|
|
1681
|
+
const ladderMs = backoffBaseMs * Math.pow(2, attempt);
|
|
1682
|
+
const allowanceMs = MAX_TOTAL_SERVER_REQUESTED_BACKOFF_MS - serverBackoffSpent;
|
|
1683
|
+
const waitMs = resolveBackoffMs(attempt, backoffBaseMs, serverMs, allowanceMs);
|
|
1684
|
+
serverBackoffSpent += serverBackoffSpentMs(waitMs, ladderMs);
|
|
1685
|
+
if (serverMs != null && waitMs > ladderMs) {
|
|
1686
|
+
logger.warn(`${vendorLabel}Transport: honouring the server's requested retry delay (${Math.round(waitMs)}ms, asked ${Math.round(serverMs)}ms)`);
|
|
1687
|
+
} else if (serverMs != null && serverMs > ladderMs) {
|
|
1688
|
+
logger.warn(`${vendorLabel}Transport: declining the server's requested retry delay (asked ${Math.round(serverMs)}ms; the ${MAX_TOTAL_SERVER_REQUESTED_BACKOFF_MS}ms sequence allowance is spent) \u2014 using the ladder's ${Math.round(waitMs)}ms`);
|
|
1689
|
+
}
|
|
1690
|
+
return abortableDelay(waitMs, signal);
|
|
1691
|
+
};
|
|
1533
1692
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
|
|
1534
1693
|
const timeoutController = new AbortController();
|
|
1535
1694
|
const startedAt = performance.now();
|
|
@@ -1563,13 +1722,18 @@ function postWithRetry(options) {
|
|
|
1563
1722
|
if (!response.ok) {
|
|
1564
1723
|
const errText = yield response.text();
|
|
1565
1724
|
const parsed = parseJsonOrUndefined(errText);
|
|
1725
|
+
const refusal = providerRefusalOf(vendor, codeOf(parsed), parsed);
|
|
1726
|
+
if (refusal) {
|
|
1727
|
+
throw providerRefusedFrom(vendorLabel, refusal, response.status, parsed, errText || void 0);
|
|
1728
|
+
}
|
|
1566
1729
|
if (isBudgetRejection(response.status, codeOf(parsed))) {
|
|
1567
1730
|
throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
|
|
1568
1731
|
}
|
|
1569
|
-
|
|
1732
|
+
const refusedRetry = shouldNotRetry(response.headers);
|
|
1733
|
+
if (retryableStatuses.includes(response.status) && !refusedRetry && attempt < MAX_RETRIES) {
|
|
1570
1734
|
logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1571
1735
|
clearTimeout(timeoutId);
|
|
1572
|
-
yield backoff(attempt);
|
|
1736
|
+
yield backoff(attempt, retryAfterMs(response.headers) || retryInfoMs(parsed));
|
|
1573
1737
|
continue;
|
|
1574
1738
|
}
|
|
1575
1739
|
throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
|
|
@@ -1582,13 +1746,17 @@ function postWithRetry(options) {
|
|
|
1582
1746
|
if (frame.code === "UPSTREAM_STALLED") {
|
|
1583
1747
|
throw new DOMException(`${vendorLabel} request stalled: ${(_f = frame.error) !== null && _f !== void 0 ? _f : "no upstream data"}`, "TimeoutError");
|
|
1584
1748
|
}
|
|
1749
|
+
const framedRefusal = providerRefusalOf(vendor, frame.code, frame);
|
|
1750
|
+
if (framedRefusal) {
|
|
1751
|
+
throw providerRefusedFrom(vendorLabel, framedRefusal, frame.status, frame, frame.error);
|
|
1752
|
+
}
|
|
1585
1753
|
if (isBudgetRejection(frame.status, frame.code)) {
|
|
1586
1754
|
throw budgetExhaustedFrom(vendorLabel, frame);
|
|
1587
1755
|
}
|
|
1588
1756
|
if (retryableStatuses.includes(frame.status) && attempt < MAX_RETRIES) {
|
|
1589
1757
|
logger.warn(`${vendorLabel}Transport: retryable status ${frame.status} (err frame), retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1590
1758
|
clearTimeout(timeoutId);
|
|
1591
|
-
yield backoff(attempt);
|
|
1759
|
+
yield backoff(attempt, retryInfoMs(frame));
|
|
1592
1760
|
continue;
|
|
1593
1761
|
}
|
|
1594
1762
|
throw new Error(`${vendorLabel} request error ${frame.status}: ${(_g = frame.error) !== null && _g !== void 0 ? _g : JSON.stringify(frame.details)}`);
|
|
@@ -3671,6 +3839,12 @@ function accumulate(messages, into) {
|
|
|
3671
3839
|
var TOOL_FOLD_SYMBOL = Symbol("toolFold");
|
|
3672
3840
|
|
|
3673
3841
|
// src/components/chat-driver/chat-driver.ts
|
|
3842
|
+
var providerRefusedDetailOf = (e) => ({
|
|
3843
|
+
vendorLabel: e.vendorLabel,
|
|
3844
|
+
kind: e.kind,
|
|
3845
|
+
...e.upstreamStatus != null ? { upstreamStatus: e.upstreamStatus } : {},
|
|
3846
|
+
...e.upstreamType ? { upstreamType: e.upstreamType } : {}
|
|
3847
|
+
});
|
|
3674
3848
|
var budgetDetailOf = (e) => {
|
|
3675
3849
|
const vendor = vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor);
|
|
3676
3850
|
if (e.budgetUsd == null && e.spentUsd == null && !vendor) return void 0;
|
|
@@ -3866,6 +4040,16 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3866
4040
|
* picked up on the next turn.
|
|
3867
4041
|
*/
|
|
3868
4042
|
this.resolvedStatusCache = /* @__PURE__ */ new Map();
|
|
4043
|
+
/**
|
|
4044
|
+
* A provider refusal was observed this turn, so the tool loop must not call the model again
|
|
4045
|
+
* (GENC-1506).
|
|
4046
|
+
*
|
|
4047
|
+
* Exists for the SUB-AGENT path only, exactly like `budgetExhaustedThisTurn`: this driver's own
|
|
4048
|
+
* refusal returns straight out of the catch, whereas a child's refusal reaches the parent as a tool
|
|
4049
|
+
* result, and without this flag the loop would issue another call into the same wall — N batched
|
|
4050
|
+
* children costing N doomed calls plus a doomed parent one.
|
|
4051
|
+
*/
|
|
4052
|
+
this.providerRefusedThisTurn = false;
|
|
3869
4053
|
/**
|
|
3870
4054
|
* Set the moment a budget wall is observed anywhere in this turn — this
|
|
3871
4055
|
* driver's own 402, or a sub-agent's (which surfaces here only as a
|
|
@@ -3893,13 +4077,15 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3893
4077
|
maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
3894
4078
|
sessionKey = "",
|
|
3895
4079
|
activityBus = NOOP_ACTIVITY_BUS,
|
|
3896
|
-
budgetExhaustedMessage
|
|
4080
|
+
budgetExhaustedMessage,
|
|
4081
|
+
providerRefusedMessage
|
|
3897
4082
|
} = config;
|
|
3898
4083
|
this.maxToolIterations = maxToolIterations;
|
|
3899
4084
|
this.condenseBatchCalls = condenseBatchCalls;
|
|
3900
4085
|
this.sessionKey = sessionKey;
|
|
3901
4086
|
this.activityBus = activityBus;
|
|
3902
4087
|
this.budgetExhaustedMessageOverride = budgetExhaustedMessage;
|
|
4088
|
+
this.providerRefusedMessageOverride = providerRefusedMessage;
|
|
3903
4089
|
if (typeof toolHandlers === "function") {
|
|
3904
4090
|
this.toolHandlersFactory = toolHandlers;
|
|
3905
4091
|
this.toolHandlers = {};
|
|
@@ -4006,9 +4192,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4006
4192
|
* (rather than set to `undefined`) so a happy-path result stays byte-identical to
|
|
4007
4193
|
* the historical `{ reason: 'done' }`.
|
|
4008
4194
|
*/
|
|
4009
|
-
turnDone(failureReason, budget) {
|
|
4195
|
+
turnDone(failureReason, budget, providerRefused) {
|
|
4010
4196
|
if (!failureReason) return { reason: "done" };
|
|
4011
|
-
return
|
|
4197
|
+
return {
|
|
4198
|
+
reason: "done",
|
|
4199
|
+
failureReason,
|
|
4200
|
+
...budget ? { budget } : {},
|
|
4201
|
+
...providerRefused ? { providerRefused } : {}
|
|
4202
|
+
};
|
|
4012
4203
|
}
|
|
4013
4204
|
/**
|
|
4014
4205
|
* Terminal budget outcome for a wall hit **outside** the tool loop — today,
|
|
@@ -4063,6 +4254,42 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4063
4254
|
this.appendToHistory({ role: "assistant", content: this.budgetExhaustedBubble(e) });
|
|
4064
4255
|
return this.turnDone("budget-exhausted", budgetDetailOf(e));
|
|
4065
4256
|
}
|
|
4257
|
+
/**
|
|
4258
|
+
* The sentence shown when the upstream provider refuses (GENC-1506).
|
|
4259
|
+
*
|
|
4260
|
+
* Deliberately far simpler than `budgetExhaustedBubble`: no vendor name, no figures, no
|
|
4261
|
+
* switch-provider advice, and no branching on `kind`. The copy is cause-free by design — a user who
|
|
4262
|
+
* can see their own remaining spend must not be told about a limit, and we must not imply the bill
|
|
4263
|
+
* has gone unpaid — so there is nothing here to compose. A host override wins verbatim.
|
|
4264
|
+
*/
|
|
4265
|
+
providerRefusedBubble() {
|
|
4266
|
+
return this.providerRefusedMessageOverride ?? DEFAULT_PROVIDER_REFUSED_MESSAGE;
|
|
4267
|
+
}
|
|
4268
|
+
/**
|
|
4269
|
+
* Terminal provider-refusal outcome. Mirrors `reportBudgetExhausted` so the two walls behave
|
|
4270
|
+
* identically from the caller's side, while keeping their diagnostics distinct.
|
|
4271
|
+
*
|
|
4272
|
+
* `kind` reaches the debug log and the result but never the transcript: it is what lets an operator
|
|
4273
|
+
* tell "top up the account" from "rotate the key", and with cause-free user copy this is the only
|
|
4274
|
+
* place that distinction survives.
|
|
4275
|
+
*/
|
|
4276
|
+
reportProviderRefused(e) {
|
|
4277
|
+
const detail = providerRefusedDetailOf(e);
|
|
4278
|
+
this.providerRefusedThisTurn = true;
|
|
4279
|
+
this.providerRefusedDetail = detail;
|
|
4280
|
+
logger2.error("ChatDriver: provider refused the request", e);
|
|
4281
|
+
recordTurnError(this.sessionKey, "provider-refused", {
|
|
4282
|
+
agent: this.activeAgentName,
|
|
4283
|
+
provider: this.lastResolvedProviderName,
|
|
4284
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? this.lastResolvedProvider,
|
|
4285
|
+
kind: e.kind,
|
|
4286
|
+
upstreamStatus: e.upstreamStatus,
|
|
4287
|
+
upstreamType: e.upstreamType,
|
|
4288
|
+
isSubAgent: this.isSubAgent
|
|
4289
|
+
});
|
|
4290
|
+
this.appendToHistory({ role: "assistant", content: this.providerRefusedBubble() });
|
|
4291
|
+
return this.turnDone("provider-refused", void 0, detail);
|
|
4292
|
+
}
|
|
4066
4293
|
/** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
|
|
4067
4294
|
static failureReasonOf(result) {
|
|
4068
4295
|
return result.reason === "done" ? result.failureReason : void 0;
|
|
@@ -4286,9 +4513,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4286
4513
|
* under a separate session key, so recording here would orphan the event off
|
|
4287
4514
|
* the user-visible debug-log timeline.)
|
|
4288
4515
|
*/
|
|
4289
|
-
failSubAgent(reason, budget) {
|
|
4516
|
+
failSubAgent(reason, budget, providerRefused) {
|
|
4290
4517
|
if (!this.isSubAgent || this.subAgentFailure) return;
|
|
4291
|
-
this.subAgentFailure =
|
|
4518
|
+
this.subAgentFailure = {
|
|
4519
|
+
reason,
|
|
4520
|
+
...budget ? { budget } : {},
|
|
4521
|
+
...providerRefused ? { providerRefused } : {}
|
|
4522
|
+
};
|
|
4292
4523
|
}
|
|
4293
4524
|
/**
|
|
4294
4525
|
* Returns true if `releaseAgent` was called during the most recent turn.
|
|
@@ -4747,6 +4978,8 @@ Output format (strict):
|
|
|
4747
4978
|
this.budgetExhaustedThisTurn = false;
|
|
4748
4979
|
this.budgetWallDetail = void 0;
|
|
4749
4980
|
this.budgetWallViaSubAgent = false;
|
|
4981
|
+
this.providerRefusedThisTurn = false;
|
|
4982
|
+
this.providerRefusedDetail = void 0;
|
|
4750
4983
|
this.appendToHistory({ role: "user", content: userInput, attachments });
|
|
4751
4984
|
this.turnStartedAt = Date.now();
|
|
4752
4985
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
@@ -4991,6 +5224,10 @@ Output format (strict):
|
|
|
4991
5224
|
this.budgetWallViaSubAgent = true;
|
|
4992
5225
|
this.budgetWallDetail ??= failure?.budget;
|
|
4993
5226
|
}
|
|
5227
|
+
if (reason === "provider_refused") {
|
|
5228
|
+
this.providerRefusedThisTurn = true;
|
|
5229
|
+
this.providerRefusedDetail ??= failure?.providerRefused;
|
|
5230
|
+
}
|
|
4994
5231
|
return { outcome: { ok: false, reason }, trace };
|
|
4995
5232
|
}
|
|
4996
5233
|
/**
|
|
@@ -5006,6 +5243,8 @@ Output format (strict):
|
|
|
5006
5243
|
this.budgetExhaustedThisTurn = false;
|
|
5007
5244
|
this.budgetWallDetail = void 0;
|
|
5008
5245
|
this.budgetWallViaSubAgent = false;
|
|
5246
|
+
this.providerRefusedThisTurn = false;
|
|
5247
|
+
this.providerRefusedDetail = void 0;
|
|
5009
5248
|
this.turnStartedAt = Date.now();
|
|
5010
5249
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
5011
5250
|
phase: "continueFromHistory",
|
|
@@ -5195,6 +5434,22 @@ Output format (strict):
|
|
|
5195
5434
|
if (this.turnController.signal.aborted) {
|
|
5196
5435
|
return this.completeAbortedTurn();
|
|
5197
5436
|
}
|
|
5437
|
+
if (this.providerRefusedThisTurn) {
|
|
5438
|
+
logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the provider wall");
|
|
5439
|
+
recordTurnError(this.sessionKey, "provider-refused", {
|
|
5440
|
+
agent: this.activeAgentName,
|
|
5441
|
+
provider: this.lastResolvedProviderName,
|
|
5442
|
+
kind: this.providerRefusedDetail?.kind,
|
|
5443
|
+
via: "sub-agent",
|
|
5444
|
+
isSubAgent: this.isSubAgent
|
|
5445
|
+
});
|
|
5446
|
+
if (this.isSubAgent) {
|
|
5447
|
+
this.failSubAgent("provider_refused", void 0, this.providerRefusedDetail);
|
|
5448
|
+
} else {
|
|
5449
|
+
this.appendToHistory({ role: "assistant", content: this.providerRefusedBubble() });
|
|
5450
|
+
}
|
|
5451
|
+
return this.turnDone("provider-refused", void 0, this.providerRefusedDetail);
|
|
5452
|
+
}
|
|
5198
5453
|
if (this.budgetExhaustedThisTurn) {
|
|
5199
5454
|
logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the AI budget wall");
|
|
5200
5455
|
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
@@ -5422,6 +5677,24 @@ ${tailBody}
|
|
|
5422
5677
|
}
|
|
5423
5678
|
return this.turnDone("response-truncated");
|
|
5424
5679
|
}
|
|
5680
|
+
if (e instanceof ProviderRefusedError) {
|
|
5681
|
+
if (this.isSubAgent) {
|
|
5682
|
+
logger2.error("ChatDriver: provider refused the request", e);
|
|
5683
|
+
recordTurnError(this.sessionKey, "provider-refused", {
|
|
5684
|
+
agent: this.activeAgentName,
|
|
5685
|
+
provider: this.lastResolvedProviderName,
|
|
5686
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? this.lastResolvedProvider,
|
|
5687
|
+
kind: e.kind,
|
|
5688
|
+
upstreamStatus: e.upstreamStatus,
|
|
5689
|
+
upstreamType: e.upstreamType,
|
|
5690
|
+
isSubAgent: true
|
|
5691
|
+
});
|
|
5692
|
+
const detail = providerRefusedDetailOf(e);
|
|
5693
|
+
this.failSubAgent("provider_refused", void 0, detail);
|
|
5694
|
+
return this.turnDone("provider-refused", void 0, detail);
|
|
5695
|
+
}
|
|
5696
|
+
return this.reportProviderRefused(e);
|
|
5697
|
+
}
|
|
5425
5698
|
if (e instanceof BudgetExhaustedError) {
|
|
5426
5699
|
this.budgetExhaustedThisTurn = true;
|
|
5427
5700
|
this.budgetWallDetail = budgetDetailOf(e);
|
|
@@ -6779,6 +7052,7 @@ function withFreshMetaSnapshot(stored, fresh) {
|
|
|
6779
7052
|
AnthropicProvider,
|
|
6780
7053
|
AnthropicTransport,
|
|
6781
7054
|
ChatDriver,
|
|
7055
|
+
DEFAULT_PROVIDER_REFUSED_MESSAGE,
|
|
6782
7056
|
GEMINI_CACHED_INPUT_MULTIPLIER,
|
|
6783
7057
|
GEMINI_LONG_CONTEXT_THRESHOLD,
|
|
6784
7058
|
GeminiProvider,
|
|
@@ -6786,6 +7060,8 @@ function withFreshMetaSnapshot(stored, fresh) {
|
|
|
6786
7060
|
MutableAIProviderRegistry,
|
|
6787
7061
|
NOOP_ACTIVITY_BUS,
|
|
6788
7062
|
OrchestratingDriver,
|
|
7063
|
+
PROVIDER_REFUSED_CODE,
|
|
7064
|
+
ProviderRefusedError,
|
|
6789
7065
|
REQUEST_CONTINUATION_TOOL,
|
|
6790
7066
|
SUPPORTED_ANTHROPIC_MODEL_IDS,
|
|
6791
7067
|
SUPPORTED_GEMINI_MODEL_IDS,
|