Unified · Split
| @@ -2,7 +2,6 @@ import { Result } from "better-result"; · 1 unmodified line | ||
| 2 | 2 | import { api } from "@executor/database/convex/_generated/api"; |
| 3 | 3 | import { ConvexClient, ConvexHttpClient } from "convex/browser"; |
| 4 | 4 | import type { |
| 5 | − BridgeEntrypointContext, | |
| 6 | 5 | BridgeProps, |
| 7 | 6 | ToolCallResult, |
| 8 | 7 | WorkerEntrypointExports, |
| @@ -10,9 +9,15 @@ import type { · 1 unmodified line | ||
| 10 | 9 | |
| 11 | 10 | const APPROVAL_SUBSCRIPTION_TIMEOUT_MS = 10 * 60 * 1000; |
| 12 | 11 | |
| 12 | +function asObject(value: unknown): Record<string, unknown> { | |
| 13 | + if (value && typeof value === "object" && !Array.isArray(value)) { | |
| 14 | + return value as Record<string, unknown>; | |
| 15 | + } | |
| 16 | + return {}; | |
| 17 | +} | |
| 18 | + | |
| 13 | 19 | function isBridgeProps(value: unknown): value is BridgeProps { |
| 14 | − if (!value || typeof value !== "object") return false; | |
| 15 | − const props = value as Partial<BridgeProps>; | |
| 20 | + const props = asObject(value); | |
| 16 | 21 | return ( |
| 17 | 22 | typeof props.callbackConvexUrl === "string" |
| 18 | 23 | && typeof props.callbackInternalSecret === "string" |
| @@ -20,33 +25,46 @@ function isBridgeProps(value: unknown): value is BridgeProps { · 1 unmodified line | ||
| 20 | 25 | ); |
| 21 | 26 | } |
| 22 | 27 | |
| 28 | +function isToolCallResult(value: unknown): value is ToolCallResult { | |
| 29 | + const record = asObject(value); | |
| 30 | + if (record.ok === true) { | |
| 31 | + return true; | |
| 32 | + } | |
| 33 | + | |
| 34 | + if (record.ok === false) { | |
| 35 | + return record.kind === "pending" || record.kind === "denied" || record.kind === "failed"; | |
| 36 | + } | |
| 37 | + | |
| 38 | + return false; | |
| 39 | +} | |
| 40 | + | |
| 23 | 41 | export function getBridgePropsFromContext(ctx: unknown): BridgeProps { |
| 24 | − if (!ctx || typeof ctx !== "object") { | |
| 42 | + const context = asObject(ctx); | |
| 43 | + if (Object.keys(context).length === 0) { | |
| 25 | 44 | throw new Error("WorkerEntrypoint context is unavailable"); |
| 26 | 45 | } |
| 27 | 46 | |
| 28 | − const maybeContext = ctx as Partial<BridgeEntrypointContext>; | |
| 29 | − if (!isBridgeProps(maybeContext.props)) { | |
| 47 | + if (!isBridgeProps(context.props)) { | |
| 30 | 48 | throw new Error("ToolBridge props are missing or invalid"); |
| 31 | 49 | } |
| 32 | 50 | |
| 33 | − return maybeContext.props; | |
| 51 | + return context.props; | |
| 34 | 52 | } |
| 35 | 53 | |
| 36 | 54 | export function getEntrypointExports(ctx: ExecutionContext): WorkerEntrypointExports { |
| 37 | − const maybeCtx = ctx as ExecutionContext & { exports?: unknown }; | |
| 38 | − const exportsValue = maybeCtx.exports; | |
| 55 | + const context = asObject(ctx); | |
| 56 | + const exportsValue = context.exports; | |
| 39 | 57 | |
| 40 | 58 | if (!exportsValue || typeof exportsValue !== "object") { |
| 41 | 59 | throw new Error("Execution context exports are unavailable"); |
| 42 | 60 | } |
| 43 | 61 | |
| 44 | − const maybeExports = exportsValue as Partial<WorkerEntrypointExports>; | |
| 45 | − if (typeof maybeExports.ToolBridge !== "function") { | |
| 62 | + const exportsObject = asObject(exportsValue); | |
| 63 | + if (typeof exportsObject.ToolBridge !== "function") { | |
| 46 | 64 | throw new Error("Execution context ToolBridge export is unavailable"); |
| 47 | 65 | } |
| 48 | 66 | |
| 49 | − return { ToolBridge: maybeExports.ToolBridge }; | |
| 67 | + return { ToolBridge: exportsObject.ToolBridge as WorkerEntrypointExports["ToolBridge"] }; | |
| 50 | 68 | } |
| 51 | 69 | |
| 52 | 70 | function createConvexClient(callbackConvexUrl: string): ConvexHttpClient { |
| @@ -136,7 +154,11 @@ export async function callToolWithBridge( · 83 unmodified lines | ||
| 136 | 154 | return { ok: false, kind: "failed", error: `Tool callback failed: ${message}` }; |
| 137 | 155 | } |
| 138 | 156 | |
| 139 | − const result = response.value as ToolCallResult; | |
| 157 | + const result = response.value; | |
| 158 | + if (!isToolCallResult(result)) { | |
| 159 | + return { ok: false, kind: "failed", error: "Tool callback returned invalid result payload" }; | |
| 160 | + } | |
| 161 | + | |
| 140 | 162 | if (!result.ok && result.kind === "pending") { |
| 141 | 163 | if (!result.approvalId) { |
| 142 | 164 | return { ok: false, kind: "failed", error: "Approval pending without approvalId" }; |