|
|
@@ -1,132 +1,122 @@
|
|
|
-import { test, expect, afterEach } from "bun:test"
|
|
|
-import path from "path"
|
|
|
-
|
|
|
-import { tmpdir } from "../fixture/fixture"
|
|
|
-import { WithInstance } from "../../src/project/with-instance"
|
|
|
+import { expect } from "bun:test"
|
|
|
import { Provider } from "../../src/provider/provider"
|
|
|
import { ProviderID } from "../../src/provider/schema"
|
|
|
-import { Env } from "../../src/env"
|
|
|
import { Effect } from "effect"
|
|
|
-import { AppRuntime } from "../../src/effect/app-runtime"
|
|
|
-import { makeRuntime } from "../../src/effect/run-service"
|
|
|
+import { testEffect } from "../lib/effect"
|
|
|
|
|
|
-const envRuntime = makeRuntime(Env.Service, Env.defaultLayer)
|
|
|
-const set = (k: string, v: string) => envRuntime.runSync((svc) => svc.set(k, v))
|
|
|
+const DIGITALOCEAN = ProviderID.make("digitalocean")
|
|
|
+const it = testEffect(Provider.defaultLayer)
|
|
|
|
|
|
-async function list() {
|
|
|
- return AppRuntime.runPromise(
|
|
|
- Effect.gen(function* () {
|
|
|
- const provider = yield* Provider.Service
|
|
|
- return yield* provider.list()
|
|
|
+const withEnv = <A, E, R>(values: Record<string, string>, effect: Effect.Effect<A, E, R>) =>
|
|
|
+ Effect.acquireUseRelease(
|
|
|
+ Effect.sync(() => {
|
|
|
+ const previous = Object.fromEntries(Object.keys(values).map((key) => [key, process.env[key]] as const))
|
|
|
+ Object.assign(process.env, values)
|
|
|
+ return previous
|
|
|
}),
|
|
|
+ () => effect,
|
|
|
+ (previous) =>
|
|
|
+ Effect.sync(() => {
|
|
|
+ for (const [key, value] of Object.entries(previous)) {
|
|
|
+ if (value === undefined) delete process.env[key]
|
|
|
+ else process.env[key] = value
|
|
|
+ }
|
|
|
+ }),
|
|
|
)
|
|
|
-}
|
|
|
-
|
|
|
-const DIGITALOCEAN = ProviderID.make("digitalocean")
|
|
|
|
|
|
-const originalAuthContent = process.env.OPENCODE_AUTH_CONTENT
|
|
|
-afterEach(() => {
|
|
|
- if (originalAuthContent === undefined) delete process.env.OPENCODE_AUTH_CONTENT
|
|
|
- else process.env.OPENCODE_AUTH_CONTENT = originalAuthContent
|
|
|
-})
|
|
|
-
|
|
|
-function injectAuth(metadata: Record<string, string> | undefined) {
|
|
|
- process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({
|
|
|
- digitalocean: {
|
|
|
- type: "api",
|
|
|
- key: "sk_do_test",
|
|
|
- ...(metadata ? { metadata } : {}),
|
|
|
+const withAuth = <A, E, R>(metadata: Record<string, string> | undefined, effect: Effect.Effect<A, E, R>) =>
|
|
|
+ withEnv(
|
|
|
+ {
|
|
|
+ OPENCODE_AUTH_CONTENT: JSON.stringify({
|
|
|
+ digitalocean: {
|
|
|
+ type: "api",
|
|
|
+ key: "sk_do_test",
|
|
|
+ ...(metadata ? { metadata } : {}),
|
|
|
+ },
|
|
|
+ }),
|
|
|
},
|
|
|
- })
|
|
|
-}
|
|
|
+ effect,
|
|
|
+ )
|
|
|
|
|
|
-test("digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
|
|
|
- },
|
|
|
- })
|
|
|
- await WithInstance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- set("DIGITALOCEAN_ACCESS_TOKEN", "test-token")
|
|
|
- const providers = await list()
|
|
|
- expect(providers[DIGITALOCEAN]).toBeDefined()
|
|
|
- expect(providers[DIGITALOCEAN].source).toBe("env")
|
|
|
- const baseModel = Object.values(providers[DIGITALOCEAN].models)[0]
|
|
|
- expect(baseModel.api.url).toBe("https://inference.do-ai.run/v1")
|
|
|
- expect(baseModel.api.npm).toBe("@ai-sdk/openai-compatible")
|
|
|
- const routerEntries = Object.keys(providers[DIGITALOCEAN].models).filter((id) => id.startsWith("router:"))
|
|
|
- expect(routerEntries.length).toBe(0)
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+it.instance(
|
|
|
+ "digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN",
|
|
|
+ () =>
|
|
|
+ withEnv(
|
|
|
+ { DIGITALOCEAN_ACCESS_TOKEN: "test-token" },
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const provider = yield* Provider.Service
|
|
|
+ const providers = yield* provider.list()
|
|
|
+ expect(providers[DIGITALOCEAN]).toBeDefined()
|
|
|
+ expect(providers[DIGITALOCEAN].source).toBe("env")
|
|
|
+ const baseModel = Object.values(providers[DIGITALOCEAN].models)[0]
|
|
|
+ expect(baseModel.api.url).toBe("https://inference.do-ai.run/v1")
|
|
|
+ expect(baseModel.api.npm).toBe("@ai-sdk/openai-compatible")
|
|
|
+ const routerEntries = Object.keys(providers[DIGITALOCEAN].models).filter((id) => id.startsWith("router:"))
|
|
|
+ expect(routerEntries.length).toBe(0)
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ { config: {} },
|
|
|
+)
|
|
|
|
|
|
-test("digitalocean provider.models surfaces cached routers from auth metadata", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
|
|
|
- },
|
|
|
- })
|
|
|
- injectAuth({
|
|
|
- routers: JSON.stringify([
|
|
|
- { name: "my-router", uuid: "11f1499a-aaaa-bbbb-cccc-4e013e2ddde4" },
|
|
|
- { name: "other-router", uuid: "22f1499a-aaaa-bbbb-cccc-4e013e2ddde4" },
|
|
|
- ]),
|
|
|
- routers_fetched_at: String(Date.now()),
|
|
|
- oauth_access: "doo_v1_test",
|
|
|
- oauth_expires: String(Date.now() + 60 * 60 * 1000),
|
|
|
- })
|
|
|
- await WithInstance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- const providers = await list()
|
|
|
- const models = providers[DIGITALOCEAN].models
|
|
|
- expect(models["router:my-router"]).toBeDefined()
|
|
|
- expect(models["router:my-router"].api.id).toBe("router:my-router")
|
|
|
- expect(models["router:my-router"].api.url).toBe("https://inference.do-ai.run/v1")
|
|
|
- expect(models["router:my-router"].api.npm).toBe("@ai-sdk/openai-compatible")
|
|
|
- expect(models["router:other-router"]).toBeDefined()
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+it.instance(
|
|
|
+ "digitalocean provider.models surfaces cached routers from auth metadata",
|
|
|
+ () =>
|
|
|
+ withAuth(
|
|
|
+ {
|
|
|
+ routers: JSON.stringify([
|
|
|
+ { name: "my-router", uuid: "11f1499a-aaaa-bbbb-cccc-4e013e2ddde4" },
|
|
|
+ { name: "other-router", uuid: "22f1499a-aaaa-bbbb-cccc-4e013e2ddde4" },
|
|
|
+ ]),
|
|
|
+ routers_fetched_at: String(Date.now()),
|
|
|
+ oauth_access: "doo_v1_test",
|
|
|
+ oauth_expires: String(Date.now() + 60 * 60 * 1000),
|
|
|
+ },
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const provider = yield* Provider.Service
|
|
|
+ const providers = yield* provider.list()
|
|
|
+ const models = providers[DIGITALOCEAN].models
|
|
|
+ expect(models["router:my-router"]).toBeDefined()
|
|
|
+ expect(models["router:my-router"].api.id).toBe("router:my-router")
|
|
|
+ expect(models["router:my-router"].api.url).toBe("https://inference.do-ai.run/v1")
|
|
|
+ expect(models["router:my-router"].api.npm).toBe("@ai-sdk/openai-compatible")
|
|
|
+ expect(models["router:other-router"]).toBeDefined()
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ { config: {} },
|
|
|
+)
|
|
|
|
|
|
-test("digitalocean provider.models skips refresh when oauth bearer is expired", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
|
|
|
- },
|
|
|
- })
|
|
|
- injectAuth({
|
|
|
- routers: JSON.stringify([{ name: "stale-router", uuid: "stale" }]),
|
|
|
- routers_fetched_at: "0",
|
|
|
- oauth_access: "doo_v1_expired",
|
|
|
- oauth_expires: "1",
|
|
|
- })
|
|
|
- await WithInstance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- const providers = await list()
|
|
|
- const models = providers[DIGITALOCEAN].models
|
|
|
- expect(models["router:stale-router"]).toBeDefined()
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+it.instance(
|
|
|
+ "digitalocean provider.models skips refresh when oauth bearer is expired",
|
|
|
+ () =>
|
|
|
+ withAuth(
|
|
|
+ {
|
|
|
+ routers: JSON.stringify([{ name: "stale-router", uuid: "stale" }]),
|
|
|
+ routers_fetched_at: "0",
|
|
|
+ oauth_access: "doo_v1_expired",
|
|
|
+ oauth_expires: "1",
|
|
|
+ },
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const provider = yield* Provider.Service
|
|
|
+ const providers = yield* provider.list()
|
|
|
+ const models = providers[DIGITALOCEAN].models
|
|
|
+ expect(models["router:stale-router"]).toBeDefined()
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ { config: {} },
|
|
|
+)
|
|
|
|
|
|
-test("digitalocean provider.models passes through base models when no auth metadata", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" }))
|
|
|
- },
|
|
|
- })
|
|
|
- await WithInstance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- set("DIGITALOCEAN_ACCESS_TOKEN", "test-token")
|
|
|
- const providers = await list()
|
|
|
- const models = providers[DIGITALOCEAN].models
|
|
|
- expect(Object.keys(models).length).toBeGreaterThan(0)
|
|
|
- expect(Object.keys(models).filter((id) => id.startsWith("router:")).length).toBe(0)
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+it.instance(
|
|
|
+ "digitalocean provider.models passes through base models when no auth metadata",
|
|
|
+ () =>
|
|
|
+ withEnv(
|
|
|
+ { DIGITALOCEAN_ACCESS_TOKEN: "test-token" },
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const provider = yield* Provider.Service
|
|
|
+ const providers = yield* provider.list()
|
|
|
+ const models = providers[DIGITALOCEAN].models
|
|
|
+ expect(Object.keys(models).length).toBeGreaterThan(0)
|
|
|
+ expect(Object.keys(models).filter((id) => id.startsWith("router:")).length).toBe(0)
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ { config: {} },
|
|
|
+)
|