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 @@
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+});
packages/platform/sdk/src/runtime/execution/scope/source-catalog.ts+52−3
@@ -1,6 +1,7 @@
11 import {
22 createToolCatalogFromEntries,
33 type ToolCatalog,
4+ type ToolNamespace,
45 } from "@executor/codemode-core";
56 import type {
67 ScopeId,
@@ -80,6 +81,49 @@ const hasSubstringMatch = (value: string, queryToken: string): boolean => { · 73 unmodified lines
8081 const queryTokenWeight = (token: string): number =>
8182 LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1;
8283
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+
83127 export const loadWorkspaceCatalogTools = (input: {
84128 scopeId: Source["scopeId"];
85129 actorScopeId: ScopeId;
@@ -291,9 +335,14 @@ export const createScopeSourceCatalog = (input: { · 205 unmodified lines
291335 return {
292336 listNamespaces: ({ limit }) =>
293337 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+ )),
297346 input.runtimeLocalScope,
298347 ),
299348