@genesislcap/ai-assistant 15.19.6 → 15.20.0
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 +605 -72
- package/dist/ai-assistant.d.ts +404 -25
- package/dist/chat-driver.cjs +341 -28
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +341 -28
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +630 -20
- package/dist/dts/components/ai-driver/ai-driver.d.ts +33 -7
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +63 -2
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -3
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +44 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +187 -5
- 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.template.d.ts.map +1 -1
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/dts/utils/context-tokens.d.ts +156 -0
- package/dist/dts/utils/context-tokens.d.ts.map +1 -0
- package/dist/dts/utils/history-transform.d.ts +76 -14
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/resolve-context-budget.d.ts +98 -0
- package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +179 -34
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +12 -4
- package/dist/esm/main/main.js +391 -21
- package/dist/esm/main/main.styles.js +128 -0
- package/dist/esm/main/main.template.js +64 -29
- package/dist/esm/state/debug-event-log.js +1 -1
- package/dist/esm/utils/condense-history.js +1 -5
- package/dist/esm/utils/context-tokens.js +339 -0
- package/dist/esm/utils/history-transform.js +101 -19
- package/dist/esm/utils/resolve-context-budget.js +84 -0
- package/package.json +16 -16
- package/sandbox/README.md +93 -4
- package/sandbox/controls.ts +77 -10
- package/sandbox/fixtures.ts +163 -6
- package/sandbox/sandbox.css +54 -1
- package/sandbox/sandbox.ts +384 -7
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { __awaiter, __rest } from "tslib";
|
|
2
|
-
import { BudgetExhaustedError, DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, ProviderRefusedError, isObservableAIProviderRegistry, MalformedFunctionCallError, ResponseTruncatedError, vendorTypeOfLabel, } from '@genesislcap/foundation-ai';
|
|
2
|
+
import { BudgetExhaustedError, DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, ContextOverflowError, DEFAULT_CONTEXT_OVERFLOW_MESSAGE, ProviderRefusedError, isObservableAIProviderRegistry, MalformedFunctionCallError, ResponseTruncatedError, vendorTypeOfLabel, } from '@genesislcap/foundation-ai';
|
|
3
3
|
import { NOOP_ACTIVITY_BUS } from '../../channel/ai-activity-bus';
|
|
4
4
|
import { resolveChatProvider } from '../../config/validate-providers';
|
|
5
5
|
import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTurnError, recordTurnRetry, } from '../../state/debug-event-log';
|
|
6
6
|
import { createInteractionContext, } from '../../state/interaction-context';
|
|
7
7
|
import { applyCondensation } from '../../utils/condense-history';
|
|
8
|
-
import {
|
|
8
|
+
import { estimateRequestTokens } from '../../utils/context-tokens';
|
|
9
|
+
import { applyHistoryCap, buildCompactionSummaryPrompt, planCompaction, normalizeForProvider, } from '../../utils/history-transform';
|
|
9
10
|
import { logger } from '../../utils/logger';
|
|
10
11
|
import { messageUsage, sumUsage } from '../../utils/sum-usage';
|
|
11
12
|
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
@@ -115,16 +116,6 @@ const HARVESTED_SUBAGENT_EVENTS = new Set([
|
|
|
115
116
|
export const REQUEST_CONTINUATION_TOOL = 'request_continuation';
|
|
116
117
|
/** Paired in history for each `request_continuation` so tool_calls stay balanced for the provider. */
|
|
117
118
|
const HANDOFF_TOOL_RESULT_PLACEHOLDER = 'Handoff to another specialist — routing continues on the next turn.';
|
|
118
|
-
/**
|
|
119
|
-
* Plain TS class that drives a multi-turn chat conversation, including the tool-call loop.
|
|
120
|
-
* Owned by `FoundationAiAssistant` — created in `connectedCallback`, torn down in `disconnectedCallback`.
|
|
121
|
-
*
|
|
122
|
-
* Dispatches `'history-updated'` events on itself so the owning element can observe changes.
|
|
123
|
-
*
|
|
124
|
-
* @fires history-updated - Fired whenever the in-memory chat history changes (append, tool loop, interaction resolution, post-resolve external cost) with the full history snapshot. detail: `ReadonlyArray<ChatMessage>`
|
|
125
|
-
*
|
|
126
|
-
* @beta
|
|
127
|
-
*/
|
|
128
119
|
export class ChatDriver extends EventTarget {
|
|
129
120
|
constructor(providerRegistry, config = {}) {
|
|
130
121
|
super();
|
|
@@ -755,6 +746,12 @@ export class ChatDriver extends EventTarget {
|
|
|
755
746
|
const status = yield this.resolveStatusForProvider(resolvedName, provider);
|
|
756
747
|
this.lastResolvedModel = status.model;
|
|
757
748
|
this.lastResolvedProvider = status.provider;
|
|
749
|
+
// The window of the provider THIS call will go to. Read here rather than
|
|
750
|
+
// pushed down by the host, because the host cannot know it until the later
|
|
751
|
+
// `provider-changed` event — so a per-agent provider switch would otherwise
|
|
752
|
+
// have its first call guarded by the previous model's limit, refusing a valid
|
|
753
|
+
// turn on the way up and waving an oversized one through on the way down.
|
|
754
|
+
this.lastResolvedContextLimit = status.contextLimit;
|
|
758
755
|
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
759
756
|
this.lastDispatchedProviderName = resolvedName;
|
|
760
757
|
recordMetaEvent(this.sessionKey, 'provider.selected', {
|
|
@@ -788,7 +785,13 @@ export class ChatDriver extends EventTarget {
|
|
|
788
785
|
try {
|
|
789
786
|
const resolved = yield ((_a = provider.getStatus) === null || _a === void 0 ? void 0 : _a.call(provider));
|
|
790
787
|
if (resolved) {
|
|
791
|
-
status = {
|
|
788
|
+
status = {
|
|
789
|
+
model: resolved.model,
|
|
790
|
+
provider: resolved.provider,
|
|
791
|
+
// Carried so the mid-loop guard can measure against the window of the
|
|
792
|
+
// provider this call actually resolved to (GENC-1567).
|
|
793
|
+
contextLimit: resolved.contextLimit,
|
|
794
|
+
};
|
|
792
795
|
}
|
|
793
796
|
}
|
|
794
797
|
catch (_b) {
|
|
@@ -962,9 +965,62 @@ export class ChatDriver extends EventTarget {
|
|
|
962
965
|
return snapshot;
|
|
963
966
|
}
|
|
964
967
|
/**
|
|
965
|
-
*
|
|
966
|
-
*
|
|
968
|
+
* Set the mid-loop context guard. Its margin is deliberately far smaller than
|
|
969
|
+
* the reserve that blocks NEW turns: that reserve exists so an accepted turn can
|
|
970
|
+
* spend it, and a guard set at the same line would kill every turn that used
|
|
971
|
+
* the headroom it was given.
|
|
967
972
|
*/
|
|
973
|
+
setContextGuard(policy) {
|
|
974
|
+
this.contextGuard = policy && policy.marginTokens > 0 ? policy : undefined;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Whether issuing `requestHistory` would run the context window out.
|
|
978
|
+
*
|
|
979
|
+
* Inert unless a margin has been set AND the resolved provider reports a
|
|
980
|
+
* window — with no window there is nothing to measure against, and inventing
|
|
981
|
+
* one to refuse a request is worse than letting the provider answer.
|
|
982
|
+
*
|
|
983
|
+
* Stopping here is strictly better than letting the request go: the provider
|
|
984
|
+
* would reject an oversized prompt outright, leaving a transcript still too
|
|
985
|
+
* large to retry and no explanation the user can act on, whereas ending the
|
|
986
|
+
* turn keeps history intact so compaction is still available and the work so
|
|
987
|
+
* far is not lost.
|
|
988
|
+
*/
|
|
989
|
+
contextExhausted(requestHistory, pendingInput) {
|
|
990
|
+
var _a, _b;
|
|
991
|
+
const guard = this.contextGuard;
|
|
992
|
+
if (!guard)
|
|
993
|
+
return undefined;
|
|
994
|
+
// The resolved provider's own window, falling back to the host's assumption
|
|
995
|
+
// only when the provider reports none. Without the fallback a host that knows
|
|
996
|
+
// its window but uses a provider that does not report it got a UI gate and no
|
|
997
|
+
// running-turn guard — the fallback protecting the composer and nothing else.
|
|
998
|
+
const limit = (_a = this.lastResolvedContextLimit) !== null && _a !== void 0 ? _a : guard.fallbackLimit;
|
|
999
|
+
if (limit == null || limit <= 0)
|
|
1000
|
+
return undefined;
|
|
1001
|
+
const threshold = Math.max(0, limit - guard.marginTokens);
|
|
1002
|
+
// The first call of a turn passes the user's text and attachments SEPARATELY
|
|
1003
|
+
// (`sendMessage` appended them to history, and `baseHistory` drops that last
|
|
1004
|
+
// message precisely because they are supplied out of band). Measuring only
|
|
1005
|
+
// `requestHistory` therefore omitted them — so a large paste or a set of
|
|
1006
|
+
// images, the very payloads most likely to overrun a window, were invisible to
|
|
1007
|
+
// the guard that exists to catch them.
|
|
1008
|
+
const request = pendingInput && (pendingInput.content || ((_b = pendingInput.attachments) === null || _b === void 0 ? void 0 : _b.length))
|
|
1009
|
+
? [
|
|
1010
|
+
...requestHistory,
|
|
1011
|
+
{
|
|
1012
|
+
role: 'user',
|
|
1013
|
+
content: pendingInput.content,
|
|
1014
|
+
attachments: pendingInput.attachments,
|
|
1015
|
+
},
|
|
1016
|
+
]
|
|
1017
|
+
: requestHistory;
|
|
1018
|
+
const estimated = estimateRequestTokens(this.history, request);
|
|
1019
|
+
if (estimated < threshold)
|
|
1020
|
+
return undefined;
|
|
1021
|
+
logger.error(`ChatDriver: ending the turn — the request would fill the context window (~${estimated} of ${limit})`);
|
|
1022
|
+
return { estimated, threshold };
|
|
1023
|
+
}
|
|
968
1024
|
setProviderHistoryTransform(transform) {
|
|
969
1025
|
this.providerHistoryTransform = transform;
|
|
970
1026
|
}
|
|
@@ -1085,8 +1141,19 @@ export class ChatDriver extends EventTarget {
|
|
|
1085
1141
|
* turns exists behind a clean boundary. Uses the same `history` `compact()`
|
|
1086
1142
|
* acts on, so the UI's gate can't disagree with the action (GENC-1351 follow-up).
|
|
1087
1143
|
*/
|
|
1088
|
-
canCompact() {
|
|
1089
|
-
return
|
|
1144
|
+
canCompact(options) {
|
|
1145
|
+
return this.getCompactionPlan(options) != null;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* {@inheritDoc AiDriver.getCompactionPlan}
|
|
1149
|
+
*
|
|
1150
|
+
* Runs against the driver's own `history` — the exact list `compact()` acts on
|
|
1151
|
+
* — so a projection and the compaction it describes can never be computed from
|
|
1152
|
+
* different transcripts (the GENC-1351 follow-up that first made `canCompact`
|
|
1153
|
+
* read `history` rather than a mirrored copy).
|
|
1154
|
+
*/
|
|
1155
|
+
getCompactionPlan(options) {
|
|
1156
|
+
return planCompaction(this.history, options);
|
|
1090
1157
|
}
|
|
1091
1158
|
/**
|
|
1092
1159
|
* Destructively compact older turns into a single `compacted-summary` message
|
|
@@ -1097,9 +1164,9 @@ export class ChatDriver extends EventTarget {
|
|
|
1097
1164
|
* the new one. Returns the created summary message, or `null` when there is
|
|
1098
1165
|
* nothing worth compacting or the default provider cannot summarize.
|
|
1099
1166
|
*/
|
|
1100
|
-
compact() {
|
|
1167
|
+
compact(options) {
|
|
1101
1168
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1102
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1169
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1103
1170
|
if (this.busy)
|
|
1104
1171
|
return null;
|
|
1105
1172
|
const defaultProvider = this.providerRegistry.default();
|
|
@@ -1108,7 +1175,10 @@ export class ChatDriver extends EventTarget {
|
|
|
1108
1175
|
return null;
|
|
1109
1176
|
}
|
|
1110
1177
|
const history = this.history;
|
|
1111
|
-
|
|
1178
|
+
// Same plan the affordance was gated on — including its worth-it floor, so a
|
|
1179
|
+
// compaction that would reclaim too little is declined here as well as being
|
|
1180
|
+
// greyed out in the UI.
|
|
1181
|
+
const cut = (_a = planCompaction(history, options)) === null || _a === void 0 ? void 0 : _a.cut;
|
|
1112
1182
|
if (cut == null)
|
|
1113
1183
|
return null;
|
|
1114
1184
|
const toCompact = history.slice(0, cut);
|
|
@@ -1122,7 +1192,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1122
1192
|
const failMessage = "Sorry, I couldn't compact the conversation just now. Nothing was changed — please try again.";
|
|
1123
1193
|
let summaryText;
|
|
1124
1194
|
try {
|
|
1125
|
-
summaryText = (
|
|
1195
|
+
summaryText = (_c = (_b = (yield defaultProvider.prompt(userMessage, { systemPrompt }))) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : '';
|
|
1126
1196
|
}
|
|
1127
1197
|
catch (e) {
|
|
1128
1198
|
logger.warn(`ChatDriver: compaction summary failed: ${e instanceof Error ? e.message : e}`);
|
|
@@ -1141,7 +1211,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1141
1211
|
break;
|
|
1142
1212
|
}
|
|
1143
1213
|
}
|
|
1144
|
-
const status = yield ((
|
|
1214
|
+
const status = yield ((_d = defaultProvider.getStatus) === null || _d === void 0 ? void 0 : _d.call(defaultProvider));
|
|
1145
1215
|
const createdAt = new Date().toISOString();
|
|
1146
1216
|
const summaryMessage = {
|
|
1147
1217
|
role: 'compacted-summary',
|
|
@@ -1150,10 +1220,10 @@ export class ChatDriver extends EventTarget {
|
|
|
1150
1220
|
// for the oldest messages and sits at the head of history, so this keeps it
|
|
1151
1221
|
// sorted to the head in any timestamp-ordered view (the debug timeline) and
|
|
1152
1222
|
// history time-monotonic. Its true creation time is in compaction.createdAt.
|
|
1153
|
-
timestamp: (
|
|
1223
|
+
timestamp: (_f = (_e = toCompact[0]) === null || _e === void 0 ? void 0 : _e.timestamp) !== null && _f !== void 0 ? _f : createdAt,
|
|
1154
1224
|
compaction: {
|
|
1155
1225
|
compactedCount: toCompact.length,
|
|
1156
|
-
coveredThroughTimestamp: (
|
|
1226
|
+
coveredThroughTimestamp: (_g = toCompact[toCompact.length - 1]) === null || _g === void 0 ? void 0 : _g.timestamp,
|
|
1157
1227
|
tokensBefore,
|
|
1158
1228
|
model: status === null || status === void 0 ? void 0 : status.model,
|
|
1159
1229
|
createdAt,
|
|
@@ -1701,6 +1771,11 @@ export class ChatDriver extends EventTarget {
|
|
|
1701
1771
|
// Mark before the first turn so the child forces tool use and reports a
|
|
1702
1772
|
// typed failure (rather than user-facing text) if it never completes.
|
|
1703
1773
|
child.markAsSubAgent();
|
|
1774
|
+
// The guard is policy, and a sub-agent runs the same tool loop against the
|
|
1775
|
+
// same window. Without this every child was inert — and the
|
|
1776
|
+
// `context_exhausted` sub-agent failure this feature added was unreachable by
|
|
1777
|
+
// any normal path.
|
|
1778
|
+
child.setContextGuard(this.contextGuard);
|
|
1704
1779
|
// Propagate disposal: if this (parent) driver is torn down while the
|
|
1705
1780
|
// sub-agent is mid-flight, dispose the child too so its in-flight request
|
|
1706
1781
|
// aborts. Detached in the `finally` below once the sub-agent completes.
|
|
@@ -2085,7 +2160,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2085
2160
|
// oxlint-disable-next-line complexity
|
|
2086
2161
|
runToolLoop(userInput, attachments, transientPrimer) {
|
|
2087
2162
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2088
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
2163
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
2089
2164
|
if (!this.systemPrompt) {
|
|
2090
2165
|
logger.warn('ChatDriver: no systemPrompt set. The assistant will have no instructions — provide a systemPrompt via agents config or the foundation-ai-assistant property.');
|
|
2091
2166
|
}
|
|
@@ -2399,6 +2474,43 @@ export class ChatDriver extends EventTarget {
|
|
|
2399
2474
|
turnSnapshot.provider = this.lastResolvedProvider;
|
|
2400
2475
|
if (this.lastResolvedModel !== undefined)
|
|
2401
2476
|
turnSnapshot.model = this.lastResolvedModel;
|
|
2477
|
+
// The context wall (GENC-1567) — checked HERE, at the last possible moment,
|
|
2478
|
+
// rather than at the top of the iteration where it started life.
|
|
2479
|
+
//
|
|
2480
|
+
// Two things are only knowable at this point, and getting either wrong
|
|
2481
|
+
// ended valid turns. `historyForCall` is what will actually be sent, after
|
|
2482
|
+
// `condenseWhen` has stubbed spent tool payloads and per-agent masking has
|
|
2483
|
+
// blanked another agent's — stored history deliberately keeps them in full,
|
|
2484
|
+
// so measuring it refused turns whose real request was a fraction of the
|
|
2485
|
+
// size. And `lastResolvedContextLimit` is the window of the provider THIS
|
|
2486
|
+
// call resolved to, which on an agent switch is not the one the host knew
|
|
2487
|
+
// about when it last pushed a threshold down.
|
|
2488
|
+
const contextStop = this.contextExhausted(historyForCall, {
|
|
2489
|
+
content: userInputForCall,
|
|
2490
|
+
attachments: attachmentsForCall,
|
|
2491
|
+
});
|
|
2492
|
+
if (contextStop) {
|
|
2493
|
+
recordTurnError(this.sessionKey, 'context-exhausted', {
|
|
2494
|
+
agent: this.activeAgentName,
|
|
2495
|
+
provider: this.lastResolvedProviderName,
|
|
2496
|
+
contextTokens: contextStop.estimated,
|
|
2497
|
+
guard: contextStop.threshold,
|
|
2498
|
+
limit: this.lastResolvedContextLimit,
|
|
2499
|
+
iterations,
|
|
2500
|
+
isSubAgent: this.isSubAgent,
|
|
2501
|
+
});
|
|
2502
|
+
if (this.isSubAgent) {
|
|
2503
|
+
this.failSubAgent('context_exhausted');
|
|
2504
|
+
}
|
|
2505
|
+
else {
|
|
2506
|
+
this.appendToHistory({
|
|
2507
|
+
role: 'assistant',
|
|
2508
|
+
content: 'I had to stop here — this conversation has filled the available context. ' +
|
|
2509
|
+
'Compact it to free up room, then ask me to continue.',
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
return this.turnDone('context-exhausted');
|
|
2513
|
+
}
|
|
2402
2514
|
let response;
|
|
2403
2515
|
try {
|
|
2404
2516
|
// oxlint-disable-next-line no-await-in-loop
|
|
@@ -2484,13 +2596,46 @@ export class ChatDriver extends EventTarget {
|
|
|
2484
2596
|
// error is an untyped transport failure, gets re-issued MAX_SETUP_TRANSPORT_RETRIES times
|
|
2485
2597
|
// against a wall that cannot move, and then surfaces as "something went wrong on my end" —
|
|
2486
2598
|
// wrong twice over, since nothing went wrong on our end and trying again will not help.
|
|
2599
|
+
// The REQUEST wall (GENC-1567) — the prompt outgrew the window. Placed
|
|
2600
|
+
// beside the account walls and before the transient-retry step for the
|
|
2601
|
+
// same reason: re-issuing an oversized prompt cannot make it fit, and
|
|
2602
|
+
// without this it surfaces as "something went wrong on my end" when in
|
|
2603
|
+
// fact the user has a clear, effective remedy.
|
|
2604
|
+
//
|
|
2605
|
+
// A backstop rather than the main defence — the mid-loop guard should
|
|
2606
|
+
// normally stop the turn before a request this large is built. It is
|
|
2607
|
+
// reached when the provider reports no context window (nothing for the
|
|
2608
|
+
// guard to measure against), when the estimate ran under, or when a
|
|
2609
|
+
// single request was already too large.
|
|
2610
|
+
if (e instanceof ContextOverflowError) {
|
|
2611
|
+
logger.error('ChatDriver: the prompt exceeded the model context window', e);
|
|
2612
|
+
recordTurnError(this.sessionKey, 'context-exhausted', {
|
|
2613
|
+
agent: this.activeAgentName,
|
|
2614
|
+
provider: this.lastResolvedProviderName,
|
|
2615
|
+
vendor: (_e = vendorTypeOfLabel(e.vendorLabel)) !== null && _e !== void 0 ? _e : this.lastResolvedProvider,
|
|
2616
|
+
promptTokens: e.promptTokens,
|
|
2617
|
+
limitTokens: e.limitTokens,
|
|
2618
|
+
via: 'provider',
|
|
2619
|
+
isSubAgent: this.isSubAgent,
|
|
2620
|
+
});
|
|
2621
|
+
if (this.isSubAgent) {
|
|
2622
|
+
this.failSubAgent('context_exhausted');
|
|
2623
|
+
}
|
|
2624
|
+
else {
|
|
2625
|
+
this.appendToHistory({
|
|
2626
|
+
role: 'assistant',
|
|
2627
|
+
content: DEFAULT_CONTEXT_OVERFLOW_MESSAGE,
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
return this.turnDone('context-exhausted');
|
|
2631
|
+
}
|
|
2487
2632
|
if (e instanceof ProviderRefusedError) {
|
|
2488
2633
|
if (this.isSubAgent) {
|
|
2489
2634
|
logger.error('ChatDriver: provider refused the request', e);
|
|
2490
2635
|
recordTurnError(this.sessionKey, 'provider-refused', {
|
|
2491
2636
|
agent: this.activeAgentName,
|
|
2492
2637
|
provider: this.lastResolvedProviderName,
|
|
2493
|
-
vendor: (
|
|
2638
|
+
vendor: (_f = vendorTypeOfLabel(e.vendorLabel)) !== null && _f !== void 0 ? _f : this.lastResolvedProvider,
|
|
2494
2639
|
kind: e.kind,
|
|
2495
2640
|
upstreamStatus: e.upstreamStatus,
|
|
2496
2641
|
upstreamType: e.upstreamType,
|
|
@@ -2524,7 +2669,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2524
2669
|
recordTurnError(this.sessionKey, 'budget-exhausted', {
|
|
2525
2670
|
agent: this.activeAgentName,
|
|
2526
2671
|
provider: this.lastResolvedProviderName,
|
|
2527
|
-
vendor: (
|
|
2672
|
+
vendor: (_h = (_g = vendorTypeOfLabel(e.vendorLabel)) !== null && _g !== void 0 ? _g : vendorTypeOfLabel(e.serverVendor)) !== null && _h !== void 0 ? _h : this.lastResolvedProvider,
|
|
2528
2673
|
budgetUsd: e.budgetUsd,
|
|
2529
2674
|
spentUsd: e.spentUsd,
|
|
2530
2675
|
isSubAgent: true,
|
|
@@ -2622,15 +2767,15 @@ export class ChatDriver extends EventTarget {
|
|
|
2622
2767
|
// fallback chain answered on a different model than the one we asked for.
|
|
2623
2768
|
if (response.model !== undefined)
|
|
2624
2769
|
turnSnapshot.model = response.model;
|
|
2625
|
-
const isThinkingStep = response.content && ((
|
|
2626
|
-
const isEmptyResponse = !((
|
|
2770
|
+
const isThinkingStep = response.content && ((_j = response.toolCalls) === null || _j === void 0 ? void 0 : _j.length);
|
|
2771
|
+
const isEmptyResponse = !((_k = response.content) === null || _k === void 0 ? void 0 : _k.trim()) && !((_l = response.toolCalls) === null || _l === void 0 ? void 0 : _l.length);
|
|
2627
2772
|
// A pre-output refusal (safety-classifier decline, e.g. Fable 5 `stop_reason: 'refusal'`)
|
|
2628
2773
|
// comes back with empty content, so it looks like a blank response — but it is deterministic:
|
|
2629
2774
|
// retrying re-sends the identical request and refuses again, burning up to
|
|
2630
2775
|
// MAX_EMPTY_RESPONSE_RETRIES turns on the most expensive models for the same outcome, ending
|
|
2631
2776
|
// in the misleading "blank response" message. Treat it as a terminal, non-retried failure with
|
|
2632
2777
|
// its own reason and message. (GENC-1461)
|
|
2633
|
-
const isRefusal = ((
|
|
2778
|
+
const isRefusal = ((_m = response.responseMeta) === null || _m === void 0 ? void 0 : _m.finishReason) === 'refusal';
|
|
2634
2779
|
if (isEmptyResponse) {
|
|
2635
2780
|
emptyResponseAttempts += 1;
|
|
2636
2781
|
if (!isRefusal && emptyResponseAttempts < MAX_EMPTY_RESPONSE_RETRIES) {
|
|
@@ -2704,7 +2849,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2704
2849
|
emptyResponseAttempts = 0;
|
|
2705
2850
|
malformedAttempts = 0;
|
|
2706
2851
|
setupTransportAttempts = 0;
|
|
2707
|
-
if (!((
|
|
2852
|
+
if (!((_o = response.toolCalls) === null || _o === void 0 ? void 0 : _o.length)) {
|
|
2708
2853
|
break;
|
|
2709
2854
|
}
|
|
2710
2855
|
const [toolCalls, systemCalls] = response.toolCalls.reduce((acc, tc) => {
|
|
@@ -2934,7 +3079,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2934
3079
|
// The response was appended before execution — find it and annotate.
|
|
2935
3080
|
let tcMsgIdx = -1;
|
|
2936
3081
|
for (let i = this.history.length - 1; i >= 0; i -= 1) {
|
|
2937
|
-
if (this.history[i].role === 'assistant' && ((
|
|
3082
|
+
if (this.history[i].role === 'assistant' && ((_p = this.history[i].toolCalls) === null || _p === void 0 ? void 0 : _p.length)) {
|
|
2938
3083
|
tcMsgIdx = i;
|
|
2939
3084
|
break;
|
|
2940
3085
|
}
|
|
@@ -2972,7 +3117,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2972
3117
|
const unknownTools = [
|
|
2973
3118
|
...new Set([
|
|
2974
3119
|
...this.recentUnknownToolNames,
|
|
2975
|
-
...((
|
|
3120
|
+
...((_q = response.toolCalls) !== null && _q !== void 0 ? _q : [])
|
|
2976
3121
|
.filter((tc) => unknownToolIds.has(tc.id))
|
|
2977
3122
|
.map((tc) => tc.name),
|
|
2978
3123
|
]),
|
|
@@ -2984,7 +3129,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2984
3129
|
const staleTools = [
|
|
2985
3130
|
...new Set([
|
|
2986
3131
|
...this.recentStaleToolNames,
|
|
2987
|
-
...((
|
|
3132
|
+
...((_r = response.toolCalls) !== null && _r !== void 0 ? _r : [])
|
|
2988
3133
|
.filter((tc) => staleToolIds.has(tc.id))
|
|
2989
3134
|
.map((tc) => tc.name),
|
|
2990
3135
|
]),
|
|
@@ -203,13 +203,21 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
203
203
|
getRawHistory() {
|
|
204
204
|
return this.chatDriver.getHistory();
|
|
205
205
|
}
|
|
206
|
+
/** {@inheritDoc AiDriver.setContextGuard} */
|
|
207
|
+
setContextGuard(policy) {
|
|
208
|
+
this.chatDriver.setContextGuard(policy);
|
|
209
|
+
}
|
|
206
210
|
/** {@inheritDoc AiDriver.compact} */
|
|
207
|
-
compact() {
|
|
208
|
-
return this.chatDriver.compact();
|
|
211
|
+
compact(options) {
|
|
212
|
+
return this.chatDriver.compact(options);
|
|
209
213
|
}
|
|
210
214
|
/** {@inheritDoc AiDriver.canCompact} */
|
|
211
|
-
canCompact() {
|
|
212
|
-
return this.chatDriver.canCompact();
|
|
215
|
+
canCompact(options) {
|
|
216
|
+
return this.chatDriver.canCompact(options);
|
|
217
|
+
}
|
|
218
|
+
/** {@inheritDoc AiDriver.getCompactionPlan} */
|
|
219
|
+
getCompactionPlan(options) {
|
|
220
|
+
return this.chatDriver.getCompactionPlan(options);
|
|
213
221
|
}
|
|
214
222
|
/** Delegates to the inner {@link ChatDriver} — turns are captured there. */
|
|
215
223
|
getTurnSnapshots() {
|