| @@ -12,6 +12,7 @@ import { · 11 unmodified lines |
| 12 | createRunExecutor, | 12 | createRunExecutor, |
| 13 | createSourceToolRegistry, | 13 | createSourceToolRegistry, |
| 14 | defaultExecuteToolExposureMode, | 14 | defaultExecuteToolExposureMode, |
| | 15 | invokeRuntimeToolCallResult, |
| 15 | makeGraphqlToolProvider, | 16 | makeGraphqlToolProvider, |
| 16 | makeMcpToolProvider, | 17 | makeMcpToolProvider, |
| 17 | makeOpenApiToolProvider, | 18 | makeOpenApiToolProvider, |
| @@ -106,6 +107,7 @@ type ControlPlaneRuntime = { · 88 unmodified lines |
| 106 | fetchOpenApiDocument: typeof fetchOpenApiDocument; | 107 | fetchOpenApiDocument: typeof fetchOpenApiDocument; |
| 107 | handleControlPlane: (request: Request) => Promise<Response>; | 108 | handleControlPlane: (request: Request) => Promise<Response>; |
| 108 | handleMcp: (request: Request, workspaceId: string) => Promise<Response>; | 109 | handleMcp: (request: Request, workspaceId: string) => Promise<Response>; |
| | 110 | handleRuntimeToolCall: (request: Request) => Promise<Response>; |
| 109 | dispose: () => Promise<void>; | 111 | dispose: () => Promise<void>; |
| 110 | }; | 112 | }; |
| 111 | | 113 | |
| @@ -119,6 +121,36 @@ type ControlPlanePrincipal = { · 7 unmodified lines |
| 119 | workspaceId: string; | 121 | workspaceId: string; |
| 120 | }; | 122 | }; |
| 121 | | 123 | |
| | 124 | type RuntimeToolCallRequest = { |
| | 125 | runId: string; |
| | 126 | callId: string; |
| | 127 | toolPath: string; |
| | 128 | input?: Record<string, unknown>; |
| | 129 | }; |
| | 130 | |
| | 131 | type RuntimeToolCallResult = |
| | 132 | | { |
| | 133 | ok: true; |
| | 134 | value: unknown; |
| | 135 | } |
| | 136 | | { |
| | 137 | ok: false; |
| | 138 | kind: "pending"; |
| | 139 | approvalId: string; |
| | 140 | retryAfterMs: number; |
| | 141 | error?: string; |
| | 142 | } |
| | 143 | | { |
| | 144 | ok: false; |
| | 145 | kind: "denied"; |
| | 146 | error: string; |
| | 147 | } |
| | 148 | | { |
| | 149 | ok: false; |
| | 150 | kind: "failed"; |
| | 151 | error: string; |
| | 152 | }; |
| | 153 | |
| 122 | const normalizeIdPart = (value: string): string => | 154 | const normalizeIdPart = (value: string): string => |
| 123 | value.replace(/[^a-zA-Z0-9_-]/g, "_"); | 155 | value.replace(/[^a-zA-Z0-9_-]/g, "_"); |
| 124 | | 156 | |
| @@ -144,6 +176,7 @@ const resolveStateRootDir = (): string => · 19 unmodified lines |
| 144 | defaultControlPlaneStateRootDir; | 176 | defaultControlPlaneStateRootDir; |
| 145 | | 177 | |
| 146 | const openApiSyncRetryDelayMs = 300; | 178 | const openApiSyncRetryDelayMs = 300; |
| | 179 | const runtimeToolCallRetentionMs = 15 * 60 * 1000; |
| 147 | | 180 | |
| 148 | const formatCause = (cause: unknown): string => { | 181 | const formatCause = (cause: unknown): string => { |
| 149 | if (cause && typeof cause === "object") { | 182 | if (cause && typeof cause === "object") { |
| @@ -181,6 +214,54 @@ const formatCause = (cause: unknown): string => { · 31 unmodified lines |
| 181 | } | 214 | } |
| 182 | }; | 215 | }; |
| 183 | | 216 | |
| | 217 | const toFailedRuntimeToolCallResult = (error: string): RuntimeToolCallResult => ({ |
| | 218 | ok: false, |
| | 219 | kind: "failed", |
| | 220 | error, |
| | 221 | }); |
| | 222 | |
| | 223 | const runtimeToolCallErrorResponse = (status: number, error: string): Response => |
| | 224 | Response.json(toFailedRuntimeToolCallResult(error), { status }); |
| | 225 | |
| | 226 | const normalizeRuntimeToolCallInput = ( |
| | 227 | value: unknown, |
| | 228 | ): Record<string, unknown> | undefined => { |
| | 229 | if (value === undefined) { |
| | 230 | return undefined; |
| | 231 | } |
| | 232 | |
| | 233 | if (value && typeof value === "object" && !Array.isArray(value)) { |
| | 234 | return value as Record<string, unknown>; |
| | 235 | } |
| | 236 | |
| | 237 | return {}; |
| | 238 | }; |
| | 239 | |
| | 240 | const parseRuntimeToolCallRequest = async ( |
| | 241 | request: Request, |
| | 242 | ): Promise<RuntimeToolCallRequest | null> => { |
| | 243 | const payload = await request.json().catch(() => null); |
| | 244 | if (!payload || typeof payload !== "object" || Array.isArray(payload)) { |
| | 245 | return null; |
| | 246 | } |
| | 247 | |
| | 248 | const record = payload as Record<string, unknown>; |
| | 249 | if ( |
| | 250 | typeof record.runId !== "string" |
| | 251 | || typeof record.callId !== "string" |
| | 252 | || typeof record.toolPath !== "string" |
| | 253 | ) { |
| | 254 | return null; |
| | 255 | } |
| | 256 | |
| | 257 | return { |
| | 258 | runId: record.runId, |
| | 259 | callId: record.callId, |
| | 260 | toolPath: record.toolPath, |
| | 261 | input: normalizeRuntimeToolCallInput(record.input), |
| | 262 | }; |
| | 263 | }; |
| | 264 | |
| 184 | const ensurePrincipalProvisioned = ( | 265 | const ensurePrincipalProvisioned = ( |
| 185 | persistence: SqlControlPlanePersistence, | 266 | persistence: SqlControlPlanePersistence, |
| 186 | principal: ControlPlanePrincipal, | 267 | principal: ControlPlanePrincipal, |
| @@ -297,10 +378,12 @@ const createControlPlaneRuntime = async (): Promise<ControlPlaneRuntime> => { · 110 unmodified lines |
| 297 | requireApprovals: requireToolApprovals, | 378 | requireApprovals: requireToolApprovals, |
| 298 | }, | 379 | }, |
| 299 | ); | 380 | ); |
| 300 | const mcpHandlers = new Map< | 381 | const mcpSessions = new Map<string, { |
| 301 | string, | 382 | handler: (request: Request) => Promise<Response>; |
| 302 | (request: Request) => Promise<Response> | 383 | toolRegistry: ReturnType<typeof createSourceToolRegistry>; |
| 303 | >(); | 384 | }>(); |
| | 385 | const workspaceByRunId = new Map<string, string>(); |
| | 386 | const runCleanupTimers = new Map<string, ReturnType<typeof setTimeout>>(); |
| 304 | | 387 | |
| 305 | const sourceCatalog = makeSourceCatalogService(sourceStore); | 388 | const sourceCatalog = makeSourceCatalogService(sourceStore); |
| 306 | const sourceManager = makeSourceManagerService(toolArtifactStore); | 389 | const sourceManager = makeSourceManagerService(toolArtifactStore); |
| @@ -424,8 +507,27 @@ const createControlPlaneRuntime = async (): Promise<ControlPlaneRuntime> => { · 117 unmodified lines |
| 424 | PmActorLive(persistence.rows), | 507 | PmActorLive(persistence.rows), |
| 425 | ); | 508 | ); |
| 426 | | 509 | |
| 427 | const resolveMcpHandler = (workspaceId: string): ((request: Request) => Promise<Response>) => { | 510 | const rememberRunWorkspace = (runId: string, workspaceId: string): void => { |
| 428 | const existing = mcpHandlers.get(workspaceId); | 511 | workspaceByRunId.set(runId, workspaceId); |
| | 512 | |
| | 513 | const existingTimer = runCleanupTimers.get(runId); |
| | 514 | if (existingTimer) { |
| | 515 | clearTimeout(existingTimer); |
| | 516 | } |
| | 517 | |
| | 518 | const timer = setTimeout(() => { |
| | 519 | workspaceByRunId.delete(runId); |
| | 520 | runCleanupTimers.delete(runId); |
| | 521 | }, runtimeToolCallRetentionMs); |
| | 522 | |
| | 523 | runCleanupTimers.set(runId, timer); |
| | 524 | }; |
| | 525 | |
| | 526 | const resolveMcpSession = (workspaceId: string): { |
| | 527 | handler: (request: Request) => Promise<Response>; |
| | 528 | toolRegistry: ReturnType<typeof createSourceToolRegistry>; |
| | 529 | } => { |
| | 530 | const existing = mcpSessions.get(workspaceId); |
| 429 | if (existing) { | 531 | if (existing) { |
| 430 | return existing; | 532 | return existing; |
| 431 | } | 533 | } |
| @@ -442,14 +544,25 @@ const createControlPlaneRuntime = async (): Promise<ControlPlaneRuntime> => { · 10 unmodified lines |
| 442 | runtimeAdapters, | 544 | runtimeAdapters, |
| 443 | toolRegistry, | 545 | toolRegistry, |
| 444 | }); | 546 | }); |
| 445 | const runExecutor = createRunExecutor(executeRuntimeRun); | 547 | const runExecutor = createRunExecutor(executeRuntimeRun, { |
| | 548 | makeRunId: () => { |
| | 549 | const runId = `run_${crypto.randomUUID()}`; |
| | 550 | rememberRunWorkspace(runId, workspaceId); |
| | 551 | return runId; |
| | 552 | }, |
| | 553 | }); |
| 446 | const next = createPmMcpHandler(runExecutor.executeRun, { | 554 | const next = createPmMcpHandler(runExecutor.executeRun, { |
| 447 | toolRegistry, | 555 | toolRegistry, |
| 448 | defaultToolExposureMode, | 556 | defaultToolExposureMode, |
| 449 | }); | 557 | }); |
| 450 | | 558 | |
| 451 | mcpHandlers.set(workspaceId, next); | 559 | const session = { |
| 452 | return next; | 560 | handler: next, |
| | 561 | toolRegistry, |
| | 562 | }; |
| | 563 | |
| | 564 | mcpSessions.set(workspaceId, session); |
| | 565 | return session; |
| 453 | }; | 566 | }; |
| 454 | | 567 | |
| 455 | return { | 568 | return { |
| @@ -459,10 +572,67 @@ const createControlPlaneRuntime = async (): Promise<ControlPlaneRuntime> => { · 3 unmodified lines |
| 459 | fetchOpenApiDocument, | 572 | fetchOpenApiDocument, |
| 460 | handleControlPlane: controlPlaneWebHandler.handler, | 573 | handleControlPlane: controlPlaneWebHandler.handler, |
| 461 | handleMcp: async (request, workspaceId) => { | 574 | handleMcp: async (request, workspaceId) => { |
| 462 | const handler = resolveMcpHandler(workspaceId); | 575 | const session = resolveMcpSession(workspaceId); |
| 463 | return handler(request); | 576 | return session.handler(request); |
| | 577 | }, |
| | 578 | handleRuntimeToolCall: async (request) => { |
| | 579 | if (request.method.toUpperCase() !== "POST") { |
| | 580 | return runtimeToolCallErrorResponse(405, "Method not allowed. Expected POST"); |
| | 581 | } |
| | 582 | |
| | 583 | const expectedSecret = webServerEnvironment.cloudflareSandboxCallbackSecret; |
| | 584 | if (!expectedSecret) { |
| | 585 | return runtimeToolCallErrorResponse(503, "Runtime callback secret is not configured"); |
| | 586 | } |
| | 587 | |
| | 588 | const providedSecret = request.headers.get("x-internal-secret")?.trim(); |
| | 589 | if (!providedSecret) { |
| | 590 | return runtimeToolCallErrorResponse(401, "Runtime callback authentication is required"); |
| | 591 | } |
| | 592 | |
| | 593 | if (providedSecret !== expectedSecret) { |
| | 594 | return runtimeToolCallErrorResponse(403, "Runtime callback authentication failed"); |
| | 595 | } |
| | 596 | |
| | 597 | const input = await parseRuntimeToolCallRequest(request); |
| | 598 | if (!input) { |
| | 599 | return runtimeToolCallErrorResponse(400, "Runtime callback request body is invalid"); |
| | 600 | } |
| | 601 | |
| | 602 | const workspaceId = workspaceByRunId.get(input.runId); |
| | 603 | if (!workspaceId) { |
| | 604 | return runtimeToolCallErrorResponse(404, `Unknown runtime callback run id: ${input.runId}`); |
| | 605 | } |
| | 606 | |
| | 607 | const session = resolveMcpSession(workspaceId); |
| | 608 | |
| | 609 | const result = await Effect.runPromise( |
| | 610 | invokeRuntimeToolCallResult(session.toolRegistry, input).pipe( |
| | 611 | Effect.catchTag("RuntimeAdapterError", (error) => |
| | 612 | Effect.succeed<RuntimeToolCallResult>( |
| | 613 | toFailedRuntimeToolCallResult( |
| | 614 | error.details ? `${error.message}: ${error.details}` : error.message, |
| | 615 | ), |
| | 616 | ) |
| | 617 | ), |
| | 618 | Effect.catchAll((cause) => |
| | 619 | Effect.succeed<RuntimeToolCallResult>( |
| | 620 | toFailedRuntimeToolCallResult(formatCause(cause)), |
| | 621 | ) |
| | 622 | ), |
| | 623 | ), |
| | 624 | ); |
| | 625 | |
| | 626 | return Response.json(result, { status: 200 }); |
| 464 | }, | 627 | }, |
| 465 | dispose: async () => { | 628 | dispose: async () => { |
| | 629 | for (const timer of runCleanupTimers.values()) { |
| | 630 | clearTimeout(timer); |
| | 631 | } |
| | 632 | runCleanupTimers.clear(); |
| | 633 | workspaceByRunId.clear(); |
| | 634 | mcpSessions.clear(); |
| | 635 | |
| 466 | await controlPlaneWebHandler.dispose(); | 636 | await controlPlaneWebHandler.dispose(); |
| 467 | await persistence.close(); | 637 | await persistence.close(); |
| 468 | }, | 638 | }, |