refactor(core): simplify integration credentials (#31968)

This commit is contained in:
Dax 2026-06-12 02:15:25 -04:00 committed by GitHub
commit 30aec297d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
112 changed files with 2467 additions and 47728 deletions

View file

@ -2,7 +2,7 @@ import path from "path"
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Connector } from "@opencode-ai/core/connector"
import { Integration } from "@opencode-ai/core/integration"
import { Credential } from "@opencode-ai/core/credential"
import { Database } from "@opencode-ai/core/database/database"
import { EventV2 } from "@opencode-ai/core/event"
@ -23,14 +23,25 @@ const locationLayer = Layer.succeed(
)
const plugins = PluginV2.layer.pipe(Layer.provide(events))
const policy = Policy.layer.pipe(Layer.provide(locationLayer))
const credentials = Credential.layer.pipe(Layer.provide(Database.layerFromPath(":memory:")), Layer.provide(events))
const catalog = Catalog.layer.pipe(Layer.provide(Layer.mergeAll(events, locationLayer, plugins, policy, credentials)))
const connectors = Connector.locationLayer.pipe(Layer.provide(credentials), Layer.provide(events))
const layer = Layer.mergeAll(catalog, connectors, credentials, events, locationLayer, plugins)
const connections = Credential.layer.pipe(
Layer.fresh,
Layer.provide(Database.layerFromPath(":memory:").pipe(Layer.fresh)),
Layer.provide(events),
)
const catalog = Catalog.layer.pipe(Layer.provide(Layer.mergeAll(events, locationLayer, plugins, policy, connections)))
const integrations = Integration.locationLayer.pipe(Layer.provide(events), Layer.provide(connections))
const layer = Layer.mergeAll(
catalog.pipe(Layer.provide(connections)),
integrations,
connections,
events,
locationLayer,
plugins,
)
const it = testEffect(layer)
describe("ModelsDevPlugin", () => {
it.effect("registers key connectors for providers with environment variables", () =>
it.effect("registers key methods for providers with environment variables", () =>
Effect.acquireUseRelease(
Effect.sync(() => {
const previous = {
@ -44,14 +55,19 @@ describe("ModelsDevPlugin", () => {
() =>
Effect.gen(function* () {
yield* ModelsDevPlugin.effect
const connectors = yield* Connector.Service
expect(yield* connectors.list()).toEqual([
new Connector.Info({
id: Connector.ID.make("acme"),
const integrations = yield* Integration.Service
expect(yield* integrations.list()).toEqual([
new Integration.Info({
id: Integration.ID.make("acme"),
name: "Acme",
methods: [
new Connector.KeyMethod({ id: Connector.MethodID.make("api-key"), type: "key", label: "API Key" }),
new Integration.KeyMethod({ type: "key" }),
new Integration.EnvMethod({
type: "env",
names: ["ACME_API_KEY"],
}),
],
connections: [],
}),
])
}).pipe(Effect.provide(ModelsDev.defaultLayer)),

View file

@ -1,7 +1,7 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { Credential } from "@opencode-ai/core/credential"
import { Connector } from "@opencode-ai/core/connector"
import { Integration } from "@opencode-ai/core/integration"
import { Database } from "@opencode-ai/core/database/database"
import { Catalog } from "@opencode-ai/core/catalog"
import { EventV2 } from "@opencode-ai/core/event"
@ -14,14 +14,15 @@ import { location } from "../fixture/location"
import { testEffect } from "../lib/effect"
import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper"
const database = Database.layerFromPath(":memory:").pipe(Layer.fresh)
const preferences = Credential.layer.pipe(Layer.provide(database))
const accounts = Layer.merge(
Credential.layer.pipe(Layer.provide(database), Layer.provide(preferences), Layer.provide(EventV2.defaultLayer)),
preferences,
)
const itWithAccount = testEffect(
Catalog.locationLayer.pipe(
Layer.provideMerge(
Credential.layer.pipe(
Layer.provide(Database.layerFromPath(":memory:").pipe(Layer.fresh)),
Layer.provide(EventV2.defaultLayer),
),
),
Layer.provideMerge(accounts),
Layer.provideMerge(EventV2.defaultLayer),
Layer.provideMerge(
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") }))),
@ -83,8 +84,7 @@ describe("AzurePlugin", () => {
const credentials = yield* Credential.Service
const catalog = yield* Catalog.Service
yield* credentials.create({
connectorID: Connector.ID.make("azure"),
methodID: Connector.MethodID.make("api-key"),
integrationID: Integration.ID.make("azure"),
value: new Credential.Key({
type: "key",
key: "key",

View file

@ -1,7 +1,7 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { Credential } from "@opencode-ai/core/credential"
import { Connector } from "@opencode-ai/core/connector"
import { Integration } from "@opencode-ai/core/integration"
import { Database } from "@opencode-ai/core/database/database"
import { Catalog } from "@opencode-ai/core/catalog"
import { Location } from "@opencode-ai/core/location"
@ -15,14 +15,15 @@ import { location } from "../fixture/location"
import { testEffect } from "../lib/effect"
import { fakeSelectorSdk, it, model, npmLayer, withEnv } from "./provider-helper"
const database = Database.layerFromPath(":memory:").pipe(Layer.fresh)
const preferences = Credential.layer.pipe(Layer.provide(database))
const accounts = Layer.merge(
Credential.layer.pipe(Layer.provide(database), Layer.provide(preferences), Layer.provide(EventV2.defaultLayer)),
preferences,
)
const itWithAccount = testEffect(
Catalog.locationLayer.pipe(
Layer.provideMerge(
Credential.layer.pipe(
Layer.provide(Database.layerFromPath(":memory:").pipe(Layer.fresh)),
Layer.provide(EventV2.defaultLayer),
),
),
Layer.provideMerge(accounts),
Layer.provideMerge(EventV2.defaultLayer),
Layer.provideMerge(
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") }))),
@ -137,8 +138,7 @@ describe("CloudflareWorkersAIPlugin", () => {
const credentials = yield* Credential.Service
const catalog = yield* Catalog.Service
yield* credentials.create({
connectorID: Connector.ID.make("cloudflare-workers-ai"),
methodID: Connector.MethodID.make("api-key"),
integrationID: Integration.ID.make("cloudflare-workers-ai"),
value: new Credential.Key({
type: "key",
key: "account-key",

View file

@ -1,7 +1,7 @@
import { describe, expect, mock } from "bun:test"
import { Effect, Layer } from "effect"
import { Credential } from "@opencode-ai/core/credential"
import { Connector } from "@opencode-ai/core/connector"
import { Integration } from "@opencode-ai/core/integration"
import { Database } from "@opencode-ai/core/database/database"
import { Catalog } from "@opencode-ai/core/catalog"
import { EventV2 } from "@opencode-ai/core/event"
@ -15,6 +15,12 @@ import { testEffect } from "../lib/effect"
import { it, model, npmLayer, withEnv } from "./provider-helper"
const gitlabSDKOptions: Record<string, unknown>[] = []
const database = Database.layerFromPath(":memory:").pipe(Layer.fresh)
const preferences = Credential.layer.pipe(Layer.provide(database))
const accounts = Layer.merge(
Credential.layer.pipe(Layer.provide(database), Layer.provide(preferences), Layer.provide(EventV2.defaultLayer)),
preferences,
)
void mock.module("gitlab-ai-provider", () => ({
VERSION: "test-version",
@ -31,12 +37,7 @@ void mock.module("gitlab-ai-provider", () => ({
const itWithAccount = testEffect(
Catalog.locationLayer.pipe(
Layer.provideMerge(
Credential.layer.pipe(
Layer.provide(Database.layerFromPath(":memory:").pipe(Layer.fresh)),
Layer.provide(EventV2.defaultLayer),
),
),
Layer.provideMerge(accounts),
Layer.provideMerge(EventV2.defaultLayer),
Layer.provideMerge(
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("/") }))),
@ -174,8 +175,7 @@ describe("GitLabPlugin", () => {
const credentials = yield* Credential.Service
const catalog = yield* Catalog.Service
yield* credentials.create({
connectorID: Connector.ID.make("gitlab"),
methodID: Connector.MethodID.make("api-key"),
integrationID: Integration.ID.make("gitlab"),
value: new Credential.Key({ type: "key", key: "account-token" }),
})
yield* plugin.add(GitLabPlugin)
@ -208,10 +208,10 @@ describe("GitLabPlugin", () => {
const credentials = yield* Credential.Service
const catalog = yield* Catalog.Service
yield* credentials.create({
connectorID: Connector.ID.make("gitlab"),
methodID: Connector.MethodID.make("oauth"),
integrationID: Integration.ID.make("gitlab"),
value: new Credential.OAuth({
type: "oauth",
methodID: Integration.MethodID.make("oauth"),
refresh: "refresh-token",
access: "account-oauth-token",
expires: 9999999999999,

View file

@ -3,7 +3,7 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"
import { expect } from "bun:test"
import { Effect, Layer, Option } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Connector } from "@opencode-ai/core/connector"
import { Integration } from "@opencode-ai/core/integration"
import { Credential } from "@opencode-ai/core/credential"
import { EventV2 } from "@opencode-ai/core/event"
import { Location } from "@opencode-ai/core/location"
@ -48,15 +48,24 @@ export const catalogLayer = Layer.succeed(
}),
)
const connectors = Connector.locationLayer.pipe(
const integrations = Integration.locationLayer.pipe(
Layer.provide(EventV2.defaultLayer),
Layer.provide(Layer.mock(Credential.Service)({ create: () => Effect.die("unexpected credential creation") })),
Layer.provide(
Layer.mock(Credential.Service)({
create: () => Effect.die("unexpected credential creation"),
list: () => Effect.succeed([]),
}),
),
)
export const it = testEffect(
Catalog.locationLayer.pipe(
Layer.provideMerge(connectors),
Layer.provideMerge(Layer.mock(Credential.Service)({ activeAll: () => Effect.succeed(new Map()) })),
Layer.provideMerge(integrations),
Layer.provideMerge(
Layer.mock(Credential.Service)({
all: () => Effect.succeed([]),
}),
),
Layer.provideMerge(EventV2.defaultLayer),
Layer.provideMerge(locationLayer),
Layer.provideMerge(npmLayer),

View file

@ -1,17 +1,17 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Connector } from "@opencode-ai/core/connector"
import { Integration } from "@opencode-ai/core/integration"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { OpenAIPlugin } from "@opencode-ai/core/plugin/provider/openai"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { fakeSelectorSdk, it, model, provider } from "./provider-helper"
function add(plugin: PluginV2.Interface, connectors: Connector.Interface) {
function add(plugin: PluginV2.Interface, integrations: Integration.Interface) {
return plugin.add({
...OpenAIPlugin,
effect: OpenAIPlugin.effect.pipe(Effect.provideService(Connector.Service, connectors)),
effect: OpenAIPlugin.effect.pipe(Effect.provideService(Integration.Service, integrations)),
})
}
@ -19,15 +19,15 @@ describe("OpenAIPlugin", () => {
it.effect("registers browser and headless ChatGPT OAuth methods", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
yield* add(plugin, yield* Connector.Service)
expect((yield* (yield* Connector.Service).get(Connector.ID.make("openai")))?.methods).toEqual([
new Connector.OAuthMethod({
id: Connector.MethodID.make("chatgpt-browser"),
yield* add(plugin, yield* Integration.Service)
expect((yield* (yield* Integration.Service).get(Integration.ID.make("openai")))?.methods).toEqual([
new Integration.OAuthMethod({
id: Integration.MethodID.make("chatgpt-browser"),
type: "oauth",
label: "ChatGPT Pro/Plus (browser)",
}),
new Connector.OAuthMethod({
id: Connector.MethodID.make("chatgpt-headless"),
new Integration.OAuthMethod({
id: Integration.MethodID.make("chatgpt-headless"),
type: "oauth",
label: "ChatGPT Pro/Plus (headless)",
}),
@ -38,7 +38,7 @@ describe("OpenAIPlugin", () => {
it.effect("creates an OpenAI SDK for @ai-sdk/openai using the provider ID as SDK name", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
yield* add(plugin, yield* Connector.Service)
yield* add(plugin, yield* Integration.Service)
const result = yield* plugin.trigger(
"aisdk.sdk",
{
@ -55,7 +55,7 @@ describe("OpenAIPlugin", () => {
it.effect("ignores non-OpenAI SDK packages", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
yield* add(plugin, yield* Connector.Service)
yield* add(plugin, yield* Integration.Service)
const result = yield* plugin.trigger(
"aisdk.sdk",
{ model: model("openai", "gpt-5"), package: "@ai-sdk/openai-compatible", options: { name: "openai" } },
@ -69,7 +69,7 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const calls: string[] = []
yield* add(plugin, yield* Connector.Service)
yield* add(plugin, yield* Integration.Service)
const result = yield* plugin.trigger(
"aisdk.language",
{
@ -90,7 +90,7 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const calls: string[] = []
yield* add(plugin, yield* Connector.Service)
yield* add(plugin, yield* Integration.Service)
const result = yield* plugin.trigger(
"aisdk.language",
{ model: model("anthropic", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} },
@ -105,7 +105,7 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* add(plugin, yield* Connector.Service)
yield* add(plugin, yield* Integration.Service)
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("openai", { api: { type: "aisdk", package: "@ai-sdk/openai" } })
@ -124,7 +124,7 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const catalog = yield* Catalog.Service
yield* add(plugin, yield* Connector.Service)
yield* add(plugin, yield* Integration.Service)
const transform = yield* catalog.transform()
yield* transform((catalog) => {
const item = provider("custom-openai")