mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 12:41:37 +03:00
feat(ai): add Vertex AI Interactions route (#54175)
This commit is contained in:
committed by
GitHub
Unverified
parent
229b422bd6
commit
d2905b8dda
@@ -32,6 +32,11 @@ Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployme
|
||||
`Google.configure({ apiKey }).interactions(modelID)` selects the Interactions API; `.model(modelID)` still selects
|
||||
GenerateContent. The package entrypoint is `@opencode/ai/providers/google/interactions`.
|
||||
|
||||
For Vertex AI, use `GoogleVertexInteractions.configure({ project, location: "global" }).model(modelID)` or
|
||||
`@opencode/ai/providers/google-vertex/interactions`. It uses Application Default Credentials by default, accepts
|
||||
an explicit `accessToken` or `auth` override, and supports Express mode with `apiKey`. Vertex metadata and raw usage
|
||||
live under `providerMetadata.vertex`. The default is still full-history replay with `store: false`.
|
||||
|
||||
```ts
|
||||
const model = Google.configure({ apiKey }).interactions("gemini-3.8-flash")
|
||||
const response = yield* LLM.generate({
|
||||
@@ -1316,13 +1321,14 @@ APIs have separate entrypoints:
|
||||
- `@opencode/ai/providers/openai-compatible/responses`
|
||||
- `@opencode/ai/providers/anthropic-compatible`
|
||||
- `@opencode/ai/providers/google-vertex/gemini`
|
||||
- `@opencode/ai/providers/google-vertex/interactions`
|
||||
- `@opencode/ai/providers/google-vertex/chat`
|
||||
- `@opencode/ai/providers/google-vertex/responses`
|
||||
- `@opencode/ai/providers/google-vertex/messages`
|
||||
|
||||
OpenAI Responses has one semantic route and uses HTTP by default. Advanced callers may supply a per-call WebSocket channel executor through `StreamOptions`; transport policy does not change provider settings, model identity, or route identity. The provider-neutral Open Responses implementation owns the reusable WebSocket request and event contract, while each provider opts in with its own handshake and connection policy. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; the Responses adapter at `providers/openai-compatible/responses` uses the provider-neutral Open Responses protocol. OpenAI Responses extends that baseline with OpenAI tools, event variants, metadata, and defaults. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
|
||||
|
||||
Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead selects express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present. Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
|
||||
Vertex Gemini, Vertex Interactions, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini and Interactions instead select express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present (explicit `project` or `location` selects ADC over an ambient key). Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
|
||||
|
||||
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { ProviderPackage } from "../provider-package.js"
|
||||
import { GoogleInteractions } from "../protocols/google-interactions.js"
|
||||
import { Auth } from "../route/auth.js"
|
||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
||||
import { Endpoint } from "../route/endpoint.js"
|
||||
import { Framing } from "../route/framing.js"
|
||||
import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/index.js"
|
||||
import { GoogleVertexShared } from "./google-vertex-shared.js"
|
||||
|
||||
export type GoogleInteractionsOptionsInput = GoogleInteractions.OptionsInput
|
||||
|
||||
export const id = ProviderID.make("google-vertex")
|
||||
|
||||
export type Config = RouteDefaultsInput &
|
||||
GoogleVertexShared.ApiKeyOptions & {
|
||||
readonly baseURL?: string
|
||||
readonly location?: string
|
||||
readonly project?: string
|
||||
readonly providerOptions?: GoogleInteractions.ProviderOptionsInput
|
||||
}
|
||||
|
||||
export type Settings = ProviderPackage.Settings &
|
||||
GoogleInteractions.ProviderOptionsInput &
|
||||
(
|
||||
| { readonly accessToken?: string; readonly apiKey?: never }
|
||||
| { readonly accessToken?: never; readonly apiKey?: string }
|
||||
) & {
|
||||
readonly baseURL?: string
|
||||
readonly location?: string
|
||||
readonly project?: string
|
||||
}
|
||||
|
||||
const route = Route.make({
|
||||
id: "google-vertex-interactions",
|
||||
provider: id,
|
||||
providerMetadataKey: "vertex",
|
||||
protocol: GoogleInteractions.protocol,
|
||||
endpoint: Endpoint.path("/interactions", { query: { alt: "sse" } }),
|
||||
auth: Auth.none,
|
||||
headers: ({ request }): Record<string, string> => {
|
||||
const serviceTier = request.providerOptions?.serviceTier
|
||||
return typeof serviceTier === "string" ? { "x-vertex-ai-llm-shared-request-type": serviceTier } : {}
|
||||
},
|
||||
framing: Framing.sse,
|
||||
})
|
||||
|
||||
export const routes = [route]
|
||||
|
||||
const configuredRoute = (input: Config) => {
|
||||
const {
|
||||
accessToken: _accessToken,
|
||||
apiKey: _apiKey,
|
||||
auth: _auth,
|
||||
baseURL,
|
||||
location: inputLocation,
|
||||
project: inputProject,
|
||||
...rest
|
||||
} = input
|
||||
const apiKey = GoogleVertexShared.apiKey(input)
|
||||
const location = GoogleVertexShared.location(inputLocation, "global")
|
||||
const project = GoogleVertexShared.project(inputProject)
|
||||
return route.with({
|
||||
...rest,
|
||||
endpoint: {
|
||||
baseURL:
|
||||
baseURL ??
|
||||
`https://${GoogleVertexShared.host(location)}/v1beta1/${apiKey === undefined ? `projects/${GoogleVertexShared.requireProject(project)}/` : ""}locations/${location}`,
|
||||
},
|
||||
auth: apiKey === undefined ? GoogleVertexShared.oauth(input, project) : Auth.header("x-goog-api-key", apiKey),
|
||||
})
|
||||
}
|
||||
|
||||
export const configure = (input: Config = {}) => {
|
||||
const route = configuredRoute(input)
|
||||
return {
|
||||
id,
|
||||
model: (modelID: string | ModelID) => route.model<GoogleInteractions.ProviderOptionsInput>({ id: modelID }),
|
||||
configure,
|
||||
}
|
||||
}
|
||||
|
||||
export const provider = {
|
||||
id,
|
||||
configure,
|
||||
}
|
||||
|
||||
export const model: ProviderPackage.Definition<Settings, GoogleInteractions.ProviderOptionsInput>["model"] = (
|
||||
modelID,
|
||||
{ accessToken, apiKey, baseURL, body, headers, location, project, ...providerOptions },
|
||||
) => {
|
||||
if (apiKey !== undefined && accessToken !== undefined)
|
||||
throw new ProviderConfigurationError({
|
||||
provider: id,
|
||||
message: "Google Vertex apiKey cannot be combined with accessToken or auth",
|
||||
})
|
||||
return configure({
|
||||
...(apiKey === undefined ? { accessToken } : { apiKey }),
|
||||
baseURL,
|
||||
headers,
|
||||
http: body === undefined ? undefined : { body },
|
||||
location,
|
||||
project,
|
||||
providerOptions,
|
||||
}).model(modelID)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { model } from "../google-vertex-interactions.js"
|
||||
export type { Settings } from "../google-vertex-interactions.js"
|
||||
@@ -22,6 +22,7 @@ export * as Fireworks from "./fireworks.js"
|
||||
export * as Google from "./google.js"
|
||||
export * as GoogleVertex from "./google-vertex.js"
|
||||
export * as GoogleVertexChat from "./google-vertex-chat.js"
|
||||
export * as GoogleVertexInteractions from "./google-vertex-interactions.js"
|
||||
export * as GoogleVertexMessages from "./google-vertex-messages.js"
|
||||
export * as GoogleVertexMistral from "./google-vertex-mistral.js"
|
||||
export * as GoogleVertexResponses from "./google-vertex-responses.js"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { LLM } from "../../src/index.js"
|
||||
import { GoogleVertexInteractions } from "../../src/providers.js"
|
||||
import { Auth } from "../../src/route/auth.js"
|
||||
import { model } from "../../src/providers/google-vertex/interactions.js"
|
||||
|
||||
const selected = GoogleVertexInteractions.configure({ project: "project" }).model("gemini-3.8-flash")
|
||||
|
||||
LLM.request({
|
||||
model: selected,
|
||||
prompt: "Hello",
|
||||
providerOptions: { thinkingLevel: "low", thinkingSummaries: "auto", serviceTier: "flex", store: false },
|
||||
})
|
||||
|
||||
LLM.request({
|
||||
model: selected,
|
||||
prompt: "Hello",
|
||||
// @ts-expect-error Interactions store must be boolean.
|
||||
providerOptions: { store: "yes" },
|
||||
})
|
||||
|
||||
GoogleVertexInteractions.configure({ auth: Auth.bearer("token"), project: "project" })
|
||||
GoogleVertexInteractions.configure({ apiKey: "key" })
|
||||
// @ts-expect-error API keys and OAuth credentials are mutually exclusive.
|
||||
GoogleVertexInteractions.configure({ apiKey: "key", accessToken: "token" })
|
||||
// @ts-expect-error API keys and auth overrides are mutually exclusive.
|
||||
GoogleVertexInteractions.configure({ apiKey: "key", auth: Auth.bearer("token") })
|
||||
model("gemini-3.8-flash", { project: "project", thinkingLevel: "low" })
|
||||
// @ts-expect-error Package settings also reject conflicting credentials.
|
||||
model("gemini-3.8-flash", { apiKey: "key", accessToken: "token" })
|
||||
@@ -451,6 +451,29 @@ describe("provider package entrypoints", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("maps Vertex Interactions package settings onto the shared protocol", async () => {
|
||||
const GoogleVertexInteractions = await import("@opencode/ai/providers/google-vertex/interactions")
|
||||
const selected = GoogleVertexInteractions.model("gemini-3.8-flash", {
|
||||
accessToken: "fixture",
|
||||
project: "vertex-project",
|
||||
location: "global",
|
||||
headers: { "x-application": "opencode" },
|
||||
body: { generation_config: { temperature: 0.5 } },
|
||||
thinkingLevel: "low",
|
||||
store: false,
|
||||
})
|
||||
expect(selected.route.id).toBe("google-vertex-interactions")
|
||||
expect(selected.route.protocol).toBe("google-interactions")
|
||||
expect(selected.route.endpoint).toMatchObject({
|
||||
baseURL: "https://aiplatform.googleapis.com/v1beta1/projects/vertex-project/locations/global",
|
||||
path: "/interactions",
|
||||
query: { alt: "sse" },
|
||||
})
|
||||
expect(selected.route.defaults.headers).toEqual({ "x-application": "opencode" })
|
||||
expect(selected.route.defaults.http?.body).toEqual({ generation_config: { temperature: 0.5 } })
|
||||
expect(selected.route.defaults.providerOptions).toEqual({ thinkingLevel: "low", store: false })
|
||||
})
|
||||
|
||||
test("rejects conflicting Vertex auth settings at runtime", async () => {
|
||||
const GoogleVertex = await import("@opencode/ai/providers/google-vertex")
|
||||
const GoogleVertexChat = await import("@opencode/ai/providers/google-vertex/chat")
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect, Redacted } from "effect"
|
||||
import { HttpClientRequest } from "effect/http"
|
||||
import { LLM, Message, ToolCallPart } from "../../src/index.js"
|
||||
import { GoogleVertexInteractions } from "../../src/providers.js"
|
||||
import { LLMClient } from "../../src/route.js"
|
||||
import { Auth } from "../../src/route/auth.js"
|
||||
import { compileRequest } from "../../src/route/client.js"
|
||||
import { it } from "../lib/effect.js"
|
||||
import { dynamicResponse, fixedResponse } from "../lib/http.js"
|
||||
import { sseEvents } from "../lib/sse.js"
|
||||
|
||||
describe("Google Vertex Interactions", () => {
|
||||
it.effect("streams through the global project endpoint with OAuth and Vertex service tiers", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: GoogleVertexInteractions.configure({
|
||||
accessToken: "vertex-token",
|
||||
location: "global",
|
||||
project: "vertex-project",
|
||||
}).model("gemini-3.8-flash"),
|
||||
system: "Be concise.",
|
||||
prompt: "Say hello.",
|
||||
providerOptions: { serviceTier: "flex", thinkingLevel: "low" },
|
||||
}),
|
||||
).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/v1beta1/projects/vertex-project/locations/global/interactions?alt=sse",
|
||||
)
|
||||
expect(request.method).toBe("POST")
|
||||
expect(request.headers.get("authorization")).toBe("Bearer vertex-token")
|
||||
expect(request.headers.has("x-goog-api-key")).toBe(false)
|
||||
expect(request.headers.get("x-vertex-ai-llm-shared-request-type")).toBe("flex")
|
||||
expect(yield* Effect.promise(() => request.json())).toMatchObject({
|
||||
model: "gemini-3.8-flash",
|
||||
input: [{ type: "user_input", content: [{ type: "text", text: "Say hello." }] }],
|
||||
system_instruction: "Be concise.",
|
||||
stream: true,
|
||||
store: false,
|
||||
service_tier: "flex",
|
||||
generation_config: { thinking_level: "low" },
|
||||
})
|
||||
return input.respond(
|
||||
sseEvents(
|
||||
{ event_type: "interaction.created", interaction: { id: "interaction_1" } },
|
||||
{ event_type: "interaction.status_update", interaction_id: "interaction_1", status: "in_progress" },
|
||||
{ event_type: "step.start", index: 0, step: { type: "model_output", content: [] } },
|
||||
{ event_type: "step.delta", index: 0, delta: { type: "text", text: "Hello." } },
|
||||
{ event_type: "step.stop", index: 0 },
|
||||
{
|
||||
event_type: "interaction.completed",
|
||||
interaction: {
|
||||
id: "interaction_1",
|
||||
status: "completed",
|
||||
usage: { total_input_tokens: 10, total_cached_tokens: 4, total_output_tokens: 2 },
|
||||
},
|
||||
},
|
||||
) + "event: done\ndata: {}\n\n",
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.text).toBe("Hello.")
|
||||
expect(response.usage?.cacheReadInputTokens).toBe(4)
|
||||
expect(response.usage?.providerMetadata).toEqual({
|
||||
vertex: { total_input_tokens: 10, total_cached_tokens: 4, total_output_tokens: 2 },
|
||||
})
|
||||
expect(response.events.at(-1)?.providerMetadata).toEqual({ vertex: { interactionId: "interaction_1" } })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("uses the project-free Express endpoint with API-key auth", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* LLMClient.generate(
|
||||
LLM.request({
|
||||
model: GoogleVertexInteractions.configure({ apiKey: "vertex-key" }).model("gemini-3.8-flash"),
|
||||
prompt: "Say hello.",
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.sync(() => {
|
||||
expect(input.request.url).toBe(
|
||||
"https://aiplatform.googleapis.com/v1beta1/locations/global/interactions?alt=sse",
|
||||
)
|
||||
expect(input.request.headers["x-goog-api-key"]).toBe("vertex-key")
|
||||
expect(input.request.headers).not.toHaveProperty("authorization")
|
||||
expect(input.request.headers).not.toHaveProperty("x-vertex-ai-llm-shared-request-type")
|
||||
return input.respond(
|
||||
sseEvents({ event_type: "interaction.completed", interaction: { status: "completed" } }),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("resolves an auth override on every request and honors a custom base URL without a project", () =>
|
||||
Effect.gen(function* () {
|
||||
const tokens = ["first-token", "refreshed-token"]
|
||||
const model = GoogleVertexInteractions.configure({
|
||||
baseURL: "https://vertex.test/v1beta1/locations/global",
|
||||
auth: Auth.effect(Effect.sync(() => Redacted.make(tokens.shift() ?? "unexpected-token"))).bearer(),
|
||||
}).model("gemini-3.8-flash")
|
||||
const headers: string[] = []
|
||||
yield* Effect.forEach(["First.", "Second."], (prompt) => LLMClient.generate(LLM.request({ model, prompt }))).pipe(
|
||||
Effect.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.sync(() => {
|
||||
expect(input.request.url).toBe("https://vertex.test/v1beta1/locations/global/interactions?alt=sse")
|
||||
headers.push(input.request.headers.authorization)
|
||||
return input.respond(
|
||||
sseEvents({ event_type: "interaction.completed", interaction: { status: "completed" } }),
|
||||
{ headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
expect(headers).toEqual(["Bearer first-token", "Bearer refreshed-token"])
|
||||
}),
|
||||
)
|
||||
|
||||
test("uses the jurisdictional host and lazily configures ADC", () => {
|
||||
const model = GoogleVertexInteractions.configure({ project: "vertex-project", location: "eu" }).model(
|
||||
"gemini-3.8-flash",
|
||||
)
|
||||
expect(model.route.endpoint.baseURL).toBe(
|
||||
"https://aiplatform.eu.rep.googleapis.com/v1beta1/projects/vertex-project/locations/eu",
|
||||
)
|
||||
expect(model.provider).toBe("google-vertex")
|
||||
expect(model.route.protocol).toBe("google-interactions")
|
||||
})
|
||||
|
||||
test("rejects conflicting credentials", () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error Exercise invalid credentials from untyped callers.
|
||||
GoogleVertexInteractions.configure({ apiKey: "key", accessToken: "token" }),
|
||||
).toThrow("Google Vertex apiKey cannot be combined with accessToken or auth")
|
||||
expect(() =>
|
||||
// @ts-expect-error Exercise invalid credentials from untyped callers.
|
||||
GoogleVertexInteractions.configure({ apiKey: "key", auth: Auth.bearer("token") }),
|
||||
).toThrow("Google Vertex apiKey cannot be combined with accessToken or auth")
|
||||
expect(() =>
|
||||
// @ts-expect-error Exercise invalid credentials from untyped callers.
|
||||
GoogleVertexInteractions.model("gemini-3.8-flash", { apiKey: "key", accessToken: "token" }),
|
||||
).toThrow("Google Vertex apiKey cannot be combined with accessToken or auth")
|
||||
})
|
||||
|
||||
test("requires a project unless a custom base URL or API key is provided", () => {
|
||||
expect(() => GoogleVertexInteractions.configure({ project: "", accessToken: "token" })).toThrow(
|
||||
"Google Vertex requires a project when baseURL is not configured",
|
||||
)
|
||||
})
|
||||
|
||||
it.effect("round-trips tool signatures under Vertex metadata without enabling storage", () =>
|
||||
Effect.gen(function* () {
|
||||
const model = GoogleVertexInteractions.configure({
|
||||
accessToken: "vertex-token",
|
||||
project: "vertex-project",
|
||||
}).model("gemini-3.8-flash")
|
||||
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Check the weather." })).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents(
|
||||
{
|
||||
event_type: "step.start",
|
||||
index: 0,
|
||||
step: { type: "function_call", id: "call_1", name: "lookup", arguments: { city: "Paris" } },
|
||||
},
|
||||
{ event_type: "step.delta", index: 0, delta: { type: "thought_signature", signature: "tool_sig" } },
|
||||
{ event_type: "step.stop", index: 0 },
|
||||
{ event_type: "interaction.completed", interaction: { id: "interaction_1", status: "requires_action" } },
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
const call = response.toolCalls[0]
|
||||
expect(call.providerMetadata).toEqual({ vertex: { interactionSignature: "tool_sig" } })
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model,
|
||||
messages: [
|
||||
Message.assistant([ToolCallPart.make(call)]),
|
||||
Message.tool({ id: call.id, name: call.name, result: { weather: "sunny" } }),
|
||||
],
|
||||
}),
|
||||
)
|
||||
expect(prepared.body).toMatchObject({
|
||||
store: false,
|
||||
input: [
|
||||
{ type: "function_call", id: "call_1", name: "lookup", arguments: { city: "Paris" }, signature: "tool_sig" },
|
||||
{ type: "function_result", call_id: "call_1", name: "lookup", result: { weather: "sunny" } },
|
||||
],
|
||||
})
|
||||
expect(prepared.body.previous_interaction_id).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -78,6 +78,10 @@ const builtins = new Map<string, () => Promise<unknown>>([
|
||||
["@opencode/ai/providers/google/interactions", () => import("@opencode/ai/providers/google/interactions")],
|
||||
["@opencode/ai/providers/google-vertex", () => import("@opencode/ai/providers/google-vertex")],
|
||||
["@opencode/ai/providers/google-vertex/gemini", () => import("@opencode/ai/providers/google-vertex/gemini")],
|
||||
[
|
||||
"@opencode/ai/providers/google-vertex/interactions",
|
||||
() => import("@opencode/ai/providers/google-vertex/interactions"),
|
||||
],
|
||||
["@opencode/ai/providers/google-vertex/chat", () => import("@opencode/ai/providers/google-vertex/chat")],
|
||||
["@opencode/ai/providers/google-vertex/responses", () => import("@opencode/ai/providers/google-vertex/responses")],
|
||||
["@opencode/ai/providers/google-vertex/messages", () => import("@opencode/ai/providers/google-vertex/messages")],
|
||||
|
||||
@@ -981,6 +981,7 @@ describe("ModelResolver", () => {
|
||||
const packages = [
|
||||
["@opencode/ai/providers/google-vertex", "accessToken"],
|
||||
["@opencode/ai/providers/google-vertex/gemini", "accessToken"],
|
||||
["@opencode/ai/providers/google-vertex/interactions", "accessToken"],
|
||||
["@opencode/ai/providers/google-vertex/chat", "accessToken"],
|
||||
["@opencode/ai/providers/google-vertex/responses", "accessToken"],
|
||||
["@opencode/ai/providers/google-vertex/messages", "accessToken"],
|
||||
|
||||
@@ -16,6 +16,7 @@ describe("Provider", () => {
|
||||
"@opencode/ai/providers/fireworks",
|
||||
"@opencode/ai/providers/google-vertex",
|
||||
"@opencode/ai/providers/google-vertex/gemini",
|
||||
"@opencode/ai/providers/google-vertex/interactions",
|
||||
"@opencode/ai/providers/google-vertex/chat",
|
||||
"@opencode/ai/providers/google-vertex/responses",
|
||||
"@opencode/ai/providers/google-vertex/messages",
|
||||
|
||||
Reference in New Issue
Block a user