diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 7de6e275fc..fbbc209057 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -42,6 +42,7 @@ import { RetrieveQueueParam, RetrieveRunResponse, RetrieveRunTraceResponseBody, + RunEvent, ScheduleObject, SendInputStreamResponseBody, StreamBatchItemsResponse, @@ -700,7 +701,7 @@ export class ApiClient { listRunEvents(runId: string, requestOptions?: ZodFetchOptions) { return zodfetch( - z.any(), // TODO: define a proper schema for this + RunEvent.array(), `${this.baseUrl}/api/v1/runs/${runId}/events`, { method: "GET", diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index c936b3c769..47b474aeee 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { InitializeDeploymentRequestBody } from "./api.js"; +import { InitializeDeploymentRequestBody, RunEvent } from "./api.js"; import type { InitializeDeploymentRequestBody as InitializeDeploymentRequestBodyType } from "./api.js"; describe("InitializeDeploymentRequestBody", () => { @@ -139,3 +139,60 @@ describe("InitializeDeploymentRequestBody", () => { }); }); }); + +describe("RunEvent Schema", () => { + const validEvent = { + spanId: "span_123", + parentId: "span_root", + runId: "run_abc", + message: "Test event", + style: { + icon: "task", + variant: "primary", + }, + startTime: "2024-03-14T00:00:00Z", + duration: 1234, + isError: false, + isPartial: false, + isCancelled: false, + level: "INFO", + kind: "TASK", + attemptNumber: 1, + }; + + it("parses a valid event correctly", () => { + const result = RunEvent.safeParse(validEvent); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.spanId).toBe("span_123"); + expect(result.data.startTime).toBeInstanceOf(Date); + expect(result.data.level).toBe("INFO"); + } + }); + + it("fails on missing required fields", () => { + const invalidEvent = { ...validEvent }; + delete (invalidEvent as any).spanId; + const result = RunEvent.safeParse(invalidEvent); + expect(result.success).toBe(false); + }); + + it("fails on invalid level", () => { + const invalidEvent = { ...validEvent, level: "INVALID_LEVEL" }; + const result = RunEvent.safeParse(invalidEvent); + expect(result.success).toBe(false); + }); + + it("coerces startTime to Date", () => { + const result = RunEvent.parse(validEvent); + expect(result.startTime).toBeInstanceOf(Date); + expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); + }); + + it("allows optional parentId", () => { + const eventWithoutParent = { ...validEvent }; + delete (eventWithoutParent as any).parentId; + const result = RunEvent.safeParse(eventWithoutParent); + expect(result.success).toBe(true); + }); +}); diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index dc72f155bd..a38b562d4d 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -9,6 +9,8 @@ import { } from "./common.js"; import { BackgroundWorkerMetadata } from "./resources.js"; import { DequeuedMessage, MachineResources } from "./runEngine.js"; +import { TaskEventStyle } from "./style.js"; +import { SpanEvents } from "./openTelemetry.js"; export const RunEngineVersion = z.union([z.literal("V1"), z.literal("V2")]); @@ -1639,3 +1641,24 @@ export const SendInputStreamResponseBody = z.object({ ok: z.boolean(), }); export type SendInputStreamResponseBody = z.infer; +export const TaskEventLevel = z.enum(["TRACE", "DEBUG", "INFO", "LOG", "WARN", "ERROR"]); +export type TaskEventLevel = z.infer; + +export const RunEvent = z.object({ + spanId: z.string(), + parentId: z.string().optional(), + runId: z.string(), + message: z.string(), + style: TaskEventStyle, + startTime: z.coerce.date(), + duration: z.number(), + isError: z.boolean(), + isPartial: z.boolean(), + isCancelled: z.boolean(), + level: TaskEventLevel, + events: SpanEvents.optional(), + kind: z.string(), + attemptNumber: z.number().optional(), +}); + +export type RunEvent = z.infer;