History

Parse tool call transport payloads with zod

Rhys Sullivan committed 8 months ago

Commit a2ae3f51 parent a4565fb1 file · +35 −12Browse files

Unified · Split

executor/packages/core/src/tool-call-result-transport.ts+35−12
@@ -1,3 +1,4 @@
1import { z } from "zod";
1import type { ToolCallResult } from "./types";2import type { ToolCallResult } from "./types";
2 3
3const UNDEFINED_SENTINEL = "__executor_tool_result_undefined__";4const UNDEFINED_SENTINEL = "__executor_tool_result_undefined__";
@@ -14,6 +15,30 @@ type ToolCallTransportResult = · 10 unmodified lines
14 | { ok: false; kind: "denied"; error: string }15 | { ok: false; kind: "denied"; error: string }
15 | { ok: false; kind: "failed"; error: string };16 | { ok: false; kind: "failed"; error: string };
16 17
18const toolCallTransportResultSchema = z.union([
19 z.object({
20 ok: z.literal(true),
21 valueJson: z.string(),
22 }),
23 z.object({
24 ok: z.literal(false),
25 kind: z.literal("pending"),
26 approvalId: z.string(),
27 retryAfterMs: z.number().optional(),
28 error: z.string().optional(),
29 }),
30 z.object({
31 ok: z.literal(false),
32 kind: z.literal("denied"),
33 error: z.string(),
34 }),
35 z.object({
36 ok: z.literal(false),
37 kind: z.literal("failed"),
38 error: z.string(),
39 }),
40]);
41
17function encodeValue(value: unknown): string {42function encodeValue(value: unknown): string {
18 if (value === undefined) {43 if (value === undefined) {
19 return UNDEFINED_SENTINEL;44 return UNDEFINED_SENTINEL;
@@ -51,7 +76,7 @@ export function encodeToolCallResultForTransport(result: ToolCallResult): string · 31 unmodified lines
51}76}
52 77
53export function decodeToolCallResultFromTransport(value: unknown): ToolCallResult | null {78export function decodeToolCallResultFromTransport(value: unknown): ToolCallResult | null {
54 const parsed: unknown = typeof value === "string"79 const parsedJson: unknown = typeof value === "string"
55 ? (() => {80 ? (() => {
56 try {81 try {
57 return JSON.parse(value) as unknown;82 return JSON.parse(value) as unknown;
@@ -61,32 +86,30 @@ export function decodeToolCallResultFromTransport(value: unknown): ToolCallResul · 3 unmodified lines
61 })()86 })()
62 : value;87 : value;
63 88
64 if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {89 const parsed = toolCallTransportResultSchema.safeParse(parsedJson);
90 if (!parsed.success) {
65 return null;91 return null;
66 }92 }
67 93
68 const record = parsed as Record<string, unknown>;94 const record = parsed.data;
69 if (record.ok === true) {95 if (record.ok) {
70 if (typeof record.valueJson !== "string") {
71 return null;
72 }
73 return {96 return {
74 ok: true,97 ok: true,
75 value: decodeValue(record.valueJson),98 value: decodeValue(record.valueJson),
76 };99 };
77 }100 }
78 101
79 if (record.ok === false && record.kind === "pending" && typeof record.approvalId === "string") {102 if (record.kind === "pending") {
80 return {103 return {
81 ok: false,104 ok: false,
82 kind: "pending",105 kind: "pending",
83 approvalId: record.approvalId,106 approvalId: record.approvalId,
84 ...(typeof record.retryAfterMs === "number" ? { retryAfterMs: record.retryAfterMs } : {}),107 ...(record.retryAfterMs !== undefined ? { retryAfterMs: record.retryAfterMs } : {}),
85 ...(typeof record.error === "string" ? { error: record.error } : {}),108 ...(record.error !== undefined ? { error: record.error } : {}),
86 };109 };
87 }110 }
88 111
89 if (record.ok === false && record.kind === "denied" && typeof record.error === "string") {112 if (record.kind === "denied") {
90 return {113 return {
91 ok: false,114 ok: false,
92 kind: "denied",115 kind: "denied",
@@ -94,7 +117,7 @@ export function decodeToolCallResultFromTransport(value: unknown): ToolCallResul · 1 unmodified line
94 };117 };
95 }118 }
96 119
97 if (record.ok === false && record.kind === "failed" && typeof record.error === "string") {120 if (record.kind === "failed") {
98 return {121 return {
99 ok: false,122 ok: false,
100 kind: "failed",123 kind: "failed",