@gt-fe/eap-runtime 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +3 -1
  2. package/dist/api/handlers/invoke.handler.d.ts +1 -1
  3. package/dist/api/handlers/invoke.handler.js +3 -0
  4. package/dist/api/handlers/stream.handler.d.ts +1 -1
  5. package/dist/api/handlers/stream.handler.js +3 -0
  6. package/dist/common/index.d.ts +1 -0
  7. package/dist/common/index.js +1 -0
  8. package/dist/common/logger.d.ts +16 -4
  9. package/dist/common/logger.js +82 -16
  10. package/dist/common/runtime-package-version.d.ts +2 -0
  11. package/dist/common/runtime-package-version.js +5 -0
  12. package/dist/debug/llm-turn-log.d.ts +12 -0
  13. package/dist/debug/llm-turn-log.js +1 -0
  14. package/dist/graph/adapters/langgraph.adapter.d.ts +3 -1
  15. package/dist/graph/adapters/langgraph.adapter.js +66 -13
  16. package/dist/graph/adapters/subgraph-compiler.js +5 -2
  17. package/dist/graph/contracts/executable-graph.d.ts +13 -2
  18. package/dist/graph/nodes/llm-node.d.ts +3 -0
  19. package/dist/graph/nodes/llm-node.js +8 -0
  20. package/dist/graph/nodes/skill-bind-node.d.ts +3 -4
  21. package/dist/graph/nodes/skill-bind-node.js +1 -20
  22. package/dist/graph/nodes/tool-node.d.ts +2 -0
  23. package/dist/graph/nodes/tool-node.js +20 -14
  24. package/dist/memory/compression/index.d.ts +1 -1
  25. package/dist/memory/compression/index.js +1 -1
  26. package/dist/memory/compression/sliding-window.compression.js +7 -7
  27. package/dist/memory/index.d.ts +1 -1
  28. package/dist/memory/index.js +1 -1
  29. package/dist/memory/message-compression.service.d.ts +12 -1
  30. package/dist/memory/message-compression.service.js +29 -1
  31. package/dist/providers/http-llm-provider.d.ts +1 -0
  32. package/dist/providers/http-llm-provider.js +4 -1
  33. package/dist/providers/index.d.ts +1 -1
  34. package/dist/providers/index.js +1 -1
  35. package/dist/providers/local-llm-provider.d.ts +3 -0
  36. package/dist/providers/local-llm-provider.js +14 -2
  37. package/dist/providers/routing-llm-provider.js +12 -3
  38. package/dist/registry/deployment-snapshot.js +18 -16
  39. package/dist/registry/graph-loader.d.ts +10 -0
  40. package/dist/registry/graph-loader.js +115 -66
  41. package/dist/runtime/agent-runtime.d.ts +3 -3
  42. package/dist/runtime/agent-runtime.js +13 -3
  43. package/dist/runtime/dev-runtime.d.ts +2 -0
  44. package/dist/runtime/dev-runtime.js +2 -0
  45. package/dist/runtime/types.d.ts +2 -2
  46. package/dist/session/compression-middleware.d.ts +2 -1
  47. package/dist/session/compression-middleware.js +7 -4
  48. package/dist/sse/events/stats-events.d.ts +9 -0
  49. package/dist/token/compression-budget-integration.d.ts +1 -1
  50. package/dist/token/compression-budget-integration.js +18 -10
  51. package/dist/token/context-window.d.ts +42 -0
  52. package/dist/token/context-window.js +73 -0
  53. package/dist/token/index.d.ts +1 -0
  54. package/dist/token/index.js +1 -0
  55. package/dist/tool/dev-http-executor.d.ts +1 -0
  56. package/dist/tool/dev-http-executor.js +12 -0
  57. package/dist/tool/dev-mcp-executor.d.ts +11 -1
  58. package/dist/tool/dev-mcp-executor.js +26 -0
  59. package/dist/tool/dev-tool-executor.d.ts +2 -1
  60. package/dist/tool/dev-tool-executor.js +6 -0
  61. package/dist/tool/harness-command.js +1 -1
  62. package/dist/tool/harness-primitives.d.ts +7 -0
  63. package/dist/tool/harness-primitives.js +56 -5
  64. package/dist/tool/index.d.ts +2 -1
  65. package/dist/tool/index.js +1 -0
  66. package/dist/tool/local-mcp-client.d.ts +4 -1
  67. package/dist/tool/local-mcp-client.js +130 -3
  68. package/dist/tool/local-tool-executor.d.ts +2 -1
  69. package/dist/tool/local-tool-executor.js +15 -0
  70. package/dist/tool/mcp-list.d.ts +53 -0
  71. package/dist/tool/mcp-list.js +199 -0
  72. package/dist/tool/mcp-tool-executor.d.ts +6 -1
  73. package/dist/tool/mcp-tool-executor.js +52 -0
  74. package/dist/tool/skill-dir-infer.d.ts +17 -0
  75. package/dist/tool/skill-dir-infer.js +81 -0
  76. package/dist/tool/skill-script-refs.d.ts +1 -8
  77. package/dist/tool/skill-script-refs.js +32 -20
  78. package/dist/tool/system-prompt-builder.d.ts +35 -16
  79. package/dist/tool/system-prompt-builder.js +30 -58
  80. package/dist/tool/timeout.js +4 -1
  81. package/dist/tool/tool-execution-router.d.ts +2 -1
  82. package/dist/tool/tool-execution-router.js +7 -0
  83. package/dist/tool/tool-executor.interface.d.ts +11 -0
  84. package/package.json +6 -33
  85. package/dist/registry/mcp-stdio-cwd.d.ts +0 -5
  86. package/dist/registry/mcp-stdio-cwd.js +0 -13
