refactor(ai): internalize request compilation (#39132)

This commit is contained in:
Shoubhit Dash 2026-07-29 16:51:11 +05:30 committed by GitHub
commit d9555f138b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 216 additions and 328 deletions

View file

@ -1,9 +1,10 @@
import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart } from "@ai-sdk/provider"
import type { LanguageModelV3, LanguageModelV3StreamPart } from "@ai-sdk/provider"
import { AISDK } from "@opencode-ai/core/aisdk"
import { Model } from "@opencode-ai/core/model"
import { Provider } from "@opencode-ai/core/provider"
import { LLM, LLMError, LLMEvent, Message } from "@opencode-ai/ai"
import { LLMClient, RequestExecutor } from "@opencode-ai/ai/route"
import { compileRequest } from "@opencode-ai/ai/route/client"
import { expect } from "bun:test"
import { Effect, Layer } from "effect"
import { testEffect } from "./lib/effect"
@ -87,9 +88,7 @@ it.effect("projects request settings, headers, and body overlays", () =>
headers: { "x-test": "header" },
body: { safety_setting: "strict" },
})
const prepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: resolved, prompt: "Hello" }),
)
const prepared = yield* compileRequest(LLM.request({ model: resolved, prompt: "Hello" }))
expect(prepared.body.providerOptions).toEqual({
google: { thinkingConfig: { thinkingBudget: 1024 } },
@ -112,9 +111,7 @@ it.effect("maps pro reasoning bodies to AI SDK provider options", () =>
...model("@ai-sdk/openai"),
body: { reasoning: { mode: "pro" } },
})
const prepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: resolved, prompt: "Hello" }),
)
const prepared = yield* compileRequest(LLM.request({ model: resolved, prompt: "Hello" }))
expect(body).toBeUndefined()
expect(prepared.body.providerOptions).toEqual({
@ -139,9 +136,7 @@ it.effect("maps package-specific AI SDK provider option keys", () =>
] as const
for (const [packageName, key, settings] of cases) {
const resolved = yield* aisdk.model(model(packageName, { reasoningEffort: "high" }))
const prepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: resolved, prompt: "Hello" }),
)
const prepared = yield* compileRequest(LLM.request({ model: resolved, prompt: "Hello" }))
expect(prepared.body.providerOptions).toEqual({ [key]: settings })
}
}),
@ -155,17 +150,13 @@ it.effect("forces reasoning and projects both Azure AI SDK namespaces", () =>
})
const openai = yield* aisdk.model(model("@ai-sdk/openai", { reasoningEffort: "high" }))
const openaiPrepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: openai, prompt: "Hello" }),
)
const openaiPrepared = yield* compileRequest(LLM.request({ model: openai, prompt: "Hello" }))
expect(openaiPrepared.body.providerOptions).toEqual({
openai: { reasoningEffort: "high", forceReasoning: true },
})
const azure = yield* aisdk.model(model("@ai-sdk/azure", { reasoningEffort: "high" }))
const azurePrepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: azure, prompt: "Hello" }),
)
const azurePrepared = yield* compileRequest(LLM.request({ model: azure, prompt: "Hello" }))
expect(azurePrepared.body.providerOptions).toEqual({
openai: { reasoningEffort: "high", forceReasoning: true },
azure: { reasoningEffort: "high", forceReasoning: true },
@ -187,9 +178,7 @@ it.effect("routes AI Gateway model options by upstream prefix", () =>
}),
modelID: Model.ID.make("anthropic/claude-sonnet-5"),
})
const anthropicPrepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: anthropic, prompt: "Hello" }),
)
const anthropicPrepared = yield* compileRequest(LLM.request({ model: anthropic, prompt: "Hello" }))
expect(anthropicPrepared.body.providerOptions).toEqual({
gateway: { order: ["anthropic"] },
anthropic: { thinking: { type: "adaptive" } },
@ -199,9 +188,7 @@ it.effect("routes AI Gateway model options by upstream prefix", () =>
...model("@ai-sdk/gateway", { reasoningConfig: { type: "enabled" } }),
modelID: Model.ID.make("amazon/nova-2-lite"),
})
const bedrockPrepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: bedrock, prompt: "Hello" }),
)
const bedrockPrepared = yield* compileRequest(LLM.request({ model: bedrock, prompt: "Hello" }))
expect(bedrockPrepared.body.providerOptions).toEqual({
bedrock: { reasoningConfig: { type: "enabled" } },
})
@ -210,9 +197,7 @@ it.effect("routes AI Gateway model options by upstream prefix", () =>
...model("@ai-sdk/gateway", { reasoningEffort: "high" }),
modelID: Model.ID.make("deepseek/deepseek-v4"),
})
const fallbackPrepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
LLM.request({ model: fallback, prompt: "Hello" }),
)
const fallbackPrepared = yield* compileRequest(LLM.request({ model: fallback, prompt: "Hello" }))
expect(fallbackPrepared.body.providerOptions).toEqual({
deepseek: { reasoningEffort: "high" },
})
@ -228,7 +213,7 @@ it.effect("projects replay metadata onto AI SDK prompt parts", () =>
const resolved = yield* aisdk.model(model("@ai-sdk/anthropic"))
expect(resolved.route.providerMetadataKey).toBe("anthropic")
const prepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
const prepared = yield* compileRequest(
LLM.request({
model: resolved,
messages: [

View file

@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { LLM, Model } from "@opencode-ai/ai"
import { LLMClient } from "@opencode-ai/ai/route"
import { compileRequest } from "@opencode-ai/ai/route/client"
import { Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Credential } from "@opencode-ai/core/credential"
@ -69,7 +69,7 @@ describe("ModelResolver", () => {
settings: { apiKey: "secret", baseURL: "https://openai.example/v1" },
}),
)
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
const prepared = yield* compileRequest(LLM.request({ model: resolved, prompt: "Hello" }))
expect(JSON.stringify(prepared.body)).not.toContain("apiKey")
expect(JSON.stringify(prepared.body)).not.toContain("secret")

View file

@ -41,7 +41,6 @@ const projects = Layer.succeed(
)
let requests: LLMRequest[] = []
const client = Layer.mock(LLMClient.Service)({
prepare: () => Effect.die("unused"),
stream: (request: LLMRequest) => {
requests.push(request)
return Stream.make(LLMEvent.textDelta({ id: "summary", text: "manual session summary" }))

View file

@ -42,7 +42,6 @@ const cost = [
},
]
const client = Layer.mock(LLMClient.Service)({
prepare: () => Effect.die("unused"),
stream: (request: LLMRequest) => {
requests.push(request)
return Stream.make(

View file

@ -49,7 +49,6 @@ const sessionID = SessionSchema.ID.make("ses_generate_test")
const model = Model.make({ id: "generate-model", provider: "test", route: OpenAIChat.route })
const client = Layer.mock(LLMClient.Service)({
prepare: () => Effect.die(new Error("unused")),
stream: () => Stream.die(new Error("unused")),
generate: (request) =>
Effect.sync(() => {

View file

@ -40,7 +40,6 @@ const cost = [
},
]
const client = Layer.mock(LLMClient.Service)({
prepare: () => Effect.die("unused"),
stream: (request: LLMRequest) => {
requests.push(request)
return Stream.make(