























@@ -167,6 +167,291 @@ describe("createAgentToolResultMiddlewareRunner", () => {
167167});
168168});
169169170+it("sanitizes incoming details before failing closed on uncoercible content", async () => {
171+const details: Record<string, unknown> = {
172+ok: true,
173+callback: () => 1,
174+};
175+details.self = details;
176+let observedDetails: unknown;
177+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [
178+(event) => {
179+observedDetails = event.result.details;
180+return undefined;
181+},
182+]);
183+184+const result = await runner.applyToolResultMiddleware({
185+toolCallId: "call-1",
186+toolName: "message",
187+args: {},
188+result: {
189+content: [{ type: "unknown", payload: "raw" } as never],
190+ details,
191+},
192+});
193+194+expect(result.details).toEqual({ status: "error", middlewareError: true });
195+expect(observedDetails).toEqual({ ok: true });
196+});
197+198+it("coerces incoming nested toolResult content before middleware validation", async () => {
199+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
200+201+const result = await runner.applyToolResultMiddleware({
202+toolCallId: "call-1",
203+toolName: "message",
204+args: {},
205+result: {
206+content: [
207+{
208+type: "toolResult",
209+toolUseId: "call-1",
210+content: [
211+{ type: "text", text: "sent message id msg_123" },
212+{ type: "text", text: "status delivered" },
213+],
214+} as never,
215+],
216+details: { status: "sent", messageId: "msg_123" },
217+},
218+});
219+220+expect(result.content).toEqual([
221+{
222+type: "text",
223+text: "sent message id msg_123\nstatus delivered",
224+},
225+]);
226+expect(result.details).toEqual({ status: "sent", messageId: "msg_123" });
227+});
228+229+it("coerces nested tool_result blocks returned by middleware", async () => {
230+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [
231+() => ({
232+result: {
233+content: [
234+{
235+type: "tool_result",
236+content: {
237+message: "message delivered",
238+id: "msg_456",
239+},
240+} as never,
241+],
242+details: { status: "sent" },
243+},
244+}),
245+]);
246+247+const result = await runner.applyToolResultMiddleware({
248+toolCallId: "call-1",
249+toolName: "message",
250+args: {},
251+result: { content: [{ type: "text", text: "raw" }], details: {} },
252+});
253+254+expect(result.content).toEqual([{ type: "text", text: "message delivered" }]);
255+expect(result.details).toEqual({ status: "sent" });
256+});
257+258+it("does not coerce tool/function call blocks as middleware results", async () => {
259+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [
260+() => ({
261+result: {
262+content: [
263+{
264+type: "function",
265+name: "send_message",
266+arguments: { text: "raw" },
267+} as never,
268+],
269+details: {},
270+},
271+}),
272+]);
273+274+const result = await runner.applyToolResultMiddleware({
275+toolCallId: "call-1",
276+toolName: "message",
277+args: {},
278+result: { content: [{ type: "text", text: "raw" }], details: {} },
279+});
280+281+expect(result.details).toEqual({ status: "error", middlewareError: true });
282+});
283+284+it("bounds nested toolResult content before flattening", async () => {
285+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
286+287+const result = await runner.applyToolResultMiddleware({
288+toolCallId: "call-1",
289+toolName: "message",
290+args: {},
291+result: {
292+content: [
293+{
294+type: "toolResult",
295+toolUseId: "call-1",
296+content: [
297+ ...Array.from({ length: 200 }, () => ({
298+type: "text",
299+text: "x".repeat(600),
300+})),
301+{ type: "text", text: "late chunk" },
302+],
303+} as never,
304+],
305+details: {},
306+},
307+});
308+309+const content = result.content[0];
310+if (content?.type !== "text") {
311+throw new Error("expected flattened text content");
312+}
313+expect(content.text.length).toBeLessThanOrEqual(100_000);
314+expect(content.text).not.toContain("late chunk");
315+});
316+317+it("preserves nested image toolResult content without stringifying data", async () => {
318+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
319+320+const result = await runner.applyToolResultMiddleware({
321+toolCallId: "call-1",
322+toolName: "vision",
323+args: {},
324+result: {
325+content: [
326+{
327+type: "toolResult",
328+toolUseId: "call-1",
329+content: [{ type: "image", mimeType: "image/png", data: "base64-image" }],
330+} as never,
331+],
332+details: {},
333+},
334+});
335+336+expect(result.content).toEqual([
337+{ type: "image", mimeType: "image/png", data: "base64-image" },
338+]);
339+});
340+341+it("preserves mixed nested text and image toolResult content", async () => {
342+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
343+344+const result = await runner.applyToolResultMiddleware({
345+toolCallId: "call-1",
346+toolName: "screenshot",
347+args: {},
348+result: {
349+content: [
350+{
351+type: "toolResult",
352+toolUseId: "call-1",
353+content: [
354+{ type: "text", text: "captured screenshot" },
355+{ type: "image", mimeType: "image/png", data: "base64-image" },
356+],
357+} as never,
358+],
359+details: {},
360+},
361+});
362+363+expect(result.content).toEqual([
364+{ type: "text", text: "captured screenshot" },
365+{ type: "image", mimeType: "image/png", data: "base64-image" },
366+]);
367+});
368+369+it("preserves images from deeper nested toolResult content", async () => {
370+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
371+372+const result = await runner.applyToolResultMiddleware({
373+toolCallId: "call-1",
374+toolName: "screenshot",
375+args: {},
376+result: {
377+content: [
378+{
379+type: "toolResult",
380+toolUseId: "call-1",
381+content: [
382+{
383+type: "tool_result",
384+content: [
385+{ type: "text", text: "captured screenshot" },
386+{ type: "image", mimeType: "image/png", data: "base64-image" },
387+],
388+},
389+],
390+} as never,
391+],
392+details: {},
393+},
394+});
395+396+expect(result.content).toEqual([
397+{ type: "text", text: "captured screenshot" },
398+{ type: "image", mimeType: "image/png", data: "base64-image" },
399+]);
400+});
401+402+it("preserves interleaved nested text and image order", async () => {
403+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
404+405+const result = await runner.applyToolResultMiddleware({
406+toolCallId: "call-1",
407+toolName: "screenshot",
408+args: {},
409+result: {
410+content: [
411+{
412+type: "toolResult",
413+toolUseId: "call-1",
414+content: [
415+{ type: "text", text: "first caption" },
416+{ type: "image", mimeType: "image/png", data: "image-one" },
417+{ type: "text", text: "second caption" },
418+{ type: "image", mimeType: "image/png", data: "image-two" },
419+],
420+} as never,
421+],
422+details: {},
423+},
424+});
425+426+expect(result.content).toEqual([
427+{ type: "text", text: "first caption" },
428+{ type: "image", mimeType: "image/png", data: "image-one" },
429+{ type: "text", text: "second caption" },
430+{ type: "image", mimeType: "image/png", data: "image-two" },
431+]);
432+});
433+434+it("fails closed instead of recursing forever on cyclic nested content", async () => {
435+const nested: Record<string, unknown> = {
436+type: "toolResult",
437+content: [],
438+};
439+nested.content = [nested];
440+const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
441+442+const result = await runner.applyToolResultMiddleware({
443+toolCallId: "call-1",
444+toolName: "message",
445+args: {},
446+result: {
447+content: [nested as never],
448+details: {},
449+},
450+});
451+452+expect(result.details).toEqual({ status: "error", middlewareError: true });
453+});
454+170455it("sanitizes incoming function/symbol/bigint values in details", async () => {
171456const runner = createAgentToolResultMiddlewareRunner({ runtime: "codex" }, [() => undefined]);
172457此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。