
























@@ -9,6 +9,7 @@ import {
99inferToolMetaFromArgs,
1010normalizeUsage,
1111runAgentHarnessAfterCompactionHook,
12+runAgentHarnessAfterToolCallHook,
1213runAgentHarnessBeforeCompactionHook,
1314TOOL_PROGRESS_OUTPUT_MAX_CHARS,
1415type AgentMessage,
@@ -50,6 +51,10 @@ export type CodexAppServerToolTelemetry = {
5051successfulCronAdds?: number;
5152};
525354+export type CodexAppServerEventProjectorOptions = {
55+nativePostToolUseRelayEnabled?: boolean;
56+};
57+5358const ZERO_USAGE: Usage = {
5459input: 0,
5560output: 0,
@@ -118,6 +123,7 @@ export class CodexAppServerEventProjector {
118123private readonly toolTranscriptResultIds = new Set<string>();
119124private readonly nativeGeneratedMediaUrls = new Set<string>();
120125private readonly diagnosticToolStartedAtByItem = new Map<string, number>();
126+private readonly afterToolCallObservedItemIds = new Set<string>();
121127private assistantStarted = false;
122128private reasoningStarted = false;
123129private reasoningEnded = false;
@@ -134,6 +140,7 @@ export class CodexAppServerEventProjector {
134140private readonly params: EmbeddedRunAttemptParams,
135141private readonly threadId: string,
136142private readonly turnId: string,
143+private readonly options: CodexAppServerEventProjectorOptions = {},
137144) {}
138145139146async handleNotification(notification: CodexServerNotification): Promise<void> {
@@ -613,6 +620,7 @@ export class CodexAppServerEventProjector {
613620this.recordToolMeta(item);
614621this.recordNativeToolTranscriptCall(item);
615622this.recordNativeToolTranscriptResult(item);
623+this.emitAfterToolCallObservation(item);
616624this.emitToolResultSummary(item);
617625this.emitToolResultOutput(item);
618626}
@@ -790,6 +798,9 @@ export class CodexAppServerEventProjector {
790798 : {}),
791799},
792800});
801+if (params.phase === "result") {
802+this.emitAfterToolCallObservation(item);
803+}
793804}
794805795806private emitDiagnosticToolExecutionEvent(params: {
@@ -840,6 +851,53 @@ export class CodexAppServerEventProjector {
840851emitTrustedDiagnosticEvent({ ...base, ...terminalEvent });
841852}
842853854+private emitAfterToolCallObservation(item: CodexThreadItem): void {
855+if (!this.shouldEmitAfterToolCallObservation(item)) {
856+return;
857+}
858+const name = itemName(item);
859+if (!name) {
860+return;
861+}
862+const status = itemStatus(item);
863+if (status === "running") {
864+return;
865+}
866+this.afterToolCallObservedItemIds.add(item.id);
867+const result = itemToolResult(item).result;
868+const error = itemToolError(item, status);
869+const startedAt =
870+typeof item.durationMs === "number" ? Date.now() - Math.max(0, item.durationMs) : undefined;
871+const hookParams = {
872+toolName: name,
873+toolCallId: item.id,
874+runId: this.params.runId,
875+agentId: this.params.agentId,
876+sessionId: this.params.sessionId,
877+sessionKey: this.params.sessionKey,
878+startArgs: itemToolArgs(item) ?? {},
879+ ...(result !== undefined ? { result } : {}),
880+ ...(error ? { error } : {}),
881+ ...(startedAt !== undefined ? { startedAt } : {}),
882+};
883+setImmediate(() => {
884+void runAgentHarnessAfterToolCallHook(hookParams);
885+});
886+}
887+888+private shouldEmitAfterToolCallObservation(item: CodexThreadItem): boolean {
889+if (
890+!shouldSynthesizeToolProgressForItem(item) ||
891+this.afterToolCallObservedItemIds.has(item.id)
892+) {
893+return false;
894+}
895+if (this.options.nativePostToolUseRelayEnabled && isNativePostToolUseRelayItem(item)) {
896+return false;
897+}
898+return true;
899+}
900+843901private emitToolResultSummary(item: CodexThreadItem | undefined): void {
844902if (!item || !this.params.onToolResult || !this.shouldEmitToolResult()) {
845903return;
@@ -1397,6 +1455,17 @@ function shouldSynthesizeToolProgressForItem(item: CodexThreadItem): boolean {
13971455}
13981456}
139914571458+function isNativePostToolUseRelayItem(item: CodexThreadItem): boolean {
1459+switch (item.type) {
1460+case "commandExecution":
1461+case "fileChange":
1462+case "mcpToolCall":
1463+return true;
1464+default:
1465+return false;
1466+}
1467+}
1468+14001469function shouldSuppressChannelProgressForItem(item: CodexThreadItem): boolean {
14011470if (shouldSynthesizeToolProgressForItem(item)) {
14021471return true;
@@ -1414,6 +1483,11 @@ function itemToolArgs(item: CodexThreadItem): Record<string, unknown> | undefine
14141483 ...(typeof item.cwd === "string" ? { cwd: item.cwd } : {}),
14151484});
14161485}
1486+if (item.type === "fileChange") {
1487+return sanitizeCodexAgentEventRecord({
1488+changes: itemFileChanges(item),
1489+});
1490+}
14171491if (item.type === "webSearch" && typeof item.query === "string") {
14181492return sanitizeCodexAgentEventRecord({ query: item.query });
14191493}
@@ -1437,7 +1511,7 @@ function itemToolResult(item: CodexThreadItem): { result?: Record<string, unknow
14371511return {
14381512result: sanitizeCodexAgentEventRecord({
14391513status: item.status,
1440-changes: item.changes.map((change) => ({ path: change.path, kind: change.kind })),
1514+changes: itemFileChanges(item),
14411515}),
14421516};
14431517}
@@ -1457,6 +1531,25 @@ function itemToolResult(item: CodexThreadItem): { result?: Record<string, unknow
14571531return {};
14581532}
145915331534+function itemFileChanges(item: CodexThreadItem): Array<{ path: string; kind: string }> {
1535+return Array.isArray(item.changes)
1536+ ? item.changes.map((change) => ({ path: change.path, kind: change.kind }))
1537+ : [];
1538+}
1539+1540+function itemToolError(
1541+item: CodexThreadItem,
1542+status: ReturnType<typeof itemStatus>,
1543+): string | undefined {
1544+if (status === "blocked") {
1545+return "codex native tool blocked";
1546+}
1547+if (status !== "failed") {
1548+return undefined;
1549+}
1550+return itemOutputText(item) ?? "codex native tool failed";
1551+}
1552+14601553function itemMeta(
14611554item: CodexThreadItem,
14621555detailMode: ToolProgressDetailMode = "explain",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。