chore: generate
This commit is contained in:
@@ -93,11 +93,7 @@ export const layer = Layer.effect(
|
|||||||
const integrations = yield* Integration.Service
|
const integrations = yield* Integration.Service
|
||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
|
|
||||||
const available = (
|
const available = (provider: ProviderV2.Info, integration: Integration.Info | undefined, connected: boolean) => {
|
||||||
provider: ProviderV2.Info,
|
|
||||||
integration: Integration.Info | undefined,
|
|
||||||
connected: boolean,
|
|
||||||
) => {
|
|
||||||
if (provider.disabled) return false
|
if (provider.disabled) return false
|
||||||
if (typeof provider.request.body.apiKey === "string") return true
|
if (typeof provider.request.body.apiKey === "string") return true
|
||||||
if (connected) return true
|
if (connected) return true
|
||||||
|
|||||||
@@ -228,7 +228,10 @@ export interface Interface {
|
|||||||
readonly label?: string
|
readonly label?: string
|
||||||
}) => Effect.Effect<Attempt, AuthorizationError>
|
}) => Effect.Effect<Attempt, AuthorizationError>
|
||||||
/** Updates a stored credential exposed as a connection. */
|
/** Updates a stored credential exposed as a connection. */
|
||||||
readonly update: (credentialID: Credential.ID, updates: Partial<Pick<Credential.Stored, "label">>) => Effect.Effect<void>
|
readonly update: (
|
||||||
|
credentialID: Credential.ID,
|
||||||
|
updates: Partial<Pick<Credential.Stored, "label">>,
|
||||||
|
) => Effect.Effect<void>
|
||||||
/** Removes a stored credential connection. */
|
/** Removes a stored credential connection. */
|
||||||
readonly remove: (credentialID: Credential.ID) => Effect.Effect<void>
|
readonly remove: (credentialID: Credential.ID) => Effect.Effect<void>
|
||||||
}
|
}
|
||||||
@@ -339,9 +342,11 @@ export const locationLayer = Layer.effect(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const connections = (entry: Entry, saved: readonly Credential.Stored[]): IntegrationConnection.Info[] => {
|
const connections = (entry: Entry, saved: readonly Credential.Stored[]): IntegrationConnection.Info[] => {
|
||||||
const connected = saved.map(
|
const connected = saved.map((credential) => ({
|
||||||
(credential) => ({ type: "credential" as const, id: credential.id, label: credential.label }),
|
type: "credential" as const,
|
||||||
)
|
id: credential.id,
|
||||||
|
label: credential.label,
|
||||||
|
}))
|
||||||
const detected = entry.methods
|
const detected = entry.methods
|
||||||
.filter((method) => method.type === "env")
|
.filter((method) => method.type === "env")
|
||||||
.flatMap((method) => method.names.filter((name) => process.env[name]))
|
.flatMap((method) => method.names.filter((name) => process.env[name]))
|
||||||
@@ -446,12 +451,10 @@ export const locationLayer = Layer.effect(
|
|||||||
list: Effect.fn("Integration.connection.list")(function* () {
|
list: Effect.fn("Integration.connection.list")(function* () {
|
||||||
const saved = Map.groupBy(yield* credentials.all(), (credential) => credential.integrationID)
|
const saved = Map.groupBy(yield* credentials.all(), (credential) => credential.integrationID)
|
||||||
return new Map(
|
return new Map(
|
||||||
new Set([...state.get().integrations.keys(), ...saved.keys()])
|
new Set([...state.get().integrations.keys(), ...saved.keys()]).values().flatMap((id) => {
|
||||||
.values()
|
const connection = activeConnection(state.get().integrations.get(id), saved.get(id) ?? [])
|
||||||
.flatMap((id) => {
|
return connection ? [[id, connection] as const] : []
|
||||||
const connection = activeConnection(state.get().integrations.get(id), saved.get(id) ?? [])
|
}),
|
||||||
return connection ? [[id, connection] as const] : []
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
forIntegration: Effect.fn("Integration.connection.forIntegration")(function* (id) {
|
forIntegration: Effect.fn("Integration.connection.forIntegration")(function* (id) {
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ export const OpencodePlugin = PluginV2.define({
|
|||||||
if (!item) return
|
if (!item) return
|
||||||
const integration = yield* integrations.get(Integration.ID.make(item.provider.id))
|
const integration = yield* integrations.get(Integration.ID.make(item.provider.id))
|
||||||
hasKey = Boolean(
|
hasKey = Boolean(
|
||||||
process.env.OPENCODE_API_KEY ||
|
process.env.OPENCODE_API_KEY || integration?.connections.length || item.provider.request.body.apiKey,
|
||||||
integration?.connections.length ||
|
|
||||||
item.provider.request.body.apiKey,
|
|
||||||
)
|
)
|
||||||
evt.provider.update(item.provider.id, (provider) => {
|
evt.provider.update(item.provider.id, (provider) => {
|
||||||
if (!hasKey) provider.request.body.apiKey = "public"
|
if (!hasKey) provider.request.body.apiKey = "public"
|
||||||
|
|||||||
@@ -48,11 +48,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
|
|||||||
/** Test or embedding seam for supplying a model resolver directly. */
|
/** Test or embedding seam for supplying a model resolver directly. */
|
||||||
export const layerWith = (resolve: Interface["resolve"]) => Layer.succeed(Service, Service.of({ resolve }))
|
export const layerWith = (resolve: Interface["resolve"]) => Layer.succeed(Service, Service.of({ resolve }))
|
||||||
|
|
||||||
const apiKey = (
|
const apiKey = (model: ModelV2.Info, connection?: IntegrationConnection.Info, credential?: Credential.Stored) => {
|
||||||
model: ModelV2.Info,
|
|
||||||
connection?: IntegrationConnection.Info,
|
|
||||||
credential?: Credential.Stored,
|
|
||||||
) => {
|
|
||||||
if (credential?.value.type === "key") return Auth.value(credential.value.key)
|
if (credential?.value.type === "key") return Auth.value(credential.value.key)
|
||||||
if (credential?.value.type === "oauth") return Auth.value(credential.value.access)
|
if (credential?.value.type === "oauth") return Auth.value(credential.value.access)
|
||||||
const value = model.request.body.apiKey ?? model.api.settings?.apiKey
|
const value = model.request.body.apiKey ?? model.api.settings?.apiKey
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ describe("CatalogV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const updated = yield* events.subscribe(Catalog.Event.Updated).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
const updated = yield* events
|
||||||
|
.subscribe(Catalog.Event.Updated)
|
||||||
|
.pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
||||||
yield* (yield* catalog.transform())((editor) =>
|
yield* (yield* catalog.transform())((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
||||||
editor.provider.update(ProviderV2.ID.make("test"), () => {}),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect((yield* Fiber.join(updated)).length).toBe(1)
|
expect((yield* Fiber.join(updated)).length).toBe(1)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -136,123 +136,126 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
it.effect("loads configured providers and applies later model overrides", () =>
|
it.effect("loads configured providers and applies later model overrides", () =>
|
||||||
withEnv({ CUSTOM_API_KEY: "secret" }, () => Effect.gen(function* () {
|
withEnv({ CUSTOM_API_KEY: "secret" }, () =>
|
||||||
const catalog = yield* Catalog.Service
|
Effect.gen(function* () {
|
||||||
const integrations = yield* Integration.Service
|
const catalog = yield* Catalog.Service
|
||||||
const plugin = yield* PluginV2.Service
|
const integrations = yield* Integration.Service
|
||||||
const providerID = ProviderV2.ID.make("custom")
|
const plugin = yield* PluginV2.Service
|
||||||
const modelID = ModelV2.ID.make("chat")
|
const providerID = ProviderV2.ID.make("custom")
|
||||||
const config = Config.Service.of({
|
const modelID = ModelV2.ID.make("chat")
|
||||||
entries: () =>
|
const config = Config.Service.of({
|
||||||
Effect.succeed([
|
entries: () =>
|
||||||
new Config.Document({
|
Effect.succeed([
|
||||||
type: "document",
|
new Config.Document({
|
||||||
info: decode({
|
type: "document",
|
||||||
model: "custom/first",
|
info: decode({
|
||||||
providers: {
|
model: "custom/first",
|
||||||
custom: {
|
providers: {
|
||||||
name: "Configured",
|
custom: {
|
||||||
env: ["CUSTOM_API_KEY"],
|
name: "Configured",
|
||||||
api: { type: "native", settings: {} },
|
env: ["CUSTOM_API_KEY"],
|
||||||
request: request({ first: "first", shared: "first" }),
|
api: { type: "native", settings: {} },
|
||||||
models: {
|
request: request({ first: "first", shared: "first" }),
|
||||||
chat: {
|
models: {
|
||||||
name: "First",
|
chat: {
|
||||||
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
name: "First",
|
||||||
disabled: true,
|
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
||||||
limit: { context: 100, output: 50 },
|
disabled: true,
|
||||||
cost: { input: 1, output: 2 },
|
limit: { context: 100, output: 50 },
|
||||||
request: request({ first: "first", shared: "first" }, "retained"),
|
cost: { input: 1, output: 2 },
|
||||||
variants: [
|
request: request({ first: "first", shared: "first" }, "retained"),
|
||||||
{
|
variants: [
|
||||||
id: "fast",
|
{
|
||||||
headers: { first: "first", shared: "first" },
|
id: "fast",
|
||||||
},
|
headers: { first: "first", shared: "first" },
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
new Config.Document({
|
||||||
new Config.Document({
|
type: "document",
|
||||||
type: "document",
|
info: decode({
|
||||||
info: decode({
|
model: "custom/default",
|
||||||
model: "custom/default",
|
providers: {
|
||||||
providers: {
|
custom: {
|
||||||
custom: {
|
api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
|
||||||
api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
|
request: request({ last: "last", shared: "last" }),
|
||||||
request: request({ last: "last", shared: "last" }),
|
models: {
|
||||||
models: {
|
default: {
|
||||||
default: {
|
name: "Default",
|
||||||
name: "Default",
|
},
|
||||||
},
|
chat: {
|
||||||
chat: {
|
api: { id: "api-chat" },
|
||||||
api: { id: "api-chat" },
|
name: "Last",
|
||||||
name: "Last",
|
limit: { output: 75 },
|
||||||
limit: { output: 75 },
|
request: request({ last: "last", shared: "last" }),
|
||||||
request: request({ last: "last", shared: "last" }),
|
variants: [
|
||||||
variants: [
|
{
|
||||||
{
|
id: "fast",
|
||||||
id: "fast",
|
headers: { last: "last", shared: "last" },
|
||||||
headers: { last: "last", shared: "last" },
|
},
|
||||||
},
|
{
|
||||||
{
|
id: "slow",
|
||||||
id: "slow",
|
headers: { slow: "slow" },
|
||||||
headers: { slow: "slow" },
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
new Config.Document({
|
||||||
new Config.Document({
|
type: "document",
|
||||||
type: "document",
|
info: decode({
|
||||||
info: decode({
|
providers: {
|
||||||
providers: {
|
custom: { name: "Renamed" },
|
||||||
custom: { name: "Renamed" },
|
},
|
||||||
},
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
]),
|
||||||
]),
|
})
|
||||||
})
|
|
||||||
|
|
||||||
yield* plugin.add({
|
yield* plugin.add({
|
||||||
...ConfigProviderPlugin.Plugin,
|
...ConfigProviderPlugin.Plugin,
|
||||||
effect: ConfigProviderPlugin.Plugin.effect.pipe(
|
effect: ConfigProviderPlugin.Plugin.effect.pipe(
|
||||||
Effect.provideService(Config.Service, config),
|
Effect.provideService(Config.Service, config),
|
||||||
Effect.provideService(Catalog.Service, catalog),
|
Effect.provideService(Catalog.Service, catalog),
|
||||||
Effect.provideService(Integration.Service, integrations),
|
Effect.provideService(Integration.Service, integrations),
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const provider = yield* catalog.provider.get(providerID)
|
const provider = yield* catalog.provider.get(providerID)
|
||||||
const model = yield* catalog.model.get(providerID, modelID)
|
const model = yield* catalog.model.get(providerID, modelID)
|
||||||
expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(ModelV2.ID.make("default"))
|
expect(Option.getOrUndefined(yield* catalog.model.default())?.id).toBe(ModelV2.ID.make("default"))
|
||||||
expect(provider.name).toBe("Renamed")
|
expect(provider.name).toBe("Renamed")
|
||||||
expect((yield* integrations.get(Integration.ID.make("custom")))?.methods).toContainEqual(
|
expect((yield* integrations.get(Integration.ID.make("custom")))?.methods).toContainEqual({
|
||||||
{ type: "env", names: ["CUSTOM_API_KEY"] },
|
type: "env",
|
||||||
)
|
names: ["CUSTOM_API_KEY"],
|
||||||
expect((yield* integrations.get(Integration.ID.make("custom")))?.name).toBe("Renamed")
|
})
|
||||||
expect(provider.disabled).toBeUndefined()
|
expect((yield* integrations.get(Integration.ID.make("custom")))?.name).toBe("Renamed")
|
||||||
expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
|
expect(provider.disabled).toBeUndefined()
|
||||||
expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
|
||||||
expect(model.api.id).toBe(ModelV2.ID.make("api-chat"))
|
expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
||||||
expect(model.name).toBe("Last")
|
expect(model.api.id).toBe(ModelV2.ID.make("api-chat"))
|
||||||
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
|
expect(model.name).toBe("Last")
|
||||||
expect(model.enabled).toBe(false)
|
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
|
||||||
expect(model.limit).toEqual({ context: 100, output: 75 })
|
expect(model.enabled).toBe(false)
|
||||||
expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }])
|
expect(model.limit).toEqual({ context: 100, output: 75 })
|
||||||
expect(model.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }])
|
||||||
expect(model.request.variant).toBe("retained")
|
expect(model.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
||||||
expect(model.variants.map((variant) => variant.id)).toEqual([
|
expect(model.request.variant).toBe("retained")
|
||||||
ModelV2.VariantID.make("fast"),
|
expect(model.variants.map((variant) => variant.id)).toEqual([
|
||||||
ModelV2.VariantID.make("slow"),
|
ModelV2.VariantID.make("fast"),
|
||||||
])
|
ModelV2.VariantID.make("slow"),
|
||||||
expect(model.variants[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
])
|
||||||
expect(model.variants[1]?.headers).toEqual({ slow: "slow" })
|
expect(model.variants[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" })
|
||||||
})),
|
expect(model.variants[1]?.headers).toEqual({ slow: "slow" })
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -147,11 +147,9 @@ describe("Integration", () => {
|
|||||||
method: { type: "key", label: "API key" },
|
method: { type: "key", label: "API key" },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const updated = yield* events.subscribe(Integration.Event.Updated).pipe(
|
const updated = yield* events
|
||||||
Stream.take(1),
|
.subscribe(Integration.Event.Updated)
|
||||||
Stream.runCollect,
|
.pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
||||||
Effect.forkScoped,
|
|
||||||
)
|
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
||||||
yield* integrations.connection.key({
|
yield* integrations.connection.key({
|
||||||
|
|||||||
+678
-1235
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user