History

Trim namespace metadata cost

Rhys Sullivan committed 7 months ago

Commit 1f82a221 parent aa4c8194 files · +223 −28Browse files

Showing the changes to packages/platform (2 of 4 files). Show all

Unified · Split

2 files
  1. packages/platform/sdk/src/runtime/execution/scope/source-catalog.test.ts +127 −0
  2. packages/platform/sdk/src/runtime/execution/scope/source-catalog.ts +52 −3
packages/platform/sdk/src/runtime/execution/scope/source-catalog.test.ts+127−0
@@ -0,0 +1,127 @@
1import {
2 describe,
3 expect,
4 it,
5} from "@effect/vitest";
6import * as Effect from "effect/Effect";
7
8import {
9 createScopeSourceCatalog,
10} from "./source-catalog";
11
12const 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
53const noopScopeConfigStore = {
54 load: () => Effect.die("unexpected config load"),
55 writeProject: () => Effect.void,
56 resolveRelativePath: (path: string) => Effect.succeed(path),
57} as any;
58
59const noopScopeStateStore = {
60 load: () => Effect.die("unexpected scope state load"),
61 write: () => Effect.void,
62} as any;
63
64const 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
71describe("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});
packages/platform/sdk/src/runtime/execution/scope/source-catalog.ts+52−3
@@ -1,6 +1,7 @@
1import {1import {
2 createToolCatalogFromEntries,2 createToolCatalogFromEntries,
3 type ToolCatalog,3 type ToolCatalog,
4 type ToolNamespace,
4} from "@executor/codemode-core";5} from "@executor/codemode-core";
5import type {6import type {
6 ScopeId,7 ScopeId,
@@ -80,6 +81,49 @@ const hasSubstringMatch = (value: string, queryToken: string): boolean => { · 73 unmodified lines
80const queryTokenWeight = (token: string): number =>81const queryTokenWeight = (token: string): number =>
81 LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1;82 LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1;
82 83
84const namespaceFromPath = (path: string): string => {
85 const [first, second] = path.split(".");
86 return second ? `${first}.${second}` : first;
87};
88
89const sortNamespaces = (namespaces: Iterable<ToolNamespace>): ToolNamespace[] =>
90 [...namespaces].sort((left, right) => left.namespace.localeCompare(right.namespace));
91
92const 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
83export const loadWorkspaceCatalogTools = (input: {127export const loadWorkspaceCatalogTools = (input: {
84 scopeId: Source["scopeId"];128 scopeId: Source["scopeId"];
85 actorScopeId: ScopeId;129 actorScopeId: ScopeId;
@@ -291,9 +335,14 @@ export const createScopeSourceCatalog = (input: { · 205 unmodified lines
291 return {335 return {
292 listNamespaces: ({ limit }) =>336 listNamespaces: ({ limit }) =>
293 provideRuntimeLocalScope(337 provideRuntimeLocalScope(
294 Effect.flatMap(createSharedCatalog(false), (catalog) =>338 provideWorkspaceStorage(Effect.map(
295 catalog.listNamespaces({ limit }),339 loadWorkspaceCatalogNamespaces({
296 ),340 scopeId: input.scopeId,
341 actorScopeId: input.actorScopeId,
342 sourceCatalogStore: input.sourceCatalogStore,
343 }),
344 (namespaces) => namespaces.slice(0, limit),
345 )),
297 input.runtimeLocalScope,346 input.runtimeLocalScope,
298 ),347 ),
299 348