@gt-fe/eap-runtime 0.0.34 → 0.0.35

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.
@@ -18,6 +18,7 @@ import { assembleLoadedTools } from '../tool/harness-primitives.js';
18
18
  import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
19
19
  import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractHitlThreadId, applyStreamReasoningEvent, applyStreamTextDelta, createStreamAssistantBuckets, createStreamReasoningState, resolvedStreamReasoning, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
20
20
  import { collectPublicArtifactLinks, collectPublicArtifactLinksFromEvent, injectPublicArtifactLinks, mergeArtifactLinks, } from '../tool/inject-public-artifact-links.js';
21
+ import { correctEmployeeVisibleReply, employeeStreamClose, takeEmployeeVisibleEvents, } from '../tool/mcp-tool-exposure.js';
21
22
  import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
22
23
  import { applySessionMemory } from '../session/apply-session-memory.js';
23
24
  import { buildSessionTitleUserPayload, invokeSessionTitle, } from '../session/session-title.js';
@@ -465,19 +466,23 @@ export class AgentRuntime {
465
466
  applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
466
467
  applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
467
468
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
468
- if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
469
- yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
469
+ for (const visibleEvent of takeEmployeeVisibleEvents(runtimeEvent.type, runtimeEvent.payload)) {
470
+ if (shouldEmitTokenSse(tokenDetail, visibleEvent.type)) {
471
+ yield this.runtimeEventToSSE({ ...runtimeEvent, type: visibleEvent.type, payload: visibleEvent.payload }, correlationId, task.id);
472
+ }
470
473
  }
471
474
  }
472
475
  await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
473
476
  await this.deps.taskService.updateStatus(task.id, 'success');
474
477
  const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
475
- const visible = visibleStreamAssistantText(assistantBuckets);
478
+ const rawVisible = visibleStreamAssistantText(assistantBuckets);
479
+ const visible = correctEmployeeVisibleReply(rawVisible, toolResults);
476
480
  const links = mergeArtifactLinks(artifactLinks, collectPublicArtifactLinks(toolResults));
477
481
  const { injected, suffix } = injectPublicArtifactLinks(visible, links);
