| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- import { AISDK } from "@opencode-ai/core/aisdk"
- import { describe, expect, it as bun_it } from "bun:test"
- import { Effect } from "effect"
- import { Model } from "@opencode-ai/core/model"
- import { Plugin } from "@opencode-ai/core/plugin"
- import { PluginHost } from "@opencode-ai/core/plugin/host"
- import { SnowflakeCortexPlugin, cortexFetch } from "@opencode-ai/core/plugin/provider/snowflake-cortex"
- import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
- import { Provider } from "@opencode-ai/core/provider"
- import { testEffect } from "../lib/effect"
- import { PluginTestLayer } from "./fixture"
- const it = testEffect(PluginTestLayer)
- const addPlugin = Effect.fn(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- const host = yield* PluginHost.make(plugin)
- yield* SnowflakeCortexPlugin.effect(host)
- })
- function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () => Effect.Effect<A, E, R>) {
- return Effect.acquireUseRelease(
- Effect.sync(() => {
- const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
- Object.entries(vars).forEach(([key, value]) => {
- if (value === undefined) delete process.env[key]
- else process.env[key] = value
- })
- return previous
- }),
- effect,
- (previous) =>
- Effect.sync(() => {
- Object.entries(previous).forEach(([key, value]) => {
- if (value === undefined) delete process.env[key]
- else process.env[key] = value
- })
- }),
- )
- }
- describe("SnowflakeCortexPlugin", () => {
- it.effect("is registered in ProviderPlugins before OpenAICompatiblePlugin", () =>
- Effect.sync(() => {
- expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.snowflake-cortex")
- const ids = ProviderPlugins.map((p) => p.id)
- expect(ids.indexOf("opencode.provider.snowflake-cortex")).toBeLessThan(
- ids.indexOf("opencode.provider.openai-compatible"),
- )
- }),
- )
- it.effect("ignores non-snowflake-cortex providers", () =>
- Effect.gen(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- yield* addPlugin()
- const result = yield* aisdk.runSDK({
- model: Model.Info.make({
- ...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-4")),
- modelID: Model.ID.make("gpt-4"),
- package: "aisdk:test-provider",
- }),
- package: "@ai-sdk/openai",
- options: { name: "openai" },
- })
- expect(result.sdk).toBeUndefined()
- }),
- )
- it.effect("creates SDK for snowflake-cortex using SNOWFLAKE_CORTEX_PAT env var", () =>
- withEnv({ SNOWFLAKE_CORTEX_PAT: "test-pat" }, () =>
- Effect.gen(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- yield* addPlugin()
- const result = yield* aisdk.runSDK({
- model: Model.Info.make({
- ...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
- modelID: Model.ID.make("claude-sonnet-4-6"),
- package: "aisdk:test-provider",
- }),
- package: "@ai-sdk/openai-compatible",
- options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" },
- })
- expect(result.sdk).toBeDefined()
- }),
- ),
- )
- it.effect("falls back to options.apiKey when SNOWFLAKE_CORTEX_PAT env var is absent", () =>
- withEnv({ SNOWFLAKE_CORTEX_PAT: undefined }, () =>
- Effect.gen(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- yield* addPlugin()
- const result = yield* aisdk.runSDK({
- model: Model.Info.make({
- ...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
- modelID: Model.ID.make("claude-sonnet-4-6"),
- package: "aisdk:test-provider",
- }),
- package: "@ai-sdk/openai-compatible",
- options: {
- name: "snowflake-cortex",
- baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1",
- apiKey: "options-pat",
- },
- })
- expect(result.sdk).toBeDefined()
- }),
- ),
- )
- it.effect("uses SNOWFLAKE_CORTEX_TOKEN env var", () =>
- withEnv({ SNOWFLAKE_CORTEX_TOKEN: "oauth-token", SNOWFLAKE_CORTEX_PAT: undefined }, () =>
- Effect.gen(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- yield* addPlugin()
- const result = yield* aisdk.runSDK({
- model: Model.Info.make({
- ...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
- modelID: Model.ID.make("claude-sonnet-4-6"),
- package: "aisdk:test-provider",
- }),
- package: "@ai-sdk/openai-compatible",
- options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" },
- })
- expect(result.sdk).toBeDefined()
- }),
- ),
- )
- it.effect("falls back to options.token when no Snowflake env token is set", () =>
- withEnv({ SNOWFLAKE_CORTEX_TOKEN: undefined, SNOWFLAKE_CORTEX_PAT: undefined }, () =>
- Effect.gen(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- yield* addPlugin()
- const result = yield* aisdk.runSDK({
- model: Model.Info.make({
- ...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
- modelID: Model.ID.make("claude-sonnet-4-6"),
- package: "aisdk:test-provider",
- }),
- package: "@ai-sdk/openai-compatible",
- options: {
- name: "snowflake-cortex",
- baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1",
- token: "options-token",
- },
- })
- expect(result.sdk).toBeDefined()
- }),
- ),
- )
- it.effect("sets includeUsage on the SDK options", () =>
- withEnv({ SNOWFLAKE_CORTEX_PAT: "test-pat" }, () =>
- Effect.gen(function* () {
- const plugin = yield* Plugin.Service
- const aisdk = yield* AISDK.Service
- yield* addPlugin()
- const result = yield* aisdk.runSDK({
- model: Model.Info.make({
- ...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
- modelID: Model.ID.make("claude-sonnet-4-6"),
- package: "aisdk:test-provider",
- }),
- package: "@ai-sdk/openai-compatible",
- options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" },
- })
- expect(result.options.includeUsage).toBe(true)
- }),
- ),
- )
- })
- type FetchLike = (url: string | URL | Request, init?: RequestInit) => Promise<Response>
- describe("cortexFetch", () => {
- bun_it("rewrites max_tokens to max_completion_tokens", async () => {
- const captured: RequestInit[] = []
- const upstream: FetchLike = async (_url, init) => {
- captured.push(init ?? {})
- return new Response("{}", { status: 200 })
- }
- await cortexFetch(upstream)("https://test", {
- method: "POST",
- body: JSON.stringify({ model: "claude-sonnet-4-6", max_tokens: 1024 }),
- })
- const body = JSON.parse(captured[0].body as string)
- expect(body.max_completion_tokens).toBe(1024)
- expect(body.max_tokens).toBeUndefined()
- })
- bun_it("preserves body when max_tokens is absent", async () => {
- const captured: RequestInit[] = []
- const upstream: FetchLike = async (_url, init) => {
- captured.push(init ?? {})
- return new Response("{}", { status: 200 })
- }
- const original = JSON.stringify({ model: "claude-sonnet-4-6", temperature: 0.7 })
- await cortexFetch(upstream)("https://test", { method: "POST", body: original })
- expect(captured[0].body).toBe(original)
- })
- bun_it("treats 400 'conversation complete' as a stop response", async () => {
- const upstream: FetchLike = async () =>
- new Response(JSON.stringify({ message: "Conversation complete" }), {
- status: 400,
- headers: { "content-type": "application/json" },
- })
- const response = await cortexFetch(upstream)("https://test", {})
- expect(response.status).toBe(200)
- const data = (await response.json()) as { choices: { finish_reason: string }[] }
- expect(data.choices[0].finish_reason).toBe("stop")
- })
- bun_it("passes through other 400 errors unchanged", async () => {
- const upstream: FetchLike = async () =>
- new Response(JSON.stringify({ message: "Invalid model" }), {
- status: 400,
- headers: { "content-type": "application/json" },
- })
- const response = await cortexFetch(upstream)("https://test", {})
- expect(response.status).toBe(400)
- })
- bun_it("passes through non-400 errors unchanged", async () => {
- const upstream: FetchLike = async () => new Response("Unauthorized", { status: 401 })
- const response = await cortexFetch(upstream)("https://test", {})
- expect(response.status).toBe(401)
- })
- bun_it("handles invalid JSON body gracefully without throwing", async () => {
- const captured: RequestInit[] = []
- const upstream: FetchLike = async (_url, init) => {
- captured.push(init ?? {})
- return new Response("{}", { status: 200 })
- }
- const invalidBody = "{ not json }"
- await cortexFetch(upstream)("https://test", { method: "POST", body: invalidBody })
- expect(captured[0].body).toBe(invalidBody)
- })
- bun_it("rewrites role:'' to role:'assistant' in streaming SSE chunks", async () => {
- const chunk = `data: {"choices":[{"delta":{"role":"","content":"Hi"},"index":0}]}\n\n`
- const upstream: FetchLike = async () =>
- new Response(
- new ReadableStream({
- start: (ctrl) => {
- ctrl.enqueue(new TextEncoder().encode(chunk))
- ctrl.close()
- },
- }),
- {
- status: 200,
- headers: { "content-type": "text/event-stream" },
- },
- )
- const response = await cortexFetch(upstream)("https://test", {})
- const text = await response.text()
- expect(text).toContain('"role":"assistant"')
- expect(text).not.toContain('"role":""')
- })
- })
|