Unified · Split
| @@ -90,7 +90,6 @@ import { · 89 unmodified lines | ||
| 90 | 90 | import { decideInteractionHandling } from "./interaction-handling"; |
| 91 | 91 | import { |
| 92 | 92 | renderMcpSessionSummary, |
| 93 | − renderUpSummary, | |
| 94 | 93 | renderWebSessionSummary, |
| 95 | 94 | } from "./session-summary"; |
| 96 | 95 | import { |
| @@ -415,8 +414,6 @@ const printRootHelp = (workflow: string) => · 318 unmodified lines | ||
| 415 | 414 | " Execute code against the local executor server.", |
| 416 | 415 | " resume", |
| 417 | 416 | " Resume a paused execution.", |
| 418 | − " up / down / status / doctor", | |
| 419 | − " Compatibility commands for the detached local daemon.", | |
| 420 | 417 | "", |
| 421 | 418 | "TIP", |
| 422 | 419 | "", |
| @@ -625,154 +622,11 @@ const waitForReachability = (baseUrl: string, expected: boolean) => · 202 unmodified lines | ||
| 625 | 622 | return yield* failReachabilityTimeout({ baseUrl, expected }); |
| 626 | 623 | }); |
| 627 | 624 | |
| 628 | −type LocalServerStatus = { | |
| 629 | − baseUrl: string; | |
| 630 | − reachable: boolean; | |
| 631 | − pidFile: string; | |
| 632 | − pid: number | null; | |
| 633 | − pidRunning: boolean; | |
| 634 | − logFile: string; | |
| 635 | − localDataDir: string; | |
| 636 | − webAssetsDir: string | null; | |
| 637 | − installation: { | |
| 638 | − accountId: string; | |
| 639 | − workspaceId: string; | |
| 640 | − } | null; | |
| 641 | −}; | |
| 642 | − | |
| 643 | −const getServerStatus = ( | |
| 644 | − baseUrl: string, | |
| 645 | −): Effect.Effect<LocalServerStatus, Error, FileSystem.FileSystem> => | |
| 646 | − Effect.gen(function* () { | |
| 647 | − const pidRecord = yield* readPidRecord(); | |
| 648 | − const reachable = yield* isServerReachable(baseUrl); | |
| 649 | − const installation = reachable | |
| 650 | − ? yield* getBootstrapClient(baseUrl).pipe( | |
| 651 | − Effect.flatMap((client) => client.local.installation({})), | |
| 652 | − Effect.map((installation) => ({ | |
| 653 | − accountId: installation.actorScopeId, | |
| 654 | − workspaceId: installation.scopeId, | |
| 655 | − })), | |
| 656 | − Effect.catchAll(() => Effect.succeed(null)), | |
| 657 | − ) | |
| 658 | − : null; | |
| 659 | − | |
| 660 | − const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null; | |
| 661 | − const pidRunning = pid !== null ? isPidRunning(pid) : false; | |
| 662 | − const logFile = pidRecord?.logFile ?? DEFAULT_SERVER_LOG_FILE; | |
| 663 | − | |
| 664 | − return { | |
| 665 | − baseUrl, | |
| 666 | − reachable, | |
| 667 | − pidFile: DEFAULT_SERVER_PID_FILE, | |
| 668 | − pid, | |
| 669 | − pidRunning, | |
| 670 | − logFile, | |
| 671 | − localDataDir: CLI_LOCAL_DATA_DIR, | |
| 672 | − webAssetsDir: resolveRuntimeWebAssetsDir(), | |
| 673 | − installation, | |
| 674 | − } satisfies LocalServerStatus; | |
| 675 | − }); | |
| 676 | − | |
| 677 | −const renderStatus = (status: LocalServerStatus): string => | |
| 678 | − [ | |
| 679 | − `baseUrl: ${status.baseUrl}`, | |
| 680 | − `reachable: ${status.reachable ? "yes" : "no"}`, | |
| 681 | − `pid: ${status.pid ?? "none"}`, | |
| 682 | − `pidRunning: ${status.pidRunning ? "yes" : "no"}`, | |
| 683 | − `pidFile: ${status.pidFile}`, | |
| 684 | − `logFile: ${status.logFile}`, | |
| 685 | − `localDataDir: ${status.localDataDir}`, | |
| 686 | − `webAssetsDir: ${status.webAssetsDir ?? "missing"}`, | |
| 687 | − `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`, | |
| 688 | − ].join("\n"); | |
| 689 | − | |
| 690 | −const getDoctorReport = (baseUrl: string) => | |
| 691 | − getServerStatus(baseUrl).pipe( | |
| 692 | − Effect.map((status) => { | |
| 693 | − const checks = { | |
| 694 | − serverReachable: { | |
| 695 | − ok: status.reachable, | |
| 696 | − detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`, | |
| 697 | − }, | |
| 698 | − pidFile: { | |
| 699 | − ok: status.pid !== null, | |
| 700 | − detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`, | |
| 701 | − }, | |
| 702 | − process: { | |
| 703 | − ok: status.pidRunning, | |
| 704 | − detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded", | |
| 705 | − }, | |
| 706 | − database: { | |
| 707 | − ok: status.localDataDir.length > 0, | |
| 708 | − detail: status.localDataDir, | |
| 709 | − }, | |
| 710 | − webAssets: { | |
| 711 | − ok: status.webAssetsDir !== null, | |
| 712 | − detail: status.webAssetsDir ?? "missing bundled web assets", | |
| 713 | − }, | |
| 714 | − installation: { | |
| 715 | − ok: status.installation !== null, | |
| 716 | − detail: status.installation | |
| 717 | − ? `workspace ${status.installation.workspaceId}` | |
| 718 | − : "local installation unavailable", | |
| 719 | − }, | |
| 720 | − } as const; | |
| 721 | − | |
| 722 | − return { | |
| 723 | − ok: Object.values(checks).every((check) => check.ok), | |
| 724 | − status, | |
| 725 | − checks, | |
| 726 | − }; | |
| 727 | − }), | |
| 728 | − ); | |
| 729 | − | |
| 730 | −const printJson = (value: unknown) => | |
| 731 | − Effect.sync(() => { | |
| 732 | − console.log(JSON.stringify(value, null, 2)); | |
| 733 | − }); | |
| 734 | − | |
| 735 | 625 | const printText = (value: string) => |
| 736 | 626 | Effect.sync(() => { |
| 737 | 627 | console.log(value); |
| 738 | 628 | }); |
| 739 | 629 | |
| 740 | −const stopServer = (baseUrl: string) => | |
| 741 | − Effect.gen(function* () { | |
| 742 | − const fs = yield* FileSystem.FileSystem; | |
| 743 | − const removePidFile = fs.remove(DEFAULT_SERVER_PID_FILE, { | |
| 744 | − force: true, | |
| 745 | − }).pipe(Effect.ignore); | |
| 746 | − const pidRecord = yield* readPidRecord(); | |
| 747 | − const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null; | |
| 748 | − | |
| 749 | − if (pid === null) { | |
| 750 | − yield* removePidFile; | |
| 751 | − return false; | |
| 752 | − } | |
| 753 | − | |
| 754 | − if (!isPidRunning(pid)) { | |
| 755 | − yield* removePidFile; | |
| 756 | − return false; | |
| 757 | − } | |
| 758 | − | |
| 759 | − | |
| 760 | − yield* Effect.sync(() => { | |
| 761 | − process.kill(pid, "SIGTERM"); | |
| 762 | − }); | |
| 763 | − | |
| 764 | − yield* waitForReachability(baseUrl, false).pipe( | |
| 765 | − Effect.catchAll(() => | |
| 766 | − removePidFile.pipe( | |
| 767 | − Effect.ignore, | |
| 768 | − Effect.zipRight(Effect.fail(executorAppEffectError("cli/main", `Timed out stopping local executor server pid ${pid}`))), | |
| 769 | − ), | |
| 770 | − ), | |
| 771 | − ); | |
| 772 | − | |
| 773 | − return true; | |
| 774 | − }); | |
| 775 | − | |
| 776 | 630 | const ensureServer = (baseUrl: string = DEFAULT_SERVER_BASE_URL) => |
| 777 | 631 | Effect.gen(function* () { |
| 778 | 632 | const reachable = yield* isServerReachable(baseUrl); |
| @@ -1243,63 +1097,6 @@ const mcpCommand = Command.make( · 464 unmodified lines | ||
| 1243 | 1097 | : runForegroundSession({ kind: "mcp", port }), |
| 1244 | 1098 | ).pipe(Command.withDescription("Start a foreground MCP session, or run stdio MCP with --stdio")); |
| 1245 | 1099 | |
| 1246 | −const upCommand = Command.make( | |
| 1247 | − "up", | |
| 1248 | − { | |
| 1249 | − baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | |
| 1250 | − }, | |
| 1251 | − ({ baseUrl }) => | |
| 1252 | − ensureServer(baseUrl).pipe( | |
| 1253 | − Effect.flatMap(({ started }) => | |
| 1254 | − getServerStatus(baseUrl).pipe( | |
| 1255 | − Effect.flatMap((status) => printText(renderUpSummary({ started, status }))), | |
| 1256 | − ) | |
| 1257 | − ), | |
| 1258 | − ), | |
| 1259 | −).pipe(Command.withDescription("Start the local executor server if needed and show how to use it")); | |
| 1260 | − | |
| 1261 | −const downCommand = Command.make( | |
| 1262 | − "down", | |
| 1263 | − { | |
| 1264 | − baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | |
| 1265 | − }, | |
| 1266 | − ({ baseUrl }) => | |
| 1267 | − stopServer(baseUrl).pipe( | |
| 1268 | − Effect.flatMap((stopped) => | |
| 1269 | − printText(stopped ? "Stopped local executor server." : "Local executor server is not running."), | |
| 1270 | − ), | |
| 1271 | − ), | |
| 1272 | −).pipe(Command.withDescription("Stop the local executor server")); | |
| 1273 | − | |
| 1274 | −const statusCommand = Command.make( | |
| 1275 | − "status", | |
| 1276 | − { | |
| 1277 | − baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | |
| 1278 | − json: Options.boolean("json").pipe(Options.withDefault(false)), | |
| 1279 | − }, | |
| 1280 | − ({ baseUrl, json }) => | |
| 1281 | − getServerStatus(baseUrl).pipe( | |
| 1282 | − Effect.flatMap((status) => json ? printJson(status) : printText(renderStatus(status))), | |
| 1283 | − ), | |
| 1284 | −).pipe(Command.withDescription("Show local executor server status")); | |
| 1285 | − | |
| 1286 | −const doctorCommand = Command.make( | |
| 1287 | − "doctor", | |
| 1288 | − { | |
| 1289 | − baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | |
| 1290 | − json: Options.boolean("json").pipe(Options.withDefault(false)), | |
| 1291 | − }, | |
| 1292 | − ({ baseUrl, json }) => | |
| 1293 | − getDoctorReport(baseUrl).pipe( | |
| 1294 | − Effect.flatMap((report) => json | |
| 1295 | − ? printJson(report) | |
| 1296 | − : printText([ | |
| 1297 | − `ok: ${report.ok ? "yes" : "no"}`, | |
| 1298 | − ...Object.entries(report.checks).map(([name, check]) => `${name}: ${check.ok ? "ok" : "fail"} - ${check.detail}`), | |
| 1299 | − ].join("\n"))), | |
| 1300 | − ), | |
| 1301 | −).pipe(Command.withDescription("Check local executor install and daemon health")); | |
| 1302 | − | |
| 1303 | 1100 | const callCommand = Command.make( |
| 1304 | 1101 | "call", |
| 1305 | 1102 | { |
| @@ -1386,10 +1183,6 @@ const root = Command.make("executor").pipe( · 80 unmodified lines | ||
| 1386 | 1183 | serverCommand, |
| 1387 | 1184 | webCommand, |
| 1388 | 1185 | mcpCommand, |
| 1389 | − upCommand, | |
| 1390 | − downCommand, | |
| 1391 | − statusCommand, | |
| 1392 | − doctorCommand, | |
| 1393 | 1186 | callCommand, |
| 1394 | 1187 | resumeCommand, |
| 1395 | 1188 | devCommand, |
| @@ -38,7 +38,7 @@ const makeEnvelope = ( · 37 unmodified lines | ||
| 38 | 38 | scopeId: "ws_123" as never, |
| 39 | 39 | createdByScopeId: "acct_123" as never, |
| 40 | 40 | status: "waiting_for_interaction", |
| 41 | − code: "return await tools.executor.sources.add({ endpoint: 'https://mcp.axiom.co/mcp' });", | |
| 41 | + code: "return await tools.executor.mcp.createSource({ name: 'Axiom', endpoint: 'https://mcp.axiom.co/mcp', transport: 'streamable-http', queryParams: null, headers: null, command: null, args: null, env: null, cwd: null, auth: { kind: 'none' } });", | |
| 42 | 42 | resultJson: null, |
| 43 | 43 | errorText: null, |
| 44 | 44 | logsJson: null, |
| @@ -8,7 +8,6 @@ import * as Effect from "effect/Effect"; · 7 unmodified lines | ||
| 8 | 8 | import { |
| 9 | 9 | loadLocalExecutorConfig, |
| 10 | 10 | mergeLocalExecutorConfigs, |
| 11 | − migrateLegacyLocalExecutorConfigFile, | |
| 12 | 11 | resolveDefaultHomeConfigCandidates, |
| 13 | 12 | resolveDefaultHomeStateDirectory, |
| 14 | 13 | resolveLocalWorkspaceContext, |
| @@ -39,10 +38,15 @@ describe("local-config", () => { · 24 unmodified lines | ||
| 39 | 38 | "sources": { |
| 40 | 39 | "github": { |
| 41 | 40 | "kind": "openapi", |
| 42 | − "binding": { | |
| 41 | + "config": { | |
| 43 | 42 | "specUrl": "https://example.com/openapi.json", |
| 44 | − }, | |
| 45 | − }, | |
| 43 | + "baseUrl": null, | |
| 44 | + "auth": { | |
| 45 | + "kind": "none" | |
| 46 | + }, | |
| 47 | + "defaultHeaders": null | |
| 48 | + } | |
| 49 | + } | |
| 46 | 50 | }, |
| 47 | 51 | } |
| 48 | 52 | `, |
| @@ -137,54 +141,4 @@ describe("local-config", () => { · 88 unmodified lines | ||
| 137 | 141 | |
| 138 | 142 | expect(merged?.runtime).toBe("deno"); |
| 139 | 143 | }); |
| 140 | − | |
| 141 | − it.effect("migrates legacy preset source stubs before strict decode", () => | |
| 142 | − Effect.gen(function* () { | |
| 143 | − const fs = yield* FileSystem.FileSystem; | |
| 144 | − const workspaceRoot = yield* makeWorkspaceRoot(); | |
| 145 | − const homeConfigPath = join(workspaceRoot, "executor-home.jsonc"); | |
| 146 | − | |
| 147 | − yield* fs.writeFileString( | |
| 148 | − homeConfigPath, | |
| 149 | − `{ | |
| 150 | − "sources": { | |
| 151 | − "axiom": { | |
| 152 | − "name": "Axiom MCP", | |
| 153 | − "kind": "mcp" | |
| 154 | − }, | |
| 155 | − "stripe-api": { | |
| 156 | − "name": "Stripe API", | |
| 157 | − "namespace": "stripe.api", | |
| 158 | − "kind": "openapi" | |
| 159 | − }, | |
| 160 | − "kept": { | |
| 161 | − "name": "Kept", | |
| 162 | − "kind": "openapi", | |
| 163 | − "connection": { | |
| 164 | − "endpoint": "https://example.com" | |
| 165 | − }, | |
| 166 | − "binding": { | |
| 167 | − "specUrl": "https://example.com/openapi.json" | |
| 168 | − } | |
| 169 | − } | |
| 170 | − } | |
| 171 | −} | |
| 172 | −`, | |
| 173 | − ); | |
| 174 | − | |
| 175 | − const changed = yield* migrateLegacyLocalExecutorConfigFile(homeConfigPath); | |
| 176 | − const migratedContent = yield* fs.readFileString(homeConfigPath, "utf8"); | |
| 177 | − const context = yield* resolveLocalWorkspaceContext({ | |
| 178 | − workspaceRoot, | |
| 179 | − homeConfigPath, | |
| 180 | − }); | |
| 181 | − const loaded = yield* loadLocalExecutorConfig(context); | |
| 182 | − | |
| 183 | − expect(changed).toBe(true); | |
| 184 | − expect(migratedContent).not.toContain("\"axiom\""); | |
| 185 | − expect(migratedContent).not.toContain("\"stripe-api\""); | |
| 186 | − expect(loaded.config?.sources?.kept?.kind).toBe("openapi"); | |
| 187 | − expect(loaded.config?.sources?.axiom).toBeUndefined(); | |
| 188 | − }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 189 | − ); | |
| 190 | 144 | }); |
| @@ -33,9 +33,6 @@ export type FileLoadedExecutorConfig = LoadedLocalExecutorConfig & { · 32 unmodified lines | ||
| 33 | 33 | export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string => |
| 34 | 34 | `${JSON.stringify(config, null, 2)}\n`; |
| 35 | 35 | |
| 36 | −const encodeUnknownConfigDocument = (config: unknown): string => | |
| 37 | − `${JSON.stringify(config, null, 2)}\n`; | |
| 38 | − | |
| 39 | 36 | export const resolveConfigRelativePath = (input: { |
| 40 | 37 | path: string; |
| 41 | 38 | scopeRoot: string; |
| @@ -226,130 +223,6 @@ const parseJsonc = (input: { path: string; content: string }): LocalExecutorConf · 184 unmodified lines | ||
| 226 | 223 | } |
| 227 | 224 | }; |
| 228 | 225 | |
| 229 | −const parseUnknownJsoncDocument = (input: { | |
| 230 | − path: string; | |
| 231 | − content: string; | |
| 232 | −}): unknown => { | |
| 233 | − const errors: JsoncParseError[] = []; | |
| 234 | − | |
| 235 | − try { | |
| 236 | − const parsed = parseJsoncDocument(input.content, errors, { | |
| 237 | − allowTrailingComma: true, | |
| 238 | − }); | |
| 239 | − if (errors.length > 0) { | |
| 240 | − throw new LocalExecutorConfigDecodeError({ | |
| 241 | − message: `Invalid executor config at ${input.path}: ${formatJsoncParseErrors(input.content, errors)}`, | |
| 242 | − path: input.path, | |
| 243 | − details: formatJsoncParseErrors(input.content, errors), | |
| 244 | − }); | |
| 245 | − } | |
| 246 | − | |
| 247 | − return parsed; | |
| 248 | − } catch (cause) { | |
| 249 | − if (cause instanceof LocalExecutorConfigDecodeError) { | |
| 250 | − throw cause; | |
| 251 | − } | |
| 252 | − throw new LocalExecutorConfigDecodeError({ | |
| 253 | − message: `Invalid executor config at ${input.path}: ${unknownLocalErrorDetails(cause)}`, | |
| 254 | − path: input.path, | |
| 255 | − details: unknownLocalErrorDetails(cause), | |
| 256 | − }); | |
| 257 | − } | |
| 258 | −}; | |
| 259 | − | |
| 260 | −const asRecord = (value: unknown): Record<string, unknown> | null => | |
| 261 | − value !== null && typeof value === "object" && !Array.isArray(value) | |
| 262 | − ? value as Record<string, unknown> | |
| 263 | − : null; | |
| 264 | − | |
| 265 | −const migrateLegacySourceEntries = ( | |
| 266 | − sources: unknown, | |
| 267 | −): { | |
| 268 | − sources: unknown; | |
| 269 | − changed: boolean; | |
| 270 | −} => { | |
| 271 | − const sourceMap = asRecord(sources); | |
| 272 | − if (!sourceMap) { | |
| 273 | − return { | |
| 274 | − sources, | |
| 275 | − changed: false, | |
| 276 | − }; | |
| 277 | − } | |
| 278 | − | |
| 279 | − let changed = false; | |
| 280 | − const migratedEntries = Object.entries(sourceMap).flatMap(([key, value]) => { | |
| 281 | − const source = asRecord(value); | |
| 282 | − if (!source) { | |
| 283 | − return [[key, value] as const]; | |
| 284 | − } | |
| 285 | − | |
| 286 | − const hasConnection = asRecord(source.connection) !== null; | |
| 287 | − const kind = typeof source.kind === "string" ? source.kind : null; | |
| 288 | − const isLegacyPresetStub = | |
| 289 | − kind !== null | |
| 290 | − && hasConnection === false; | |
| 291 | − | |
| 292 | − if (isLegacyPresetStub) { | |
| 293 | − changed = true; | |
| 294 | − return []; | |
| 295 | − } | |
| 296 | − | |
| 297 | − return [[key, value] as const]; | |
| 298 | − }); | |
| 299 | − | |
| 300 | − if (!changed) { | |
| 301 | − return { | |
| 302 | − sources, | |
| 303 | − changed: false, | |
| 304 | − }; | |
| 305 | − } | |
| 306 | − | |
| 307 | − return { | |
| 308 | − sources: migratedEntries.length > 0 | |
| 309 | − ? Object.fromEntries(migratedEntries) | |
| 310 | − : undefined, | |
| 311 | − changed: true, | |
| 312 | − }; | |
| 313 | −}; | |
| 314 | − | |
| 315 | −const migrateLegacyLocalExecutorConfigDocument = ( | |
| 316 | − value: unknown, | |
| 317 | −): { | |
| 318 | − value: unknown; | |
| 319 | − changed: boolean; | |
| 320 | −} => { | |
| 321 | − const root = asRecord(value); | |
| 322 | − if (!root) { | |
| 323 | − return { | |
| 324 | − value, | |
| 325 | − changed: false, | |
| 326 | − }; | |
| 327 | − } | |
| 328 | − | |
| 329 | − const migratedSources = migrateLegacySourceEntries(root.sources); | |
| 330 | − if (!migratedSources.changed) { | |
| 331 | − return { | |
| 332 | − value, | |
| 333 | − changed: false, | |
| 334 | − }; | |
| 335 | − } | |
| 336 | − | |
| 337 | − const nextRoot: Record<string, unknown> = { | |
| 338 | − ...root, | |
| 339 | − }; | |
| 340 | − | |
| 341 | − if (migratedSources.sources === undefined) { | |
| 342 | − delete nextRoot.sources; | |
| 343 | − } else { | |
| 344 | − nextRoot.sources = migratedSources.sources; | |
| 345 | − } | |
| 346 | − | |
| 347 | − return { | |
| 348 | − value: nextRoot, | |
| 349 | − changed: true, | |
| 350 | − }; | |
| 351 | −}; | |
| 352 | − | |
| 353 | 226 | const mergeSourceMaps = ( |
| 354 | 227 | base: Record<string, LocalConfigSource> | undefined, |
| 355 | 228 | extra: Record<string, LocalConfigSource> | undefined, |
| @@ -553,51 +426,6 @@ export const readOptionalLocalExecutorConfig = ( · 197 unmodified lines | ||
| 553 | 426 | }); |
| 554 | 427 | }); |
| 555 | 428 | |
| 556 | −export const migrateLegacyLocalExecutorConfigFile = ( | |
| 557 | − path: string, | |
| 558 | −): Effect.Effect< | |
| 559 | − boolean, | |
| 560 | − LocalFileSystemError | LocalExecutorConfigDecodeError, | |
| 561 | − FileSystem.FileSystem | |
| 562 | −> => | |
| 563 | − Effect.gen(function* () { | |
| 564 | − const fs = yield* FileSystem.FileSystem; | |
| 565 | − const exists = yield* fs.exists(path).pipe( | |
| 566 | − Effect.mapError(mapFileSystemError(path, "check config path")), | |
| 567 | − ); | |
| 568 | − if (!exists) { | |
| 569 | − return false; | |
| 570 | − } | |
| 571 | − | |
| 572 | − const content = yield* fs.readFileString(path, "utf8").pipe( | |
| 573 | − Effect.mapError(mapFileSystemError(path, "read config")), | |
| 574 | − ); | |
| 575 | − const parsed = parseUnknownJsoncDocument({ path, content }); | |
| 576 | − const migrated = migrateLegacyLocalExecutorConfigDocument(parsed); | |
| 577 | − | |
| 578 | − if (!migrated.changed) { | |
| 579 | − return false; | |
| 580 | − } | |
| 581 | − | |
| 582 | − const decoded = decodeLocalExecutorConfig(migrated.value); | |
| 583 | − yield* fs.writeFileString(path, encodeUnknownConfigDocument(decoded)).pipe( | |
| 584 | − Effect.mapError(mapFileSystemError(path, "write config")), | |
| 585 | − ); | |
| 586 | − return true; | |
| 587 | − }); | |
| 588 | − | |
| 589 | −export const migrateLegacyLocalExecutorConfigs = ( | |
| 590 | − context: ResolvedLocalWorkspaceContext, | |
| 591 | −): Effect.Effect< | |
| 592 | − void, | |
| 593 | − LocalFileSystemError | LocalExecutorConfigDecodeError, | |
| 594 | − FileSystem.FileSystem | |
| 595 | −> => | |
| 596 | − Effect.gen(function* () { | |
| 597 | − yield* migrateLegacyLocalExecutorConfigFile(context.homeConfigPath); | |
| 598 | − yield* migrateLegacyLocalExecutorConfigFile(context.projectConfigPath); | |
| 599 | − }); | |
| 600 | − | |
| 601 | 429 | export const loadLocalExecutorConfig = ( |
| 602 | 430 | context: ResolvedLocalWorkspaceContext, |
| 603 | 431 | ): Effect.Effect< |
| @@ -28,8 +28,6 @@ import { · 27 unmodified lines | ||
| 28 | 28 | |
| 29 | 29 | const LOCAL_EXECUTOR_STATE_VERSION = 1 as const; |
| 30 | 30 | const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json"; |
| 31 | −const LEGACY_LOCAL_SECRET_STORE_ID = "sts_builtin_local"; | |
| 32 | −const LEGACY_KEYCHAIN_SECRET_STORE_ID = "sts_builtin_keychain"; | |
| 33 | 31 | |
| 34 | 32 | const LocalExecutorStateSnapshotSchema = Schema.Struct({ |
| 35 | 33 | version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION), |
| @@ -63,145 +61,6 @@ const defaultLocalExecutorStateSnapshot = (): LocalExecutorStateSnapshot => ({ · 27 unmodified lines | ||
| 63 | 61 | const cloneValue = <T>(value: T): T => |
| 64 | 62 | JSON.parse(JSON.stringify(value)) as T; |
| 65 | 63 | |
| 66 | −const asRecord = ( | |
| 67 | − value: unknown, | |
| 68 | −): Record<string, unknown> | null => | |
| 69 | − value !== null && typeof value === "object" && !Array.isArray(value) | |
| 70 | − ? (value as Record<string, unknown>) | |
| 71 | − : null; | |
| 72 | − | |
| 73 | −const legacySecretStoreIdForProviderId = (providerId: string): string => | |
| 74 | − providerId === "local" | |
| 75 | − ? LEGACY_LOCAL_SECRET_STORE_ID | |
| 76 | − : providerId === "keychain" | |
| 77 | − ? LEGACY_KEYCHAIN_SECRET_STORE_ID | |
| 78 | − : `sts_legacy_${providerId}`; | |
| 79 | − | |
| 80 | −const legacySecretStoreNameForProviderId = (providerId: string): string => { | |
| 81 | − switch (providerId) { | |
| 82 | − case "local": | |
| 83 | − return "Local Store"; | |
| 84 | − case "keychain": | |
| 85 | − return process.platform === "darwin" | |
| 86 | − ? "macOS Keychain" | |
| 87 | − : "Desktop Keyring"; | |
| 88 | − default: | |
| 89 | − return `Legacy ${providerId}`; | |
| 90 | − } | |
| 91 | −}; | |
| 92 | − | |
| 93 | −const migrateLegacyExecutorStateValue = ( | |
| 94 | − value: unknown, | |
| 95 | − input: { | |
| 96 | − scopeId: string; | |
| 97 | − }, | |
| 98 | −): { | |
| 99 | − value: unknown; | |
| 100 | − migrated: boolean; | |
| 101 | −} => { | |
| 102 | − if (Array.isArray(value)) { | |
| 103 | − let migrated = false; | |
| 104 | − const next = value.map((item) => { | |
| 105 | − const result = migrateLegacyExecutorStateValue(item, input); | |
| 106 | − migrated = migrated || result.migrated; | |
| 107 | − return result.value; | |
| 108 | − }); | |
| 109 | − return { value: next, migrated }; | |
| 110 | − } | |
| 111 | − | |
| 112 | − const record = asRecord(value); | |
| 113 | − if (record === null) { | |
| 114 | − return { value, migrated: false }; | |
| 115 | − } | |
| 116 | − | |
| 117 | − let migrated = false; | |
| 118 | − const next: Record<string, unknown> = {}; | |
| 119 | − | |
| 120 | − for (const [key, entry] of Object.entries(record)) { | |
| 121 | − let nextKey = key; | |
| 122 | − if (key === "workspaceId") { | |
| 123 | − nextKey = "scopeId"; | |
| 124 | − migrated = true; | |
| 125 | − } else if (key === "actorAccountId") { | |
| 126 | − nextKey = "actorScopeId"; | |
| 127 | − migrated = true; | |
| 128 | − } else if (key === "createdByAccountId") { | |
| 129 | − nextKey = "createdByScopeId"; | |
| 130 | − migrated = true; | |
| 131 | − } else if (key === "workspaceOauthClients") { | |
| 132 | − nextKey = "scopeOauthClients"; | |
| 133 | − migrated = true; | |
| 134 | − } | |
| 135 | − | |
| 136 | − const migratedEntry = migrateLegacyExecutorStateValue(entry, input); | |
| 137 | − migrated = migrated || migratedEntry.migrated; | |
| 138 | − next[nextKey] = migratedEntry.value; | |
| 139 | − } | |
| 140 | − | |
| 141 | − if (next.version === LOCAL_EXECUTOR_STATE_VERSION) { | |
| 142 | − const secretMaterialsValue = Array.isArray(next.secretMaterials) | |
| 143 | − ? next.secretMaterials | |
| 144 | − : []; | |
| 145 | − const secretStoresValue = Array.isArray(next.secretStores) | |
| 146 | − ? next.secretStores | |
| 147 | − : []; | |
| 148 | − const secretStoresById = new Map<string, Record<string, unknown>>(); | |
| 149 | − | |
| 150 | − for (const entry of secretStoresValue) { | |
| 151 | − const record = asRecord(entry); | |
| 152 | − if (record !== null && typeof record.id === "string") { | |
| 153 | − secretStoresById.set(record.id, record); | |
| 154 | − } | |
| 155 | − } | |
| 156 | − | |
| 157 | − const migratedSecretMaterials = secretMaterialsValue.map((entry) => { | |
| 158 | − const record = asRecord(entry); | |
| 159 | − if (record === null) { | |
| 160 | − return entry; | |
| 161 | − } | |
| 162 | − | |
| 163 | − if (typeof record.providerId !== "string" || typeof record.storeId === "string") { | |
| 164 | − return entry; | |
| 165 | − } | |
| 166 | − | |
| 167 | − const providerId = record.providerId; | |
| 168 | − const storeId = legacySecretStoreIdForProviderId(providerId); | |
| 169 | − const createdAt = | |
| 170 | − typeof record.createdAt === "number" ? record.createdAt : 0; | |
| 171 | − const updatedAt = | |
| 172 | − typeof record.updatedAt === "number" ? record.updatedAt : createdAt; | |
| 173 | − | |
| 174 | − secretStoresById.set(storeId, { | |
| 175 | − id: storeId, | |
| 176 | − scopeId: input.scopeId, | |
| 177 | − name: legacySecretStoreNameForProviderId(providerId), | |
| 178 | − kind: providerId, | |
| 179 | − status: providerId === "local" || providerId === "keychain" | |
| 180 | − ? "connected" | |
| 181 | − : "error", | |
| 182 | − enabled: true, | |
| 183 | − createdAt, | |
| 184 | − updatedAt, | |
| 185 | − }); | |
| 186 | − | |
| 187 | − migrated = true; | |
| 188 | − return { | |
| 189 | − ...record, | |
| 190 | − storeId, | |
| 191 | − }; | |
| 192 | − }); | |
| 193 | − | |
| 194 | − if (!Array.isArray(next.secretStores)) { | |
| 195 | − migrated = true; | |
| 196 | − } | |
| 197 | − | |
| 198 | − next.secretStores = [...secretStoresById.values()]; | |
| 199 | − next.secretMaterials = migratedSecretMaterials; | |
| 200 | − } | |
| 201 | − | |
| 202 | − return { value: next, migrated }; | |
| 203 | −}; | |
| 204 | − | |
| 205 | 64 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => |
| 206 | 65 | new LocalFileSystemError({ |
| 207 | 66 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, |
| @@ -258,15 +117,9 @@ const readStateFromDisk = ( · 50 unmodified lines | ||
| 258 | 117 | try: () => JSON.parse(content) as unknown, |
| 259 | 118 | catch: mapFileSystemError(path, "parse executor state"), |
| 260 | 119 | }); |
| 261 | − const migrated = migrateLegacyExecutorStateValue(parsed, { | |
| 262 | − scopeId: deriveLocalInstallation(context).scopeId, | |
| 263 | − }); | |
| 264 | − const decoded = yield* decodeLocalExecutorStateSnapshot(migrated.value).pipe( | |
| 120 | + const decoded = yield* decodeLocalExecutorStateSnapshot(parsed).pipe( | |
| 265 | 121 | Effect.mapError(mapFileSystemError(path, "decode executor state")), |
| 266 | 122 | ); |
| 267 | − if (migrated.migrated) { | |
| 268 | − yield* writeStateToDisk(context, decoded); | |
| 269 | − } | |
| 270 | 123 | return decoded; |
| 271 | 124 | }); |
| 272 | 125 | |
| @@ -27,11 +27,11 @@ import { · 26 unmodified lines | ||
| 27 | 27 | type ExecutorRuntime, |
| 28 | 28 | type ExecutorRuntimeOptions, |
| 29 | 29 | type ResolveSecretMaterial, |
| 30 | + emptyExecutorPluginRegistry, | |
| 30 | 31 | } from "@executor/platform-sdk/runtime"; |
| 31 | 32 | import * as Effect from "effect/Effect"; |
| 32 | 33 | import { |
| 33 | 34 | type LoadedLocalExecutorConfig, |
| 34 | − migrateLegacyLocalExecutorConfigs, | |
| 35 | 35 | type ResolvedLocalWorkspaceContext, |
| 36 | 36 | loadLocalExecutorConfig, |
| 37 | 37 | resolveConfigRelativePath, |
| @@ -238,10 +238,6 @@ export const createLocalExecutorRepositoriesEffect = ( · 200 unmodified lines | ||
| 238 | 238 | homeStateDirectory: options.homeStateDirectory, |
| 239 | 239 | }), |
| 240 | 240 | ).pipe(Effect.mapError(toError)); |
| 241 | − yield* bindFileSystem( | |
| 242 | − fileSystem, | |
| 243 | − migrateLegacyLocalExecutorConfigs(workspaceContext), | |
| 244 | − ).pipe(Effect.mapError(toError)); | |
| 245 | 241 | |
| 246 | 242 | const executorStateStorage = createLocalExecutorStatePersistence( |
| 247 | 243 | workspaceContext, |
| @@ -252,14 +248,17 @@ export const createLocalExecutorRepositoriesEffect = ( · 4 unmodified lines | ||
| 252 | 248 | fileSystem, |
| 253 | 249 | ); |
| 254 | 250 | const loadedConfig = yield* workspaceConfigStore.load(); |
| 251 | + const pluginRegistry = runtimeOptions.pluginRegistry ?? emptyExecutorPluginRegistry(); | |
| 255 | 252 | yield* provisionBuiltinSecretStores({ |
| 256 | 253 | executorState: executorStateStorage.executorState, |
| 254 | + pluginRegistry, | |
| 257 | 255 | scopeId: deriveLocalInstallation(workspaceContext).scopeId, |
| 258 | 256 | }); |
| 259 | 257 | const resolveSecretMaterial: ResolveSecretMaterial = |
| 260 | 258 | runtimeOptions.resolveSecretMaterial |
| 261 | 259 | ?? createDefaultSecretMaterialResolver({ |
| 262 | 260 | executorState: executorStateStorage.executorState, |
| 261 | + pluginRegistry, | |
| 263 | 262 | }); |
| 264 | 263 | |
| 265 | 264 | return { |
| @@ -288,14 +287,17 @@ export const createLocalExecutorRepositoriesEffect = ( · 22 unmodified lines | ||
| 288 | 287 | resolve: resolveSecretMaterial, |
| 289 | 288 | store: createDefaultSecretMaterialStorer({ |
| 290 | 289 | executorState: executorStateStorage.executorState, |
| 290 | + pluginRegistry, | |
| 291 | 291 | resolveSecretMaterial, |
| 292 | 292 | }), |
| 293 | 293 | delete: createDefaultSecretMaterialDeleter({ |
| 294 | 294 | executorState: executorStateStorage.executorState, |
| 295 | + pluginRegistry, | |
| 295 | 296 | resolveSecretMaterial, |
| 296 | 297 | }), |
| 297 | 298 | update: createDefaultSecretMaterialUpdater({ |
| 298 | 299 | executorState: executorStateStorage.executorState, |
| 300 | + pluginRegistry, | |
| 299 | 301 | resolveSecretMaterial, |
| 300 | 302 | }), |
| 301 | 303 | }, |
| @@ -308,6 +310,7 @@ export const createLocalExecutorRepositoriesEffect = ( · 6 unmodified lines | ||
| 308 | 310 | instanceConfig: { |
| 309 | 311 | resolve: createLocalInstanceConfigResolver({ |
| 310 | 312 | executorState: executorStateStorage.executorState, |
| 313 | + pluginRegistry, | |
| 311 | 314 | }), |
| 312 | 315 | }, |
| 313 | 316 | close: executorStateStorage.close, |
| @@ -4,6 +4,9 @@ import type { · 3 unmodified lines | ||
| 4 | 4 | InstanceConfig, |
| 5 | 5 | SecretProvider, |
| 6 | 6 | } from "@executor/platform-sdk/contracts"; |
| 7 | +import type { | |
| 8 | + ExecutorSdkPluginRegistry, | |
| 9 | +} from "@executor/platform-sdk/plugins"; | |
| 7 | 10 | import type { |
| 8 | 11 | SecretMaterial, |
| 9 | 12 | SecretMaterialPurpose, |
| @@ -43,8 +46,10 @@ const trimOrNull = (value: string | null | undefined): string | null => { · 33 unmodified lines | ||
| 43 | 46 | return trimmed.length > 0 ? trimmed : null; |
| 44 | 47 | }; |
| 45 | 48 | |
| 46 | −const builtinSecretStoreContributions = () => | |
| 47 | − registeredSecretStoreContributions() | |
| 49 | +const builtinSecretStoreContributions = ( | |
| 50 | + pluginRegistry: ExecutorSdkPluginRegistry, | |
| 51 | +) => | |
| 52 | + registeredSecretStoreContributions(pluginRegistry) | |
| 48 | 53 | .filter((entry) => entry.builtin !== undefined) |
| 49 | 54 | .filter((entry) => entry.builtin?.enabled?.() ?? true) |
| 50 | 55 | .map((entry) => ({ |
| @@ -52,11 +57,17 @@ const builtinSecretStoreContributions = () => · 1 unmodified line | ||
| 52 | 57 | builtin: entry.builtin!, |
| 53 | 58 | })); |
| 54 | 59 | |
| 55 | −const resolveStoreContribution = (store: SecretStore) => | |
| 56 | − getSecretStoreContribution(store.kind); | |
| 57 | − | |
| 58 | −const loadStoreCapabilities = (store: SecretStore) => | |
| 59 | − resolveStoreContribution(store).getCapabilities({ | |
| 60 | +const resolveStoreContribution = ( | |
| 61 | + pluginRegistry: ExecutorSdkPluginRegistry, | |
| 62 | + store: SecretStore, | |
| 63 | +) => | |
| 64 | + getSecretStoreContribution(pluginRegistry, store.kind); | |
| 65 | + | |
| 66 | +const loadStoreCapabilities = ( | |
| 67 | + pluginRegistry: ExecutorSdkPluginRegistry, | |
| 68 | + store: SecretStore, | |
| 69 | +) => | |
| 70 | + resolveStoreContribution(pluginRegistry, store).getCapabilities({ | |
| 60 | 71 | store, |
| 61 | 72 | }) as Effect.Effect<{ |
| 62 | 73 | canCreateSecrets: boolean; |
| @@ -72,6 +83,7 @@ const parseDefaultStoreKind = (value: string | undefined): string | null => { · 9 unmodified lines | ||
| 72 | 83 | }; |
| 73 | 84 | |
| 74 | 85 | const resolveDefaultStoreId = ( |
| 86 | + pluginRegistry: ExecutorSdkPluginRegistry, | |
| 75 | 87 | stores: ReadonlyArray<{ |
| 76 | 88 | id: string; |
| 77 | 89 | kind: string; |
| @@ -85,7 +97,7 @@ const resolveDefaultStoreId = ( · 7 unmodified lines | ||
| 85 | 97 | } |
| 86 | 98 | } |
| 87 | 99 | |
| 88 | − const prioritizedBuiltins = builtinSecretStoreContributions() | |
| 100 | + const prioritizedBuiltins = builtinSecretStoreContributions(pluginRegistry) | |
| 89 | 101 | .sort((left, right) => |
| 90 | 102 | (right.builtin.defaultPriority ?? 0) - (left.builtin.defaultPriority ?? 0) |
| 91 | 103 | ); |
| @@ -102,12 +114,13 @@ const resolveDefaultStoreId = ( · 10 unmodified lines | ||
| 102 | 114 | |
| 103 | 115 | export const provisionBuiltinSecretStores = (input: { |
| 104 | 116 | executorState: ExecutorStateStoreShape; |
| 117 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 105 | 118 | scopeId: string; |
| 106 | 119 | }) => |
| 107 | 120 | Effect.gen(function* () { |
| 108 | 121 | const now = Date.now(); |
| 109 | 122 | |
| 110 | − for (const entry of builtinSecretStoreContributions()) { | |
| 123 | + for (const entry of builtinSecretStoreContributions(input.pluginRegistry)) { | |
| 111 | 124 | const existing = yield* input.executorState.secretStores.getById( |
| 112 | 125 | entry.builtin.storeId as SecretStore["id"], |
| 113 | 126 | ); |
| @@ -169,6 +182,7 @@ const provideSecretResolver = <A, E>( · 55 unmodified lines | ||
| 169 | 182 | |
| 170 | 183 | export const createDefaultSecretMaterialResolver = (input: { |
| 171 | 184 | executorState: ExecutorStateStoreShape; |
| 185 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 172 | 186 | keychainServiceName?: string; |
| 173 | 187 | }): ResolveSecretMaterial => { |
| 174 | 188 | let resolveSecretMaterial!: ResolveSecretMaterial; |
| @@ -182,7 +196,7 @@ export const createDefaultSecretMaterialResolver = (input: { · 7 unmodified lines | ||
| 182 | 196 | executorState: input.executorState, |
| 183 | 197 | storeId: material.storeId, |
| 184 | 198 | }); |
| 185 | − const contribution = resolveStoreContribution(store); | |
| 199 | + const contribution = resolveStoreContribution(input.pluginRegistry, store); | |
| 186 | 200 | return yield* provideSecretResolver( |
| 187 | 201 | contribution.resolveSecret({ |
| 188 | 202 | secret: material, |
| @@ -198,13 +212,14 @@ export const createDefaultSecretMaterialResolver = (input: { · 9 unmodified lines | ||
| 198 | 212 | |
| 199 | 213 | export const createDefaultSecretMaterialStorer = (input: { |
| 200 | 214 | executorState: ExecutorStateStoreShape; |
| 215 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 201 | 216 | resolveSecretMaterial: ResolveSecretMaterial; |
| 202 | 217 | keychainServiceName?: string; |
| 203 | 218 | }): StoreSecretMaterial => { |
| 204 | 219 | return ({ purpose, value, name, storeId }) => |
| 205 | 220 | Effect.gen(function* () { |
| 206 | 221 | const stores = yield* input.executorState.secretStores.listAll(); |
| 207 | − const resolvedStoreId = storeId ?? resolveDefaultStoreId(stores); | |
| 222 | + const resolvedStoreId = storeId ?? resolveDefaultStoreId(input.pluginRegistry, stores); | |
| 208 | 223 | if (resolvedStoreId === null) { |
| 209 | 224 | return yield* runtimeEffectError( |
| 210 | 225 | "local/secret-material-providers", |
| @@ -216,9 +231,9 @@ export const createDefaultSecretMaterialStorer = (input: { · 5 unmodified lines | ||
| 216 | 231 | executorState: input.executorState, |
| 217 | 232 | storeId: resolvedStoreId as SecretStore["id"], |
| 218 | 233 | }); |
| 219 | − const contribution = resolveStoreContribution(store); | |
| 234 | + const contribution = resolveStoreContribution(input.pluginRegistry, store); | |
| 220 | 235 | |
| 221 | − const capabilities = yield* loadStoreCapabilities(store); | |
| 236 | + const capabilities = yield* loadStoreCapabilities(input.pluginRegistry, store); | |
| 222 | 237 | if (!capabilities.canCreateSecrets) { |
| 223 | 238 | return yield* runtimeEffectError( |
| 224 | 239 | "local/secret-material-providers", |
| @@ -258,6 +273,7 @@ export const createDefaultSecretMaterialStorer = (input: { · 33 unmodified lines | ||
| 258 | 273 | |
| 259 | 274 | export const createDefaultSecretMaterialUpdater = (input: { |
| 260 | 275 | executorState: ExecutorStateStoreShape; |
| 276 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 261 | 277 | resolveSecretMaterial: ResolveSecretMaterial; |
| 262 | 278 | keychainServiceName?: string; |
| 263 | 279 | }): UpdateSecretMaterial => { |
| @@ -271,9 +287,9 @@ export const createDefaultSecretMaterialUpdater = (input: { · 7 unmodified lines | ||
| 271 | 287 | executorState: input.executorState, |
| 272 | 288 | storeId: material.storeId, |
| 273 | 289 | }); |
| 274 | − const contribution = resolveStoreContribution(store); | |
| 290 | + const contribution = resolveStoreContribution(input.pluginRegistry, store); | |
| 275 | 291 | |
| 276 | − const capabilities = yield* loadStoreCapabilities(store); | |
| 292 | + const capabilities = yield* loadStoreCapabilities(input.pluginRegistry, store); | |
| 277 | 293 | if (!capabilities.canUpdateSecrets) { |
| 278 | 294 | return yield* runtimeEffectError( |
| 279 | 295 | "local/secret-material-providers", |
| @@ -313,6 +329,7 @@ export const createDefaultSecretMaterialUpdater = (input: { · 33 unmodified lines | ||
| 313 | 329 | |
| 314 | 330 | export const createDefaultSecretMaterialDeleter = (input: { |
| 315 | 331 | executorState: ExecutorStateStoreShape; |
| 332 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 316 | 333 | resolveSecretMaterial: ResolveSecretMaterial; |
| 317 | 334 | keychainServiceName?: string; |
| 318 | 335 | }): DeleteSecretMaterial => { |
| @@ -326,8 +343,8 @@ export const createDefaultSecretMaterialDeleter = (input: { · 7 unmodified lines | ||
| 326 | 343 | executorState: input.executorState, |
| 327 | 344 | storeId: material.storeId, |
| 328 | 345 | }); |
| 329 | − const contribution = resolveStoreContribution(store); | |
| 330 | − const capabilities = yield* loadStoreCapabilities(store); | |
| 346 | + const contribution = resolveStoreContribution(input.pluginRegistry, store); | |
| 347 | + const capabilities = yield* loadStoreCapabilities(input.pluginRegistry, store); | |
| 331 | 348 | const deleted = capabilities.canDeleteSecrets |
| 332 | 349 | ? yield* provideSecretResolver( |
| 333 | 350 | contribution.deleteSecret({ |
| @@ -346,8 +363,10 @@ export const createDefaultSecretMaterialDeleter = (input: { · 12 unmodified lines | ||
| 346 | 363 | }); |
| 347 | 364 | }; |
| 348 | 365 | |
| 349 | −const listAvailableSecretStorePlugins = (): SecretProvider[] => { | |
| 350 | − return registeredSecretStoreContributions().map((entry) => ({ | |
| 366 | +const listAvailableSecretStorePlugins = ( | |
| 367 | + pluginRegistry: ExecutorSdkPluginRegistry, | |
| 368 | +): SecretProvider[] => { | |
| 369 | + return registeredSecretStoreContributions(pluginRegistry).map((entry) => ({ | |
| 351 | 370 | kind: entry.kind, |
| 352 | 371 | displayName: entry.displayName, |
| 353 | 372 | canCreate: entry.canCreate, |
| @@ -356,18 +375,20 @@ const listAvailableSecretStorePlugins = (): SecretProvider[] => { · 2 unmodified lines | ||
| 356 | 375 | |
| 357 | 376 | export const createLocalInstanceConfigResolver = (input: { |
| 358 | 377 | executorState: ExecutorStateStoreShape; |
| 378 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 359 | 379 | }): ResolveInstanceConfig => () => |
| 360 | 380 | Effect.gen(function* () { |
| 361 | 381 | const stores = yield* input.executorState.secretStores.listAll(); |
| 362 | 382 | return { |
| 363 | 383 | platform: process.platform, |
| 364 | − secretStorePlugins: listAvailableSecretStorePlugins(), | |
| 365 | − defaultSecretStoreId: resolveDefaultStoreId(stores), | |
| 384 | + secretStorePlugins: listAvailableSecretStorePlugins(input.pluginRegistry), | |
| 385 | + defaultSecretStoreId: resolveDefaultStoreId(input.pluginRegistry, stores), | |
| 366 | 386 | } satisfies InstanceConfig; |
| 367 | 387 | }); |
| 368 | 388 | |
| 369 | 389 | export const SecretMaterialResolverLive = (input: { |
| 370 | 390 | executorState: ExecutorStateStoreShape; |
| 391 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 371 | 392 | resolveSecretMaterial?: ResolveSecretMaterial; |
| 372 | 393 | keychainServiceName?: string; |
| 373 | 394 | }) => |
| @@ -380,6 +401,7 @@ export const SecretMaterialResolverLive = (input: { · 6 unmodified lines | ||
| 380 | 401 | |
| 381 | 402 | export const SecretMaterialStorerLive = (input: { |
| 382 | 403 | executorState: ExecutorStateStoreShape; |
| 404 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 383 | 405 | resolveSecretMaterial: ResolveSecretMaterial; |
| 384 | 406 | keychainServiceName?: string; |
| 385 | 407 | }) => |
| @@ -390,6 +412,7 @@ export const SecretMaterialStorerLive = (input: { · 4 unmodified lines | ||
| 390 | 412 | |
| 391 | 413 | export const SecretMaterialDeleterLive = (input: { |
| 392 | 414 | executorState: ExecutorStateStoreShape; |
| 415 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 393 | 416 | resolveSecretMaterial: ResolveSecretMaterial; |
| 394 | 417 | keychainServiceName?: string; |
| 395 | 418 | }) => |
| @@ -400,6 +423,7 @@ export const SecretMaterialDeleterLive = (input: { · 4 unmodified lines | ||
| 400 | 423 | |
| 401 | 424 | export const SecretMaterialUpdaterLive = (input: { |
| 402 | 425 | executorState: ExecutorStateStoreShape; |
| 426 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 403 | 427 | resolveSecretMaterial: ResolveSecretMaterial; |
| 404 | 428 | keychainServiceName?: string; |
| 405 | 429 | }) => |
| @@ -410,6 +434,7 @@ export const SecretMaterialUpdaterLive = (input: { · 4 unmodified lines | ||
| 410 | 434 | |
| 411 | 435 | export const SecretMaterialLive = (input: { |
| 412 | 436 | executorState: ExecutorStateStoreShape; |
| 437 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 413 | 438 | resolveSecretMaterial?: ResolveSecretMaterial; |
| 414 | 439 | keychainServiceName?: string; |
| 415 | 440 | }) => |
| @@ -421,8 +446,10 @@ export const SecretMaterialLive = (input: { · 5 unmodified lines | ||
| 421 | 446 | input.resolveSecretMaterial |
| 422 | 447 | ?? createDefaultSecretMaterialResolver({ |
| 423 | 448 | executorState: input.executorState, |
| 449 | + pluginRegistry: input.pluginRegistry, | |
| 424 | 450 | keychainServiceName: input.keychainServiceName, |
| 425 | 451 | }), |
| 452 | + pluginRegistry: input.pluginRegistry, | |
| 426 | 453 | keychainServiceName: input.keychainServiceName, |
| 427 | 454 | }), |
| 428 | 455 | SecretMaterialDeleterLive({ |
| @@ -431,8 +458,10 @@ export const SecretMaterialLive = (input: { · 2 unmodified lines | ||
| 431 | 458 | input.resolveSecretMaterial |
| 432 | 459 | ?? createDefaultSecretMaterialResolver({ |
| 433 | 460 | executorState: input.executorState, |
| 461 | + pluginRegistry: input.pluginRegistry, | |
| 434 | 462 | keychainServiceName: input.keychainServiceName, |
| 435 | 463 | }), |
| 464 | + pluginRegistry: input.pluginRegistry, | |
| 436 | 465 | keychainServiceName: input.keychainServiceName, |
| 437 | 466 | }), |
| 438 | 467 | SecretMaterialUpdaterLive({ |
| @@ -441,14 +470,17 @@ export const SecretMaterialLive = (input: { · 2 unmodified lines | ||
| 441 | 470 | input.resolveSecretMaterial |
| 442 | 471 | ?? createDefaultSecretMaterialResolver({ |
| 443 | 472 | executorState: input.executorState, |
| 473 | + pluginRegistry: input.pluginRegistry, | |
| 444 | 474 | keychainServiceName: input.keychainServiceName, |
| 445 | 475 | }), |
| 476 | + pluginRegistry: input.pluginRegistry, | |
| 446 | 477 | keychainServiceName: input.keychainServiceName, |
| 447 | 478 | }), |
| 448 | 479 | ); |
| 449 | 480 | |
| 450 | 481 | export const LocalInstanceConfigLive = (input: { |
| 451 | 482 | executorState: ExecutorStateStoreShape; |
| 483 | + pluginRegistry: ExecutorSdkPluginRegistry; | |
| 452 | 484 | }) => |
| 453 | 485 | Layer.succeed( |
| 454 | 486 | LocalInstanceConfigService, |
| @@ -18,6 +18,7 @@ import { · 17 unmodified lines | ||
| 18 | 18 | ControlPlaneStorageError, |
| 19 | 19 | } from "../errors"; |
| 20 | 20 | import { |
| 21 | + ExecutorPluginRegistryService, | |
| 21 | 22 | createManagedSecretStoreRecord, |
| 22 | 23 | getManagedSecretStore, |
| 23 | 24 | getSecretStoreContribution, |
| @@ -75,13 +76,15 @@ const asRecord = (value: unknown): Record<string, unknown> | null => · 51 unmodified lines | ||
| 75 | 76 | ? value as Record<string, unknown> |
| 76 | 77 | : null; |
| 77 | 78 | |
| 78 | −const getSecretStoreContributionOption = (kind: string) => { | |
| 79 | − try { | |
| 80 | − return Option.some(getSecretStoreContribution(kind)); | |
| 81 | − } catch { | |
| 82 | − return Option.none<typeof getSecretStoreContribution extends (kind: string) => infer T ? T : never>(); | |
| 83 | − } | |
| 84 | −}; | |
| 79 | +const getSecretStoreContributionOption = (kind: string) => | |
| 80 | + Effect.gen(function* () { | |
| 81 | + const pluginRegistry = yield* ExecutorPluginRegistryService; | |
| 82 | + try { | |
| 83 | + return Option.some(getSecretStoreContribution(pluginRegistry, kind)); | |
| 84 | + } catch { | |
| 85 | + return Option.none<typeof getSecretStoreContribution extends (registry: any, kind: string) => infer T ? T : never>(); | |
| 86 | + } | |
| 87 | + }); | |
| 85 | 88 | |
| 86 | 89 | const defaultCapabilities: SecretStoreCapabilities = { |
| 87 | 90 | canCreateSecrets: false, |
| @@ -95,7 +98,7 @@ const loadStoreCapabilities = ( · 7 unmodified lines | ||
| 95 | 98 | store: SecretStore, |
| 96 | 99 | )=> |
| 97 | 100 | Effect.gen(function* () { |
| 98 | − const contribution = getSecretStoreContributionOption(store.kind); | |
| 101 | + const contribution = yield* getSecretStoreContributionOption(store.kind); | |
| 99 | 102 | if (Option.isNone(contribution)) { |
| 100 | 103 | return defaultCapabilities; |
| 101 | 104 | } |
| @@ -183,8 +186,9 @@ export const createLocalSecretStore = (payload: CreateSecretStorePayload) => · 81 unmodified lines | ||
| 183 | 186 | ), |
| 184 | 187 | ), |
| 185 | 188 | ); |
| 189 | + const pluginRegistry = yield* ExecutorPluginRegistryService; | |
| 186 | 190 | const contribution = yield* Effect.try({ |
| 187 | − try: () => getSecretStoreContribution(payload.kind), | |
| 191 | + try: () => getSecretStoreContribution(pluginRegistry, payload.kind), | |
| 188 | 192 | catch: (cause) => |
| 189 | 193 | cause instanceof Error ? cause : new Error(String(cause)), |
| 190 | 194 | }).pipe( |
| @@ -254,7 +258,7 @@ export const updateLocalSecretStore = (input: { · 63 unmodified lines | ||
| 254 | 258 | ), |
| 255 | 259 | ); |
| 256 | 260 | |
| 257 | − const contribution = getSecretStoreContributionOption(store.kind); | |
| 261 | + const contribution = yield* getSecretStoreContributionOption(store.kind); | |
| 258 | 262 | const configRecord = input.payload.config === undefined |
| 259 | 263 | ? null |
| 260 | 264 | : asRecord(input.payload.config); |
| @@ -318,7 +322,7 @@ export const deleteLocalSecretStore = (storeId: string) => · 57 unmodified lines | ||
| 318 | 322 | }); |
| 319 | 323 | } |
| 320 | 324 | |
| 321 | − const contribution = getSecretStoreContributionOption(store.value.kind); | |
| 325 | + const contribution = yield* getSecretStoreContributionOption(store.value.kind); | |
| 322 | 326 | if (Option.isNone(contribution)) { |
| 323 | 327 | return yield* new ControlPlaneBadRequestError({ |
| 324 | 328 | operation: "secretStores.delete", |
| @@ -359,7 +363,7 @@ export const browseLocalSecretStore = (input: { · 34 unmodified lines | ||
| 359 | 363 | ), |
| 360 | 364 | ); |
| 361 | 365 | |
| 362 | − const contribution = getSecretStoreContributionOption(store.kind); | |
| 366 | + const contribution = yield* getSecretStoreContributionOption(store.kind); | |
| 363 | 367 | const capabilities = yield* loadStoreCapabilities(store); |
| 364 | 368 | if (Option.isNone(contribution) || !capabilities.canBrowseSecrets) { |
| 365 | 369 | return yield* new ControlPlaneBadRequestError({ |
| @@ -399,7 +403,7 @@ export const importLocalSecretFromStore = (input: { · 33 unmodified lines | ||
| 399 | 403 | ), |
| 400 | 404 | ); |
| 401 | 405 | |
| 402 | − const contribution = getSecretStoreContributionOption(store.kind); | |
| 406 | + const contribution = yield* getSecretStoreContributionOption(store.kind); | |
| 403 | 407 | if (Option.isNone(contribution)) { |
| 404 | 408 | return yield* new ControlPlaneBadRequestError({ |
| 405 | 409 | operation: "secretStores.import", |
| @@ -1402,7 +1402,7 @@ export const defineExecutorSecretStorePlugin = < · 1401 unmodified lines | ||
| 1402 | 1402 | ...pluginInput, |
| 1403 | 1403 | secretStore: createExecutorSecretStorePluginApi( |
| 1404 | 1404 | input.secretStore, |
| 1405 | − pluginInput.host as ExecutorSecretStorePluginInternalHost, | |
| 1405 | + pluginInput.host as unknown as ExecutorSecretStorePluginInternalHost, | |
| 1406 | 1406 | ), |
| 1407 | 1407 | }) |
| 1408 | 1408 | : undefined, |
| @@ -1412,7 +1412,7 @@ export const defineExecutorSecretStorePlugin = < · 3 unmodified lines | ||
| 1412 | 1412 | ...pluginInput, |
| 1413 | 1413 | secretStore: createExecutorSecretStorePluginApi( |
| 1414 | 1414 | input.secretStore, |
| 1415 | − pluginInput.host as ExecutorSecretStorePluginInternalHost, | |
| 1415 | + pluginInput.host as unknown as ExecutorSecretStorePluginInternalHost, | |
| 1416 | 1416 | ), |
| 1417 | 1417 | }) |
| 1418 | 1418 | : undefined, |
| @@ -156,7 +156,7 @@ export const createScopeToolInvoker = (input: { · 155 unmodified lines | ||
| 156 | 156 | return catalog; |
| 157 | 157 | }, |
| 158 | 158 | }); |
| 159 | − const hasLocalToolsSourceContribution = registeredSourceContributions() | |
| 159 | + const hasLocalToolsSourceContribution = registeredSourceContributions(input.pluginRegistry) | |
| 160 | 160 | .some((source) => source.kind === "local-tools"); |
| 161 | 161 | const authoredTools = mergeToolMaps([ |
| 162 | 162 | systemTools, |
| @@ -146,6 +146,8 @@ export type { · 145 unmodified lines | ||
| 146 | 146 | LocalToolRuntimeLoaderShape, |
| 147 | 147 | } from "./local-tool-runtime"; |
| 148 | 148 | export { |
| 149 | + createExecutorPluginRegistry, | |
| 150 | + emptyExecutorPluginRegistry, | |
| 149 | 151 | registeredSourceContributions, |
| 150 | 152 | registeredSecretStoreContributions, |
| 151 | 153 | registeredManagementToolContributions, |
| @@ -31,7 +31,6 @@ import type { · 30 unmodified lines | ||
| 31 | 31 | ExecutorSdkPluginRegistry, |
| 32 | 32 | } from "../plugins"; |
| 33 | 33 | |
| 34 | −const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const; | |
| 35 | 34 | const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const; |
| 36 | 35 | |
| 37 | 36 | export const LocalSourceArtifactSchema = Schema.Struct({ |
| @@ -45,15 +44,6 @@ export const LocalSourceArtifactSchema = Schema.Struct({ · 7 unmodified lines | ||
| 45 | 44 | |
| 46 | 45 | export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type; |
| 47 | 46 | |
| 48 | −const ReadableLegacyLocalSourceArtifactSchema = Schema.Struct({ | |
| 49 | − version: Schema.Literal(LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION), | |
| 50 | − sourceId: SourceIdSchema, | |
| 51 | − catalogId: SourceCatalogIdSchema, | |
| 52 | − generatedAt: TimestampMsSchema, | |
| 53 | − revision: StoredSourceCatalogRevisionRecordSchema, | |
| 54 | − snapshot: Schema.Unknown, | |
| 55 | −}); | |
| 56 | − | |
| 57 | 47 | const ReadableLocalSourceArtifactSchema = Schema.Struct({ |
| 58 | 48 | version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION), |
| 59 | 49 | sourceId: SourceIdSchema, |
| @@ -63,30 +53,15 @@ const ReadableLocalSourceArtifactSchema = Schema.Struct({ · 3 unmodified lines | ||
| 63 | 53 | snapshot: Schema.Unknown, |
| 64 | 54 | }); |
| 65 | 55 | |
| 66 | −type ReadableLegacyLocalSourceArtifact = | |
| 67 | − typeof ReadableLegacyLocalSourceArtifactSchema.Type; | |
| 68 | 56 | type ReadableLocalSourceArtifact = |
| 69 | 57 | typeof ReadableLocalSourceArtifactSchema.Type; |
| 70 | 58 | |
| 71 | 59 | const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption( |
| 72 | 60 | Schema.parseJson( |
| 73 | − Schema.Union( | |
| 74 | − ReadableLocalSourceArtifactSchema, | |
| 75 | − ReadableLegacyLocalSourceArtifactSchema, | |
| 76 | − ), | |
| 61 | + ReadableLocalSourceArtifactSchema, | |
| 77 | 62 | ), |
| 78 | 63 | ); |
| 79 | 64 | |
| 80 | −const normalizeLocalSourceArtifact = ( | |
| 81 | − artifact: ReadableLocalSourceArtifact | ReadableLegacyLocalSourceArtifact, | |
| 82 | −): Omit<LocalSourceArtifact, "snapshot"> & { snapshot: unknown } => | |
| 83 | − artifact.version === LOCAL_SOURCE_ARTIFACT_VERSION | |
| 84 | − ? artifact | |
| 85 | − : { | |
| 86 | − ...artifact, | |
| 87 | − version: LOCAL_SOURCE_ARTIFACT_VERSION, | |
| 88 | − }; | |
| 89 | − | |
| 90 | 65 | const mutableRecord = <K extends string, V>( |
| 91 | 66 | value: Readonly<Record<K, V>>, |
| 92 | 67 | ): Record<K, V> => value as Record<K, V>; |
| @@ -188,7 +163,7 @@ export const decodeStoredLocalSourceArtifact = ( · 95 unmodified lines | ||
| 188 | 163 | return null; |
| 189 | 164 | } |
| 190 | 165 | |
| 191 | − const artifact = normalizeLocalSourceArtifact(decodedArtifact.value); | |
| 166 | + const artifact = decodedArtifact.value; | |
| 192 | 167 | const snapshot = decodeCatalogSnapshotV1Option(artifact.snapshot); |
| 193 | 168 | if (snapshot === null) { |
| 194 | 169 | return null; |
| @@ -219,72 +219,4 @@ describe("executor internal tools", () => { · 218 unmodified lines | ||
| 219 | 219 | expect(stored.has((created as { id: string }).id)).toBe(false); |
| 220 | 220 | }).pipe(Effect.provide(NodeFileSystem.layer)), |
| 221 | 221 | ); |
| 222 | − | |
| 223 | − it.scoped("keeps executor.sources.add as a compatibility alias", () => | |
| 224 | − Effect.gen(function* () { | |
| 225 | − const fs = yield* FileSystem.FileSystem; | |
| 226 | − const workspaceRoot = yield* fs.makeTempDirectory({ | |
| 227 | − prefix: "executor-add-alias-", | |
| 228 | − }); | |
| 229 | − const specServer = yield* Effect.acquireRelease( | |
| 230 | − Effect.tryPromise({ | |
| 231 | − try: () => startStaticSpecServer(), | |
| 232 | − catch: (cause) => | |
| 233 | − cause instanceof Error ? cause : new Error(String(cause)), | |
| 234 | − }), | |
| 235 | − (server) => | |
| 236 | − Effect.promise(() => server.close()).pipe(Effect.orDie), | |
| 237 | − ); | |
| 238 | − const executor = yield* Effect.acquireRelease( | |
| 239 | − createLocalExecutorEffect({ | |
| 240 | − localDataDir: ":memory:", | |
| 241 | − workspaceRoot, | |
| 242 | − plugins: [ | |
| 243 | − openApiSdkPlugin({ | |
| 244 | − storage: { | |
| 245 | − get: () => Effect.succeed(null), | |
| 246 | − put: () => Effect.void, | |
| 247 | − remove: () => Effect.void, | |
| 248 | − }, | |
| 249 | − }), | |
| 250 | − ] as const, | |
| 251 | − }), | |
| 252 | − (openedExecutor) => | |
| 253 | − Effect.promise(() => openedExecutor.close()).pipe( | |
| 254 | − Effect.orDie, | |
| 255 | − Effect.zipRight( | |
| 256 | − fs.remove(workspaceRoot, { | |
| 257 | − recursive: true, | |
| 258 | − force: true, | |
| 259 | − }), | |
| 260 | − ), | |
| 261 | − ), | |
| 262 | − ); | |
| 263 | − | |
| 264 | − const environment = yield* resolveExecutionEnvironment(executor); | |
| 265 | − const aliasTool = yield* environment.catalog!.getToolByPath({ | |
| 266 | − path: "executor.sources.add", | |
| 267 | − includeSchemas: false, | |
| 268 | − }); | |
| 269 | − expect(aliasTool?.path).toBe("executor.sources.add"); | |
| 270 | − | |
| 271 | − const created = yield* environment.toolInvoker.invoke({ | |
| 272 | − path: "executor.sources.add", | |
| 273 | − args: { | |
| 274 | − kind: "openapi", | |
| 275 | − name: "Alias API", | |
| 276 | − specUrl: specServer.specUrl, | |
| 277 | − baseUrl: null, | |
| 278 | − auth: { | |
| 279 | − kind: "none", | |
| 280 | − }, | |
| 281 | − }, | |
| 282 | − }); | |
| 283 | − | |
| 284 | − expect(created).toMatchObject({ | |
| 285 | − kind: "openapi", | |
| 286 | − name: "Alias API", | |
| 287 | − }); | |
| 288 | − }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 289 | − ); | |
| 290 | 222 | }); |
| @@ -7,7 +7,6 @@ import { · 6 unmodified lines | ||
| 7 | 7 | } from "#schema"; |
| 8 | 8 | import * as Effect from "effect/Effect"; |
| 9 | 9 | import * as Layer from "effect/Layer"; |
| 10 | −import * as Option from "effect/Option"; | |
| 11 | 10 | import * as Schema from "effect/Schema"; |
| 12 | 11 | |
| 13 | 12 | import type { |
| @@ -22,7 +21,6 @@ import { · 8 unmodified lines | ||
| 22 | 21 | createManagedSourceRecord, |
| 23 | 22 | } from "../../sources/operations"; |
| 24 | 23 | import { |
| 25 | − deriveSchemaJson, | |
| 26 | 24 | deriveSchemaTypeSignature, |
| 27 | 25 | } from "../catalog/schema-type-signature"; |
| 28 | 26 | import { |
| @@ -75,54 +73,6 @@ type RegisteredSourceContribution = · 46 unmodified lines | ||
| 75 | 73 | type RegisteredManagementToolContribution = |
| 76 | 74 | ExecutorSdkPluginRegistry["managementTools"][number]; |
| 77 | 75 | |
| 78 | −const createExecutorSourcesAddSchema = ( | |
| 79 | − sources: ReadonlyArray<RegisteredSourceContribution>, | |
| 80 | −): Schema.Schema<any, any, never> => { | |
| 81 | − if (sources.length === 0) { | |
| 82 | − return Schema.Unknown; | |
| 83 | − } | |
| 84 | − | |
| 85 | − if (sources.length === 1) { | |
| 86 | − return sources[0]!.inputSchema; | |
| 87 | − } | |
| 88 | − | |
| 89 | − return Schema.Union( | |
| 90 | − ...(sources.map((source) => source.inputSchema) as [ | |
| 91 | − Schema.Schema<any, any, never>, | |
| 92 | − Schema.Schema<any, any, never>, | |
| 93 | − ...Array<Schema.Schema<any, any, never>>, | |
| 94 | − ]), | |
| 95 | − ); | |
| 96 | −}; | |
| 97 | − | |
| 98 | −export const getExecutorSourcesAddInputHint = ( | |
| 99 | − pluginRegistry: ExecutorSdkPluginRegistry, | |
| 100 | −): string => | |
| 101 | − deriveSchemaTypeSignature( | |
| 102 | − createExecutorSourcesAddSchema( | |
| 103 | − registeredSourceContributions(pluginRegistry), | |
| 104 | − ), | |
| 105 | − 340, | |
| 106 | − ); | |
| 107 | − | |
| 108 | −export const EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE = deriveSchemaTypeSignature( | |
| 109 | − SourceSchema, | |
| 110 | − 260, | |
| 111 | −); | |
| 112 | − | |
| 113 | −export const getExecutorSourcesAddInputSchemaJson = ( | |
| 114 | − pluginRegistry: ExecutorSdkPluginRegistry, | |
| 115 | −): Record<string, unknown> => | |
| 116 | − deriveSchemaJson( | |
| 117 | − createExecutorSourcesAddSchema( | |
| 118 | − registeredSourceContributions(pluginRegistry), | |
| 119 | − ), | |
| 120 | − ) ?? {}; | |
| 121 | − | |
| 122 | −export const EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA = deriveSchemaJson( | |
| 123 | − SourceSchema, | |
| 124 | −) ?? {}; | |
| 125 | − | |
| 126 | 76 | const coreSourceManagementHelpLines = (): readonly string[] => [ |
| 127 | 77 | "- executor.sources.list", |
| 128 | 78 | `- executor.sources.get: ${deriveSchemaTypeSignature(SourceIdInputSchema, 180)}`, |
| @@ -147,53 +97,9 @@ export const getExecutorInternalToolHelpLines = ( · 18 unmodified lines | ||
| 147 | 97 | `- ${tool.path}: ${deriveSchemaTypeSignature(tool.inputSchema, 260)}` |
| 148 | 98 | ), |
| 149 | 99 | ]), |
| 150 | − ...(sources.length === 0 | |
| 151 | − ? [] | |
| 152 | − : [ | |
| 153 | − "Legacy compatibility tool:", | |
| 154 | − `- executor.sources.add: ${deriveSchemaTypeSignature( | |
| 155 | − createExecutorSourcesAddSchema(sources), | |
| 156 | − 340, | |
| 157 | − )}`, | |
| 158 | − ]), | |
| 159 | − ]; | |
| 160 | −}; | |
| 161 | − | |
| 162 | −export const getExecutorSourcesAddHelpLines = ( | |
| 163 | − pluginRegistry: ExecutorSdkPluginRegistry, | |
| 164 | −): readonly string[] => { | |
| 165 | − const sources = registeredSourceContributions(pluginRegistry); | |
| 166 | − if (sources.length === 0) { | |
| 167 | − return ["No source plugins are registered in this build."] as const; | |
| 168 | − } | |
| 169 | − | |
| 170 | − return [ | |
| 171 | − "Source add input shapes:", | |
| 172 | − ...sources.flatMap((source) => [ | |
| 173 | − `- ${source.displayName}: ${deriveSchemaTypeSignature( | |
| 174 | − source.inputSchema, | |
| 175 | − source.inputSignatureWidth ?? 260, | |
| 176 | − )}`, | |
| 177 | − ...(source.helpText ?? []).map((line) => ` ${line}`), | |
| 178 | − ]), | |
| 179 | 100 | ]; |
| 180 | 101 | }; |
| 181 | 102 | |
| 182 | −export const buildExecutorSourcesAddDescription = ( | |
| 183 | − pluginRegistry: ExecutorSdkPluginRegistry, | |
| 184 | −): string => { | |
| 185 | − const sources = registeredSourceContributions(pluginRegistry); | |
| 186 | − if (sources.length === 0) { | |
| 187 | − return "No source plugins are registered in this build."; | |
| 188 | − } | |
| 189 | − | |
| 190 | − return [ | |
| 191 | − "Legacy compatibility alias for creating a source.", | |
| 192 | − "Prefer plugin-specific tools such as executor.openapi.createSource or executor.mcp.createSource.", | |
| 193 | − ...getExecutorSourcesAddHelpLines(pluginRegistry), | |
| 194 | − ].join("\n"); | |
| 195 | −}; | |
| 196 | − | |
| 197 | 103 | const createSourceConnectorHost = (input: { |
| 198 | 104 | scopeId: ScopeId; |
| 199 | 105 | actorScopeId: ScopeId; |
| @@ -297,28 +203,6 @@ const runExecutorToolEffect = async <A>( · 97 unmodified lines | ||
| 297 | 203 | ); |
| 298 | 204 | }; |
| 299 | 205 | |
| 300 | −const resolveSourceContribution = ( | |
| 301 | − sources: ReadonlyArray<RegisteredSourceContribution>, | |
| 302 | − args: unknown, | |
| 303 | −): | |
| 304 | − | { | |
| 305 | − source: RegisteredSourceContribution; | |
| 306 | − parsedArgs: unknown; | |
| 307 | − } | |
| 308 | − | null => { | |
| 309 | − for (const source of sources) { | |
| 310 | − const parsed = Schema.decodeUnknownOption(source.inputSchema)(args); | |
| 311 | − if (Option.isSome(parsed)) { | |
| 312 | − return { | |
| 313 | − source, | |
| 314 | − parsedArgs: parsed.value, | |
| 315 | − }; | |
| 316 | − } | |
| 317 | − } | |
| 318 | − | |
| 319 | − return null; | |
| 320 | −}; | |
| 321 | − | |
| 322 | 206 | const toSerializableValue = <A>(value: A): A => |
| 323 | 207 | JSON.parse(JSON.stringify(value)) as A; |
| 324 | 208 | |
| @@ -549,36 +433,6 @@ export const createExecutorToolMap = (input: { · 224 unmodified lines | ||
| 549 | 433 | }), |
| 550 | 434 | }; |
| 551 | 435 | |
| 552 | − if (sources.length > 0) { | |
| 553 | − coreTools["executor.sources.add"] = toTool({ | |
| 554 | − tool: { | |
| 555 | − description: buildExecutorSourcesAddDescription(input.pluginRegistry), | |
| 556 | − inputSchema: Schema.standardSchemaV1( | |
| 557 | − createExecutorSourcesAddSchema(sources), | |
| 558 | − ), | |
| 559 | − outputSchema: Schema.standardSchemaV1(SourceSchema), | |
| 560 | − execute: async (args: unknown): Promise<Source> => { | |
| 561 | − const matched = resolveSourceContribution(sources, args); | |
| 562 | − if (matched === null) { | |
| 563 | − throw new Error( | |
| 564 | − "executor.sources.add input did not match a registered source plugin.", | |
| 565 | − ); | |
| 566 | − } | |
| 567 | − | |
| 568 | − const createdSource = await runExecutorToolEffect( | |
| 569 | − matched.source.createSource({ | |
| 570 | − args: matched.parsedArgs, | |
| 571 | − host, | |
| 572 | − }), | |
| 573 | − runtime, | |
| 574 | − ); | |
| 575 | − | |
| 576 | − return toSerializableValue(createdSource); | |
| 577 | − }, | |
| 578 | − }, | |
| 579 | − }); | |
| 580 | − } | |
| 581 | − | |
| 582 | 436 | return { |
| 583 | 437 | ...coreTools, |
| 584 | 438 | ...pluginToolMap, |
| @@ -1,23 +1,12 @@ | ||
| 1 | 1 | import type { |
| 2 | − LocalConfigSecretInput, | |
| 3 | 2 | LocalConfigSource, |
| 4 | − LocalExecutorConfig, | |
| 5 | − SecretRef, | |
| 6 | 3 | Source, |
| 7 | 4 | } from "#schema"; |
| 8 | −import { | |
| 9 | − LocalConfigSecretInputSchema, | |
| 10 | −} from "#schema"; | |
| 11 | −import * as Option from "effect/Option"; | |
| 12 | 5 | import * as Schema from "effect/Schema"; |
| 13 | 6 | |
| 14 | −import { | |
| 15 | − toConfigSecretProviderId, | |
| 16 | −} from "../../scope/config-secrets"; | |
| 17 | 7 | import { |
| 18 | 8 | cloneJson, |
| 19 | 9 | configSourceBaseFromLocalSource, |
| 20 | − trimOrNull, | |
| 21 | 10 | } from "./config"; |
| 22 | 11 | |
| 23 | 12 | export const createPluginLocalConfigEntrySchema = < |
| @@ -49,75 +38,3 @@ export const pluginLocalConfigSourceFromConfig = <TConfig>(input: { · 25 unmodified lines | ||
| 49 | 38 | kind: input.source.kind as LocalConfigSource["kind"], |
| 50 | 39 | config: cloneJson(input.config), |
| 51 | 40 | }); |
| 52 | − | |
| 53 | −const decodeLocalConfigSecretInputOption = Schema.decodeUnknownOption( | |
| 54 | − LocalConfigSecretInputSchema, | |
| 55 | −); | |
| 56 | − | |
| 57 | −const resolveDefaultConfigSecretProviderAlias = ( | |
| 58 | − config: LocalExecutorConfig | null | undefined, | |
| 59 | −): string | null => { | |
| 60 | − const defaultAlias = trimOrNull(config?.secrets?.defaults?.env); | |
| 61 | − if (defaultAlias !== null && config?.secrets?.providers?.[defaultAlias]) { | |
| 62 | − return defaultAlias; | |
| 63 | − } | |
| 64 | − | |
| 65 | − return config?.secrets?.providers?.default ? "default" : null; | |
| 66 | −}; | |
| 67 | − | |
| 68 | −export const secretRefFromLocalConfigSecretInput = (input: { | |
| 69 | − auth: LocalConfigSecretInput; | |
| 70 | − loadedConfig: LocalExecutorConfig | null; | |
| 71 | −}): SecretRef => { | |
| 72 | − if (typeof input.auth === "string") { | |
| 73 | − const providerAlias = resolveDefaultConfigSecretProviderAlias( | |
| 74 | − input.loadedConfig, | |
| 75 | − ); | |
| 76 | − | |
| 77 | − return { | |
| 78 | − providerId: providerAlias ? toConfigSecretProviderId(providerAlias) : "env", | |
| 79 | − handle: input.auth, | |
| 80 | − }; | |
| 81 | − } | |
| 82 | − | |
| 83 | − return { | |
| 84 | − providerId: | |
| 85 | − input.auth.provider === "params" | |
| 86 | − ? "params" | |
| 87 | − : input.auth.provider | |
| 88 | − ? toConfigSecretProviderId(input.auth.provider) | |
| 89 | − : input.auth.source === "params" | |
| 90 | − ? "params" | |
| 91 | − : "env", | |
| 92 | − handle: input.auth.id, | |
| 93 | − }; | |
| 94 | −}; | |
| 95 | − | |
| 96 | −export const decodeCurrentOrLegacyLocalConfigAuth = <TAuth>(input: { | |
| 97 | − auth: unknown; | |
| 98 | − authSchema: Schema.Schema<TAuth, any, never>; | |
| 99 | − loadedConfig: LocalExecutorConfig | null; | |
| 100 | − onLegacySecretRef: (ref: SecretRef) => TAuth; | |
| 101 | − fallback: TAuth; | |
| 102 | −}): TAuth => { | |
| 103 | − if (input.auth === undefined) { | |
| 104 | − return input.fallback; | |
| 105 | − } | |
| 106 | − | |
| 107 | − const current = Schema.decodeUnknownOption(input.authSchema)(input.auth); | |
| 108 | − if (Option.isSome(current)) { | |
| 109 | − return current.value; | |
| 110 | − } | |
| 111 | − | |
| 112 | − const legacy = decodeLocalConfigSecretInputOption(input.auth); | |
| 113 | − if (Option.isSome(legacy)) { | |
| 114 | − return input.onLegacySecretRef( | |
| 115 | − secretRefFromLocalConfigSecretInput({ | |
| 116 | − auth: legacy.value, | |
| 117 | − loadedConfig: input.loadedConfig, | |
| 118 | − }), | |
| 119 | − ); | |
| 120 | − } | |
| 121 | − | |
| 122 | − throw new Error("Unsupported local source auth configuration."); | |
| 123 | −}; | |
| @@ -264,7 +264,7 @@ const writeConfiguredLocalMcpSource = (input: { · 263 unmodified lines | ||
| 264 | 264 | }); |
| 265 | 265 | }).pipe(Effect.provide(NodeFileSystem.layer)); |
| 266 | 266 | |
| 267 | −const writeLegacyConfiguredWorkspaceSources = (input: { | |
| 267 | +const writeConfiguredWorkspaceSources = (input: { | |
| 268 | 268 | workspaceRoot: string; |
| 269 | 269 | sources: Record<string, LocalConfigSource>; |
| 270 | 270 | }) => |
| @@ -288,8 +288,8 @@ const writeLegacyConfiguredWorkspaceSources = (input: { · 17 unmodified lines | ||
| 288 | 288 | }).pipe(Effect.provide(NodeFileSystem.layer)); |
| 289 | 289 | |
| 290 | 290 | const writeMissingSourceCatalogArtifact = (input: { |
| 291 | − context: Effect.Effect.Success<ReturnType<typeof writeLegacyConfiguredWorkspaceSources>>["context"]; | |
| 292 | − installation: Effect.Effect.Success<ReturnType<typeof writeLegacyConfiguredWorkspaceSources>>["installation"]; | |
| 291 | + context: Effect.Effect.Success<ReturnType<typeof writeConfiguredWorkspaceSources>>["context"]; | |
| 292 | + installation: Effect.Effect.Success<ReturnType<typeof writeConfiguredWorkspaceSources>>["installation"]; | |
| 293 | 293 | source: { |
| 294 | 294 | id: string; |
| 295 | 295 | name: string; |
| @@ -1632,7 +1632,7 @@ describe("local-executor-server", () => { · 1336 unmodified lines | ||
| 1632 | 1632 | 15_000, |
| 1633 | 1633 | ); |
| 1634 | 1634 | |
| 1635 | − it.scoped("adds an OpenAPI source via executor.sources.add and calls it end to end", () => | |
| 1635 | + it.scoped("adds an OpenAPI source via executor.openapi.createSource and calls it end to end", () => | |
| 1636 | 1636 | Effect.gen(function* () { |
| 1637 | 1637 | const openApiServer = yield* Effect.acquireRelease( |
| 1638 | 1638 | Effect.promise(() => startOpenApiDemoServer()), |
| @@ -1645,8 +1645,7 @@ describe("local-executor-server", () => { · 6 unmodified lines | ||
| 1645 | 1645 | workspaceId: installation.scopeId, |
| 1646 | 1646 | }, |
| 1647 | 1647 | payload: { |
| 1648 | − code: `return await tools.executor.sources.add({ | |
| 1649 | − kind: "openapi", | |
| 1648 | + code: `return await tools.executor.openapi.createSource({ | |
| 1650 | 1649 | name: "GitHub", |
| 1651 | 1650 | specUrl: ${JSON.stringify(openApiServer.specUrl)}, |
| 1652 | 1651 | baseUrl: ${JSON.stringify(openApiServer.baseUrl)}, |
| @@ -1831,16 +1830,14 @@ describe("local-executor-server", () => { · 178 unmodified lines | ||
| 1831 | 1830 | (server) => Effect.promise(() => server.close()).pipe(Effect.orDie), |
| 1832 | 1831 | ); |
| 1833 | 1832 | |
| 1834 | − const workspace = yield* writeLegacyConfiguredWorkspaceSources({ | |
| 1833 | + const workspace = yield* writeConfiguredWorkspaceSources({ | |
| 1835 | 1834 | workspaceRoot, |
| 1836 | 1835 | sources: { |
| 1837 | 1836 | deepwiki: { |
| 1838 | 1837 | kind: "mcp", |
| 1839 | 1838 | name: "DeepWiki MCP", |
| 1840 | − connection: { | |
| 1839 | + config: { | |
| 1841 | 1840 | endpoint: demoServer.endpoint, |
| 1842 | − }, | |
| 1843 | − binding: { | |
| 1844 | 1841 | transport: "streamable-http", |
| 1845 | 1842 | queryParams: null, |
| 1846 | 1843 | headers: null, |
| @@ -1848,16 +1845,20 @@ describe("local-executor-server", () => { · 1 unmodified line | ||
| 1848 | 1845 | args: null, |
| 1849 | 1846 | env: null, |
| 1850 | 1847 | cwd: null, |
| 1848 | + auth: { | |
| 1849 | + kind: "none", | |
| 1850 | + }, | |
| 1851 | 1851 | }, |
| 1852 | 1852 | }, |
| 1853 | 1853 | vercel: { |
| 1854 | 1854 | kind: "openapi", |
| 1855 | 1855 | name: "Vercel API", |
| 1856 | − connection: { | |
| 1857 | − endpoint: openApiServer.baseUrl, | |
| 1858 | − }, | |
| 1859 | − binding: { | |
| 1856 | + config: { | |
| 1860 | 1857 | specUrl: openApiServer.specUrl, |
| 1858 | + baseUrl: openApiServer.baseUrl, | |
| 1859 | + auth: { | |
| 1860 | + kind: "none", | |
| 1861 | + }, | |
| 1861 | 1862 | defaultHeaders: null, |
| 1862 | 1863 | }, |
| 1863 | 1864 | }, |
| @@ -18,7 +18,6 @@ import { · 17 unmodified lines | ||
| 18 | 18 | } from "@executor/platform-sdk/plugins"; |
| 19 | 19 | import { |
| 20 | 20 | createPluginLocalConfigEntrySchema, |
| 21 | − decodeCurrentOrLegacyLocalConfigAuth, | |
| 22 | 21 | pluginLocalConfigSourceFromConfig, |
| 23 | 22 | SecretMaterialDeleterService, |
| 24 | 23 | SecretMaterialResolverService, |
| @@ -103,29 +102,6 @@ const decodeGoogleDiscoveryCurrentLocalConfigOption = Schema.decodeUnknownOption · 78 unmodified lines | ||
| 103 | 102 | config: GoogleDiscoveryStoredSourceDataSchema, |
| 104 | 103 | }), |
| 105 | 104 | ); |
| 106 | −const decodeGoogleDiscoveryLegacyLocalConfigOption = Schema.decodeUnknownOption( | |
| 107 | − Schema.Struct({ | |
| 108 | − kind: Schema.Literal(GOOGLE_DISCOVERY_SOURCE_KIND), | |
| 109 | − name: Schema.optional(Schema.String), | |
| 110 | − namespace: Schema.optional(Schema.String), | |
| 111 | − enabled: Schema.optional(Schema.Boolean), | |
| 112 | − connection: Schema.Struct({ | |
| 113 | − endpoint: Schema.optional(Schema.NullOr(Schema.String)), | |
| 114 | − auth: Schema.optional(Schema.Unknown), | |
| 115 | − }), | |
| 116 | − binding: Schema.Struct({ | |
| 117 | − service: Schema.String, | |
| 118 | − version: Schema.String, | |
| 119 | − discoveryUrl: Schema.optional(Schema.NullOr(Schema.String)), | |
| 120 | − defaultHeaders: Schema.optional( | |
| 121 | − Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String })), | |
| 122 | − ), | |
| 123 | − scopes: Schema.optional(Schema.Array(Schema.String)), | |
| 124 | − }), | |
| 125 | − config: Schema.optional(Schema.Unknown), | |
| 126 | − }), | |
| 127 | −); | |
| 128 | − | |
| 129 | 105 | export type GoogleDiscoverySourceStorage = { |
| 130 | 106 | get: (input: { |
| 131 | 107 | scopeId: string; |
| @@ -582,32 +558,6 @@ const googleDiscoveryStoredSourceDataFromLocalConfig = (input: { · 450 unmodified lines | ||
| 582 | 558 | return current.value.config; |
| 583 | 559 | } |
| 584 | 560 | |
| 585 | − const legacy = decodeGoogleDiscoveryLegacyLocalConfigOption(input.config); | |
| 586 | − if (Option.isSome(legacy)) { | |
| 587 | − return storedSourceDataFromInput({ | |
| 588 | − name: | |
| 589 | − legacy.value.name | |
| 590 | − ?? `${legacy.value.binding.service} ${legacy.value.binding.version}`, | |
| 591 | − service: legacy.value.binding.service, | |
| 592 | − version: legacy.value.binding.version, | |
| 593 | − discoveryUrl: legacy.value.binding.discoveryUrl ?? null, | |
| 594 | − defaultHeaders: legacy.value.binding.defaultHeaders ?? null, | |
| 595 | − scopes: legacy.value.binding.scopes ?? [], | |
| 596 | − auth: decodeCurrentOrLegacyLocalConfigAuth({ | |
| 597 | − auth: legacy.value.connection.auth, | |
| 598 | − authSchema: GoogleDiscoveryConnectionAuthSchema, | |
| 599 | − loadedConfig: input.loadedConfig, | |
| 600 | − onLegacySecretRef: (tokenSecretRef) => ({ | |
| 601 | − kind: "bearer", | |
| 602 | − tokenSecretRef, | |
| 603 | − }), | |
| 604 | − fallback: { | |
| 605 | − kind: "none", | |
| 606 | − }, | |
| 607 | − }), | |
| 608 | − }); | |
| 609 | − } | |
| 610 | − | |
| 611 | 561 | throw new Error("Unsupported Google Discovery local source config."); |
| 612 | 562 | }; |
| 613 | 563 | |
| @@ -24,7 +24,6 @@ import { · 23 unmodified lines | ||
| 24 | 24 | import { |
| 25 | 25 | SecretMaterialResolverService, |
| 26 | 26 | createPluginLocalConfigEntrySchema, |
| 27 | − decodeCurrentOrLegacyLocalConfigAuth, | |
| 28 | 27 | pluginLocalConfigSourceFromConfig, |
| 29 | 28 | provideExecutorRuntime, |
| 30 | 29 | runtimeEffectError, |
| @@ -158,25 +157,6 @@ const decodeGraphqlCurrentLocalConfigOption = Schema.decodeUnknownOption( · 127 unmodified lines | ||
| 158 | 157 | }), |
| 159 | 158 | ); |
| 160 | 159 | |
| 161 | −const decodeGraphqlLegacyLocalConfigOption = Schema.decodeUnknownOption( | |
| 162 | − Schema.Struct({ | |
| 163 | − kind: Schema.Literal("graphql"), | |
| 164 | − name: Schema.optional(Schema.String), | |
| 165 | − namespace: Schema.optional(Schema.String), | |
| 166 | − enabled: Schema.optional(Schema.Boolean), | |
| 167 | − connection: Schema.Struct({ | |
| 168 | − endpoint: Schema.String, | |
| 169 | − auth: Schema.optional(Schema.Unknown), | |
| 170 | − }), | |
| 171 | − binding: Schema.Struct({ | |
| 172 | − defaultHeaders: Schema.optional( | |
| 173 | − Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String })), | |
| 174 | − ), | |
| 175 | − }), | |
| 176 | − config: Schema.optional(Schema.Unknown), | |
| 177 | − }), | |
| 178 | −); | |
| 179 | − | |
| 180 | 160 | const withoutUndefinedEntries = ( |
| 181 | 161 | record: Readonly<Record<string, unknown>>, |
| 182 | 162 | ): Record<string, unknown> => |
| @@ -363,29 +343,6 @@ const graphqlStoredSourceDataFromLocalConfig = (input: { · 180 unmodified lines | ||
| 363 | 343 | return normalizeStoredSourceData(current.value.config); |
| 364 | 344 | } |
| 365 | 345 | |
| 366 | − const legacy = decodeGraphqlLegacyLocalConfigOption(input.config); | |
| 367 | − if (Option.isSome(legacy)) { | |
| 368 | − return storedSourceDataFromInput({ | |
| 369 | − name: legacy.value.name ?? "GraphQL", | |
| 370 | − endpoint: legacy.value.connection.endpoint, | |
| 371 | − defaultHeaders: legacy.value.binding.defaultHeaders ?? null, | |
| 372 | − auth: decodeCurrentOrLegacyLocalConfigAuth({ | |
| 373 | − auth: legacy.value.connection.auth, | |
| 374 | − authSchema: GraphqlConnectionAuthSchema, | |
| 375 | − loadedConfig: input.loadedConfig, | |
| 376 | − onLegacySecretRef: (tokenSecretRef) => ({ | |
| 377 | − kind: "bearer", | |
| 378 | − tokenSecretRef, | |
| 379 | − headerName: null, | |
| 380 | − prefix: null, | |
| 381 | − }), | |
| 382 | − fallback: { | |
| 383 | − kind: "none", | |
| 384 | − }, | |
| 385 | − }), | |
| 386 | − }); | |
| 387 | − } | |
| 388 | − | |
| 389 | 346 | throw new Error("Unsupported GraphQL local source config."); |
| 390 | 347 | }; |
| 391 | 348 | |
| @@ -249,37 +249,12 @@ const normalizeStringArray = ( · 248 unmodified lines | ||
| 249 | 249 | return normalized.length > 0 ? normalized : null; |
| 250 | 250 | }; |
| 251 | 251 | |
| 252 | −const decodeMcpAuthOption = Schema.decodeUnknownOption(McpConnectionAuthSchema); | |
| 253 | 252 | const decodeMcpCurrentLocalConfigOption = Schema.decodeUnknownOption( |
| 254 | 253 | createPluginLocalConfigEntrySchema({ |
| 255 | 254 | kind: "mcp", |
| 256 | 255 | config: McpStoredSourceDataSchema, |
| 257 | 256 | }), |
| 258 | 257 | ); |
| 259 | −const decodeMcpLegacyLocalConfigOption = Schema.decodeUnknownOption( | |
| 260 | − Schema.Struct({ | |
| 261 | − kind: Schema.Literal("mcp"), | |
| 262 | − name: Schema.optional(Schema.String), | |
| 263 | − namespace: Schema.optional(Schema.String), | |
| 264 | − enabled: Schema.optional(Schema.Boolean), | |
| 265 | − connection: Schema.optional( | |
| 266 | − Schema.Struct({ | |
| 267 | − endpoint: Schema.optional(Schema.NullOr(Schema.String)), | |
| 268 | − auth: Schema.optional(Schema.Unknown), | |
| 269 | − }), | |
| 270 | − ), | |
| 271 | − binding: Schema.Struct({ | |
| 272 | − transport: Schema.optional(Schema.NullOr(SourceTransportSchema)), | |
| 273 | − queryParams: Schema.optional(Schema.NullOr(StringMapSchema)), | |
| 274 | − headers: Schema.optional(Schema.NullOr(StringMapSchema)), | |
| 275 | − command: Schema.optional(Schema.NullOr(Schema.String)), | |
| 276 | − args: Schema.optional(Schema.NullOr(StringArraySchema)), | |
| 277 | − env: Schema.optional(Schema.NullOr(StringMapSchema)), | |
| 278 | − cwd: Schema.optional(Schema.NullOr(Schema.String)), | |
| 279 | − }), | |
| 280 | − config: Schema.optional(Schema.Unknown), | |
| 281 | − }), | |
| 282 | −); | |
| 283 | 258 | |
| 284 | 259 | const normalizeStoredSourceData = ( |
| 285 | 260 | input: McpConnectInput | McpSourceConfigPayload, |
| @@ -399,37 +374,6 @@ const mcpStoredSourceDataFromLocalConfig = ( · 113 unmodified lines | ||
| 399 | 374 | return Effect.succeed(current.value.config); |
| 400 | 375 | } |
| 401 | 376 | |
| 402 | − const legacy = decodeMcpLegacyLocalConfigOption(config); | |
| 403 | − if (Option.isSome(legacy)) { | |
| 404 | − const auth = decodeMcpAuthOption(legacy.value.connection?.auth); | |
| 405 | − if ( | |
| 406 | − legacy.value.connection?.auth !== undefined && | |
| 407 | − Option.isNone(auth) | |
| 408 | − ) { | |
| 409 | − return runtimeEffectError( | |
| 410 | − "plugins/mcp/sdk", | |
| 411 | − "Unsupported MCP auth configuration in local source config.", | |
| 412 | − ); | |
| 413 | − } | |
| 414 | − | |
| 415 | − return normalizeStoredSourceData({ | |
| 416 | − name: legacy.value.name ?? "MCP", | |
| 417 | − endpoint: legacy.value.connection?.endpoint ?? null, | |
| 418 | − transport: legacy.value.binding.transport ?? null, | |
| 419 | − queryParams: legacy.value.binding.queryParams ?? null, | |
| 420 | − headers: legacy.value.binding.headers ?? null, | |
| 421 | − command: legacy.value.binding.command ?? null, | |
| 422 | − args: legacy.value.binding.args ?? null, | |
| 423 | − env: legacy.value.binding.env ?? null, | |
| 424 | − cwd: legacy.value.binding.cwd ?? null, | |
| 425 | − auth: Option.isSome(auth) | |
| 426 | − ? auth.value | |
| 427 | − : { | |
| 428 | − kind: "none", | |
| 429 | − }, | |
| 430 | − }); | |
| 431 | − } | |
| 432 | − | |
| 433 | 377 | return runtimeEffectError( |
| 434 | 378 | "plugins/mcp/sdk", |
| 435 | 379 | "Unsupported MCP local source config.", |
| @@ -500,7 +500,7 @@ describe("codemode-mcp", () => { · 499 unmodified lines | ||
| 500 | 500 | action: "accept" as const, |
| 501 | 501 | }; |
| 502 | 502 | }), |
| 503 | − path: "executor.sources.add" as ToolPath, | |
| 503 | + path: "executor.mcp.createSource" as ToolPath, | |
| 504 | 504 | sourceKey: "executor", |
| 505 | 505 | args: { |
| 506 | 506 | endpoint: "https://example.com/mcp", |
| @@ -19,7 +19,6 @@ import { · 18 unmodified lines | ||
| 19 | 19 | } from "@executor/platform-sdk/plugins"; |
| 20 | 20 | import { |
| 21 | 21 | createPluginLocalConfigEntrySchema, |
| 22 | − decodeCurrentOrLegacyLocalConfigAuth, | |
| 23 | 22 | pluginLocalConfigSourceFromConfig, |
| 24 | 23 | runtimeEffectError, |
| 25 | 24 | SecretMaterialResolverService, |
| @@ -163,26 +162,6 @@ const decodeOpenApiCurrentLocalConfigOption = Schema.decodeUnknownOption( · 137 unmodified lines | ||
| 163 | 162 | }), |
| 164 | 163 | ); |
| 165 | 164 | |
| 166 | −const decodeOpenApiLegacyLocalConfigOption = Schema.decodeUnknownOption( | |
| 167 | − Schema.Struct({ | |
| 168 | − kind: Schema.Literal("openapi"), | |
| 169 | − name: Schema.optional(Schema.String), | |
| 170 | − namespace: Schema.optional(Schema.String), | |
| 171 | − enabled: Schema.optional(Schema.Boolean), | |
| 172 | − connection: Schema.Struct({ | |
| 173 | − endpoint: Schema.optional(Schema.NullOr(Schema.String)), | |
| 174 | − auth: Schema.optional(Schema.Unknown), | |
| 175 | − }), | |
| 176 | − binding: Schema.Struct({ | |
| 177 | − specUrl: Schema.String, | |
| 178 | − defaultHeaders: Schema.optional( | |
| 179 | − Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String })), | |
| 180 | − ), | |
| 181 | − }), | |
| 182 | − config: Schema.optional(Schema.Unknown), | |
| 183 | − }), | |
| 184 | −); | |
| 185 | − | |
| 186 | 165 | const createStoredSourceData = ( |
| 187 | 166 | input: OpenApiConnectInput, |
| 188 | 167 | ): OpenApiStoredSourceData => ({ |
| @@ -224,31 +203,6 @@ const openApiStoredSourceDataFromLocalConfig = (input: { · 35 unmodified lines | ||
| 224 | 203 | }); |
| 225 | 204 | } |
| 226 | 205 | |
| 227 | − const legacy = decodeOpenApiLegacyLocalConfigOption(input.config); | |
| 228 | − if (Option.isSome(legacy)) { | |
| 229 | − return normalizeStoredSourceData({ | |
| 230 | − specUrl: legacy.value.binding.specUrl.trim(), | |
| 231 | − baseUrl: legacy.value.connection.endpoint?.trim() || null, | |
| 232 | − auth: decodeCurrentOrLegacyLocalConfigAuth({ | |
| 233 | − auth: legacy.value.connection.auth, | |
| 234 | − authSchema: OpenApiConnectionAuthSchema, | |
| 235 | − loadedConfig: input.loadedConfig, | |
| 236 | − onLegacySecretRef: (tokenSecretRef) => ({ | |
| 237 | − kind: "bearer", | |
| 238 | − tokenSecretRef, | |
| 239 | − headerName: null, | |
| 240 | − prefix: null, | |
| 241 | − }), | |
| 242 | − fallback: { | |
| 243 | − kind: "none", | |
| 244 | − }, | |
| 245 | − }), | |
| 246 | − defaultHeaders: legacy.value.binding.defaultHeaders ?? null, | |
| 247 | − etag: null, | |
| 248 | − lastSyncAt: null, | |
| 249 | − }); | |
| 250 | − } | |
| 251 | − | |
| 252 | 206 | throw new Error("Unsupported OpenAPI local source config."); |
| 253 | 207 | }; |
| 254 | 208 | |