feat(ai): add Vertex Chat entrypoint (#37281)
This commit is contained in:
parent
282f3f7eb2
commit
75f9fd5208
10 changed files with 192 additions and 10 deletions
|
|
@ -11,6 +11,7 @@ import * as Cloudflare from "../src/providers/cloudflare"
|
|||
import * as GitHubCopilot from "../src/providers/github-copilot"
|
||||
import * as Google from "../src/providers/google"
|
||||
import * as GoogleVertex from "../src/providers/google-vertex"
|
||||
import * as GoogleVertexChat from "../src/providers/google-vertex-chat"
|
||||
import * as GoogleVertexMessages from "../src/providers/google-vertex-messages"
|
||||
import * as OpenAI from "../src/providers/openai"
|
||||
import * as OpenAICompatible from "../src/providers/openai-compatible"
|
||||
|
|
@ -171,6 +172,24 @@ GoogleVertex.configure({ accessToken: "vertex-token", apiKey: "vertex-key", proj
|
|||
// @ts-expect-error Vertex Gemini package settings accept only one auth source.
|
||||
GoogleVertex.model("gemini-3.5-flash", { accessToken: "vertex-token", apiKey: "vertex-key", project: "project" })
|
||||
|
||||
GoogleVertexChat.configure({ accessToken: "vertex-token", project: "project" }).model("deepseek-ai/deepseek-v3.2-maas")
|
||||
GoogleVertexChat.configure({ auth: RuntimeAuth.bearer("vertex-token"), project: "project" }).model(
|
||||
"deepseek-ai/deepseek-v3.2-maas",
|
||||
)
|
||||
// @ts-expect-error Vertex Chat package settings do not accept API keys.
|
||||
GoogleVertexChat.model("deepseek-ai/deepseek-v3.2-maas", { apiKey: "vertex-key", project: "project" })
|
||||
GoogleVertexChat.configure({ accessToken: "vertex-token", project: "project" }).model(
|
||||
"deepseek-ai/deepseek-v3.2-maas",
|
||||
// @ts-expect-error Vertex Chat model selectors only accept model ids.
|
||||
{},
|
||||
)
|
||||
GoogleVertexChat.configure({
|
||||
accessToken: "vertex-token",
|
||||
// @ts-expect-error Vertex Chat config accepts only one auth source.
|
||||
auth: RuntimeAuth.bearer("vertex-token"),
|
||||
project: "project",
|
||||
})
|
||||
|
||||
GoogleVertexMessages.configure({ accessToken: "vertex-token", project: "project" }).model("claude-sonnet-4-6")
|
||||
// @ts-expect-error Vertex Messages package settings do not accept API keys.
|
||||
GoogleVertexMessages.model("claude-sonnet-4-6", { apiKey: "vertex-key", project: "project" })
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ describe("provider package entrypoints", () => {
|
|||
import("@opencode-ai/ai/providers/google"),
|
||||
import("@opencode-ai/ai/providers/google-vertex"),
|
||||
import("@opencode-ai/ai/providers/google-vertex/gemini"),
|
||||
import("@opencode-ai/ai/providers/google-vertex/chat"),
|
||||
import("@opencode-ai/ai/providers/google-vertex/messages"),
|
||||
])
|
||||
|
||||
|
|
@ -182,6 +183,7 @@ describe("provider package entrypoints", () => {
|
|||
test("selects Vertex entrypoints with the same model contract", async () => {
|
||||
const GoogleVertex = await import("@opencode-ai/ai/providers/google-vertex")
|
||||
const GoogleVertexGemini = await import("@opencode-ai/ai/providers/google-vertex/gemini")
|
||||
const GoogleVertexChat = await import("@opencode-ai/ai/providers/google-vertex/chat")
|
||||
const GoogleVertexMessages = await import("@opencode-ai/ai/providers/google-vertex/messages")
|
||||
const gemini = GoogleVertex.model("gemini-3.5-flash", {
|
||||
apiKey: "fixture",
|
||||
|
|
@ -194,6 +196,11 @@ describe("provider package entrypoints", () => {
|
|||
location: "global",
|
||||
project: "vertex-project",
|
||||
})
|
||||
const chat = GoogleVertexChat.model("deepseek-ai/deepseek-v3.2-maas", {
|
||||
accessToken: "fixture",
|
||||
location: "global",
|
||||
project: "vertex-project",
|
||||
})
|
||||
|
||||
expect(GoogleVertexGemini.model).toBe(GoogleVertex.model)
|
||||
expect(gemini.route.id).toBe("google-vertex-gemini")
|
||||
|
|
@ -214,10 +221,17 @@ describe("provider package entrypoints", () => {
|
|||
expect(messages.route.endpoint.baseURL).toBe(
|
||||
"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/publishers/anthropic/models",
|
||||
)
|
||||
expect(chat.route.id).toBe("google-vertex-chat")
|
||||
expect(chat.route.protocol).toBe("openai-chat")
|
||||
expect(chat.route.endpoint).toMatchObject({
|
||||
baseURL: "https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/endpoints/openapi",
|
||||
path: "/chat/completions",
|
||||
})
|
||||
})
|
||||
|
||||
test("rejects conflicting Vertex auth settings at runtime", async () => {
|
||||
const GoogleVertex = await import("@opencode-ai/ai/providers/google-vertex")
|
||||
const GoogleVertexChat = await import("@opencode-ai/ai/providers/google-vertex/chat")
|
||||
const GoogleVertexMessages = await import("@opencode-ai/ai/providers/google-vertex/messages")
|
||||
const Providers = await import("@opencode-ai/ai/providers")
|
||||
expect(() =>
|
||||
|
|
@ -241,5 +255,16 @@ describe("provider package entrypoints", () => {
|
|||
{ apiKey: "fixture", project: "vertex-project" },
|
||||
]),
|
||||
).toThrow("Google Vertex Messages does not support API keys")
|
||||
expect(() =>
|
||||
Reflect.apply(GoogleVertexChat.model, undefined, [
|
||||
"deepseek-ai/deepseek-v3.2-maas",
|
||||
{ apiKey: "fixture", project: "vertex-project" },
|
||||
]),
|
||||
).toThrow("Google Vertex Chat does not support API keys")
|
||||
expect(() =>
|
||||
Reflect.apply(Providers.GoogleVertexChat.configure, undefined, [
|
||||
{ apiKey: "fixture", project: "vertex-project" },
|
||||
]),
|
||||
).toThrow("Google Vertex Chat does not support API keys")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import { describe, expect } from "bun:test"
|
|||
import { Effect } from "effect"
|
||||
import { HttpClientRequest } from "effect/unstable/http"
|
||||
import { LLM } from "../../src"
|
||||
import { GoogleVertex, GoogleVertexMessages } from "../../src/providers"
|
||||
import { GoogleVertex, GoogleVertexChat, GoogleVertexMessages } from "../../src/providers"
|
||||
import { LLMClient } from "../../src/route"
|
||||
import { it } from "../lib/effect"
|
||||
import { dynamicResponse } from "../lib/http"
|
||||
import { deltaChunk, finishChunk } from "../lib/openai-chunks"
|
||||
import { sseEvents } from "../lib/sse"
|
||||
|
||||
describe("Google Vertex providers", () => {
|
||||
|
|
@ -99,6 +100,44 @@ describe("Google Vertex providers", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("sends MaaS requests through Vertex Chat Completions", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: GoogleVertexChat.configure({
|
||||
accessToken: "vertex-token",
|
||||
location: "global",
|
||||
project: "vertex-project",
|
||||
}).model("deepseek-ai/deepseek-v3.2-maas"),
|
||||
prompt: "Say hello.",
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.gen(function* () {
|
||||
const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||
expect(request.url).toBe(
|
||||
"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/endpoints/openapi/chat/completions",
|
||||
)
|
||||
expect(request.headers.get("authorization")).toBe("Bearer vertex-token")
|
||||
expect(yield* Effect.promise(() => request.json())).toMatchObject({
|
||||
model: "deepseek-ai/deepseek-v3.2-maas",
|
||||
messages: [{ role: "user", content: "Say hello." }],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
})
|
||||
return input.respond(sseEvents(deltaChunk({ content: "Hello." }), finishChunk("stop")), {
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.text).toBe("Hello.")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("protects the Vertex Messages API version from body overlays", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.prepare(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue