



























@@ -0,0 +1,164 @@
1+import { runCliAgent } from "../../agents/cli-runner.js";
2+import type { RunCliAgentParams } from "../../agents/cli-runner/types.js";
3+import type { EmbeddedPiRunResult } from "../../agents/pi-embedded.js";
4+import { emitAgentEvent, onAgentEvent } from "../../infra/agent-events.js";
5+import {
6+normalizeLowercaseStringOrEmpty,
7+normalizeOptionalString,
8+} from "../../shared/string-coerce.js";
9+10+function shouldBridgeCliAssistantTextToReasoning(provider: string): boolean {
11+return normalizeLowercaseStringOrEmpty(provider) === "claude-cli";
12+}
13+14+function createAssistantTextBridge(params: {
15+runId: string;
16+suppressed?: boolean;
17+deliver?: (text: string) => Promise<void>;
18+}) {
19+const deliver = params.deliver;
20+if (!deliver) {
21+return {
22+unsubscribe: () => undefined,
23+drain: async (): Promise<void> => undefined,
24+};
25+}
26+let lastText: string | undefined;
27+let unsubscribed = false;
28+let delivery = Promise.resolve();
29+const rawUnsubscribe = onAgentEvent((evt) => {
30+if (evt.runId !== params.runId || evt.stream !== "assistant") {
31+return;
32+}
33+if (params.suppressed) {
34+return;
35+}
36+const text = typeof evt.data.text === "string" ? evt.data.text : undefined;
37+if (text === undefined || text === lastText) {
38+return;
39+}
40+lastText = text;
41+delivery = delivery.then(() => deliver(text)).catch(() => undefined);
42+});
43+return {
44+unsubscribe() {
45+if (unsubscribed) {
46+return;
47+}
48+unsubscribed = true;
49+rawUnsubscribe();
50+},
51+async drain(): Promise<void> {
52+await delivery;
53+},
54+};
55+}
56+57+export async function runCliAgentWithLifecycle(params: {
58+runId: string;
59+provider: string;
60+runParams: RunCliAgentParams;
61+startedAt?: number;
62+emitLifecycleStart?: boolean;
63+emitLifecycleTerminal?: boolean;
64+onAgentRunStart?: () => void;
65+suppressAssistantBridge?: boolean;
66+onAssistantText?: (text: string) => Promise<void>;
67+onReasoningText?: (text: string) => Promise<void>;
68+onErrorBeforeLifecycle?: (err: unknown) => Promise<void>;
69+transformResult?: (result: EmbeddedPiRunResult) => EmbeddedPiRunResult;
70+}): Promise<EmbeddedPiRunResult> {
71+const startedAt = params.startedAt ?? Date.now();
72+const emitLifecycleStart = params.emitLifecycleStart ?? true;
73+const emitLifecycleTerminal = params.emitLifecycleTerminal ?? true;
74+params.onAgentRunStart?.();
75+if (emitLifecycleStart) {
76+emitAgentEvent({
77+runId: params.runId,
78+stream: "lifecycle",
79+data: {
80+phase: "start",
81+ startedAt,
82+},
83+});
84+}
85+const assistantBridge = createAssistantTextBridge({
86+runId: params.runId,
87+suppressed: params.suppressAssistantBridge,
88+deliver: params.onAssistantText,
89+});
90+const reasoningBridge = createAssistantTextBridge({
91+runId: params.runId,
92+suppressed: params.suppressAssistantBridge,
93+deliver: shouldBridgeCliAssistantTextToReasoning(params.provider)
94+ ? params.onReasoningText
95+ : undefined,
96+});
97+let lifecycleTerminalEmitted = false;
98+try {
99+const rawResult = await runCliAgent(params.runParams);
100+const result = params.transformResult?.(rawResult) ?? rawResult;
101+assistantBridge.unsubscribe();
102+reasoningBridge.unsubscribe();
103+await assistantBridge.drain();
104+await reasoningBridge.drain();
105+106+const cliText = normalizeOptionalString(result.payloads?.[0]?.text);
107+if (cliText) {
108+emitAgentEvent({
109+runId: params.runId,
110+stream: "assistant",
111+data: { text: cliText },
112+});
113+}
114+115+if (emitLifecycleTerminal) {
116+emitAgentEvent({
117+runId: params.runId,
118+stream: "lifecycle",
119+data: {
120+phase: "end",
121+ startedAt,
122+endedAt: Date.now(),
123+},
124+});
125+lifecycleTerminalEmitted = true;
126+}
127+return result;
128+} catch (err) {
129+assistantBridge.unsubscribe();
130+reasoningBridge.unsubscribe();
131+await assistantBridge.drain();
132+await reasoningBridge.drain();
133+await params.onErrorBeforeLifecycle?.(err);
134+if (emitLifecycleTerminal) {
135+emitAgentEvent({
136+runId: params.runId,
137+stream: "lifecycle",
138+data: {
139+phase: "error",
140+ startedAt,
141+endedAt: Date.now(),
142+error: String(err),
143+},
144+});
145+lifecycleTerminalEmitted = true;
146+}
147+throw err;
148+} finally {
149+assistantBridge.unsubscribe();
150+reasoningBridge.unsubscribe();
151+if (emitLifecycleTerminal && !lifecycleTerminalEmitted) {
152+emitAgentEvent({
153+runId: params.runId,
154+stream: "lifecycle",
155+data: {
156+phase: "error",
157+ startedAt,
158+endedAt: Date.now(),
159+error: "CLI run completed without lifecycle terminal event",
160+},
161+});
162+}
163+}
164+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。