@genesislcap/ai-assistant 15.24.0 → 15.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +2 -2
- package/dist/ai-assistant.d.ts +22 -11
- package/dist/chat-driver.cjs +138 -120
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +138 -120
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +119 -61
- package/dist/dts/components/chat-driver/chat-driver.d.ts +22 -11
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/turn-failures.d.ts +96 -0
- package/dist/dts/components/chat-driver/turn-failures.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +92 -142
- package/dist/esm/components/chat-driver/turn-failures.js +84 -0
- package/package.json +16 -16
- package/docs/.gitattributes +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __awaiter, __rest } from "tslib";
|
|
2
|
-
import { BudgetExhaustedError,
|
|
2
|
+
import { BudgetExhaustedError, ContextOverflowError, 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';
|
|
@@ -10,6 +10,7 @@ import { applyHistoryCap, buildCompactionSummaryPrompt, planCompaction, normaliz
|
|
|
10
10
|
import { logger } from '../../utils/logger';
|
|
11
11
|
import { messageUsage, sumUsage } from '../../utils/sum-usage';
|
|
12
12
|
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
13
|
+
import { TURN_FAILURE_EXITS } from './turn-failures';
|
|
13
14
|
/**
|
|
14
15
|
* Lift the reportable facts off a {@link BudgetExhaustedError}, or `undefined`
|
|
15
16
|
* when it carries none.
|
|
@@ -472,6 +473,35 @@ export class ChatDriver extends EventTarget {
|
|
|
472
473
|
// a shape unchanged for a consumer that structurally compares it.
|
|
473
474
|
return Object.assign(Object.assign({ reason: 'done', failureReason }, (budget ? { budget } : {})), (providerRefused ? { providerRefused } : {}));
|
|
474
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* End the turn on a terminal failure: fail upward when this driver is a sub-agent, speak
|
|
478
|
+
* to the user when it is not, and hand the reason to the caller either way.
|
|
479
|
+
*
|
|
480
|
+
* Only that fork lives here. Each exit keeps its own `recordTurnError` call, because the
|
|
481
|
+
* telemetry payload is what legitimately differs between them — attempt counts, token
|
|
482
|
+
* figures, tool names — and folding it in would mean one optional field per condition.
|
|
483
|
+
*
|
|
484
|
+
* `overrides.copy` serves the two walls whose prose is composed per occurrence:
|
|
485
|
+
* `budgetExhaustedBubble` names the vendor and can offer another, `providerRefusedBubble`
|
|
486
|
+
* prefers a host-supplied message. Every other exit reads its copy from the table.
|
|
487
|
+
*
|
|
488
|
+
* One exit stays out: the `BudgetExhaustedError` catch. Not because its shape differs —
|
|
489
|
+
* its two telemetry payloads are field-for-field identical — but because its main-turn
|
|
490
|
+
* branch calls `reportBudgetExhausted`, which is public and also serves
|
|
491
|
+
* `OrchestratingDriver`'s classification phase, where no tool loop is running. That
|
|
492
|
+
* method has to survive either way, so routing the catch through here would relocate the
|
|
493
|
+
* duplication rather than remove it.
|
|
494
|
+
*/
|
|
495
|
+
endTurnWithFailure(exit, overrides) {
|
|
496
|
+
var _a;
|
|
497
|
+
if (this.isSubAgent) {
|
|
498
|
+
this.failSubAgent(exit.subReason, overrides === null || overrides === void 0 ? void 0 : overrides.budget, overrides === null || overrides === void 0 ? void 0 : overrides.providerRefused);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
this.appendDriverNotice((_a = overrides === null || overrides === void 0 ? void 0 : overrides.copy) !== null && _a !== void 0 ? _a : exit.copy);
|
|
502
|
+
}
|
|
503
|
+
return this.turnDone(exit.resultReason, overrides === null || overrides === void 0 ? void 0 : overrides.budget, overrides === null || overrides === void 0 ? void 0 : overrides.providerRefused);
|
|
504
|
+
}
|
|
475
505
|
/**
|
|
476
506
|
* Terminal budget outcome for a wall hit **outside** the tool loop — today,
|
|
477
507
|
* `OrchestratingDriver`'s classification phase, which calls the provider
|
|
@@ -512,7 +542,7 @@ export class ChatDriver extends EventTarget {
|
|
|
512
542
|
// said "switch" while the transcript bubble directly under it said only
|
|
513
543
|
// "contact your administrator" (GENC-1464 tester finding).
|
|
514
544
|
if ((wall === null || wall === void 0 ? void 0 : wall.otherVendorAvailable) !== true)
|
|
515
|
-
return
|
|
545
|
+
return TURN_FAILURE_EXITS.budgetExhausted.copy;
|
|
516
546
|
// The label is display copy, nothing more — attribution (which vendor gets
|
|
517
547
|
// WALLED) stays `budgetDetailOf`'s job. The parameter is the shared shape of
|
|
518
548
|
// both call sites: the thrown `BudgetExhaustedError` on the direct path, and
|
|
@@ -555,33 +585,7 @@ export class ChatDriver extends EventTarget {
|
|
|
555
585
|
*/
|
|
556
586
|
providerRefusedBubble() {
|
|
557
587
|
var _a;
|
|
558
|
-
return (_a = this.providerRefusedMessageOverride) !== null && _a !== void 0 ? _a :
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Terminal provider-refusal outcome. Mirrors `reportBudgetExhausted` so the two walls behave
|
|
562
|
-
* identically from the caller's side, while keeping their diagnostics distinct.
|
|
563
|
-
*
|
|
564
|
-
* `kind` reaches the debug log and the result but never the transcript: it is what lets an operator
|
|
565
|
-
* tell "top up the account" from "rotate the key", and with cause-free user copy this is the only
|
|
566
|
-
* place that distinction survives.
|
|
567
|
-
*/
|
|
568
|
-
reportProviderRefused(e) {
|
|
569
|
-
var _a;
|
|
570
|
-
const detail = providerRefusedDetailOf(e);
|
|
571
|
-
this.providerRefusedThisTurn = true;
|
|
572
|
-
this.providerRefusedDetail = detail;
|
|
573
|
-
logger.error('ChatDriver: provider refused the request', e);
|
|
574
|
-
recordTurnError(this.sessionKey, 'provider-refused', {
|
|
575
|
-
agent: this.activeAgentName,
|
|
576
|
-
provider: this.lastResolvedProviderName,
|
|
577
|
-
vendor: (_a = vendorTypeOfLabel(e.vendorLabel)) !== null && _a !== void 0 ? _a : this.lastResolvedProvider,
|
|
578
|
-
kind: e.kind,
|
|
579
|
-
upstreamStatus: e.upstreamStatus,
|
|
580
|
-
upstreamType: e.upstreamType,
|
|
581
|
-
isSubAgent: this.isSubAgent,
|
|
582
|
-
});
|
|
583
|
-
this.appendDriverNotice(this.providerRefusedBubble());
|
|
584
|
-
return this.turnDone('provider-refused', undefined, detail);
|
|
588
|
+
return (_a = this.providerRefusedMessageOverride) !== null && _a !== void 0 ? _a : TURN_FAILURE_EXITS.providerRefused.copy;
|
|
585
589
|
}
|
|
586
590
|
/** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
|
|
587
591
|
static failureReasonOf(result) {
|
|
@@ -2272,18 +2276,15 @@ export class ChatDriver extends EventTarget {
|
|
|
2272
2276
|
via: 'sub-agent',
|
|
2273
2277
|
isSubAgent: this.isSubAgent,
|
|
2274
2278
|
});
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
this.
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
this.appendDriverNotice(this.providerRefusedBubble());
|
|
2285
|
-
}
|
|
2286
|
-
return this.turnDone('provider-refused', undefined, this.providerRefusedDetail);
|
|
2279
|
+
// The detail is forwarded, not just the reason — this driver may itself be an INTERMEDIATE
|
|
2280
|
+
// sub-agent, and a refusal that started in a grandchild reaches its own parent only through
|
|
2281
|
+
// this call. Dropping it still stopped the top-level turn (the reason travels), but it
|
|
2282
|
+
// arrived with no vendor and no kind, which is the whole diagnostic this payload exists to
|
|
2283
|
+
// carry across hops. The adjacent budget path forwards `budgetWallDetail` for the same reason.
|
|
2284
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.providerRefused, {
|
|
2285
|
+
copy: this.providerRefusedBubble(),
|
|
2286
|
+
providerRefused: this.providerRefusedDetail,
|
|
2287
|
+
});
|
|
2287
2288
|
}
|
|
2288
2289
|
if (this.budgetExhaustedThisTurn) {
|
|
2289
2290
|
logger.error('ChatDriver: ending the turn — a sub-agent hit the AI budget wall');
|
|
@@ -2303,20 +2304,17 @@ export class ChatDriver extends EventTarget {
|
|
|
2303
2304
|
via: 'sub-agent',
|
|
2304
2305
|
isSubAgent: this.isSubAgent,
|
|
2305
2306
|
});
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
this.
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
// vendor rather than falling through to `lastResolvedProvider`, and the
|
|
2318
|
-
// host's latch walls the vendor that actually ran out.
|
|
2319
|
-
return this.turnDone('budget-exhausted', this.budgetWallDetail);
|
|
2307
|
+
// The copy is composed from the DETAIL, not an error object: on this path the
|
|
2308
|
+
// wall arrived as a sub-agent's failure payload — `budgetWallDetail`, first
|
|
2309
|
+
// attribution wins — and an unattributable child wall (detail undefined)
|
|
2310
|
+
// composes the neutral default. The same detail rides the result, so
|
|
2311
|
+
// `loopEndDetail` publishes the refusing vendor rather than falling through to
|
|
2312
|
+
// `lastResolvedProvider`, and the host's latch walls the vendor that actually
|
|
2313
|
+
// ran out.
|
|
2314
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.budgetExhausted, {
|
|
2315
|
+
copy: this.budgetExhaustedBubble(this.budgetWallDetail),
|
|
2316
|
+
budget: this.budgetWallDetail,
|
|
2317
|
+
});
|
|
2320
2318
|
}
|
|
2321
2319
|
const promptCtx = {
|
|
2322
2320
|
agentName: (_c = this.activeAgentName) !== null && _c !== void 0 ? _c : '',
|
|
@@ -2552,14 +2550,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2552
2550
|
iterations,
|
|
2553
2551
|
isSubAgent: this.isSubAgent,
|
|
2554
2552
|
});
|
|
2555
|
-
|
|
2556
|
-
this.failSubAgent('context_exhausted');
|
|
2557
|
-
}
|
|
2558
|
-
else {
|
|
2559
|
-
this.appendDriverNotice('I had to stop here — this conversation has filled the available context. ' +
|
|
2560
|
-
'Compact it to free up room, then ask me to continue.');
|
|
2561
|
-
}
|
|
2562
|
-
return this.turnDone('context-exhausted');
|
|
2553
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.contextGuard);
|
|
2563
2554
|
}
|
|
2564
2555
|
let response;
|
|
2565
2556
|
try {
|
|
@@ -2589,14 +2580,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2589
2580
|
finishMessage: e.finishMessage,
|
|
2590
2581
|
isSubAgent: this.isSubAgent,
|
|
2591
2582
|
});
|
|
2592
|
-
|
|
2593
|
-
// Bubble a typed failure to the parent instead of speaking to the user.
|
|
2594
|
-
this.failSubAgent('malformed_tool_call');
|
|
2595
|
-
}
|
|
2596
|
-
else {
|
|
2597
|
-
this.appendDriverNotice('While working on your request, I repeatedly called my tools incorrectly. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.');
|
|
2598
|
-
}
|
|
2599
|
-
return this.turnDone('malformed-function-call');
|
|
2583
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.malformedToolCall);
|
|
2600
2584
|
}
|
|
2601
2585
|
// The response was truncated at the provider's output-token cap while it
|
|
2602
2586
|
// still carried a tool call — its arguments are incomplete and unusable.
|
|
@@ -2615,14 +2599,7 @@ export class ChatDriver extends EventTarget {
|
|
|
2615
2599
|
tools: e.toolNames,
|
|
2616
2600
|
isSubAgent: this.isSubAgent,
|
|
2617
2601
|
});
|
|
2618
|
-
|
|
2619
|
-
// Bubble a typed failure to the parent instead of speaking to the user.
|
|
2620
|
-
this.failSubAgent('response_truncated');
|
|
2621
|
-
}
|
|
2622
|
-
else {
|
|
2623
|
-
this.appendDriverNotice('My response was cut off because a single step reached the model output limit. This usually means one step tried to produce too much at once — try breaking your request into smaller steps.');
|
|
2624
|
-
}
|
|
2625
|
-
return this.turnDone('response-truncated');
|
|
2602
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.responseTruncated);
|
|
2626
2603
|
}
|
|
2627
2604
|
// The AI-spend budget is gone and the proxy refused the request (HTTP
|
|
2628
2605
|
// 402). Terminal in the strongest sense available to us: unlike a
|
|
@@ -2662,35 +2639,35 @@ export class ChatDriver extends EventTarget {
|
|
|
2662
2639
|
via: 'provider',
|
|
2663
2640
|
isSubAgent: this.isSubAgent,
|
|
2664
2641
|
});
|
|
2665
|
-
|
|
2666
|
-
this.failSubAgent('context_exhausted');
|
|
2667
|
-
}
|
|
2668
|
-
else {
|
|
2669
|
-
this.appendDriverNotice(DEFAULT_CONTEXT_OVERFLOW_MESSAGE);
|
|
2670
|
-
}
|
|
2671
|
-
return this.turnDone('context-exhausted');
|
|
2642
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.contextOverflow);
|
|
2672
2643
|
}
|
|
2673
2644
|
if (e instanceof ProviderRefusedError) {
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
upstreamType: e.upstreamType,
|
|
2683
|
-
isSubAgent: true,
|
|
2684
|
-
});
|
|
2685
|
-
// The detail is BUBBLED, not merely flagged. Setting `providerRefusedThisTurn` on `this`
|
|
2686
|
-
// would be pointless here — `this` is the CHILD, and the parent that must stop is a
|
|
2687
|
-
// different driver instance. `invokeSubAgent` reads this payload off the child and sets its
|
|
2688
|
-
// own flag; see the branch there.
|
|
2689
|
-
const detail = providerRefusedDetailOf(e);
|
|
2690
|
-
this.failSubAgent('provider_refused', undefined, detail);
|
|
2691
|
-
return this.turnDone('provider-refused', undefined, detail);
|
|
2645
|
+
const detail = providerRefusedDetailOf(e);
|
|
2646
|
+
if (!this.isSubAgent) {
|
|
2647
|
+
// Flagged only on the driver whose own turn must stop. On the sub-agent path the
|
|
2648
|
+
// detail is BUBBLED instead: `this` is the CHILD, and the parent that must stop is a
|
|
2649
|
+
// different driver instance, so a flag here would help nobody — `invokeSubAgent`
|
|
2650
|
+
// reads the payload off the child and sets its own.
|
|
2651
|
+
this.providerRefusedThisTurn = true;
|
|
2652
|
+
this.providerRefusedDetail = detail;
|
|
2692
2653
|
}
|
|
2693
|
-
|
|
2654
|
+
logger.error('ChatDriver: provider refused the request', e);
|
|
2655
|
+
// `kind` reaches the debug log and the result but never the transcript: it is what lets
|
|
2656
|
+
// an operator tell "top up the account" from "rotate the key", and with cause-free user
|
|
2657
|
+
// copy this is the only place that distinction survives.
|
|
2658
|
+
recordTurnError(this.sessionKey, 'provider-refused', {
|
|
2659
|
+
agent: this.activeAgentName,
|
|
2660
|
+
provider: this.lastResolvedProviderName,
|
|
2661
|
+
vendor: (_f = vendorTypeOfLabel(e.vendorLabel)) !== null && _f !== void 0 ? _f : this.lastResolvedProvider,
|
|
2662
|
+
kind: e.kind,
|
|
2663
|
+
upstreamStatus: e.upstreamStatus,
|
|
2664
|
+
upstreamType: e.upstreamType,
|
|
2665
|
+
isSubAgent: this.isSubAgent,
|
|
2666
|
+
});
|
|
2667
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.providerRefused, {
|
|
2668
|
+
copy: this.providerRefusedBubble(),
|
|
2669
|
+
providerRefused: detail,
|
|
2670
|
+
});
|
|
2694
2671
|
}
|
|
2695
2672
|
if (e instanceof BudgetExhaustedError) {
|
|
2696
2673
|
// Flagged as well as returned: a sub-agent's wall reaches the PARENT
|
|
@@ -2737,16 +2714,10 @@ export class ChatDriver extends EventTarget {
|
|
|
2737
2714
|
message: e.message,
|
|
2738
2715
|
isSubAgent: this.isSubAgent,
|
|
2739
2716
|
});
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
else {
|
|
2745
|
-
this.appendDriverNotice('The request timed out. You can ask me to try again, or break this into a smaller step.');
|
|
2746
|
-
}
|
|
2747
|
-
// Recorded as `exception` above (there is no separate `timeout` member of
|
|
2748
|
-
// TurnFailureReason for the main turn); surface the same reason here.
|
|
2749
|
-
return this.turnDone('exception');
|
|
2717
|
+
// The caller sees `exception`, matching the telemetry above: there is no
|
|
2718
|
+
// `timeout` member of TurnFailureReason. A sub-agent gets the specific reason —
|
|
2719
|
+
// see the `timeout` entry in TURN_FAILURE_EXITS.
|
|
2720
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.timeout);
|
|
2750
2721
|
}
|
|
2751
2722
|
// The request was aborted: either a user cancel (turnController) or a
|
|
2752
2723
|
// driver dispose (lifecycleController, chained into the turn). A user
|
|
@@ -2827,23 +2798,15 @@ export class ChatDriver extends EventTarget {
|
|
|
2827
2798
|
iterations -= 1;
|
|
2828
2799
|
continue;
|
|
2829
2800
|
}
|
|
2830
|
-
const
|
|
2801
|
+
const exit = isRefusal ? TURN_FAILURE_EXITS.refusal : TURN_FAILURE_EXITS.emptyResponse;
|
|
2831
2802
|
logger.error(isRefusal
|
|
2832
2803
|
? 'ChatDriver: model refused the request (safety decline) — not retrying'
|
|
2833
2804
|
: 'ChatDriver: empty model response after all retries');
|
|
2834
|
-
recordTurnError(this.sessionKey,
|
|
2805
|
+
recordTurnError(this.sessionKey, exit.resultReason, Object.assign({ agent: this.activeAgentName, provider: this.lastResolvedProviderName, attempts: emptyResponseAttempts,
|
|
2835
2806
|
// See the retry above: a zero here says the agent had no tools, which is most of the
|
|
2836
2807
|
// explanation for a blank turn and is otherwise invisible.
|
|
2837
2808
|
toolCount: this.toolDefinitions.length, isSubAgent: this.isSubAgent }, response.responseMeta));
|
|
2838
|
-
|
|
2839
|
-
this.failSubAgent(isRefusal ? 'refusal' : 'empty_response');
|
|
2840
|
-
}
|
|
2841
|
-
else {
|
|
2842
|
-
this.appendDriverNotice(isRefusal
|
|
2843
|
-
? 'This request was declined. Try rephrasing it, or breaking it into smaller steps.'
|
|
2844
|
-
: 'While working on your request, I repeatedly generated a blank response. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.');
|
|
2845
|
-
}
|
|
2846
|
-
return this.turnDone(failureReason);
|
|
2809
|
+
return this.endTurnWithFailure(exit);
|
|
2847
2810
|
}
|
|
2848
2811
|
else {
|
|
2849
2812
|
// Split one model response into separate, individually-toggleable messages so each has its
|
|
@@ -3187,13 +3150,7 @@ export class ChatDriver extends EventTarget {
|
|
|
3187
3150
|
availableTools: Object.keys(this.toolHandlers),
|
|
3188
3151
|
isSubAgent: this.isSubAgent,
|
|
3189
3152
|
});
|
|
3190
|
-
|
|
3191
|
-
this.failSubAgent('unknown_tool_limit');
|
|
3192
|
-
}
|
|
3193
|
-
else {
|
|
3194
|
-
this.appendDriverNotice("I'm sorry, I repeatedly tried to use tools that aren't available to me, so I couldn't complete that. If a 'Download agent log' option appears in the Settings (cog) menu, you can download the log and share it with whoever set up this assistant to help fix the issue.");
|
|
3195
|
-
}
|
|
3196
|
-
return this.turnDone('unknown-tool-limit');
|
|
3153
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.unknownToolLimit);
|
|
3197
3154
|
}
|
|
3198
3155
|
const firstContinuation = systemCalls[0];
|
|
3199
3156
|
if (firstContinuation) {
|
|
@@ -3208,7 +3165,6 @@ export class ChatDriver extends EventTarget {
|
|
|
3208
3165
|
}
|
|
3209
3166
|
// The loop fell through: either it hit the iteration cap (a failure) or it
|
|
3210
3167
|
// broke on a clean final answer (success). Only the former carries a reason.
|
|
3211
|
-
let failureReason;
|
|
3212
3168
|
if (iterations >= this.maxToolIterations) {
|
|
3213
3169
|
logger.warn('ChatDriver: reached max tool iterations, stopping');
|
|
3214
3170
|
recordTurnError(this.sessionKey, 'max-iterations', {
|
|
@@ -3218,15 +3174,9 @@ export class ChatDriver extends EventTarget {
|
|
|
3218
3174
|
limit: this.maxToolIterations,
|
|
3219
3175
|
isSubAgent: this.isSubAgent,
|
|
3220
3176
|
});
|
|
3221
|
-
|
|
3222
|
-
this.failSubAgent('max_iterations');
|
|
3223
|
-
}
|
|
3224
|
-
else {
|
|
3225
|
-
this.appendDriverNotice("I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.");
|
|
3226
|
-
}
|
|
3227
|
-
failureReason = 'max-iterations';
|
|
3177
|
+
return this.endTurnWithFailure(TURN_FAILURE_EXITS.maxIterations);
|
|
3228
3178
|
}
|
|
3229
|
-
return this.turnDone(
|
|
3179
|
+
return this.turnDone();
|
|
3230
3180
|
});
|
|
3231
3181
|
}
|
|
3232
3182
|
/**
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_CONTEXT_OVERFLOW_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, } from '@genesislcap/foundation-ai';
|
|
2
|
+
/**
|
|
3
|
+
* The terminal outcomes of a tool loop.
|
|
4
|
+
*
|
|
5
|
+
* `contextGuard` and `contextOverflow` share a `subReason` because one failure is reached
|
|
6
|
+
* two ways the user experiences differently: the guard stops at the edge of the window with
|
|
7
|
+
* the transcript intact, so its copy can point at compaction, while an overflow means the
|
|
8
|
+
* provider has already rejected the request.
|
|
9
|
+
*
|
|
10
|
+
* Three entries take their copy from `@genesislcap/foundation-ai`, which owns the default
|
|
11
|
+
* message for every failure it also exports an error class for. The rest is for conditions
|
|
12
|
+
* this driver detects itself, which have no upstream home.
|
|
13
|
+
*/
|
|
14
|
+
export const TURN_FAILURE_EXITS = {
|
|
15
|
+
contextGuard: {
|
|
16
|
+
subReason: 'context_exhausted',
|
|
17
|
+
resultReason: 'context-exhausted',
|
|
18
|
+
copy: 'I had to stop here — this conversation has filled the available context. ' +
|
|
19
|
+
'Compact it to free up room, then ask me to continue.',
|
|
20
|
+
},
|
|
21
|
+
contextOverflow: {
|
|
22
|
+
subReason: 'context_exhausted',
|
|
23
|
+
resultReason: 'context-exhausted',
|
|
24
|
+
copy: DEFAULT_CONTEXT_OVERFLOW_MESSAGE,
|
|
25
|
+
},
|
|
26
|
+
malformedToolCall: {
|
|
27
|
+
subReason: 'malformed_tool_call',
|
|
28
|
+
// Not `malformed-tool-call`: the main-turn reason is named after the provider finish
|
|
29
|
+
// reason (`MALFORMED_FUNCTION_CALL`) that produces it, and is part of the published
|
|
30
|
+
// TurnFailureReason union, so the two names cannot be brought into line here.
|
|
31
|
+
resultReason: 'malformed-function-call',
|
|
32
|
+
copy: 'While working on your request, I repeatedly called my tools incorrectly. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',
|
|
33
|
+
},
|
|
34
|
+
responseTruncated: {
|
|
35
|
+
subReason: 'response_truncated',
|
|
36
|
+
resultReason: 'response-truncated',
|
|
37
|
+
copy: 'My response was cut off because a single step reached the model output limit. This usually means one step tried to produce too much at once — try breaking your request into smaller steps.',
|
|
38
|
+
},
|
|
39
|
+
timeout: {
|
|
40
|
+
subReason: 'timeout',
|
|
41
|
+
// There is no `timeout` member of TurnFailureReason, so the main turn reports the
|
|
42
|
+
// generic `exception`. A sub-agent gets the specific reason because its parent
|
|
43
|
+
// branches on it to decide whether a retry is worth attempting.
|
|
44
|
+
resultReason: 'exception',
|
|
45
|
+
copy: 'The request timed out. You can ask me to try again, or break this into a smaller step.',
|
|
46
|
+
},
|
|
47
|
+
unknownToolLimit: {
|
|
48
|
+
subReason: 'unknown_tool_limit',
|
|
49
|
+
resultReason: 'unknown-tool-limit',
|
|
50
|
+
copy: "I'm sorry, I repeatedly tried to use tools that aren't available to me, so I couldn't complete that. If a 'Download agent log' option appears in the Settings (cog) menu, you can download the log and share it with whoever set up this assistant to help fix the issue.",
|
|
51
|
+
},
|
|
52
|
+
maxIterations: {
|
|
53
|
+
subReason: 'max_iterations',
|
|
54
|
+
resultReason: 'max-iterations',
|
|
55
|
+
copy: "I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.",
|
|
56
|
+
},
|
|
57
|
+
refusal: {
|
|
58
|
+
subReason: 'refusal',
|
|
59
|
+
resultReason: 'refusal',
|
|
60
|
+
copy: 'This request was declined. Try rephrasing it, or breaking it into smaller steps.',
|
|
61
|
+
},
|
|
62
|
+
emptyResponse: {
|
|
63
|
+
subReason: 'empty_response',
|
|
64
|
+
resultReason: 'empty-response',
|
|
65
|
+
copy: 'While working on your request, I repeatedly generated a blank response. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',
|
|
66
|
+
},
|
|
67
|
+
providerRefused: {
|
|
68
|
+
subReason: 'provider_refused',
|
|
69
|
+
resultReason: 'provider-refused',
|
|
70
|
+
// Overridden per wall by `ChatDriver.providerRefusedBubble`, which prefers a
|
|
71
|
+
// host-supplied message; this is the fallback that method returns.
|
|
72
|
+
copy: DEFAULT_PROVIDER_REFUSED_MESSAGE,
|
|
73
|
+
},
|
|
74
|
+
budgetExhausted: {
|
|
75
|
+
subReason: 'budget_exhausted',
|
|
76
|
+
resultReason: 'budget-exhausted',
|
|
77
|
+
// Overridden per wall by `ChatDriver.budgetExhaustedBubble`, which upgrades this with
|
|
78
|
+
// "switch to another AI" when a second vendor is reachable.
|
|
79
|
+
copy: DEFAULT_BUDGET_EXHAUSTED_MESSAGE,
|
|
80
|
+
},
|
|
81
|
+
// `satisfies`, not an annotation: an annotation would widen `resultReason` to
|
|
82
|
+
// `TurnFailureReason` and cost the literal types that let `turn-failures.test.ts` pin the
|
|
83
|
+
// two deliberate divergences at compile time instead of at runtime.
|
|
84
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ai-assistant",
|
|
3
3
|
"description": "Genesis AI Assistant micro-frontend",
|
|
4
|
-
"version": "15.24.
|
|
4
|
+
"version": "15.24.2",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -74,26 +74,26 @@
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@genesislcap/foundation-testing": "15.24.
|
|
78
|
-
"@genesislcap/genx": "15.24.
|
|
79
|
-
"@genesislcap/rollup-builder": "15.24.
|
|
80
|
-
"@genesislcap/ts-builder": "15.24.
|
|
81
|
-
"@genesislcap/uvu-playwright-builder": "15.24.
|
|
82
|
-
"@genesislcap/vite-builder": "15.24.
|
|
83
|
-
"@genesislcap/webpack-builder": "15.24.
|
|
77
|
+
"@genesislcap/foundation-testing": "15.24.2",
|
|
78
|
+
"@genesislcap/genx": "15.24.2",
|
|
79
|
+
"@genesislcap/rollup-builder": "15.24.2",
|
|
80
|
+
"@genesislcap/ts-builder": "15.24.2",
|
|
81
|
+
"@genesislcap/uvu-playwright-builder": "15.24.2",
|
|
82
|
+
"@genesislcap/vite-builder": "15.24.2",
|
|
83
|
+
"@genesislcap/webpack-builder": "15.24.2",
|
|
84
84
|
"@types/dompurify": "^3.0.5",
|
|
85
85
|
"@types/marked": "^5.0.2",
|
|
86
86
|
"esbuild": "0.25.12"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
|
-
"@genesislcap/foundation-ai": "15.24.
|
|
90
|
-
"@genesislcap/foundation-logger": "15.24.
|
|
91
|
-
"@genesislcap/foundation-notifications": "15.24.
|
|
92
|
-
"@genesislcap/foundation-redux": "15.24.
|
|
93
|
-
"@genesislcap/foundation-ui": "15.24.
|
|
94
|
-
"@genesislcap/foundation-utils": "15.24.
|
|
95
|
-
"@genesislcap/rapid-design-system": "15.24.
|
|
96
|
-
"@genesislcap/web-core": "15.24.
|
|
89
|
+
"@genesislcap/foundation-ai": "15.24.2",
|
|
90
|
+
"@genesislcap/foundation-logger": "15.24.2",
|
|
91
|
+
"@genesislcap/foundation-notifications": "15.24.2",
|
|
92
|
+
"@genesislcap/foundation-redux": "15.24.2",
|
|
93
|
+
"@genesislcap/foundation-ui": "15.24.2",
|
|
94
|
+
"@genesislcap/foundation-utils": "15.24.2",
|
|
95
|
+
"@genesislcap/rapid-design-system": "15.24.2",
|
|
96
|
+
"@genesislcap/web-core": "15.24.2",
|
|
97
97
|
"dompurify": "^3.3.1",
|
|
98
98
|
"marked": "^17.0.3"
|
|
99
99
|
},
|
package/docs/.gitattributes
DELETED