@mcp-abap-adt/llm-agent-server 16.2.0 → 17.0.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.
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "16.2.0";
1
+ export declare const PACKAGE_VERSION = "17.0.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.js — do not edit
2
- export const PACKAGE_VERSION = '16.2.0';
2
+ export const PACKAGE_VERSION = '17.0.0';
3
3
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,34 @@
1
+ import type { ILlm, ISubAgent } from '@mcp-abap-adt/llm-agent';
2
+ import type { DagCoordinatorHandlerDeps } from '@mcp-abap-adt/llm-agent-libs';
3
+ import { type NormalizedLlmMap } from './config.js';
4
+ import type { SmartServerLlmConfig } from './smart-server.js';
5
+ export interface BuildDagCoordinatorDepsInput {
6
+ coordCfg: Record<string, unknown> | undefined;
7
+ llmMap: NormalizedLlmMap | undefined;
8
+ /** Adapted from `pipeline.llm.main` (already shape-normalized to
9
+ * SmartServerLlmConfig: `{ provider, apiKey, url: baseURL, model,
10
+ * temperature }`). When set, used as the final fallback in the
11
+ * role-resolution chain map[name] → map.main → pipelineFallback. */
12
+ pipelineFallback: SmartServerLlmConfig | undefined;
13
+ mainLlm: ILlm;
14
+ helperLlm: ILlm | undefined;
15
+ mainTemp: number;
16
+ registry: ReadonlyMap<string, ISubAgent>;
17
+ makeLlm: (config: SmartServerLlmConfig) => Promise<ILlm>;
18
+ warn: (msg: string) => void;
19
+ }
20
+ export type BuiltDagCoordinatorDeps = Omit<DagCoordinatorHandlerDeps, 'workers'> & {
21
+ workers: Map<string, ISubAgent>;
22
+ oracleName?: string;
23
+ };
24
+ /**
25
+ * Assemble the full deps record for `withDagCoordinator`. Returns
26
+ * `undefined` when the YAML does not declare a DAG coordinator (no
27
+ * `planner` block), so the caller can branch.
28
+ *
29
+ * Roles resolve their LLM via the chain:
30
+ * resolveLlmConfig(llmMap, name, pipelineFallback)
31
+ * → top-level llm.<name> → llm.main → pipelineFallback (pipeline.llm.main)
32
+ */
33
+ export declare function buildDagCoordinatorDeps(input: BuildDagCoordinatorDepsInput): Promise<BuiltDagCoordinatorDeps | undefined>;
34
+ //# sourceMappingURL=build-dag-coordinator-deps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-dag-coordinator-deps.d.ts","sourceRoot":"","sources":["../../src/smart-agent/build-dag-coordinator-deps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EAEJ,SAAS,EACV,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAS9E,OAAO,EAEL,KAAK,gBAAgB,EAKtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9C,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACrC;;;yEAGqE;IACrE,gBAAgB,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACnD,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,IAAI,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACzC,OAAO,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,yBAAyB,EACzB,SAAS,CACV,GAAG;IACF,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CA0I9C"}
@@ -0,0 +1,107 @@
1
+ import { AbortErrorStrategy, DagPlanInterpreter, LlmDagPlanner, LlmReviewStrategy, ReplanErrorStrategy, SubAgentStateOracle, } from '@mcp-abap-adt/llm-agent-libs';
2
+ import { buildFinalizer, resolveCoordinatorActivation, resolveLlmConfig, resolveLlmConfigStrict, resolveReviewerLlmName, } from './config.js';
3
+ /**
4
+ * Assemble the full deps record for `withDagCoordinator`. Returns
5
+ * `undefined` when the YAML does not declare a DAG coordinator (no
6
+ * `planner` block), so the caller can branch.
7
+ *
8
+ * Roles resolve their LLM via the chain:
9
+ * resolveLlmConfig(llmMap, name, pipelineFallback)
10
+ * → top-level llm.<name> → llm.main → pipelineFallback (pipeline.llm.main)
11
+ */
12
+ export async function buildDagCoordinatorDeps(input) {
13
+ const { coordCfg, llmMap, pipelineFallback, mainLlm, helperLlm, mainTemp, registry, makeLlm, warn, } = input;
14
+ if (!coordCfg || coordCfg.planner === undefined)
15
+ return undefined;
16
+ // ---- Role LLM resolver -----------------------------------------------
17
+ // Priority chain:
18
+ // 1. map[name] — explicit named entry in the top-level llm: block
19
+ // 2. 'helper' | 'planner' alias — route to the prebuilt helperLlm
20
+ // 3. pipelineFallback (pipeline.llm.main) — last resort
21
+ // 4. fallbackPrebuilt (mainLlm) — when nothing else resolves
22
+ const resolveRoleLlm = async (name, fallbackPrebuilt = mainLlm) => {
23
+ // 1. Explicit map[name] — use it (strict: no main fallback here).
24
+ const strict = resolveLlmConfigStrict(llmMap, name);
25
+ if (strict) {
26
+ return makeLlm({
27
+ ...strict,
28
+ temperature: Number(strict.temperature ?? mainTemp),
29
+ });
30
+ }
31
+ // 2. 'helper' | 'planner' alias → reuse prebuilt helperLlm.
32
+ if (name === 'helper' || name === 'planner') {
33
+ return helperLlm ?? fallbackPrebuilt;
34
+ }
35
+ // 3. Unknown name → final fallback chain via resolveLlmConfig (map.main → pipelineFallback).
36
+ if (name) {
37
+ const fb = resolveLlmConfig(llmMap, name, pipelineFallback);
38
+ if (fb) {
39
+ return makeLlm({
40
+ ...fb,
41
+ temperature: Number(fb.temperature ?? mainTemp),
42
+ });
43
+ }
44
+ }
45
+ // 4. No name at all → fallbackPrebuilt (mainLlm by default).
46
+ return fallbackPrebuilt;
47
+ };
48
+ // ---- Planner ----------------------------------------------------------
49
+ const plannerBlock = coordCfg.planner;
50
+ const plannerLlm = await resolveRoleLlm(plannerBlock?.plannerLlm);
51
+ const planner = new LlmDagPlanner(plannerLlm);
52
+ // ---- Reviewer (optional) ---------------------------------------------
53
+ let reviewer;
54
+ if (coordCfg.reviewer !== undefined) {
55
+ const reviewerBlock = coordCfg.reviewer;
56
+ const reviewerName = resolveReviewerLlmName(reviewerBlock, warn);
57
+ const reviewerLlm = await resolveRoleLlm(reviewerName);
58
+ reviewer = new LlmReviewStrategy(reviewerLlm);
59
+ }
60
+ // ---- Interpreter, workers, oracle, activation, error strategy --------
61
+ const interpreter = new DagPlanInterpreter();
62
+ const oracleName = coordCfg.stateOracle;
63
+ let rawOracle;
64
+ if (oracleName) {
65
+ rawOracle = registry.get(oracleName);
66
+ if (!rawOracle) {
67
+ throw new Error(`coordinator.stateOracle '${oracleName}' is not a declared subagent`);
68
+ }
69
+ }
70
+ const workers = new Map([...registry].filter(([name]) => name !== oracleName));
71
+ if (workers.size === 0) {
72
+ throw new Error('coordinator.planner is set (DAG mode) but no workers are configured. ' +
73
+ 'Add at least one entry under the top-level `subagents:` block.');
74
+ }
75
+ const activation = resolveCoordinatorActivation((coordCfg.activation ?? 'explicit'));
76
+ let errorStrategy;
77
+ const esCfg = coordCfg.errorStrategy;
78
+ if (esCfg?.type === 'replan') {
79
+ errorStrategy = new ReplanErrorStrategy(planner, esCfg.maxReplans);
80
+ }
81
+ else if (esCfg?.type === 'abort') {
82
+ errorStrategy = new AbortErrorStrategy();
83
+ }
84
+ // ---- Finalizer --------------------------------------------------------
85
+ const finalizer = await buildFinalizer(coordCfg.finalizer, llmMap, pipelineFallback, async (lc) => makeLlm({
86
+ ...lc,
87
+ temperature: Number(lc.temperature ?? mainTemp),
88
+ }));
89
+ // ---- Oracle wrap ------------------------------------------------------
90
+ const stateOracle = rawOracle
91
+ ? new SubAgentStateOracle(rawOracle)
92
+ : undefined;
93
+ void mainLlm;
94
+ return {
95
+ planner,
96
+ interpreter,
97
+ workers,
98
+ activation,
99
+ reviewer,
100
+ errorStrategy,
101
+ stateOracle,
102
+ finalizer,
103
+ maxRoundTrips: coordCfg.maxRoundTrips,
104
+ oracleName,
105
+ };
106
+ }
107
+ //# sourceMappingURL=build-dag-coordinator-deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-dag-coordinator-deps.js","sourceRoot":"","sources":["../../src/smart-agent/build-dag-coordinator-deps.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,45 @@
1
1
  /**
2
2
  * Shared config utilities for SmartServer.
3
3
  */
