openapi/graphql resolveAnnotations ran one listOperationsBySource per distinct scope in the toolRows. Store the owning scopeId on each StoredOperation instead and issue a single query, partitioning by op.scopeId so shadowed tools still resolve against the right binding. Also push executor.tools.list's sourceId filter down to storage so `/sources/:id/tools` stops pulling every row in the scope stack.
Showing the changes to packages/plugins/openapi/src (2 of 5 files). Show all
Unified · Split
| @@ -408,6 +408,7 @@ export const openApiPlugin = definePlugin( · 407 unmodified lines | |||
| 408 | const storedOps: StoredOperation[] = definitions.map((def) => ({ | 408 | const storedOps: StoredOperation[] = definitions.map((def) => ({ |
| 409 | toolId: `${namespace}.${def.toolPath}`, | 409 | toolId: `${namespace}.${def.toolPath}`, |
| 410 | sourceId: namespace, | 410 | sourceId: namespace, |
| 411 | scopeId: input.scope, | ||
| 411 | binding: toBinding(def), | 412 | binding: toBinding(def), |
| 412 | })); | 413 | })); |
| 413 | 414 | ||
| @@ -949,19 +950,19 @@ export const openApiPlugin = definePlugin( · 535 unmodified lines | |||
| 949 | // toolRows for a single (plugin_id, source_id) group can still | 950 | // toolRows for a single (plugin_id, source_id) group can still |
| 950 | // straddle multiple scopes when the source is shadowed (e.g. an | 951 | // straddle multiple scopes when the source is shadowed (e.g. an |
| 951 | // org-level openapi source plus a per-user override that | 952 | // org-level openapi source plus a per-user override that |
| 952 | // re-registers the same tool ids). Run one listOperationsBySource | 953 | // re-registers the same tool ids). One query returns every op |
| 953 | // per distinct scope so each lookup pins {source_id, scope_id} | 954 | // row in the caller's stack (the scoped adapter pins |
| 954 | // and we don't fall through to the wrong scope's bindings. | 955 | // `scope_id IN (stack)`); partition by `op.scopeId` so each |
| 955 | const scopes = new Set<string>(); | 956 | // tool row resolves against its own scope's binding. |
| 956 | for (const row of toolRows as readonly ToolRow[]) { | 957 | const ops = yield* ctx.storage.listOperationsBySource(sourceId); |
| 957 | scopes.add(row.scope_id as string); | ||
| 958 | } | ||
| 959 | const byScope = new Map<string, Map<string, OperationBinding>>(); | 958 | const byScope = new Map<string, Map<string, OperationBinding>>(); |
| 960 | for (const scope of scopes) { | 959 | for (const op of ops) { |
| 961 | const ops = yield* ctx.storage.listOperationsBySource(sourceId, scope); | 960 | let byId = byScope.get(op.scopeId); |
| 962 | const byId = new Map<string, OperationBinding>(); | 961 | if (!byId) { |
| 963 | for (const op of ops) byId.set(op.toolId, op.binding); | 962 | byId = new Map<string, OperationBinding>(); |
| 964 | byScope.set(scope, byId); | 963 | byScope.set(op.scopeId, byId); |
| 964 | } | ||
| 965 | byId.set(op.toolId, op.binding); | ||
| 965 | } | 966 | } |
| 966 | 967 | ||
| 967 | const out: Record<string, ToolAnnotations> = {}; | 968 | const out: Record<string, ToolAnnotations> = {}; |
| @@ -119,6 +119,7 @@ export type StoredSourceSchemaType = typeof StoredSourceSchema.Type; · 118 unmodified lines | |||
| 119 | export interface StoredOperation { | 119 | export interface StoredOperation { |
| 120 | readonly toolId: string; | 120 | readonly toolId: string; |
| 121 | readonly sourceId: string; | 121 | readonly sourceId: string; |
| 122 | readonly scopeId: string; | ||
| 122 | readonly binding: OperationBinding; | 123 | readonly binding: OperationBinding; |
| 123 | } | 124 | } |
| 124 | 125 | ||
| @@ -202,7 +203,6 @@ export interface OpenapiStore { · 77 unmodified lines | |||
| 202 | 203 | ||
| 203 | readonly listOperationsBySource: ( | 204 | readonly listOperationsBySource: ( |
| 204 | sourceId: string, | 205 | sourceId: string, |
| 205 | scope: string, | ||
| 206 | ) => Effect.Effect<readonly StoredOperation[], StorageFailure>; | 206 | ) => Effect.Effect<readonly StoredOperation[], StorageFailure>; |
| 207 | 207 | ||
| 208 | readonly removeSource: ( | 208 | readonly removeSource: ( |
| @@ -255,6 +255,7 @@ export const makeDefaultOpenapiStore = ({ · 46 unmodified lines | |||
| 255 | const rowToOperation = (row: Record<string, unknown>): StoredOperation => ({ | 255 | const rowToOperation = (row: Record<string, unknown>): StoredOperation => ({ |
| 256 | toolId: row.id as string, | 256 | toolId: row.id as string, |
| 257 | sourceId: row.source_id as string, | 257 | sourceId: row.source_id as string, |
| 258 | scopeId: row.scope_id as string, | ||
| 258 | binding: decodeBinding( | 259 | binding: decodeBinding( |
| 259 | typeof row.binding === "string" ? JSON.parse(row.binding) : row.binding, | 260 | typeof row.binding === "string" ? JSON.parse(row.binding) : row.binding, |
| 260 | ), | 261 | ), |
| @@ -388,14 +389,11 @@ export const makeDefaultOpenapiStore = ({ · 127 unmodified lines | |||
| 388 | }) | 389 | }) |
| 389 | .pipe(Effect.map((row) => (row ? rowToOperation(row) : null))), | 390 | .pipe(Effect.map((row) => (row ? rowToOperation(row) : null))), |
| 390 | 391 | ||
| 391 | listOperationsBySource: (sourceId, scope) => | 392 | listOperationsBySource: (sourceId) => |
| 392 | adapter | 393 | adapter |
| 393 | .findMany({ | 394 | .findMany({ |
| 394 | model: "openapi_operation", | 395 | model: "openapi_operation", |
| 395 | where: [ | 396 | where: [{ field: "source_id", value: sourceId }], |
| 396 | { field: "source_id", value: sourceId }, | ||
| 397 | { field: "scope_id", value: scope }, | ||
| 398 | ], | ||
| 399 | }) | 397 | }) |
| 400 | .pipe(Effect.map((rows) => rows.map(rowToOperation))), | 398 | .pipe(Effect.map((rows) => rows.map(rowToOperation))), |
| 401 | 399 | ||