@mcp-abap-adt/llm-agent-libs 26.0.0 → 27.0.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 (94) hide show
  1. package/README.md +11 -14
  2. package/dist/agent.d.ts +2 -1
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +6 -5
  5. package/dist/agent.js.map +1 -1
  6. package/dist/builder.d.ts +36 -14
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +472 -348
  9. package/dist/builder.js.map +1 -1
  10. package/dist/config/config-watcher.d.ts.map +1 -1
  11. package/dist/config/config-watcher.js +9 -5
  12. package/dist/config/config-watcher.js.map +1 -1
  13. package/dist/index.d.ts +1 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -5
  16. package/dist/index.js.map +1 -1
  17. package/dist/mcp/vectorize-mcp-tools.d.ts.map +1 -1
  18. package/dist/mcp/vectorize-mcp-tools.js +21 -2
  19. package/dist/mcp/vectorize-mcp-tools.js.map +1 -1
  20. package/dist/pipeline/handlers/history-upsert.d.ts.map +1 -1
  21. package/dist/pipeline/handlers/history-upsert.js +8 -1
  22. package/dist/pipeline/handlers/history-upsert.js.map +1 -1
  23. package/dist/pipeline/handlers/rag-query.d.ts +1 -1
  24. package/dist/pipeline/handlers/rag-query.d.ts.map +1 -1
  25. package/dist/pipeline/handlers/rag-query.js +10 -2
  26. package/dist/pipeline/handlers/rag-query.js.map +1 -1
  27. package/dist/plugins/index.d.ts +1 -1
  28. package/dist/plugins/index.d.ts.map +1 -1
  29. package/dist/plugins/index.js +1 -1
  30. package/dist/plugins/index.js.map +1 -1
  31. package/dist/plugins/types.d.ts +7 -0
  32. package/dist/plugins/types.d.ts.map +1 -1
  33. package/dist/plugins/types.js +85 -14
  34. package/dist/plugins/types.js.map +1 -1
  35. package/dist/providers.d.ts.map +1 -1
  36. package/dist/providers.js +8 -5
  37. package/dist/providers.js.map +1 -1
  38. package/dist/session/session-graph-factory.d.ts +49 -16
  39. package/dist/session/session-graph-factory.d.ts.map +1 -1
  40. package/dist/session/session-graph-factory.js +140 -62
  41. package/dist/session/session-graph-factory.js.map +1 -1
  42. package/dist/skills/plugin-host/qdrant-store.d.ts +6 -1
  43. package/dist/skills/plugin-host/qdrant-store.d.ts.map +1 -1
  44. package/dist/skills/plugin-host/qdrant-store.js +7 -7
  45. package/dist/skills/plugin-host/qdrant-store.js.map +1 -1
  46. package/dist/util/stop-all.d.ts +19 -0
  47. package/dist/util/stop-all.d.ts.map +1 -0
  48. package/dist/util/stop-all.js +30 -0
  49. package/dist/util/stop-all.js.map +1 -0
  50. package/package.json +5 -35
  51. package/dist/coordinator/dag/reviewer-error-strategy.d.ts +0 -15
  52. package/dist/coordinator/dag/reviewer-error-strategy.d.ts.map +0 -1
  53. package/dist/coordinator/dag/reviewer-error-strategy.js +0 -38
  54. package/dist/coordinator/dag/reviewer-error-strategy.js.map +0 -1
  55. package/dist/skills/plugin-host/chunker.test.d.ts +0 -2
  56. package/dist/skills/plugin-host/chunker.test.d.ts.map +0 -1
  57. package/dist/skills/plugin-host/chunker.test.js +0 -31
  58. package/dist/skills/plugin-host/chunker.test.js.map +0 -1
  59. package/dist/skills/plugin-host/compatible-skills-rag.test.d.ts +0 -2
  60. package/dist/skills/plugin-host/compatible-skills-rag.test.d.ts.map +0 -1
  61. package/dist/skills/plugin-host/compatible-skills-rag.test.js +0 -174
  62. package/dist/skills/plugin-host/compatible-skills-rag.test.js.map +0 -1
  63. package/dist/skills/plugin-host/http-marketplace-source.test.d.ts +0 -2
  64. package/dist/skills/plugin-host/http-marketplace-source.test.d.ts.map +0 -1
  65. package/dist/skills/plugin-host/http-marketplace-source.test.js +0 -71
  66. package/dist/skills/plugin-host/http-marketplace-source.test.js.map +0 -1
  67. package/dist/skills/plugin-host/in-memory-store.test.d.ts +0 -2
  68. package/dist/skills/plugin-host/in-memory-store.test.d.ts.map +0 -1
  69. package/dist/skills/plugin-host/in-memory-store.test.js +0 -100
  70. package/dist/skills/plugin-host/in-memory-store.test.js.map +0 -1
  71. package/dist/skills/plugin-host/marketplace-adapter.test.d.ts +0 -2
  72. package/dist/skills/plugin-host/marketplace-adapter.test.d.ts.map +0 -1
  73. package/dist/skills/plugin-host/marketplace-adapter.test.js +0 -46
  74. package/dist/skills/plugin-host/marketplace-adapter.test.js.map +0 -1
  75. package/dist/skills/plugin-host/qdrant-store.test.d.ts +0 -2
  76. package/dist/skills/plugin-host/qdrant-store.test.d.ts.map +0 -1
  77. package/dist/skills/plugin-host/qdrant-store.test.js +0 -396
  78. package/dist/skills/plugin-host/qdrant-store.test.js.map +0 -1
  79. package/dist/skills/plugin-host/skill-plugin-host.ingest.test.d.ts +0 -2
  80. package/dist/skills/plugin-host/skill-plugin-host.ingest.test.d.ts.map +0 -1
  81. package/dist/skills/plugin-host/skill-plugin-host.ingest.test.js +0 -520
  82. package/dist/skills/plugin-host/skill-plugin-host.ingest.test.js.map +0 -1
  83. package/dist/skills/plugin-host/skill-plugin-host.recall-and-reload.test.d.ts +0 -2
  84. package/dist/skills/plugin-host/skill-plugin-host.recall-and-reload.test.d.ts.map +0 -1
  85. package/dist/skills/plugin-host/skill-plugin-host.recall-and-reload.test.js +0 -328
  86. package/dist/skills/plugin-host/skill-plugin-host.recall-and-reload.test.js.map +0 -1
  87. package/dist/skills/plugin-host/skills-rag-source.test.d.ts +0 -2
  88. package/dist/skills/plugin-host/skills-rag-source.test.d.ts.map +0 -1
  89. package/dist/skills/plugin-host/skills-rag-source.test.js +0 -69
  90. package/dist/skills/plugin-host/skills-rag-source.test.js.map +0 -1
  91. package/dist/skills/plugin-host/source-strategies.test.d.ts +0 -2
  92. package/dist/skills/plugin-host/source-strategies.test.d.ts.map +0 -1
  93. package/dist/skills/plugin-host/source-strategies.test.js +0 -52
  94. package/dist/skills/plugin-host/source-strategies.test.js.map +0 -1
package/dist/builder.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * .setToolsRag(myRag)
13
13
  * .build();
14
14
  */
15
- import { buildNamespacedTools, CircuitBreaker, CircuitBreakerLlm, defaultToolNamespace, FallbackRag, InMemoryRag, QueryEmbedding, SimpleRagProviderRegistry, SimpleRagRegistry, } from '@mcp-abap-adt/llm-agent';
15
+ import { buildNamespacedTools, CircuitBreaker, CircuitBreakerLlm, collectServerDescriptors, defaultToolNamespace, FallbackRag, InMemoryRag, normaliseLogger, QueryEmbedding, ragStoreKey, SimpleRagProviderRegistry, SimpleRagRegistry, } from '@mcp-abap-adt/llm-agent';
16
16
  import { makeConnectionStrategy } from '@mcp-abap-adt/llm-agent-mcp';
17
17
  import { wrapEmbedder } from './adapters/usage-logging-embedder.js';
18
18
  import { SmartAgent } from './agent.js';
@@ -30,6 +30,7 @@ import { RateLimiterLlm } from './resilience/rate-limiter-llm.js';
30
30
  import { RetryLlm } from './resilience/retry-llm.js';
31
31
  import { DefaultSubAgentContextBuilder, } from './subagent/default-context-builder.js';
32
32
  import { SmartAgentSubAgent } from './subagent/smart-agent-subagent.js';
33
+ import { stopAll } from './util/stop-all.js';
33
34
  // ---------------------------------------------------------------------------
34
35
  // Helpers
35
36
  // ---------------------------------------------------------------------------
@@ -59,6 +60,7 @@ export class SmartAgentBuilder {
59
60
  _historyRag;
60
61
  _pipeline;
61
62
  _mcpClients;
63
+ _mcpServers;
62
64
  _classifier;
63
65
  _assembler;
64
66
  _logger;
@@ -179,6 +181,30 @@ export class SmartAgentBuilder {
179
181
  this._mcpClients = clients;
180
182
  return this;
181
183
  }
184
+ /**
185
+ * Servers this builder owns: `build()` starts each one and `handle.close()`
186
+ * stops it. Use this when the caller knows HOW a server is started — a stdio
187
+ * child with its own environment, an HTTP target with a credential — rather
188
+ * than handing over an already-connected client.
189
+ *
190
+ * Beside `withMcpClients`, never replacing it: a consumer that already holds
191
+ * clients keeps passing clients. Setting BOTH is a configuration error and
192
+ * `build()` throws, rather than one silently winning over the other.
193
+ *
194
+ * Every server must carry a `descriptor`, or none may — a partly-filled set
195
+ * throws, because dropping it would re-namespace tools from their `label`
196
+ * to `s${slotIndex}` with nothing reporting it.
197
+ *
198
+ * Those descriptors DO reach the pipeline (`PipelineDeps.mcpClientDescriptors`,
199
+ * so namespacing works), but do NOT reach `SmartAgentHandle` — unlike the
200
+ * YAML `mcp:` branch, `handle.mcpClientDescriptors`/`configuredSlotCount`
201
+ * stay unset here. A caller forced to supply descriptors all-or-none
202
+ * currently gets nothing back on the handle for it.
203
+ */
204
+ withMcpServers(servers) {
205
+ this._mcpServers = servers;
206
+ return this;
207
+ }
182
208
  /** Override the intent classifier. */
183
209
  withClassifier(classifier) {
184
210
  this._classifier = classifier;
@@ -189,9 +215,18 @@ export class SmartAgentBuilder {
189
215
  this._assembler = assembler;
190
216
  return this;
191
217
  }
192
- /** Set a logger for internal pipeline events. */
218
+ /**
219
+ * Set a logger for internal pipeline events.
220
+ *
221
+ * Takes either shape: the event `ILogger`, or an ordinary `ITextLogger`
222
+ * (`info`/`warn`/`error`/`debug`). A text logger is normalised here, at the
223
+ * boundary, so everything downstream — `PipelineDeps.logger`, the agent, the
224
+ * connection strategy — keeps receiving the event logger it already expects,
225
+ * and `IPipelineContext.logger` still hands plugins the type they compile
226
+ * against.
227
+ */
193
228
  withLogger(logger) {
194
- this._logger = logger;
229
+ this._logger = normaliseLogger(logger);
195
230
  return this;
196
231
  }
197
232
  /** Set a tool execution policy (allow/deny list). */
@@ -583,9 +618,13 @@ export class SmartAgentBuilder {
583
618
  }
584
619
  }
585
620
  } // end skipModelValidation guard
586
- // Auto-create tools RAG if MCP clients will be configured and embedder available
621
+ // Auto-create tools RAG if MCP clients will be configured and embedder available.
622
+ // `this._mcpServers` (withMcpServers) must agree with `this._mcpClients`
623
+ // (withMcpClients) here — a consumer migrating from one seam to the other
624
+ // must not silently lose the auto-created tools store (and, with it,
625
+ // DefaultPipeline's rag-tools stage).
587
626
  const toolsRag = this._toolsRag ??