4
- import type { ILlm, ISubAgentContextBuilder, IToolSelectionStrategy } from '@mcp-abap-adt/llm-agent';
4
+ import type { IFinalizer, ILlm, ISubAgentContextBuilder, IToolSelectionStrategy } from '@mcp-abap-adt/llm-agent';
5
5
  import { AutoActivation, ExplicitActivation, HybridDispatch, OneShotPlanning, ReplanOnErrorPlanning, SelfDispatch, SkillStepsPlanning, SubAgentDispatch } from '@mcp-abap-adt/llm-agent-libs';
6
- import type { SmartServerConfig } from './smart-server.js';
6
+ import type { SmartServerConfig, SmartServerLlmConfig } from './smart-server.js';
7
+ export type LlmConfigMap = Record<string, SmartServerLlmConfig>;
8
+ export type NormalizedLlmMap = {
9
+ main: SmartServerLlmConfig;
10
+ } & LlmConfigMap;
11
+ /**
12
+ * Normalize the optional top-level `llm:` block.
13
+ * - undefined → undefined (pipeline-only configs stay valid)
14
+ * - flat shape (has `provider` | `apiKey` | `model` | `url`) → { main: flat } (backward compat)
15
+ * - map shape → must include `main`; returned as NormalizedLlmMap
16
+ */
17
+ export declare function normalizeLlmConfig(input?: SmartServerLlmConfig | LlmConfigMap): NormalizedLlmMap | undefined;
18
+ /**
19
+ * Strict lookup: returns map[name] if explicitly present, else undefined.
20
+ * Does NOT fall through to map.main. Use when the caller needs to
21
+ * detect explicit-presence (e.g. to decide between an alias and the
22
+ * named map entry).
23
+ */
24
+ export declare function resolveLlmConfigStrict(map: NormalizedLlmMap | undefined, name: string | undefined): SmartServerLlmConfig | undefined;
25
+ /**
26
+ * Resolve a per-role LLM config by name from a normalized map.
27
+ * Lookup chain: map[name] → map.main → pipelineFallback.
28
+ * When map is undefined, falls back to pipelineFallback (so pipeline-only
29
+ * configs keep working with no top-level llm: block).
30
+ *
31
+ * The caller decides whether `undefined` is an error.
32
+ */
33
+ export declare function resolveLlmConfig(map: NormalizedLlmMap | undefined, name?: string, pipelineFallback?: SmartServerLlmConfig): SmartServerLlmConfig | undefined;
34
+ /**
35
+ * Read the reviewer block's LLM-name selector, accepting both the
36
+ * preferred `reviewerLlm` field and the deprecated `plannerLlm` alias.
37
+ * When the alias is used, calls `warn(message)`.
38
+ */
39
+ export declare function resolveReviewerLlmName(block: {
40
+ reviewerLlm?: string;
41
+ plannerLlm?: string;
42
+ } | undefined, warn: (msg: string) => void): string | undefined;
7
43
  export interface YamlCoordinator {
8
44
  planning?: 'one-shot' | 'replan-on-error' | 'skill-steps';
9
45
  dispatch?: 'subagent' | 'self' | 'hybrid';
@@ -13,8 +49,41 @@ export interface YamlCoordinator {
13
49
  maxRetriesPerStep?: number;
14
50
  failPolicy?: 'abort' | 'continue';
15
51
  maxLayer?: number;
52
+ planner?: {
53
+ type?: string;
54
+ plannerLlm?: 'main' | 'planner' | 'helper';
55
+ } | Record<string, unknown>;
56
+ interpreter?: {
57
+ type?: string;
58
+ } | Record<string, unknown>;
59
+ reviewer?: {
60
+ type?: string;
61
+ reviewerLlm?: string;
62
+ plannerLlm?: 'main' | 'planner' | 'helper';
63
+ };
64
+ finalizer?: {
65
+ type?: 'passthrough' | 'llm' | 'template';
66
+ finalizerLlm?: string;
67
+ systemPrompt?: string;
68
+ };
69
+ errorStrategy?: {
70
+ type?: string;
71
+ maxReplans?: number;
72
+ };
73
+ stateOracle?: string;
74
+ maxRoundTrips?: number;
16
75
  }
76
+ /** Fail-loud guard: a coordinator block is either DAG (has `planner`) or linear,
77
+ * never mixed. `activation` is shared and always allowed. */
78
+ export declare function assertCoordinatorConfigShape(coord: Record<string, unknown>): void;
17
79
  export declare function resolveCoordinatorPlanning(name: string, plannerLlm: ILlm): OneShotPlanning | ReplanOnErrorPlanning | SkillStepsPlanning;
80
+ /**
81
+ * Default coordinator dispatch kind. Omitted → 'hybrid' for ALL planning kinds:
82
+ * agentless steps — the synthesized answer-directly step (#155) and skill steps
83
+ * without an explicit `agent:` — need a self-LLM fallback. Pin 'subagent'
84
+ * explicitly for strict subagent-only routing.
85
+ */
86
+ export declare function resolveCoordinatorDispatchKind(explicit?: 'subagent' | 'self' | 'hybrid'): 'subagent' | 'self' | 'hybrid';
18
87
  export declare function resolveCoordinatorDispatch(name: string, fallbackLlm?: ILlm, contextBuilder?: ISubAgentContextBuilder): SubAgentDispatch | SelfDispatch | HybridDispatch;
19
88
  export declare function resolveCoordinatorActivation(name: string): AutoActivation | ExplicitActivation;
20
89
  export declare function resolveToolSelectionStrategy(name: string, params?: {
@@ -48,10 +117,27 @@ export interface ResolveConfigArgs {
48
117
  'plugin-dir'?: string;
49
118
  mode?: string | boolean;
50
119
  }
51
- export declare const YAML_TEMPLATE = "port: 4004\nhost: 0.0.0.0\n\n# Request routing mode:\n# hard \u2014 Fully managed context. Ignores client history/system prompt. Uses RAG + internal MCP tools only.\n# pass \u2014 Transparent proxy. Logs everything but modifies nothing.\n# smart \u2014 Hybrid. Preserves client history but enriches it with RAG context and MCP tools based on analysis. (default)\nmode: smart\n\nllm:\n provider: deepseek # deepseek | openai | anthropic | sap-ai-sdk | ollama\n apiKey: ${DEEPSEEK_API_KEY} # not required for ollama / sap-ai-sdk\n model: deepseek-chat\n temperature: 0.7\n classifierTemperature: 0.1\n\nrag:\n type: in-memory # in-memory | qdrant | hana-vector | pg-vector\n embedder: ollama # Embedder to use: ollama | openai | sap-ai-core | <custom>\n url: http://localhost:11434\n model: bge-m3\n # resourceGroup: default # SAP AI Core resource group (sap-ai-core embedder)\n # scenario: orchestration # SAP AI Core scenario: orchestration (default) | foundation-models\n # collectionName: llm-agent # Collection/table name (qdrant | hana-vector | pg-vector)\n dedupThreshold: 0.92\n vectorWeight: 0.7 # Semantic similarity weight (0..1)\n keywordWeight: 0.3 # Lexical matching weight (0..1)\n\nmcp:\n # type: none | http | stdio\n # To disable MCP, set type to 'none'\n type: http\n url: http://localhost:3001/mcp/stream/http\n\nagent:\n externalToolsValidationMode: permissive # permissive | strict\n maxIterations: 10\n maxToolCalls: 30\n toolUnavailableTtlMs: 600000 # Temporary tool blacklist TTL (ms)\n ragQueryK: 10\n # contextBudgetTokens: 4000 # Max tokens for RAG context in system prompt (0 = no limit)\n # semanticHistoryEnabled: false # Enable semantic history via RAG\n # historyRecencyWindow: 4 # Last N messages from client history in LLM context\n # historyTurnSummaryPrompt: \"...\" # LLM prompt for turn summarization\n showReasoning: false # Explain strategy at start of response\n historyAutoSummarizeLimit: 10 # History length to trigger compression\n queryExpansionEnabled: false # Expand RAG queries with LLM-generated synonyms\n toolResultCacheTtlMs: 300000 # Tool result cache TTL (ms); 0 to disable\n sessionTokenBudget: 0 # Multi-turn token budget; 0 to disable\n # ragTranslateEnabled: true # Translate non-ASCII RAG queries to English (default: true)\n # classificationEnabled: false # Enable for custom pipelines with multi-store routing\n # toolReselectPerIteration: false # Re-select tools via RAG on each tool-loop iteration\n # llmCallStrategy: streaming # streaming | non-streaming | fallback\n # streamMode: full # full | final \u2014 streaming behavior for tool loops\n # heartbeatIntervalMs: 5000 # SSE heartbeat interval during tool execution (ms)\n # healthTimeoutMs: 5000 # Health check probe timeout (ms); increase for slow providers (SAP AI Core: 15000)\n # retry: # LLM retry config for 429/5xx errors\n # maxAttempts: 3\n # backoffMs: 1000\n # retryOn: [429, 500, 502, 503]\n # retryOnMidStream: ['SSE stream'] # Substrings triggering mid-stream retry\n\n# --- Advanced Multi-Model Pipeline (optional) -------------------------------\n# Use this section to assign different models for different internal tasks.\n# pipeline:\n# llm:\n# main:\n# provider: deepseek # deepseek | openai | anthropic | sap-ai-sdk\n# apiKey: ${DEEPSEEK_API_KEY}\n# model: deepseek-chat\n# temperature: 0.7\n# streaming: true # false to disable streaming for this provider\n# classifier: # optional; if absent, main config is reused\n# provider: deepseek\n# apiKey: ${DEEPSEEK_API_KEY}\n# model: deepseek-chat\n# temperature: 0.1\n# helper: # optional; if absent, main config is reused\n# provider: deepseek\n# apiKey: ${DEEPSEEK_API_KEY}\n# model: deepseek-chat\n# temperature: 0.1\n#\n# rag:\n# tools:\n# type: qdrant\n# url: http://qdrant:6333\n# embedder: openai # ollama | openai | <custom registered name>\n# model: text-embedding-3-small\n# apiKey: ${OPENAI_API_KEY}\n# history:\n# type: in-memory\n#\n# mcp:\n# - type: http\n# url: http://localhost:3001/mcp/stream/http\n\n# --- Structured Pipeline (optional) -------------------------------------------\n# Replaces the hardcoded orchestration flow with a YAML-defined stage tree.\n# When absent, the default flow runs unchanged (full backwards compatibility).\n#\n# pipeline:\n# version: \"1\"\n# stages:\n# - id: classify\n# type: classify\n# - id: summarize\n# type: summarize\n# - id: rag-retrieval\n# type: parallel\n# when: \"shouldRetrieve\"\n# stages:\n# - { id: translate, type: translate }\n# - { id: expand, type: expand }\n# after:\n# - id: rag-queries\n# type: parallel\n# stages:\n# - { id: tools, type: rag-query, config: { store: tools, k: 10 } }\n# - { id: history, type: rag-query, config: { store: history, k: 5 } }\n# - { id: rerank, type: rerank }\n# - { id: tool-select, type: tool-select }\n# - id: assemble\n# type: assemble\n# - id: tool-loop\n# type: tool-loop\n\n# prompts:\n# system: \"You are a helpful assistant specialized in SAP ABAP development.\"\n# classifier: |\n# You are an intent classifier... (see source for full default prompt)\n# reasoning: |\n# IMPORTANT: Always start your response with a brief <reasoning> block...\n# ragTranslate: |\n# Translate the user request to English for search purposes...\n# historySummary: |\n# Summarize the conversation so far...\n\nlog: smart-server.log # path to log file; omit for stdout\n# logDir: sessions # Directory for detailed session debug logs\n# pluginDir: ./my-plugins # Additional plugin directory (loaded after defaults)\n\n# subagents: # Optional: nested agents callable from pipeline\n# - name: code-reviewer # Used as stage config: { agent: code-reviewer }\n# description: | # Optional. Shown to the Coordinator planner LLM\n# Reviews code and returns # so it can pick this agent for the right step.\n# structured JSON.\n# config: ./agents/code-reviewer.yaml\n\n# coordinator: # Optional: enable autonomous plan-execute loop\n# planning: one-shot # one-shot | replan-on-error | skill-steps\n# dispatch: subagent # subagent | self | hybrid\n# activation: explicit # explicit (default) | auto\n# plannerLlm: main # main | planner | helper (unused by skill-steps)\n# maxSteps: 12\n# maxRetriesPerStep: 1\n# failPolicy: abort # abort | continue\n# maxLayer: 1 # Max nested-dispatch depth (default 1)\n";
120
+ export declare const YAML_TEMPLATE = "port: 4004\nhost: 0.0.0.0\n\n# Request routing mode:\n# hard \u2014 Fully managed context. Ignores client history/system prompt. Uses RAG + internal MCP tools only.\n# pass \u2014 Transparent proxy. Logs everything but modifies nothing.\n# smart \u2014 Hybrid. Preserves client history but enriches it with RAG context and MCP tools based on analysis. (default)\nmode: smart\n\nllm:\n provider: deepseek # deepseek | openai | anthropic | sap-ai-sdk | ollama\n apiKey: ${DEEPSEEK_API_KEY} # not required for ollama / sap-ai-sdk\n model: deepseek-chat\n temperature: 0.7\n classifierTemperature: 0.1\n\nrag:\n type: in-memory # in-memory | qdrant | hana-vector | pg-vector\n embedder: ollama # Embedder to use: ollama | openai | sap-ai-core | <custom>\n url: http://localhost:11434\n model: bge-m3\n # resourceGroup: default # SAP AI Core resource group (sap-ai-core embedder)\n # scenario: orchestration # SAP AI Core scenario: orchestration (default) | foundation-models\n # collectionName: llm-agent # Collection/table name (qdrant | hana-vector | pg-vector)\n dedupThreshold: 0.92\n vectorWeight: 0.7 # Semantic similarity weight (0..1)\n keywordWeight: 0.3 # Lexical matching weight (0..1)\n\nmcp:\n # type: none | http | stdio\n # To disable MCP, set type to 'none'\n type: http\n url: http://localhost:3001/mcp/stream/http\n\nagent:\n externalToolsValidationMode: permissive # permissive | strict\n maxIterations: 10\n maxToolCalls: 30\n toolUnavailableTtlMs: 600000 # Temporary tool blacklist TTL (ms)\n ragQueryK: 10\n # contextBudgetTokens: 4000 # Max tokens for RAG context in system prompt (0 = no limit)\n # semanticHistoryEnabled: false # Enable semantic history via RAG\n # historyRecencyWindow: 4 # Last N messages from client history in LLM context\n # historyTurnSummaryPrompt: \"...\" # LLM prompt for turn summarization\n showReasoning: false # Explain strategy at start of response\n historyAutoSummarizeLimit: 10 # History length to trigger compression\n queryExpansionEnabled: false # Expand RAG queries with LLM-generated synonyms\n toolResultCacheTtlMs: 300000 # Tool result cache TTL (ms); 0 to disable\n sessionTokenBudget: 0 # Multi-turn token budget; 0 to disable\n # ragTranslateEnabled: true # Translate non-ASCII RAG queries to English (default: true)\n # classificationEnabled: false # Enable for custom pipelines with multi-store routing\n # toolReselectPerIteration: false # Re-select tools via RAG on each tool-loop iteration\n # llmCallStrategy: streaming # streaming | non-streaming | fallback\n # streamMode: full # full | final \u2014 streaming behavior for tool loops\n # heartbeatIntervalMs: 5000 # SSE heartbeat interval during tool execution (ms)\n # healthTimeoutMs: 5000 # Health check probe timeout (ms); increase for slow providers (SAP AI Core: 15000)\n # retry: # LLM retry config for 429/5xx errors\n # maxAttempts: 3\n # backoffMs: 1000\n # retryOn: [429, 500, 502, 503]\n # retryOnMidStream: ['SSE stream'] # Substrings triggering mid-stream retry\n\n# --- Advanced Multi-Model Pipeline (optional) -------------------------------\n# Use this section to assign different models for different internal tasks.\n# pipeline:\n# llm:\n# main:\n# provider: deepseek # deepseek | openai | anthropic | sap-ai-sdk\n# apiKey: ${DEEPSEEK_API_KEY}\n# model: deepseek-chat\n# temperature: 0.7\n# streaming: true # false to disable streaming for this provider\n# classifier: # optional; if absent, main config is reused\n# provider: deepseek\n# apiKey: ${DEEPSEEK_API_KEY}\n# model: deepseek-chat\n# temperature: 0.1\n# helper: # optional; if absent, main config is reused\n# provider: deepseek\n# apiKey: ${DEEPSEEK_API_KEY}\n# model: deepseek-chat\n# temperature: 0.1\n#\n# rag:\n# tools:\n# type: qdrant\n# url: http://qdrant:6333\n# embedder: openai # ollama | openai | <custom registered name>\n# model: text-embedding-3-small\n# apiKey: ${OPENAI_API_KEY}\n# history:\n# type: in-memory\n#\n# mcp:\n# - type: http\n# url: http://localhost:3001/mcp/stream/http\n\n# --- Structured Pipeline (optional) -------------------------------------------\n# Replaces the hardcoded orchestration flow with a YAML-defined stage tree.\n# When absent, the default flow runs unchanged (full backwards compatibility).\n#\n# pipeline:\n# version: \"1\"\n# stages:\n# - id: classify\n# type: classify\n# - id: summarize\n# type: summarize\n# - id: rag-retrieval\n# type: parallel\n# when: \"shouldRetrieve\"\n# stages:\n# - { id: translate, type: translate }\n# - { id: expand, type: expand }\n# after:\n# - id: rag-queries\n# type: parallel\n# stages:\n# - { id: tools, type: rag-query, config: { store: tools, k: 10 } }\n# - { id: history, type: rag-query, config: { store: history, k: 5 } }\n# - { id: rerank, type: rerank }\n# - { id: tool-select, type: tool-select }\n# - id: assemble\n# type: assemble\n# - id: tool-loop\n# type: tool-loop\n\n# prompts:\n# system: \"You are a helpful assistant specialized in SAP ABAP development.\"\n# classifier: |\n# You are an intent classifier... (see source for full default prompt)\n# reasoning: |\n# IMPORTANT: Always start your response with a brief <reasoning> block...\n# ragTranslate: |\n# Translate the user request to English for search purposes...\n# historySummary: |\n# Summarize the conversation so far...\n\nlog: smart-server.log # path to log file; omit for stdout\n# logDir: sessions # Directory for detailed session debug logs\n# pluginDir: ./my-plugins # Additional plugin directory (loaded after defaults)\n\n# subagents: # Optional: nested agents callable from pipeline\n# - name: code-reviewer # Used as stage config: { agent: code-reviewer }\n# description: | # Optional. Shown to the Coordinator planner LLM\n# Reviews code and returns # so it can pick this agent for the right step.\n# structured JSON.\n# config: ./agents/code-reviewer.yaml\n\n# coordinator: # Optional: enable autonomous plan-execute loop\n# planning: one-shot # one-shot | replan-on-error | skill-steps\n# dispatch: subagent # subagent | self | hybrid\n# activation: explicit # explicit (default) | auto\n# plannerLlm: main # main | planner | helper (unused by skill-steps)\n# maxSteps: 12\n# maxRetriesPerStep: 1\n# failPolicy: abort # abort | continue\n# maxLayer: 1 # DEPRECATED \u2014 accepted but ignored (nested\n# # dispatch removed; subagents are leaves)\n";
52
121
  export declare function resolveEnvVars(value: unknown, env?: NodeJS.ProcessEnv): unknown;
53
122
  export declare function loadYamlConfig(filePath: string, env?: NodeJS.ProcessEnv): YamlConfig;
54
123
  export declare function generateConfigTemplate(outputPath: string): void;
124
+ export type FinalizerYaml = {
125
+ type?: 'passthrough' | 'llm' | 'template';
126
+ finalizerLlm?: string;
127
+ systemPrompt?: string;
128
+ };
129
+ /**
130
+ * Build the IFinalizer impl from `coordinator.finalizer:` YAML.
131
+ *
132
+ * Lookup chain for `type: llm`:
133
+ * resolveLlmConfig(llmMap, cfg.finalizerLlm, pipelineFallback)
134
+ * → top-level llm.<name> → llm.main → pipelineFallback (pipeline.llm.main)
135
+ * → ConfigError if all three are missing.
136
+ *
137
+ * Absent block / `type: passthrough` → PassthroughFinalizer.
138
+ * `type: template` → TemplateFinalizer.
139
+ */
140
+ export declare function buildFinalizer(cfg: FinalizerYaml | undefined, llmMap: NormalizedLlmMap | undefined, pipelineFallback: SmartServerLlmConfig | undefined, makeLlm: (config: SmartServerLlmConfig) => Promise<ILlm>): Promise<IFinalizer>;
55
141
  export interface ResolveSmartServerConfigOptions {
56
142
  /**
57
143
  * Filesystem path of the YAML config that produced `yaml`. Required for
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/smart-agent/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/smart-agent/config.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,172 @@
3
3
  */
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
- import { AutoActivation, ExplicitActivation, HybridDispatch, OneShotPlanning, ReplanOnErrorPlanning, ScoreThresholdToolSelection, SelfDispatch, SkillStepsPlanning, SubAgentDispatch, TopKToolSelection, } from '@mcp-abap-adt/llm-agent-libs';
6
+ import { AutoActivation, ExplicitActivation, HybridDispatch, LlmFinalizer, OneShotPlanning, PassthroughFinalizer, ReplanOnErrorPlanning, ScoreThresholdToolSelection, SelfDispatch, SkillStepsPlanning, SubAgentDispatch, TemplateFinalizer, TopKToolSelection, } from '@mcp-abap-adt/llm-agent-libs';
7
7
  import { parse as parseYaml } from 'yaml';
8
+ /**
9
+ * Detect whether an object is a flat SmartServerLlmConfig shape.
10
+ * Flat shape is identified by the presence of ANY of the known flat-shape
11
+ * fields: `provider`, `apiKey`, `model`, or `url`. This covers keyless
12
+ * providers (Ollama, SAP AI Core) that omit `apiKey` — using `apiKey`-only
13
+ * detection silently misclassified those configs as a "map" shape.
14
+ */
15
+ function isFlatLlmConfig(input) {
16
+ const flat = input;
17
+ return (typeof flat.provider === 'string' ||
18
+ typeof flat.apiKey === 'string' ||
19
+ typeof flat.model === 'string' ||
20
+ typeof flat.url === 'string');
21
+ }
22
+ /**
23
+ * Normalize the optional top-level `llm:` block.
24
+ * - undefined → undefined (pipeline-only configs stay valid)
25
+ * - flat shape (has `provider` | `apiKey` | `model` | `url`) → { main: flat } (backward compat)
26
+ * - map shape → must include `main`; returned as NormalizedLlmMap
27
+ */
28
+ export function normalizeLlmConfig(input) {
29
+ if (input === undefined)
30
+ return undefined;
31
+ if (isFlatLlmConfig(input)) {
32
+ return { main: input };
33
+ }
34
+ const map = input;
35
+ if (!map.main) {
36
+ throw new Error("llm: map must include a 'main' key (default LLM for unspecified roles)");
37
+ }
38
+ return map;
39
+ }
40
+ /**
41
+ * Strict lookup: returns map[name] if explicitly present, else undefined.
42
+ * Does NOT fall through to map.main. Use when the caller needs to
43
+ * detect explicit-presence (e.g. to decide between an alias and the
44
+ * named map entry).
45
+ */
46
+ export function resolveLlmConfigStrict(map, name) {
47
+ if (!map || !name)
48
+ return undefined;
49
+ return map[name];
50
+ }
51
+ /**
52
+ * Resolve a per-role LLM config by name from a normalized map.
53
+ * Lookup chain: map[name] → map.main → pipelineFallback.
54
+ * When map is undefined, falls back to pipelineFallback (so pipeline-only
55
+ * configs keep working with no top-level llm: block).
56
+ *
57
+ * The caller decides whether `undefined` is an error.
58
+ */
59
+ export function resolveLlmConfig(map, name, pipelineFallback) {
60
+ if (!map)
61
+ return pipelineFallback;
62
+ if (!name || name === 'main')
63
+ return map.main;
64
+ return map[name] ?? map.main;
65
+ }
66
+ /**
67
+ * Read the reviewer block's LLM-name selector, accepting both the
68
+ * preferred `reviewerLlm` field and the deprecated `plannerLlm` alias.
69
+ * When the alias is used, calls `warn(message)`.
70
+ */
71
+ export function resolveReviewerLlmName(block, warn) {
72
+ if (!block)
73
+ return undefined;
74
+ if (typeof block.reviewerLlm === 'string')
75
+ return block.reviewerLlm;
76
+ if (typeof block.plannerLlm === 'string') {
77
+ warn("coordinator.reviewer.plannerLlm is deprecated; rename to 'reviewerLlm'");
78
+ return block.plannerLlm;
79
+ }
80
+ return undefined;
81
+ }
82
+ const LINEAR_ONLY = [
83
+ 'planning',
84
+ 'dispatch',
85
+ 'maxSteps',
86
+ 'maxRetriesPerStep',
87
+ 'failPolicy',
88
+ 'maxLayer',
89
+ 'plannerLlm',
90
+ ];
91
+ const DAG_ONLY = [
92
+ 'planner',
93
+ 'interpreter',
94
+ 'reviewer',
95
+ 'errorStrategy',
96
+ 'finalizer',
97
+ 'stateOracle',
98
+ 'maxRoundTrips',
99
+ ];
100
+ /** Validate a coordinator role block shape. */
101
+ function assertLlmRoleShape(label, role) {
102
+ if (typeof role !== 'object' || role === null || Array.isArray(role)) {
103
+ throw new Error(`coordinator.${label} must be an object (e.g. { type: llm }), got: ${JSON.stringify(role)}`);
104
+ }
105
+ const kind = role.type;
106
+ if (kind !== undefined &&
107
+ kind !== 'llm' &&
108
+ !(label === 'finalizer' && (kind === 'passthrough' || kind === 'template'))) {
109
+ throw new Error(`coordinator.${label}: unknown type '${String(kind)}'`);
110
+ }
111
+ for (const field of ['plannerLlm', 'reviewerLlm', 'finalizerLlm']) {
112
+ const sel = role[field];
113
+ if (sel !== undefined && typeof sel !== 'string') {
114
+ throw new Error(`coordinator.${label}.${field} must be a string referencing an llm.* key, got: ${String(sel)}`);
115
+ }
116
+ }
117
+ const sp = role.systemPrompt;
118
+ if (sp !== undefined && typeof sp !== 'string') {
119
+ throw new Error(`coordinator.${label}.systemPrompt must be a string, got: ${String(sp)}`);
120
+ }
121
+ }
122
+ function assertErrorStrategyShape(es) {
123
+ if (typeof es !== 'object' || es === null || Array.isArray(es)) {
124
+ throw new Error(`coordinator.errorStrategy must be an object (e.g. { type: replan }), got: ${JSON.stringify(es)}`);
125
+ }
126
+ const type = es.type;
127
+ if (type !== undefined && type !== 'abort' && type !== 'replan') {
128
+ throw new Error(`coordinator.errorStrategy: unknown type '${String(type)}' (only 'abort' | 'replan')`);
129
+ }
130
+ const mr = es.maxReplans;
131
+ if (mr !== undefined && (typeof mr !== 'number' || mr < 0)) {
132
+ throw new Error(`coordinator.errorStrategy.maxReplans must be a non-negative number, got: ${String(mr)}`);
133
+ }
134
+ }
135
+ /** Fail-loud guard: a coordinator block is either DAG (has `planner`) or linear,
136
+ * never mixed. `activation` is shared and always allowed. */
137
+ export function assertCoordinatorConfigShape(coord) {
138
+ const isDag = coord.planner !== undefined;
139
+ if (isDag) {
140
+ assertLlmRoleShape('planner', coord.planner);
141
+ if (coord.reviewer !== undefined) {
142
+ assertLlmRoleShape('reviewer', coord.reviewer);
143
+ }
144
+ if (coord.finalizer !== undefined) {
145
+ assertLlmRoleShape('finalizer', coord.finalizer);
146
+ }
147
+ if (coord.errorStrategy !== undefined) {
148
+ assertErrorStrategyShape(coord.errorStrategy);
149
+ }
150
+ if (coord.stateOracle !== undefined &&
151
+ typeof coord.stateOracle !== 'string') {
152
+ throw new Error(`coordinator.stateOracle must be a string (a declared subagent name), got: ${JSON.stringify(coord.stateOracle)}`);
153
+ }
154
+ if (coord.maxRoundTrips !== undefined &&
155
+ (typeof coord.maxRoundTrips !== 'number' || coord.maxRoundTrips < 0)) {
156
+ throw new Error(`coordinator.maxRoundTrips must be a non-negative number, got: ${String(coord.maxRoundTrips)}`);
157
+ }
158
+ for (const f of LINEAR_ONLY) {
159
+ if (coord[f] !== undefined) {
160
+ throw new Error(`coordinator: '${f}' is a linear-only field and cannot be combined with 'planner' (DAG mode)`);
161
+ }
162
+ }
163
+ }
164
+ else {
165
+ for (const f of DAG_ONLY) {
166
+ if (coord[f] !== undefined) {
167
+ throw new Error(`coordinator: '${f}' is a DAG-only field; a linear coordinator uses 'planning'/'dispatch'`);
168
+ }
169
+ }
170
+ }
171
+ }
8
172
  export function resolveCoordinatorPlanning(name, plannerLlm) {
9
173
  switch (name) {
10
174
  case 'one-shot':
@@ -20,6 +184,15 @@ export function resolveCoordinatorPlanning(name, plannerLlm) {
20
184
  throw new Error(`Unknown coordinator.planning strategy: '${name}'. Allowed: one-shot, replan-on-error, skill-steps.`);
21
185
  }
22
186
  }
187
+ /**
188
+ * Default coordinator dispatch kind. Omitted → 'hybrid' for ALL planning kinds:
189
+ * agentless steps — the synthesized answer-directly step (#155) and skill steps
190
+ * without an explicit `agent:` — need a self-LLM fallback. Pin 'subagent'
191
+ * explicitly for strict subagent-only routing.
192
+ */
193
+ export function resolveCoordinatorDispatchKind(explicit) {
194
+ return explicit ?? 'hybrid';
195
+ }
23
196
  export function resolveCoordinatorDispatch(name, fallbackLlm, contextBuilder) {
24
197
  switch (name) {
25
198
  case 'subagent':
@@ -241,7 +414,8 @@ log: smart-server.log # path to log file; omit for stdout
241
414
  # maxSteps: 12
242
415
  # maxRetriesPerStep: 1
243
416
  # failPolicy: abort # abort | continue
244
- # maxLayer: 1 # Max nested-dispatch depth (default 1)
417
+ # maxLayer: 1 # DEPRECATED accepted but ignored (nested
418
+ # # dispatch removed; subagents are leaves)
245
419
  `;
246
420
  export function resolveEnvVars(value, env = process.env) {
247
421
  if (typeof value === 'string')
@@ -262,6 +436,33 @@ export function loadYamlConfig(filePath, env = process.env) {
262
436
  export function generateConfigTemplate(outputPath) {
263
437
  fs.writeFileSync(outputPath, YAML_TEMPLATE, 'utf8');
264
438
  }
439
+ /**
440
+ * Build the IFinalizer impl from `coordinator.finalizer:` YAML.
441
+ *
442
+ * Lookup chain for `type: llm`:
443
+ * resolveLlmConfig(llmMap, cfg.finalizerLlm, pipelineFallback)
444
+ * → top-level llm.<name> → llm.main → pipelineFallback (pipeline.llm.main)
445
+ * → ConfigError if all three are missing.
446
+ *
447
+ * Absent block / `type: passthrough` → PassthroughFinalizer.
448
+ * `type: template` → TemplateFinalizer.
449
+ */
450
+ export async function buildFinalizer(cfg, llmMap, pipelineFallback, makeLlm) {
451
+ const kind = cfg?.type ?? 'passthrough';
452
+ if (kind === 'passthrough')
453
+ return new PassthroughFinalizer();
454
+ if (kind === 'template')
455
+ return new TemplateFinalizer();
456
+ // kind === 'llm'
457
+ const resolved = resolveLlmConfig(llmMap, cfg?.finalizerLlm, pipelineFallback);
458
+ if (!resolved) {
459
+ throw new Error('coordinator.finalizer (type: llm) requires an LLM config: provide top-level llm.<name>, llm.main, or pipeline.llm.main');
460
+ }
461
+ const llm = await makeLlm(resolved);
462
+ return new LlmFinalizer(llm, {
463
+ systemPrompt: cfg?.systemPrompt,
464
+ });
465
+ }
265
466
  const get = (obj, ...keys) => keys.reduce((o, k) => {
266
467
  if (o !== null && typeof o === 'object' && k in o) {
267
468
  return o[k];
@@ -335,7 +536,10 @@ function checkRagStore(label, store, issues) {
335
536
  issues.push(`${label}.model: required when an embedder is used (e.g. bge-m3 for ollama)`);
336
537
  }
337
538
  }
338
- function validateResolvedConfig(resolved, yaml, env) {
539
+ function validateLlmEntry(label, cfg, required, env, issues) {
540
+ checkLlmRole(label, cfg, required, env, issues);
541
+ }
542
+ function validateResolvedConfig(_resolved, yaml, env) {
339
543
  const issues = [];
340
544
  const usingPipeline = !!get(yaml, 'pipeline', 'llm', 'main');
341
545
  if (usingPipeline) {
@@ -349,11 +553,34 @@ function validateResolvedConfig(resolved, yaml, env) {
349
553
  }
350
554
  }
351
555
  else {
352
- checkLlmRole('llm', {
353
- provider: resolved.llm.provider,
354
- apiKey: resolved.llm.apiKey,
355
- model: resolved.llm.model,
356
- }, true, env, issues);
556
+ // Read from the raw YAML so we can distinguish flat vs map shape.
557
+ // `resolved.llm` is always constructed as a flat object by resolveSmartServerConfig,
558
+ // so it cannot be used to detect the map shape.
559
+ const rawLlm = get(yaml, 'llm');
560
+ if (rawLlm === undefined) {
561
+ // No top-level llm: AND no pipeline.llm.main → would have been caught
562
+ // by `usingPipeline` branch. Defensive: surface a clear issue.
563
+ issues.push('llm: required when pipeline.llm.main is not configured');
564
+ }
565
+ else if (typeof rawLlm.provider === 'string') {
566
+ // Flat shape — existing behaviour.
567
+ validateLlmEntry('llm', rawLlm, true, env, issues);
568
+ }
569
+ else {
570
+ // Map shape — llm.main is required; every named entry is validated.
571
+ const map = rawLlm;
572
+ if (!map.main) {
573
+ issues.push("llm.main: required when 'llm' is a named map");
574
+ }
575
+ else {
576
+ validateLlmEntry('llm.main', map.main, true, env, issues);
577
+ }
578
+ for (const [name, entry] of Object.entries(map)) {
579
+ if (name === 'main')
580
+ continue;
581
+ validateLlmEntry(`llm.${name}`, entry, true, env, issues);
582
+ }
583
+ }
357
584
  }
358
585
  if (get(yaml, 'mcp')) {
359
586
  const mcpType = get(yaml, 'mcp', 'type');
@@ -478,14 +705,18 @@ export function resolveSmartServerConfig(args = {}, yaml = {}, env = process.env
478
705
  const resolved = {
479
706
  port: Number(args.port ?? get(yaml, 'port') ?? env.PORT ?? 4004),
480
707
  host: args.host ?? get(yaml, 'host') ?? '0.0.0.0',
481
- llm: {
482
- provider: get(yaml, 'llm', 'provider'),
483
- apiKey,
484
- url: get(yaml, 'llm', 'url'),
485
- model: get(yaml, 'llm', 'model'),
486
- temperature: Number(get(yaml, 'llm', 'temperature') ?? 0.7),
487
- classifierTemperature: Number(get(yaml, 'llm', 'classifierTemperature') ?? 0.1),
488
- },
708
+ llm: get(yaml, 'llm')
709
+ ? typeof get(yaml, 'llm', 'provider') === 'string'
710
+ ? {
711
+ provider: get(yaml, 'llm', 'provider'),
712
+ apiKey,
713
+ url: get(yaml, 'llm', 'url'),
714
+ model: get(yaml, 'llm', 'model'),
715
+ temperature: Number(get(yaml, 'llm', 'temperature') ?? 0.7),
716
+ classifierTemperature: Number(get(yaml, 'llm', 'classifierTemperature') ?? 0.1),
717
+ }
718
+ : get(yaml, 'llm')
719
+ : undefined,
489
720
  rag: get(yaml, 'rag')
490
721
  ? {
491
722
  type: get(yaml, 'rag', 'type'),