478
- if (suffix) {
482
+ const close = employeeStreamClose({ raw: rawVisible, tools: toolResults, suffix });
483
+ for (const content of close.chunks) {
479
484
  yield this.toSSE('response.chunk', correlationId, task.id, {
480
- content: suffix,
485
+ content,
481
486
  format: 'text',
482
487
  });
483
488
  }
@@ -492,7 +497,7 @@ export class AgentRuntime {
492
497
  messages: buildTurnTranscriptFromStreamEvents({
493
498
  pendingToolCalls,
494
499
  toolResults,
495
- assistantTexts: suffix ? undefined : streamAssistantTexts(assistantBuckets),
500
+ assistantTexts: close.persistCorrected ? undefined : streamAssistantTexts(assistantBuckets),
496
501
  assistantText: injected,
497
502
  finishPoints: graph.finishPoints,
498
503
  toolCallReasoningContent,
@@ -558,6 +563,7 @@ export class AgentRuntime {
558
563
  */
559
564
  async resumeHitl(taskId, response) {
560
565
  let output = '';
566
+ let finalText = '';
561
567
  let waiting;
562
568
  let sawWait = false;
563
569
  const assistantBuckets = createStreamAssistantBuckets();
@@ -572,12 +578,21 @@ export class AgentRuntime {
572
578
  };
573
579
  output = waiting.question;
574
580
  }
581
+ if (event.type === 'response.final') {
582
+ const content = event.data?.content;
583
+ if (typeof content === 'string')
584
+ finalText = content;
585
+ }
575
586
  applyStreamTextDelta(assistantBuckets, event.type, event.data);
576
587
  }
577
588
  if (sawWait) {
578
589
  return { taskId, status: 'waiting_hitl', output, hitl: waiting };
579
590
  }
580
- return { taskId, status: 'success', output: visibleStreamAssistantText(assistantBuckets) || output };
591
+ return {
592
+ taskId,
593
+ status: 'success',
594
+ output: finalText || visibleStreamAssistantText(assistantBuckets) || output,
595
+ };
581
596
  }
582
597
  /**
583
598
  * HITL 恢复的流式入口:新开一条 SSE(第一条 stream 在 `hitl.waited` 后已结束)。
@@ -692,16 +707,20 @@ export class AgentRuntime {
692
707
  applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
693
708
  applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
694
709
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
695
- if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
696
- yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
710
+ for (const visibleEvent of takeEmployeeVisibleEvents(runtimeEvent.type, runtimeEvent.payload)) {
711
+ if (shouldEmitTokenSse(tokenDetail, visibleEvent.type)) {
712
+ yield this.runtimeEventToSSE({ ...runtimeEvent, type: visibleEvent.type, payload: visibleEvent.payload }, correlationId, taskId);
713
+ }
697
714
  }
698
715
  }
699
- const visible = visibleStreamAssistantText(assistantBuckets);
716
+ const rawVisible = visibleStreamAssistantText(assistantBuckets);
717
+ const visible = correctEmployeeVisibleReply(rawVisible, toolResults);
700
718
  const links = mergeArtifactLinks(artifactLinks, collectPublicArtifactLinks(toolResults));
701
719
  const { injected, suffix } = injectPublicArtifactLinks(visible, links);
702
- if (suffix) {
720
+ const close = employeeStreamClose({ raw: rawVisible, tools: toolResults, suffix });
721
+ for (const content of close.chunks) {
703
722
  yield this.toSSE('response.chunk', correlationId, taskId, {
704
- content: suffix,
723
+ content,
705
724
  format: 'text',
706
725
  });
707
726
  }
@@ -720,7 +739,7 @@ export class AgentRuntime {
720
739
  messages: buildTurnTranscriptFromStreamEvents({
721
740
  pendingToolCalls,
722
741
  toolResults,
723
- assistantTexts: suffix ? undefined : streamAssistantTexts(assistantBuckets),
742
+ assistantTexts: close.persistCorrected ? undefined : streamAssistantTexts(assistantBuckets),
724
743
  assistantText: injected,
725
744
  finishPoints: graph.finishPoints,
726
745
  toolCallReasoningContent,
@@ -22,4 +22,5 @@ export { expandSkillScriptRefs, isSkillEntryFile, EAP_SKILL_DIR_ENV, } from './s
22
22
  export { ToolResultInjector, type ToolMessage, } from './tool-result-injector.js';
23
23
  export { ToolEventEmitter } from './tool-event-emitter.js';
24
24
  export { ToolConfirmationInterceptor } from './tool-confirmation.js';
25
+ export { TOOL_SEARCH_NAME, buildMcpToolIndex, deferMcpToolsEnabled, resolveModelToolSurface, runToolSearch, } from './mcp-tool-exposure.js';
25
26
  export { SystemPromptBuilder, naturalTimeWindows, buildSkillCatalog, catalogEntryTitle, spokenName, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, type PromptBuildContext, type PromptSection, type AssembledSystemPrompt, type ApiToolDefinition, type LoadedSkillInfo, type LoadedSkillMap, } from './system-prompt-builder.js';
@@ -21,4 +21,5 @@ export { expandSkillScriptRefs, isSkillEntryFile, EAP_SKILL_DIR_ENV, } from './s
21
21
  export { ToolResultInjector, } from './tool-result-injector.js';
22
22
  export { ToolEventEmitter } from './tool-event-emitter.js';
23
23
  export { ToolConfirmationInterceptor } from './tool-confirmation.js';
24
+ export { TOOL_SEARCH_NAME, buildMcpToolIndex, deferMcpToolsEnabled, resolveModelToolSurface, runToolSearch, } from './mcp-tool-exposure.js';
24
25
  export { SystemPromptBuilder, naturalTimeWindows, buildSkillCatalog, catalogEntryTitle, spokenName, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, } from './system-prompt-builder.js';
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 每轮发给模型的 function schema 按工具来源决定。
3
+ *
4
+ * builtin(execution.source=builtin)以及 type 为 code / http 的工具,始终带完整参数。
5
+ * type=mcp 且不是 builtin 的,默认不带参数;名称和一行用途写入 system 段 mcp-tools。
6
+ * 点名,或没点名就调用,都不执行;名字记入本会话,下一次请求带上当前参数后再打外部系统。
7
+ * 没点名的那句只给模型。员工看模型正文和真正的工具结果(含飞书报错、用户拒绝),不另写未执行。
8
+ * 新会话不沿用。没有 sessionId 时按 taskId。EAP_DEFER_MCP_TOOLS=0 关闭,恢复为全部 schema 都下发。
9
+ */
10
+ import type { GraphMessage } from '../graph/contracts/executable-graph.js';
11
+ import type { LLMNodeConfig } from '../graph/nodes/llm-node.js';
12
+ import type { LoadedToolDefinition, LoadedToolMap } from './loaded-tool-definition.js';
13
+ import { type ApiToolDefinition } from './system-prompt-builder.js';
14
+ export declare const TOOL_SEARCH_NAME = "tool_search";
15
+ export declare function deferMcpToolsEnabled(env?: Record<string, string | undefined>): boolean;
16
+ export declare function isDeferredMcpTool(tool: LoadedToolDefinition): boolean;
17
+ export declare function hasDeferredMcp(loadedTools: LoadedToolMap | undefined): boolean;
18
+ /** 具名 toolChoice 指向的函数。auto / none / required 没有名字。 */
19
+ export declare function forcedToolName(toolChoice: LLMNodeConfig['toolChoice'] | unknown): string | undefined;
20
+ /** 有 sessionId 用会话,否则用 taskId。空字符串表示无处可记。 */
21
+ export declare function exposureScopeId(input: {
22
+ sessionId?: unknown;
23
+ taskId?: unknown;
24
+ }): string;
25
+ export declare function exposedNamesForScope(names: unknown, storedScopeId: unknown, scopeId: string): string[];
26
+ export declare function mergeExposed(current: readonly string[], additions: readonly string[]): string[];
27
+ export interface ModelToolSurface {
28
+ apiTools: ApiToolDefinition[];
29
+ /** 段 mcp-tools 的正文。没有要推迟的 MCP、或本轮 disableTools、或开关关闭时缺省。 */
30
+ mcpToolIndex?: string;
31
+ /** 本轮是否按来源推迟了 MCP 参数。 */
32
+ deferred: boolean;
33
+ }
34
+ export declare function resolveModelToolSurface(input: {
35
+ loadedTools: LoadedToolMap | undefined;
36
+ disableTools: boolean;
37
+ exposedNames: readonly string[];
38
+ forcedName?: string;
39
+ env?: Record<string, string | undefined>;
40
+ }): ModelToolSurface;
41
+ /** 全部推迟中的 MCP:名称 + 现有 description,不截断,不含参数 schema。 */
42
+ export declare function buildMcpToolIndex(loadedTools: LoadedToolMap | undefined): string | undefined;
43
+ export declare function toolSearchApiTool(): ApiToolDefinition;
44
+ export interface ToolSearchOutcome {
45
+ content: string;
46
+ /** 这些 MCP 名字的完整参数从下一次模型调用开始带上。 */
47
+ expose: string[];
48
+ }
49
+ export declare function unloadedCallToolContent(name: string): string;
50
+ export declare function isUnloadedDeferredCall(tool: LoadedToolDefinition | undefined, sentToolNames: readonly string[] | undefined): boolean;
51
+ export declare function runToolSearch(args: Record<string, unknown> | undefined, loadedTools: LoadedToolMap | undefined, alreadyInTools?: readonly string[]): ToolSearchOutcome;
52
+ export declare function partitionToolSearchCalls<T extends {
53
+ name: string;
54
+ }>(calls: T[], loadedTools: LoadedToolMap | undefined, env?: Record<string, string | undefined>): {
55
+ deferActive: boolean;
56
+ intercept: boolean;
57
+ search: T[];
58
+ business: T[];
59
+ };
60
+ /** tool_search 点名,以及本轮调用过的 MCP,记入本会话。直接调用这一次不执行。 */
61
+ export declare function mcpNamesToExpose(loadedTools: LoadedToolMap | undefined, searchExpose: readonly string[], businessNames: readonly string[]): string[];
62
+ export interface VisibleToolFact {
63
+ name?: string;
64
+ toolName?: string;
65
+ content?: unknown;
66
+ success?: boolean;
67
+ }
68
+ /** 发给模型的历史。不改会话存档。只处理这次 tools 里没有的名字。 */
69
+ export declare function projectHistoryForModelTools(messages: GraphMessage[], toolNames: ReadonlySet<string>): GraphMessage[];
70
+ /**
71
+ * 员工可见正文。未点名、只报了名,都不改模型的话。
72
+ * 用户拒绝仍补一行。飞书自己的成功或错误不改。
73
+ */
74
+ export declare function correctEmployeeVisibleReply(visible: string, tools: readonly VisibleToolFact[]): string;
75
+ /**
76
+ * 还有未点名的 MCP 时,这段模型正文先攒着,等这条消息落定再决定发不发给员工。
77
+ * 全部已经在本次 tools 里,或本次没有 tools,仍逐 token 推。
78
+ */
79
+ export declare function holdEmployeeTextForUnnamedMcp(loadedTools: LoadedToolMap | undefined, sentToolNames: readonly string[]): boolean;
80
+ /**
81
+ * 这条模型消息还不能当员工结论:带着没点名的调用,或这一次只点了名。
82
+ * 没有工具调用时不丢,交给回合结束时的闸门。
83
+ */
84
+ export declare function shouldDropUnsettledEmployeeText(calls: readonly {
85
+ name: string;
86
+ }[] | undefined, loadedTools: LoadedToolMap | undefined, sentToolNames: readonly string[] | undefined): boolean;
87
+ export interface EmployeeHeldEvent {
88
+ type: string;
89
+ payload: Record<string, unknown>;
90
+ }
91
+ /**
92
+ * 没点名的工具事件和 tool_search 不进员工 SSE。
93
+ * 判定:`modelOnly`,或工具名是 `tool_search`,或正文以 `未调用 ` 开头。
94
+ */
95
+ export declare function takeEmployeeVisibleEvents(eventType: string, payload: unknown): EmployeeHeldEvent[];
96
+ export interface EmployeeTurnClose {
97
+ visible: string;
98
+ /** 用户拒绝时补在最后的那几行。带开头换行。没有则为空。 */
99
+ tailChunk: string;
100
+ }
101
+ export declare function employeeTurnClose(raw: string, tools: readonly VisibleToolFact[]): EmployeeTurnClose;
102
+ /** 回合结束时要补发的 response.chunk。final 仍是 injected。 */
103
+ export declare function employeeStreamClose(input: {
104
+ raw: string;
105
+ tools: readonly VisibleToolFact[];
106
+ suffix?: string;
107
+ }): {
108
+ chunks: string[];
109
+ persistCorrected: boolean;
110
+ };
111
+ export declare function toolFactsSinceLastUser(messages: readonly unknown[]): VisibleToolFact[];
112
+ /** 本轮最后一条助手正文。图若停在 tool 节点,不用工具正文当员工结论。 */
113
+ export declare function lastAssistantTextSinceLastUser(messages: readonly unknown[]): string;