@@ -10,6 +10,8 @@
10
10
  * @see requirements.md Requirement 1 AC6, Requirement 9
11
11
  */
12
12
  import { formatUnknownError } from '../../utils/format-error.js';
13
+ import { buildContextUsage, turnSectionChars } from '../../token/context-window.js';
14
+ import { isAbortError } from '../../tool/timeout.js';
13
15
  // ─────────────────────────────────────────────
14
16
  // Default retry config
15
17
  // ─────────────────────────────────────────────
@@ -94,6 +96,7 @@ export class LLMNode {
94
96
  // (即 user 消息重复的根因)。
95
97
  //
96
98
  // __llmTokenUsage channel 同样是 **数组累加器 reducer**,每次追加一条使用明细。
99
+ const context = buildContextUsage(response.inputTokens, this.config.maxContextTokens, turnSectionChars(this.config, messages));
97
100
  return {
98
101
  messages: [assistantMessage],
99
102
  currentNode: this.nodeId,
@@ -104,6 +107,7 @@ export class LLMNode {
104
107
  inputTokens: response.inputTokens,
105
108
  outputTokens: response.outputTokens,
106
109
  durationMs,
110
+ ...(context ?? {}),
107
111
  },
108
112
  ],
109
113
  };
@@ -183,6 +187,7 @@ export class LLMNode {
183
187
  *
184
188
  * 根据 streaming 配置选择 invoke 或 stream 模式。
185
189
  * 失败时按 backoff 策略重试,最多重试 maxRetries 次。
190
+ * AbortError / 「This operation was aborted」(含 LLM HTTP 超时包装)立即失败,不重试。
186
191
  */
187
192
  async invokeWithRetry(messages) {
188
193
  let lastError;
@@ -195,6 +200,9 @@ export class LLMNode {
195
200
  }
196
201
  catch (error) {
197
202
  lastError = error instanceof Error ? error : new Error(formatUnknownError(error));
203
+ if (isAbortError(error) || isAbortError(lastError)) {
204
+ throw lastError;
205
+ }
198
206
  // 最后一次尝试不需要等待
199
207
  if (attempt < this.retryConfig.maxRetries) {
200
208
  const delay = this.calculateBackoffDelay(attempt);
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 可选图胶水:把 silent LLM 的结构化输出写成 activeSkillPrompt / skillRoute。
3
- * 不是 Graph 节点类型。任何「多本手册互斥」的域图都可选用;字段是约定不是契约。
2
+ * 可选图胶水:把 silent LLM 的结构化输出写成 skillRoute / activatedSkill。
3
+ * 不是 Graph 节点类型。技能正文走短目录 + read_file,不灌 system。
4
4
  */
5
5
  import type { LoadedSkillInfo, LoadedSkillMap } from '../../tool/system-prompt-builder.js';
6
6
  export type SkillRoute = 'skill' | 'clarify' | 'none';
@@ -9,7 +9,6 @@ export interface SkillBindPatch {
9
9
  activatedSkill: string | null;
10
10
  /** 展示名;未选中时为空 */
11
11
  skillName?: string;
12
- activeSkillPrompt?: string;
13
12
  skillInput?: Record<string, unknown>;
14
13
  messages: Array<Record<string, unknown>>;
15
14
  /** 用户写了 skill 名但清单里没有,才算 unmatched */
@@ -25,7 +24,7 @@ interface SkillSelectPayload {
25
24
  export declare function extractJsonObject(text: string): SkillSelectPayload | null;
26
25
  export declare function resolveSkillDefinition(defs: LoadedSkillMap | undefined, rawName: string): LoadedSkillInfo | undefined;
27
26
  /**
28
- * 消费选择节点刚写出的 assistant JSON,改写为路由 + Skill 提示词。
27
+ * 消费选择节点刚写出的 assistant JSON,改写为路由。
29
28
  * 返回的 messages 是**整段替换**(调用方须覆盖 messages channel 时改用 replace 语义)。
30
29
  */
31
30
  export declare function bindSelectedSkill(state: Record<string, unknown>): SkillBindPatch;
@@ -38,26 +38,8 @@ export function resolveSkillDefinition(defs, rawName) {
38
38
  (skill.name && skill.name.toLowerCase() === needle));
39
39
  });
40
40
  }
41
- function formatParams(params) {
42
- return Object.entries(params)
43
- .map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`)
44
- .join('\n');
45
- }
46
- function buildSkillPrompt(skill, params) {
47
- const parts = [`# 技能:${skill.name || skill.skillId}`];
48
- if (skill.description) {
49
- parts.push(skill.description);
50
- }
51
- if (params && Object.keys(params).length > 0) {
52
- parts.push(`# 提取的参数\n${formatParams(params)}`);
53
- }
54
- if (skill.prompt?.trim()) {
55
- parts.push(skill.prompt.trim());
56
- }
57
- return parts.join('\n\n');
58
- }
59
41
  /**
60
- * 消费选择节点刚写出的 assistant JSON,改写为路由 + Skill 提示词。
42
+ * 消费选择节点刚写出的 assistant JSON,改写为路由。
61
43
  * 返回的 messages 是**整段替换**(调用方须覆盖 messages channel 时改用 replace 语义)。
62
44
  */
63
45
  export function bindSelectedSkill(state) {
@@ -108,7 +90,6 @@ export function bindSelectedSkill(state) {
108
90
  skillRoute: 'skill',
109
91
  activatedSkill: skill.skillId,
110
92
  skillName: skill.name || skill.skillId,
111
- activeSkillPrompt: buildSkillPrompt(skill, params),
112
93
  skillInput: params,
113
94
  messages: nextMessages(),
114
95
  };
@@ -33,6 +33,8 @@ type LangGraphState = Record<string, unknown>;
33
33
  export interface ToolNodeProgress {
34
34
  toolCallId: string;
35
35
  toolName: string;
36
+ /** 工具集编码;与 toolName 一起给 UI 显示 `toolset/toolName` */
37
+ toolset?: string;
36
38
  status: 'started' | 'completed' | 'error';
37
39
  arguments?: Record<string, unknown>;
38
40
  content?: string;
@@ -10,29 +10,18 @@
10
10
  * @see requirements.md Requirement 8 AC1, AC3, AC5
11
11
  */
12
12
  import { resolveJailFromState } from '../../tool/harness-primitives.js';
13
+ import { inferHarnessSkillDir } from '../../tool/skill-dir-infer.js';
13
14
  import { isHitlDecision, unwrapHitlPaused } from '../hitl/hitl-handler.js';
14
15
  import { ToolConfirmationInterceptor } from '../../tool/tool-confirmation.js';
15
16
  import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
16
17
  import { resolveTraceId } from '../../common/trace-id.js';
18
+ import { toolsetCodeFromLoaded } from '../../tool/mcp-list.js';
17
19
  function skillDirsFromState(state) {
18
20
  const defs = state['skillDefinitions'];
19
21
  return Object.values(defs ?? {})
20
22
  .filter((skill) => Boolean(skill.bundlePath))
21
23
  .map((skill) => ({ skillId: skill.skillId, bundlePath: skill.bundlePath }));
22
24
  }
23
- function resolveActiveSkillDir(state) {
24
- const defs = state['skillDefinitions'];
25
- if (!defs)
26
- return undefined;
27
- const activated = state['activatedSkill'];
28
- if (typeof activated === 'string' && defs[activated]?.bundlePath) {
29
- return defs[activated].bundlePath;
30
- }
31
- const paths = Object.values(defs)
32
- .map((skill) => skill.bundlePath)
33
- .filter((p) => Boolean(p));
34
- return paths.length === 1 ? paths[0] : undefined;
35
- }
36
25
  export class ToolNode {
37
26
  orchestrator;
38
27
  injector;
@@ -75,12 +64,24 @@ export class ToolNode {
75
64
  skillDefinitions: state['skillDefinitions'],
76
65
  });
77
66
  const jailExtraRoots = jail.extraRoots ?? [];
78
- const skillDir = resolveActiveSkillDir(state);
67
+ const batchCalls = lastAssistantMessage.toolCalls.map((tc) => ({
68
+ name: tc.name,
69
+ arguments: tc.arguments,
70
+ }));
71
+ const boundSkill = typeof state['activatedSkill'] === 'string' ? state['activatedSkill'] : undefined;
79
72
  // 4. 转换为 ToolCallRequest[](附带 manifest 路由字段)
80
73
  const requests = lastAssistantMessage.toolCalls.map((tc) => {
81
74
  const loaded = loadedTools?.[tc.name];
82
75
  const correlationId = String(state['correlationId'] ?? tc.id);
83
76
  const taskId = String(state['taskId'] ?? state['correlationId'] ?? tc.id);
77
+ const command = typeof tc.arguments?.command === 'string' ? tc.arguments.command : undefined;
78
+ const skillDir = inferHarnessSkillDir({
79
+ command,
80
+ toolCalls: batchCalls,
81
+ messages,
82
+ skillDirs,
83
+ activatedSkill: boundSkill,
84
+ });
84
85
  return {
85
86
  toolCallId: tc.id,
86
87
  toolName: tc.name,
@@ -153,11 +154,13 @@ export class ToolNode {
153
154
  const needsHitl = Boolean(this.hitlHandler) &&
154
155
  requests.some((req) => confirmationTools.has(req.toolName));
155
156
  const runOne = async (req, i) => {
157
+ const toolset = toolsetCodeFromLoaded(loadedTools?.[req.toolName]);
156
158
  const resumingHitl = isHitlDecision(state['hitlChoice']);
157
159
  if (!resumingHitl) {
158
160
  emitProgress({
159
161
  toolCallId: req.toolCallId,
160
162
  toolName: req.toolName,
163
+ ...(toolset ? { toolset } : {}),
161
164
  status: 'started',
162
165
  arguments: req.arguments,
163
166
  });
@@ -197,6 +200,7 @@ export class ToolNode {
197
200
  emitProgress({
198
201
  toolCallId: result.toolCallId,
199
202
  toolName: result.toolName,
203
+ ...(toolset ? { toolset } : {}),
200
204
  status: 'completed',
201
205
  arguments: req.arguments,
202
206
  content: result.content,
@@ -207,6 +211,7 @@ export class ToolNode {
207
211
  emitProgress({
208
212
  toolCallId: result.toolCallId,
209
213
  toolName: result.toolName,
214
+ ...(toolset ? { toolset } : {}),
210
215
  status: 'error',
211
216
  arguments: req.arguments,
212
217
  error: result.error ?? 'unknown error',
@@ -243,6 +248,7 @@ export class ToolNode {
243
248
  emitProgress({
244
249
  toolCallId: req.toolCallId,
245
250
  toolName: req.toolName,
251
+ ...(toolset ? { toolset } : {}),
246
252
  status: 'error',
247
253
  arguments: req.arguments,
248
254
  error: message,
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 提供多种压缩策略用于处理长对话历史。
5
5
  */
6
- export { type CompressionConfig, type CompressionResult, type CompressionRecord, type CompressionStrategy, type CompressionReason, type IMessageCompressionService, DEFAULT_COMPRESSION_CONFIG, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
6
+ export { type CompressionConfig, type CompressionResult, type CompressionRecord, type CompressionStrategy, type CompressionReason, type IMessageCompressionService, DEFAULT_COMPRESSION_CONFIG, COMPRESSION_WINDOW_FACTOR, COMPRESSION_MAX_TOKENS_FALLBACK, resolveCompressionMaxTokens, mergeCompressionConfig, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
7
7
  export { SlidingWindowCompression, TruncateCompression, type SummarizeFunction, } from './sliding-window.compression.js';
8
8
  export { createLLMSummarize, createSimpleSummarize, type LLMCallFunction, } from './llm-summarize.js';
9
9
  export { PostgresCompressionRepository, InMemoryCompressionRepository, type ICompressionRepository, } from './compression.repository.js';
@@ -4,7 +4,7 @@
4
4
  * 提供多种压缩策略用于处理长对话历史。
5
5
  */
6
6
  // 压缩接口和工具函数
7
- export { DEFAULT_COMPRESSION_CONFIG, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
7
+ export { DEFAULT_COMPRESSION_CONFIG, COMPRESSION_WINDOW_FACTOR, COMPRESSION_MAX_TOKENS_FALLBACK, resolveCompressionMaxTokens, mergeCompressionConfig, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
8
8
  // 滑动窗口压缩策略
9
9
  export { SlidingWindowCompression, TruncateCompression, } from './sliding-window.compression.js';
10
10
  // LLM 摘要函数
@@ -9,7 +9,7 @@
9
9
  * @see message-compression.service.ts 压缩接口定义
10
10
  * @see requirements.md Requirement 4.3
11
11
  */
12
- import { DEFAULT_COMPRESSION_CONFIG, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
12
+ import { DEFAULT_COMPRESSION_CONFIG, estimateMessagesTokens, countRounds, mergeCompressionConfig, } from '../message-compression.service.js';
13
13
  /**
14
14
  * 默认摘要函数(占位)
15
15
  *
@@ -30,14 +30,14 @@ export class SlidingWindowCompression {
30
30
  config;
31
31
  summarize;
32
32
  constructor(config, summarize) {
33
- this.config = { ...DEFAULT_COMPRESSION_CONFIG, ...config };
33
+ this.config = mergeCompressionConfig(DEFAULT_COMPRESSION_CONFIG, config);
34
34
  this.summarize = summarize ?? defaultSummarize;
35
35
  }
36
36
  /**
37
37
  * 检查是否需要压缩
38
38
  */
39
39
  checkCompression(messages, config) {
40
- const mergedConfig = { ...this.config, ...config };
40
+ const mergedConfig = mergeCompressionConfig(this.config, config);
41
41
  const currentTokens = this.estimateTokens(messages);
42
42
  const currentRounds = countRounds(messages);
43
43
  // 检查 Token 数是否超限
@@ -73,7 +73,7 @@ export class SlidingWindowCompression {
73
73
  * 3. 用摘要 + 最近 N 轮替换原始消息
74
74
  */
75
75
  async compress(messages, config) {
76
- const mergedConfig = { ...this.config, ...config };
76
+ const mergedConfig = mergeCompressionConfig(this.config, config);
77
77
  // 1. 找到最近 N 轮的起始位置
78
78
  const splitIndex = this.findSplitIndex(messages, mergedConfig.preserveRecentRounds);
79
79
  // 如果消息太少,不需要压缩
@@ -134,10 +134,10 @@ export class SlidingWindowCompression {
134
134
  export class TruncateCompression {
135
135
  config;
136
136
  constructor(config) {
137
- this.config = { ...DEFAULT_COMPRESSION_CONFIG, ...config };
137
+ this.config = mergeCompressionConfig(DEFAULT_COMPRESSION_CONFIG, config);
138
138
  }
139
139
  checkCompression(messages, config) {
140
- const mergedConfig = { ...this.config, ...config };
140
+ const mergedConfig = mergeCompressionConfig(this.config, config);
141
141
  const currentTokens = this.estimateTokens(messages);
142
142
  const currentRounds = countRounds(messages);
143
143
  if (currentTokens > mergedConfig.maxTokens || currentRounds > mergedConfig.maxRounds) {
@@ -155,7 +155,7 @@ export class TruncateCompression {
155
155
  };
156
156
  }
157
157
  async compress(messages, config) {
158
- const mergedConfig = { ...this.config, ...config };
158
+ const mergedConfig = mergeCompressionConfig(this.config, config);
159
159
  const preserveCount = mergedConfig.preserveRecentRounds * 2; // 每轮 2 条消息
160
160
  if (messages.length <= preserveCount) {
161
161
  return messages;
@@ -1,4 +1,4 @@
1
1
  export { LongTermMemoryService, InMemoryMemoryRepository, type IMemoryRepository, type MemoryRecord, type MemoryType, type CompressionResult, } from './long-term-memory.service.js';
2
2
  export { PostgresMemoryRepository } from './pg-memory.repository.js';
3
- export { SlidingWindowCompression, TruncateCompression, type IMessageCompressionService, type CompressionConfig, type SummarizeFunction, DEFAULT_COMPRESSION_CONFIG, estimateMessagesTokens, countRounds, } from './compression/index.js';
3
+ export { SlidingWindowCompression, TruncateCompression, type IMessageCompressionService, type CompressionConfig, type SummarizeFunction, DEFAULT_COMPRESSION_CONFIG, COMPRESSION_WINDOW_FACTOR, resolveCompressionMaxTokens, estimateMessagesTokens, countRounds, } from './compression/index.js';
4
4
  export { SemanticRetrievalService, InMemoryVectorStore, type RetrievalConfig, type RetrievalStrategy, type RetrievalResult, type IVectorStore, type VectorResult, type EmbeddingFunction, DEFAULT_RETRIEVAL_CONFIG, } from './semantic-retrieval.service.js';
@@ -2,6 +2,6 @@
2
2
  export { LongTermMemoryService, InMemoryMemoryRepository, } from './long-term-memory.service.js';
3
3
  export { PostgresMemoryRepository } from './pg-memory.repository.js';
4
4
  // 消息压缩模块
5
- export { SlidingWindowCompression, TruncateCompression, DEFAULT_COMPRESSION_CONFIG, estimateMessagesTokens, countRounds, } from './compression/index.js';
5
+ export { SlidingWindowCompression, TruncateCompression, DEFAULT_COMPRESSION_CONFIG, COMPRESSION_WINDOW_FACTOR, resolveCompressionMaxTokens, estimateMessagesTokens, countRounds, } from './compression/index.js';
6
6
  // 语义检索模块
7
7
  export { SemanticRetrievalService, InMemoryVectorStore, DEFAULT_RETRIEVAL_CONFIG, } from './semantic-retrieval.service.js';
@@ -18,8 +18,10 @@ export type CompressionStrategy = 'sliding-window' | 'truncate' | 'headroom';
18
18
  export interface CompressionConfig {
19
19
  /** 压缩策略(默认 sliding-window) */
20
20
  strategy: CompressionStrategy;
21
- /** 触发压缩的 Token 上限(默认模型 context window 的 70%) */
21
+ /** 触发压缩的 Token 上限。有 maxContextTokens 且未显式传 maxTokens 时按 window×系数 */
22
22
  maxTokens: number;
23
+ /** 模型上下文窗。有则 maxTokens = floor(window × COMPRESSION_WINDOW_FACTOR) */
24
+ maxContextTokens?: number;
23
25
  /** 备用:按轮次触发(默认 20 轮) */
24
26
  maxRounds: number;
25
27
  /** 压缩后保留的最近轮次(默认 5 轮) */
@@ -29,6 +31,15 @@ export interface CompressionConfig {
29
31
  /** 用于摘要的模型(可选,默认使用当前 Agent 的模型) */
30
32
  summaryModel?: string;
31
33
  }
34
+ /** 触发阈值 = 模型窗口 × 此系数(与原先 100000/128000 一致) */
35
+ export declare const COMPRESSION_WINDOW_FACTOR = 0.7;
36
+ /** 无模型窗口时的触发阈值(旧默认,按 128k×0.7) */
37
+ export declare const COMPRESSION_MAX_TOKENS_FALLBACK = 100000;
38
+ export declare function resolveCompressionMaxTokens(window?: number, factor?: number): number;
39
+ /**
40
+ * 合并压缩配置。未显式传 maxTokens 且有 maxContextTokens 时,按窗口×系数。
41
+ */
42
+ export declare function mergeCompressionConfig(base: CompressionConfig, override?: Partial<CompressionConfig>): CompressionConfig;
32
43
  /**
33
44
  * 默认压缩配置
34
45
  */
@@ -7,12 +7,40 @@
7
7
  * @see design.md §1.1 记忆管理
8
8
  * @see requirements.md Requirement 4.3
9
9
  */
10
+ /** 触发阈值 = 模型窗口 × 此系数(与原先 100000/128000 一致) */
11
+ export const COMPRESSION_WINDOW_FACTOR = 0.7;
12
+ /** 无模型窗口时的触发阈值(旧默认,按 128k×0.7) */
13
+ export const COMPRESSION_MAX_TOKENS_FALLBACK = 100000;
14
+ export function resolveCompressionMaxTokens(window, factor = COMPRESSION_WINDOW_FACTOR) {
15
+ if (typeof window === 'number' && Number.isFinite(window) && window > 0) {
16
+ const f = typeof factor === 'number' && Number.isFinite(factor) && factor > 0 && factor <= 1
17
+ ? factor
18
+ : COMPRESSION_WINDOW_FACTOR;
19
+ return Math.max(1, Math.floor(window * f));
20
+ }
21
+ return COMPRESSION_MAX_TOKENS_FALLBACK;
22
+ }
23
+ /**
24
+ * 合并压缩配置。未显式传 maxTokens 且有 maxContextTokens 时,按窗口×系数。
25
+ */
26
+ export function mergeCompressionConfig(base, override) {
27
+ if (!override)
28
+ return { ...base };
29
+ const merged = { ...base, ...override };
30
+ if (Object.prototype.hasOwnProperty.call(override, 'maxTokens')) {
31
+ return merged;
32
+ }
33
+ if (override.maxContextTokens) {
34
+ merged.maxTokens = resolveCompressionMaxTokens(override.maxContextTokens);
35
+ }
36
+ return merged;
37
+ }
10
38
  /**
11
39
  * 默认压缩配置
12
40
  */
13
41
  export const DEFAULT_COMPRESSION_CONFIG = {
14
42
  strategy: 'sliding-window',
15
- maxTokens: 100000, // 约 70% of 128k context
43
+ maxTokens: COMPRESSION_MAX_TOKENS_FALLBACK,
16
44
  maxRounds: 20,
17
45
  preserveRecentRounds: 5,
18
46
  compressionRatio: 0.3,
@@ -9,6 +9,7 @@
9
9
  import type { GraphMessage } from '../graph/contracts/executable-graph.js';
10
10
  import type { ILLMProvider, LLMNodeConfig, LLMResponse, LLMChunk } from '../graph/nodes/llm-node.js';
11
11
  import { type ILlmTurnLog } from '../debug/llm-turn-log.js';
12
+ export declare const DEFAULT_LLM_HTTP_TIMEOUT_MS = 180000;
12
13
  export interface HttpLLMProviderConfig {
13
14
  apiKey: string;
14
15
  baseUrl?: string;
@@ -8,6 +8,8 @@
8
8
  */
9
9
  import { resolveLlmModelName } from './resolve-llm-model.js';
10
10
  import { createFileLlmTurnLogFromEnv, maskApiKey } from '../debug/llm-turn-log.js';
11
+ import { buildContextUsage, turnSectionChars } from '../token/context-window.js';
12
+ export const DEFAULT_LLM_HTTP_TIMEOUT_MS = 180_000;
11
13
  export class HttpLLMProvider {
12
14
  apiKey;
13
15
  baseUrl;
@@ -18,7 +20,7 @@ export class HttpLLMProvider {
18
20
  this.apiKey = config.apiKey;
19
21
  this.baseUrl = (config.baseUrl ?? 'https://api.openai.com/v1').replace(/\/$/, '');
20
22
  this.defaultModel = config.defaultModel ?? 'DeepSeek-V4-Pro';
21
- this.timeoutMs = config.timeoutMs ?? 60000;
23
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_LLM_HTTP_TIMEOUT_MS;
22
24
  this.turnLog = 'turnLog' in config ? config.turnLog : createFileLlmTurnLogFromEnv();
23
25
  }
24
26
  async writeTurn(args) {
@@ -57,6 +59,7 @@ export class HttpLLMProvider {
57
59
  error: args.response.error,
58
60
  },
59
61
  latencyMs: Date.now() - args.startedAt,
62
+ context: buildContextUsage(args.response.inputTokens, args.config.maxContextTokens, turnSectionChars(args.config, args.messages)),
60
63
  });
61
64
  }
62
65
  catch (err) {
@@ -1,5 +1,5 @@
1
1
  export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.js';
2
- export { HttpLLMProvider, type HttpLLMProviderConfig } from './http-llm-provider.js';
2
+ export { HttpLLMProvider, DEFAULT_LLM_HTTP_TIMEOUT_MS, type HttpLLMProviderConfig } from './http-llm-provider.js';
3
3
  export { RoutingLLMProvider, type RoutingLLMProviderConfig } from './routing-llm-provider.js';
4
4
  export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
5
5
  export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
@@ -1,5 +1,5 @@
1
1
  export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.js';
2
- export { HttpLLMProvider } from './http-llm-provider.js';
2
+ export { HttpLLMProvider, DEFAULT_LLM_HTTP_TIMEOUT_MS } from './http-llm-provider.js';
3
3
  export { RoutingLLMProvider } from './routing-llm-provider.js';
4
4
  export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
5
5
  export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
@@ -9,11 +9,14 @@ export interface LocalLlmConnection {
9
9
  baseUrl: string;
10
10
  apiKey: string;
11
11
  defaults?: ModelCallDefaults;
12
+ /** HttpLLMProvider 超时(毫秒);缺省用 DEFAULT_LLM_HTTP_TIMEOUT_MS */
13
+ timeoutMs?: number;
12
14
  }
13
15
  export interface LocalLlmConnectionRef {
14
16
  baseUrl: string;
15
17
  apiKeyEnv: string;
16
18
  defaults?: ModelCallDefaults;
19
+ timeoutMs?: number;
17
20
  }
18
21
  export interface LocalLlmConnectionRefs {
19
22
  default?: LocalLlmConnectionRef;
@@ -4,6 +4,11 @@
4
4
  */
5
5
  import { mergeModelCallParams } from '@gt-fe/eap-contracts';
6
6
  import { HttpLLMProvider } from './http-llm-provider.js';
7
+ function parseTimeoutMs(value) {
8
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 1000)
9
+ return undefined;
10
+ return Math.min(3_600_000, Math.trunc(value));
11
+ }
7
12
  export const LLM_CONNECTIONS_ENV = 'EAP_LLM_CONNECTIONS';
8
13
  export function parseLocalLlmConnectionRefs(env = process.env) {
9
14
  const raw = env[LLM_CONNECTIONS_ENV]?.trim();
@@ -42,7 +47,13 @@ export function hydrateLocalLlmConnections(refs, env = process.env) {
42
47
  const apiKey = env[ref.apiKeyEnv]?.trim();
43
48
  if (!apiKey)
44
49
  return undefined;
45
- return { baseUrl: ref.baseUrl, apiKey, defaults: ref.defaults };
50
+ const timeoutMs = parseTimeoutMs(ref.timeoutMs);
51
+ return {
52
+ baseUrl: ref.baseUrl,
53
+ apiKey,
54
+ defaults: ref.defaults,
55
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
56
+ };
46
57
  };
47
58
  const byModel = {};
48
59
  for (const [name, ref] of Object.entries(refs.byModel ?? {})) {
@@ -97,7 +108,7 @@ class LocalLlmProvider {
97
108
  }
98
109
  providerFor(model) {
99
110
  const connection = resolveLocalLlmConnection(this.connections, model);
100
- const cacheKey = `${connection.baseUrl}\0${connection.apiKey}\0${model ?? ''}`;
111
+ const cacheKey = `${connection.baseUrl}\0${connection.apiKey}\0${model ?? ''}\0${connection.timeoutMs ?? ''}`;
101
112
  const cached = this.cache.get(cacheKey);
102
113
  if (cached)
103
114
  return cached;
@@ -105,6 +116,7 @@ class LocalLlmProvider {
105
116
  apiKey: connection.apiKey,
106
117
  baseUrl: connection.baseUrl,
107
118
  defaultModel: model,
119
+ timeoutMs: connection.timeoutMs,
108
120
  turnLog: this.turnLog,
109
121
  });
110
122
  this.cache.set(cacheKey, provider);
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { mergeModelCallParams } from '@gt-fe/eap-contracts';
15
15
  import { EchoLLMProvider } from './echo-llm-provider.js';
16
- import { HttpLLMProvider } from './http-llm-provider.js';
16
+ import { DEFAULT_LLM_HTTP_TIMEOUT_MS, HttpLLMProvider } from './http-llm-provider.js';
17
17
  export class RoutingLLMProvider {
18
18
  foundationUrl;
19
19
  fallback;
@@ -35,7 +35,12 @@ export class RoutingLLMProvider {
35
35
  }
36
36
  withCallDefaults(config, selected) {
37
37
  const merged = mergeModelCallParams(config, selected.defaults);
38
- return { ...config, ...merged, model: selected.name };
38
+ return {
39
+ ...config,
40
+ ...merged,
41
+ model: selected.name,
42
+ maxContextTokens: selected.maxContextTokens ?? config.maxContextTokens,
43
+ };
39
44
  }
40
45
  async selectModel(config) {
41
46
  try {
@@ -50,11 +55,14 @@ export class RoutingLLMProvider {
50
55
  if (res.ok) {
51
56
  const body = await res.json();
52
57
  if (body.data?.name) {
58
+ const window = body.data.maxContextTokens
59
+ ?? body.data.capabilities?.maxContextTokens;
53
60
  return {
54
61
  name: body.data.name,
55
62
  provider: body.data.provider,
56
63
  connection: body.data.connection ?? null,
57
64
  defaults: body.data.defaults ?? null,
65
+ ...(typeof window === 'number' && window > 0 ? { maxContextTokens: window } : {}),
58
66
  };
59
67
  }
60
68
  }
@@ -71,7 +79,7 @@ export class RoutingLLMProvider {
71
79
  apiKey: selected.connection.apiKey ?? '',
72
80
  baseUrl: selected.connection.baseUrl,
73
81
  defaultModel: selected.name,
74
- timeoutMs: selected.connection.timeoutMs,
82
+ timeoutMs: selected.connection.timeoutMs ?? DEFAULT_LLM_HTTP_TIMEOUT_MS,
75
83
  turnLog: this.turnLog,
76
84
  });
77
85
  }
@@ -81,6 +89,7 @@ export class RoutingLLMProvider {
81
89
  apiKey: process.env.OPENAI_API_KEY,
82
90
  baseUrl: process.env.OPENAI_BASE_URL,
83
91
  defaultModel: selected.name,
92
+ timeoutMs: DEFAULT_LLM_HTTP_TIMEOUT_MS,
84
93
  turnLog: this.turnLog,
85
94
  });
86
95
  }
@@ -538,35 +538,37 @@ export class FileSystemDeploymentSnapshot {
538
538
  break;
539
539
  }
540
540
  }
541
- if (!manifest && entryFile) {
542
- const parsed = parseSkillMd(await readFile(entryFile, 'utf8'));
543
- if (!parsed)
541
+ const parsedMd = entryFile
542
+ ? parseSkillMd(await readFile(entryFile, 'utf8'))
543
+ : null;
544
+ if (!manifest) {
545
+ if (!parsedMd)
544
546
  continue;
545
547
  manifest = {
546
- code: parsed.frontmatter.name,
547
- name: parsed.frontmatter.name,
548
- description: parsed.frontmatter.description,
549
- ...(parsed.frontmatter.license ? { license: parsed.frontmatter.license } : {}),
548
+ code: ref.skillId,
549
+ name: parsedMd.frontmatter.name,
550
+ description: parsedMd.frontmatter.description,
551
+ ...(parsedMd.frontmatter.license ? { license: parsedMd.frontmatter.license } : {}),
550
552
  };
551
553
  manifestPath = entryFile;
552
554
  }
555
+ else if (parsedMd) {
556
+ manifest = {
557
+ ...manifest,
558
+ name: parsedMd.frontmatter.name,
559
+ description: parsedMd.frontmatter.description,
560
+ ...(parsedMd.frontmatter.license ? { license: parsedMd.frontmatter.license } : {}),
561
+ };
562
+ }
553
563
  if (!manifest || !manifestPath)
554
564
  continue;
555
- if (!manifest.description && entryFile) {
556
- const parsed = parseSkillMd(await readFile(entryFile, 'utf8'));
557
- if (parsed?.frontmatter.description) {
558
- manifest = { ...manifest, description: parsed.frontmatter.description };
559
- if (!manifest.name)
560
- manifest.name = parsed.frontmatter.name;
561
- }
562
- }
563
565
  const isUserOverridePath = bundlePath.includes('/users/') || bundlePath.includes('\\users\\');
564
566
  const isAuthorOverlay = extraRoots.some((root) => root && isPathInside(root, bundlePath));
565
567
  if (ref.sha256 && !isUserOverridePath && !isAuthorOverlay && manifestPath.endsWith('.json')) {
566
568
  const actual = await sha256OfFile(manifestPath);
567
569
  await assertDigestMatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath });
568
570
  }
569
- const skillId = String(manifest.code ?? ref.skillId);
571
+ const skillId = String(ref.skillId);
570
572
  return {
571
573
  skillId,
572
574
  name: String(manifest.name ?? ref.name ?? skillId),
@@ -17,6 +17,11 @@ export interface LoadAgentOptions {
17
17
  /** 跳过读写进程内定义缓存 */
18
18
  bypassCache?: boolean;
19
19
  }
20
+ export declare function resolveAgentSystemPromptFromLoader(loader: {
21
+ resolveAgentSystemPrompt?: (agentCode: string, version?: string) => Promise<string | undefined>;
22
+ } | undefined, agentCode: string, version?: string): Promise<string | undefined>;
23
+ /** manifest.prompts.system;空白视为缺省。 */
24
+ export declare function extractAgentSystemPrompt(manifest: unknown): string | undefined;
20
25
  export interface GraphLoaderConfig {
21
26
  registryClient?: AgentRegistryClient;
22
27
  deploymentContext?: DeploymentContext;
@@ -104,6 +109,11 @@ export declare class GraphLoader {
104
109
  private cloneCached;
105
110
  loadGraph(agentCode: string, options?: LoadAgentOptions): Promise<GraphDSL>;
106
111
  private loadGraphFromSource;
112
+ /**
113
+ * Agent 基线系统提示词(manifest.prompts.system)。
114
+ * 每轮 execute/stream 注入 GraphState.agentSystemPrompt;不写进节点 config。
115
+ */
116
+ resolveAgentSystemPrompt(agentCode: string, version?: string): Promise<string | undefined>;
107
117
  /**
108
118
  * 解析 agent tools[] + graph tool 节点 union;
109
119
  * server+预装:以预装 manifest 为准;desktop 合并 ILocalToolInstallStore。