@genesislcap/ai-assistant 14.444.1 → 14.445.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 +312 -41
- package/dist/ai-assistant.d.ts +105 -8
- package/dist/dts/components/ai-driver/ai-driver.d.ts +7 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +37 -3
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +5 -3
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +31 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +9 -0
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/config/validate-providers.d.ts +25 -0
- package/dist/dts/config/validate-providers.d.ts.map +1 -0
- package/dist/dts/config/validate-providers.test.d.ts +2 -0
- package/dist/dts/config/validate-providers.test.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts +17 -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/state/ai-assistant-slice.d.ts +14 -1
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +2 -0
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/sum-costs.d.ts +13 -0
- package/dist/dts/utils/sum-costs.d.ts.map +1 -0
- package/dist/dts/utils/sum-costs.test.d.ts +2 -0
- package/dist/dts/utils/sum-costs.test.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +93 -15
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +23 -4
- package/dist/esm/config/define-stateful-agent.js +12 -0
- package/dist/esm/config/validate-providers.js +47 -0
- package/dist/esm/config/validate-providers.test.js +100 -0
- package/dist/esm/main/main.js +76 -21
- package/dist/esm/main/main.styles.js +52 -0
- package/dist/esm/main/main.template.js +36 -1
- package/dist/esm/state/ai-assistant-slice.js +8 -0
- package/dist/esm/utils/sum-costs.js +23 -0
- package/dist/esm/utils/sum-costs.test.js +88 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/migration-GENC-1262.md +219 -0
- package/package.json +16 -16
- package/src/components/ai-driver/ai-driver.ts +8 -0
- package/src/components/chat-driver/chat-driver.ts +107 -14
- package/src/components/orchestrating-driver/orchestrating-driver.ts +29 -4
- package/src/config/config.ts +32 -0
- package/src/config/define-stateful-agent.ts +28 -0
- package/src/config/validate-providers.test.ts +148 -0
- package/src/config/validate-providers.ts +58 -0
- package/src/main/main.styles.ts +52 -0
- package/src/main/main.template.ts +50 -2
- package/src/main/main.ts +69 -14
- package/src/state/ai-assistant-slice.ts +24 -1
- package/src/utils/sum-costs.test.ts +108 -0
- package/src/utils/sum-costs.ts +22 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChatInputDuringExecutionMode, ChatMessage } from '@genesislcap/foundation-ai';
|
|
1
|
+
import type { AIProviderRegistryStatusEntry, ChatInputDuringExecutionMode, ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
2
|
import type { PayloadAction } from '@genesislcap/foundation-redux';
|
|
3
3
|
import type { AgentConfig } from '../config/config';
|
|
4
4
|
import type { AiAssistantAnimation, AiAssistantState, SuggestionsState } from '../main/main.types';
|
|
@@ -36,6 +36,17 @@ export interface AiAssistantSessionState {
|
|
|
36
36
|
sessionCostUsd: number;
|
|
37
37
|
/** Active model id (e.g. `claude-sonnet-4-6`), resolved on connect. */
|
|
38
38
|
activeModel: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Name of the AI provider used on the most recent turn — drives "current"
|
|
41
|
+
* marker in the settings panel. `undefined` until the first turn runs.
|
|
42
|
+
*/
|
|
43
|
+
activeProviderName: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Snapshot of every registered provider's status, in registration order.
|
|
46
|
+
* Populated on connect (or on settings-open if not yet loaded) and reused
|
|
47
|
+
* for the lifetime of the session — provider registration is static.
|
|
48
|
+
*/
|
|
49
|
+
providerStatuses: AIProviderRegistryStatusEntry[];
|
|
39
50
|
activeAgent: Omit<AgentConfig, 'toolHandlers'> | undefined;
|
|
40
51
|
/**
|
|
41
52
|
* Name of the agent the user has pinned via the picker. `null` means the
|
|
@@ -87,6 +98,8 @@ export declare const aiAssistantSlice: import("@reduxjs/toolkit").Slice<AiAssist
|
|
|
87
98
|
setContextLimit(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<number | undefined>): void;
|
|
88
99
|
setSessionCostUsd(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<number>): void;
|
|
89
100
|
setActiveModel(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | undefined>): void;
|
|
101
|
+
setActiveProviderName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | undefined>): void;
|
|
102
|
+
setProviderStatuses(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<AIProviderRegistryStatusEntry[]>): void;
|
|
90
103
|
setActiveAgent(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<Omit<AgentConfig, "toolHandlers"> | undefined>): void;
|
|
91
104
|
setPinnedAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
|
|
92
105
|
setFlowOwnerAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-assistant-slice.d.ts","sourceRoot":"","sources":["../../../src/state/ai-assistant-slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ai-assistant-slice.d.ts","sourceRoot":"","sources":["../../../src/state/ai-assistant-slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,4BAA4B,EAC5B,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEnG;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,4BAA4B,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,oBAAoB,EAAE,CAAC;IAC1C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC;;;OAGG;IACH,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,gBAAgB,EAAE,6BAA6B,EAAE,CAAC;IAClD,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;IAC3D;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,mBAAmB,EAAE,uBAsBjC,CAAC;AAEF,eAAO,MAAM,gBAAgB;uFAIE,aAAa,CAAC,WAAW,EAAE,CAAC;oFAG/B,aAAa,CAAC,gBAAgB,CAAC;4FAGvB,aAAa,CAAC,OAAO,CAAC;gGAGlB,aAAa,CAAC,OAAO,CAAC;uGAGf,aAAa,CAAC,OAAO,CAAC;gGAG7B,aAAa,CAAC,oBAAoB,EAAE,CAAC;+FAGtC,aAAa,CAAC,gBAAgB,CAAC;4FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;2FAGlC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;6FAG/B,aAAa,CAAC,MAAM,CAAC;0FAGxB,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;iGAG1B,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;+FAGnC,aAAa,CAAC,6BAA6B,EAAE,CAAC;0FAGnD,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,SAAS,CAAC;8FAGxD,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;iGAGzB,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;8FAG/B,aAAa,CAAC,OAAO,CAAC;yFAG3B,aAAa,CAAC,MAAM,CAAC;gGAGd,aAAa,CAAC,WAAW,EAAE,CAAC;+FAG7B,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;4FAG/B,aAAa,CAAC,aAAa,CAAC;+FAGzB,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;oCAKlE,CAAC"}
|
|
@@ -12,6 +12,8 @@ declare function makeStore(devTools: boolean | undefined): import("@genesislcap/
|
|
|
12
12
|
setContextLimit(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<number | undefined>): void;
|
|
13
13
|
setSessionCostUsd(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<number>): void;
|
|
14
14
|
setActiveModel(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | undefined>): void;
|
|
15
|
+
setActiveProviderName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | undefined>): void;
|
|
16
|
+
setProviderStatuses(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("@genesislcap/foundation-ai").AIProviderRegistryStatusEntry[]>): void;
|
|
15
17
|
setActiveAgent(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<Omit<import("..").AgentConfig, "toolHandlers"> | undefined>): void;
|
|
16
18
|
setPinnedAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
|
|
17
19
|
setFlowOwnerAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
+
/**
|
|
3
|
+
* Sum the `cost` field across a message list, recursing into each tool call's
|
|
4
|
+
* `subAgentTrace` so LLM calls made by sub-agents (potentially on a different
|
|
5
|
+
* provider with different rates) contribute to the total.
|
|
6
|
+
*
|
|
7
|
+
* Returns 0 when no messages carry a cost — providers that don't report usage
|
|
8
|
+
* metadata (e.g. Chrome built-in) are skipped silently.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare function sumCosts(messages: readonly ChatMessage[]): number;
|
|
13
|
+
//# sourceMappingURL=sum-costs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sum-costs.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-costs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CASjE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sum-costs.test.d.ts","sourceRoot":"","sources":["../../../src/utils/sum-costs.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { MalformedFunctionCallError } from '@genesislcap/foundation-ai';
|
|
3
3
|
import { agenticActivityBus } from '../../channel/ai-activity-bus';
|
|
4
|
+
import { resolveChatProvider } from '../../config/validate-providers';
|
|
4
5
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
5
6
|
import { logger } from '../../utils/logger';
|
|
6
7
|
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
@@ -26,9 +27,9 @@ const HANDOFF_TOOL_RESULT_PLACEHOLDER = 'Handoff to another specialist — routi
|
|
|
26
27
|
* @beta
|
|
27
28
|
*/
|
|
28
29
|
export class ChatDriver extends EventTarget {
|
|
29
|
-
constructor(
|
|
30
|
+
constructor(providerRegistry, toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS) {
|
|
30
31
|
super();
|
|
31
|
-
this.
|
|
32
|
+
this.providerRegistry = providerRegistry;
|
|
32
33
|
this.maxToolIterations = maxToolIterations;
|
|
33
34
|
this.history = [];
|
|
34
35
|
this.busy = false;
|
|
@@ -59,6 +60,12 @@ export class ChatDriver extends EventTarget {
|
|
|
59
60
|
this.turnSnapshots = [];
|
|
60
61
|
/** Monotonic counter that survives agent swaps — useful for cross-referencing with history. */
|
|
61
62
|
this.globalTurnIndex = 0;
|
|
63
|
+
/**
|
|
64
|
+
* Caches validated provider lookups per name within the current agent. Cleared
|
|
65
|
+
* by `applyAgent` so each new agent's static/function-resolved names are
|
|
66
|
+
* validated fresh.
|
|
67
|
+
*/
|
|
68
|
+
this.resolvedProviderCache = new Map();
|
|
62
69
|
if (typeof toolHandlers === 'function') {
|
|
63
70
|
this.toolHandlersFactory = toolHandlers;
|
|
64
71
|
this.toolHandlers = {};
|
|
@@ -117,10 +124,71 @@ export class ChatDriver extends EventTarget {
|
|
|
117
124
|
typeof config.displayName === 'string' ? config.displayName : config.name;
|
|
118
125
|
this.debugSnapshotter = config.getDebugSnapshot;
|
|
119
126
|
this.subAgentsMap = new Map(((_c = config.subAgents) !== null && _c !== void 0 ? _c : []).map((s) => [s.name, s]));
|
|
127
|
+
this.activeProviderInput = config.provider;
|
|
128
|
+
this.resolvedProviderCache.clear();
|
|
129
|
+
this.lastResolvedProviderName = undefined;
|
|
130
|
+
// Static validation: resolve the name now so unknown-provider and missing-
|
|
131
|
+
// capability errors fire at agent swap rather than on the first LLM call.
|
|
132
|
+
// Function-form `provider` is validated lazily inside `resolveProviderForTurn`.
|
|
133
|
+
if (typeof config.provider === 'string') {
|
|
134
|
+
this.resolveProviderByName(config.provider, config.name);
|
|
135
|
+
}
|
|
120
136
|
// Reset fold state when agent changes — each specialist starts fresh
|
|
121
137
|
this.foldStack = [];
|
|
122
138
|
this.consecutiveFoldOps = 0;
|
|
123
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns the most recently resolved provider name. Falls back to the
|
|
142
|
+
* registry's default when no per-turn resolution has happened yet.
|
|
143
|
+
*/
|
|
144
|
+
getActiveProviderName() {
|
|
145
|
+
var _a;
|
|
146
|
+
return (_a = this.lastResolvedProviderName) !== null && _a !== void 0 ? _a : this.providerRegistry.defaultName();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a named provider against the registry. Cached per-agent so
|
|
150
|
+
* repeated lookups during one agent's lifetime don't re-validate.
|
|
151
|
+
* Validation lives in `resolveChatProvider`; this wrapper just adds the
|
|
152
|
+
* cache.
|
|
153
|
+
*/
|
|
154
|
+
resolveProviderByName(name, agentName) {
|
|
155
|
+
const cached = this.resolvedProviderCache.get(name);
|
|
156
|
+
if (cached)
|
|
157
|
+
return cached;
|
|
158
|
+
const provider = resolveChatProvider(this.providerRegistry, name, agentName);
|
|
159
|
+
this.resolvedProviderCache.set(name, provider);
|
|
160
|
+
return provider;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the provider to use for the current turn. Walks the agent's
|
|
164
|
+
* `provider` selector (static or function form) or falls back to the
|
|
165
|
+
* registry default. Dispatches `provider-changed` when the resolved name
|
|
166
|
+
* differs from the last dispatched value.
|
|
167
|
+
*/
|
|
168
|
+
resolveProviderForTurn(ctx) {
|
|
169
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
var _a;
|
|
171
|
+
let provider;
|
|
172
|
+
let resolvedName;
|
|
173
|
+
if (this.activeProviderInput === undefined) {
|
|
174
|
+
provider = this.providerRegistry.default();
|
|
175
|
+
resolvedName = this.providerRegistry.defaultName();
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const name = typeof this.activeProviderInput === 'function'
|
|
179
|
+
? yield this.activeProviderInput(ctx)
|
|
180
|
+
: this.activeProviderInput;
|
|
181
|
+
provider = this.resolveProviderByName(name, (_a = this.activeAgentName) !== null && _a !== void 0 ? _a : '<unknown>');
|
|
182
|
+
resolvedName = name;
|
|
183
|
+
}
|
|
184
|
+
this.lastResolvedProviderName = resolvedName;
|
|
185
|
+
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
186
|
+
this.lastDispatchedProviderName = resolvedName;
|
|
187
|
+
this.dispatchEvent(new CustomEvent('provider-changed', { detail: { name: resolvedName } }));
|
|
188
|
+
}
|
|
189
|
+
return provider;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
124
192
|
/**
|
|
125
193
|
* Returns the early-stop result set by `completeSubAgent`, if any.
|
|
126
194
|
* Called by a parent `ChatDriver` after running this instance as a sub-agent.
|
|
@@ -197,8 +265,12 @@ export class ChatDriver extends EventTarget {
|
|
|
197
265
|
}
|
|
198
266
|
getSuggestions(history, prompt, count, allAgentInfo) {
|
|
199
267
|
return __awaiter(this, void 0, void 0, function* () {
|
|
200
|
-
|
|
201
|
-
|
|
268
|
+
// Suggestions are an out-of-turn UI helper, not bound to any single agent —
|
|
269
|
+
// always run against the registry default. Best-effort: a default with no
|
|
270
|
+
// `prompt()` just means no suggestions, not a hard error.
|
|
271
|
+
const defaultProvider = this.providerRegistry.default();
|
|
272
|
+
if (!defaultProvider.prompt) {
|
|
273
|
+
logger.warn('ChatDriver: default AI provider does not implement prompt()');
|
|
202
274
|
return [];
|
|
203
275
|
}
|
|
204
276
|
let agentContext = '';
|
|
@@ -275,7 +347,7 @@ export class ChatDriver extends EventTarget {
|
|
|
275
347
|
`- No numbering, bullets, markdown, emojis, code blocks, or quotes around the suggestion.\n` +
|
|
276
348
|
`- No preamble, headings, summary, or commentary before or after the list.`;
|
|
277
349
|
}
|
|
278
|
-
const text = yield
|
|
350
|
+
const text = yield defaultProvider.prompt(userMessage, { systemPrompt });
|
|
279
351
|
// Lenient parsing as a defensive backstop: even with the strict prompt,
|
|
280
352
|
// models occasionally slip in numbering, bullets, or surrounding markdown.
|
|
281
353
|
return (text
|
|
@@ -391,10 +463,6 @@ export class ChatDriver extends EventTarget {
|
|
|
391
463
|
return __awaiter(this, void 0, void 0, function* () {
|
|
392
464
|
if (this.busy || (!userInput.trim() && !(attachments === null || attachments === void 0 ? void 0 : attachments.length)))
|
|
393
465
|
return { reason: 'done' };
|
|
394
|
-
if (!this.aiProvider.chat) {
|
|
395
|
-
logger.warn('ChatDriver: AIProvider does not implement chat()');
|
|
396
|
-
return { reason: 'done' };
|
|
397
|
-
}
|
|
398
466
|
this.busy = true;
|
|
399
467
|
this.subAgentCompletion = undefined;
|
|
400
468
|
this.agentReleaseRequested = false;
|
|
@@ -477,7 +545,7 @@ export class ChatDriver extends EventTarget {
|
|
|
477
545
|
...contextMessages,
|
|
478
546
|
...((_b = subConfig.primerHistory) !== null && _b !== void 0 ? _b : []),
|
|
479
547
|
];
|
|
480
|
-
const child = new ChatDriver(this.
|
|
548
|
+
const child = new ChatDriver(this.providerRegistry);
|
|
481
549
|
child.applyAgent(Object.assign(Object.assign({}, subConfig), { primerHistory: effectivePrimer }));
|
|
482
550
|
// Route interactions back through this driver so widgets render in the
|
|
483
551
|
// parent's (ultimately the root's) history and resolve via the same
|
|
@@ -489,7 +557,15 @@ export class ChatDriver extends EventTarget {
|
|
|
489
557
|
detail: { agentName: subConfig.name, history: e.detail },
|
|
490
558
|
}));
|
|
491
559
|
};
|
|
560
|
+
// Re-dispatch the child's `provider-changed` so the UI reflects whichever
|
|
561
|
+
// provider is *actually* running right now (the sub-agent may use a
|
|
562
|
+
// different provider than the parent). Restoration of the parent's
|
|
563
|
+
// provider on `sub-agent-stop` is handled by the listener in main.ts.
|
|
564
|
+
const forwardProviderChanged = (e) => {
|
|
565
|
+
this.dispatchEvent(new CustomEvent('provider-changed', { detail: e.detail }));
|
|
566
|
+
};
|
|
492
567
|
child.addEventListener('history-updated', forwardTrace);
|
|
568
|
+
child.addEventListener('provider-changed', forwardProviderChanged);
|
|
493
569
|
// Unique per-invocation id so listeners can pair start/stop reliably even
|
|
494
570
|
// when the same sub-agent runs multiple times in parallel.
|
|
495
571
|
const invocationId = crypto.randomUUID();
|
|
@@ -501,6 +577,7 @@ export class ChatDriver extends EventTarget {
|
|
|
501
577
|
}
|
|
502
578
|
finally {
|
|
503
579
|
child.removeEventListener('history-updated', forwardTrace);
|
|
580
|
+
child.removeEventListener('provider-changed', forwardProviderChanged);
|
|
504
581
|
this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: lifecycleDetail }));
|
|
505
582
|
}
|
|
506
583
|
const trace = child.getHistory();
|
|
@@ -522,10 +599,6 @@ export class ChatDriver extends EventTarget {
|
|
|
522
599
|
return __awaiter(this, void 0, void 0, function* () {
|
|
523
600
|
if (this.busy)
|
|
524
601
|
return { reason: 'done' };
|
|
525
|
-
if (!this.aiProvider.chat) {
|
|
526
|
-
logger.warn('ChatDriver: AIProvider does not implement chat()');
|
|
527
|
-
return { reason: 'done' };
|
|
528
|
-
}
|
|
529
602
|
this.busy = true;
|
|
530
603
|
this.subAgentCompletion = undefined;
|
|
531
604
|
agenticActivityBus.publish('tool-loop-start', undefined);
|
|
@@ -762,10 +835,15 @@ export class ChatDriver extends EventTarget {
|
|
|
762
835
|
tools: this.toolDefinitions.length ? this.toolDefinitions : undefined,
|
|
763
836
|
attachments: attachmentsForCall,
|
|
764
837
|
};
|
|
838
|
+
// Resolve the active provider for this turn. Static names were validated
|
|
839
|
+
// in `applyAgent`; function-form names are validated on first resolution
|
|
840
|
+
// here and cached for the agent's lifetime.
|
|
841
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
842
|
+
const activeProvider = yield this.resolveProviderForTurn(promptCtx);
|
|
765
843
|
let response;
|
|
766
844
|
try {
|
|
767
845
|
// oxlint-disable-next-line no-await-in-loop
|
|
768
|
-
response = yield
|
|
846
|
+
response = yield activeProvider.chat(historyForCall, userInputForCall, options);
|
|
769
847
|
}
|
|
770
848
|
catch (e) {
|
|
771
849
|
if (e instanceof MalformedFunctionCallError) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
|
+
import { validateStaticAgentProviders } from '../../config/validate-providers';
|
|
2
3
|
import { transformHistoryForAgent } from '../../utils/history-transform';
|
|
3
4
|
import { logger } from '../../utils/logger';
|
|
4
5
|
import { ChatDriver, REQUEST_CONTINUATION_TOOL, } from '../chat-driver/chat-driver';
|
|
@@ -50,10 +51,10 @@ function buildFallbackSystemPrompt(fallback, specialists) {
|
|
|
50
51
|
* @beta
|
|
51
52
|
*/
|
|
52
53
|
export class OrchestratingDriver extends EventTarget {
|
|
53
|
-
constructor(
|
|
54
|
+
constructor(providerRegistry, agents, options = {}) {
|
|
54
55
|
var _a, _b, _c, _d;
|
|
55
56
|
super();
|
|
56
|
-
this.
|
|
57
|
+
this.providerRegistry = providerRegistry;
|
|
57
58
|
this.agents = agents;
|
|
58
59
|
/**
|
|
59
60
|
* Aborted on driver disposal. Threaded into `AgentLifecycleContext.signal`
|
|
@@ -80,6 +81,10 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
80
81
|
this.classifierHistoryLength =
|
|
81
82
|
(_c = options.classifierHistoryLength) !== null && _c !== void 0 ? _c : DEFAULT_CLASSIFIER_HISTORY_LENGTH;
|
|
82
83
|
this.classifierRetries = (_d = options.classifierRetries) !== null && _d !== void 0 ? _d : DEFAULT_CLASSIFIER_RETRIES;
|
|
84
|
+
// Static-name validation: walk every agent (and nested sub-agents). Any
|
|
85
|
+
// `provider: '<name>'` that isn't registered, or resolves to a provider
|
|
86
|
+
// without `chat()`, throws now rather than at first turn.
|
|
87
|
+
validateStaticAgentProviders(agents, providerRegistry);
|
|
83
88
|
// Specialists drive the classifier. `excludeFromClassifier` agents are still
|
|
84
89
|
// resolvable by name (so manual pinning works) but never auto-routed.
|
|
85
90
|
this.specialists = agents.filter(isSpecialist).filter((a) => !a.excludeFromClassifier);
|
|
@@ -90,7 +95,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
90
95
|
const rawFallback = fallbacks[0];
|
|
91
96
|
this.fallback = rawFallback
|
|
92
97
|
? Object.assign(Object.assign({}, rawFallback), { systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }) : undefined;
|
|
93
|
-
this.chatDriver = new ChatDriver(
|
|
98
|
+
this.chatDriver = new ChatDriver(providerRegistry, {}, [], undefined, undefined, options.maxToolIterations, options.maxFoldOperations, options.maxTurnSnapshots);
|
|
94
99
|
// Proxy events from the shared driver
|
|
95
100
|
this.chatDriver.addEventListener('history-updated', (e) => {
|
|
96
101
|
this.dispatchEvent(new CustomEvent('history-updated', { detail: e.detail }));
|
|
@@ -110,6 +115,9 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
110
115
|
this.chatDriver.addEventListener('interaction-stop', (e) => {
|
|
111
116
|
this.dispatchEvent(new CustomEvent('interaction-stop', { detail: e.detail }));
|
|
112
117
|
});
|
|
118
|
+
this.chatDriver.addEventListener('provider-changed', (e) => {
|
|
119
|
+
this.dispatchEvent(new CustomEvent('provider-changed', { detail: e.detail }));
|
|
120
|
+
});
|
|
113
121
|
}
|
|
114
122
|
resolveInteraction(interactionId, result) {
|
|
115
123
|
this.chatDriver.resolveInteraction(interactionId, result);
|
|
@@ -117,6 +125,10 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
117
125
|
isBusy() {
|
|
118
126
|
return this.chatDriver.isBusy();
|
|
119
127
|
}
|
|
128
|
+
/** Currently active provider name from the underlying ChatDriver. */
|
|
129
|
+
getActiveProviderName() {
|
|
130
|
+
return this.chatDriver.getActiveProviderName();
|
|
131
|
+
}
|
|
120
132
|
/**
|
|
121
133
|
* Pins routing to a specific agent by name. While pinned, the classifier is
|
|
122
134
|
* skipped and the continuation tool is hidden from the agent's tool list, so
|
|
@@ -427,8 +439,15 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
427
439
|
systemPrompt: classifierPrompt,
|
|
428
440
|
tools: [routingTool],
|
|
429
441
|
};
|
|
442
|
+
// Classification is orchestrator-level, not bound to any single agent —
|
|
443
|
+
// always run against the registry default.
|
|
444
|
+
const classifierProvider = this.providerRegistry.default();
|
|
445
|
+
if (!classifierProvider.chat) {
|
|
446
|
+
logger.warn('OrchestratingDriver: default AI provider does not implement chat() — cannot classify.');
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
430
449
|
// oxlint-disable-next-line no-await-in-loop
|
|
431
|
-
const response = yield
|
|
450
|
+
const response = yield classifierProvider.chat([], input, options);
|
|
432
451
|
const tc = (_b = response.toolCalls) === null || _b === void 0 ? void 0 : _b[0];
|
|
433
452
|
const index = (tc === null || tc === void 0 ? void 0 : tc.name) === 'select_agent' ? tc.args.agent_index : -1;
|
|
434
453
|
if (index >= 0 && index < this.specialists.length) {
|
|
@@ -107,6 +107,17 @@ export function defineStatefulAgent(opts) {
|
|
|
107
107
|
return opts.displayName(Object.assign(Object.assign({}, ctx), { state }));
|
|
108
108
|
})
|
|
109
109
|
: undefined;
|
|
110
|
+
// Function-form `provider` needs `state` injected the same way the other
|
|
111
|
+
// per-turn resolvers do; the static-string form is passed through unchanged
|
|
112
|
+
// and validated up-front by OrchestratingDriver.
|
|
113
|
+
const wrappedProvider = typeof opts.provider === 'function'
|
|
114
|
+
? (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
if (!state) {
|
|
116
|
+
throw new Error(`Stateful agent "${opts.name}" provider called before init`);
|
|
117
|
+
}
|
|
118
|
+
return opts.provider(Object.assign(Object.assign({}, ctx), { state }));
|
|
119
|
+
})
|
|
120
|
+
: opts.provider;
|
|
110
121
|
const base = {
|
|
111
122
|
name: opts.name,
|
|
112
123
|
displayName: wrappedDisplayName,
|
|
@@ -116,6 +127,7 @@ export function defineStatefulAgent(opts) {
|
|
|
116
127
|
chatInputDuringExecution: opts.chatInputDuringExecution,
|
|
117
128
|
toolDefinitions: wrappedTools,
|
|
118
129
|
toolHandlers: wrappedHandlers,
|
|
130
|
+
provider: wrappedProvider,
|
|
119
131
|
onActivate: (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
120
132
|
state = yield opts.init(ctx);
|
|
121
133
|
// Sample once with the init state to fail loud on fold misuse at the
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a provider name against the registry and assert it implements
|
|
3
|
+
* `chat()` — the capability the chat tool-loop depends on. Throws with a
|
|
4
|
+
* message naming the agent so misconfigured agents are easy to track down.
|
|
5
|
+
*
|
|
6
|
+
* Used by both the up-front static walk in `OrchestratingDriver` and by
|
|
7
|
+
* `ChatDriver`'s per-turn resolver for function-form `provider` values.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export function resolveChatProvider(registry, providerName, agentName) {
|
|
12
|
+
const provider = registry.get(providerName);
|
|
13
|
+
if (!provider) {
|
|
14
|
+
const available = registry.names().join(', ') || '(none)';
|
|
15
|
+
throw new Error(`Agent "${agentName}" references unknown provider "${providerName}". Registered: ${available}`);
|
|
16
|
+
}
|
|
17
|
+
if (!provider.chat) {
|
|
18
|
+
throw new Error(`Agent "${agentName}" resolved to provider "${providerName}" which does not implement chat().`);
|
|
19
|
+
}
|
|
20
|
+
return provider;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Walk an agent tree (including nested `subAgents`) and validate every static
|
|
24
|
+
* `provider: string` against the registry. Function-form `provider` values are
|
|
25
|
+
* deferred — they validate on first resolution inside the driver.
|
|
26
|
+
*
|
|
27
|
+
* Throws on the first failure, with the same message shape produced by
|
|
28
|
+
* {@link resolveChatProvider}.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export function validateStaticAgentProviders(agents, registry) {
|
|
33
|
+
const visited = new Set();
|
|
34
|
+
const walk = (agent) => {
|
|
35
|
+
var _a;
|
|
36
|
+
if (visited.has(agent))
|
|
37
|
+
return;
|
|
38
|
+
visited.add(agent);
|
|
39
|
+
if (typeof agent.provider === 'string') {
|
|
40
|
+
resolveChatProvider(registry, agent.provider, agent.name);
|
|
41
|
+
}
|
|
42
|
+
for (const sub of (_a = agent.subAgents) !== null && _a !== void 0 ? _a : [])
|
|
43
|
+
walk(sub);
|
|
44
|
+
};
|
|
45
|
+
for (const root of agents)
|
|
46
|
+
walk(root);
|
|
47
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
+
import { resolveChatProvider, validateStaticAgentProviders } from './validate-providers';
|
|
4
|
+
const chatProvider = () => ({
|
|
5
|
+
chat: () => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: '' }); }),
|
|
6
|
+
});
|
|
7
|
+
const promptOnlyProvider = () => ({
|
|
8
|
+
prompt: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Minimal in-test registry — implements only what the validation helpers
|
|
12
|
+
* touch (`get`, `names`). Avoids depending on foundation-ai's concrete
|
|
13
|
+
* registry impl, and keeps these tests Node-runnable.
|
|
14
|
+
*/
|
|
15
|
+
const makeRegistry = (providers) => {
|
|
16
|
+
const map = new Map(Object.entries(providers));
|
|
17
|
+
return {
|
|
18
|
+
get: (name) => map.get(name),
|
|
19
|
+
default: () => map.values().next().value,
|
|
20
|
+
defaultName: () => map.keys().next().value,
|
|
21
|
+
names: () => [...map.keys()],
|
|
22
|
+
getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
23
|
+
listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const specialist = (overrides) => (Object.assign({ name: overrides.name, description: 'test specialist' }, overrides));
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// resolveChatProvider
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const resolve = createLogicSuite('resolveChatProvider');
|
|
31
|
+
resolve('returns the provider when the name is registered and has chat()', () => {
|
|
32
|
+
const fast = chatProvider();
|
|
33
|
+
const registry = makeRegistry({ fast });
|
|
34
|
+
assert.is(resolveChatProvider(registry, 'fast', 'A'), fast);
|
|
35
|
+
});
|
|
36
|
+
resolve('throws on an unknown provider name with the agent name in the message', () => {
|
|
37
|
+
const registry = makeRegistry({ fast: chatProvider() });
|
|
38
|
+
assert.throws(() => resolveChatProvider(registry, 'deep', 'A'), /Agent "A" references unknown provider "deep"/);
|
|
39
|
+
});
|
|
40
|
+
resolve('throws including the registered names so the misconfiguration is debuggable', () => {
|
|
41
|
+
const registry = makeRegistry({
|
|
42
|
+
fast: chatProvider(),
|
|
43
|
+
deep: chatProvider(),
|
|
44
|
+
cheap: chatProvider(),
|
|
45
|
+
});
|
|
46
|
+
assert.throws(() => resolveChatProvider(registry, 'typo', 'A'), /Registered: fast, deep, cheap/);
|
|
47
|
+
});
|
|
48
|
+
resolve('throws when the resolved provider does not implement chat()', () => {
|
|
49
|
+
const registry = makeRegistry({ chrome: promptOnlyProvider() });
|
|
50
|
+
assert.throws(() => resolveChatProvider(registry, 'chrome', 'A'), /Agent "A" resolved to provider "chrome" which does not implement chat\(\)/);
|
|
51
|
+
});
|
|
52
|
+
resolve.run();
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// validateStaticAgentProviders
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
const validate = createLogicSuite('validateStaticAgentProviders');
|
|
57
|
+
validate('accepts agents with no `provider` field (back-compat path)', () => {
|
|
58
|
+
const registry = makeRegistry({ default: chatProvider() });
|
|
59
|
+
assert.not.throws(() => validateStaticAgentProviders([specialist({ name: 'A' })], registry));
|
|
60
|
+
});
|
|
61
|
+
validate('accepts agents that name a registered chat-capable provider', () => {
|
|
62
|
+
const registry = makeRegistry({ fast: chatProvider(), deep: chatProvider() });
|
|
63
|
+
assert.not.throws(() => validateStaticAgentProviders([specialist({ name: 'A', provider: 'deep' })], registry));
|
|
64
|
+
});
|
|
65
|
+
validate('throws when a top-level agent references an unknown provider', () => {
|
|
66
|
+
const registry = makeRegistry({ fast: chatProvider() });
|
|
67
|
+
assert.throws(() => validateStaticAgentProviders([specialist({ name: 'A', provider: 'deep' })], registry), /Agent "A" references unknown provider "deep"/);
|
|
68
|
+
});
|
|
69
|
+
validate('recurses into subAgents — bad provider on a sub-agent throws', () => {
|
|
70
|
+
const registry = makeRegistry({ fast: chatProvider() });
|
|
71
|
+
const sub = specialist({ name: 'Sub', provider: 'missing' });
|
|
72
|
+
assert.throws(() => validateStaticAgentProviders([specialist({ name: 'A', subAgents: [sub] })], registry), /Agent "Sub" references unknown provider "missing"/);
|
|
73
|
+
});
|
|
74
|
+
validate('recurses through multiple levels — bad provider on a deeply-nested sub-agent throws', () => {
|
|
75
|
+
const registry = makeRegistry({ fast: chatProvider() });
|
|
76
|
+
const deepest = specialist({ name: 'Deepest', provider: 'missing' });
|
|
77
|
+
const middle = specialist({ name: 'Middle', subAgents: [deepest] });
|
|
78
|
+
assert.throws(() => validateStaticAgentProviders([specialist({ name: 'Top', subAgents: [middle] })], registry), /Agent "Deepest" references unknown provider "missing"/);
|
|
79
|
+
});
|
|
80
|
+
validate('defers function-form `provider` — it is not invoked at validation time', () => {
|
|
81
|
+
const registry = makeRegistry({ fast: chatProvider() });
|
|
82
|
+
const agent = specialist({
|
|
83
|
+
name: 'A',
|
|
84
|
+
// If validateStaticAgentProviders eagerly resolved this, it would throw
|
|
85
|
+
// here. The contract is that function-form is deferred to the driver's
|
|
86
|
+
// per-turn resolver.
|
|
87
|
+
provider: () => {
|
|
88
|
+
throw new Error('function-form provider should not be invoked at validation time');
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
assert.not.throws(() => validateStaticAgentProviders([agent], registry));
|
|
92
|
+
});
|
|
93
|
+
validate('shared sub-agent across two parents validates cleanly', () => {
|
|
94
|
+
const registry = makeRegistry({ fast: chatProvider() });
|
|
95
|
+
const shared = specialist({ name: 'Shared', provider: 'fast' });
|
|
96
|
+
const parentA = specialist({ name: 'ParentA', subAgents: [shared] });
|
|
97
|
+
const parentB = specialist({ name: 'ParentB', subAgents: [shared] });
|
|
98
|
+
assert.not.throws(() => validateStaticAgentProviders([parentA, parentB], registry));
|
|
99
|
+
});
|
|
100
|
+
validate.run();
|