@gt-fe/eap-runtime 0.0.12 → 0.0.14

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 (112) 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 +4 -1
  6. package/dist/api/routes/agent.routes.d.ts +2 -2
  7. package/dist/common/index.d.ts +1 -0
  8. package/dist/common/index.js +1 -0
  9. package/dist/common/logger.d.ts +16 -4
  10. package/dist/common/logger.js +82 -16
  11. package/dist/common/runtime-package-version.d.ts +2 -0
  12. package/dist/common/runtime-package-version.js +5 -0
  13. package/dist/common/types.d.ts +5 -5
  14. package/dist/common/types.js +2 -2
  15. package/dist/contracts/permission-checker.d.ts +1 -1
  16. package/dist/debug/llm-turn-log.d.ts +12 -0
  17. package/dist/debug/llm-turn-log.js +1 -0
  18. package/dist/governance/governance-client.d.ts +1 -1
  19. package/dist/governance/index.d.ts +1 -1
  20. package/dist/governance/permission-guard.d.ts +1 -1
  21. package/dist/graph/adapters/langgraph.adapter.d.ts +3 -1
  22. package/dist/graph/adapters/langgraph.adapter.js +67 -14
  23. package/dist/graph/adapters/subgraph-compiler.js +5 -2
  24. package/dist/graph/contracts/executable-graph.d.ts +19 -8
  25. package/dist/graph/contracts/executable-graph.js +1 -1
  26. package/dist/graph/nodes/llm-node.d.ts +3 -0
  27. package/dist/graph/nodes/llm-node.js +8 -0
  28. package/dist/graph/nodes/skill-bind-node.d.ts +3 -4
  29. package/dist/graph/nodes/skill-bind-node.js +1 -20
  30. package/dist/graph/nodes/tool-node.d.ts +2 -0
  31. package/dist/graph/nodes/tool-node.js +20 -14
  32. package/dist/index.d.ts +1 -1
  33. package/dist/memory/compression/index.d.ts +1 -1
  34. package/dist/memory/compression/index.js +1 -1
  35. package/dist/memory/compression/sliding-window.compression.js +7 -7
  36. package/dist/memory/index.d.ts +1 -1
  37. package/dist/memory/index.js +1 -1
  38. package/dist/memory/message-compression.service.d.ts +12 -1
  39. package/dist/memory/message-compression.service.js +29 -1
  40. package/dist/providers/http-llm-provider.d.ts +1 -0
  41. package/dist/providers/http-llm-provider.js +4 -1
  42. package/dist/providers/index.d.ts +1 -1
  43. package/dist/providers/index.js +1 -1
  44. package/dist/providers/local-llm-provider.d.ts +4 -1
  45. package/dist/providers/local-llm-provider.js +15 -3
  46. package/dist/providers/routing-llm-provider.js +13 -4
  47. package/dist/registry/agent-models.d.ts +1 -1
  48. package/dist/registry/agent-models.js +1 -1
  49. package/dist/registry/agent-registry-client.d.ts +1 -1
  50. package/dist/registry/deployment-snapshot.js +19 -17
  51. package/dist/registry/graph-loader.d.ts +11 -1
  52. package/dist/registry/graph-loader.js +115 -56
  53. package/dist/registry/preload-errors.d.ts +1 -1
  54. package/dist/registry/preload-errors.js +1 -1
  55. package/dist/registry/skill-io-guard.js +1 -1
  56. package/dist/registry/skill-tree.js +1 -1
  57. package/dist/runtime/agent-runtime.d.ts +3 -3
  58. package/dist/runtime/agent-runtime.js +14 -4
  59. package/dist/runtime/dev-runtime.d.ts +2 -0
  60. package/dist/runtime/dev-runtime.js +2 -0
  61. package/dist/runtime/types.d.ts +4 -4
  62. package/dist/server.js +1 -1
  63. package/dist/session/compression-middleware.d.ts +2 -1
  64. package/dist/session/compression-middleware.js +7 -4
  65. package/dist/sse/events/skill-events.d.ts +1 -1
  66. package/dist/sse/events/skill-events.js +1 -1
  67. package/dist/sse/events/stats-events.d.ts +9 -0
  68. package/dist/token/compression-budget-integration.d.ts +1 -1
  69. package/dist/token/compression-budget-integration.js +18 -10
  70. package/dist/token/context-window.d.ts +42 -0
  71. package/dist/token/context-window.js +73 -0
  72. package/dist/token/index.d.ts +1 -0
  73. package/dist/token/index.js +1 -0
  74. package/dist/tool/create-tool-executor.d.ts +1 -1
  75. package/dist/tool/dev-http-executor.d.ts +2 -1
  76. package/dist/tool/dev-http-executor.js +14 -2
  77. package/dist/tool/dev-mcp-executor.d.ts +11 -1
  78. package/dist/tool/dev-mcp-executor.js +27 -1
  79. package/dist/tool/dev-tool-executor.d.ts +2 -1
  80. package/dist/tool/dev-tool-executor.js +6 -0
  81. package/dist/tool/harness-command.js +1 -1
  82. package/dist/tool/harness-primitives.d.ts +7 -0
  83. package/dist/tool/harness-primitives.js +57 -6
  84. package/dist/tool/http-tool-executor.js +1 -1
  85. package/dist/tool/index.d.ts +2 -1
  86. package/dist/tool/index.js +1 -0
  87. package/dist/tool/local-mcp-client.d.ts +5 -2
  88. package/dist/tool/local-mcp-client.js +130 -3
  89. package/dist/tool/local-tool-executor.d.ts +3 -2
  90. package/dist/tool/local-tool-executor.js +16 -1
  91. package/dist/tool/mcp-list.d.ts +53 -0
  92. package/dist/tool/mcp-list.js +199 -0
  93. package/dist/tool/mcp-tool-executor.d.ts +6 -1
  94. package/dist/tool/mcp-tool-executor.js +53 -1
  95. package/dist/tool/skill-dir-infer.d.ts +17 -0
  96. package/dist/tool/skill-dir-infer.js +81 -0
  97. package/dist/tool/skill-script-refs.d.ts +1 -8
  98. package/dist/tool/skill-script-refs.js +32 -20
  99. package/dist/tool/system-prompt-builder.d.ts +35 -16
  100. package/dist/tool/system-prompt-builder.js +30 -58
  101. package/dist/tool/timeout.d.ts +2 -2
  102. package/dist/tool/timeout.js +6 -3
  103. package/dist/tool/tool-execution-router.d.ts +3 -2
  104. package/dist/tool/tool-execution-router.js +8 -1
  105. package/dist/tool/tool-executor.interface.d.ts +11 -0
  106. package/dist/tool/tool-orchestrator.d.ts +1 -1
  107. package/dist/tool/tool-orchestrator.js +1 -1
  108. package/dist/utils/format-error.d.ts +2 -2
  109. package/dist/utils/format-error.js +2 -2
  110. package/dist/utils/http-error.d.ts +2 -2
  111. package/dist/utils/http-error.js +2 -2
  112. package/package.json +6 -33
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 预装加载错误 — 对齐 runtime.preload.* / 联调契约
3
3
  */
