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 | 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 | |