The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
| 4 |
| 5 | /** |
| 6 | * Wire payloads for the execution kinds added after Phase 6, carried in |
| 7 | * the protocol-2 envelope. Each one is dispatched by the platform to the |
| 8 | * app's logic — the hosted Dynamic Worker or the external backend's |
| 9 | * `/coline/app` handler — with a per-run invocation token. |
| 10 | */ |
| 11 |
| 12 | const actorSchema = z.discriminatedUnion("kind", [ |
| 13 | z.strictObject({ kind: z.literal("user"), userId: z.string().min(1).max(200) }), |
| 14 | z.strictObject({ kind: z.literal("app") }), |
| 15 | ]); |
| 16 |
| 17 | export const TRIGGER_EVENT_KIND = "trigger.event" as const; |
| 18 |
| 19 | export const triggerEventPayloadSchema = z.strictObject({ |
| 20 | event: z.strictObject({ |
| 21 | id: z.string().min(1).max(200), |
| 22 | type: z.string().min(1).max(160), |
| 23 | occurredAt: z.iso.datetime(), |
| 24 | actor: actorSchema.nullable(), |
| 25 | source: z.discriminatedUnion("kind", [ |
| 26 | z.strictObject({ kind: z.literal("platform") }), |
| 27 | z.strictObject({ kind: z.literal("app"), appKey: z.string().min(1).max(64) }), |
| 28 | ]), |
| 29 | data: z.record(z.string(), z.unknown()), |
| 30 | }), |
| 31 | attempt: z.number().int().min(1), |
| 32 | capabilities: toolInvokeCapabilitiesSchema, |
| 33 | }); |
| 34 | export type TriggerEventPayload = z.infer<typeof triggerEventPayloadSchema>; |
| 35 |
| 36 | export const ENDPOINT_REQUEST_KIND = "endpoint.request" as const; |
| 37 |
| 38 | export const endpointRequestPayloadSchema = z.strictObject({ |
| 39 | request: z.strictObject({ |
| 40 | endpointId: z.string().min(1).max(80), |
| 41 | method: z.string().min(1).max(10), |
| 42 | path: z.string().min(1).max(2000), |
| 43 | query: z.record(z.string(), z.string()), |
| 44 | headers: z.record(z.string(), z.string()), |
| 45 | body: z.string(), |
| 46 | bodyBase64: z.string().nullable(), |
| 47 | receivedAt: z.iso.datetime(), |
| 48 | verifiedBy: z.string().min(1).max(40), |
| 49 | }), |
| 50 | capabilities: toolInvokeCapabilitiesSchema, |
| 51 | }); |
| 52 | export type EndpointRequestPayload = z.infer<typeof endpointRequestPayloadSchema>; |
| 53 |
| 54 | export const endpointResponseSchema = z.strictObject({ |
| 55 | status: z.number().int().min(100).max(599).default(200), |
| 56 | headers: z.record(z.string().max(120), z.string().max(4000)).default({}), |
| 57 | body: z.unknown().optional(), |
| 58 | }); |
| 59 | export type EndpointResponse = z.infer<typeof endpointResponseSchema>; |
| 60 |
| 61 | export const JOB_RUN_KIND = "job.run" as const; |
| 62 |
| 63 | export const jobRunPayloadSchema = z.strictObject({ |
| 64 | job: z.strictObject({ |
| 65 | id: z.string().min(1).max(200), |
| 66 | name: z.string().min(1).max(80), |
| 67 | attempt: z.number().int().min(1), |
| 68 | maxAttempts: z.number().int().min(1), |
| 69 | enqueuedAt: z.iso.datetime(), |
| 70 | checkpoint: z.unknown(), |
| 71 | payload: z.record(z.string(), z.unknown()), |
| 72 | }), |
| 73 | actor: actorSchema, |
| 74 | capabilities: toolInvokeCapabilitiesSchema, |
| 75 | }); |
| 76 | export type JobRunPayload = z.infer<typeof jobRunPayloadSchema>; |
| 77 |
| 78 | export const jobRunResultSchema = z.union([ |
| 79 | z.strictObject({ kind: z.literal("done"), result: z.unknown().optional() }), |
| 80 | z.strictObject({ |
| 81 | kind: z.literal("continue"), |
| 82 | payload: z.record(z.string(), z.unknown()), |
| 83 | delaySeconds: z.number().int().min(0).max(86_400).optional(), |
| 84 | }), |
| 85 | ]); |
| 86 | export type JobRunResult = z.infer<typeof jobRunResultSchema>; |
| 87 |
| 88 | export const RENDER_PUBLIC_KIND = "render.public" as const; |
| 89 |
| 90 | export const renderPublicPayloadSchema = z.strictObject({ |
| 91 | path: z.string().min(1).max(2000), |
| 92 | query: z.record(z.string(), z.string()), |
| 93 | visitor: z.strictObject({ id: z.string().min(1).max(200) }), |
| 94 | capabilities: toolInvokeCapabilitiesSchema, |
| 95 | }); |
| 96 | export type RenderPublicPayload = z.infer<typeof renderPublicPayloadSchema>; |
| 97 |
| 98 | export const renderPublicResultSchema = z.strictObject({ tree: colineUiNodeSchema }); |
| 99 |
| 100 | /** Convert a job's return value into the wire result. */ |
| 101 | export function toJobRunResult(outcome: unknown): JobRunResult { |
| 102 | if (outcome && typeof outcome === "object" && "continueWith" in outcome) { |
| 103 | const value = outcome as { continueWith: Record<string, unknown>; delaySeconds?: number }; |
| 104 | return { |
| 105 | kind: "continue", |
| 106 | payload: value.continueWith, |
| 107 | ...(value.delaySeconds !== undefined ? { delaySeconds: value.delaySeconds } : {}), |
| 108 | }; |
| 109 | } |
| 110 | if (outcome && typeof outcome === "object" && "result" in outcome) { |
| 111 | return { kind: "done", result: (outcome as { result?: unknown }).result }; |
| 112 | } |
| 113 | return { kind: "done" }; |
| 114 | } |
| 115 |
| 116 | /** Normalize a handler's endpoint response for the wire. */ |
| 117 | export function toEndpointResponse(value: unknown): EndpointResponse { |
| 118 | if (!value || typeof value !== "object") return { status: 200, headers: {} }; |
| 119 | const parsed = endpointResponseSchema.safeParse(value); |
| 120 | if (!parsed.success) { |
| 121 | throw Object.assign(new Error("Endpoint handler returned an invalid response."), { |
| 122 | code: "invalid_response", |
| 123 | }); |
| 124 | } |
| 125 | return parsed.data; |
| 126 | } |
| 127 |