@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.mjs
CHANGED
|
@@ -969,6 +969,67 @@ var GeminiProvider = class {
|
|
|
969
969
|
}
|
|
970
970
|
};
|
|
971
971
|
|
|
972
|
+
// ../../foundation-ai/dist/esm/transports/budget-exhausted-error.js
|
|
973
|
+
var BUDGET_EXCEEDED_CODE = "BUDGET_EXCEEDED";
|
|
974
|
+
var VENDOR_LABELS = Object.freeze({
|
|
975
|
+
anthropic: "Anthropic",
|
|
976
|
+
gemini: "Gemini",
|
|
977
|
+
openai: "OpenAI",
|
|
978
|
+
chrome: "Chrome"
|
|
979
|
+
});
|
|
980
|
+
var BUDGETED_VENDORS = Object.freeze([
|
|
981
|
+
"anthropic",
|
|
982
|
+
"gemini"
|
|
983
|
+
]);
|
|
984
|
+
var VENDOR_TYPE_BY_LABEL = new Map(Object.entries(VENDOR_LABELS).map(([type, label]) => [
|
|
985
|
+
label.toLowerCase(),
|
|
986
|
+
type
|
|
987
|
+
]));
|
|
988
|
+
function vendorTypeOfLabel(label) {
|
|
989
|
+
if (typeof label !== "string")
|
|
990
|
+
return void 0;
|
|
991
|
+
return VENDOR_TYPE_BY_LABEL.get(label.trim().toLowerCase());
|
|
992
|
+
}
|
|
993
|
+
var BUDGET_EXCEEDED_STATUS = 402;
|
|
994
|
+
var DEFAULT_BUDGET_EXHAUSTED_MESSAGE = "You've reached your AI usage limit. Contact your administrator to raise it.";
|
|
995
|
+
var BudgetExhaustedError = class extends Error {
|
|
996
|
+
constructor(vendorLabel, budgetUsd, spentUsd, detail, extra) {
|
|
997
|
+
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.");
|
|
998
|
+
this.vendorLabel = vendorLabel;
|
|
999
|
+
this.budgetUsd = budgetUsd;
|
|
1000
|
+
this.spentUsd = spentUsd;
|
|
1001
|
+
this.detail = detail;
|
|
1002
|
+
this.name = "BudgetExhaustedError";
|
|
1003
|
+
this.otherVendorAvailable = extra === null || extra === void 0 ? void 0 : extra.otherVendorAvailable;
|
|
1004
|
+
this.serverVendor = extra === null || extra === void 0 ? void 0 : extra.serverVendor;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
var isBudgetRejection = (status, code) => status === BUDGET_EXCEEDED_STATUS || code === BUDGET_EXCEEDED_CODE;
|
|
1008
|
+
var parseJsonOrUndefined = (text) => {
|
|
1009
|
+
try {
|
|
1010
|
+
return JSON.parse(text);
|
|
1011
|
+
} catch (_a) {
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
var codeOf = (payload) => {
|
|
1016
|
+
const code = payload === null || payload === void 0 ? void 0 : payload.code;
|
|
1017
|
+
return typeof code === "string" ? code : void 0;
|
|
1018
|
+
};
|
|
1019
|
+
var numberOrUndefined = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1020
|
+
var booleanOrUndefined = (value) => typeof value === "boolean" ? value : void 0;
|
|
1021
|
+
var stringOrUndefined = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
1022
|
+
function budgetExhaustedFrom(vendorLabel, payload, fallbackDetail) {
|
|
1023
|
+
var _a, _b, _c, _d;
|
|
1024
|
+
const body = typeof payload === "object" && payload !== null ? payload : {};
|
|
1025
|
+
const details = typeof body.details === "object" && body.details !== null ? body.details : {};
|
|
1026
|
+
const detail = typeof body.error === "string" ? body.error : fallbackDetail;
|
|
1027
|
+
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, {
|
|
1028
|
+
otherVendorAvailable: (_c = booleanOrUndefined(body.otherVendorAvailable)) !== null && _c !== void 0 ? _c : booleanOrUndefined(details.otherVendorAvailable),
|
|
1029
|
+
serverVendor: (_d = stringOrUndefined(body.vendor)) !== null && _d !== void 0 ? _d : stringOrUndefined(details.vendor)
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
972
1033
|
// ../../foundation-ai/dist/esm/types/config.types.js
|
|
973
1034
|
var SUPPORTED_GEMINI_MODEL_IDS = [
|
|
974
1035
|
"gemini-2.5-pro",
|
|
@@ -1102,7 +1163,8 @@ function readFramedBody(stream, onChunk, vendorLabel, stallTimeout) {
|
|
|
1102
1163
|
var MAX_RETRIES = 5;
|
|
1103
1164
|
function postWithRetry(options) {
|
|
1104
1165
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1105
|
-
var _a, _b
|
|
1166
|
+
var _a, _b;
|
|
1167
|
+
var _c, _d;
|
|
1106
1168
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal } = options;
|
|
1107
1169
|
const backoff = (attempt) => abortableDelay(backoffBaseMs * (1 << attempt), signal);
|
|
1108
1170
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
|
|
@@ -1123,15 +1185,19 @@ function postWithRetry(options) {
|
|
|
1123
1185
|
signal: combinedSignal,
|
|
1124
1186
|
credentials
|
|
1125
1187
|
});
|
|
1126
|
-
if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
|
|
1127
|
-
logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1128
|
-
clearTimeout(timeoutId);
|
|
1129
|
-
yield backoff(attempt);
|
|
1130
|
-
continue;
|
|
1131
|
-
}
|
|
1132
1188
|
if (!response.ok) {
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1189
|
+
const errText = yield response.text();
|
|
1190
|
+
const parsed = parseJsonOrUndefined(errText);
|
|
1191
|
+
if (isBudgetRejection(response.status, codeOf(parsed))) {
|
|
1192
|
+
throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
|
|
1193
|
+
}
|
|
1194
|
+
if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
|
|
1195
|
+
logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1196
|
+
clearTimeout(timeoutId);
|
|
1197
|
+
yield backoff(attempt);
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
|
|
1135
1201
|
}
|
|
1136
1202
|
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) {
|
|
1137
1203
|
rearmStallTimer();
|
|
@@ -1141,6 +1207,9 @@ function postWithRetry(options) {
|
|
|
1141
1207
|
if (frame.code === "UPSTREAM_STALLED") {
|
|
1142
1208
|
throw new DOMException(`${vendorLabel} request stalled: ${(_c = frame.error) !== null && _c !== void 0 ? _c : "no upstream data"}`, "TimeoutError");
|
|
1143
1209
|
}
|
|
1210
|
+
if (isBudgetRejection(frame.status, frame.code)) {
|
|
1211
|
+
throw budgetExhaustedFrom(vendorLabel, frame);
|
|
1212
|
+
}
|
|
1144
1213
|
if (retryableStatuses.includes(frame.status) && attempt < MAX_RETRIES) {
|
|
1145
1214
|
logger.warn(`${vendorLabel}Transport: retryable status ${frame.status} (err frame), retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
1146
1215
|
clearTimeout(timeoutId);
|
|
@@ -1457,7 +1526,8 @@ var AnthropicTransport = class _AnthropicTransport {
|
|
|
1457
1526
|
* the payload tidy.
|
|
1458
1527
|
*/
|
|
1459
1528
|
toAnthropicMessages(history, userMessage, attachments) {
|
|
1460
|
-
var _a, _b
|
|
1529
|
+
var _a, _b;
|
|
1530
|
+
var _c;
|
|
1461
1531
|
const messages = [];
|
|
1462
1532
|
const pushBlock = (role, block) => {
|
|
1463
1533
|
const last = messages[messages.length - 1];
|
|
@@ -1487,13 +1557,13 @@ var AnthropicTransport = class _AnthropicTransport {
|
|
|
1487
1557
|
type: "tool_use",
|
|
1488
1558
|
id: tc.id,
|
|
1489
1559
|
name: tc.name,
|
|
1490
|
-
input: (
|
|
1560
|
+
input: (_c = tc.args) !== null && _c !== void 0 ? _c : {}
|
|
1491
1561
|
});
|
|
1492
1562
|
}
|
|
1493
1563
|
continue;
|
|
1494
1564
|
}
|
|
1495
1565
|
const role = msg.role === "user" ? "user" : "assistant";
|
|
1496
|
-
if (role === "user" && ((
|
|
1566
|
+
if (role === "user" && ((_b = msg.attachments) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
1497
1567
|
pushBlock(role, { type: "text", text: msg.content });
|
|
1498
1568
|
for (const att of msg.attachments) {
|
|
1499
1569
|
pushBlock(role, { type: "text", text: `[File: ${att.name}]
|
|
@@ -1611,7 +1681,7 @@ ${att.content}` });
|
|
|
1611
1681
|
headers: requestHeaders,
|
|
1612
1682
|
body,
|
|
1613
1683
|
credentials,
|
|
1614
|
-
vendorLabel:
|
|
1684
|
+
vendorLabel: VENDOR_LABELS.anthropic,
|
|
1615
1685
|
retryableStatuses: _AnthropicTransport.RETRYABLE_STATUSES,
|
|
1616
1686
|
timeout: this.timeout,
|
|
1617
1687
|
stallTimeout: this.stallTimeout,
|
|
@@ -1969,7 +2039,8 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
1969
2039
|
// ── ChatTransport (multi-turn chat) ────────────────────────────────────
|
|
1970
2040
|
sendChatMessage(history, userMessage, options) {
|
|
1971
2041
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1972
|
-
var _a
|
|
2042
|
+
var _a;
|
|
2043
|
+
var _b;
|
|
1973
2044
|
const contents = this.toGeminiContents(history, userMessage, options === null || options === void 0 ? void 0 : options.attachments);
|
|
1974
2045
|
if (options === null || options === void 0 ? void 0 : options.tailContext) {
|
|
1975
2046
|
contents.push({ role: "user", parts: [{ text: options.tailContext }] });
|
|
@@ -2033,7 +2104,8 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2033
2104
|
return totalCost;
|
|
2034
2105
|
}
|
|
2035
2106
|
toGeminiContents(history, userMessage, attachments) {
|
|
2036
|
-
var _a, _b
|
|
2107
|
+
var _a, _b;
|
|
2108
|
+
var _c;
|
|
2037
2109
|
const contents = [];
|
|
2038
2110
|
const toolCallNameById = /* @__PURE__ */ new Map();
|
|
2039
2111
|
for (const msg of history) {
|
|
@@ -2041,7 +2113,7 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2041
2113
|
continue;
|
|
2042
2114
|
const role = msg.role === "user" ? "user" : "model";
|
|
2043
2115
|
if (msg.toolResult) {
|
|
2044
|
-
const fnName = (
|
|
2116
|
+
const fnName = (_c = toolCallNameById.get(msg.toolResult.toolCallId)) !== null && _c !== void 0 ? _c : msg.toolResult.toolCallId;
|
|
2045
2117
|
contents.push({
|
|
2046
2118
|
role: "user",
|
|
2047
2119
|
parts: [
|
|
@@ -2053,7 +2125,7 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2053
2125
|
}
|
|
2054
2126
|
]
|
|
2055
2127
|
});
|
|
2056
|
-
} else if ((
|
|
2128
|
+
} else if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
|
|
2057
2129
|
for (const tc of msg.toolCalls) {
|
|
2058
2130
|
toolCallNameById.set(tc.id, tc.name);
|
|
2059
2131
|
}
|
|
@@ -2072,7 +2144,7 @@ var GeminiTransport = class _GeminiTransport {
|
|
|
2072
2144
|
return part;
|
|
2073
2145
|
})
|
|
2074
2146
|
});
|
|
2075
|
-
} else if (role === "user" && ((
|
|
2147
|
+
} else if (role === "user" && ((_b = msg.attachments) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
2076
2148
|
const msgAttachmentParts = msg.attachments.map((att) => ({
|
|
2077
2149
|
text: `[File: ${att.name}]
|
|
2078
2150
|
${att.content}`
|
|
@@ -2125,11 +2197,12 @@ ${att.content}`
|
|
|
2125
2197
|
};
|
|
2126
2198
|
}
|
|
2127
2199
|
fromGeminiResponse(response, offeredToolNames = /* @__PURE__ */ new Set()) {
|
|
2128
|
-
var _a, _b
|
|
2200
|
+
var _a, _b;
|
|
2201
|
+
var _c, _d;
|
|
2129
2202
|
const { inputTokens, outputTokens, thoughtsTokens, cacheReadTokens, cost } = this.usageFromGemini(response.usageMetadata);
|
|
2130
2203
|
const candidates = response === null || response === void 0 ? void 0 : response.candidates;
|
|
2131
2204
|
const firstCandidate = candidates === null || candidates === void 0 ? void 0 : candidates[0];
|
|
2132
|
-
const parts = (
|
|
2205
|
+
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 : [];
|
|
2133
2206
|
const toolCalls = [];
|
|
2134
2207
|
const thoughtParts = [];
|
|
2135
2208
|
const textParts = [];
|
|
@@ -2142,7 +2215,7 @@ ${att.content}`
|
|
|
2142
2215
|
toolCalls.push({
|
|
2143
2216
|
id: crypto.randomUUID(),
|
|
2144
2217
|
name: part.functionCall.name,
|
|
2145
|
-
args: (
|
|
2218
|
+
args: (_d = part.functionCall.args) !== null && _d !== void 0 ? _d : {},
|
|
2146
2219
|
providerMetadata: { [GEMINI_PROVIDER_KEY]: state }
|
|
2147
2220
|
});
|
|
2148
2221
|
} else if (part.thought && part.text) {
|
|
@@ -2180,7 +2253,7 @@ ${att.content}`
|
|
|
2180
2253
|
base.cacheReadTokens = cacheReadTokens;
|
|
2181
2254
|
if (cost != null)
|
|
2182
2255
|
base.cost = cost;
|
|
2183
|
-
const blockReason = (
|
|
2256
|
+
const blockReason = (_b = response.promptFeedback) === null || _b === void 0 ? void 0 : _b.blockReason;
|
|
2184
2257
|
if (finishReason != null || thoughtsTokens != null || blockReason != null) {
|
|
2185
2258
|
base.responseMeta = {
|
|
2186
2259
|
finishReason,
|
|
@@ -2226,9 +2299,10 @@ ${att.content}`
|
|
|
2226
2299
|
* without answering" signature.
|
|
2227
2300
|
*/
|
|
2228
2301
|
logAbnormalResponse(response, parsed) {
|
|
2229
|
-
var _a
|
|
2230
|
-
|
|
2231
|
-
const
|
|
2302
|
+
var _a;
|
|
2303
|
+
var _b, _c;
|
|
2304
|
+
const usage = (_b = response.usageMetadata) !== null && _b !== void 0 ? _b : {};
|
|
2305
|
+
const candidate = (_c = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0]) !== null && _c !== void 0 ? _c : {};
|
|
2232
2306
|
logger.warn("GeminiTransport: blank/non-STOP response", {
|
|
2233
2307
|
model: this.model,
|
|
2234
2308
|
finishReason: parsed.finishReason,
|
|
@@ -2279,7 +2353,7 @@ ${att.content}`
|
|
|
2279
2353
|
headers,
|
|
2280
2354
|
body: payload,
|
|
2281
2355
|
credentials,
|
|
2282
|
-
vendorLabel:
|
|
2356
|
+
vendorLabel: VENDOR_LABELS.gemini,
|
|
2283
2357
|
retryableStatuses: _GeminiTransport.RETRYABLE_STATUSES,
|
|
2284
2358
|
timeout: this.timeout,
|
|
2285
2359
|
stallTimeout: this.stallTimeout,
|
|
@@ -2332,7 +2406,8 @@ var EmptyAIProviderRegistry = class _EmptyAIProviderRegistry {
|
|
|
2332
2406
|
}
|
|
2333
2407
|
getStatus(name) {
|
|
2334
2408
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2335
|
-
var _a, _b
|
|
2409
|
+
var _a, _b;
|
|
2410
|
+
var _c;
|
|
2336
2411
|
if (name !== void 0 && name !== _EmptyAIProviderRegistry.DEFAULT_NAME)
|
|
2337
2412
|
return null;
|
|
2338
2413
|
return (_c = yield (_b = (_a = this.noopProvider).getStatus) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
|
|
@@ -2340,7 +2415,8 @@ var EmptyAIProviderRegistry = class _EmptyAIProviderRegistry {
|
|
|
2340
2415
|
}
|
|
2341
2416
|
listStatuses() {
|
|
2342
2417
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2343
|
-
var _a, _b
|
|
2418
|
+
var _a, _b;
|
|
2419
|
+
var _c;
|
|
2344
2420
|
const status = (_c = yield (_b = (_a = this.noopProvider).getStatus) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
|
|
2345
2421
|
return [{ name: _EmptyAIProviderRegistry.DEFAULT_NAME, isDefault: true, status }];
|
|
2346
2422
|
});
|
|
@@ -2384,7 +2460,8 @@ var MutableAIProviderRegistry = class _MutableAIProviderRegistry {
|
|
|
2384
2460
|
}
|
|
2385
2461
|
getStatus(name) {
|
|
2386
2462
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2387
|
-
var _a
|
|
2463
|
+
var _a;
|
|
2464
|
+
var _b;
|
|
2388
2465
|
const provider = name === void 0 ? this.default() : this.providers.get(name);
|
|
2389
2466
|
if (!provider)
|
|
2390
2467
|
return null;
|
|
@@ -2395,7 +2472,8 @@ var MutableAIProviderRegistry = class _MutableAIProviderRegistry {
|
|
|
2395
2472
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2396
2473
|
const entries = [...this.providers.entries()];
|
|
2397
2474
|
return Promise.all(entries.map((_a) => __awaiter(this, [_a], void 0, function* ([name, provider]) {
|
|
2398
|
-
var _b
|
|
2475
|
+
var _b;
|
|
2476
|
+
var _c;
|
|
2399
2477
|
return {
|
|
2400
2478
|
name,
|
|
2401
2479
|
isDefault: name === this._defaultName,
|
|
@@ -3029,6 +3107,19 @@ function accumulate(messages, into) {
|
|
|
3029
3107
|
var TOOL_FOLD_SYMBOL = Symbol("toolFold");
|
|
3030
3108
|
|
|
3031
3109
|
// src/components/chat-driver/chat-driver.ts
|
|
3110
|
+
var budgetDetailOf = (e) => {
|
|
3111
|
+
const vendor = vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor);
|
|
3112
|
+
if (e.budgetUsd == null && e.spentUsd == null && !vendor) return void 0;
|
|
3113
|
+
return {
|
|
3114
|
+
budgetUsd: e.budgetUsd,
|
|
3115
|
+
spentUsd: e.spentUsd,
|
|
3116
|
+
vendorLabel: e.vendorLabel,
|
|
3117
|
+
...vendor ? { vendor } : {},
|
|
3118
|
+
// Server-known truth the client cannot re-derive, so it rides all the way to
|
|
3119
|
+
// the banner rather than being re-guessed there from registry membership.
|
|
3120
|
+
...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
|
|
3121
|
+
};
|
|
3122
|
+
};
|
|
3032
3123
|
var DEFAULT_MAX_TOOL_ITERATIONS = 50;
|
|
3033
3124
|
var DEFAULT_MAX_FOLD_OPERATIONS = 5;
|
|
3034
3125
|
var DEFAULT_MAX_TURN_SNAPSHOTS = 400;
|
|
@@ -3211,6 +3302,22 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3211
3302
|
* picked up on the next turn.
|
|
3212
3303
|
*/
|
|
3213
3304
|
this.resolvedStatusCache = /* @__PURE__ */ new Map();
|
|
3305
|
+
/**
|
|
3306
|
+
* Set the moment a budget wall is observed anywhere in this turn — this
|
|
3307
|
+
* driver's own 402, or a sub-agent's (which surfaces here only as a
|
|
3308
|
+
* `'budget_exhausted'` tool result). Read at the top of the tool loop to end
|
|
3309
|
+
* the turn before issuing another model call that would hit the same wall.
|
|
3310
|
+
* Reset per turn alongside the other per-turn counters.
|
|
3311
|
+
*/
|
|
3312
|
+
this.budgetExhaustedThisTurn = false;
|
|
3313
|
+
/**
|
|
3314
|
+
* Whether this turn's budget wall came from a SUB-AGENT rather than this
|
|
3315
|
+
* driver's own request. Decides whether `lastResolvedProvider` is a valid
|
|
3316
|
+
* attribution fallback: for an own wall it is the refusing vendor, for a
|
|
3317
|
+
* child's wall it is the parent's vendor — the one known NOT to have refused.
|
|
3318
|
+
* Reset per turn alongside `budgetWallDetail`.
|
|
3319
|
+
*/
|
|
3320
|
+
this.budgetWallViaSubAgent = false;
|
|
3214
3321
|
const {
|
|
3215
3322
|
toolHandlers = {},
|
|
3216
3323
|
toolDefinitions = [],
|
|
@@ -3221,12 +3328,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3221
3328
|
condenseBatchCalls = 1,
|
|
3222
3329
|
maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
3223
3330
|
sessionKey = "",
|
|
3224
|
-
activityBus = NOOP_ACTIVITY_BUS
|
|
3331
|
+
activityBus = NOOP_ACTIVITY_BUS,
|
|
3332
|
+
budgetExhaustedMessage = DEFAULT_BUDGET_EXHAUSTED_MESSAGE
|
|
3225
3333
|
} = config;
|
|
3226
3334
|
this.maxToolIterations = maxToolIterations;
|
|
3227
3335
|
this.condenseBatchCalls = condenseBatchCalls;
|
|
3228
3336
|
this.sessionKey = sessionKey;
|
|
3229
3337
|
this.activityBus = activityBus;
|
|
3338
|
+
this.budgetExhaustedMessage = budgetExhaustedMessage;
|
|
3230
3339
|
if (typeof toolHandlers === "function") {
|
|
3231
3340
|
this.toolHandlersFactory = toolHandlers;
|
|
3232
3341
|
this.toolHandlers = {};
|
|
@@ -3333,8 +3442,56 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3333
3442
|
* (rather than set to `undefined`) so a happy-path result stays byte-identical to
|
|
3334
3443
|
* the historical `{ reason: 'done' }`.
|
|
3335
3444
|
*/
|
|
3336
|
-
turnDone(failureReason) {
|
|
3337
|
-
|
|
3445
|
+
turnDone(failureReason, budget) {
|
|
3446
|
+
if (!failureReason) return { reason: "done" };
|
|
3447
|
+
return budget ? { reason: "done", failureReason, budget } : { reason: "done", failureReason };
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* Terminal budget outcome for a wall hit **outside** the tool loop — today,
|
|
3451
|
+
* `OrchestratingDriver`'s classification phase, which calls the provider
|
|
3452
|
+
* directly and so never enters `runToolLoop`.
|
|
3453
|
+
*
|
|
3454
|
+
* Does **not** publish `tool-loop-end`: no `tool-loop-start` was published for
|
|
3455
|
+
* the classify phase, and an unbalanced end would break start/end pairing for
|
|
3456
|
+
* subscribers that rely on it. The driver **return value** is what reports this
|
|
3457
|
+
* case — see `FoundationAiAssistant`'s latch, which reads both seams for
|
|
3458
|
+
* exactly this reason.
|
|
3459
|
+
*
|
|
3460
|
+
* The non-sub-agent tail of the in-loop `BudgetExhaustedError` branch lives
|
|
3461
|
+
* here so there is one copy of the log line, the debug-log entry, the
|
|
3462
|
+
* transcript bubble and the result shape rather than two that can drift.
|
|
3463
|
+
*
|
|
3464
|
+
* @param pendingUserMessage - a user message that has NOT yet been appended,
|
|
3465
|
+
* appended first so the answer does not end up replying to nothing. Only the
|
|
3466
|
+
* classification seam passes it: `OrchestratingDriver` dispatches the user's
|
|
3467
|
+
* text as an optimistic `history-updated` detail and leaves the real append
|
|
3468
|
+
* to `chatDriver.sendMessage`, which never runs when `classify()` throws — so
|
|
3469
|
+
* the bubble below would re-dispatch a history the user's own message was
|
|
3470
|
+
* never in, and it would vanish from the transcript on the next render. The
|
|
3471
|
+
* in-loop caller has already appended it and passes nothing.
|
|
3472
|
+
*
|
|
3473
|
+
* @internal
|
|
3474
|
+
*/
|
|
3475
|
+
reportBudgetExhausted(e, pendingUserMessage) {
|
|
3476
|
+
if (pendingUserMessage) this.appendToHistory(pendingUserMessage);
|
|
3477
|
+
this.budgetExhaustedThisTurn = true;
|
|
3478
|
+
logger2.error("ChatDriver: AI budget exhausted", e);
|
|
3479
|
+
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
3480
|
+
agent: this.activeAgentName,
|
|
3481
|
+
provider: this.lastResolvedProviderName,
|
|
3482
|
+
// The registry ALIAS (e.g. 'high') is what `provider` records; the vendor
|
|
3483
|
+
// is the thing a per-vendor budget is actually scoped to, and it was known
|
|
3484
|
+
// at both ends and discarded in the middle until now. Taken from the
|
|
3485
|
+
// refusing transport's label first — this method also serves the
|
|
3486
|
+
// classification seam, where `lastResolvedProvider` is the PREVIOUS turn's
|
|
3487
|
+
// vendor (or nothing), because classify runs against the registry default.
|
|
3488
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
|
|
3489
|
+
budgetUsd: e.budgetUsd,
|
|
3490
|
+
spentUsd: e.spentUsd,
|
|
3491
|
+
isSubAgent: this.isSubAgent
|
|
3492
|
+
});
|
|
3493
|
+
this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
|
|
3494
|
+
return this.turnDone("budget-exhausted", budgetDetailOf(e));
|
|
3338
3495
|
}
|
|
3339
3496
|
/** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
|
|
3340
3497
|
static failureReasonOf(result) {
|
|
@@ -3344,10 +3501,28 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3344
3501
|
* Build the `tool-loop-end` event detail for a turn's result. A failure carries a
|
|
3345
3502
|
* `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
|
|
3346
3503
|
* kept byte-identical so subscribers see exactly what they always have.
|
|
3504
|
+
*
|
|
3505
|
+
* A budget failure additionally carries `vendor` — the concrete vendor
|
|
3506
|
+
* (`'anthropic'`/`'gemini'`) the walled turn resolved to, which the driver knows
|
|
3507
|
+
* and used to discard. Optional and additive: a subscriber reading only
|
|
3508
|
+
* `failureReason` is unaffected, a non-budget failure still emits the historical
|
|
3509
|
+
* `{ failureReason }` with no `vendor` key, and the value is a plain string so
|
|
3510
|
+
* the detail stays structured-cloneable for the cross-tab hop. It is the field a
|
|
3511
|
+
* per-vendor budget model needs and the one that would be awkward to retrofit.
|
|
3347
3512
|
*/
|
|
3348
|
-
|
|
3513
|
+
loopEndDetail(result) {
|
|
3349
3514
|
const failureReason = _ChatDriver.failureReasonOf(result);
|
|
3350
|
-
|
|
3515
|
+
if (!failureReason) return void 0;
|
|
3516
|
+
if (failureReason !== "budget-exhausted") {
|
|
3517
|
+
return { failureReason };
|
|
3518
|
+
}
|
|
3519
|
+
const budget = result.reason === "done" ? result.budget : void 0;
|
|
3520
|
+
const vendor = budget?.vendor ?? (this.budgetWallViaSubAgent ? void 0 : this.lastResolvedProvider);
|
|
3521
|
+
return {
|
|
3522
|
+
failureReason,
|
|
3523
|
+
...vendor ? { vendor } : {},
|
|
3524
|
+
...budget ? { budget } : {}
|
|
3525
|
+
};
|
|
3351
3526
|
}
|
|
3352
3527
|
/**
|
|
3353
3528
|
* Swap in a new agent's configuration. Called by OrchestratingDriver before
|
|
@@ -3533,9 +3708,9 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
3533
3708
|
* under a separate session key, so recording here would orphan the event off
|
|
3534
3709
|
* the user-visible debug-log timeline.)
|
|
3535
3710
|
*/
|
|
3536
|
-
failSubAgent(reason) {
|
|
3711
|
+
failSubAgent(reason, budget) {
|
|
3537
3712
|
if (!this.isSubAgent || this.subAgentFailure) return;
|
|
3538
|
-
this.subAgentFailure = { reason };
|
|
3713
|
+
this.subAgentFailure = budget ? { reason, budget } : { reason };
|
|
3539
3714
|
}
|
|
3540
3715
|
/**
|
|
3541
3716
|
* Returns true if `releaseAgent` was called during the most recent turn.
|
|
@@ -3985,6 +4160,9 @@ Output format (strict):
|
|
|
3985
4160
|
this.subAgentCompletion = void 0;
|
|
3986
4161
|
this.subAgentFailure = void 0;
|
|
3987
4162
|
this.agentReleaseRequested = false;
|
|
4163
|
+
this.budgetExhaustedThisTurn = false;
|
|
4164
|
+
this.budgetWallDetail = void 0;
|
|
4165
|
+
this.budgetWallViaSubAgent = false;
|
|
3988
4166
|
this.appendToHistory({ role: "user", content: userInput, attachments });
|
|
3989
4167
|
this.turnStartedAt = Date.now();
|
|
3990
4168
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
@@ -4019,7 +4197,7 @@ Output format (strict):
|
|
|
4019
4197
|
});
|
|
4020
4198
|
this.busy = false;
|
|
4021
4199
|
this.endTurn();
|
|
4022
|
-
this.activityBus.publish("tool-loop-end",
|
|
4200
|
+
this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
|
|
4023
4201
|
}
|
|
4024
4202
|
}
|
|
4025
4203
|
/**
|
|
@@ -4210,8 +4388,14 @@ Output format (strict):
|
|
|
4210
4388
|
recordMetaEvent(this.sessionKey, "subagent.completed", { agent: name });
|
|
4211
4389
|
return { outcome: { ok: true, result: completion.result }, trace };
|
|
4212
4390
|
}
|
|
4213
|
-
const
|
|
4391
|
+
const failure = child.getSubAgentFailure();
|
|
4392
|
+
const reason = failure?.reason ?? "max_iterations";
|
|
4214
4393
|
recordMetaEvent(this.sessionKey, "subagent.failed", { agent: name, reason });
|
|
4394
|
+
if (reason === "budget_exhausted") {
|
|
4395
|
+
this.budgetExhaustedThisTurn = true;
|
|
4396
|
+
this.budgetWallViaSubAgent = true;
|
|
4397
|
+
this.budgetWallDetail ??= failure?.budget;
|
|
4398
|
+
}
|
|
4215
4399
|
return { outcome: { ok: false, reason }, trace };
|
|
4216
4400
|
}
|
|
4217
4401
|
/**
|
|
@@ -4224,6 +4408,9 @@ Output format (strict):
|
|
|
4224
4408
|
this.beginTurn();
|
|
4225
4409
|
this.subAgentCompletion = void 0;
|
|
4226
4410
|
this.subAgentFailure = void 0;
|
|
4411
|
+
this.budgetExhaustedThisTurn = false;
|
|
4412
|
+
this.budgetWallDetail = void 0;
|
|
4413
|
+
this.budgetWallViaSubAgent = false;
|
|
4227
4414
|
this.turnStartedAt = Date.now();
|
|
4228
4415
|
recordMetaEvent(this.sessionKey, "turn.start", {
|
|
4229
4416
|
phase: "continueFromHistory",
|
|
@@ -4257,7 +4444,7 @@ Output format (strict):
|
|
|
4257
4444
|
});
|
|
4258
4445
|
this.busy = false;
|
|
4259
4446
|
this.endTurn();
|
|
4260
|
-
this.activityBus.publish("tool-loop-end",
|
|
4447
|
+
this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
|
|
4261
4448
|
}
|
|
4262
4449
|
}
|
|
4263
4450
|
// ---------------------------------------------------------------------------
|
|
@@ -4411,6 +4598,31 @@ Output format (strict):
|
|
|
4411
4598
|
if (this.turnController.signal.aborted) {
|
|
4412
4599
|
return this.completeAbortedTurn();
|
|
4413
4600
|
}
|
|
4601
|
+
if (this.budgetExhaustedThisTurn) {
|
|
4602
|
+
logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the AI budget wall");
|
|
4603
|
+
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
4604
|
+
agent: this.activeAgentName,
|
|
4605
|
+
provider: this.lastResolvedProviderName,
|
|
4606
|
+
// The CHILD's vendor when it knew one, and NOTHING otherwise. On this
|
|
4607
|
+
// path the wall is definitionally the child's, and on a mixed registry
|
|
4608
|
+
// `lastResolvedProvider` is this driver's own vendor — the one known
|
|
4609
|
+
// NOT to have refused. An unattributable child wall must degrade to
|
|
4610
|
+
// the vendor-agnostic block (which `latchBlockedFrom` handles
|
|
4611
|
+
// fail-safe), never to a vendor that is known to be wrong: naming the
|
|
4612
|
+
// parent's vendor here walled BOTH — the child's via its own
|
|
4613
|
+
// tool-loop-end, the parent's via this event — and derived `blocked`
|
|
4614
|
+
// over headroom that still existed.
|
|
4615
|
+
vendor: this.budgetWallDetail?.vendor,
|
|
4616
|
+
via: "sub-agent",
|
|
4617
|
+
isSubAgent: this.isSubAgent
|
|
4618
|
+
});
|
|
4619
|
+
if (this.isSubAgent) {
|
|
4620
|
+
this.failSubAgent("budget_exhausted", this.budgetWallDetail);
|
|
4621
|
+
} else {
|
|
4622
|
+
this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
|
|
4623
|
+
}
|
|
4624
|
+
return this.turnDone("budget-exhausted", this.budgetWallDetail);
|
|
4625
|
+
}
|
|
4414
4626
|
const promptCtx = {
|
|
4415
4627
|
agentName: this.activeAgentName ?? "",
|
|
4416
4628
|
history: this.history,
|
|
@@ -4582,6 +4794,25 @@ ${tailBody}
|
|
|
4582
4794
|
}
|
|
4583
4795
|
return this.turnDone("response-truncated");
|
|
4584
4796
|
}
|
|
4797
|
+
if (e instanceof BudgetExhaustedError) {
|
|
4798
|
+
this.budgetExhaustedThisTurn = true;
|
|
4799
|
+
this.budgetWallDetail = budgetDetailOf(e);
|
|
4800
|
+
this.budgetWallViaSubAgent = false;
|
|
4801
|
+
if (this.isSubAgent) {
|
|
4802
|
+
logger2.error("ChatDriver: AI budget exhausted", e);
|
|
4803
|
+
recordTurnError(this.sessionKey, "budget-exhausted", {
|
|
4804
|
+
agent: this.activeAgentName,
|
|
4805
|
+
provider: this.lastResolvedProviderName,
|
|
4806
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
|
|
4807
|
+
budgetUsd: e.budgetUsd,
|
|
4808
|
+
spentUsd: e.spentUsd,
|
|
4809
|
+
isSubAgent: true
|
|
4810
|
+
});
|
|
4811
|
+
this.failSubAgent("budget_exhausted", this.budgetWallDetail);
|
|
4812
|
+
return this.turnDone("budget-exhausted", this.budgetWallDetail);
|
|
4813
|
+
}
|
|
4814
|
+
return this.reportBudgetExhausted(e);
|
|
4815
|
+
}
|
|
4585
4816
|
if (e instanceof DOMException && e.name === "TimeoutError") {
|
|
4586
4817
|
logger2.error("ChatDriver: request timed out", e);
|
|
4587
4818
|
recordTurnError(this.sessionKey, "exception", {
|
|
@@ -5057,6 +5288,17 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5057
5288
|
* user stops. Reset at the start of each `sendMessage`.
|
|
5058
5289
|
*/
|
|
5059
5290
|
this.cancelled = false;
|
|
5291
|
+
/**
|
|
5292
|
+
* Whether the CURRENT turn's user message has reached the inner driver's
|
|
5293
|
+
* history. False through the pre-first-turn classify (where the only echo of
|
|
5294
|
+
* the message is an optimistic `history-updated` dispatch), true from the
|
|
5295
|
+
* moment `chatDriver.sendMessage` is entered — including through every later
|
|
5296
|
+
* handoff classify. Read by the budget-wall catch in `sendMessage` to decide
|
|
5297
|
+
* whether `reportBudgetExhausted` must append the message itself: appending
|
|
5298
|
+
* it when already appended duplicated it; not appending it when unappended
|
|
5299
|
+
* made it vanish. Reset at the top of each `runOrchestratedTurn`.
|
|
5300
|
+
*/
|
|
5301
|
+
this.userMessageAppended = false;
|
|
5060
5302
|
/**
|
|
5061
5303
|
* Sticky user pick from the picker (or the host's `setAgent` API). Only
|
|
5062
5304
|
* changes on explicit user action. Survives flow completion: when a stateful
|
|
@@ -5098,7 +5340,8 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5098
5340
|
maxFoldOperations: options.maxFoldOperations,
|
|
5099
5341
|
maxTurnSnapshots: options.maxTurnSnapshots,
|
|
5100
5342
|
sessionKey: this.sessionKey,
|
|
5101
|
-
activityBus: options.activityBus
|
|
5343
|
+
activityBus: options.activityBus,
|
|
5344
|
+
budgetExhaustedMessage: options.budgetExhaustedMessage
|
|
5102
5345
|
});
|
|
5103
5346
|
this.chatDriver.addEventListener("history-updated", (e) => {
|
|
5104
5347
|
this.dispatchEvent(new CustomEvent("history-updated", { detail: e.detail }));
|
|
@@ -5197,7 +5440,25 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5197
5440
|
return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
|
|
5198
5441
|
}
|
|
5199
5442
|
async sendMessage(input, attachments) {
|
|
5443
|
+
try {
|
|
5444
|
+
return await this.runOrchestratedTurn(input, attachments);
|
|
5445
|
+
} catch (e) {
|
|
5446
|
+
if (e instanceof BudgetExhaustedError) {
|
|
5447
|
+
return this.chatDriver.reportBudgetExhausted(
|
|
5448
|
+
e,
|
|
5449
|
+
this.userMessageAppended ? void 0 : {
|
|
5450
|
+
role: "user",
|
|
5451
|
+
content: input,
|
|
5452
|
+
...attachments ? { attachments } : {}
|
|
5453
|
+
}
|
|
5454
|
+
);
|
|
5455
|
+
}
|
|
5456
|
+
throw e;
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
async runOrchestratedTurn(input, attachments) {
|
|
5200
5460
|
this.cancelled = false;
|
|
5461
|
+
this.userMessageAppended = false;
|
|
5201
5462
|
const history = this.chatDriver.getHistory();
|
|
5202
5463
|
this.dispatchEvent(
|
|
5203
5464
|
new CustomEvent("history-updated", {
|
|
@@ -5211,6 +5472,7 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5211
5472
|
let handoffs = 0;
|
|
5212
5473
|
let handoffSummary = "";
|
|
5213
5474
|
let remainingTask = "";
|
|
5475
|
+
let lastResult;
|
|
5214
5476
|
while (true) {
|
|
5215
5477
|
if (this.cancelled) break;
|
|
5216
5478
|
await this.applyAgent(currentAgent);
|
|
@@ -5219,8 +5481,10 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5219
5481
|
const contextPrimer = handoffSummary ? [{ role: "user", content: `[Context from previous agent]: ${handoffSummary}` }] : [];
|
|
5220
5482
|
result = await this.chatDriver.continueFromHistory(contextPrimer);
|
|
5221
5483
|
} else {
|
|
5484
|
+
this.userMessageAppended = true;
|
|
5222
5485
|
result = await this.chatDriver.sendMessage(input, attachments);
|
|
5223
5486
|
}
|
|
5487
|
+
lastResult = result;
|
|
5224
5488
|
if (this.chatDriver.getAgentReleaseRequested()) {
|
|
5225
5489
|
await this.releaseActiveAgent();
|
|
5226
5490
|
break;
|
|
@@ -5242,7 +5506,7 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
5242
5506
|
this.dispatchEvent(new CustomEvent("orchestrating-start"));
|
|
5243
5507
|
currentAgent = await this.classify(remainingTask, updatedHistory);
|
|
5244
5508
|
}
|
|
5245
|
-
return { reason: "done" };
|
|
5509
|
+
return lastResult?.reason === "done" ? lastResult : { reason: "done" };
|
|
5246
5510
|
}
|
|
5247
5511
|
async continueFromHistory(transientPrimer) {
|
|
5248
5512
|
return this.chatDriver.continueFromHistory(transientPrimer);
|
|
@@ -5425,6 +5689,7 @@ ${recentMessages}` : ""}`;
|
|
|
5425
5689
|
}
|
|
5426
5690
|
}
|
|
5427
5691
|
};
|
|
5692
|
+
let routedNoMatch = false;
|
|
5428
5693
|
for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
|
|
5429
5694
|
try {
|
|
5430
5695
|
const options = {
|
|
@@ -5444,8 +5709,10 @@ ${recentMessages}` : ""}`;
|
|
|
5444
5709
|
if (index >= 0 && index < this.specialists.length) {
|
|
5445
5710
|
return this.specialists[index];
|
|
5446
5711
|
}
|
|
5712
|
+
routedNoMatch = true;
|
|
5447
5713
|
break;
|
|
5448
5714
|
} catch (e) {
|
|
5715
|
+
if (e instanceof BudgetExhaustedError) throw e;
|
|
5449
5716
|
logger2.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
|
|
5450
5717
|
if (attempt === this.classifierRetries) {
|
|
5451
5718
|
logger2.error("OrchestratingDriver: classifier failed after all retries, using fallback");
|
|
@@ -5453,10 +5720,12 @@ ${recentMessages}` : ""}`;
|
|
|
5453
5720
|
}
|
|
5454
5721
|
}
|
|
5455
5722
|
if (this.fallback) return this.fallback;
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5723
|
+
if (routedNoMatch) {
|
|
5724
|
+
const specialistNames = this.specialists.map((s) => s.name).join(", ");
|
|
5725
|
+
this.appendInlineMessage(
|
|
5726
|
+
`I'm not sure how to help with that. I can assist with: ${specialistNames}.`
|
|
5727
|
+
);
|
|
5728
|
+
}
|
|
5460
5729
|
return this.specialists[0];
|
|
5461
5730
|
}
|
|
5462
5731
|
/**
|