@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.
Files changed (55) hide show
  1. package/dist/ai-assistant.api.json +312 -41
  2. package/dist/ai-assistant.d.ts +105 -8
  3. package/dist/dts/components/ai-driver/ai-driver.d.ts +7 -0
  4. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  5. package/dist/dts/components/chat-driver/chat-driver.d.ts +37 -3
  6. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  7. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +5 -3
  8. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  9. package/dist/dts/config/config.d.ts +31 -0
  10. package/dist/dts/config/config.d.ts.map +1 -1
  11. package/dist/dts/config/define-stateful-agent.d.ts +9 -0
  12. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  13. package/dist/dts/config/validate-providers.d.ts +25 -0
  14. package/dist/dts/config/validate-providers.d.ts.map +1 -0
  15. package/dist/dts/config/validate-providers.test.d.ts +2 -0
  16. package/dist/dts/config/validate-providers.test.d.ts.map +1 -0
  17. package/dist/dts/main/main.d.ts +17 -5
  18. package/dist/dts/main/main.d.ts.map +1 -1
  19. package/dist/dts/main/main.styles.d.ts.map +1 -1
  20. package/dist/dts/main/main.template.d.ts.map +1 -1
  21. package/dist/dts/state/ai-assistant-slice.d.ts +14 -1
  22. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  23. package/dist/dts/state/session-store.d.ts +2 -0
  24. package/dist/dts/state/session-store.d.ts.map +1 -1
  25. package/dist/dts/utils/sum-costs.d.ts +13 -0
  26. package/dist/dts/utils/sum-costs.d.ts.map +1 -0
  27. package/dist/dts/utils/sum-costs.test.d.ts +2 -0
  28. package/dist/dts/utils/sum-costs.test.d.ts.map +1 -0
  29. package/dist/esm/components/chat-driver/chat-driver.js +93 -15
  30. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +23 -4
  31. package/dist/esm/config/define-stateful-agent.js +12 -0
  32. package/dist/esm/config/validate-providers.js +47 -0
  33. package/dist/esm/config/validate-providers.test.js +100 -0
  34. package/dist/esm/main/main.js +76 -21
  35. package/dist/esm/main/main.styles.js +52 -0
  36. package/dist/esm/main/main.template.js +36 -1
  37. package/dist/esm/state/ai-assistant-slice.js +8 -0
  38. package/dist/esm/utils/sum-costs.js +23 -0
  39. package/dist/esm/utils/sum-costs.test.js +88 -0
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/docs/migration-GENC-1262.md +219 -0
  42. package/package.json +16 -16
  43. package/src/components/ai-driver/ai-driver.ts +8 -0
  44. package/src/components/chat-driver/chat-driver.ts +107 -14
  45. package/src/components/orchestrating-driver/orchestrating-driver.ts +29 -4
  46. package/src/config/config.ts +32 -0
  47. package/src/config/define-stateful-agent.ts +28 -0
  48. package/src/config/validate-providers.test.ts +148 -0
  49. package/src/config/validate-providers.ts +58 -0
  50. package/src/main/main.styles.ts +52 -0
  51. package/src/main/main.template.ts +50 -2
  52. package/src/main/main.ts +69 -14
  53. package/src/state/ai-assistant-slice.ts +24 -1
  54. package/src/utils/sum-costs.test.ts +108 -0
  55. 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,EAAE,4BAA4B,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC5F,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,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,uBAoBjC,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;0FAGjC,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"}
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;;;;;;;;;;;;;;;;;;;;;;;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"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sum-costs.test.d.ts.map
@@ -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(aiProvider, toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS) {
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.aiProvider = aiProvider;
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
- if (!this.aiProvider.prompt) {
201
- logger.warn('ChatDriver: AIProvider does not implement prompt()');
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 this.aiProvider.prompt(userMessage, { systemPrompt });
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.aiProvider);
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 this.aiProvider.chat(historyForCall, userInputForCall, options);
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(aiProvider, agents, options = {}) {
54
+ constructor(providerRegistry, agents, options = {}) {
54
55
  var _a, _b, _c, _d;
55
56
  super();
56
- this.aiProvider = aiProvider;
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(aiProvider, {}, [], undefined, undefined, options.maxToolIterations, options.maxFoldOperations, options.maxTurnSnapshots);
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 this.aiProvider.chat([], input, options);
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();