Unified · Split
| @@ -1,7 +1,5 @@ | |||
| 1 | import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; | 1 | import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; |
| 2 | import { createHash, randomUUID } from "node:crypto"; | 2 | import { randomUUID } from "node:crypto"; |
| 3 | import { mkdir, writeFile } from "node:fs/promises"; | ||
| 4 | import { join } from "node:path"; | ||
| 5 | 3 | ||
| 6 | import { | 4 | import { |
| 7 | HttpApi, | 5 | HttpApi, |
| @@ -24,7 +22,6 @@ import { · 16 unmodified lines | |||
| 24 | type ControlPlaneClient, | 22 | type ControlPlaneClient, |
| 25 | type ResolveExecutionEnvironment, | 23 | type ResolveExecutionEnvironment, |
| 26 | SourceIdSchema, | 24 | SourceIdSchema, |
| 27 | SourceRecipeIdSchema, | ||
| 28 | SourceRecipeRevisionIdSchema, | 25 | SourceRecipeRevisionIdSchema, |
| 29 | } from "@executor/control-plane"; | 26 | } from "@executor/control-plane"; |
| 30 | import { makeSesExecutor } from "@executor/runtime-ses"; | 27 | import { makeSesExecutor } from "@executor/runtime-ses"; |
| @@ -41,6 +38,15 @@ import { · 10 unmodified lines | |||
| 41 | seedGithubOpenApiSourceInWorkspace, | 38 | seedGithubOpenApiSourceInWorkspace, |
| 42 | } from "../cli/dev"; | 39 | } from "../cli/dev"; |
| 43 | import { createLocalExecutorServer } from "@executor/server"; | 40 | import { createLocalExecutorServer } from "@executor/server"; |
| 41 | import { | ||
| 42 | buildLocalSourceArtifact, | ||
| 43 | writeLocalSourceArtifact, | ||
| 44 | } from "../../../../packages/control-plane/src/runtime/local-source-artifacts"; | ||
| 45 | import { | ||
| 46 | resolveLocalWorkspaceContext, | ||
| 47 | writeProjectLocalExecutorConfig, | ||
| 48 | } from "../../../../packages/control-plane/src/runtime/local-config"; | ||
| 49 | import { materializationFromMcpManifestEntries } from "../../../../packages/control-plane/src/runtime/source-adapters/mcp"; | ||
| 44 | 50 | ||
| 45 | const executionResolver: ResolveExecutionEnvironment = () => | 51 | const executionResolver: ResolveExecutionEnvironment = () => |
| 46 | Effect.succeed({ | 52 | Effect.succeed({ |
| @@ -82,17 +88,6 @@ const createIsolatedLocalExecutorServer = ( · 35 unmodified lines | |||
| 82 | }; | 88 | }; |
| 83 | }); | 89 | }); |
| 84 | 90 | ||
| 85 | const stableLocalSourceId = (input: { | ||
| 86 | workspaceRoot: string; | ||
| 87 | configKey: string; | ||
| 88 | }) => | ||
| 89 | SourceIdSchema.make( | ||
| 90 | `src_local_${createHash("sha256") | ||
| 91 | .update(`${input.workspaceRoot}:${input.configKey}`) | ||
| 92 | .digest("hex") | ||
| 93 | .slice(0, 16)}`, | ||
| 94 | ); | ||
| 95 | |||
| 96 | const writeConfiguredLocalMcpSource = (input: { | 91 | const writeConfiguredLocalMcpSource = (input: { |
| 97 | workspaceRoot: string; | 92 | workspaceRoot: string; |
| 98 | configKey: string; | 93 | configKey: string; |
| @@ -101,16 +96,58 @@ const writeConfiguredLocalMcpSource = (input: { · 2 unmodified lines | |||
| 101 | namespace?: string; | 96 | namespace?: string; |
| 102 | }) => | 97 | }) => |
| 103 | Effect.promise(async () => { | 98 | Effect.promise(async () => { |
| 104 | const sourceId = stableLocalSourceId({ | 99 | const sourceId = SourceIdSchema.make(input.configKey); |
| 100 | const context = await resolveLocalWorkspaceContext({ | ||
| 105 | workspaceRoot: input.workspaceRoot, | 101 | workspaceRoot: input.workspaceRoot, |
| 106 | configKey: input.configKey, | ||
| 107 | }); | 102 | }); |
| 108 | const recipeId = SourceRecipeIdSchema.make(`src_recipe_${sourceId}`); | 103 | |
| 109 | const recipeRevisionId = SourceRecipeRevisionIdSchema.make(`src_recipe_rev_${sourceId}`); | 104 | await writeProjectLocalExecutorConfig({ |
| 110 | const now = Date.now(); | 105 | context, |
| 111 | const manifest = { | 106 | config: { |
| 112 | version: 1 as const, | 107 | sources: { |
| 113 | tools: [{ | 108 | [input.configKey]: { |
| 109 | kind: "mcp", | ||
| 110 | name: input.name ?? "Demo", | ||
| 111 | namespace: input.namespace ?? input.configKey, | ||
| 112 | connection: { | ||
| 113 | endpoint: input.endpoint, | ||
| 114 | }, | ||
| 115 | binding: { | ||
| 116 | transport: "streamable-http", | ||
| 117 | }, | ||
| 118 | }, | ||
| 119 | }, | ||
| 120 | }, | ||
| 121 | }); | ||
| 122 | |||
| 123 | const source = { | ||
| 124 | id: sourceId, | ||
| 125 | workspaceId: "ws_local_server_test" as never, | ||
| 126 | name: input.name ?? "Demo", | ||
| 127 | kind: "mcp" as const, | ||
| 128 | endpoint: input.endpoint, | ||
| 129 | status: "connected" as const, | ||
| 130 | enabled: true, | ||
| 131 | namespace: input.namespace ?? input.configKey, | ||
| 132 | bindingVersion: 1, | ||
| 133 | binding: { | ||
| 134 | transport: "streamable-http", | ||
| 135 | queryParams: null, | ||
| 136 | headers: null, | ||
| 137 | }, | ||
| 138 | importAuthPolicy: "reuse_runtime" as const, | ||
| 139 | importAuth: { kind: "none" as const }, | ||
| 140 | auth: { kind: "none" as const }, | ||
| 141 | sourceHash: null, | ||
| 142 | lastError: null, | ||
| 143 | createdAt: Date.now(), | ||
| 144 | updatedAt: Date.now(), | ||
| 145 | }; | ||
| 146 | |||
| 147 | const materialization = materializationFromMcpManifestEntries({ | ||
| 148 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"), | ||
| 149 | endpoint: input.endpoint, | ||
| 150 | manifestEntries: [{ | ||
| 114 | toolId: "gated_echo", | 151 | toolId: "gated_echo", |
| 115 | toolName: "gated_echo", | 152 | toolName: "gated_echo", |
| 116 | description: "Asks for approval before echoing a value", | 153 | description: "Asks for approval before echoing a value", |
| @@ -125,93 +162,16 @@ const writeConfiguredLocalMcpSource = (input: { · 8 unmodified lines | |||
| 125 | additionalProperties: false, | 162 | additionalProperties: false, |
| 126 | }), | 163 | }), |
| 127 | }], | 164 | }], |
| 128 | }; | 165 | }); |
| 129 | const manifestJson = JSON.stringify(manifest); | ||
| 130 | 166 | ||
| 131 | await mkdir(join(input.workspaceRoot, ".executor", "artifacts", "sources"), { | 167 | await writeLocalSourceArtifact({ |
| 132 | recursive: true, | 168 | context, |
| 169 | sourceId, | ||
| 170 | artifact: buildLocalSourceArtifact({ | ||
| 171 | source, | ||
| 172 | materialization, | ||
| 173 | }), | ||
| 133 | }); | 174 | }); |
| 134 | await writeFile( | ||
| 135 | join(input.workspaceRoot, ".executor", "executor.jsonc"), | ||
| 136 | `${JSON.stringify({ | ||
| 137 | sources: { | ||
| 138 | [input.configKey]: { | ||
| 139 | kind: "mcp", | ||
| 140 | name: input.name ?? "Demo", | ||
| 141 | namespace: input.namespace ?? input.configKey, | ||
| 142 | connection: { | ||
| 143 | endpoint: input.endpoint, | ||
| 144 | }, | ||
| 145 | binding: { | ||
| 146 | transport: "streamable-http", | ||
| 147 | }, | ||
| 148 | }, | ||
| 149 | }, | ||
| 150 | }, null, 2)}\n`, | ||
| 151 | "utf8", | ||
| 152 | ); | ||
| 153 | await writeFile( | ||
| 154 | join(input.workspaceRoot, ".executor", "artifacts", "sources", `${input.configKey}.json`), | ||
| 155 | `${JSON.stringify({ | ||
| 156 | version: 1, | ||
| 157 | sourceId, | ||
| 158 | configKey: input.configKey, | ||
| 159 | recipeId, | ||
| 160 | generatedAt: now, | ||
| 161 | revision: { | ||
| 162 | id: recipeRevisionId, | ||
| 163 | recipeId, | ||
| 164 | revisionNumber: 1, | ||
| 165 | sourceConfigJson: JSON.stringify({ | ||
| 166 | kind: "mcp", | ||
| 167 | endpoint: input.endpoint, | ||
| 168 | transport: "streamable-http", | ||
| 169 | queryParams: null, | ||
| 170 | headers: null, | ||
| 171 | }), | ||
| 172 | manifestJson, | ||
| 173 | manifestHash: null, | ||
| 174 | materializationHash: null, | ||
| 175 | createdAt: now, | ||
| 176 | updatedAt: now, | ||
| 177 | }, | ||
| 178 | documents: [{ | ||
| 179 | id: `src_recipe_doc_${sourceId}`, | ||
| 180 | recipeRevisionId, | ||
| 181 | documentKind: "mcp_manifest", | ||
| 182 | documentKey: "manifest", | ||
| 183 | contentText: manifestJson, | ||
| 184 | contentHash: `manifest_${sourceId}`, | ||
| 185 | fetchedAt: now, | ||
| 186 | createdAt: now, | ||
| 187 | updatedAt: now, | ||
| 188 | }], | ||
| 189 | schemaBundles: [], | ||
| 190 | operations: [{ | ||
| 191 | id: `src_recipe_op_${sourceId}`, | ||
| 192 | recipeRevisionId, | ||
| 193 | operationKey: "gated_echo", | ||
| 194 | transportKind: "mcp", | ||
| 195 | toolId: "gated_echo", | ||
| 196 | title: "gated_echo", | ||
| 197 | description: "Asks for approval before echoing a value", | ||
| 198 | operationKind: "unknown", | ||
| 199 | searchText: "demo.gated_echo gated echo approval", | ||
| 200 | inputSchemaJson: manifest.tools[0]!.inputSchemaJson ?? null, | ||
| 201 | outputSchemaJson: null, | ||
| 202 | providerKind: "mcp", | ||
| 203 | providerDataJson: JSON.stringify({ | ||
| 204 | kind: "mcp", | ||
| 205 | toolId: "gated_echo", | ||
| 206 | toolName: "gated_echo", | ||
| 207 | description: "Asks for approval before echoing a value", | ||
| 208 | }), | ||
| 209 | createdAt: now, | ||
| 210 | updatedAt: now, | ||
| 211 | }], | ||
| 212 | }, null, 2)}\n`, | ||
| 213 | "utf8", | ||
| 214 | ); | ||
| 215 | }); | 175 | }); |
| 216 | 176 | ||
| 217 | const makeServer = createIsolatedLocalExecutorServer({ | 177 | const makeServer = createIsolatedLocalExecutorServer({ |