From bb7f52b24d4c87ce6cba49a58831a1e7dbd8d0e7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 28 Apr 2026 20:44:11 -0400 Subject: [PATCH] refactor(llm): remove ambiguous Adapter provider scoping field The optional 'provider' field on Adapter / AdapterInput / FromProtocolInput existed as a registry filter: requests with a different model.provider could not find adapters that set it. After the four-axis migration no adapter needed it (and an earlier pass removed it from the five migrated providers because setting it broke session/llm-native tests). Drop the field entirely and collapse the registry to a single-tier protocol lookup. If a future deployment genuinely needs to be scoped (e.g. an Azure-only OpenAI Responses adapter), reintroduce as 'scopedTo' with an explicit name. Solve when needed, not before. Also drops the test that exercised the now-removed two-tier lookup ('prefers provider-specific adapters over protocol fallbacks'). --- packages/llm/src/adapter.ts | 27 +++------------------------ packages/llm/test/adapter.test.ts | 16 ---------------- 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/packages/llm/src/adapter.ts b/packages/llm/src/adapter.ts index cca55e22d6..9f00a96865 100644 --- a/packages/llm/src/adapter.ts +++ b/packages/llm/src/adapter.ts @@ -15,7 +15,6 @@ import { LLMResponse, NoAdapterError, PreparedRequest as PreparedRequestSchema } interface RuntimeAdapter { readonly id: string - readonly provider?: string readonly protocol: ProtocolID readonly patches: ReadonlyArray> readonly redact: (target: unknown) => unknown @@ -36,7 +35,6 @@ export interface HttpContext { export interface Adapter { readonly id: string - readonly provider?: string readonly protocol: ProtocolID readonly patches: ReadonlyArray> readonly redact: (target: Target) => unknown @@ -48,7 +46,6 @@ export interface Adapter { export interface AdapterInput { readonly id: string - readonly provider?: string readonly protocol: ProtocolID readonly patches?: ReadonlyArray> readonly redact: (target: Target) => unknown @@ -66,7 +63,6 @@ export interface AdapterDefinition extends Adapter export interface ComposeInput { readonly id: string - readonly provider?: string readonly protocol?: ProtocolID readonly base: Adapter readonly patches?: ReadonlyArray> @@ -100,7 +96,6 @@ const normalizeRegistry = (patches: PatchRegistry | ReadonlyArray | un export function define(input: AdapterInput): AdapterDefinition { const build = (patches: ReadonlyArray>): AdapterDefinition => ({ id: input.id, - provider: input.provider, protocol: input.protocol, patches, get runtime() { @@ -123,7 +118,6 @@ export function define(input: AdapterInput): Adapt export function compose(input: ComposeInput): AdapterDefinition { return define({ id: input.id, - provider: input.provider, protocol: input.protocol ?? input.base.protocol, patches: [...input.base.patches, ...(input.patches ?? [])], redact: input.redact ?? input.base.redact, @@ -137,8 +131,6 @@ export function compose(input: ComposeInput): Adap export interface FromProtocolInput { /** Adapter id used in registry lookup, error messages, and patch namespaces. */ readonly id: string - /** Provider id used to scope provider-specific adapters in the registry. */ - readonly provider?: string /** Semantic API contract — owns lowering, validation, encoding, and parsing. */ readonly protocol: Protocol /** Where the request is sent. */ @@ -216,7 +208,6 @@ export function fromProtocol( return define({ id: input.id, - provider: input.provider, protocol: input.protocolId ?? protocol.id, patches: input.patches, redact: protocol.redact, @@ -229,25 +220,13 @@ export function fromProtocol( const makeClient = (options: ClientOptions): LLMClient => { const registry = normalizeRegistry(options.patches) - const adapters = options.adapters.map((adapter) => adapter.runtime) - const providerAdapters = adapters - .filter((adapter): adapter is RuntimeAdapter & { readonly provider: string } => adapter.provider !== undefined) - .reduce((map, adapter) => { - const current = map.get(adapter.provider) ?? new Map() - current.set(adapter.protocol, adapter) - return map.set(adapter.provider, current) - }, new Map>()) - const protocolAdapters = new Map( - adapters - .filter((adapter) => adapter.provider === undefined) - .map((adapter) => [adapter.protocol, adapter] as const), + const adapters = new Map( + options.adapters.map((source) => [source.runtime.protocol, source.runtime] as const), ) const resolveAdapter = (request: LLMRequest) => Effect.gen(function* () { - const adapter = - providerAdapters.get(request.model.provider)?.get(request.model.protocol) ?? - protocolAdapters.get(request.model.protocol) + const adapter = adapters.get(request.model.protocol) if (!adapter) return yield* noAdapter(request.model) return adapter }) diff --git a/packages/llm/test/adapter.test.ts b/packages/llm/test/adapter.test.ts index eaf47ad061..794f8e49d4 100644 --- a/packages/llm/test/adapter.test.ts +++ b/packages/llm/test/adapter.test.ts @@ -120,13 +120,6 @@ const gemini = Adapter.define({ protocol: "gemini", }) -const providerFake = Adapter.compose({ - id: "provider-fake", - provider: "fake-provider", - base: fake, - prepare: (request) => fake.prepare(request).pipe(Effect.map((draft) => ({ ...draft, body: `provider:${draft.body}` }))), -}) - const echoLayer = dynamicResponse(({ text, respond }) => Effect.succeed( respond( @@ -180,15 +173,6 @@ describe("llm adapter", () => { }), ) - it.effect("prefers provider-specific adapters over protocol fallbacks", () => - Effect.gen(function* () { - const prepared = yield* LLMClient.make({ adapters: [fake, providerFake] }).prepare(request) - - expect(prepared.adapter).toBe("provider-fake") - expect(prepared.target).toEqual({ body: "provider:hello" }) - }), - ) - it.effect("request, prompt, and tool-schema patches run before adapter prepare", () => Effect.gen(function* () { const prepared = yield* LLMClient.make({