Showing the changes to packages/platform (2 of 4 files). Show all
Unified · Split
| @@ -0,0 +1,127 @@ | ||
| 1 | +import { | |
| 2 | + describe, | |
| 3 | + expect, | |
| 4 | + it, | |
| 5 | +} from "@effect/vitest"; | |
| 6 | +import * as Effect from "effect/Effect"; | |
| 7 | + | |
| 8 | +import { | |
| 9 | + createScopeSourceCatalog, | |
| 10 | +} from "./source-catalog"; | |
| 11 | + | |
| 12 | +const makeLoadedCatalog = (input: { | |
| 13 | + sourceId: string; | |
| 14 | + toolPaths: ReadonlyArray<string>; | |
| 15 | + enabled?: boolean; | |
| 16 | + status?: "connected" | "error"; | |
| 17 | +}) => ({ | |
| 18 | + source: { | |
| 19 | + id: input.sourceId, | |
| 20 | + scopeId: "ws_test", | |
| 21 | + name: input.sourceId, | |
| 22 | + kind: "openapi", | |
| 23 | + endpoint: `https://example.test/${input.sourceId}`, | |
| 24 | + status: input.status ?? "connected", | |
| 25 | + enabled: input.enabled ?? true, | |
| 26 | + namespace: null, | |
| 27 | + bindingVersion: 1, | |
| 28 | + binding: {}, | |
| 29 | + importAuthPolicy: "reuse_runtime", | |
| 30 | + importAuth: { | |
| 31 | + kind: "none", | |
| 32 | + }, | |
| 33 | + auth: { | |
| 34 | + kind: "none", | |
| 35 | + }, | |
| 36 | + sourceHash: null, | |
| 37 | + lastError: null, | |
| 38 | + createdAt: 0, | |
| 39 | + updatedAt: 0, | |
| 40 | + }, | |
| 41 | + projected: { | |
| 42 | + toolDescriptors: Object.fromEntries( | |
| 43 | + input.toolPaths.map((path, index) => [ | |
| 44 | + `cap_${index}`, | |
| 45 | + { | |
| 46 | + toolPath: path.split("."), | |
| 47 | + }, | |
| 48 | + ]), | |
| 49 | + ), | |
| 50 | + }, | |
| 51 | +}) as any; | |
| 52 | + | |
| 53 | +const noopScopeConfigStore = { | |
| 54 | + load: () => Effect.die("unexpected config load"), | |
| 55 | + writeProject: () => Effect.void, | |
| 56 | + resolveRelativePath: (path: string) => Effect.succeed(path), | |
| 57 | +} as any; | |
| 58 | + | |
| 59 | +const noopScopeStateStore = { | |
| 60 | + load: () => Effect.die("unexpected scope state load"), | |
| 61 | + write: () => Effect.void, | |
| 62 | +} as any; | |
| 63 | + | |
| 64 | +const noopSourceArtifactStore = { | |
| 65 | + build: () => Effect.die("unexpected source artifact build"), | |
| 66 | + read: () => Effect.die("unexpected source artifact read"), | |
| 67 | + write: () => Effect.void, | |
| 68 | + remove: () => Effect.void, | |
| 69 | +} as any; | |
| 70 | + | |
| 71 | +describe("scope source catalog", () => { | |
| 72 | + it.effect("lists namespaces from projected tool paths without loading the tool index", () => | |
| 73 | + Effect.gen(function* () { | |
| 74 | + const catalog = createScopeSourceCatalog({ | |
| 75 | + scopeId: "ws_test" as any, | |
| 76 | + actorScopeId: "acc_test" as any, | |
| 77 | + sourceCatalogStore: { | |
| 78 | + loadWorkspaceSourceCatalogs: () => Effect.succeed([ | |
| 79 | + makeLoadedCatalog({ | |
| 80 | + sourceId: "github", | |
| 81 | + toolPaths: [ | |
| 82 | + "github.repos.get", | |
| 83 | + "github.repos.list", | |
| 84 | + "github.issues.get", | |
| 85 | + ], | |
| 86 | + }), | |
| 87 | + makeLoadedCatalog({ | |
| 88 | + sourceId: "linear", | |
| 89 | + toolPaths: [ | |
| 90 | + "linear.issue.create", | |
| 91 | + ], | |
| 92 | + }), | |
| 93 | + makeLoadedCatalog({ | |
| 94 | + sourceId: "disabled", | |
| 95 | + toolPaths: [ | |
| 96 | + "disabled.admin.delete", | |
| 97 | + ], | |
| 98 | + enabled: false, | |
| 99 | + }), | |
| 100 | + ]), | |
| 101 | + loadWorkspaceSourceCatalogToolIndex: () => Effect.die("unexpected tool index load"), | |
| 102 | + loadWorkspaceSourceCatalogToolByPath: () => Effect.die("unexpected tool lookup"), | |
| 103 | + } as any, | |
| 104 | + scopeConfigStore: noopScopeConfigStore, | |
| 105 | + scopeStateStore: noopScopeStateStore, | |
| 106 | + sourceArtifactStore: noopSourceArtifactStore, | |
| 107 | + runtimeLocalScope: null, | |
| 108 | + }); | |
| 109 | + | |
| 110 | + const namespaces = yield* catalog.listNamespaces({ limit: 10 }); | |
| 111 | + | |
| 112 | + expect(namespaces).toEqual([ | |
| 113 | + { | |
| 114 | + namespace: "github.issues", | |
| 115 | + toolCount: 1, | |
| 116 | + }, | |
| 117 | + { | |
| 118 | + namespace: "github.repos", | |
| 119 | + toolCount: 2, | |
| 120 | + }, | |
| 121 | + { | |
| 122 | + namespace: "linear.issue", | |
| 123 | + toolCount: 1, | |
| 124 | + }, | |
| 125 | + ]); | |
| 126 | + })); | |
| 127 | +}); | |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import { |
| 2 | 2 | createToolCatalogFromEntries, |
| 3 | 3 | type ToolCatalog, |
| 4 | + type ToolNamespace, | |
| 4 | 5 | } from "@executor/codemode-core"; |
| 5 | 6 | import type { |
| 6 | 7 | ScopeId, |
| @@ -80,6 +81,49 @@ const hasSubstringMatch = (value: string, queryToken: string): boolean => { · 73 unmodified lines | ||
| 80 | 81 | const queryTokenWeight = (token: string): number => |
| 81 | 82 | LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1; |
| 82 | 83 | |
| 84 | +const namespaceFromPath = (path: string): string => { | |
| 85 | + const [first, second] = path.split("."); | |
| 86 | + return second ? `${first}.${second}` : first; | |
| 87 | +}; | |
| 88 | + | |
| 89 | +const sortNamespaces = (namespaces: Iterable<ToolNamespace>): ToolNamespace[] => | |
| 90 | + [...namespaces].sort((left, right) => left.namespace.localeCompare(right.namespace)); | |
| 91 | + | |
| 92 | +const loadWorkspaceCatalogNamespaces = (input: { | |
| 93 | + scopeId: Source["scopeId"]; | |
| 94 | + actorScopeId: ScopeId; | |
| 95 | + sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>; | |
| 96 | +}): Effect.Effect< | |
| 97 | + readonly ToolNamespace[], | |
| 98 | + Error, | |
| 99 | + ScopeStorageServices | |
| 100 | +> => | |
| 101 | + Effect.gen(function* () { | |
| 102 | + const catalogs = yield* input.sourceCatalogStore.loadWorkspaceSourceCatalogs({ | |
| 103 | + scopeId: input.scopeId, | |
| 104 | + actorScopeId: input.actorScopeId, | |
| 105 | + }); | |
| 106 | + | |
| 107 | + const namespaces = new Map<string, ToolNamespace>(); | |
| 108 | + | |
| 109 | + for (const catalog of catalogs) { | |
| 110 | + if (!catalog.source.enabled || catalog.source.status !== "connected") { | |
| 111 | + continue; | |
| 112 | + } | |
| 113 | + | |
| 114 | + for (const descriptor of Object.values(catalog.projected.toolDescriptors)) { | |
| 115 | + const namespace = namespaceFromPath(descriptor.toolPath.join(".")); | |
| 116 | + const existing = namespaces.get(namespace); | |
| 117 | + namespaces.set(namespace, { | |
| 118 | + namespace, | |
| 119 | + toolCount: (existing?.toolCount ?? 0) + 1, | |
| 120 | + }); | |
| 121 | + } | |
| 122 | + } | |
| 123 | + | |
| 124 | + return sortNamespaces(namespaces.values()); | |
| 125 | + }); | |
| 126 | + | |
| 83 | 127 | export const loadWorkspaceCatalogTools = (input: { |
| 84 | 128 | scopeId: Source["scopeId"]; |
| 85 | 129 | actorScopeId: ScopeId; |
| @@ -291,9 +335,14 @@ export const createScopeSourceCatalog = (input: { · 205 unmodified lines | ||
| 291 | 335 | return { |
| 292 | 336 | listNamespaces: ({ limit }) => |
| 293 | 337 | provideRuntimeLocalScope( |
| 294 | − Effect.flatMap(createSharedCatalog(false), (catalog) => | |
| 295 | − catalog.listNamespaces({ limit }), | |
| 296 | − ), | |
| 338 | + provideWorkspaceStorage(Effect.map( | |
| 339 | + loadWorkspaceCatalogNamespaces({ | |
| 340 | + scopeId: input.scopeId, | |
| 341 | + actorScopeId: input.actorScopeId, | |
| 342 | + sourceCatalogStore: input.sourceCatalogStore, | |
| 343 | + }), | |
| 344 | + (namespaces) => namespaces.slice(0, limit), | |
| 345 | + )), | |
| 297 | 346 | input.runtimeLocalScope, |
| 298 | 347 | ), |
| 299 | 348 | |