@genesislcap/ai-assistant 15.6.2 → 15.7.0-ts7.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 +392 -6
- package/dist/ai-assistant.d.ts +616 -24
- package/dist/chat-driver.cjs +315 -46
- package/dist/chat-driver.cjs.map +3 -3
- package/dist/chat-driver.mjs +315 -46
- package/dist/chat-driver.mjs.map +3 -3
- package/dist/custom-elements.json +254 -10
- package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
- package/dist/dts/channel/ai-activity-channel.d.ts +51 -1
- package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
- package/dist/dts/components/activity-halo/activity-halo.d.ts.map +1 -1
- package/dist/dts/components/agent-picker/agent-picker.d.ts.map +1 -1
- package/dist/dts/components/agent-picker/agent-picker.template.d.ts.map +1 -1
- package/dist/dts/components/chat-bubble/chat-bubble.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +99 -1
- 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/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
- package/dist/dts/components/chat-markdown/chat-markdown.d.ts.map +1 -1
- package/dist/dts/components/flowing-waves-indicator.d.ts.map +1 -1
- package/dist/dts/components/halo-overlay.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +2 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +1 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +14 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/components/popout-manager/popout-manager.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/components/waves-indicator.d.ts.map +1 -1
- package/dist/dts/main/blocked-state.test.d.ts +2 -0
- package/dist/dts/main/blocked-state.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +394 -6
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.styles.test.d.ts +2 -0
- package/dist/dts/main/main.styles.test.d.ts.map +1 -0
- package/dist/dts/main/main.template.d.ts +53 -0
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +2 -2
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +163 -7
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts +6 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +12 -1
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/suggestions/chat-suggestions.d.ts.map +1 -1
- package/dist/dts/utils/animated-panel-toggle.d.ts.map +1 -1
- package/dist/dts/utils/message-partition.d.ts.map +1 -1
- package/dist/esm/components/agent-picker/agent-picker.js +2 -1
- package/dist/esm/components/agent-picker/agent-picker.template.js +2 -2
- package/dist/esm/components/chat-bubble/chat-bubble.js +2 -1
- package/dist/esm/components/chat-driver/align-event-globals.js +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +281 -38
- package/dist/esm/components/chat-driver/chat-driver.test.js +471 -14
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +312 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +94 -9
- package/dist/esm/components/popout-manager/popout-manager.js +4 -1
- package/dist/esm/components/settings-modal/settings-modal.template.js +5 -8
- package/dist/esm/main/blocked-state.test.js +969 -0
- package/dist/esm/main/main.js +834 -104
- package/dist/esm/main/main.styles.js +47 -0
- package/dist/esm/main/main.styles.test.js +87 -0
- package/dist/esm/main/main.template.js +131 -20
- package/dist/esm/state/ai-assistant-slice.js +145 -7
- package/dist/esm/state/ai-assistant-slice.test.js +138 -1
- package/dist/esm/state/debug-event-log.js +9 -3
- package/dist/esm/state/debug-event-log.test.js +49 -1
- package/dist/esm/state/persistence/session-persister.js +13 -10
- package/dist/esm/state/persistence/session-snapshot.js +3 -2
- package/dist/esm/state/persistence/session-snapshot.test.js +18 -0
- package/dist/esm/utils/collect-session-models.test.js +2 -1
- package/dist/esm/utils/condense-history.js +6 -4
- package/dist/esm/utils/condense-history.test.js +15 -21
- package/dist/esm/utils/flatten-sub-agent-messages.js +4 -3
- package/dist/esm/utils/history-transform.js +2 -1
- package/dist/esm/utils/resolve-cost-history-config.js +4 -3
- package/dist/esm/utils/resolve-preference-baseline.js +2 -1
- package/dist/esm/utils/sum-usage.js +7 -6
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/migration-GENC-1464.md +562 -0
- package/docs/sub_agent.md +20 -3
- package/package.json +17 -17
- package/src/channel/ai-activity-channel.ts +56 -2
- package/src/components/chat-driver/chat-driver.test.ts +549 -0
- package/src/components/chat-driver/chat-driver.ts +324 -14
- package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +438 -0
- package/src/components/orchestrating-driver/orchestrating-driver.ts +101 -6
- package/src/main/blocked-state.test.ts +1316 -0
- package/src/main/main.styles.test.ts +103 -0
- package/src/main/main.styles.ts +47 -0
- package/src/main/main.template.ts +131 -4
- package/src/main/main.ts +704 -10
- package/src/state/ai-assistant-slice.test.ts +215 -0
- package/src/state/ai-assistant-slice.ts +218 -8
- package/src/state/debug-event-log.test.ts +63 -0
- package/src/state/debug-event-log.ts +7 -2
- package/src/state/persistence/session-snapshot.test.ts +22 -0
- package/tsconfig.json +1 -0
package/dist/chat-driver.cjs
CHANGED
|
@@ -1016,6 +1016,67 @@ var GeminiProvider = class {
|
|
|
1016
1016
|
}
|
|
1017
1017
|
};
|
|
1018
1018
|
|
|
1019
|
+
// ../../foundation-ai/dist/esm/transports/budget-exhausted-error.js
|
|
1020
|
+
var BUDGET_EXCEEDED_CODE = "BUDGET_EXCEEDED";
|
|
1021
|
+
var VENDOR_LABELS = Object.freeze({
|
|
1022
|
+
anthropic: "Anthropic",
|
|
1023
|
+
gemini: "Gemini",
|
|
1024
|
+
openai: "OpenAI",
|
|
1025
|
+
chrome: "Chrome"
|
|
1026
|
+
});
|
|
1027
|
+
var BUDGETED_VENDORS = Object.freeze([
|
|
1028
|
+
"anthropic",
|
|
1029
|
+
"gemini"
|
|
1030
|
+
]);
|
|
1031
|
+
var VENDOR_TYPE_BY_LABEL = new Map(Object.entries(VENDOR_LABELS).map(([type, label]) => [
|
|
1032
|
+
label.toLowerCase(),
|
|
1033
|
+
type
|
|
1034
|
+
]));
|
|
1035
|
+
function vendorTypeOfLabel(label) {
|
|
1036
|
+
if (typeof label !== "string")
|
|
1037
|
+
return void 0;
|
|
1038
|
+
return VENDOR_TYPE_BY_LABEL.get(label.trim().toLowerCase());
|
|
1039
|
+
}
|
|
1040
|
+
var BUDGET_EXCEEDED_STATUS = 402;
|
|
1041
|
+
var DEFAULT_BUDGET_EXHAUSTED_MESSAGE = "You've reached your AI usage limit. Contact your administrator to raise it.";
|
|
1042
|
+
var BudgetExhaustedError = class extends Error {
|
|
1043
|
+
constructor(vendorLabel, budgetUsd, spentUsd, detail, extra) {
|
|
1044
|
+
super(`${vendorLabel} request refused: the AI usage budget is exhausted` + (budgetUsd != null ? ` (spent ${spentUsd != null ? `$${spentUsd}` : "an unknown amount"} of a $${budgetUsd} budget)` : "") + (detail ? `: ${detail}` : "") + ". Retrying will not clear this \u2014 the budget must be raised.");
|
|
1045
|
+
this.vendorLabel = vendorLabel;
|
|
1046
|
+
this.budgetUsd = budgetUsd;
|
|
1047
|
+
this.spentUsd = spentUsd;
|
|
1048
|
+
this.detail = detail;
|
|
1049
|
+
this.name = "BudgetExhaustedError";
|
|
1050
|
+
this.otherVendorAvailable = extra === null || extra === void 0 ? void 0 : extra.otherVendorAvailable;
|
|
1051
|
+
this.serverVendor = extra === null || extra === void 0 ? void 0 : extra.serverVendor;
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
var isBudgetRejection = (status, code) => status === BUDGET_EXCEEDED_STATUS || code === BUDGET_EXCEEDED_CODE;
|
|
1055
|
+
var parseJsonOrUndefined = (text) => {
|
|
1056
|
+
try {
|
|
1057
|
+
return JSON.parse(text);
|
|
1058
|
+
} catch (_a) {
|
|
1059
|
+
return void 0;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
var codeOf = (payload) => {
|
|
1063
|
+
const code = payload === null || payload === void 0 ? void 0 : payload.code;
|
|
1064
|
+
return typeof code === "string" ? code : void 0;
|
|
1065
|
+
};
|
|
1066
|
+
var numberOrUndefined = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1067
|
+
var booleanOrUndefined = (value) => typeof value === "boolean" ? value : void 0;
|
|
1068
|
+
var stringOrUndefined = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
1069
|
+
function budgetExhaustedFrom(vendorLabel, payload, fallbackDetail) {
|
|
1070
|
+
var _a, _b, _c, _d;
|
|
1071
|
+
const body = typeof payload === "object" && payload !== null ? payload : {};
|
|
1072
|
+
const details = typeof body.details === "object" && body.details !== null ? body.details : {};
|
|
1073
|
+
const detail = typeof body.error === "string" ? body.error : fallbackDetail;
|
|
1074
|
+
return new BudgetExhaustedError(vendorLabel, (_a = numberOrUndefined(body.budgetUsd)) !== null && _a !== void 0 ? _a : numberOrUndefined(details.budgetUsd), (_b = numberOrUndefined(body.spentUsd)) !== null && _b !== void 0 ? _b : numberOrUndefined(details.spentUsd), detail, {
|
|
1075
|
+
otherVendorAvailable: (_c = booleanOrUndefined(body.otherVendorAvailable)) !== null && _c !== void 0 ? _c : booleanOrUndefined(details.otherVendorAvailable),
|
|
1076
|
+
serverVendor: (_d = stringOrUndefined(body.vendor)) !== null && _d !== void 0 ? _d : stringOrUndefined(details.vendor)
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1019
1080
|
// ../../foundation-ai/dist/esm/types/config.types.js
|
|
1020
1081
|
var SUPPORTED_GEMINI_MODEL_IDS = [
|
|
1021
1082
|
"gemini-2.5-pro",
|
|
@@ -1149,7 +1210,8 @@ function readFramedBody(stream, onChunk, vendorLabel, stallTimeout) {
|
|
|
1149
1210
|
var MAX_RETRIES = 5;
|
|
1150
1211
|
function postWithRetry(options) {
|
|
1151
1212
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1152
|
-
var _a, _b
|
|
1213
|
+
var _a, _b;
|
|
1214
|
+
var _c, _d;
|
|
1153
1215
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal } = options;
|
|
1154
1216
|
const backoff = (attempt) => abortableDelay(backoffBaseMs * (1 << attempt), signal);
|
|
1155
1217
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
|
|
@@ -1170,15 +1232,19 @@ function postWithRetry(options) {
|
|
|
1170
1232
|
signal: combinedSignal,
|
|
1171
1233
|
credentials
|
|
1172
1234
|
});
|
|
1173
|
-
if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
|
|
1174
|
-
logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1175
|
-
clearTimeout(timeoutId);
|
|
1176
|
-
yield backoff(attempt);
|
|
1177
|
-
continue;
|
|
1178
|
-
}
|
|
1179
1235
|
if (!response.ok) {
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1236
|
+
const errText = yield response.text();
|
|
1237
|
+
const parsed = parseJsonOrUndefined(errText);
|
|
1238
|
+
if (isBudgetRejection(response.status, codeOf(parsed))) {
|
|
1239
|
+
throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
|
|
1240
|
+
}
|
|
1241
|
+
if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
|
|
1242
|
+
logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1243
|
+
clearTimeout(timeoutId);
|
|
1244
|
+
yield backoff(attempt);
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
|
|
1182
1248
|
}
|
|
1183
1249
|
if (((_b = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get("content-type")) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes("application/x-ndjson")) && response.body) {
|
|
1184
1250
|
rearmStallTimer();
|
|
@@ -1188,6 +1254,9 @@ function postWithRetry(options) {
|
|
|
1188
1254
|
if (frame.code === "UPSTREAM_STALLED") {
|
|
1189
1255
|
throw new DOMException(`${vendorLabel} request stalled: ${(_c = frame.error) !== null && _c !== void 0 ? _c : "no upstream data"}`, "TimeoutError");
|
|
1190
1256
|
}
|
|
1257
|
+
if (isBudgetRejection(frame.status, frame.code)) {
|
|
1258
|
+
throw budgetExhaustedFrom(vendorLabel, frame);
|
|
1259
|
+
}
|
|
1191
1260
|
if (retryableStatuses.includes(frame.status) && attempt < MAX_RETRIES) {
|
|
1192
1261
|
logger.warn(`${vendorLabel}Transport: retryable status ${frame.status} (err frame), retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1193
1262
|
clearTimeout(timeoutId);
|
|
@@ -1504,7 +1573,8 @@ var AnthropicTransport = class _AnthropicTransport {
|
|
|
1504
1573
|
* the payload tidy.
|
|
1505
1574
|
*/
|
|
1506
1575
|
toAnthropicMessages(history, userMessage, attachments) {
|
|
1507
|
-
var _a, _b
|
|
1576
|
+
var _a, _b;
|
|
1577
|
+
var _c;
|
|
1508
1578
|
const messages = [];
|
|
1509
1579
|
const pushBlock = (role, block) => {
|
|
1510
1580
|
const last = messages[messages.length - 1];
|
|
@@ -1534,13 +1604,13 @@ var AnthropicTransport = class _AnthropicTransport {
|
|
|
1534
1604
|
type: "tool_use",
|
|
1535
1605
|
id: tc.id,
|
|
1536
1606
|
name: tc.name,
|
|
1537
|
-
input: (
|
|
1607
|
+
input: (_c = tc.args) !== null && _c !== void 0 ? _c : {}
|
|
1538
1608
|
});
|
|
1539
1609
|
}
|
|
1540
1610
|
continue;
|
|
1541
1611
|
}
|
|
1542
1612
|
const role = msg.role === "user" ? "user" : "assistant";
|
|
1543
|
-
if (role === "user" && ((
|
|
1613
|
+
if (role === "user" && ((_b = msg.attachments) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
1544
1614
|
pushBlock(role, { type: "text", text: msg.content });
|
|
1545
1615
|
for (const att of msg.attachments) {
|
|
1546
1616
|
pushBlock(role, { type: "text", text: `[File: ${att.name}]
|
|
@@ -1658,7 +1728,7 @@ ${att.content}` });
|
|
|
1658
1728
|
headers: requestHeaders,
|
|
1659
1729
|
body,
|
|
1660
1730
|
credentials,
|
|
1661
|
-
vendorLabel:
|
|
1731
|
+
vendorLabel: VENDOR_LABELS.anthropic,
|
|
1662
1732
|
retryableStatuses: _AnthropicTransport.RETRYABLE_STATUSES,
|
|
1663
1733
|
timeout: this.timeout,
|
|
1664
1734
|
stallTimeout: this.stallTimeout,
|
|
@@ -2016,7 +2086,8 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2016
2086
|
// ── ChatTransport (multi-turn chat) ────────────────────────────────────
|
|
2017
2087
|
sendChatMessage(history, userMessage, options) {
|
|
2018
2088
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2019
|
-
var _a
|
|
2089
|
+
var _a;
|
|
2090
|
+
var _b;
|
|
2020
2091
|
const contents = this.toGeminiContents(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
|
|
2021
2092
|
if (options === null || options === void 0 ? void 0 : options.tailContext) {
|
|
2022
2093
|
contents.push({ role: "user", parts: [{ text: options.tailContext }] });
|
|
@@ -2080,7 +2151,8 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2080
2151
|
return totalCost;
|
|
2081
2152
|
}
|
|
2082
2153
|
toGeminiContents(history, userMessage, attachments) {
|
|
2083
|
-
var _a, _b
|
|
2154
|
+
var _a, _b;
|
|
2155
|
+
var _c;
|
|
2084
2156
|
const contents = [];
|
|
2085
2157
|
const toolCallNameById = /* @__PURE__ */ new Map();
|
|
2086
2158
|
for (const msg of history) {
|
|
@@ -2088,7 +2160,7 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2088
2160
|
continue;
|
|
2089
2161
|
const role = msg.role === "user" ? "user" : "model";
|
|
2090
2162
|
if (msg.toolResult) {
|
|
2091
|
-
const fnName = (
|
|
2163
|
+
const fnName = (_c = toolCallNameById.get(msg.toolResult.toolCallId)) !== null && _c !== void 0 ? _c : msg.toolResult.toolCallId;
|
|
2092
2164
|
contents.push({
|
|
2093
2165
|
role: "user",
|
|
2094
2166
|
parts: [
|
|
@@ -2100,7 +2172,7 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2100
2172
|
}
|
|
2101
2173
|
]
|
|
2102
2174
|
});
|
|
2103
|
-
} else if ((
|
|
2175
|
+
} else if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
|
|
2104
2176
|
for (const tc of msg.toolCalls) {
|
|
2105
2177
|
toolCallNameById.set(tc.id, tc.name);
|
|
2106
2178
|
}
|
|
@@ -2119,7 +2191,7 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2119
2191
|
return part;
|
|
2120
2192
|
})
|
|
2121
2193
|
});
|
|
2122
|
-
} else if (role === "user" && ((
|
|
2194
|
+
} else if (role === "user" && ((_b = msg.attachments) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
2123
2195
|
const msgAttachmentParts = msg.attachments.map((att) => ({
|
|
2124
2196
|
text: `[File: ${att.name}]
|
|
2125
2197
|
${att.content}`
|
|
@@ -2172,11 +2244,12 @@ ${att.content}`
|
|
|
2172
2244
|
};
|
|
2173
2245
|
}
|
|
2174
2246
|
fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set()) {
|
|
2175
|
-
var _a, _b
|
|
2247
|
+
var _a, _b;
|
|
2248
|
+
var _c, _d;
|
|
2176
2249
|
const { inputTokens, outputTokens, thoughtsTokens, cacheReadTokens, cost } = this.usageFromGemini(response.usageMetadata);
|
|
2177
2250
|
const candidates = response === null || response === void 0 ? void 0 : response.candidates;
|
|
2178
2251
|
const firstCandidate = candidates === null || candidates === void 0 ? void 0 : candidates[0];
|
|
2179
|
-
const parts = (
|
|
2252
|
+
const parts = (_c = (_a = firstCandidate === null || firstCandidate === void 0 ? void 0 : firstCandidate.content) === null || _a === void 0 ? void 0 : _a.parts) !== null && _c !== void 0 ? _c : [];
|
|
2180
2253
|
const toolCalls = [];
|
|
2181
2254
|
const thoughtParts = [];
|
|
2182
2255
|
const textParts = [];
|
|
@@ -2189,7 +2262,7 @@ ${att.content}`
|
|
|
2189
2262
|
toolCalls.push({
|
|
2190
2263
|
id: crypto.randomUUID(),
|
|
2191
2264
|
name: part.functionCall.name,
|
|
2192
|
-
args: (
|
|
2265
|
+
args: (_d = part.functionCall.args) !== null && _d !== void 0 ? _d : {},
|
|
2193
2266
|
providerMetadata: { [GEMINI_PROVIDER_KEY]: state }
|
|
2194
2267
|
});
|
|
2195
2268
|
} else if (part.thought && part.text) {
|
|
@@ -2227,7 +2300,7 @@ ${att.content}`
|
|
|
2227
2300
|
base.cacheReadTokens = cacheReadTokens;
|
|
2228
2301
|
if (cost != null)
|
|
2229
2302
|
base.cost = cost;
|
|
2230
|
-
const blockReason = (
|
|
2303
|
+
const blockReason = (_b = response.promptFeedback) === null || _b === void 0 ? void 0 : _b.blockReason;
|
|
2231
2304
|
if (finishReason != null || thoughtsTokens != null || blockReason != null) {
|
|
2232
2305
|
base.responseMeta = {
|
|
2233
2306
|
finishReason,
|
|
@@ -2273,9 +2346,10 @@ ${att.content}`
|
|
|
2273
2346
|
* without answering" signature.
|
|
2274
2347
|
*/
|
|
2275
2348
|
logAbnormalResponse(response, parsed) {
|
|
2276
|
-
var _a
|
|
2277
|
-
|
|
2278
|
-
const
|
|
2349
|
+
var _a;
|
|
2350
|
+
var _b, _c;
|
|
2351
|
+
const usage = (_b = response.usageMetadata) !== null && _b !== void 0 ? _b : {};
|
|
2352
|
+
const candidate = (_c = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0]) !== null && _c !== void 0 ? _c : {};
|
|
2279
2353
|
logger.warn("GeminiTransport: blank/non-STOP response", {
|
|
2280
2354
|
model: this.model,
|
|
2281
2355
|
finishReason: parsed.finishReason,
|
|
@@ -2326,7 +2400,7 @@ ${att.content}`
|
|
|
2326
2400
|
headers,
|
|
2327
2401
|
body: payload,
|
|
2328
2402
|
credentials,
|
|
2329
|
-
vendorLabel:
|
|
2403
|
+
vendorLabel: VENDOR_LABELS.gemini,
|
|
2330
2404
|
retryableStatuses: _GeminiTransport.RETRYABLE_STATUSES,
|
|
2331
2405
|
timeout: this.timeout,
|
|
2332
2406
|
stallTimeout: this.stallTimeout,
|
|
@@ -2379,7 +2453,8 @@ var EmptyAIProviderRegistry = class _EmptyAIProviderRegistry {
|
|
|
2379
2453
|
}
|
|
2380
2454
|
getStatus(name) {
|
|
2381
2455
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2382
|
-
var _a, _b
|
|
2456
|
+
var _a, _b;
|
|
2457
|
+
var _c;
|
|
2383
2458
|
if (name !== void 0 && name !== _EmptyAIProviderRegistry.DEFAULT_NAME)
|
|
2384
2459
|
return null;
|
|
2385
2460
|
return (_c = yield (_b = (_a = this.noopProvider).getStatus) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
|
|
@@ -2387,7 +2462,8 @@ var EmptyAIProviderRegistry = class _EmptyAIProviderRegistry {
|
|
|
2387
2462
|
}
|
|
2388
2463
|
listStatuses() {
|
|
2389
2464
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2390
|
-
var _a, _b
|
|
2465
|
+
var _a, _b;
|
|
2466
|
+
var _c;
|
|
2391
2467
|
const status = (_c = yield (_b = (_a = this.noopProvider).getStatus) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
|
|
2392
2468
|
return [{ name: _EmptyAIProviderRegistry.DEFAULT_NAME, isDefault: true, status }];
|
|
2393
2469
|
});
|
|
@@ -2431,7 +2507,8 @@ var MutableAIProviderRegistry = class _MutableAIProviderRegistry {
|
|
|
2431
2507
|
}
|
|
2432
2508
|
getStatus(name) {
|
|
2433
2509
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2434
|
-
var _a
|
|
2510
|
+
var _a;
|
|
2511
|
+
var _b;
|
|
2435
2512
|
const provider = name === void 0 ? this.default() : this.providers.get(name);
|
|
2436
2513
|
if (!provider)
|
|
2437
2514
|
return null;
|
|
@@ -2442,7 +2519,8 @@ var MutableAIProviderRegistry = class _MutableAIProviderRegistry {
|
|
|
2442
2519
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2443
2520
|
const entries = [...this.providers.entries()];
|
|
2444
2521
|
return Promise.all(entries.map((_a) => __awaiter(this, [_a], void 0, function* ([name, provider]) {
|
|
2445
|
-
var _b
|
|
2522
|
+
var _b;
|
|
2523
|
+
var _c;
|
|
2446
2524
|
return {
|
|
2447
2525
|
name,
|
|
2448
2526
|
isDefault: name === this._defaultName,
|
|
@@ -3076,6 +3154,19 @@ function accumulate(messages, into) {
|
|
|
3076
3154
|
var TOOL_FOLD_SYMBOL = Symbol("toolFold");
|
|
3077
3155
|
|
|
3078
3156
|
// src/components/chat-driver/chat-driver.ts
|
|
3157
|
+
var budgetDetailOf = (e) => {
|
|
3158
|
+
const vendor = vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor);
|
|
3159
|
+
if (e.budgetUsd == null && e.spentUsd == null && !vendor) return void 0;
|
|
3160
|
+
return {
|
|
3161
|
+
budgetUsd: e.budgetUsd,
|
|
3162
|
+
spentUsd: e.spentUsd,
|
|
3163
|
+
vendorLabel: e.vendorLabel,
|
|
3164
|
+
...vendor ? { vendor } : {},
|
|
3165
|
+
// Server-known truth the client cannot re-derive, so it rides all the way to
|
|
3166
|
+
// the banner rather than being re-guessed there from registry membership.
|
|
3167
|
+
...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
|
|
3168
|
+
};
|
|
3169
|
+
};
|
|
3079
3170
|
var DEFAULT_MAX_TOOL_ITERATIONS = 50;
|
|
3080
3171
|
var DEFAULT_MAX_FOLD_OPERATIONS = 5;
|
|
3081
3172
|
var DEFAULT_MAX_TURN_SNAPSHOTS = 400;
|
|
@@ -3258,6 +3349,22 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3258
3349
|
* picked up on the next turn.
|
|
3259
3350
|
*/
|
|
3260
3351
|
this.resolvedStatusCache = /* @__PURE__ */ new Map();
|
|
3352
|
+
/**
|
|
3353
|
+
* Set the moment a budget wall is observed anywhere in this turn — this
|
|
3354
|
+
* driver's own 402, or a sub-agent's (which surfaces here only as a
|
|
3355
|
+
* `'budget_exhausted'` tool result). Read at the top of the tool loop to end
|
|
3356
|
+
* the turn before issuing another model call that would hit the same wall.
|
|
3357
|
+
* Reset per turn alongside the other per-turn counters.
|
|
3358
|
+
*/
|
|
3359
|
+
this.budgetExhaustedThisTurn = false;
|
|
3360
|
+
/**
|
|
3361
|
+
* Whether this turn's budget wall came from a SUB-AGENT rather than this
|
|
3362
|
+
* driver's own request. Decides whether `lastResolvedProvider` is a valid
|
|
3363
|
+
* attribution fallback: for an own wall it is the refusing vendor, for a
|
|
3364
|
+
* child's wall it is the parent's vendor — the one known NOT to have refused.
|
|
3365
|
+
* Reset per turn alongside `budgetWallDetail`.
|
|
3366
|
+
*/
|
|
3367
|
+
this.budgetWallViaSubAgent = false;
|
|
3261
3368
|
const {
|
|
3262
3369
|
toolHandlers = {},
|
|
3263
3370
|
toolDefinitions = [],
|
|
@@ -3268,12 +3375,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3268
3375
|
condenseBatchCalls = 1,
|
|
3269
3376
|
maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
3270
3377
|
sessionKey = "",
|
|
3271
|
-
activityBus = NOOP_ACTIVITY_BUS
|
|
3378
|
+
activityBus = NOOP_ACTIVITY_BUS,
|
|
3379
|
+
budgetExhaustedMessage = DEFAULT_BUDGET_EXHAUSTED_MESSAGE
|
|
3272
3380
|
} = config;
|
|
3273
3381
|
this.maxToolIterations = maxToolIterations;
|
|
3274
3382
|
this.condenseBatchCalls = condenseBatchCalls;
|
|
3275
3383
|
this.sessionKey = sessionKey;
|
|
3276
3384
|
this.activityBus = activityBus;
|
|
3385
|
+
this.budgetExhaustedMessage = budgetExhaustedMessage;
|
|
3277
3386
|
if (typeof toolHandlers === "function") {
|
|
3278
3387
|
this.toolHandlersFactory = toolHandlers;
|
|
3279
3388
|
this.toolHandlers = {};
|
|
@@ -3380,8 +3489,56 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3380
3489
|
* (rather than set to `undefined`) so a happy-path result stays byte-identical to
|
|
3381
3490
|
* the historical `{ reason: 'done' }`.
|
|
3382
3491
|
*/
|
|
3383
|
-
turnDone(failureReason) {
|
|
3384
|
-
|
|
3492
|
+
turnDone(failureReason, budget) {
|
|
3493
|
+
if (!failureReason) return { reason: "done" };
|
|
3494
|
+
return budget ? { reason: "done", failureReason, budget } : { reason: "done", failureReason };
|
|
3495
|
+
}
|
|
3496
|
+
/**
|
|
3497
|
+
* Terminal budget outcome for a wall hit **outside** the tool loop — today,
|
|
3498
|
+
* `OrchestratingDriver`'s classification phase, which calls the provider
|
|
3499
|
+
* directly and so never enters `runToolLoop`.
|
|
3500
|
+
*
|
|
3501
|
+
* Does **not** publish `tool-loop-end`: no `tool-loop-start` was published for
|
|
3502
|
+
* the classify phase, and an unbalanced end would break start/end pairing for
|
|
3503
|
+
* subscribers that rely on it. The driver **return value** is what reports this
|
|
3504
|
+
* case — see `FoundationAiAssistant`'s latch, which reads both seams for
|
|
3505
|
+
* exactly this reason.
|
|
3506
|
+
*
|
|
3507
|
+
* The non-sub-agent tail of the in-loop `BudgetExhaustedError` branch lives
|
|
3508
|
+
* here so there is one copy of the log line, the debug-log entry, the
|
|
3509
|
+
* transcript bubble and the result shape rather than two that can drift.
|
|
3510
|
+
*
|
|
3511
|
+
* @param pendingUserMessage - a user message that has NOT yet been appended,
|
|
3512
|
+
* appended first so the answer does not end up replying to nothing. Only the
|
|
3513
|
+
* classification seam passes it: `OrchestratingDriver` dispatches the user's
|
|
3514
|
+
* text as an optimistic `history-updated` detail and leaves the real append
|
|
3515
|
+
* to `chatDriver.sendMessage`, which never runs when `classify()` throws — so
|
|
3516
|
+
* the bubble below would re-dispatch a history the user's own message was
|
|
3517
|
+
* never in, and it would vanish from the transcript on the next render. The
|
|
3518
|
+
* in-loop caller has already appended it and passes nothing.
|
|
3519
|
+
*
|
|
3520
|
+
* @internal
|
|
3521
|
+
*/
|
|
3522
|
+
reportBudgetExhausted(e, pendingUserMessage) {
|
|
3523
|
+
if (pendingUserMessage) this.appendToHistory(pendingUserMessage);
|
|
3524
|
+
this.budgetExhaustedThisTurn = true;
|
|
3525
|
+
logger2.error("ChatDriver: AI budget exhausted", e);
|
|
3526
|
+
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
3527
|
+
agent: this.activeAgentName,
|
|
3528
|
+
provider: this.lastResolvedProviderName,
|
|
3529
|
+
// The registry ALIAS (e.g. 'high') is what `provider` records; the vendor
|
|
3530
|
+
// is the thing a per-vendor budget is actually scoped to, and it was known
|
|
3531
|
+
// at both ends and discarded in the middle until now. Taken from the
|
|
3532
|
+
// refusing transport's label first — this method also serves the
|
|
3533
|
+
// classification seam, where `lastResolvedProvider` is the PREVIOUS turn's
|
|
3534
|
+
// vendor (or nothing), because classify runs against the registry default.
|
|
3535
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
|
|
3536
|
+
budgetUsd: e.budgetUsd,
|
|
3537
|
+
spentUsd: e.spentUsd,
|
|
3538
|
+
isSubAgent: this.isSubAgent
|
|
3539
|
+
});
|
|
3540
|
+
this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
|
|
3541
|
+
return this.turnDone("budget-exhausted", budgetDetailOf(e));
|
|
3385
3542
|
}
|
|
3386
3543
|
/** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
|
|
3387
3544
|
static failureReasonOf(result) {
|
|
@@ -3391,10 +3548,28 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3391
3548
|
* Build the `tool-loop-end` event detail for a turn's result. A failure carries a
|
|
3392
3549
|
* `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
|
|
3393
3550
|
* kept byte-identical so subscribers see exactly what they always have.
|
|
3551
|
+
*
|
|
3552
|
+
* A budget failure additionally carries `vendor` — the concrete vendor
|
|
3553
|
+
* (`'anthropic'`/`'gemini'`) the walled turn resolved to, which the driver knows
|
|
3554
|
+
* and used to discard. Optional and additive: a subscriber reading only
|
|
3555
|
+
* `failureReason` is unaffected, a non-budget failure still emits the historical
|
|
3556
|
+
* `{ failureReason }` with no `vendor` key, and the value is a plain string so
|
|
3557
|
+
* the detail stays structured-cloneable for the cross-tab hop. It is the field a
|
|
3558
|
+
* per-vendor budget model needs and the one that would be awkward to retrofit.
|
|
3394
3559
|
*/
|
|
3395
|
-
|
|
3560
|
+
loopEndDetail(result) {
|
|
3396
3561
|
const failureReason = _ChatDriver.failureReasonOf(result);
|
|
3397
|
-
|
|
3562
|
+
if (!failureReason) return void 0;
|
|
3563
|
+
if (failureReason !== "budget-exhausted") {
|
|
3564
|
+
return { failureReason };
|
|
3565
|
+
}
|
|
3566
|
+
const budget = result.reason === "done" ? result.budget : void 0;
|
|
3567
|
+
const vendor = budget?.vendor ?? (this.budgetWallViaSubAgent ? void 0 : this.lastResolvedProvider);
|
|
3568
|
+
return {
|
|
3569
|
+
failureReason,
|
|
3570
|
+
...vendor ? { vendor } : {},
|
|
3571
|
+
...budget ? { budget } : {}
|
|
3572
|
+
};
|
|
3398
3573
|
}
|
|
3399
3574
|
/**
|
|
3400
3575
|
* Swap in a new agent's configuration. Called by OrchestratingDriver before
|
|
@@ -3580,9 +3755,9 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3580
3755
|
* under a separate session key, so recording here would orphan the event off
|
|
3581
3756
|
* the user-visible debug-log timeline.)
|
|
3582
3757
|
*/
|
|
3583
|
-
failSubAgent(reason) {
|
|
3758
|
+
failSubAgent(reason, budget) {
|
|
3584
3759
|
if (!this.isSubAgent || this.subAgentFailure) return;
|
|
3585
|
-
this.subAgentFailure = { reason };
|
|
3760
|
+
this.subAgentFailure = budget ? { reason, budget } : { reason };
|
|
3586
3761
|
}
|
|
3587
3762
|
/**
|
|
3588
3763
|
* Returns true if `releaseAgent` was called during the most recent turn.
|
|
@@ -4032,6 +4207,9 @@ Output format (strict):
|
|
|
4032
4207
|
this.subAgentCompletion = void 0;
|
|
4033
4208
|
this.subAgentFailure = void 0;
|
|
4034
4209
|
this.agentReleaseRequested = false;
|
|
4210
|
+
this.budgetExhaustedThisTurn = false;
|
|
4211
|
+
this.budgetWallDetail = void 0;
|
|
4212
|
+
this.budgetWallViaSubAgent = false;
|
|
4035
4213
|
this.appendToHistory({ role: "user", content: userInput, attachments });
|
|
4036
4214
|
this.turnStartedAt = Date.now();
|
|
4037
4215
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
@@ -4066,7 +4244,7 @@ Output format (strict):
|
|
|
4066
4244
|
});
|
|
4067
4245
|
this.busy = false;
|
|
4068
4246
|
this.endTurn();
|
|
4069
|
-
this.activityBus.publish("tool-loop-end",
|
|
4247
|
+
this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
|
|
4070
4248
|
}
|
|
4071
4249
|
}
|
|
4072
4250
|
/**
|
|
@@ -4257,8 +4435,14 @@ Output format (strict):
|
|
|
4257
4435
|
recordMetaEvent(this.sessionKey, "subagent.completed", { agent: name });
|
|
4258
4436
|
return { outcome: { ok: true, result: completion.result }, trace };
|
|
4259
4437
|
}
|
|
4260
|
-
const
|
|
4438
|
+
const failure = child.getSubAgentFailure();
|
|
4439
|
+
const reason = failure?.reason ?? "max_iterations";
|
|
4261
4440
|
recordMetaEvent(this.sessionKey, "subagent.failed", { agent: name, reason });
|
|
4441
|
+
if (reason === "budget_exhausted") {
|
|
4442
|
+
this.budgetExhaustedThisTurn = true;
|
|
4443
|
+
this.budgetWallViaSubAgent = true;
|
|
4444
|
+
this.budgetWallDetail ??= failure?.budget;
|
|
4445
|
+
}
|
|
4262
4446
|
return { outcome: { ok: false, reason }, trace };
|
|
4263
4447
|
}
|
|
4264
4448
|
/**
|
|
@@ -4271,6 +4455,9 @@ Output format (strict):
|
|
|
4271
4455
|
this.beginTurn();
|
|
4272
4456
|
this.subAgentCompletion = void 0;
|
|
4273
4457
|
this.subAgentFailure = void 0;
|
|
4458
|
+
this.budgetExhaustedThisTurn = false;
|
|
4459
|
+
this.budgetWallDetail = void 0;
|
|
4460
|
+
this.budgetWallViaSubAgent = false;
|
|
4274
4461
|
this.turnStartedAt = Date.now();
|
|
4275
4462
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
4276
4463
|
phase: "continueFromHistory",
|
|
@@ -4304,7 +4491,7 @@ Output format (strict):
|
|
|
4304
4491
|
});
|
|
4305
4492
|
this.busy = false;
|
|
4306
4493
|
this.endTurn();
|
|
4307
|
-
this.activityBus.publish("tool-loop-end",
|
|
4494
|
+
this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
|
|
4308
4495
|
}
|
|
4309
4496
|
}
|
|
4310
4497
|
// ---------------------------------------------------------------------------
|
|
@@ -4458,6 +4645,31 @@ Output format (strict):
|
|
|
4458
4645
|
if (this.turnController.signal.aborted) {
|
|
4459
4646
|
return this.completeAbortedTurn();
|
|
4460
4647
|
}
|
|
4648
|
+
if (this.budgetExhaustedThisTurn) {
|
|
4649
|
+
logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the AI budget wall");
|
|
4650
|
+
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
4651
|
+
agent: this.activeAgentName,
|
|
4652
|
+
provider: this.lastResolvedProviderName,
|
|
4653
|
+
// The CHILD's vendor when it knew one, and NOTHING otherwise. On this
|
|
4654
|
+
// path the wall is definitionally the child's, and on a mixed registry
|
|
4655
|
+
// `lastResolvedProvider` is this driver's own vendor — the one known
|
|
4656
|
+
// NOT to have refused. An unattributable child wall must degrade to
|
|
4657
|
+
// the vendor-agnostic block (which `latchBlockedFrom` handles
|
|
4658
|
+
// fail-safe), never to a vendor that is known to be wrong: naming the
|
|
4659
|
+
// parent's vendor here walled BOTH — the child's via its own
|
|
4660
|
+
// tool-loop-end, the parent's via this event — and derived `blocked`
|
|
4661
|
+
// over headroom that still existed.
|
|
4662
|
+
vendor: this.budgetWallDetail?.vendor,
|
|
4663
|
+
via: "sub-agent",
|
|
4664
|
+
isSubAgent: this.isSubAgent
|
|
4665
|
+
});
|
|
4666
|
+
if (this.isSubAgent) {
|
|
4667
|
+
this.failSubAgent("budget_exhausted", this.budgetWallDetail);
|
|
4668
|
+
} else {
|
|
4669
|
+
this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
|
|
4670
|
+
}
|
|
4671
|
+
return this.turnDone("budget-exhausted", this.budgetWallDetail);
|
|
4672
|
+
}
|
|
4461
4673
|
const promptCtx = {
|
|
4462
4674
|
agentName: this.activeAgentName ?? "",
|
|
4463
4675
|
history: this.history,
|
|
@@ -4629,6 +4841,25 @@ ${tailBody}
|
|
|
4629
4841
|
}
|
|
4630
4842
|
return this.turnDone("response-truncated");
|
|
4631
4843
|
}
|
|
4844
|
+
if (e instanceof BudgetExhaustedError) {
|
|
4845
|
+
this.budgetExhaustedThisTurn = true;
|
|
4846
|
+
this.budgetWallDetail = budgetDetailOf(e);
|
|
4847
|
+
this.budgetWallViaSubAgent = false;
|
|
4848
|
+
if (this.isSubAgent) {
|
|
4849
|
+
logger2.error("ChatDriver: AI budget exhausted", e);
|
|
4850
|
+
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
4851
|
+
agent: this.activeAgentName,
|
|
4852
|
+
provider: this.lastResolvedProviderName,
|
|
4853
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
|
|
4854
|
+
budgetUsd: e.budgetUsd,
|
|
4855
|
+
spentUsd: e.spentUsd,
|
|
4856
|
+
isSubAgent: true
|
|
4857
|
+
});
|
|
4858
|
+
this.failSubAgent("budget_exhausted", this.budgetWallDetail);
|
|
4859
|
+
return this.turnDone("budget-exhausted", this.budgetWallDetail);
|
|
4860
|
+
}
|
|
4861
|
+
return this.reportBudgetExhausted(e);
|
|
4862
|
+
}
|
|
4632
4863
|
if (e instanceof DOMException && e.name === "TimeoutError") {
|
|
4633
4864
|
logger2.error("ChatDriver: request timed out", e);
|
|
4634
4865
|
recordTurnError(this.sessionKey, "exception", {
|
|
@@ -5104,6 +5335,17 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5104
5335
|
* user stops. Reset at the start of each `sendMessage`.
|
|
5105
5336
|
*/
|
|
5106
5337
|
this.cancelled = false;
|
|
5338
|
+
/**
|
|
5339
|
+
* Whether the CURRENT turn's user message has reached the inner driver's
|
|
5340
|
+
* history. False through the pre-first-turn classify (where the only echo of
|
|
5341
|
+
* the message is an optimistic `history-updated` dispatch), true from the
|
|
5342
|
+
* moment `chatDriver.sendMessage` is entered — including through every later
|
|
5343
|
+
* handoff classify. Read by the budget-wall catch in `sendMessage` to decide
|
|
5344
|
+
* whether `reportBudgetExhausted` must append the message itself: appending
|
|
5345
|
+
* it when already appended duplicated it; not appending it when unappended
|
|
5346
|
+
* made it vanish. Reset at the top of each `runOrchestratedTurn`.
|
|
5347
|
+
*/
|
|
5348
|
+
this.userMessageAppended = false;
|
|
5107
5349
|
/**
|
|
5108
5350
|
* Sticky user pick from the picker (or the host's `setAgent` API). Only
|
|
5109
5351
|
* changes on explicit user action. Survives flow completion: when a stateful
|
|
@@ -5145,7 +5387,8 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5145
5387
|
maxFoldOperations: options.maxFoldOperations,
|
|
5146
5388
|
maxTurnSnapshots: options.maxTurnSnapshots,
|
|
5147
5389
|
sessionKey: this.sessionKey,
|
|
5148
|
-
activityBus: options.activityBus
|
|
5390
|
+
activityBus: options.activityBus,
|
|
5391
|
+
budgetExhaustedMessage: options.budgetExhaustedMessage
|
|
5149
5392
|
});
|
|
5150
5393
|
this.chatDriver.addEventListener("history-updated", (e) => {
|
|
5151
5394
|
this.dispatchEvent(new CustomEvent("history-updated", { detail: e.detail }));
|
|
@@ -5244,7 +5487,25 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5244
5487
|
return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
|
|
5245
5488
|
}
|
|
5246
5489
|
async sendMessage(input, attachments) {
|
|
5490
|
+
try {
|
|
5491
|
+
return await this.runOrchestratedTurn(input, attachments);
|
|
5492
|
+
} catch (e) {
|
|
5493
|
+
if (e instanceof BudgetExhaustedError) {
|
|
5494
|
+
return this.chatDriver.reportBudgetExhausted(
|
|
5495
|
+
e,
|
|
5496
|
+
this.userMessageAppended ? void 0 : {
|
|
5497
|
+
role: "user",
|
|
5498
|
+
content: input,
|
|
5499
|
+
...attachments ? { attachments } : {}
|
|
5500
|
+
}
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
throw e;
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
async runOrchestratedTurn(input, attachments) {
|
|
5247
5507
|
this.cancelled = false;
|
|
5508
|
+
this.userMessageAppended = false;
|
|
5248
5509
|
const history = this.chatDriver.getHistory();
|
|
5249
5510
|
this.dispatchEvent(
|
|
5250
5511
|
new CustomEvent("history-updated", {
|
|
@@ -5258,6 +5519,7 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5258
5519
|
let handoffs = 0;
|
|
5259
5520
|
let handoffSummary = "";
|
|
5260
5521
|
let remainingTask = "";
|
|
5522
|
+
let lastResult;
|
|
5261
5523
|
while (true) {
|
|
5262
5524
|
if (this.cancelled) break;
|
|
5263
5525
|
await this.applyAgent(currentAgent);
|
|
@@ -5266,8 +5528,10 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5266
5528
|
const contextPrimer = handoffSummary ? [{ role: "user", content: `[Context from previous agent]: ${handoffSummary}` }] : [];
|
|
5267
5529
|
result = await this.chatDriver.continueFromHistory(contextPrimer);
|
|
5268
5530
|
} else {
|
|
5531
|
+
this.userMessageAppended = true;
|
|
5269
5532
|
result = await this.chatDriver.sendMessage(input, attachments);
|
|
5270
5533
|
}
|
|
5534
|
+
lastResult = result;
|
|
5271
5535
|
if (this.chatDriver.getAgentReleaseRequested()) {
|
|
5272
5536
|
await this.releaseActiveAgent();
|
|
5273
5537
|
break;
|
|
@@ -5289,7 +5553,7 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5289
5553
|
this.dispatchEvent(new CustomEvent("orchestrating-start"));
|
|
5290
5554
|
currentAgent = await this.classify(remainingTask, updatedHistory);
|
|
5291
5555
|
}
|
|
5292
|
-
return { reason: "done" };
|
|
5556
|
+
return lastResult?.reason === "done" ? lastResult : { reason: "done" };
|
|
5293
5557
|
}
|
|
5294
5558
|
async continueFromHistory(transientPrimer) {
|
|
5295
5559
|
return this.chatDriver.continueFromHistory(transientPrimer);
|
|
@@ -5472,6 +5736,7 @@ ${recentMessages}` : ""}`;
|
|
|
5472
5736
|
}
|
|
5473
5737
|
}
|
|
5474
5738
|
};
|
|
5739
|
+
let routedNoMatch = false;
|
|
5475
5740
|
for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
|
|
5476
5741
|
try {
|
|
5477
5742
|
const options = {
|
|
@@ -5491,8 +5756,10 @@ ${recentMessages}` : ""}`;
|
|
|
5491
5756
|
if (index >= 0 && index < this.specialists.length) {
|
|
5492
5757
|
return this.specialists[index];
|
|
5493
5758
|
}
|
|
5759
|
+
routedNoMatch = true;
|
|
5494
5760
|
break;
|
|
5495
5761
|
} catch (e) {
|
|
5762
|
+
if (e instanceof BudgetExhaustedError) throw e;
|
|
5496
5763
|
logger2.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
|
|
5497
5764
|
if (attempt === this.classifierRetries) {
|
|
5498
5765
|
logger2.error("OrchestratingDriver: classifier failed after all retries, using fallback");
|
|
@@ -5500,10 +5767,12 @@ ${recentMessages}` : ""}`;
|
|
|
5500
5767
|
}
|
|
5501
5768
|
}
|
|
5502
5769
|
if (this.fallback) return this.fallback;
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5770
|
+
if (routedNoMatch) {
|
|
5771
|
+
const specialistNames = this.specialists.map((s) => s.name).join(", ");
|
|
5772
|
+
this.appendInlineMessage(
|
|
5773
|
+
`I'm not sure how to help with that. I can assist with: ${specialistNames}.`
|
|
5774
|
+
);
|
|
5775
|
+
}
|
|
5507
5776
|
return this.specialists[0];
|
|
5508
5777
|
}
|
|
5509
5778
|
/**
|