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 @@
1+import { z } from "zod";
12 import type { ToolCallResult } from "./types";
23
34 const UNDEFINED_SENTINEL = "__executor_tool_result_undefined__";
@@ -14,6 +15,30 @@ type ToolCallTransportResult = · 10 unmodified lines
1415 | { ok: false; kind: "denied"; error: string }
1516 | { ok: false; kind: "failed"; error: string };
1617
18+const 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+
1742 function encodeValue(value: unknown): string {
1843 if (value === undefined) {
1944 return UNDEFINED_SENTINEL;
@@ -51,7 +76,7 @@ export function encodeToolCallResultForTransport(result: ToolCallResult): string · 31 unmodified lines
5176 }
5277
5378 export function decodeToolCallResultFromTransport(value: unknown): ToolCallResult | null {
54− const parsed: unknown = typeof value === "string"
79+ const parsedJson: unknown = typeof value === "string"
5580 ? (() => {
5681 try {
5782 return JSON.parse(value) as unknown;
@@ -61,32 +86,30 @@ export function decodeToolCallResultFromTransport(value: unknown): ToolCallResul · 3 unmodified lines
6186 })()
6287 : value;
6388
64− if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
89+ const parsed = toolCallTransportResultSchema.safeParse(parsedJson);
90+ if (!parsed.success) {
6591 return null;
6692 }
6793
68− const record = parsed as Record<string, unknown>;
69− if (record.ok === true) {
70− if (typeof record.valueJson !== "string") {
71− return null;
72− }
94+ const record = parsed.data;
95+ if (record.ok) {
7396 return {
7497 ok: true,
7598 value: decodeValue(record.valueJson),
7699 };
77100 }
78101
79− if (record.ok === false && record.kind === "pending" && typeof record.approvalId === "string") {
102+ if (record.kind === "pending") {
80103 return {
81104 ok: false,
82105 kind: "pending",
83106 approvalId: record.approvalId,
84− ...(typeof record.retryAfterMs === "number" ? { retryAfterMs: record.retryAfterMs } : {}),
85− ...(typeof record.error === "string" ? { error: record.error } : {}),
107+ ...(record.retryAfterMs !== undefined ? { retryAfterMs: record.retryAfterMs } : {}),
108+ ...(record.error !== undefined ? { error: record.error } : {}),
86109 };
87110 }
88111
89− if (record.ok === false && record.kind === "denied" && typeof record.error === "string") {
112+ if (record.kind === "denied") {
90113 return {
91114 ok: false,
92115 kind: "denied",
@@ -94,7 +117,7 @@ export function decodeToolCallResultFromTransport(value: unknown): ToolCallResul · 1 unmodified line
94117 };
95118 }
96119
97− if (record.ok === false && record.kind === "failed" && typeof record.error === "string") {
120+ if (record.kind === "failed") {
98121 return {
99122 ok: false,
100123 kind: "failed",