refactor(ai): internalize request compilation (#39132)
This commit is contained in:
parent
b47cfbee7c
commit
d9555f138b
28 changed files with 216 additions and 328 deletions
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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" }))
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue