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.
Unified · Split
| @@ -1762,7 +1762,16 @@ export const createExecutor = < · 1761 unmodified lines | ||
| 1762 | 1762 | |
| 1763 | 1763 | const listTools = (filter?: ToolListFilter) => |
| 1764 | 1764 | Effect.gen(function* () { |
| 1765 | − const dynamic = yield* core.findMany({ model: "tool" }); | |
| 1765 | + // Push sourceId down to storage so we don't pull every tool row | |
| 1766 | + // in the stack when the caller only wants one source (e.g. the | |
| 1767 | + // `/sources/:id/tools` endpoint). `query` is a free-text match | |
| 1768 | + // that still needs the row contents, so we apply it in memory. | |
| 1769 | + const dynamic = yield* core.findMany({ | |
| 1770 | + model: "tool", | |
| 1771 | + where: filter?.sourceId | |
| 1772 | + ? [{ field: "source_id", value: filter.sourceId }] | |
| 1773 | + : undefined, | |
| 1774 | + }); | |
| 1766 | 1775 | // Dedup by tool id, innermost scope winning — same reason as |
| 1767 | 1776 | // `listSources` above: a shadowed id must surface as one entry |
| 1768 | 1777 | // (the inner one), not two. |
| @@ -366,6 +366,7 @@ export const graphqlPlugin = definePlugin( · 365 unmodified lines | ||
| 366 | 366 | const storedOps: StoredOperation[] = prepared.map((p) => ({ |
| 367 | 367 | toolId: `${namespace}.${p.toolPath}`, |
| 368 | 368 | sourceId: namespace, |
| 369 | + scopeId: config.scope, | |
| 369 | 370 | binding: p.binding, |
| 370 | 371 | })); |
| 371 | 372 | |
| @@ -527,22 +528,19 @@ export const graphqlPlugin = definePlugin( · 155 unmodified lines | ||
| 527 | 528 | // toolRows for a single (plugin_id, source_id) group can still |
| 528 | 529 | // straddle multiple scopes when the source is shadowed (e.g. an |
| 529 | 530 | // org-level GraphQL source plus a per-user override that |
| 530 | − // re-registers the same tool ids). Run one listOperationsBySource | |
| 531 | − // per distinct scope so each lookup pins {source_id, scope_id} | |
| 532 | − // and we don't fall through to the wrong scope's bindings. | |
| 533 | − const scopes = new Set<string>(); | |
| 534 | − for (const row of toolRows as readonly ToolRow[]) { | |
| 535 | − scopes.add(row.scope_id as string); | |
| 536 | − } | |
| 531 | + // re-registers the same tool ids). One query returns every op | |
| 532 | + // row in the caller's stack (the scoped adapter pins | |
| 533 | + // `scope_id IN (stack)`); partition by `op.scopeId` so each | |
| 534 | + // tool row resolves against its own scope's binding. | |
| 535 | + const ops = yield* ctx.storage.listOperationsBySource(sourceId); | |
| 537 | 536 | const byScope = new Map<string, Map<string, OperationBinding>>(); |
| 538 | − for (const scope of scopes) { | |
| 539 | − const ops = yield* ctx.storage.listOperationsBySource( | |
| 540 | − sourceId, | |
| 541 | − scope, | |
| 542 | − ); | |
| 543 | − const byId = new Map<string, OperationBinding>(); | |
| 544 | − for (const op of ops) byId.set(op.toolId, op.binding); | |
| 545 | − byScope.set(scope, byId); | |
| 537 | + for (const op of ops) { | |
| 538 | + let byId = byScope.get(op.scopeId); | |
| 539 | + if (!byId) { | |
| 540 | + byId = new Map<string, OperationBinding>(); | |
| 541 | + byScope.set(op.scopeId, byId); | |
| 542 | + } | |
| 543 | + byId.set(op.toolId, op.binding); | |
| 546 | 544 | } |
| 547 | 545 | |
| 548 | 546 | const out: Record<string, ToolAnnotations> = {}; |
| @@ -50,6 +50,7 @@ export interface StoredGraphqlSource { · 49 unmodified lines | ||
| 50 | 50 | export interface StoredOperation { |
| 51 | 51 | readonly toolId: string; |
| 52 | 52 | readonly sourceId: string; |
| 53 | + readonly scopeId: string; | |
| 53 | 54 | readonly binding: OperationBinding; |
| 54 | 55 | } |
| 55 | 56 | |
| @@ -126,7 +127,6 @@ export interface GraphqlStore { · 70 unmodified lines | ||
| 126 | 127 | |
| 127 | 128 | readonly listOperationsBySource: ( |
| 128 | 129 | sourceId: string, |
| 129 | − scope: string, | |
| 130 | 130 | ) => Effect.Effect<readonly StoredOperation[], StorageFailure>; |
| 131 | 131 | |
| 132 | 132 | readonly removeSource: ( |
| @@ -153,6 +153,7 @@ export const makeDefaultGraphqlStore = ({ · 20 unmodified lines | ||
| 153 | 153 | const rowToOperation = (row: Record<string, unknown>): StoredOperation => ({ |
| 154 | 154 | toolId: row.id as string, |
| 155 | 155 | sourceId: row.source_id as string, |
| 156 | + scopeId: row.scope_id as string, | |
| 156 | 157 | binding: decodeBinding(row.binding), |
| 157 | 158 | }); |
| 158 | 159 | |
| @@ -257,14 +258,11 @@ export const makeDefaultGraphqlStore = ({ · 98 unmodified lines | ||
| 257 | 258 | }) |
| 258 | 259 | .pipe(Effect.map((row) => (row ? rowToOperation(row) : null))), |
| 259 | 260 | |
| 260 | − listOperationsBySource: (sourceId, scope) => | |
| 261 | + listOperationsBySource: (sourceId) => | |
| 261 | 262 | db |
| 262 | 263 | .findMany({ |
| 263 | 264 | model: "graphql_operation", |
| 264 | − where: [ | |
| 265 | − { field: "source_id", value: sourceId }, | |
| 266 | − { field: "scope_id", value: scope }, | |
| 267 | − ], | |
| 265 | + where: [{ field: "source_id", value: sourceId }], | |
| 268 | 266 | }) |
| 269 | 267 | .pipe(Effect.map((rows) => rows.map(rowToOperation))), |
| 270 | 268 | |
| @@ -408,6 +408,7 @@ export const openApiPlugin = definePlugin( · 407 unmodified lines | ||
| 408 | 408 | const storedOps: StoredOperation[] = definitions.map((def) => ({ |
| 409 | 409 | toolId: `${namespace}.${def.toolPath}`, |
| 410 | 410 | sourceId: namespace, |
| 411 | + scopeId: input.scope, | |
| 411 | 412 | binding: toBinding(def), |
| 412 | 413 | })); |
| 413 | 414 | |
| @@ -949,19 +950,19 @@ export const openApiPlugin = definePlugin( · 535 unmodified lines | ||
| 949 | 950 | // toolRows for a single (plugin_id, source_id) group can still |
| 950 | 951 | // straddle multiple scopes when the source is shadowed (e.g. an |
| 951 | 952 | // org-level openapi source plus a per-user override that |
| 952 | − // re-registers the same tool ids). Run one listOperationsBySource | |
| 953 | − // per distinct scope so each lookup pins {source_id, scope_id} | |
| 954 | − // and we don't fall through to the wrong scope's bindings. | |
| 955 | − const scopes = new Set<string>(); | |
| 956 | − for (const row of toolRows as readonly ToolRow[]) { | |
| 957 | − scopes.add(row.scope_id as string); | |
| 958 | − } | |
| 953 | + // re-registers the same tool ids). One query returns every op | |
| 954 | + // row in the caller's stack (the scoped adapter pins | |
| 955 | + // `scope_id IN (stack)`); partition by `op.scopeId` so each | |
| 956 | + // tool row resolves against its own scope's binding. | |
| 957 | + const ops = yield* ctx.storage.listOperationsBySource(sourceId); | |
| 959 | 958 | const byScope = new Map<string, Map<string, OperationBinding>>(); |
| 960 | − for (const scope of scopes) { | |
| 961 | − const ops = yield* ctx.storage.listOperationsBySource(sourceId, scope); | |
| 962 | − const byId = new Map<string, OperationBinding>(); | |
| 963 | − for (const op of ops) byId.set(op.toolId, op.binding); | |
| 964 | − byScope.set(scope, byId); | |
| 959 | + for (const op of ops) { | |
| 960 | + let byId = byScope.get(op.scopeId); | |
| 961 | + if (!byId) { | |
| 962 | + byId = new Map<string, OperationBinding>(); | |
| 963 | + byScope.set(op.scopeId, byId); | |
| 964 | + } | |
| 965 | + byId.set(op.toolId, op.binding); | |
| 965 | 966 | } |
| 966 | 967 | |
| 967 | 968 | const out: Record<string, ToolAnnotations> = {}; |
| @@ -119,6 +119,7 @@ export type StoredSourceSchemaType = typeof StoredSourceSchema.Type; · 118 unmodified lines | ||
| 119 | 119 | export interface StoredOperation { |
| 120 | 120 | readonly toolId: string; |
| 121 | 121 | readonly sourceId: string; |
| 122 | + readonly scopeId: string; | |
| 122 | 123 | readonly binding: OperationBinding; |
| 123 | 124 | } |
| 124 | 125 | |
| @@ -202,7 +203,6 @@ export interface OpenapiStore { · 77 unmodified lines | ||
| 202 | 203 | |
| 203 | 204 | readonly listOperationsBySource: ( |
| 204 | 205 | sourceId: string, |
| 205 | − scope: string, | |
| 206 | 206 | ) => Effect.Effect<readonly StoredOperation[], StorageFailure>; |
| 207 | 207 | |
| 208 | 208 | readonly removeSource: ( |
| @@ -255,6 +255,7 @@ export const makeDefaultOpenapiStore = ({ · 46 unmodified lines | ||
| 255 | 255 | const rowToOperation = (row: Record<string, unknown>): StoredOperation => ({ |
| 256 | 256 | toolId: row.id as string, |
| 257 | 257 | sourceId: row.source_id as string, |
| 258 | + scopeId: row.scope_id as string, | |
| 258 | 259 | binding: decodeBinding( |
| 259 | 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 | 390 | .pipe(Effect.map((row) => (row ? rowToOperation(row) : null))), |
| 390 | 391 | |
| 391 | − listOperationsBySource: (sourceId, scope) => | |
| 392 | + listOperationsBySource: (sourceId) => | |
| 392 | 393 | adapter |
| 393 | 394 | .findMany({ |
| 394 | 395 | model: "openapi_operation", |
| 395 | − where: [ | |
| 396 | − { field: "source_id", value: sourceId }, | |
| 397 | − { field: "scope_id", value: scope }, | |
| 398 | − ], | |
| 396 | + where: [{ field: "source_id", value: sourceId }], | |
| 399 | 397 | }) |
| 400 | 398 | .pipe(Effect.map((rows) => rows.map(rowToOperation))), |
| 401 | 399 | |