@mcp-abap-adt/llm-agent-server-libs 19.2.0 → 20.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builders/controller-skill-pipeline-builder.d.ts +57 -0
- package/dist/builders/controller-skill-pipeline-builder.d.ts.map +1 -0
- package/dist/builders/controller-skill-pipeline-builder.js +175 -0
- package/dist/builders/controller-skill-pipeline-builder.js.map +1 -0
- package/dist/factories/controller-factory.d.ts +2 -2
- package/dist/factories/controller-factory.d.ts.map +1 -1
- package/dist/factories/controller-factory.js +2 -1
- package/dist/factories/controller-factory.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pipelines/controller.d.ts +4 -2
- package/dist/pipelines/controller.d.ts.map +1 -1
- package/dist/pipelines/controller.js +33 -8
- package/dist/pipelines/controller.js.map +1 -1
- package/dist/pipelines/coordinator-resolvers.d.ts +68 -0
- package/dist/pipelines/coordinator-resolvers.d.ts.map +1 -0
- package/dist/pipelines/coordinator-resolvers.js +97 -0
- package/dist/pipelines/coordinator-resolvers.js.map +1 -0
- package/dist/pipelines/dag.d.ts +2 -2
- package/dist/pipelines/dag.js +2 -2
- package/dist/pipelines/parsers.d.ts +1 -1
- package/dist/pipelines/parsers.d.ts.map +1 -1
- package/dist/pipelines/parsers.js +5 -4
- package/dist/pipelines/parsers.js.map +1 -1
- package/dist/pipelines/register-skill-sources.d.ts.map +1 -1
- package/dist/pipelines/register-skill-sources.js +5 -0
- package/dist/pipelines/register-skill-sources.js.map +1 -1
- package/dist/pipelines/stepper.d.ts +2 -2
- package/dist/pipelines/stepper.js +2 -2
- package/dist/smart-agent/build-stepper-root.d.ts +3 -2
- package/dist/smart-agent/build-stepper-root.d.ts.map +1 -1
- package/dist/smart-agent/build-stepper-root.js +2 -1
- package/dist/smart-agent/build-stepper-root.js.map +1 -1
- package/dist/smart-agent/config-reload-watcher.d.ts +30 -0
- package/dist/smart-agent/config-reload-watcher.d.ts.map +1 -0
- package/dist/smart-agent/config-reload-watcher.js +101 -0
- package/dist/smart-agent/config-reload-watcher.js.map +1 -0
- package/dist/smart-agent/config-validator.d.ts +21 -0
- package/dist/smart-agent/config-validator.d.ts.map +1 -0
- package/dist/smart-agent/config-validator.js +196 -0
- package/dist/smart-agent/config-validator.js.map +1 -0
- package/dist/smart-agent/config.d.ts +16 -254
- package/dist/smart-agent/config.d.ts.map +1 -1
- package/dist/smart-agent/config.js +17 -904
- package/dist/smart-agent/config.js.map +1 -1
- package/dist/smart-agent/controller/board.d.ts +6 -3
- package/dist/smart-agent/controller/board.d.ts.map +1 -1
- package/dist/smart-agent/controller/board.js +21 -0
- package/dist/smart-agent/controller/board.js.map +1 -1
- package/dist/smart-agent/controller/controller-coordinator-handler.d.ts +16 -50
- package/dist/smart-agent/controller/controller-coordinator-handler.d.ts.map +1 -1
- package/dist/smart-agent/controller/controller-coordinator-handler.js +83 -371
- package/dist/smart-agent/controller/controller-coordinator-handler.js.map +1 -1
- package/dist/smart-agent/controller/finalizer.d.ts +5 -0
- package/dist/smart-agent/controller/finalizer.d.ts.map +1 -1
- package/dist/smart-agent/controller/finalizer.js +12 -5
- package/dist/smart-agent/controller/finalizer.js.map +1 -1
- package/dist/smart-agent/controller/parser.d.ts +11 -0
- package/dist/smart-agent/controller/parser.d.ts.map +1 -0
- package/dist/smart-agent/controller/parser.js +77 -0
- package/dist/smart-agent/controller/parser.js.map +1 -0
- package/dist/smart-agent/controller/planner.d.ts +20 -16
- package/dist/smart-agent/controller/planner.d.ts.map +1 -1
- package/dist/smart-agent/controller/planner.js +58 -64
- package/dist/smart-agent/controller/planner.js.map +1 -1
- package/dist/smart-agent/controller/recall.d.ts +48 -0
- package/dist/smart-agent/controller/recall.d.ts.map +1 -0
- package/dist/smart-agent/controller/recall.js +196 -0
- package/dist/smart-agent/controller/recall.js.map +1 -0
- package/dist/smart-agent/controller/reviewer.d.ts.map +1 -1
- package/dist/smart-agent/controller/reviewer.js +1 -1
- package/dist/smart-agent/controller/reviewer.js.map +1 -1
- package/dist/smart-agent/controller/types.d.ts +8 -9
- package/dist/smart-agent/controller/types.d.ts.map +1 -1
- package/dist/smart-agent/controller/usage-logging.d.ts +16 -0
- package/dist/smart-agent/controller/usage-logging.d.ts.map +1 -0
- package/dist/smart-agent/controller/usage-logging.js +39 -0
- package/dist/smart-agent/controller/usage-logging.js.map +1 -0
- package/dist/smart-agent/http/adapter-route-handler.d.ts +13 -0
- package/dist/smart-agent/http/adapter-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/adapter-route-handler.js +76 -0
- package/dist/smart-agent/http/adapter-route-handler.js.map +1 -0
- package/dist/smart-agent/http/chat-route-handler.d.ts +15 -0
- package/dist/smart-agent/http/chat-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/chat-route-handler.js +327 -0
- package/dist/smart-agent/http/chat-route-handler.js.map +1 -0
- package/dist/smart-agent/http/config-route-handler.d.ts +21 -0
- package/dist/smart-agent/http/config-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/config-route-handler.js +149 -0
- package/dist/smart-agent/http/config-route-handler.js.map +1 -0
- package/dist/smart-agent/http/health-route-handler.d.ts +12 -0
- package/dist/smart-agent/http/health-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/health-route-handler.js +19 -0
- package/dist/smart-agent/http/health-route-handler.js.map +1 -0
- package/dist/smart-agent/http/models-route-handler.d.ts +17 -0
- package/dist/smart-agent/http/models-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/models-route-handler.js +65 -0
- package/dist/smart-agent/http/models-route-handler.js.map +1 -0
- package/dist/smart-agent/http/response-helpers.d.ts +31 -0
- package/dist/smart-agent/http/response-helpers.d.ts.map +1 -0
- package/dist/smart-agent/http/response-helpers.js +59 -0
- package/dist/smart-agent/http/response-helpers.js.map +1 -0
- package/dist/smart-agent/http/route-table.d.ts +44 -0
- package/dist/smart-agent/http/route-table.d.ts.map +1 -0
- package/dist/smart-agent/http/route-table.js +35 -0
- package/dist/smart-agent/http/route-table.js.map +1 -0
- package/dist/smart-agent/http/session-cookie.d.ts +10 -0
- package/dist/smart-agent/http/session-cookie.d.ts.map +1 -0
- package/dist/smart-agent/http/session-cookie.js +16 -0
- package/dist/smart-agent/http/session-cookie.js.map +1 -0
- package/dist/smart-agent/http/sessions-route-handler.d.ts +8 -0
- package/dist/smart-agent/http/sessions-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/sessions-route-handler.js +54 -0
- package/dist/smart-agent/http/sessions-route-handler.js.map +1 -0
- package/dist/smart-agent/http/usage-route-handler.d.ts +12 -0
- package/dist/smart-agent/http/usage-route-handler.d.ts.map +1 -0
- package/dist/smart-agent/http/usage-route-handler.js +28 -0
- package/dist/smart-agent/http/usage-route-handler.js.map +1 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts +13 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.d.ts.map +1 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.js +18 -0
- package/dist/smart-agent/knowledge/make-knowledge-backend.js.map +1 -0
- package/dist/smart-agent/llm/role-llm-resolver.d.ts +30 -0
- package/dist/smart-agent/llm/role-llm-resolver.d.ts.map +1 -0
- package/dist/smart-agent/llm/role-llm-resolver.js +44 -0
- package/dist/smart-agent/llm/role-llm-resolver.js.map +1 -0
- package/dist/smart-agent/llm-config-map.d.ts +38 -0
- package/dist/smart-agent/llm-config-map.d.ts.map +1 -0
- package/dist/smart-agent/llm-config-map.js +75 -0
- package/dist/smart-agent/llm-config-map.js.map +1 -0
- package/dist/smart-agent/mcp-readiness-monitor.d.ts +38 -0
- package/dist/smart-agent/mcp-readiness-monitor.d.ts.map +1 -0
- package/dist/smart-agent/mcp-readiness-monitor.js +81 -0
- package/dist/smart-agent/mcp-readiness-monitor.js.map +1 -0
- package/dist/smart-agent/mcp-readiness-registry.d.ts +37 -0
- package/dist/smart-agent/mcp-readiness-registry.d.ts.map +1 -0
- package/dist/smart-agent/mcp-readiness-registry.js +55 -0
- package/dist/smart-agent/mcp-readiness-registry.js.map +1 -0
- package/dist/smart-agent/resolve-config-sections.d.ts +15 -0
- package/dist/smart-agent/resolve-config-sections.d.ts.map +1 -0
- package/dist/smart-agent/resolve-config-sections.js +212 -0
- package/dist/smart-agent/resolve-config-sections.js.map +1 -0
- package/dist/smart-agent/session-lifecycle/index.d.ts +117 -0
- package/dist/smart-agent/session-lifecycle/index.d.ts.map +1 -0
- package/dist/smart-agent/session-lifecycle/index.js +152 -0
- package/dist/smart-agent/session-lifecycle/index.js.map +1 -0
- package/dist/smart-agent/skill-plugins-config.d.ts +9 -1
- package/dist/smart-agent/skill-plugins-config.d.ts.map +1 -1
- package/dist/smart-agent/skill-plugins-config.js +27 -1
- package/dist/smart-agent/skill-plugins-config.js.map +1 -1
- package/dist/smart-agent/skill-plugins-host-factory.d.ts +14 -2
- package/dist/smart-agent/skill-plugins-host-factory.d.ts.map +1 -1
- package/dist/smart-agent/skill-plugins-host-factory.js +16 -4
- package/dist/smart-agent/skill-plugins-host-factory.js.map +1 -1
- package/dist/smart-agent/smart-server.d.ts +151 -222
- package/dist/smart-agent/smart-server.d.ts.map +1 -1
- package/dist/smart-agent/smart-server.js +529 -1385
- package/dist/smart-agent/smart-server.js.map +1 -1
- package/dist/smart-agent/stepper-config.d.ts +138 -0
- package/dist/smart-agent/stepper-config.d.ts.map +1 -0
- package/dist/smart-agent/stepper-config.js +216 -0
- package/dist/smart-agent/stepper-config.js.map +1 -0
- package/dist/smart-agent/tools-rag-handle.d.ts +10 -0
- package/dist/smart-agent/tools-rag-handle.d.ts.map +1 -0
- package/dist/smart-agent/tools-rag-handle.js +76 -0
- package/dist/smart-agent/tools-rag-handle.js.map +1 -0
- package/dist/smart-agent/workers/worker-registry.d.ts +159 -0
- package/dist/smart-agent/workers/worker-registry.d.ts.map +1 -0
- package/dist/smart-agent/workers/worker-registry.js +203 -0
- package/dist/smart-agent/workers/worker-registry.js.map +1 -0
- package/dist/smart-agent/yaml-loader.d.ts +7 -0
- package/dist/smart-agent/yaml-loader.d.ts.map +1 -0
- package/dist/smart-agent/yaml-loader.js +146 -0
- package/dist/smart-agent/yaml-loader.js.map +1 -0
- package/package.json +7 -7
|
@@ -1,510 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared config utilities for SmartServer.
|
|
3
3
|
*/
|
|
4
|
-
import fs from 'node:fs';
|
|
5
4
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { assertNoLegacyPipelineConfig, validateResolvedConfig, } from './config-validator.js';
|
|
6
|
+
import { resolveAgentSection, resolveLlmSection, resolveMcpSection, resolvePipelineSelection, resolvePromptsSection, resolveRagSection, } from './resolve-config-sections.js';
|
|
8
7
|
import { parseSkillPluginsConfig } from './skill-plugins-config.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* detection silently misclassified those configs as a "map" shape.
|
|
15
|
-
*/
|
|
16
|
-
function isFlatLlmConfig(input) {
|
|
17
|
-
const flat = input;
|
|
18
|
-
return (typeof flat.provider === 'string' ||
|
|
19
|
-
typeof flat.apiKey === 'string' ||
|
|
20
|
-
typeof flat.model === 'string' ||
|
|
21
|
-
typeof flat.url === 'string');
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Normalize the optional top-level `llm:` block.
|
|
25
|
-
* - undefined → undefined (pipeline-only configs stay valid)
|
|
26
|
-
* - flat shape (has `provider` | `apiKey` | `model` | `url`) → { main: flat } (backward compat)
|
|
27
|
-
* - map shape → must include `main`; returned as NormalizedLlmMap
|
|
28
|
-
*/
|
|
29
|
-
export function normalizeLlmConfig(input) {
|
|
30
|
-
if (input === undefined)
|
|
31
|
-
return undefined;
|
|
32
|
-
if (isFlatLlmConfig(input)) {
|
|
33
|
-
return { main: input };
|
|
34
|
-
}
|
|
35
|
-
const map = input;
|
|
36
|
-
if (!map.main) {
|
|
37
|
-
throw new Error("llm: map must include a 'main' key (default LLM for unspecified roles)");
|
|
38
|
-
}
|
|
39
|
-
return map;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Strict lookup: returns map[name] if explicitly present, else undefined.
|
|
43
|
-
* Does NOT fall through to map.main. Use when the caller needs to
|
|
44
|
-
* detect explicit-presence (e.g. to decide between an alias and the
|
|
45
|
-
* named map entry).
|
|
46
|
-
*/
|
|
47
|
-
export function resolveLlmConfigStrict(map, name) {
|
|
48
|
-
if (!map || !name)
|
|
49
|
-
return undefined;
|
|
50
|
-
return map[name];
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Resolve a per-role LLM config by name from a normalized map.
|
|
54
|
-
* Lookup chain: map[name] → map.main → pipelineFallback.
|
|
55
|
-
* When map is undefined, falls back to pipelineFallback (so pipeline-only
|
|
56
|
-
* configs keep working with no top-level llm: block).
|
|
57
|
-
*
|
|
58
|
-
* The caller decides whether `undefined` is an error.
|
|
59
|
-
*/
|
|
60
|
-
export function resolveLlmConfig(map, name, pipelineFallback) {
|
|
61
|
-
if (!map)
|
|
62
|
-
return pipelineFallback;
|
|
63
|
-
if (!name || name === 'main')
|
|
64
|
-
return map.main;
|
|
65
|
-
return map[name] ?? map.main;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Read the reviewer block's LLM-name selector, accepting both the
|
|
69
|
-
* preferred `reviewerLlm` field and the deprecated `plannerLlm` alias.
|
|
70
|
-
* When the alias is used, calls `warn(message)`.
|
|
71
|
-
*/
|
|
72
|
-
export function resolveReviewerLlmName(block, warn) {
|
|
73
|
-
if (!block)
|
|
74
|
-
return undefined;
|
|
75
|
-
if (typeof block.reviewerLlm === 'string')
|
|
76
|
-
return block.reviewerLlm;
|
|
77
|
-
if (typeof block.plannerLlm === 'string') {
|
|
78
|
-
warn("coordinator.reviewer.plannerLlm is deprecated; rename to 'reviewerLlm'");
|
|
79
|
-
return block.plannerLlm;
|
|
80
|
-
}
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
export function resolveCoordinatorPlanning(name, plannerLlm) {
|
|
84
|
-
switch (name) {
|
|
85
|
-
case 'one-shot':
|
|
86
|
-
return new OneShotPlanning(plannerLlm);
|
|
87
|
-
case 'replan-on-error':
|
|
88
|
-
return new ReplanOnErrorPlanning(plannerLlm);
|
|
89
|
-
case 'skill-steps':
|
|
90
|
-
// SkillStepsPlanning reads `ctx.activeSkillMeta` (populated by
|
|
91
|
-
// CoordinatorHandler from `ctx.selectedSkills`). No planner LLM
|
|
92
|
-
// needed — the plan comes directly from the skill's `steps:` block.
|
|
93
|
-
return new SkillStepsPlanning();
|
|
94
|
-
default:
|
|
95
|
-
throw new Error(`Unknown coordinator.planning strategy: '${name}'. Allowed: one-shot, replan-on-error, skill-steps.`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Default coordinator dispatch kind. Omitted → 'hybrid' for ALL planning kinds:
|
|
100
|
-
* agentless steps — the synthesized answer-directly step (#155) and skill steps
|
|
101
|
-
* without an explicit `agent:` — need a self-LLM fallback. Pin 'subagent'
|
|
102
|
-
* explicitly for strict subagent-only routing.
|
|
103
|
-
*/
|
|
104
|
-
export function resolveCoordinatorDispatchKind(explicit) {
|
|
105
|
-
return explicit ?? 'hybrid';
|
|
106
|
-
}
|
|
107
|
-
export function resolveCoordinatorDispatch(name, fallbackLlm, contextBuilder) {
|
|
108
|
-
switch (name) {
|
|
109
|
-
case 'subagent':
|
|
110
|
-
return new SubAgentDispatch(contextBuilder);
|
|
111
|
-
case 'self':
|
|
112
|
-
if (!fallbackLlm) {
|
|
113
|
-
throw new Error('coordinator.dispatch=self requires a planner or main LLM');
|
|
114
|
-
}
|
|
115
|
-
return new SelfDispatch(fallbackLlm);
|
|
116
|
-
case 'hybrid':
|
|
117
|
-
if (!fallbackLlm) {
|
|
118
|
-
throw new Error('coordinator.dispatch=hybrid requires a planner or main LLM');
|
|
119
|
-
}
|
|
120
|
-
return new HybridDispatch(new SubAgentDispatch(contextBuilder), new SelfDispatch(fallbackLlm));
|
|
121
|
-
default:
|
|
122
|
-
throw new Error(`Unknown coordinator.dispatch strategy: '${name}'. Allowed: subagent, self, hybrid.`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
export function resolveCoordinatorActivation(name) {
|
|
126
|
-
switch (name) {
|
|
127
|
-
case 'auto':
|
|
128
|
-
return new AutoActivation();
|
|
129
|
-
case 'explicit':
|
|
130
|
-
return new ExplicitActivation();
|
|
131
|
-
default:
|
|
132
|
-
throw new Error(`Unknown coordinator.activation strategy: '${name}'. Allowed: auto, explicit.`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
export function resolveToolSelectionStrategy(name, params) {
|
|
136
|
-
switch (name) {
|
|
137
|
-
case 'top-k':
|
|
138
|
-
return new TopKToolSelection();
|
|
139
|
-
case 'threshold': {
|
|
140
|
-
const minScore = params?.minScore;
|
|
141
|
-
if (typeof minScore !== 'number') {
|
|
142
|
-
throw new Error("agent.toolSelection.strategy 'threshold' requires a numeric 'minScore'");
|
|
143
|
-
}
|
|
144
|
-
return new ScoreThresholdToolSelection(minScore);
|
|
145
|
-
}
|
|
146
|
-
default:
|
|
147
|
-
throw new Error(`Unknown agent.toolSelection.strategy '${name}'. Allowed: top-k, threshold.`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const VALID_PROVIDERS = [
|
|
151
|
-
'openai',
|
|
152
|
-
'anthropic',
|
|
153
|
-
'deepseek',
|
|
154
|
-
'sap-ai-sdk',
|
|
155
|
-
'ollama',
|
|
156
|
-
];
|
|
157
|
-
const VALID_RAG_TYPES = [
|
|
158
|
-
'in-memory',
|
|
159
|
-
'qdrant',
|
|
160
|
-
'hana-vector',
|
|
161
|
-
'pg-vector',
|
|
162
|
-
];
|
|
163
|
-
export class ConfigValidationError extends Error {
|
|
164
|
-
constructor(issues) {
|
|
165
|
-
super(`Configuration error in smart-server.yaml:\n${issues
|
|
166
|
-
.map((i) => ` - ${i}`)
|
|
167
|
-
.join('\n')}\nSet these fields in your YAML and restart.`);
|
|
168
|
-
this.name = 'ConfigValidationError';
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
export const YAML_TEMPLATE = `port: 4004
|
|
172
|
-
host: 0.0.0.0
|
|
173
|
-
|
|
174
|
-
# Request routing mode:
|
|
175
|
-
# hard — Fully managed context. Ignores client history/system prompt. Uses RAG + internal MCP tools only.
|
|
176
|
-
# pass — Transparent proxy. Logs everything but modifies nothing.
|
|
177
|
-
# smart — Hybrid. Preserves client history but enriches it with RAG context and MCP tools based on analysis. (default)
|
|
178
|
-
mode: smart
|
|
179
|
-
|
|
180
|
-
llm:
|
|
181
|
-
provider: deepseek # deepseek | openai | anthropic | sap-ai-sdk | ollama
|
|
182
|
-
apiKey: \${DEEPSEEK_API_KEY} # not required for ollama / sap-ai-sdk
|
|
183
|
-
model: deepseek-chat
|
|
184
|
-
temperature: 0.7
|
|
185
|
-
classifierTemperature: 0.1
|
|
186
|
-
|
|
187
|
-
rag:
|
|
188
|
-
type: in-memory # in-memory | qdrant | hana-vector | pg-vector
|
|
189
|
-
embedder: ollama # Embedder to use: ollama | openai | sap-ai-core | <custom>
|
|
190
|
-
url: http://localhost:11434
|
|
191
|
-
model: bge-m3
|
|
192
|
-
# resourceGroup: default # SAP AI Core resource group (sap-ai-core embedder)
|
|
193
|
-
# scenario: orchestration # SAP AI Core scenario: orchestration (default) | foundation-models
|
|
194
|
-
# collectionName: llm-agent # Collection/table name (qdrant | hana-vector | pg-vector)
|
|
195
|
-
dedupThreshold: 0.92
|
|
196
|
-
vectorWeight: 0.7 # Semantic similarity weight (0..1)
|
|
197
|
-
keywordWeight: 0.3 # Lexical matching weight (0..1)
|
|
198
|
-
|
|
199
|
-
mcp:
|
|
200
|
-
# type: none | http | stdio
|
|
201
|
-
# To disable MCP, set type to 'none'
|
|
202
|
-
type: http
|
|
203
|
-
url: http://localhost:3001/mcp/stream/http
|
|
204
|
-
|
|
205
|
-
agent:
|
|
206
|
-
externalToolsValidationMode: permissive # permissive | strict
|
|
207
|
-
maxIterations: 10
|
|
208
|
-
maxToolCalls: 30
|
|
209
|
-
toolUnavailableTtlMs: 600000 # Temporary tool blacklist TTL (ms)
|
|
210
|
-
ragQueryK: 10
|
|
211
|
-
# contextBudgetTokens: 4000 # Max tokens for RAG context in system prompt (0 = no limit)
|
|
212
|
-
# semanticHistoryEnabled: false # Enable semantic history via RAG
|
|
213
|
-
# historyRecencyWindow: 4 # Last N messages from client history in LLM context
|
|
214
|
-
# historyTurnSummaryPrompt: "..." # LLM prompt for turn summarization
|
|
215
|
-
showReasoning: false # Explain strategy at start of response
|
|
216
|
-
historyAutoSummarizeLimit: 10 # History length to trigger compression
|
|
217
|
-
queryExpansionEnabled: false # Expand RAG queries with LLM-generated synonyms
|
|
218
|
-
toolResultCacheTtlMs: 300000 # Tool result cache TTL (ms); 0 to disable
|
|
219
|
-
sessionTokenBudget: 0 # Multi-turn token budget; 0 to disable
|
|
220
|
-
# ragTranslateEnabled: true # Translate non-ASCII RAG queries to English (default: true)
|
|
221
|
-
# classificationEnabled: false # Enable for custom pipelines with multi-store routing
|
|
222
|
-
# toolReselectPerIteration: false # Re-select tools via RAG on each tool-loop iteration
|
|
223
|
-
# llmCallStrategy: streaming # streaming | non-streaming | fallback
|
|
224
|
-
# streamMode: full # full | final — streaming behavior for tool loops
|
|
225
|
-
# heartbeatIntervalMs: 5000 # SSE heartbeat interval during tool execution (ms)
|
|
226
|
-
# healthTimeoutMs: 5000 # Health check probe timeout (ms); increase for slow providers (SAP AI Core: 15000)
|
|
227
|
-
# retry: # LLM retry config for 429/5xx errors
|
|
228
|
-
# maxAttempts: 3
|
|
229
|
-
# backoffMs: 1000
|
|
230
|
-
# retryOn: [429, 500, 502, 503]
|
|
231
|
-
# retryOnMidStream: ['SSE stream'] # Substrings triggering mid-stream retry
|
|
232
|
-
|
|
233
|
-
# --- Advanced Multi-Model LLM (optional) ------------------------------------
|
|
234
|
-
# Assign different models for different internal roles via a top-level llm: map.
|
|
235
|
-
# (The legacy pipeline.llm override was removed — use the top-level llm: map now.)
|
|
236
|
-
# llm:
|
|
237
|
-
# main:
|
|
238
|
-
# provider: deepseek # deepseek | openai | anthropic | sap-ai-sdk
|
|
239
|
-
# apiKey: \${DEEPSEEK_API_KEY}
|
|
240
|
-
# model: deepseek-chat
|
|
241
|
-
# temperature: 0.7
|
|
242
|
-
# classifier: # optional; if absent, main config is reused
|
|
243
|
-
# provider: deepseek
|
|
244
|
-
# apiKey: \${DEEPSEEK_API_KEY}
|
|
245
|
-
# model: deepseek-chat
|
|
246
|
-
# temperature: 0.1
|
|
247
|
-
# helper: # optional; if absent, main config is reused
|
|
248
|
-
# provider: deepseek
|
|
249
|
-
# apiKey: \${DEEPSEEK_API_KEY}
|
|
250
|
-
# model: deepseek-chat
|
|
251
|
-
# temperature: 0.1
|
|
252
|
-
|
|
253
|
-
# prompts:
|
|
254
|
-
# system: "You are a helpful assistant specialized in SAP ABAP development."
|
|
255
|
-
# classifier: |
|
|
256
|
-
# You are an intent classifier... (see source for full default prompt)
|
|
257
|
-
# reasoning: |
|
|
258
|
-
# IMPORTANT: Always start your response with a brief <reasoning> block...
|
|
259
|
-
# ragTranslate: |
|
|
260
|
-
# Translate the user request to English for search purposes...
|
|
261
|
-
# historySummary: |
|
|
262
|
-
# Summarize the conversation so far...
|
|
263
|
-
|
|
264
|
-
log: smart-server.log # path to log file; omit for stdout
|
|
265
|
-
# logDir: sessions # Directory for detailed session debug logs
|
|
266
|
-
# pluginDir: ./my-plugins # Additional plugin directory (loaded after defaults)
|
|
267
|
-
# plugins: # Explicit plugin module specifiers (npm packages or paths)
|
|
268
|
-
# - "@scope/my-pipeline-plugin" # bare specifier resolved from cwd
|
|
269
|
-
# - ./local-plugin.mjs # relative path resolved against cwd
|
|
270
|
-
|
|
271
|
-
# subagents: # Optional: nested agents callable from pipeline
|
|
272
|
-
# - name: code-reviewer # Used as stage config: { agent: code-reviewer }
|
|
273
|
-
# description: | # Optional. Shown to the Coordinator planner LLM
|
|
274
|
-
# Reviews code and returns # so it can pick this agent for the right step.
|
|
275
|
-
# structured JSON.
|
|
276
|
-
# config: ./agents/code-reviewer.yaml
|
|
277
|
-
|
|
278
|
-
# pipeline: # Optional: select the request pipeline.
|
|
279
|
-
# name: flat # flat (default) | linear | dag | stepper | <plugin>
|
|
280
|
-
# config: # Opaque per-pipeline dialect (validated by the plugin)
|
|
281
|
-
# mode: planned-react # e.g. stepper: cyclic-react | planned-react | deep-stepper
|
|
282
|
-
# knowledgeSeed: [] # stepper: deployment-supplied tool guidance
|
|
283
|
-
#
|
|
284
|
-
# NOTE: the legacy 'coordinator:' block and the old 'pipeline:' shape
|
|
285
|
-
# (mcp/rag/stages/llm overrides) were REMOVED in this major. A config that
|
|
286
|
-
# still uses them fails loud at startup. Top-level 'llm:', 'mcp:', 'rag:' now
|
|
287
|
-
# own those concerns; pipeline behavior moves under 'pipeline.config'.
|
|
288
|
-
`;
|
|
289
|
-
export function resolveEnvVars(value, env = process.env) {
|
|
290
|
-
if (typeof value === 'string')
|
|
291
|
-
return value.replace(/\$\{([^}:]+)(?::-(.*?))?\}/g, (_, name, fallback) => env[name] || fallback || '');
|
|
292
|
-
if (Array.isArray(value))
|
|
293
|
-
return value.map((v) => resolveEnvVars(v, env));
|
|
294
|
-
if (value !== null && typeof value === 'object')
|
|
295
|
-
return Object.fromEntries(Object.entries(value).map(([k, v]) => [
|
|
296
|
-
k,
|
|
297
|
-
resolveEnvVars(v, env),
|
|
298
|
-
]));
|
|
299
|
-
return value;
|
|
300
|
-
}
|
|
301
|
-
export function loadYamlConfig(filePath, env = process.env) {
|
|
302
|
-
const raw = fs.readFileSync(filePath, 'utf8');
|
|
303
|
-
return resolveEnvVars(parseYaml(raw), env);
|
|
304
|
-
}
|
|
305
|
-
export function generateConfigTemplate(outputPath) {
|
|
306
|
-
fs.writeFileSync(outputPath, YAML_TEMPLATE, 'utf8');
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Build the IFinalizer impl from `coordinator.finalizer:` YAML.
|
|
310
|
-
*
|
|
311
|
-
* Lookup chain for `type: llm`:
|
|
312
|
-
* resolveLlmConfig(llmMap, cfg.finalizerLlm, pipelineFallback)
|
|
313
|
-
* → top-level llm.<name> → llm.main → pipelineFallback (pipeline.llm.main)
|
|
314
|
-
* → ConfigError if all three are missing.
|
|
315
|
-
*
|
|
316
|
-
* Absent block / `type: passthrough` → PassthroughFinalizer.
|
|
317
|
-
* `type: template` → TemplateFinalizer.
|
|
318
|
-
*/
|
|
319
|
-
export async function buildFinalizer(cfg, llmMap, pipelineFallback, makeLlm) {
|
|
320
|
-
const kind = cfg?.type ?? 'passthrough';
|
|
321
|
-
if (kind === 'passthrough')
|
|
322
|
-
return new PassthroughFinalizer();
|
|
323
|
-
if (kind === 'template')
|
|
324
|
-
return new TemplateFinalizer();
|
|
325
|
-
// kind === 'llm'
|
|
326
|
-
const resolved = resolveLlmConfig(llmMap, cfg?.finalizerLlm, pipelineFallback);
|
|
327
|
-
if (!resolved) {
|
|
328
|
-
throw new Error('coordinator.finalizer (type: llm) requires an LLM config: provide top-level llm.<name>, llm.main, or pipeline.llm.main');
|
|
329
|
-
}
|
|
330
|
-
const llm = await makeLlm(resolved);
|
|
331
|
-
return new LlmFinalizer(llm, {
|
|
332
|
-
systemPrompt: cfg?.systemPrompt,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
const get = (obj, ...keys) => keys.reduce((o, k) => {
|
|
336
|
-
if (o !== null && typeof o === 'object' && k in o) {
|
|
337
|
-
return o[k];
|
|
338
|
-
}
|
|
339
|
-
return undefined;
|
|
340
|
-
}, obj);
|
|
341
|
-
function checkLlmRole(label, role, requireModel, env, issues) {
|
|
342
|
-
const provider = role?.provider;
|
|
343
|
-
if (!provider) {
|
|
344
|
-
issues.push(`${label}.provider: required (one of: openai, anthropic, deepseek, sap-ai-sdk, ollama)`);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
// `as readonly string[]` is required so .includes() accepts an arbitrary
|
|
348
|
-
// string; do not "simplify" — it preserves the const-tuple narrowing.
|
|
349
|
-
if (!VALID_PROVIDERS.includes(provider)) {
|
|
350
|
-
issues.push(`${label}.provider: "${provider}" is invalid (one of: openai, anthropic, deepseek, sap-ai-sdk, ollama)`);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (requireModel && !role?.model) {
|
|
354
|
-
issues.push(`${label}.model: required (string)`);
|
|
355
|
-
}
|
|
356
|
-
if (provider === 'openai' ||
|
|
357
|
-
provider === 'anthropic' ||
|
|
358
|
-
provider === 'deepseek') {
|
|
359
|
-
if (!role?.apiKey) {
|
|
360
|
-
issues.push(`${provider} requires ${label}.apiKey to resolve to a non-empty value (typically via \${${provider.toUpperCase()}_API_KEY} env reference).`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
else if (provider === 'sap-ai-sdk') {
|
|
364
|
-
if (!env.AICORE_SERVICE_KEY) {
|
|
365
|
-
issues.push('sap-ai-sdk requires the AICORE_SERVICE_KEY env var to be set with the SAP AI Core service-key JSON content. None found.');
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
// ollama: no credential check.
|
|
369
|
-
}
|
|
370
|
-
function checkRagStore(label, store, issues) {
|
|
371
|
-
if (!store)
|
|
372
|
-
return;
|
|
373
|
-
const ragType = store.type;
|
|
374
|
-
if (!ragType) {
|
|
375
|
-
issues.push(`${label}.type: required (one of: in-memory, qdrant, hana-vector, pg-vector)`);
|
|
376
|
-
}
|
|
377
|
-
else if (ragType === 'ollama' || ragType === 'openai') {
|
|
378
|
-
issues.push(`${label}.type: "${ragType}" is an embedder, not a store — use \`type: in-memory\` with \`embedder: ${ragType}\` (or a real store: qdrant, hana-vector, pg-vector)`);
|
|
379
|
-
}
|
|
380
|
-
else if (!VALID_RAG_TYPES.includes(ragType)) {
|
|
381
|
-
issues.push(`${label}.type: "${ragType}" is invalid (one of: in-memory, qdrant, hana-vector, pg-vector)`);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
if (ragType === 'qdrant' && !store.url) {
|
|
385
|
-
issues.push(`${label}.url: required for ${label}.type qdrant`);
|
|
386
|
-
}
|
|
387
|
-
if ((ragType === 'hana-vector' || ragType === 'pg-vector') &&
|
|
388
|
-
!store.collectionName) {
|
|
389
|
-
issues.push(`${label}.collectionName: required for ${label}.type ${ragType}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// Blocklist (NOT allowlist): consumers can register custom embedder
|
|
393
|
-
// factories, so only known embedder-less providers are hard-rejected here.
|
|
394
|
-
const embedder = store.embedder;
|
|
395
|
-
if (embedder === 'deepseek' || embedder === 'anthropic') {
|
|
396
|
-
issues.push(`${label}.embedder: "${embedder}" provider has no embedder; embedding-capable providers are ollama, openai, sap-ai-core`);
|
|
397
|
-
}
|
|
398
|
-
// Require model when an embedder is used: vector stores always use an
|
|
399
|
-
// embedder; in-memory uses one only when embedder is explicitly set.
|
|
400
|
-
const usesEmbedder = ragType === 'qdrant' ||
|
|
401
|
-
ragType === 'hana-vector' ||
|
|
402
|
-
ragType === 'pg-vector' ||
|
|
403
|
-
(ragType === 'in-memory' && embedder != null);
|
|
404
|
-
if (usesEmbedder && !store.model) {
|
|
405
|
-
issues.push(`${label}.model: required when an embedder is used (e.g. bge-m3 for ollama)`);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
function validateLlmEntry(label, cfg, required, env, issues) {
|
|
409
|
-
checkLlmRole(label, cfg, required, env, issues);
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Fail-loud migration guard for the clean break to `pipeline: { name, config }`.
|
|
413
|
-
*
|
|
414
|
-
* Throws when the raw YAML still carries either:
|
|
415
|
-
* - a `coordinator:` block (the old runtime dispatch, removed in this major), OR
|
|
416
|
-
* - a `pipeline:` value in the LEGACY PipelineConfig shape — an object that
|
|
417
|
-
* carries `mcp`/`rag`/`stages`/`llm` but NO `name`.
|
|
418
|
-
*
|
|
419
|
-
* A new `pipeline: { name, ... }` object or a bare string shorthand
|
|
420
|
-
* (`pipeline: stepper`) passes untouched.
|
|
421
|
-
*/
|
|
422
|
-
export function assertNoLegacyPipelineConfig(yaml) {
|
|
423
|
-
const hasCoordinator = yaml.coordinator !== undefined;
|
|
424
|
-
const rawPipeline = yaml.pipeline;
|
|
425
|
-
const isLegacyPipeline = rawPipeline !== undefined &&
|
|
426
|
-
rawPipeline !== null &&
|
|
427
|
-
typeof rawPipeline === 'object' &&
|
|
428
|
-
!Array.isArray(rawPipeline) &&
|
|
429
|
-
typeof rawPipeline.name !== 'string' &&
|
|
430
|
-
['mcp', 'rag', 'stages', 'llm'].some((k) => rawPipeline[k] !== undefined);
|
|
431
|
-
if (hasCoordinator || isLegacyPipeline) {
|
|
432
|
-
throw new Error("Legacy 'coordinator:' / 'pipeline:' config is no longer supported (removed in this major). " +
|
|
433
|
-
'Migrate to: pipeline: { name: <flat|linear|dag|stepper>, config: { ... } }. ' +
|
|
434
|
-
"(Stepper's knowledgeSeed moves under pipeline.config.knowledgeSeed.) " +
|
|
435
|
-
'Pin a version <= 18 for the old behavior.');
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
function validateResolvedConfig(_resolved, yaml, env) {
|
|
439
|
-
const issues = [];
|
|
440
|
-
// LLM is always sourced from the top-level `llm:` block now — the legacy
|
|
441
|
-
// `pipeline.llm.*` override has been removed with the `pipeline: {name,config}`
|
|
442
|
-
// migration. Read from the raw YAML so we can distinguish flat vs map shape.
|
|
443
|
-
// `resolved.llm` is always constructed as a flat object by
|
|
444
|
-
// resolveSmartServerConfig, so it cannot be used to detect the map shape.
|
|
445
|
-
const rawLlm = get(yaml, 'llm');
|
|
446
|
-
if (rawLlm === undefined) {
|
|
447
|
-
issues.push('llm: required (top-level llm.main or a flat llm block)');
|
|
448
|
-
}
|
|
449
|
-
else if (typeof rawLlm.provider === 'string') {
|
|
450
|
-
// Flat shape — existing behaviour.
|
|
451
|
-
validateLlmEntry('llm', rawLlm, true, env, issues);
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
// Map shape — llm.main is required; every named entry is validated.
|
|
455
|
-
const map = rawLlm;
|
|
456
|
-
if (!map.main) {
|
|
457
|
-
issues.push("llm.main: required when 'llm' is a named map");
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
validateLlmEntry('llm.main', map.main, true, env, issues);
|
|
461
|
-
}
|
|
462
|
-
for (const [name, entry] of Object.entries(map)) {
|
|
463
|
-
if (name === 'main')
|
|
464
|
-
continue;
|
|
465
|
-
validateLlmEntry(`llm.${name}`, entry, true, env, issues);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
// Pipeline selection shape: `pipeline:` must name a pipeline (string or
|
|
469
|
-
// { name } object). The plugin validates its own `config` dialect at build
|
|
470
|
-
// time — we only enforce the presence of a name here.
|
|
471
|
-
const rawPipeline = yaml.pipeline;
|
|
472
|
-
if (rawPipeline !== undefined && rawPipeline !== null) {
|
|
473
|
-
const ok = typeof rawPipeline === 'string' ||
|
|
474
|
-
(typeof rawPipeline === 'object' &&
|
|
475
|
-
typeof rawPipeline.name === 'string');
|
|
476
|
-
if (!ok) {
|
|
477
|
-
issues.push("pipeline: requires a 'name' (string, or { name, config }); built-ins: flat, linear, dag, stepper");
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
if (get(yaml, 'mcp')) {
|
|
481
|
-
const rawMcpVal = yaml.mcp;
|
|
482
|
-
const mcpEntries = Array.isArray(rawMcpVal)
|
|
483
|
-
? rawMcpVal
|
|
484
|
-
: [rawMcpVal];
|
|
485
|
-
mcpEntries.forEach((entry, i) => {
|
|
486
|
-
const label = Array.isArray(rawMcpVal) ? `mcp[${i}]` : 'mcp';
|
|
487
|
-
const mcpType = entry?.type;
|
|
488
|
-
if (mcpType && !['http', 'stdio', 'none'].includes(mcpType)) {
|
|
489
|
-
issues.push(`${label}.type: "${mcpType}" is invalid (one of: http, stdio, none)`);
|
|
490
|
-
}
|
|
491
|
-
if (mcpType === 'http' && !entry?.url) {
|
|
492
|
-
issues.push(`${label}.url: required when ${label}.type is http`);
|
|
493
|
-
}
|
|
494
|
-
if (mcpType === 'stdio' && !entry?.command) {
|
|
495
|
-
issues.push(`${label}.command: required when ${label}.type is stdio`);
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
if (get(yaml, 'rag')) {
|
|
500
|
-
checkRagStore('rag', get(yaml, 'rag'), issues);
|
|
501
|
-
}
|
|
502
|
-
// NOTE: the legacy `pipeline.rag.{name}` multistore was removed with the
|
|
503
|
-
// `pipeline: {name,config}` migration; the top-level `rag:` block is the sole
|
|
504
|
-
// RAG source, validated above.
|
|
505
|
-
if (issues.length > 0)
|
|
506
|
-
throw new ConfigValidationError([...new Set(issues)]);
|
|
507
|
-
}
|
|
8
|
+
import { get, loadYamlConfig } from './yaml-loader.js';
|
|
9
|
+
export { buildFinalizer, resolveCoordinatorActivation, resolveCoordinatorDispatch, resolveCoordinatorDispatchKind, resolveCoordinatorPlanning, resolveToolSelectionStrategy, } from '../pipelines/coordinator-resolvers.js';
|
|
10
|
+
export { assertNoLegacyPipelineConfig, ConfigValidationError, } from './config-validator.js';
|
|
11
|
+
export { normalizeLlmConfig, resolveLlmConfig, resolveLlmConfigStrict, resolveReviewerLlmName, } from './llm-config-map.js';
|
|
12
|
+
export { generateConfigTemplate, loadYamlConfig, resolveEnvVars, YAML_TEMPLATE, } from './yaml-loader.js';
|
|
508
13
|
/**
|
|
509
14
|
* Recursively parse the top-level `subagents:` block from a YAML config.
|
|
510
15
|
*
|
|
@@ -585,176 +90,14 @@ export function resolveSmartServerConfig(args = {}, yaml = {}, env = process.env
|
|
|
585
90
|
// API key derives solely from the top-level `llm:` block now (the legacy
|
|
586
91
|
// `pipeline.llm.main.apiKey` override was removed with the schema migration).
|
|
587
92
|
const apiKey = get(yaml, 'llm', 'apiKey') ?? '';
|
|
588
|
-
const rawMcp = yaml.mcp;
|
|
589
|
-
const mcpIsArray = Array.isArray(rawMcp);
|
|
590
|
-
const mcpUrl = get(yaml, 'mcp', 'url');
|
|
591
|
-
const mcpCommand = get(yaml, 'mcp', 'command');
|
|
592
|
-
const mcpTypeRaw = mcpIsArray
|
|
593
|
-
? null // array form: type resolved per-entry inside connectMcpClientsFromConfig
|
|
594
|
-
: (get(yaml, 'mcp', 'type') ??
|
|
595
|
-
(mcpUrl ? 'http' : mcpCommand ? 'stdio' : null));
|
|
596
|
-
const mcpType = (mcpTypeRaw === 'none' ? null : mcpTypeRaw);
|
|
597
|
-
const promptSystem = get(yaml, 'prompts', 'system') ?? null;
|
|
598
|
-
const promptClassifier = get(yaml, 'prompts', 'classifier') ?? null;
|
|
599
|
-
const promptReasoning = get(yaml, 'prompts', 'reasoning') ?? null;
|
|
600
|
-
const promptRagTranslate = get(yaml, 'prompts', 'ragTranslate') ?? null;
|
|
601
|
-
const promptHistorySummary = get(yaml, 'prompts', 'historySummary') ?? null;
|
|
602
93
|
const resolved = {
|
|
603
94
|
port: Number(args.port ?? get(yaml, 'port') ?? env.PORT ?? 4004),
|
|
604
95
|
host: args.host ?? get(yaml, 'host') ?? '0.0.0.0',
|
|
605
|
-
llm:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
url: get(yaml, 'llm', 'url'),
|
|
611
|
-
model: get(yaml, 'llm', 'model'),
|
|
612
|
-
temperature: Number(get(yaml, 'llm', 'temperature') ?? 0.7),
|
|
613
|
-
classifierTemperature: Number(get(yaml, 'llm', 'classifierTemperature') ?? 0.1),
|
|
614
|
-
}
|
|
615
|
-
: get(yaml, 'llm')
|
|
616
|
-
: undefined,
|
|
617
|
-
rag: get(yaml, 'rag')
|
|
618
|
-
? {
|
|
619
|
-
type: get(yaml, 'rag', 'type'),
|
|
620
|
-
embedder: get(yaml, 'rag', 'embedder') ?? undefined,
|
|
621
|
-
url: get(yaml, 'rag', 'url'),
|
|
622
|
-
model: get(yaml, 'rag', 'model'),
|
|
623
|
-
collectionName: args['rag-collection-name'] ??
|
|
624
|
-
get(yaml, 'rag', 'collectionName') ??
|
|
625
|
-
undefined,
|
|
626
|
-
dedupThreshold: Number(get(yaml, 'rag', 'dedupThreshold') ?? 0.92),
|
|
627
|
-
vectorWeight: Number(get(yaml, 'rag', 'vectorWeight') ?? 0.7),
|
|
628
|
-
keywordWeight: Number(get(yaml, 'rag', 'keywordWeight') ?? 0.3),
|
|
629
|
-
...(get(yaml, 'rag', 'resourceGroup') !== undefined
|
|
630
|
-
? { resourceGroup: String(get(yaml, 'rag', 'resourceGroup')) }
|
|
631
|
-
: {}),
|
|
632
|
-
...(get(yaml, 'rag', 'scenario') !== undefined
|
|
633
|
-
? {
|
|
634
|
-
scenario: String(get(yaml, 'rag', 'scenario')),
|
|
635
|
-
}
|
|
636
|
-
: {}),
|
|
637
|
-
}
|
|
638
|
-
: undefined,
|
|
639
|
-
mcp: mcpIsArray
|
|
640
|
-
? // Array form: pass through as-is so connectMcpClientsFromConfig can
|
|
641
|
-
// iterate and connect each entry. Typed as SmartServerMcpConfig[].
|
|
642
|
-
rawMcp
|
|
643
|
-
: mcpType
|
|
644
|
-
? {
|
|
645
|
-
type: mcpType,
|
|
646
|
-
url: mcpUrl || undefined,
|
|
647
|
-
command: mcpCommand || undefined,
|
|
648
|
-
args: args['mcp-args'] || get(yaml, 'mcp', 'args')
|
|
649
|
-
? String(args['mcp-args'] || get(yaml, 'mcp', 'args')).split(' ')
|
|
650
|
-
: undefined,
|
|
651
|
-
headers: get(yaml, 'mcp', 'headers') ||
|
|
652
|
-
undefined,
|
|
653
|
-
}
|
|
654
|
-
: undefined,
|
|
655
|
-
agent: {
|
|
656
|
-
externalToolsValidationMode: (get(yaml, 'agent', 'externalToolsValidationMode') ?? 'permissive'),
|
|
657
|
-
maxIterations: Number(get(yaml, 'agent', 'maxIterations') ?? 10),
|
|
658
|
-
maxToolCalls: Number(get(yaml, 'agent', 'maxToolCalls') ?? 30),
|
|
659
|
-
toolUnavailableTtlMs: Number(get(yaml, 'agent', 'toolUnavailableTtlMs') ?? 600000),
|
|
660
|
-
ragQueryK: Number(get(yaml, 'agent', 'ragQueryK') ?? 10),
|
|
661
|
-
...(get(yaml, 'agent', 'contextBudgetTokens') !== undefined
|
|
662
|
-
? {
|
|
663
|
-
contextBudgetTokens: Number(get(yaml, 'agent', 'contextBudgetTokens')),
|
|
664
|
-
}
|
|
665
|
-
: {}),
|
|
666
|
-
...(get(yaml, 'agent', 'semanticHistoryEnabled') !== undefined
|
|
667
|
-
? {
|
|
668
|
-
semanticHistoryEnabled: Boolean(get(yaml, 'agent', 'semanticHistoryEnabled')),
|
|
669
|
-
}
|
|
670
|
-
: {}),
|
|
671
|
-
...(get(yaml, 'agent', 'historyRecencyWindow') !== undefined
|
|
672
|
-
? {
|
|
673
|
-
historyRecencyWindow: Number(get(yaml, 'agent', 'historyRecencyWindow')),
|
|
674
|
-
}
|
|
675
|
-
: {}),
|
|
676
|
-
...(get(yaml, 'agent', 'historyTurnSummaryPrompt') !== undefined
|
|
677
|
-
? {
|
|
678
|
-
historyTurnSummaryPrompt: String(get(yaml, 'agent', 'historyTurnSummaryPrompt')),
|
|
679
|
-
}
|
|
680
|
-
: {}),
|
|
681
|
-
showReasoning: Boolean(args['agent-show-reasoning'] ??
|
|
682
|
-
get(yaml, 'agent', 'showReasoning') ??
|
|
683
|
-
false),
|
|
684
|
-
historyAutoSummarizeLimit: Number(get(yaml, 'agent', 'historyAutoSummarizeLimit') ?? 10),
|
|
685
|
-
queryExpansionEnabled: Boolean(get(yaml, 'agent', 'queryExpansionEnabled') ?? false),
|
|
686
|
-
toolResultCacheTtlMs: Number(get(yaml, 'agent', 'toolResultCacheTtlMs') ?? 300000),
|
|
687
|
-
sessionTokenBudget: Number(get(yaml, 'agent', 'sessionTokenBudget') ?? 0),
|
|
688
|
-
...(get(yaml, 'agent', 'classificationEnabled') !== undefined
|
|
689
|
-
? {
|
|
690
|
-
classificationEnabled: Boolean(get(yaml, 'agent', 'classificationEnabled')),
|
|
691
|
-
}
|
|
692
|
-
: {}),
|
|
693
|
-
...(get(yaml, 'agent', 'toolReselectPerIteration') !== undefined
|
|
694
|
-
? {
|
|
695
|
-
toolReselectPerIteration: Boolean(get(yaml, 'agent', 'toolReselectPerIteration')),
|
|
696
|
-
}
|
|
697
|
-
: {}),
|
|
698
|
-
...(get(yaml, 'agent', 'ragTranslateEnabled') !== undefined
|
|
699
|
-
? {
|
|
700
|
-
ragTranslateEnabled: Boolean(get(yaml, 'agent', 'ragTranslateEnabled')),
|
|
701
|
-
}
|
|
702
|
-
: {}),
|
|
703
|
-
...(get(yaml, 'agent', 'refreshToolsPerIteration') !== undefined
|
|
704
|
-
? {
|
|
705
|
-
refreshToolsPerIteration: Boolean(get(yaml, 'agent', 'refreshToolsPerIteration')),
|
|
706
|
-
}
|
|
707
|
-
: {}),
|
|
708
|
-
...(get(yaml, 'agent', 'streamMode') !== undefined
|
|
709
|
-
? {
|
|
710
|
-
streamMode: String(get(yaml, 'agent', 'streamMode')),
|
|
711
|
-
}
|
|
712
|
-
: {}),
|
|
713
|
-
...(get(yaml, 'agent', 'llmCallStrategy') !== undefined
|
|
714
|
-
? {
|
|
715
|
-
llmCallStrategy: String(get(yaml, 'agent', 'llmCallStrategy')),
|
|
716
|
-
}
|
|
717
|
-
: {}),
|
|
718
|
-
...(get(yaml, 'agent', 'heartbeatIntervalMs') !== undefined
|
|
719
|
-
? {
|
|
720
|
-
heartbeatIntervalMs: Number(get(yaml, 'agent', 'heartbeatIntervalMs')),
|
|
721
|
-
}
|
|
722
|
-
: {}),
|
|
723
|
-
...(get(yaml, 'agent', 'healthTimeoutMs') !== undefined
|
|
724
|
-
? {
|
|
725
|
-
healthTimeoutMs: Number(get(yaml, 'agent', 'healthTimeoutMs')),
|
|
726
|
-
}
|
|
727
|
-
: {}),
|
|
728
|
-
...(get(yaml, 'agent', 'retry') !== undefined
|
|
729
|
-
? {
|
|
730
|
-
retry: get(yaml, 'agent', 'retry'),
|
|
731
|
-
}
|
|
732
|
-
: {}),
|
|
733
|
-
...(get(yaml, 'agent', 'toolSelection') !== undefined
|
|
734
|
-
? {
|
|
735
|
-
toolSelection: get(yaml, 'agent', 'toolSelection'),
|
|
736
|
-
}
|
|
737
|
-
: {}),
|
|
738
|
-
},
|
|
739
|
-
prompts: promptSystem ||
|
|
740
|
-
promptClassifier ||
|
|
741
|
-
promptReasoning ||
|
|
742
|
-
promptRagTranslate ||
|
|
743
|
-
promptHistorySummary
|
|
744
|
-
? {
|
|
745
|
-
...(promptSystem ? { system: promptSystem } : {}),
|
|
746
|
-
...(promptClassifier ? { classifier: promptClassifier } : {}),
|
|
747
|
-
...(typeof promptReasoning === 'string'
|
|
748
|
-
? { reasoning: promptReasoning }
|
|
749
|
-
: {}),
|
|
750
|
-
...(typeof promptRagTranslate === 'string'
|
|
751
|
-
? { ragTranslate: promptRagTranslate }
|
|
752
|
-
: {}),
|
|
753
|
-
...(typeof promptHistorySummary === 'string'
|
|
754
|
-
? { historySummary: promptHistorySummary }
|
|
755
|
-
: {}),
|
|
756
|
-
}
|
|
757
|
-
: undefined,
|
|
96
|
+
llm: resolveLlmSection(yaml, apiKey),
|
|
97
|
+
rag: resolveRagSection(yaml, args),
|
|
98
|
+
mcp: resolveMcpSection(yaml, args),
|
|
99
|
+
agent: resolveAgentSection(yaml, args),
|
|
100
|
+
prompts: resolvePromptsSection(yaml),
|
|
758
101
|
mode: get(yaml, 'mode') ?? undefined,
|
|
759
102
|
logDir: args['log-dir'] ?? get(yaml, 'logDir') ?? null,
|
|
760
103
|
pluginDir: args['plugin-dir'] ?? get(yaml, 'pluginDir') ?? undefined,
|
|
@@ -769,29 +112,7 @@ export function resolveSmartServerConfig(args = {}, yaml = {}, env = process.env
|
|
|
769
112
|
const subAgentConfigs = parseSubAgents(yaml, options.configPath, args, env);
|
|
770
113
|
return subAgentConfigs ? { subAgentConfigs } : {};
|
|
771
114
|
})(),
|
|
772
|
-
...(
|
|
773
|
-
// Pipeline selection: `pipeline: { name, config }`. `name` is required;
|
|
774
|
-
// `config` is the plugin's opaque dialect (validated by the plugin's
|
|
775
|
-
// parseConfig at build time). A bare string (`pipeline: stepper`) is
|
|
776
|
-
// accepted as shorthand for `{ name: <string> }`.
|
|
777
|
-
const raw = yaml.pipeline;
|
|
778
|
-
if (raw === undefined || raw === null)
|
|
779
|
-
return {};
|
|
780
|
-
if (typeof raw === 'string')
|
|
781
|
-
return { pipeline: { name: raw } };
|
|
782
|
-
const obj = raw;
|
|
783
|
-
if (typeof obj.name !== 'string') {
|
|
784
|
-
throw new Error("pipeline: requires a 'name' (one of: flat, linear, dag, stepper, or a registered plugin)");
|
|
785
|
-
}
|
|
786
|
-
return {
|
|
787
|
-
pipeline: {
|
|
788
|
-
name: obj.name,
|
|
789
|
-
...(obj.config && typeof obj.config === 'object'
|
|
790
|
-
? { config: obj.config }
|
|
791
|
-
: {}),
|
|
792
|
-
},
|
|
793
|
-
};
|
|
794
|
-
})(),
|
|
115
|
+
...resolvePipelineSelection(yaml),
|
|
795
116
|
...(yaml.skills
|
|
796
117
|
? {
|
|
797
118
|
skills: {
|
|
@@ -805,218 +126,10 @@ export function resolveSmartServerConfig(args = {}, yaml = {}, env = process.env
|
|
|
805
126
|
? { skillPlugins: parseSkillPluginsConfig(yaml.skillPlugins) }
|
|
806
127
|
: {}),
|
|
807
128
|
};
|
|
808
|
-
validateResolvedConfig(resolved, yaml, env
|
|
129
|
+
validateResolvedConfig(resolved, yaml, env, {
|
|
130
|
+
skipProviderRuntimeChecks: options.skipProviderRuntimeChecks,
|
|
131
|
+
});
|
|
809
132
|
return resolved;
|
|
810
133
|
}
|
|
811
|
-
|
|
812
|
-
'cyclic-react',
|
|
813
|
-
'planned-react',
|
|
814
|
-
'deep-stepper',
|
|
815
|
-
]);
|
|
816
|
-
/**
|
|
817
|
-
* Preset expansion: each `mode` maps to a default `flow` composition.
|
|
818
|
-
* An explicit `coordinator.flow` block overrides these per-component.
|
|
819
|
-
* `deep-stepper` = llm planner + RECURSIVE executor (demand-driven recursion),
|
|
820
|
-
* relying on the Evaluator as terminator (enforced below).
|
|
821
|
-
*/
|
|
822
|
-
const MODE_FLOW_PRESET = {
|
|
823
|
-
'cyclic-react': { planner: 'none', executor: 'cyclic-react' },
|
|
824
|
-
'planned-react': { planner: 'llm', executor: 'cyclic-react' },
|
|
825
|
-
'deep-stepper': { planner: 'llm', executor: 'recursive' },
|
|
826
|
-
};
|
|
827
|
-
/** Parse declarative `flow.plan` nodes (for the static planner). */
|
|
828
|
-
function parseFlowPlan(raw) {
|
|
829
|
-
if (!Array.isArray(raw))
|
|
830
|
-
return undefined;
|
|
831
|
-
const nodes = raw
|
|
832
|
-
.filter((n) => !!n && typeof n.goal === 'string')
|
|
833
|
-
.map((n, i) => ({
|
|
834
|
-
id: typeof n.id === 'string' && n.id ? n.id : `n${i}`,
|
|
835
|
-
goal: n.goal,
|
|
836
|
-
...(Array.isArray(n.dependsOn)
|
|
837
|
-
? {
|
|
838
|
-
dependsOn: n.dependsOn.filter((d) => typeof d === 'string'),
|
|
839
|
-
}
|
|
840
|
-
: {}),
|
|
841
|
-
...(typeof n.agent === 'string' ? { agent: n.agent } : {}),
|
|
842
|
-
}));
|
|
843
|
-
return nodes.length > 0 ? nodes : undefined;
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Parse a (possibly nested) `flow` block into a full StepperCompositionSpec,
|
|
847
|
-
* inheriting bounds from the root. Mutually recursive with
|
|
848
|
-
* parseCompositionNodes (function declarations are hoisted).
|
|
849
|
-
*/
|
|
850
|
-
function parseNestedFlowSpec(flowCfg, bounds) {
|
|
851
|
-
const plannerType = flowCfg?.planner?.type ?? 'llm';
|
|
852
|
-
if (!['none', 'llm', 'static'].includes(plannerType))
|
|
853
|
-
throw new Error(`flow.planner.type must be none|llm|static`);
|
|
854
|
-
const granularity = flowCfg?.planner?.granularity ?? 'shallow';
|
|
855
|
-
if (!['shallow', 'detailed'].includes(granularity))
|
|
856
|
-
throw new Error(`flow.planner.granularity must be shallow|detailed`);
|
|
857
|
-
const executor = flowCfg?.executor?.type ?? 'cyclic-react';
|
|
858
|
-
if (!['simple', 'cyclic-react', 'recursive'].includes(executor))
|
|
859
|
-
throw new Error(`flow.executor.type must be simple|cyclic-react|recursive`);
|
|
860
|
-
const plannerSystemPrompt = parseSystemPromptOverride(flowCfg?.planner?.systemPrompt, 'flow.planner.systemPrompt');
|
|
861
|
-
const executorSystemPrompt = parseSystemPromptOverride(flowCfg?.executor?.systemPrompt, 'flow.executor.systemPrompt');
|
|
862
|
-
const plan = parseFlowPlan(flowCfg?.plan);
|
|
863
|
-
const nodes = parseCompositionNodes(flowCfg?.nodes, bounds);
|
|
864
|
-
return {
|
|
865
|
-
// Declared nodes ARE the plan ⇒ this level is static (keep the spec honest:
|
|
866
|
-
// buildFromComposition routes a node-bearing level to a StaticPlanner).
|
|
867
|
-
planner: (nodes ? 'static' : plannerType),
|
|
868
|
-
granularity: granularity,
|
|
869
|
-
...(plan ? { plan } : {}),
|
|
870
|
-
...(nodes ? { nodes } : {}),
|
|
871
|
-
executor: executor,
|
|
872
|
-
finalizer: 'llm',
|
|
873
|
-
...(plannerSystemPrompt ? { plannerSystemPrompt } : {}),
|
|
874
|
-
...(executorSystemPrompt ? { executorSystemPrompt } : {}),
|
|
875
|
-
...bounds,
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
/** Validate an optional system-prompt override: must be a non-empty string. */
|
|
879
|
-
function parseSystemPromptOverride(raw, label) {
|
|
880
|
-
if (raw === undefined || raw === null)
|
|
881
|
-
return undefined;
|
|
882
|
-
if (typeof raw !== 'string' || raw.trim() === '')
|
|
883
|
-
throw new Error(`coordinator.${label} must be a non-empty string`);
|
|
884
|
-
return raw;
|
|
885
|
-
}
|
|
886
|
-
/** Parse composition nodes; a node with a nested `flow` recurses into a sub-spec. */
|
|
887
|
-
function parseCompositionNodes(raw, bounds) {
|
|
888
|
-
if (!Array.isArray(raw))
|
|
889
|
-
return undefined;
|
|
890
|
-
const nodes = raw
|
|
891
|
-
.filter((n) => !!n && typeof n.goal === 'string')
|
|
892
|
-
.map((n, i) => ({
|
|
893
|
-
id: typeof n.id === 'string' && n.id ? n.id : `n${i}`,
|
|
894
|
-
goal: n.goal,
|
|
895
|
-
...(Array.isArray(n.dependsOn)
|
|
896
|
-
? {
|
|
897
|
-
dependsOn: n.dependsOn.filter((d) => typeof d === 'string'),
|
|
898
|
-
}
|
|
899
|
-
: {}),
|
|
900
|
-
...(n.flow ? { flow: parseNestedFlowSpec(n.flow, bounds) } : {}),
|
|
901
|
-
}));
|
|
902
|
-
return nodes.length > 0 ? nodes : undefined;
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Parse stepper coordinator configuration from a raw config object.
|
|
906
|
-
*
|
|
907
|
-
* Supports:
|
|
908
|
-
* - `mode` (string) — default 'planned-react'; one of cyclic-react | planned-react
|
|
909
|
-
* - `stepper.maxParallelSteps` (number) — default 4
|
|
910
|
-
* - `stepper.maxDepth` (number) — default 4
|
|
911
|
-
* - `stepper.tokenBudget` (number) — default 1,000,000
|
|
912
|
-
* - `stepper.reviewer.atDepths` (number[] | 'all') — default [0,1]; 'all' means accept any depth
|
|
913
|
-
*/
|
|
914
|
-
export function parseStepperCoordinatorConfig(coord) {
|
|
915
|
-
const mode = coord.mode ?? 'planned-react';
|
|
916
|
-
if (!MODES.has(mode))
|
|
917
|
-
throw new Error(`unknown coordinator.mode '${String(coord.mode)}'`);
|
|
918
|
-
// Tool permissioning is the MCP SERVER's responsibility — whatever it exposes
|
|
919
|
-
// via tools/list is allowed. The agent does not classify tools (read-only vs
|
|
920
|
-
// mutating); there is no agent-side gate. The consumer wires the agent to a
|
|
921
|
-
// server that exposes only the permitted tools (e.g. a read-only MCP proxy).
|
|
922
|
-
const stepper = coord.stepper ?? {};
|
|
923
|
-
const reviewerCfg = stepper.reviewer ?? {};
|
|
924
|
-
const atDepths = reviewerCfg.atDepths ?? [0, 1];
|
|
925
|
-
const reviewerAtDepths = atDepths === 'all'
|
|
926
|
-
? { has: () => true }
|
|
927
|
-
: (() => {
|
|
928
|
-
const s = new Set(atDepths);
|
|
929
|
-
return { has: (d) => s.has(d) };
|
|
930
|
-
})();
|
|
931
|
-
const knowledgeSeed = Array.isArray(coord.knowledgeSeed)
|
|
932
|
-
? coord.knowledgeSeed
|
|
933
|
-
.filter((e) => e && typeof e.content === 'string' && e.content.trim() !== '')
|
|
934
|
-
.map((e) => ({
|
|
935
|
-
content: e.content,
|
|
936
|
-
artifactType: typeof e.artifactType === 'string' && e.artifactType
|
|
937
|
-
? e.artifactType
|
|
938
|
-
: 'guidance',
|
|
939
|
-
}))
|
|
940
|
-
: [];
|
|
941
|
-
// Resolve the program flow: explicit `coordinator.flow` overrides the
|
|
942
|
-
// mode-derived preset per component. `mode` thus becomes a preset alias.
|
|
943
|
-
const preset = MODE_FLOW_PRESET[mode];
|
|
944
|
-
const flowCfg = coord.flow;
|
|
945
|
-
const plannerType = flowCfg?.planner?.type ?? preset.planner;
|
|
946
|
-
if (!['none', 'llm', 'static'].includes(plannerType))
|
|
947
|
-
throw new Error(`coordinator.flow.planner.type must be none|llm|static`);
|
|
948
|
-
const granularity = flowCfg?.planner?.granularity ?? 'shallow';
|
|
949
|
-
if (!['shallow', 'detailed'].includes(granularity))
|
|
950
|
-
throw new Error(`coordinator.flow.planner.granularity must be shallow|detailed`);
|
|
951
|
-
const executorType = flowCfg?.executor?.type ?? preset.executor;
|
|
952
|
-
if (!['simple', 'cyclic-react', 'recursive'].includes(executorType))
|
|
953
|
-
throw new Error(`coordinator.flow.executor.type must be simple|cyclic-react|recursive`);
|
|
954
|
-
const finalizerType = flowCfg?.finalizer?.type ?? 'llm';
|
|
955
|
-
if (finalizerType !== 'llm')
|
|
956
|
-
throw new Error(`coordinator.flow.finalizer.type 'passthrough' is not yet implemented (use 'llm')`);
|
|
957
|
-
const plannerSystemPrompt = parseSystemPromptOverride(flowCfg?.planner?.systemPrompt, 'flow.planner.systemPrompt');
|
|
958
|
-
const executorSystemPrompt = parseSystemPromptOverride(flowCfg?.executor?.systemPrompt, 'flow.executor.systemPrompt');
|
|
959
|
-
// 18.1 Evaluator: ON by default at all depths (per design). Disable via
|
|
960
|
-
// `flow.evaluator.enabled: false`; narrow via `flow.evaluator.atDepths`.
|
|
961
|
-
const evaluatorEnabled = flowCfg?.evaluator?.enabled !== false;
|
|
962
|
-
// RUNAWAY GUARD: demand-driven recursion (executor:recursive / deep-stepper)
|
|
963
|
-
// terminates via the Evaluator (executable → leaf, needs-work → recurse).
|
|
964
|
-
// Without it recursion has no termination judge — that is exactly the 18.0
|
|
965
|
-
// runaway (141 spawns). So recursion REQUIRES the Evaluator enabled.
|
|
966
|
-
if (executorType === 'recursive' && !evaluatorEnabled)
|
|
967
|
-
throw new Error('coordinator.flow.executor.type "recursive" (deep-stepper) requires the Evaluator ' +
|
|
968
|
-
'(it is the recursion terminator) — do not set coordinator.flow.evaluator.enabled: false');
|
|
969
|
-
const evalAtDepths = flowCfg?.evaluator?.atDepths ?? 'all';
|
|
970
|
-
const evaluatorAtDepths = evalAtDepths === 'all'
|
|
971
|
-
? { has: () => true }
|
|
972
|
-
: (() => {
|
|
973
|
-
const s = new Set(evalAtDepths);
|
|
974
|
-
return { has: (d) => s.has(d) };
|
|
975
|
-
})();
|
|
976
|
-
const evaluatorSystemPrompt = parseSystemPromptOverride(flowCfg?.evaluator?.systemPrompt, 'flow.evaluator.systemPrompt');
|
|
977
|
-
const plan = parseFlowPlan(flowCfg?.plan);
|
|
978
|
-
const maxParallelSteps = Number(stepper.maxParallelSteps ?? 4);
|
|
979
|
-
const maxDepth = Number(stepper.maxDepth ?? 4);
|
|
980
|
-
const tokenBudget = Number(stepper.tokenBudget ?? 1_000_000);
|
|
981
|
-
const formalizeTask = coord.formalizeTask === true;
|
|
982
|
-
// Nested composition nodes inherit the root bounds (a sub-cycle uses the same
|
|
983
|
-
// parallelism / depth / budget / safety unless the runtime threads otherwise).
|
|
984
|
-
const bounds = {
|
|
985
|
-
reviewerAtDepths,
|
|
986
|
-
evaluatorEnabled,
|
|
987
|
-
evaluatorAtDepths,
|
|
988
|
-
...(evaluatorSystemPrompt ? { evaluatorSystemPrompt } : {}),
|
|
989
|
-
maxParallelSteps,
|
|
990
|
-
maxDepth,
|
|
991
|
-
tokenBudget,
|
|
992
|
-
formalizeTask,
|
|
993
|
-
};
|
|
994
|
-
const nodes = parseCompositionNodes(flowCfg?.nodes, bounds);
|
|
995
|
-
// Static planner needs an explicit plan OR declared nodes (nodes ARE the plan).
|
|
996
|
-
if (plannerType === 'static' && !plan && !nodes)
|
|
997
|
-
throw new Error(`coordinator.flow.planner.type 'static' requires coordinator.flow.plan or coordinator.flow.nodes`);
|
|
998
|
-
return {
|
|
999
|
-
mode,
|
|
1000
|
-
reviewerAtDepths,
|
|
1001
|
-
maxParallelSteps,
|
|
1002
|
-
maxDepth,
|
|
1003
|
-
tokenBudget,
|
|
1004
|
-
knowledgeSeed,
|
|
1005
|
-
formalizeTask,
|
|
1006
|
-
flow: {
|
|
1007
|
-
// Declared root nodes ARE the plan ⇒ static at the root (honest spec).
|
|
1008
|
-
planner: (nodes ? 'static' : plannerType),
|
|
1009
|
-
granularity: granularity,
|
|
1010
|
-
executor: executorType,
|
|
1011
|
-
finalizer: 'llm',
|
|
1012
|
-
...(plannerSystemPrompt ? { plannerSystemPrompt } : {}),
|
|
1013
|
-
...(executorSystemPrompt ? { executorSystemPrompt } : {}),
|
|
1014
|
-
evaluatorEnabled,
|
|
1015
|
-
evaluatorAtDepths,
|
|
1016
|
-
...(evaluatorSystemPrompt ? { evaluatorSystemPrompt } : {}),
|
|
1017
|
-
...(plan ? { plan } : {}),
|
|
1018
|
-
...(nodes ? { nodes } : {}),
|
|
1019
|
-
},
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
134
|
+
export { parseStepperCoordinatorConfig } from './stepper-config.js';
|
|
1022
135
|
//# sourceMappingURL=config.js.map
|