588
- ((this.cfg.mcp || this._mcpClients) && this._embedder
627
+ ((this.cfg.mcp || this._mcpClients || this._mcpServers) && this._embedder
589
628
  ? new InMemoryRag()
590
629
  : undefined);
591
630
  // Auto-create history RAG if history summarization is enabled
@@ -615,19 +654,19 @@ export class SmartAgentBuilder {
615
654
  // leave it unset so a genuine name collision still surfaces instead of
616
655
  // silently keeping the old RAG/editor/meta.
617
656
  for (const c of this._staticCollections) {
618
- if (c.idempotent && ragRegistry.get(c.name))
657
+ if (c.idempotent && ragRegistry.get(c.name, c.meta?.scope ?? 'global'))
619
658
  continue;
620
659
  ragRegistry.register(c.name, c.rag, c.editor, c.meta);
621
660
  }
622
661
  // Mirror the built-in toolsRag / historyRag into the registry so the projection preserves
623
662
  // ragStores.tools / ragStores.history behavior that existing handlers rely on.
624
- if (toolsRag && !ragRegistry.get('tools')) {
663
+ if (toolsRag && !ragRegistry.get('tools', 'global')) {
625
664
  ragRegistry.register('tools', toolsRag, undefined, {
626
665
  displayName: 'tools',
627
666
  scope: 'global',
628
667
  });
629
668
  }
630
- if (historyRag && !ragRegistry.get('history')) {
669
+ if (historyRag && !ragRegistry.get('history', 'global')) {
631
670
  ragRegistry.register('history', historyRag, undefined, {
632
671
  displayName: 'history',
633
672
  scope: 'global',
@@ -640,17 +679,19 @@ export class SmartAgentBuilder {
640
679
  throw new Error(`Failed to create collection '${c.collectionName}': ${res.error.message}`);
641
680
  }
642
681
  }
643
- // Derive ragStores as a live projection of the registry; keep it in sync via the
644
- // registry's mutation listener so existing code paths (assembler, handlers,
645
- // addRagStore/removeRagStore) see the same shape.
682
+ // Derive ragStores as a live projection of the registry, kept in sync by the
683
+ // registry's mutation listener. A global keeps its bare name — every stage
684
+ // configuration naming one keeps working — and a user or session collection
685
+ // is keyed user/<name> or session/<name>, so one name in several scopes gives
686
+ // several keys and nothing overwrites anything (§6.4).
646
687
  const ragStores = {};
647
688
  const rebuildProjection = () => {
648
689
  for (const k of Object.keys(ragStores))
649
690
  delete ragStores[k];
650
691
  for (const m of ragRegistry.list()) {
651
- const r = ragRegistry.get(m.name);
692
+ const r = ragRegistry.get(m.name, m.scope ?? 'global');
652
693
  if (r)
653
- ragStores[m.name] = r;
694
+ ragStores[ragStoreKey(m)] = r;
654
695
  }
655
696
  };
656
697
  rebuildProjection();
@@ -683,17 +724,33 @@ export class SmartAgentBuilder {
683
724
  onStateChange: cbCfg.onStateChange ?? makeOnStateChange('embedder'),
684
725
  });
685
726
  circuitBreakers.push(embedderBreaker);
686
- for (const [key, store] of Object.entries(ragStores)) {
727
+ // list() is a snapshot, so changing entries while iterating is safe.
728
+ for (const meta of ragRegistry.list()) {
729
+ const scope = meta.scope ?? 'global';
730
+ const store = ragRegistry.get(meta.name, scope);
731
+ if (!store)
732
+ continue;
687
733
  const wrapped = new FallbackRag(store, new InMemoryRag(), embedderBreaker);
688
- // Update both registry and projection so later lookups (and the mutation-listener
689
- // rebuild) see the wrapped store.
690
- const existingMeta = ragRegistry.list().find((m) => m.name === key);
691
- ragRegistry.unregister(key);
692
- ragRegistry.register(key, wrapped, undefined, {
693
- displayName: existingMeta?.displayName ?? key,
694
- scope: existingMeta?.scope ?? 'global',
734
+ // Later lookups (and the mutation-listener rebuild) see the wrapped
735
+ // store. The entry keeps its scope, owner, editor, provider and store
736
+ // name: without the last two a hydrated collection's delete would reach
737
+ // no store and its catalog record would bring it back (§6.3).
738
+ if (ragRegistry instanceof SimpleRagRegistry) {
739
+ ragRegistry.replaceRag(meta.name, scope, wrapped);
740
+ continue;
741
+ }
742
+ // Another IRagRegistry: re-register with everything register carries.
743
+ const editor = ragRegistry.getEditor(meta.name, scope);
744
+ ragRegistry.unregister(meta.name, scope);
745
+ ragRegistry.register(meta.name, wrapped, editor, {
746
+ displayName: meta.displayName,
747
+ description: meta.description,
748
+ scope,
749
+ sessionId: meta.sessionId,
750
+ userId: meta.userId,
751
+ providerName: meta.providerName,
752
+ tags: meta.tags,
695
753
  });
696
- // Projection gets rebuilt by the mutation listener; no direct write to ragStores needed.
697
754
  }
698
755
  }
699
756
  // ---- Request logger ---------------------------------------------------
@@ -716,352 +773,419 @@ export class SmartAgentBuilder {
716
773
  // `vectorizeMcpTools` below consumes this SAME view rather than relisting.
717
774
  let namespacedTools;
718
775
  let toolProvenance;
719
- if (this._mcpClients) {
720
- // Caller-provided clients: skip auto-connect and vectorization
721
- mcpClients = this._mcpClients;
722
- }
723
- else {
724
- // YAML `mcp:` → route connection through an IMcpConnectionStrategy
725
- // (build-on-components). Default to a resilient `makeConnectionStrategy`
726
- // (connect + periodic reconnect + readiness via IReadinessReporter) unless
727
- // the consumer injected their own. The strategy OWNS lifecycle: its initial
728
- // `resolve([])` connects the targets and the agent resolves through it each
729
- // iteration; disposal is `connectionStrategy.dispose()` (no per-client
730
- // closeFns). `BuilderMcpConfig` is structurally `McpConnectionConfig`.
731
- const mcpConfigs = prepareMcpConfigs(this.cfg.mcp, this._mcpRequestHeadersStrategy);
732
- if (mcpConfigs.length > 0 && !connectionStrategy) {
733
- connectionStrategy = makeConnectionStrategy(mcpConfigs, log ? { logger: log } : undefined);
734
- }
735
- resolved = connectionStrategy
736
- ? await connectionStrategy.resolve([])
737
- : { clients: [], toolsChanged: false };
738
- mcpClients = resolved.clients;
739
- // Same snapshot as `mcpClients` above — paired so the pipeline context's
740
- // `mcpClientDescriptors` never drifts relative to `mcpClients` across a
741
- // reconnect (both refresh paths zip them by index at point-of-use).
742
- mcpClientDescriptors = resolved.clientDescriptors;
743
- // ---- Single listTools() pass, settled per client -------------------
744
- // Preserve the ORIGINAL client index on a partial failure: build the
745
- // per-client input index-preservingly (flatMap over the settled array
746
- // aligned by position), NOT via filter().map() — a middle-client
747
- // failure must not shift later slots' slotIndex.
748
- const descs = resolved.clientDescriptors ??
749
- mcpClients.map((_, i) => ({ slotIndex: i }));
750
- const settled = await Promise.all(mcpClients.map(async (client) => {
776
+ try {
777
+ if (this._mcpServers) {
778
+ if (this._mcpClients)
779
+ throw new Error('withMcpServers and withMcpClients are mutually exclusive: pass clients you already hold, or servers this builder should start');
780
+ // A partly-filled descriptor set is a caller bug: dropping it would
781
+ // re-namespace every tool from its label to `s${slotIndex}`. Check
782
+ // before starting anything.
783
+ const descriptors = collectServerDescriptors(this._mcpServers, 'withMcpServers');
784
+ // Caller-provided servers: start each, keep its stop() for handle.close().
785
+ // Auto-connect and vectorization are skipped, exactly as on the
786
+ // `withMcpClients` branch.
787
+ const started = [];
751
788
  try {
752
- const result = await client.listTools();
753
- return result.ok
754
- ? { ok: true, value: result.value }
755
- : { ok: false };
789
+ for (const server of this._mcpServers) {
790
+ started.push(await server.start());
791
+ closeFns.push(() => server.stop());
792
+ }
756
793
  }
757
- catch {
758
- return { ok: false };
794
+ catch (err) {
795
+ // Stop what did start, so a failure half-way leaves no live child.
796
+ await stopAll(closeFns.splice(0));
797
+ throw err;
759
798
  }
760
- }));
761
- let clientFailures = 0;
762
- let total = 0;
763
- const perClient = settled.flatMap((entry, i) => {
764
- if (!entry.ok) {
765
- clientFailures++;
766
- return [];
799
+ mcpClients = started;
800
+ mcpClientDescriptors = descriptors;
801
+ }
802
+ else if (this._mcpClients) {
803
+ // Caller-provided clients: skip auto-connect and vectorization
804
+ mcpClients = this._mcpClients;
805
+ }
806
+ else {
807
+ // YAML `mcp:` → route connection through an IMcpConnectionStrategy
808
+ // (build-on-components). Default to a resilient `makeConnectionStrategy`
809
+ // (connect + periodic reconnect + readiness via IReadinessReporter) unless
810
+ // the consumer injected their own. The strategy OWNS lifecycle: its initial
811
+ // `resolve([])` connects the targets and the agent resolves through it each
812
+ // iteration; disposal is `connectionStrategy.dispose()` (no per-client
813
+ // closeFns). `BuilderMcpConfig` is structurally `McpConnectionConfig`.
814
+ const mcpConfigs = prepareMcpConfigs(this.cfg.mcp, this._mcpRequestHeadersStrategy);
815
+ if (mcpConfigs.length > 0 && !connectionStrategy) {
816
+ connectionStrategy = makeConnectionStrategy(mcpConfigs, log ? { logger: log } : undefined);
767
817
  }
768
- total += entry.value.length;
769
- return [
770
- {
771
- slotIndex: descs[i]?.slotIndex ?? i,
772
- label: descs[i]?.label,
773
- client: mcpClients[i],
774
- tools: entry.value,
818
+ resolved = connectionStrategy
819
+ ? await connectionStrategy.resolve([])
820
+ : { clients: [], toolsChanged: false };
821
+ mcpClients = resolved.clients;
822
+ // Same snapshot as `mcpClients` above — paired so the pipeline context's
823
+ // `mcpClientDescriptors` never drifts relative to `mcpClients` across a
824
+ // reconnect (both refresh paths zip them by index at point-of-use).
825
+ mcpClientDescriptors = resolved.clientDescriptors;
826
+ // ---- Single listTools() pass, settled per client -------------------
827
+ // Preserve the ORIGINAL client index on a partial failure: build the
828
+ // per-client input index-preservingly (flatMap over the settled array
829
+ // aligned by position), NOT via filter().map() — a middle-client
830
+ // failure must not shift later slots' slotIndex.
831
+ const descs = resolved.clientDescriptors ??
832
+ mcpClients.map((_, i) => ({ slotIndex: i }));
833
+ const settled = await Promise.all(mcpClients.map(async (client) => {
834
+ try {
835
+ const result = await client.listTools();
836
+ return result.ok
837
+ ? { ok: true, value: result.value }
838
+ : { ok: false };
839
+ }
840
+ catch {
841
+ return { ok: false };
842
+ }
843
+ }));
844
+ let clientFailures = 0;
845
+ let total = 0;
846
+ const perClient = settled.flatMap((entry, i) => {
847
+ if (!entry.ok) {
848
+ clientFailures++;
849
+ return [];
850
+ }
851
+ total += entry.value.length;
852
+ return [
853
+ {
854
+ slotIndex: descs[i]?.slotIndex ?? i,
855
+ label: descs[i]?.label,
856
+ client: mcpClients[i],
857
+ tools: entry.value,
858
+ },
859
+ ];
860
+ });
861
+ const built = buildNamespacedTools(perClient, this._toolNamespace ?? defaultToolNamespace);
862
+ namespacedTools = built.tools;
863
+ toolProvenance = built.provenance;
864
+ const toolSummary = await vectorizeMcpTools(mcpClients, toolsRag, requestLogger, log, this._toolRecordKey, undefined, {
865
+ descriptors: mcpClientDescriptors,
866
+ configuredSlotCount: resolved.configuredSlotCount,
867
+ toolNamespace: this._toolNamespace ?? defaultToolNamespace,
868
+ prebuiltView: {
869
+ tools: namespacedTools,
870
+ provenance: toolProvenance,
871
+ clientFailures,
872
+ total,
775
873
  },
776
- ];
777
- });
778
- const built = buildNamespacedTools(perClient, this._toolNamespace ?? defaultToolNamespace);
779
- namespacedTools = built.tools;
780
- toolProvenance = built.provenance;
781
- const toolSummary = await vectorizeMcpTools(mcpClients, toolsRag, requestLogger, log, this._toolRecordKey, undefined, {
782
- descriptors: mcpClientDescriptors,
783
- configuredSlotCount: resolved.configuredSlotCount,
784
- toolNamespace: this._toolNamespace ?? defaultToolNamespace,
785
- prebuiltView: {
786
- tools: namespacedTools,
787
- provenance: toolProvenance,
788
- clientFailures,
789
- total,
790
- },
791
- });
792
- // Published only when defined: a skipped run must leave the holder empty
793
- // rather than storing a zeroed summary. Computing the snapshot above is
794
- // unconditional; publishing a catalog status is NOT — it still happens
795
- // ONLY through this guard, exactly as before (no writable store → no
796
- // publish, even though the snapshot now always exists).
797
- if (toolSummary)
798
- toolCatalogStatus.publish(toolSummary);
799
- }
800
- // ---- SmartAgent Config ------------------------------------------------
801
- const agentCfg = {
802
- maxIterations: 10,
803
- maxToolCalls: 30,
804
- ragQueryK: 10,
805
- ragTranslatePrompt: this.cfg.prompts?.ragTranslate,
806
- historySummaryPrompt: this.cfg.prompts?.historySummary,
807
- historyAutoSummarizeLimit: this.cfg.agent?.historyAutoSummarizeLimit,
808
- ...this.cfg.agent,
809
- ...(this.cfg.sessionPolicy
810
- ? { sessionPolicy: this.cfg.sessionPolicy }
811
- : {}),
812
- // Fluent overrides take precedence over cfg.agent
813
- ...this._agentOverrides,
814
- onBeforeStream: this._onBeforeStream,
815
- };
816
- // ---- Retry wrapping (outside circuit breaker) ----------------------------
817
- // Enable retry by default with sensible defaults; explicit config overrides.
818
- const retryOpts = agentCfg.retry ?? {
819
- maxAttempts: 3,
820
- backoffMs: 2000,
821
- retryOn: [429, 500, 502, 503],
822
- retryOnMidStream: [],
823
- };
824
- wrappedMainLlm = new RetryLlm(wrappedMainLlm, retryOpts);
825
- // ---- Rate limiter wrapping (outermost — retry attempts also throttled) ----
826
- if (this._rateLimiter) {
827
- wrappedMainLlm = new RateLimiterLlm(wrappedMainLlm, this._rateLimiter);
828
- }
829
- // ---- Classifier -------------------------------------------------------
830
- const classifierCfg = {};
831
- if (this.cfg.prompts?.classifier)
832
- classifierCfg.systemPrompt = this.cfg.prompts.classifier;
833
- const classifier = this._classifier ??
834
- new LlmClassifier(classifierLlm, classifierCfg, requestLogger);
835
- // ---- Assembler --------------------------------------------------------
836
- const assemblerCfg = {
837
- maxTokens: agentCfg.contextBudgetTokens,
838
- showReasoning: agentCfg.showReasoning,
839
- reasoningInstruction: this.cfg.prompts?.reasoning,
840
- historyRecencyWindow: agentCfg.historyRecencyWindow,
841
- };
842
- if (this.cfg.prompts?.system)
843
- assemblerCfg.systemPromptPreamble = this.cfg.prompts.system;
844
- const assembler = this._assembler ?? new ContextAssembler(assemblerCfg);
845
- // ---- History memory & summarizer ----------------------------------------
846
- let historyMemory;
847
- let historySummarizer;
848
- if (agentCfg.semanticHistoryEnabled) {
849
- historyMemory =
850
- this._historyMemory ??
851
- new HistoryMemory({
852
- maxSize: agentCfg.historyRecencyWindow ?? 3,
853
- });
854
- const summarizerLlm = this._helperLlm ?? mainLlm;
855
- historySummarizer =
856
- this._historySummarizer ??
857
- new HistorySummarizer(summarizerLlm, agentCfg.historyTurnSummaryPrompt
858
- ? { prompt: agentCfg.historyTurnSummaryPrompt }
859
- : undefined);
860
- if (historyRag && !ragRegistry.get('history')) {
861
- ragRegistry.register('history', historyRag, undefined, {
862
- displayName: 'history',
863
- scope: 'global',
864
874
  });
865
- // ragStores projection updates via mutation listener.
866
- }
867
- }
868
- // ---- Plugin loader (optional) -------------------------------------------
869
- let loadedPlugins;
870
- if (this._pluginLoader) {
871
- const plugins = await this._pluginLoader.load();
872
- loadedPlugins = plugins;
873
- if (plugins.reranker && !this._reranker) {
874
- this._reranker = plugins.reranker;
875
+ // Published only when defined: a skipped run must leave the holder empty
876
+ // rather than storing a zeroed summary. Computing the snapshot above is
877
+ // unconditional; publishing a catalog status is NOT — it still happens
878
+ // ONLY through this guard, exactly as before (no writable store → no
879
+ // publish, even though the snapshot now always exists).
880
+ if (toolSummary)
881
+ toolCatalogStatus.publish(toolSummary);
875
882
  }
876
- if (plugins.queryExpander && !this._queryExpander) {
877
- this._queryExpander = plugins.queryExpander;
883
+ // ---- SmartAgent Config ------------------------------------------------
884
+ const agentCfg = {
885
+ maxIterations: 10,
886
+ maxToolCalls: 30,
887
+ ragQueryK: 10,
888
+ ragTranslatePrompt: this.cfg.prompts?.ragTranslate,
889
+ historySummaryPrompt: this.cfg.prompts?.historySummary,
890
+ historyAutoSummarizeLimit: this.cfg.agent?.historyAutoSummarizeLimit,
891
+ ...this.cfg.agent,
892
+ ...(this.cfg.sessionPolicy
893
+ ? { sessionPolicy: this.cfg.sessionPolicy }
894
+ : {}),
895
+ // Fluent overrides take precedence over cfg.agent
896
+ ...this._agentOverrides,
897
+ onBeforeStream: this._onBeforeStream,
898
+ };
899
+ // ---- Retry wrapping (outside circuit breaker) ----------------------------
900
+ // Enable retry by default with sensible defaults; explicit config overrides.
901
+ const retryOpts = agentCfg.retry ?? {
902
+ maxAttempts: 3,
903
+ backoffMs: 2000,
904
+ retryOn: [429, 500, 502, 503],
905
+ retryOnMidStream: [],
906
+ };
907
+ wrappedMainLlm = new RetryLlm(wrappedMainLlm, retryOpts);
908
+ // ---- Rate limiter wrapping (outermost — retry attempts also throttled) ----
909
+ if (this._rateLimiter) {
910
+ wrappedMainLlm = new RateLimiterLlm(wrappedMainLlm, this._rateLimiter);
878
911
  }
879
- if (plugins.outputValidator && !this._outputValidator) {
880
- this._outputValidator = plugins.outputValidator;
912
+ // ---- Classifier -------------------------------------------------------
913
+ const classifierCfg = {};
914
+ if (this.cfg.prompts?.classifier)
915
+ classifierCfg.systemPrompt = this.cfg.prompts.classifier;
916
+ const classifier = this._classifier ??
917
+ new LlmClassifier(classifierLlm, classifierCfg, requestLogger);
918
+ // ---- Assembler --------------------------------------------------------
919
+ const assemblerCfg = {
920
+ maxTokens: agentCfg.contextBudgetTokens,
921
+ showReasoning: agentCfg.showReasoning,
922
+ reasoningInstruction: this.cfg.prompts?.reasoning,
923
+ historyRecencyWindow: agentCfg.historyRecencyWindow,
924
+ };
925
+ if (this.cfg.prompts?.system)
926
+ assemblerCfg.systemPromptPreamble = this.cfg.prompts.system;
927
+ const assembler = this._assembler ?? new ContextAssembler(assemblerCfg);
928
+ // ---- History memory & summarizer ----------------------------------------
929
+ let historyMemory;
930
+ let historySummarizer;
931
+ if (agentCfg.semanticHistoryEnabled) {
932
+ historyMemory =
933
+ this._historyMemory ??
934
+ new HistoryMemory({
935
+ maxSize: agentCfg.historyRecencyWindow ?? 3,
936
+ });
937
+ const summarizerLlm = this._helperLlm ?? mainLlm;
938
+ historySummarizer =
939
+ this._historySummarizer ??
940
+ new HistorySummarizer(summarizerLlm, agentCfg.historyTurnSummaryPrompt
941
+ ? { prompt: agentCfg.historyTurnSummaryPrompt }
942
+ : undefined);
943
+ if (historyRag && !ragRegistry.get('history', 'global')) {
944
+ ragRegistry.register('history', historyRag, undefined, {
945
+ displayName: 'history',
946
+ scope: 'global',
947
+ });
948
+ // ragStores projection updates via mutation listener.
949
+ }
881
950
  }
882
- if (plugins.skillManager && !this._skillManager) {
883
- this._skillManager = plugins.skillManager;
951
+ // ---- Plugin loader (optional) -------------------------------------------
952
+ let loadedPlugins;
953
+ if (this._pluginLoader) {
954
+ const plugins = await this._pluginLoader.load();
955
+ loadedPlugins = plugins;
956
+ if (plugins.reranker && !this._reranker) {
957
+ this._reranker = plugins.reranker;
958
+ }
959
+ if (plugins.queryExpander && !this._queryExpander) {
960
+ this._queryExpander = plugins.queryExpander;
961
+ }
962
+ if (plugins.outputValidator && !this._outputValidator) {
963
+ this._outputValidator = plugins.outputValidator;
964
+ }
965
+ if (plugins.skillManager && !this._skillManager) {
966
+ this._skillManager = plugins.skillManager;
967
+ }
968
+ if (plugins.clientAdapters.length > 0) {
969
+ this._clientAdapters.push(...plugins.clientAdapters);
970
+ }
884
971
  }
885
- if (plugins.clientAdapters.length > 0) {
886
- this._clientAdapters.push(...plugins.clientAdapters);
972
+ // ---- Skill vectorization (optional) ------------------------------------
973
+ if (this._skillManager && toolsRag) {
974
+ await vectorizeSkills(this._skillManager, toolsRag, requestLogger, log);
887
975
  }
888
- }
889
- // ---- Skill vectorization (optional) ------------------------------------
890
- if (this._skillManager && toolsRag) {
891
- await vectorizeSkills(this._skillManager, toolsRag, requestLogger, log);
892
- }
893
- // ---- Pipeline initialization -------------------------------------------
894
- let resolvedCoordinator;
895
- if (this._coordinator) {
896
- const plannerLlm = this._coordinator.plannerLlm ?? wrappedMainLlm;
897
- if (!plannerLlm) {
898
- throw new Error('withCoordinator: requires either cfg.plannerLlm or withMainLlm() to be called');
976
+ // ---- Pipeline initialization -------------------------------------------
977
+ let resolvedCoordinator;
978
+ if (this._coordinator) {
979
+ const plannerLlm = this._coordinator.plannerLlm ?? wrappedMainLlm;
980
+ if (!plannerLlm) {
981
+ throw new Error('withCoordinator: requires either cfg.plannerLlm or withMainLlm() to be called');
982
+ }
983
+ // Construct a default context builder from this builder's available RAG
984
+ // + embedder resources. `toolSource` comes from the toolsRag the parent
985
+ // already uses for tool-loop retrieval. `projectSource` is left unset
986
+ // until a dedicated project/domain RAG slot is exposed on the builder.
987
+ const toolSource = this.buildRetrievalSource(toolsRag, this._embedder);
988
+ const defaultContextBuilder = new DefaultSubAgentContextBuilder({
989
+ toolSource,
990
+ });
991
+ resolvedCoordinator = {
992
+ ...this._coordinator,
993
+ planning: this._coordinator.planning ?? new OneShotPlanning(plannerLlm),
994
+ dispatch: this._coordinator.dispatch ??
995
+ new HybridDispatch(new SubAgentDispatch(defaultContextBuilder), new SelfDispatch(plannerLlm)),
996
+ };
899
997
  }
900
- // Construct a default context builder from this builder's available RAG
901
- // + embedder resources. `toolSource` comes from the toolsRag the parent
902
- // already uses for tool-loop retrieval. `projectSource` is left unset
903
- // until a dedicated project/domain RAG slot is exposed on the builder.
904
- const toolSource = this.buildRetrievalSource(toolsRag, this._embedder);
905
- const defaultContextBuilder = new DefaultSubAgentContextBuilder({
906
- toolSource,
907
- });
908
- resolvedCoordinator = {
909
- ...this._coordinator,
910
- planning: this._coordinator.planning ?? new OneShotPlanning(plannerLlm),
911
- dispatch: this._coordinator.dispatch ??
912
- new HybridDispatch(new SubAgentDispatch(defaultContextBuilder), new SelfDispatch(plannerLlm)),
913
- };
914
- }
915
- const pipeline = this._pipeline ??
916
- new DefaultPipeline({
917
- subAgents: this._subAgents,
918
- coordinator: resolvedCoordinator,
919
- dagCoordinator: this._dagCoordinator,
920
- stepperCoordinator: this._stepperCoordinator,
998
+ const pipeline = this._pipeline ??
999
+ new DefaultPipeline({
1000
+ subAgents: this._subAgents,
1001
+ coordinator: resolvedCoordinator,
1002
+ dagCoordinator: this._dagCoordinator,
1003
+ stepperCoordinator: this._stepperCoordinator,
1004
+ });
1005
+ pipeline.initialize({
1006
+ mainLlm: wrappedMainLlm,
1007
+ helperLlm,
1008
+ classifierLlm,
1009
+ classifier,
1010
+ assembler,
1011
+ mcpClients,
1012
+ mcpClientDescriptors,
1013
+ toolNamespace: this._toolNamespace,
1014
+ toolsRag,
1015
+ historyRag,
1016
+ ragStores,
1017
+ ragRegistry,
1018
+ ragProviderRegistry,
1019
+ embedder: this._embedder,
1020
+ toolSelectionStrategy: this._toolSelectionStrategy,
1021
+ reranker: this._reranker,
1022
+ queryExpander: this._queryExpander,
1023
+ toolPolicy: this._toolPolicy,
1024
+ injectionDetector: this._injectionDetector,
1025
+ toolCache: this._toolCache,
1026
+ outputValidator: this._outputValidator,
1027
+ sessionManager: this._sessionManager,
1028
+ skillManager: this._skillManager,
1029
+ logger: log,
1030
+ requestLogger,
1031
+ tracer: this._tracer,
1032
+ metrics: this._metrics,
1033
+ historyMemory,
1034
+ historySummarizer,
1035
+ llmCallStrategy: this._llmCallStrategy,
1036
+ agentConfig: agentCfg,
1037
+ ...(this._mcpFailureClassifier
1038
+ ? { mcpFailureClassifier: this._mcpFailureClassifier }
1039
+ : {}),
1040
+ ...(this._toolLoopContextStrategyFactory
1041
+ ? {
1042
+ toolLoopContextStrategyFactory: this._toolLoopContextStrategyFactory,
1043
+ }
1044
+ : {}),
921
1045
  });
922
- pipeline.initialize({
923
- mainLlm: wrappedMainLlm,
924
- helperLlm,
925
- classifierLlm,
926
- classifier,
927
- assembler,
928
- mcpClients,
929
- mcpClientDescriptors,
930
- toolNamespace: this._toolNamespace,
931
- toolsRag,
932
- historyRag,
933
- ragStores,
934
- ragRegistry,
935
- ragProviderRegistry,
936
- embedder: this._embedder,
937
- toolSelectionStrategy: this._toolSelectionStrategy,
938
- reranker: this._reranker,
939
- queryExpander: this._queryExpander,
940
- toolPolicy: this._toolPolicy,
941
- injectionDetector: this._injectionDetector,
942
- toolCache: this._toolCache,
943
- outputValidator: this._outputValidator,
944
- sessionManager: this._sessionManager,
945
- skillManager: this._skillManager,
946
- logger: log,
947
- requestLogger,
948
- tracer: this._tracer,
949
- metrics: this._metrics,
950
- historyMemory,
951
- historySummarizer,
952
- llmCallStrategy: this._llmCallStrategy,
953
- agentConfig: agentCfg,
954
- ...(this._mcpFailureClassifier
955
- ? { mcpFailureClassifier: this._mcpFailureClassifier }
956
- : {}),
957
- ...(this._toolLoopContextStrategyFactory
958
- ? {
959
- toolLoopContextStrategyFactory: this._toolLoopContextStrategyFactory,
1046
+ const agent = new SmartAgent({
1047
+ mainLlm: wrappedMainLlm,
1048
+ helperLlm: this._helperLlm,
1049
+ mcpClients,
1050
+ ragStores,
1051
+ ragRegistry,
1052
+ ragProviderRegistry,
1053
+ classifier,
1054
+ classifierLlm,
1055
+ classifierConfig: classifierCfg,
1056
+ assembler,
1057
+ pipeline,
1058
+ ...(log ? { logger: log } : {}),
1059
+ ...(this._toolPolicy ? { toolPolicy: this._toolPolicy } : {}),
1060
+ ...(this._injectionDetector
1061
+ ? { injectionDetector: this._injectionDetector }
1062
+ : {}),
1063
+ ...(this._reranker ? { reranker: this._reranker } : {}),
1064
+ ...(this._queryExpander
1065
+ ? { queryExpander: this._queryExpander }
1066
+ : {}),
1067
+ ...(this._tracer ? { tracer: this._tracer } : {}),
1068
+ ...(this._metrics ? { metrics: this._metrics } : {}),
1069
+ ...(this._toolCache ? { toolCache: this._toolCache } : {}),
1070
+ ...(this._outputValidator
1071
+ ? { outputValidator: this._outputValidator }
1072
+ : {}),
1073
+ ...(this._sessionManager
1074
+ ? { sessionManager: this._sessionManager }
1075
+ : {}),
1076
+ ...(this._skillManager ? { skillManager: this._skillManager } : {}),
1077
+ ...(this._clientAdapters.length > 0
1078
+ ? { clientAdapters: this._clientAdapters }
1079
+ : {}),
1080
+ ...(this._embedder ? { embedder: this._embedder } : {}),
1081
+ ...(connectionStrategy ? { connectionStrategy } : {}),
1082
+ toolCatalogStatus,
1083
+ ...(this._toolRecordKey
1084
+ ? { toolRecordKey: this._toolRecordKey }
1085
+ : {}),
1086
+ ...(this._toolNamespace
1087
+ ? { toolNamespace: this._toolNamespace }
1088
+ : {}),
1089
+ ...(historyMemory ? { historyMemory } : {}),
1090
+ ...(historySummarizer ? { historySummarizer } : {}),
1091
+ ...(this._llmCallStrategy
1092
+ ? { llmCallStrategy: this._llmCallStrategy }
1093
+ : {}),
1094
+ ...(translateQueryStores.size > 0 ? { translateQueryStores } : {}),
1095
+ ...(this._mcpFailureClassifier
1096
+ ? { mcpFailureClassifier: this._mcpFailureClassifier }
1097
+ : {}),
1098
+ ...(this._toolLoopContextStrategyFactory
1099
+ ? {
1100
+ toolLoopContextStrategyFactory: this._toolLoopContextStrategyFactory,
1101
+ }
1102
+ : {}),
1103
+ requestLogger,
1104
+ }, agentCfg);
1105
+ // ---- Model provider auto-detection ------------------------------------
1106
+ let modelProvider = this._modelProvider;
1107
+ if (!modelProvider) {
1108
+ const candidate = mainLlm;
1109
+ if (isModelProvider(candidate)) {
1110
+ modelProvider = candidate;
960
1111
  }
961
- : {}),
962
- });
963
- const agent = new SmartAgent({
964
- mainLlm: wrappedMainLlm,
965
- helperLlm: this._helperLlm,
966
- mcpClients,
967
- ragStores,
968
- ragRegistry,
969
- ragProviderRegistry,
970
- classifier,
971
- classifierLlm,
972
- classifierConfig: classifierCfg,
973
- assembler,
974
- pipeline,
975
- ...(log ? { logger: log } : {}),
976
- ...(this._toolPolicy ? { toolPolicy: this._toolPolicy } : {}),
977
- ...(this._injectionDetector
978
- ? { injectionDetector: this._injectionDetector }
979
- : {}),
980
- ...(this._reranker ? { reranker: this._reranker } : {}),
981
- ...(this._queryExpander ? { queryExpander: this._queryExpander } : {}),
982
- ...(this._tracer ? { tracer: this._tracer } : {}),
983
- ...(this._metrics ? { metrics: this._metrics } : {}),
984
- ...(this._toolCache ? { toolCache: this._toolCache } : {}),
985
- ...(this._outputValidator
986
- ? { outputValidator: this._outputValidator }
987
- : {}),
988
- ...(this._sessionManager
989
- ? { sessionManager: this._sessionManager }
990
- : {}),
991
- ...(this._skillManager ? { skillManager: this._skillManager } : {}),
992
- ...(this._clientAdapters.length > 0
993
- ? { clientAdapters: this._clientAdapters }
994
- : {}),
995
- ...(this._embedder ? { embedder: this._embedder } : {}),
996
- ...(connectionStrategy ? { connectionStrategy } : {}),
997
- toolCatalogStatus,
998
- ...(this._toolRecordKey ? { toolRecordKey: this._toolRecordKey } : {}),
999
- ...(this._toolNamespace ? { toolNamespace: this._toolNamespace } : {}),
1000
- ...(historyMemory ? { historyMemory } : {}),
1001
- ...(historySummarizer ? { historySummarizer } : {}),
1002
- ...(this._llmCallStrategy
1003
- ? { llmCallStrategy: this._llmCallStrategy }
1004
- : {}),
1005
- ...(translateQueryStores.size > 0 ? { translateQueryStores } : {}),
1006
- ...(this._mcpFailureClassifier
1007
- ? { mcpFailureClassifier: this._mcpFailureClassifier }
1008
- : {}),
1009
- ...(this._toolLoopContextStrategyFactory
1010
- ? {
1011
- toolLoopContextStrategyFactory: this._toolLoopContextStrategyFactory,
1112
+ }
1113
+ // ---- API adapters: merge plugin adapters → builder adapters (builder wins) ---
1114
+ // plugins.apiAdapters does not exist yet (added in Task 4); safe future-compat check.
1115
+ const apiAdapters = new Map();
1116
+ const pluginApiAdapters = loadedPlugins?.apiAdapters;
1117
+ if (pluginApiAdapters) {
1118
+ for (const [name, adapter] of pluginApiAdapters) {
1119
+ apiAdapters.set(name, adapter);
1012
1120
  }
1013
- : {}),
1014
- requestLogger,
1015
- }, agentCfg);
1016
- // ---- Model provider auto-detection ------------------------------------
1017
- let modelProvider = this._modelProvider;
1018
- if (!modelProvider) {
1019
- const candidate = mainLlm;
1020
- if (isModelProvider(candidate)) {
1021
- modelProvider = candidate;
1022
1121
  }
1023
- }
1024
- // ---- API adapters: merge plugin adapters → builder adapters (builder wins) ---
1025
- // plugins.apiAdapters does not exist yet (added in Task 4); safe future-compat check.
1026
- const apiAdapters = new Map();
1027
- const pluginApiAdapters = loadedPlugins?.apiAdapters;
1028
- if (pluginApiAdapters) {
1029
- for (const [name, adapter] of pluginApiAdapters) {
1122
+ for (const [name, adapter] of this._apiAdapters) {
1030
1123
  apiAdapters.set(name, adapter);
1031
1124
  }
1125
+ return {
1126
+ agent,
1127
+ chat: (messages, tools, options) => agent.currentMainLlm.chat(messages, tools, options),
1128
+ streamChat: (messages, tools, options) => agent.currentMainLlm.streamChat(messages, tools, options),
1129
+ requestLogger,
1130
+ close: async () => {
1131
+ // Best-effort, like every other teardown routine in this codebase
1132
+ // (SessionGraph.dispose(), SessionGraphFactory's per-stop loop): a
1133
+ // failure in ONE resource must not stop the rest from being torn
1134
+ // down, and close() itself never throws — it is routinely awaited
1135
+ // in a `finally` block, where a raised error would either mask
1136
+ // whatever the `try` block was doing or force every caller to wrap
1137
+ // a routine cleanup call in its own try/catch. Failures are never
1138
+ // swallowed SILENTLY, though: both surface through `log` (or
1139
+ // console.warn without one), the same pattern used everywhere else
1140
+ // teardown failures are reported in this branch.
1141
+ const warn = (message) => {
1142
+ if (log) {
1143
+ log.log({ type: 'warning', traceId: 'builder', message });
1144
+ }
1145
+ else {
1146
+ console.warn(`[builder] ${message}`);
1147
+ }
1148
+ };
1149
+ try {
1150
+ await connectionStrategy?.dispose?.();
1151
+ }
1152
+ catch (err) {
1153
+ const message = err instanceof Error ? err.message : String(err);
1154
+ warn(`close_connection_strategy_dispose_failed: ${message}`);
1155
+ }
1156
+ await stopAll(closeFns, (err) => {
1157
+ const message = err instanceof Error ? err.message : String(err);
1158
+ warn(`close_mcp_stop_failed: ${message}`);
1159
+ });
1160
+ },
1161
+ circuitBreakers,
1162
+ ragStores,
1163
+ ragRegistry,
1164
+ mcpClients,
1165
+ modelProvider,
1166
+ getApiAdapter: (name) => apiAdapters.get(name),
1167
+ listApiAdapters: () => [...apiAdapters.keys()],
1168
+ // Absent (not just undefined-valued) on the caller-provided-`mcpClients`
1169
+ // branch, where `resolved` is undefined and namespacing was never
1170
+ // computed — matches the SmartAgentHandle field docs.
1171
+ ...(resolved
1172
+ ? {
1173
+ namespacedTools,
1174
+ toolProvenance,
1175
+ mcpClientDescriptors: resolved.clientDescriptors,
1176
+ configuredSlotCount: resolved.configuredSlotCount,
1177
+ }
1178
+ : {}),
1179
+ };
1032
1180
  }
1033
- for (const [name, adapter] of this._apiAdapters) {
1034
- apiAdapters.set(name, adapter);
1181
+ catch (err) {
1182
+ // Everything from here down (model validation was already done above)
1183
+ // can throw before a handle exists to own `close()`: stop every server
1184
+ // already registered in `closeFns` — via `withMcpServers` — on the
1185
+ // ORIGINAL failure, then let that failure through unchanged.
1186
+ await stopAll(closeFns);
1187
+ throw err;
1035
1188
  }
1036
- return {
1037
- agent,
1038
- chat: (messages, tools, options) => agent.currentMainLlm.chat(messages, tools, options),
1039
- streamChat: (messages, tools, options) => agent.currentMainLlm.streamChat(messages, tools, options),
1040
- requestLogger,
1041
- close: async () => {
1042
- await connectionStrategy?.dispose?.();
1043
- for (const fn of closeFns)
1044
- await fn();
1045
- },
1046
- circuitBreakers,
1047
- ragStores,
1048
- ragRegistry,
1049
- mcpClients,
1050
- modelProvider,
1051
- getApiAdapter: (name) => apiAdapters.get(name),
1052
- listApiAdapters: () => [...apiAdapters.keys()],
1053
- // Absent (not just undefined-valued) on the caller-provided-`mcpClients`
1054
- // branch, where `resolved` is undefined and namespacing was never
1055
- // computed — matches the SmartAgentHandle field docs.
1056
- ...(resolved
1057
- ? {
1058
- namespacedTools,
1059
- toolProvenance,
1060
- mcpClientDescriptors: resolved.clientDescriptors,
1061
- configuredSlotCount: resolved.configuredSlotCount,
1062
- }
1063
- : {}),
1064
- };
1065
1189
  }
1066
1190
  }
1067
1191
  //# sourceMappingURL=builder.js.map