





















@@ -69,4 +69,186 @@ describe("buildEmbeddedExtensionFactories", () => {
6969expect(seenToolCallIds[1]).toMatch(/^pi-/);
7070expect(seenToolCallIds[0]).not.toBe(seenToolCallIds[1]);
7171});
72+73+it("marks status-error tool results as model-visible failures", async () => {
74+setActivePluginRegistry(createEmptyPluginRegistry());
75+76+const factories = buildEmbeddedExtensionFactories({
77+cfg: undefined,
78+sessionManager: SessionManager.inMemory(),
79+provider: "openai",
80+modelId: "gpt-5.4",
81+model: undefined,
82+});
83+84+const handlers = new Map<string, Function>();
85+await factories[0]?.({
86+on(event: string, handler: Function) {
87+handlers.set(event, handler);
88+},
89+} as never);
90+const handler = handlers.get("tool_result");
91+const content = [{ type: "text", text: "oldText must be unique" }];
92+const details = {
93+status: "error",
94+tool: "edit",
95+error: "oldText must be unique",
96+};
97+98+const result = await handler?.(
99+{
100+toolName: "edit",
101+toolCallId: "call-edit",
102+ content,
103+ details,
104+isError: false,
105+},
106+{ cwd: "/tmp" },
107+);
108+109+expect(result).toEqual({
110+ content,
111+ details,
112+isError: true,
113+});
114+});
115+116+it("preserves model-visible failures when middleware rewrites details", async () => {
117+const registry = createEmptyPluginRegistry();
118+registry.agentToolResultMiddlewares.push({
119+pluginId: "redactor",
120+pluginName: "redactor",
121+rawHandler: () => undefined,
122+handler: (event) => {
123+event.result.content = [{ type: "text", text: "redacted error" }];
124+event.result.details = { redacted: true };
125+return undefined;
126+},
127+runtimes: ["pi"],
128+source: "test",
129+});
130+setActivePluginRegistry(registry);
131+132+const factories = buildEmbeddedExtensionFactories({
133+cfg: undefined,
134+sessionManager: SessionManager.inMemory(),
135+provider: "openai",
136+modelId: "gpt-5.4",
137+model: undefined,
138+});
139+140+const handlers = new Map<string, Function>();
141+await factories[0]?.({
142+on(event: string, handler: Function) {
143+handlers.set(event, handler);
144+},
145+} as never);
146+const handler = handlers.get("tool_result");
147+148+const result = await handler?.(
149+{
150+toolName: "edit",
151+toolCallId: "call-edit",
152+content: [{ type: "text", text: "oldText must be unique" }],
153+details: { status: "error", tool: "edit", error: "oldText must be unique" },
154+isError: false,
155+},
156+{ cwd: "/tmp" },
157+);
158+159+expect(result).toEqual({
160+content: [{ type: "text", text: "redacted error" }],
161+details: { redacted: true },
162+isError: true,
163+});
164+});
165+166+it("marks status-timeout tool results as model-visible failures", async () => {
167+setActivePluginRegistry(createEmptyPluginRegistry());
168+169+const factories = buildEmbeddedExtensionFactories({
170+cfg: undefined,
171+sessionManager: SessionManager.inMemory(),
172+provider: "openai",
173+modelId: "gpt-5.4",
174+model: undefined,
175+});
176+177+const handlers = new Map<string, Function>();
178+await factories[0]?.({
179+on(event: string, handler: Function) {
180+handlers.set(event, handler);
181+},
182+} as never);
183+const handler = handlers.get("tool_result");
184+185+const result = await handler?.(
186+{
187+toolName: "exec",
188+toolCallId: "call-exec",
189+content: [{ type: "text", text: "Timed out" }],
190+details: { status: "timeout", tool: "exec", error: "Timed out" },
191+isError: false,
192+},
193+{ cwd: "/tmp" },
194+);
195+196+expect(result).toEqual({
197+content: [{ type: "text", text: "Timed out" }],
198+details: { status: "timeout", tool: "exec", error: "Timed out" },
199+isError: true,
200+});
201+});
202+203+it("does not mark results as errors when status is absent or non-error", async () => {
204+setActivePluginRegistry(createEmptyPluginRegistry());
205+206+const factories = buildEmbeddedExtensionFactories({
207+cfg: undefined,
208+sessionManager: SessionManager.inMemory(),
209+provider: "openai",
210+modelId: "gpt-5.4",
211+model: undefined,
212+});
213+214+const handlers = new Map<string, Function>();
215+await factories[0]?.({
216+on(event: string, handler: Function) {
217+handlers.set(event, handler);
218+},
219+} as never);
220+const handler = handlers.get("tool_result");
221+222+// Empty details — no status field
223+const noStatusResult = await handler?.(
224+{
225+toolName: "read",
226+toolCallId: "call-read",
227+content: [{ type: "text", text: "file contents" }],
228+details: {},
229+isError: false,
230+},
231+{ cwd: "/tmp" },
232+);
233+expect(noStatusResult).toEqual({
234+content: [{ type: "text", text: "file contents" }],
235+details: {},
236+});
237+238+// Explicit ok status
239+const okResult = await handler?.(
240+{
241+toolName: "read",
242+toolCallId: "call-read-2",
243+content: [{ type: "text", text: "ok" }],
244+details: { status: "ok" },
245+isError: false,
246+},
247+{ cwd: "/tmp" },
248+);
249+expect(okResult).toEqual({
250+content: [{ type: "text", text: "ok" }],
251+details: { status: "ok" },
252+});
253+});
72254});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。