





























@@ -95,6 +95,10 @@ const GPT_CHAT_BREVITY_ACK_MAX_SENTENCES = 3;
9595const GPT_CHAT_BREVITY_SOFT_MAX_CHARS = 900;
9696const GPT_CHAT_BREVITY_SOFT_MAX_SENTENCES = 6;
979798+function shouldBridgeCliAssistantTextToReasoning(provider: string): boolean {
99+return normalizeLowercaseStringOrEmpty(provider) === "claude-cli";
100+}
101+98102function readApprovalScopeValue(value: unknown): "turn" | "session" | undefined {
99103return value === "turn" || value === "session" ? value : undefined;
100104}
@@ -1508,92 +1512,53 @@ export async function runAgentTurnWithFallback(params: {
15081512});
15091513return (async () => {
15101514let lifecycleTerminalEmitted = false;
1511-let lastBridgedAssistantText: string | undefined;
1512-let assistantBridgeUnsubscribed = false;
1513-let assistantBridgeDelivery: Promise<void> = Promise.resolve();
1514-const deliverBridgedAssistantText = async (text: string): Promise<void> => {
1515+const createAssistantTextBridge = (deliver: (text: string) => Promise<void>) => {
1516+let lastText: string | undefined;
1517+let unsubscribed = false;
1518+let delivery = Promise.resolve();
1519+const rawUnsubscribe = onAgentEvent((evt) => {
1520+if (evt.runId !== runId || evt.stream !== "assistant") {
1521+return;
1522+}
1523+if (params.followupRun.run.silentExpected) {
1524+return;
1525+}
1526+const text = typeof evt.data.text === "string" ? evt.data.text : undefined;
1527+if (text === undefined || text === lastText) {
1528+return;
1529+}
1530+lastText = text;
1531+delivery = delivery.then(() => deliver(text)).catch(() => undefined);
1532+});
1533+return {
1534+unsubscribe() {
1535+if (unsubscribed) {
1536+return;
1537+}
1538+unsubscribed = true;
1539+rawUnsubscribe();
1540+},
1541+async drain(): Promise<void> {
1542+await delivery;
1543+},
1544+};
1545+};
1546+const noopBridge = {
1547+unsubscribe: () => undefined,
1548+drain: async (): Promise<void> => undefined,
1549+};
1550+const assistantBridge = createAssistantTextBridge(async (text) => {
15151551const textForTyping = await handlePartialForTyping({ text } as ReplyPayload);
15161552if (textForTyping === undefined || !params.opts?.onPartialReply) {
15171553return;
15181554}
15191555await params.opts.onPartialReply({ text: textForTyping });
1520-};
1521-const queueBridgedAssistantText = (text: string) => {
1522-assistantBridgeDelivery = assistantBridgeDelivery
1523-.then(() => deliverBridgedAssistantText(text))
1524-.catch(() => undefined);
1525-};
1526-const drainAssistantBridgeDelivery = async (): Promise<void> => {
1527-await assistantBridgeDelivery;
1528-};
1529-const rawUnsubscribeAssistantBridge = onAgentEvent((evt) => {
1530-if (evt.runId !== runId || evt.stream !== "assistant") {
1531-return;
1532-}
1533-if (params.followupRun.run.silentExpected) {
1534-return;
1535-}
1536-const text = typeof evt.data.text === "string" ? evt.data.text : undefined;
1537-if (text === undefined || text === lastBridgedAssistantText) {
1538-return;
1539-}
1540-lastBridgedAssistantText = text;
1541-queueBridgedAssistantText(text);
1542-});
1543-const unsubscribeAssistantBridge = () => {
1544-if (assistantBridgeUnsubscribed) {
1545-return;
1546-}
1547-assistantBridgeUnsubscribed = true;
1548-rawUnsubscribeAssistantBridge();
1549-};
1550-let lastBridgedReasoningText: string | undefined;
1551-let reasoningBridgeUnsubscribed = false;
1552-let reasoningBridgeDelivery: Promise<void> = Promise.resolve();
1553-const deliverBridgedReasoningText = async (text: string): Promise<void> => {
1554-if (typeof params.opts?.onReasoningStream !== "function") {
1555-return;
1556-}
1557-await params.opts.onReasoningStream({ text });
1558-};
1559-const queueBridgedReasoningText = (text: string) => {
1560-reasoningBridgeDelivery = reasoningBridgeDelivery
1561-.then(() => deliverBridgedReasoningText(text))
1562-.catch(() => undefined);
1563-};
1564-const drainReasoningBridgeDelivery = async (): Promise<void> => {
1565-await reasoningBridgeDelivery;
1566-};
1567-// CLI-runtime-only bridge: claude-opus-4-7 over `claude --output-format
1568-// stream-json` suppresses readable `thinking_delta` events on the wire,
1569-// so the reasoning preview lane stays silent during streaming. Bridge
1570-// `stream: "assistant"` agent-events to `onReasoningStream` so the
1571-// reasoning lane reflects the model's live text output. The reply lane
1572-// is unaffected: `onPartialReply` is still settled by the assistant-text
1573-// bridge above (PR #76914). API/native runtimes get reasoning content
1574-// from real thinking_delta events and must NOT double-receive
1575-// text_delta as reasoning — this bridge is gated on `isCliProvider`.
1576-const rawUnsubscribeReasoningBridge = onAgentEvent((evt) => {
1577-if (evt.runId !== runId || evt.stream !== "assistant") {
1578-return;
1579-}
1580-if (params.followupRun.run.silentExpected) {
1581-return;
1582-}
1583-const text = typeof evt.data.text === "string" ? evt.data.text : undefined;
1584-if (text === undefined || text === lastBridgedReasoningText) {
1585-return;
1586-}
1587-lastBridgedReasoningText = text;
1588-queueBridgedReasoningText(text);
15891556});
1590-const unsubscribeReasoningBridge = () => {
1591-if (reasoningBridgeUnsubscribed) {
1592-return;
1593-}
1594-reasoningBridgeUnsubscribed = true;
1595-rawUnsubscribeReasoningBridge();
1596-};
1557+const reasoningBridge = shouldBridgeCliAssistantTextToReasoning(cliExecutionProvider)
1558+ ? createAssistantTextBridge(async (text) => {
1559+await params.opts?.onReasoningStream?.({ text });
1560+})
1561+ : noopBridge;
15971562try {
15981563const result = await runCliAgent({
15991564sessionId: params.followupRun.run.sessionId,
@@ -1641,10 +1606,10 @@ export async function runAgentTurnWithFallback(params: {
16411606result.meta?.systemPromptReport,
16421607);
164316081644-unsubscribeAssistantBridge();
1645-unsubscribeReasoningBridge();
1646-await drainAssistantBridgeDelivery();
1647-await drainReasoningBridgeDelivery();
1609+assistantBridge.unsubscribe();
1610+reasoningBridge.unsubscribe();
1611+await assistantBridge.drain();
1612+await reasoningBridge.drain();
1648161316491614// CLI backends don't emit streaming assistant events, so we need to
16501615// emit one with the final text so server-chat can populate its buffer
@@ -1671,10 +1636,10 @@ export async function runAgentTurnWithFallback(params: {
1671163616721637return result;
16731638} catch (err) {
1674-unsubscribeAssistantBridge();
1675-unsubscribeReasoningBridge();
1676-await drainAssistantBridgeDelivery();
1677-await drainReasoningBridgeDelivery();
1639+assistantBridge.unsubscribe();
1640+reasoningBridge.unsubscribe();
1641+await assistantBridge.drain();
1642+await reasoningBridge.drain();
16781643if (rollbackFallbackCandidateSelection) {
16791644try {
16801645await rollbackFallbackCandidateSelection();
@@ -1698,8 +1663,8 @@ export async function runAgentTurnWithFallback(params: {
16981663lifecycleTerminalEmitted = true;
16991664throw err;
17001665} finally {
1701-unsubscribeAssistantBridge();
1702-unsubscribeReasoningBridge();
1666+assistantBridge.unsubscribe();
1667+reasoningBridge.unsubscribe();
17031668// Defensive backstop: never let a CLI run complete without a terminal
17041669// lifecycle event, otherwise downstream consumers can hang.
17051670if (!lifecycleTerminalEmitted) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。