4
- import { AppError, type ErrorCode } from '@gt-fe/eap-contracts';
4
+ import { AppError, type ErrorCode } from '@eap/contracts';
5
5
  export declare function preloadError(code: ErrorCode, message: string, details?: unknown): AppError;
6
6
  export declare function preloadMissing(message: string, details?: unknown): AppError;
7
7
  export declare function preloadManifestNotFound(message: string, details?: unknown): AppError;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 预装加载错误 — 对齐 runtime.preload.* / 联调契约
3
3
  */
4
- import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
4
+ import { AppError, ErrorCodes } from '@eap/contracts';
5
5
  export function preloadError(code, message, details) {
6
6
  return new AppError(code, message, {
7
7
  exposeMessage: true,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Skill inputDef / outputDef 轻量校验(默认开启;EAP_PRELOAD_VALIDATE_SKILL_IO=0 关闭)
3
3
  */
4
- import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
4
+ import { AppError, ErrorCodes } from '@eap/contracts';
5
5
  import { isValidateSkillIo } from './preload-flags.js';
6
6
  function asRecord(value) {
7
7
  if (value && typeof value === 'object' && !Array.isArray(value)) {
@@ -10,7 +10,7 @@ import { readdir, stat, access, readFile } from 'node:fs/promises';
10
10
  import { watch } from 'node:fs';
11
11
  import { constants as fsConstants } from 'node:fs';
12
12
  import path from 'node:path';
13
- import { SKILL_ENTRY_CANDIDATES } from '@gt-fe/eap-contracts';
13
+ import { SKILL_ENTRY_CANDIDATES } from '@eap/contracts';
14
14
  const SKILL_CODE = /^[a-z0-9][a-z0-9-]*$/;
15
15
  async function exists(p) {
16
16
  try {
@@ -11,7 +11,7 @@ import type { ICheckpointSaver } from '../checkpoint/contracts/checkpoint-saver.
11
11
  import type { ITraceCollector } from '../contracts/trace-collector.js';
12
12
  import type { IStateGraphEngine } from '../graph/contracts/state-graph.contract.js';
13
13
  import { HitlHandler } from '../graph/hitl/hitl-handler.js';
14
- import type { GraphLoader } from '../registry/graph-loader.js';
14
+ import { type GraphLoader } from '../registry/graph-loader.js';
15
15
  import type { SessionContextService } from '../session/session-context.service.js';
16
16
  import type { MessageService } from '../session/message.service.js';
17
17
  import type { SessionService, SessionRecord, MessageRecord } from '../session/session.service.js';
@@ -86,8 +86,8 @@ export declare class AgentRuntime {
86
86
  * “Session 历史 + 当前输入”途径。
87
87
  *
88
88
  * 行为:
89
- * - `role` 仅接受 `user` / `assistant`;`system` 应写入 graph.json LLM 节点的
90
- * `config.systemPrompt`(由 SystemPromptBuilder 拼平台规则 + 开发者提示词);
89
+ * - `role` 仅接受 `user` / `assistant`;`system` 由 SystemPromptBuilder 拼
90
+ * (平台规则 + `prompts.system` 基线 + 节点 `systemPrompt`);
91
91
  * `tool` 必须与前置 assistant.toolCalls 严格配对,不得单独作为种子。
92
92
  * - 幂等语义(exactly-once):当 session 已存在任何历史消息时,直接返回
93
93
  * `seeded: false`,不重复写入。
@@ -8,11 +8,12 @@
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
10
  import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
11
+ import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
11
12
  import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
12
13
  import { assembleLoadedTools } from '../tool/harness-primitives.js';
13
14
  import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
14
15
  import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, extractStreamTextDelta, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../session/persist-turn-messages.js';
15
- import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
16
+ import { AppError, ErrorCodes } from '@eap/contracts';
16
17
  import { resolveExecutionContext } from '../governance/governance-client.js';
17
18
  import { resolveAgentCode } from '../common/agent-code.js';
18
19
  import { resolveTraceId } from '../common/trace-id.js';
@@ -71,8 +72,8 @@ export class AgentRuntime {
71
72
  * “Session 历史 + 当前输入”途径。
72
73
  *
73
74
  * 行为:
74
- * - `role` 仅接受 `user` / `assistant`;`system` 应写入 graph.json LLM 节点的
75
- * `config.systemPrompt`(由 SystemPromptBuilder 拼平台规则 + 开发者提示词);
75
+ * - `role` 仅接受 `user` / `assistant`;`system` 由 SystemPromptBuilder 拼
76
+ * (平台规则 + `prompts.system` 基线 + 节点 `systemPrompt`);
76
77
  * `tool` 必须与前置 assistant.toolCalls 严格配对,不得单独作为种子。
77
78
  * - 幂等语义(exactly-once):当 session 已存在任何历史消息时,直接返回
78
79
  * `seeded: false`,不重复写入。
@@ -100,7 +101,7 @@ export class AgentRuntime {
100
101
  throw new AppError(ErrorCodes.PARAMS_INVALID, `seedSessionMessages messages[${index}] must be an object`, { exposeMessage: true });
101
102
  }
102
103
  if (message.role !== 'user' && message.role !== 'assistant') {
103
- throw new AppError(ErrorCodes.PARAMS_INVALID, `seedSessionMessages messages[${index}].role "${String(message.role)}" is not allowed; only 'user' and 'assistant' are accepted (system belongs to config.systemPrompt; tool must pair with prior assistant.toolCalls)`, { exposeMessage: true });
104
+ throw new AppError(ErrorCodes.PARAMS_INVALID, `seedSessionMessages messages[${index}].role "${String(message.role)}" is not allowed; only 'user' and 'assistant' are accepted (system belongs to SystemPromptBuilder: platform rules + prompts.system + node systemPrompt; tool must pair with prior assistant.toolCalls)`, { exposeMessage: true });
104
105
  }
105
106
  if (typeof message.content !== 'string' || message.content.length === 0) {
106
107
  throw new AppError(ErrorCodes.PARAMS_INVALID, `seedSessionMessages messages[${index}].content must be a non-empty string`, { exposeMessage: true });
@@ -271,6 +272,7 @@ export class AgentRuntime {
271
272
  const agentDir = this.deps.graphLoader
272
273
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
273
274
  : undefined;
275
+ const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
274
276
  const history = messageRecordsToGraphMessages(sessionContext.messages);
275
277
  const messages = await buildTurnGraphMessages({
276
278
  history,
@@ -301,6 +303,7 @@ export class AgentRuntime {
301
303
  skillDefinitions,
302
304
  promptRoots,
303
305
  agentDir,
306
+ agentSystemPrompt,
304
307
  skillInput: resolveSkillInputFromInvokeInput(params.input),
305
308
  traceId: resolveTraceId(params),
306
309
  traceCollector: this.deps.traceCollector,
@@ -490,6 +493,9 @@ export class AgentRuntime {
490
493
  (this.deps.graphLoader
491
494
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, agentVersion)) ?? undefined
492
495
  : undefined),
496
+ agentSystemPrompt: (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof resumedState.agentSystemPrompt === 'string'
497
+ ? resumedState.agentSystemPrompt
498
+ : undefined),
493
499
  activatedSkill: resumedState.activatedSkill,
494
500
  skillRoute: resumedState.skillRoute ??
495
501
  (typeof resumedState.activatedSkill === 'string' && resumedState.activatedSkill
@@ -652,11 +658,15 @@ export class AgentRuntime {
652
658
  (this.deps.graphLoader
653
659
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, agentVersion)) ?? undefined
654
660
  : undefined);
661
+ const agentSystemPrompt = (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentSystemPrompt === 'string'
662
+ ? forkedCheckpoint.state.agentSystemPrompt
663
+ : undefined);
655
664
  try {
656
665
  const result = await this.deps.graphEngine.invoke(graph, {
657
666
  ...forkedCheckpoint.state,
658
667
  agentVersion,
659
668
  agentDir,
669
+ agentSystemPrompt,
660
670
  userId,
661
671
  delegationToken: overrides.executionContext?.delegationToken,
662
672
  executionContext: overrides.executionContext,
@@ -49,6 +49,8 @@ export interface DevRuntime {
49
49
  dispose: () => void;
50
50
  /** 装配期诊断摘要(CLI 启动时打印,便于排查环境问题) */
51
51
  diagnostics: {
52
+ /** 当前加载的 Runtime 包 version */
53
+ packageVersion: string;
52
54
  deploymentContext: string;
53
55
  toolExecutionMode: string;
54
56
  llmProviderKind: 'routing' | 'openai-http' | 'echo';
@@ -35,6 +35,7 @@ import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService,
35
35
  import { createAgentRuntime } from './agent-runtime.js';
36
36
  import { assembleRuntimeStorage } from './storage-profile.js';
37
37
  import { resolveSkillHotReload, watchPreinstallSkillRoot, watchSkillRoots } from '../registry/skill-tree.js';
38
+ import { RUNTIME_PACKAGE_VERSION } from '../common/runtime-package-version.js';
38
39
  /**
39
40
  * 装配本地 dev 模式所需的全部 Runtime 依赖
40
41
  *
@@ -185,6 +186,7 @@ export function createDevRuntime() {
185
186
  graphLoader,
186
187
  dispose: stopSkillWatch,
187
188
  diagnostics: {
189
+ packageVersion: RUNTIME_PACKAGE_VERSION,
188
190
  deploymentContext,
189
191
  toolExecutionMode,
190
192
  llmProviderKind,
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @see 04-runtime-core/design.md §1.5
8
8
  */
9
- import type { ExecutionContext, InvokeConfig, ResumeHitlResult } from '@gt-fe/eap-contracts';
9
+ import type { ExecutionContext, InvokeConfig, ResumeHitlResult } from '@eap/contracts';
10
10
  import type { InvokeResponseData } from '../api/routes/agent.routes.js';
11
11
  import type { GraphMessage } from '../graph/contracts/executable-graph.js';
12
12
  import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
@@ -31,7 +31,7 @@ export interface ExecuteParams {
31
31
  }
32
32
  /** 同步执行结果 */
33
33
  export type ExecuteResult = InvokeResponseData;
34
- /** HITL 恢复结果(与 @gt-fe/eap-contracts ResumeHitlResult 对齐) */
34
+ /** HITL 恢复结果(与 @eap/contracts ResumeHitlResult 对齐) */
35
35
  export type { ResumeHitlResult };
36
36
  /** HITL 用户响应 */
37
37
  export interface HitlResponse {
@@ -91,8 +91,8 @@ export interface ForkResult {
91
91
  * 开发者提供的对话上下文(开场白 / few-shot)。
92
92
  *
93
93
  * 约束(AgentRuntime.seedSessionMessages 强制校验):
94
- * - `role` 白名单为 `user` / `assistant`,`system` 走 graph.json 的
95
- * `config.systemPrompt`(由 SystemPromptBuilder 拼平台规则),`tool` 必须与
94
+ * - `role` 白名单为 `user` / `assistant`,`system` 由 SystemPromptBuilder 拼
95
+ * (平台规则 + `prompts.system` + 节点 `systemPrompt`),`tool` 必须与
96
96
  * 前置 assistant.toolCalls 严格配对,两者禁止作为种子;
97
97
  * - 该 session 已有历史时,视作已种入,跳过写入(exactly-once 幂等)。
98
98
  */
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * TODO: Phase 2 将本文件迁移至 dev-tools/cli(作为 `eap dev` 命令实现),
8
8
  * runtime 包不再包含 HTTP 服务代码,仅作为纯库导出。
9
9
  */
10
- import { ErrorCodes } from '@gt-fe/eap-contracts';
10
+ import { ErrorCodes } from '@eap/contracts';
11
11
  import { createServer } from 'node:http';
12
12
  import { URL } from 'node:url';
13
13
  import { resolveExecutionContext } from './governance/index.js';
@@ -65,11 +65,12 @@ export declare class CompressionMiddleware {
65
65
  sessionId: string;
66
66
  taskId?: string;
67
67
  userId: string;
68
+ maxContextTokens?: number;
68
69
  }): Promise<MessageRecord[]>;
69
70
  /**
70
71
  * 检查是否需要压缩(不执行)
71
72
  */
72
- checkCompression(messages: MessageRecord[]): {
73
+ checkCompression(messages: MessageRecord[], maxContextTokens?: number): {
73
74
  needsCompression: boolean;
74
75
  currentTokens: number;
75
76
  currentRounds: number;
@@ -65,13 +65,16 @@ export class CompressionMiddleware {
65
65
  // 转换为 GraphMessage 格式进行检查
66
66
  const graphMessages = this.toGraphMessages(messages);
67
67
  // 检查是否需要压缩
68
- const checkResult = this.compression.checkCompression(graphMessages);
68
+ const windowOverride = context.maxContextTokens
69
+ ? { maxContextTokens: context.maxContextTokens }
70
+ : undefined;
71
+ const checkResult = this.compression.checkCompression(graphMessages, windowOverride);
69
72
  if (!checkResult.needsCompression) {
70
73
  return messages;
71
74
  }
72
75
  // 执行压缩
73
76
  const startTime = Date.now();
74
- const compressedMessages = await this.compression.compress(graphMessages);
77
+ const compressedMessages = await this.compression.compress(graphMessages, windowOverride);
75
78
  const duration = Date.now() - startTime;
76
79
  // 转换回 MessageRecord 格式
77
80
  const compressedRecords = this.toMessageRecords(compressedMessages, messages);
@@ -106,9 +109,9 @@ export class CompressionMiddleware {
106
109
  /**
107
110
  * 检查是否需要压缩(不执行)
108
111
  */
109
- checkCompression(messages) {
112
+ checkCompression(messages, maxContextTokens) {
110
113
  const graphMessages = this.toGraphMessages(messages);
111
- return this.compression.checkCompression(graphMessages);
114
+ return this.compression.checkCompression(graphMessages, maxContextTokens ? { maxContextTokens } : undefined);
112
115
  }
113
116
  /**
114
117
  * 获取配置
@@ -6,7 +6,7 @@
6
6
  * - skill.started / skill.completed / skill.failed(子图执行生命周期)
7
7
  *
8
8
  * @see design.md §1.6 SSE事件协议定义
9
- * @see @gt-fe/eap-contracts OPTIONAL_SSE_EVENTS
9
+ * @see @eap/contracts OPTIONAL_SSE_EVENTS
10
10
  */
11
11
  /** 技能匹配成功 */
12
12
  export interface SkillMatchedPayload {
@@ -6,6 +6,6 @@
6
6
  * - skill.started / skill.completed / skill.failed(子图执行生命周期)
7
7
  *
8
8
  * @see design.md §1.6 SSE事件协议定义
9
- * @see @gt-fe/eap-contracts OPTIONAL_SSE_EVENTS
9
+ * @see @eap/contracts OPTIONAL_SSE_EVENTS
10
10
  */
11
11
  export {};
@@ -41,4 +41,13 @@ export interface StatsNodeTokenUsagePayload {
41
41
  costEstimate: number;
42
42
  /** 该节点本次调用耗时(毫秒),可选 */
43
43
  durationMs?: number;
44
+ /** 本轮 prompt 占用的上下文窗 used/window;缺 window 则省略 */
45
+ used?: number;
46
+ window?: number;
47
+ /** 按字符比例把 used 拆到 system/messages/tools;合计 = used */
48
+ parts?: {
49
+ system: number;
50
+ messages: number;
51
+ tools: number;
52
+ };
44
53
  }
@@ -18,7 +18,7 @@ import type { IMessageRepository } from '../session/session.service.js';
18
18
  export interface CompressionBudgetConfig {
19
19
  /** 模型 context window 大小 */
20
20
  modelContextWindow: number;
21
- /** 历史消息占比阈值(超过此比例触发压缩,默认 0.8) */
21
+ /** 相对模型窗口的触发系数(默认 0.7,与滑动窗口同一公式) */
22
22
  historyThreshold?: number;
23
23
  /** 压缩后目标占比(默认 0.5) */
24
24
  targetRatio?: number;
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { createContextBudget } from './context-window-budget.js';
11
11
  import { CompressionMiddleware, createCompressionMiddlewareWithLLM, } from '../session/compression-middleware.js';
12
- import { estimateMessagesTokens } from '../memory/message-compression.service.js';
12
+ import { estimateMessagesTokens, resolveCompressionMaxTokens, } from '../memory/message-compression.service.js';
13
13
  // ─────────────────────────────────────────────
14
14
  // CompressionBudgetIntegration
15
15
  // ─────────────────────────────────────────────
@@ -28,7 +28,7 @@ export class CompressionBudgetIntegration {
28
28
  constructor(config, repository, messageRepository) {
29
29
  this.config = {
30
30
  modelContextWindow: config.modelContextWindow,
31
- historyThreshold: config.historyThreshold ?? 0.8,
31
+ historyThreshold: config.historyThreshold ?? 0.7,
32
32
  targetRatio: config.targetRatio ?? 0.5,
33
33
  autoCompress: config.autoCompress ?? true,
34
34
  llmConfig: config.llmConfig,
@@ -36,13 +36,17 @@ export class CompressionBudgetIntegration {
36
36
  // 创建预算管理器
37
37
  this.budget = createContextBudget(this.config.modelContextWindow);
38
38
  // 创建压缩中间件
39
+ const compressionConfig = {
40
+ maxContextTokens: this.config.modelContextWindow,
41
+ };
39
42
  if (this.config.llmConfig?.provider) {
40
43
  this.compression = createCompressionMiddlewareWithLLM(this.config.llmConfig.provider, {
41
44
  llmConfig: this.config.llmConfig,
45
+ compressionConfig,
42
46
  }, repository, messageRepository);
43
47
  }
44
48
  else {
45
- this.compression = new CompressionMiddleware({}, undefined, repository, messageRepository);
49
+ this.compression = new CompressionMiddleware({ compressionConfig }, undefined, repository, messageRepository);
46
50
  }
47
51
  }
48
52
  /**
@@ -55,12 +59,13 @@ export class CompressionBudgetIntegration {
55
59
  // 估算当前 Token 数
56
60
  const graphMessages = this.toGraphMessages(messages);
57
61
  const currentTokens = estimateMessagesTokens(graphMessages);
58
- // 获取历史消息的 Token 上限
59
- const maxTokens = this.budget.getMaxTokens('history');
60
- // 计算使用率
61
- const usageRatio = maxTokens > 0 ? currentTokens / maxTokens : 0;
62
- // 检查是否超过阈值
63
- const needsCompression = usageRatio > this.config.historyThreshold;
62
+ // 与滑动窗口同一阈值:window × 系数(默认 0.7);historyThreshold 作为系数覆盖
63
+ const maxTokens = resolveCompressionMaxTokens(this.config.modelContextWindow, this.config.historyThreshold);
64
+ // 计算使用率(相对窗口,不是相对阈值)
65
+ const usageRatio = this.config.modelContextWindow > 0
66
+ ? currentTokens / this.config.modelContextWindow
67
+ : 0;
68
+ const needsCompression = currentTokens > maxTokens;
64
69
  // 计算压缩目标
65
70
  const targetTokens = needsCompression
66
71
  ? Math.floor(maxTokens * this.config.targetRatio)
@@ -96,7 +101,10 @@ export class CompressionBudgetIntegration {
96
101
  }
97
102
  // 执行压缩
98
103
  const startTime = Date.now();
99
- const compressedMessages = await this.compression.processMessages(messages, context);
104
+ const compressedMessages = await this.compression.processMessages(messages, {
105
+ ...context,
106
+ maxContextTokens: this.config.modelContextWindow,
107
+ });
100
108
  const duration = Date.now() - startTime;
101
109
  // 计算压缩后的 Token 数
102
110
  const compressedGraphMessages = this.toGraphMessages(compressedMessages);
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 本轮 prompt 占用的上下文窗:used = 本轮 inputTokens,window = ModelRecord.capabilities.maxContextTokens。
3
+ * 不是账单合计,也不是 max_tokens。
4
+ * parts 按本轮 system / messages / tools 字符比例把 used 拆开(无独立 tokenizer)。
5
+ */
6
+ export interface ContextUsageParts {
7
+ system: number;
8
+ messages: number;
9
+ tools: number;
10
+ }
11
+ export interface ContextWindowUsage {
12
+ used: number;
13
+ window?: number;
14
+ parts?: ContextUsageParts;
15
+ }
16
+ export declare function contextWindowUsage(used: number, window: number | undefined): ContextWindowUsage | undefined;
17
+ export declare function splitContextUsage(used: number, sections: {
18
+ systemChars: number;
19
+ messageChars: number;
20
+ toolChars: number;
21
+ }): ContextUsageParts | undefined;
22
+ export declare function turnSectionChars(config: {
23
+ systemPrompt?: string;
24
+ tools?: unknown[];
25
+ }, messages: Array<{
26
+ content?: string;
27
+ toolCalls?: unknown;
28
+ }>): {
29
+ systemChars: number;
30
+ messageChars: number;
31
+ toolChars: number;
32
+ };
33
+ export declare function buildContextUsage(used: number, window: number | undefined, sections?: {
34
+ systemChars: number;
35
+ messageChars: number;
36
+ toolChars: number;
37
+ }): ContextWindowUsage | undefined;
38
+ export declare function pickContextFields(raw: {
39
+ used?: unknown;
40
+ window?: unknown;
41
+ parts?: unknown;
42
+ }): Partial<ContextWindowUsage>;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 本轮 prompt 占用的上下文窗:used = 本轮 inputTokens,window = ModelRecord.capabilities.maxContextTokens。
3
+ * 不是账单合计,也不是 max_tokens。
4
+ * parts 按本轮 system / messages / tools 字符比例把 used 拆开(无独立 tokenizer)。
5
+ */
6
+ export function contextWindowUsage(used, window) {
7
+ return buildContextUsage(used, window);
8
+ }
9
+ export function splitContextUsage(used, sections) {
10
+ if (!(used > 0))
11
+ return undefined;
12
+ const systemChars = Math.max(0, sections.systemChars);
13
+ const messageChars = Math.max(0, sections.messageChars);
14
+ const toolChars = Math.max(0, sections.toolChars);
15
+ const total = systemChars + messageChars + toolChars;
16
+ if (total <= 0)
17
+ return undefined;
18
+ const system = Math.floor((used * systemChars) / total);
19
+ const tools = Math.floor((used * toolChars) / total);
20
+ const messages = used - system - tools;
21
+ return { system, messages, tools };
22
+ }
23
+ export function turnSectionChars(config, messages) {
24
+ const systemChars = (config.systemPrompt ?? '').length;
25
+ let messageChars = 0;
26
+ for (const message of messages) {
27
+ if (message.content)
28
+ messageChars += message.content.length;
29
+ if (message.toolCalls)
30
+ messageChars += jsonChars(message.toolCalls);
31
+ }
32
+ const toolChars = config.tools?.length ? jsonChars(config.tools) : 0;
33
+ return { systemChars, messageChars, toolChars };
34
+ }
35
+ export function buildContextUsage(used, window, sections) {
36
+ const safeUsed = typeof used === 'number' && Number.isFinite(used) && used >= 0 ? Math.floor(used) : 0;
37
+ const win = typeof window === 'number' && Number.isFinite(window) && window > 0
38
+ ? Math.floor(window)
39
+ : undefined;
40
+ const parts = sections ? splitContextUsage(safeUsed, sections) : undefined;
41
+ if (safeUsed === 0 && win == null && !parts)
42
+ return undefined;
43
+ return {
44
+ used: safeUsed,
45
+ ...(win != null ? { window: win } : {}),
46
+ ...(parts ? { parts } : {}),
47
+ };
48
+ }
49
+ export function pickContextFields(raw) {
50
+ const out = {};
51
+ if (typeof raw.used === 'number' && Number.isFinite(raw.used))
52
+ out.used = raw.used;
53
+ if (typeof raw.window === 'number' && Number.isFinite(raw.window) && raw.window > 0) {
54
+ out.window = raw.window;
55
+ }
56
+ if (raw.parts && typeof raw.parts === 'object' && !Array.isArray(raw.parts)) {
57
+ const p = raw.parts;
58
+ if (typeof p.system === 'number'
59
+ && typeof p.messages === 'number'
60
+ && typeof p.tools === 'number') {
61
+ out.parts = { system: p.system, messages: p.messages, tools: p.tools };
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+ function jsonChars(value) {
67
+ try {
68
+ return JSON.stringify(value).length;
69
+ }
70
+ catch {
71
+ return 0;
72
+ }
73
+ }
@@ -7,6 +7,7 @@ export { SessionBudgetChecker, SessionBudgetExhaustedError, } from './session-bu
7
7
  export type { NodeTokenUsage, TokenBreakdown, TaskTokenSummary, } from './token-stats.service.js';
8
8
  export { TokenStatsService } from './token-stats.service.js';
9
9
  export type { TokenDetailMode } from './token-detail-mode.js';
10
+ export { contextWindowUsage, buildContextUsage, splitContextUsage, turnSectionChars, pickContextFields, type ContextWindowUsage, type ContextUsageParts, } from './context-window.js';
10
11
  export { TokenStatsEmitter } from './token-detail-mode.js';
11
12
  export { ContextWindowBudget, createContextBudget, type ContextWindowBudgetConfig, type ContextSource, type SourceAllocation, type BudgetAllocation, DEFAULT_CONTEXT_BUDGET_CONFIG, } from './context-window-budget.js';
12
13
  export { CharBasedTokenEstimator, ModelBasedTokenEstimator, createTokenEstimator, type ITokenEstimator, } from './token-estimator.js';
@@ -3,6 +3,7 @@ export { PostgresTokenBudgetRepository } from './pg-token-budget.repository.js';
3
3
  export { TokenBudgetGuard, TokenBudgetExhaustedError, } from './token-budget-guard.js';
4
4
  export { SessionBudgetChecker, SessionBudgetExhaustedError, } from './session-budget-checker.js';
5
5
  export { TokenStatsService } from './token-stats.service.js';
6
+ export { contextWindowUsage, buildContextUsage, splitContextUsage, turnSectionChars, pickContextFields, } from './context-window.js';
6
7
  export { TokenStatsEmitter } from './token-detail-mode.js';
7
8
  // 上下文窗口预算分配
8
9
  export { ContextWindowBudget, createContextBudget, DEFAULT_CONTEXT_BUDGET_CONFIG, } from './context-window-budget.js';
@@ -5,7 +5,7 @@
5
5
  * `EAP_TOOL_GATEWAY_TRANSPORT=http` 时用 HttpToolExecutor(仅调试)。
6
6
  * desktop 经 ToolExecutionRouter 分流 Local / Gateway。
7
7
  */
8
- import type { ILocalToolInstallStore, LocalToolInstallRecord } from '@gt-fe/eap-contracts';
8
+ import type { ILocalToolInstallStore, LocalToolInstallRecord } from '@eap/contracts';
9
9
  import { type DeploymentContext } from './tool-execution-router.js';
10
10
  import type { IToolExecutor } from './tool-executor.interface.js';
11
11
  export declare class EmptyInstallStore implements ILocalToolInstallStore {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Dev 下按 type:http 清单直连出站(gateway.baseUrl + pathTemplate)。
3
- * URL 拼接与 host 白名单与 Tool Gateway 共用 @gt-fe/eap-contracts http-outbound。
3
+ * URL 拼接与 host 白名单与 Tool Gateway 共用 @eap/contracts http-outbound。
4
4
  */
5
5
  import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
6
6
  export interface DevHttpExecution {
@@ -9,6 +9,7 @@ export interface DevHttpExecution {
9
9
  baseUrl?: string;
10
10
  pathTemplate?: string;
11
11
  allowedHosts?: string[];
12
+ header?: Record<string, string>;
12
13
  timeout?: number;
13
14
  }
14
15
  export declare function resolveHttpExecutionFromRequest(request: ToolCallRequest): DevHttpExecution | null;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Dev 下按 type:http 清单直连出站(gateway.baseUrl + pathTemplate)。
3
- * URL 拼接与 host 白名单与 Tool Gateway 共用 @gt-fe/eap-contracts http-outbound。
3
+ * URL 拼接与 host 白名单与 Tool Gateway 共用 @eap/contracts http-outbound。
4
4
  */
5
- import { assertHttpHostAllowlisted, buildHttpToolUrl as buildSharedHttpToolUrl, ErrorCodes } from '@gt-fe/eap-contracts';
5
+ import { assertHttpHostAllowlisted, buildHttpToolUrl as buildSharedHttpToolUrl, ErrorCodes } from '@eap/contracts';
6
6
  import { abortOrTimeoutMessage, DEFAULT_HTTP_TOOL_TIMEOUT_SEC, isAbortError, resolveToolTimeoutSec, toToolTimeoutMs, } from './timeout.js';
7
7
  export function resolveHttpExecutionFromRequest(request) {
8
8
  const raw = request.execution;
@@ -23,9 +23,20 @@ export function resolveHttpExecutionFromRequest(request) {
23
23
  allowedHosts: Array.isArray(e.allowedHosts)
24
24
  ? e.allowedHosts.filter((item) => typeof item === 'string')
25
25
  : [],
26
+ header: readHeaderMap(e.header),
26
27
  timeout: resolveToolTimeoutSec(e.timeout, DEFAULT_HTTP_TOOL_TIMEOUT_SEC),
27
28
  };
28
29
  }
30
+ function readHeaderMap(raw) {
31
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
32
+ return undefined;
33
+ const out = {};
34
+ for (const [key, value] of Object.entries(raw)) {
35
+ if (typeof value === 'string')
36
+ out[key] = value;
37
+ }
38
+ return Object.keys(out).length > 0 ? out : undefined;
39
+ }
29
40
  export function buildHttpToolUrl(spec, args) {
30
41
  const url = buildSharedHttpToolUrl(spec.baseUrl ?? '', spec.pathTemplate ?? '', args);
31
42
  assertHttpHostAllowlisted(url, spec.allowedHosts ?? []);
@@ -54,6 +65,7 @@ export class DevHttpExecutor {
54
65
  const timer = setTimeout(() => controller.abort(), toToolTimeoutMs(timeoutSec, DEFAULT_HTTP_TOOL_TIMEOUT_SEC));
55
66
  const headers = {
56
67
  Accept: 'application/json, text/plain;q=0.9',
68
+ ...(spec.header ?? {}),
57
69
  };
58
70
  const init = { method, headers, signal: controller.signal };
59
71
  if (method !== 'GET' && method !== 'HEAD') {
@@ -17,13 +17,23 @@
17
17
  * @see docs/workflows/local-agent-dev-loop.md
18
18
  * @see runtime/src/tool/local-mcp-client.ts
19
19
  */
20
+ import { type McpRuntimeExecution } from '@eap/contracts';
20
21
  import { type ILocalMcpClient } from './local-mcp-client.js';
21
- import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
22
+ import type { IToolExecutor, McpListedTool, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
22
23
  export interface DevMcpExecutorOptions {
23
24
  client?: ILocalMcpClient;
24
25
  }
25
26
  export declare class DevMcpExecutor implements IToolExecutor {
26
27
  private readonly client;
28
+ private readonly listCache;
27
29
  constructor(options?: DevMcpExecutorOptions);
28
30
  execute(request: ToolCallRequest): Promise<ToolCallResult>;
31
+ listTools(request: ToolCallRequest): Promise<McpListedTool[]>;
29
32
  }
33
+ /**
34
+ * 把 tool.manifest.execution(McpExecution 结构,transport 可为 stdio/sse/streamable_http)
35
+ * 规范化为 LocalMcpClient 消费的 McpRuntimeExecution(transport 只支持 stdio/streamable_http)。
36
+ *
37
+ * sse 归并到 streamable_http(LocalMcpClient 的 http 分支已能解析 SSE 响应)。
38
+ */
39
+ export declare function normalizeMcpExecution(raw: unknown, fallbackToolName: string): McpRuntimeExecution | null;
@@ -17,11 +17,12 @@
17
17
  * @see docs/workflows/local-agent-dev-loop.md
18
18
  * @see runtime/src/tool/local-mcp-client.ts
19
19
  */
20
- import { ErrorCodes } from '@gt-fe/eap-contracts';
20
+ import { ErrorCodes } from '@eap/contracts';
21
21
  import { isAbortError } from './timeout.js';
22
22
  import { LocalMcpClient } from './local-mcp-client.js';
23
23
  export class DevMcpExecutor {
24
24
  client;
25
+ listCache = new Map();
25
26
  constructor(options = {}) {
26
27
  this.client = options.client ?? new LocalMcpClient();
27
28
  }
@@ -64,6 +65,28 @@ export class DevMcpExecutor {
64
65
  };
65
66
  }
66
67
  }
68
+ async listTools(request) {
69
+ const execution = normalizeExecution(request.execution, request.toolName);
70
+ if (!execution) {
71
+ throw new Error(`Dev MCP: tool "${request.toolName}" has no valid mcp execution config (expect manifest.execution.protocol="mcp")`);
72
+ }
73
+ const url = execution.server?.url ?? '';
74
+ const cmd = execution.server?.command ?? '';
75
+ const args = (execution.server?.args ?? []).join('\0');
76
+ const key = `${url || `stdio:${cmd}:${args}`}\0${request.authContext?.userId ?? ''}\0${request.authContext?.agentCode ?? ''}`;
77
+ const cached = this.listCache.get(key);
78
+ if (cached)
79
+ return cached;
80
+ const pending = this.client.list(execution, { userId: request.authContext?.userId });
81
+ this.listCache.set(key, pending);
82
+ try {
83
+ return await pending;
84
+ }
85
+ catch (err) {
86
+ this.listCache.delete(key);
87
+ throw err;
88
+ }
89
+ }
67
90
  }
68
91
  /**
69
92
  * 把 tool.manifest.execution(McpExecution 结构,transport 可为 stdio/sse/streamable_http)
@@ -71,6 +94,9 @@ export class DevMcpExecutor {
71
94
  *
72
95
  * sse 归并到 streamable_http(LocalMcpClient 的 http 分支已能解析 SSE 响应)。
73
96
  */
97
+ export function normalizeMcpExecution(raw, fallbackToolName) {
98
+ return normalizeExecution(raw, fallbackToolName);
99
+ }
74
100
  function normalizeExecution(raw, fallbackToolName) {
75
101
  if (!raw || typeof raw !== 'object')
76
102
  return null;