refactor(plugin): own integration method types

This commit is contained in:
Aiden Cline
2026-08-08 21:53:11 -05:00
parent 9fed1e9764
commit 5cfb70e93a
4 changed files with 80 additions and 87 deletions
+3 -3
View File
@@ -261,7 +261,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../p
update: (id, update) => draft.update(Integration.ID.make(id), update),
remove: (id) => draft.remove(Integration.ID.make(id)),
method: {
list: (id) => mutable(draft.method.list(Integration.ID.make(id))),
list: (id) => draft.method.list(Integration.ID.make(id)),
update: (input) => draft.method.update(methodImplementation(input)),
remove: (id, method) =>
draft.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)),
@@ -386,13 +386,13 @@ function methodImplementation(input: IntegrationMethodRegistration): Integration
if (input.method.type === "env") {
return {
integrationID: Integration.ID.make(input.integrationID),
method: { type: "env", names: input.method.names },
method: input.method,
}
}
if (input.method.type === "command") {
return {
integrationID: Integration.ID.make(input.integrationID),
method: Schema.decodeUnknownSync(Integration.CommandMethod)(input.method),
method: { ...input.method, id: Integration.MethodID.make(input.method.id) },
}
}
return {
+9 -64
View File
@@ -1,5 +1,5 @@
import { Plugin } from "@opencode-ai/plugin/effect"
import type { IntegrationDraft, IntegrationMethodRegistration } from "@opencode-ai/plugin/effect/integration"
import type { IntegrationMethod, IntegrationMethodRegistration } from "@opencode-ai/plugin/effect/integration"
import { Agent } from "@opencode-ai/core/agent"
import { Catalog } from "@opencode-ai/core/catalog"
import { Credential } from "@opencode-ai/core/credential"
@@ -10,13 +10,11 @@ import { Project } from "@opencode-ai/core/project"
import { Provider } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { WebSearch } from "@opencode-ai/core/websearch"
import { Effect, Schema, Stream } from "effect"
import { Effect, Stream } from "effect"
type Overrides = Partial<Omit<Plugin.Context, "options" | "session">> & {
readonly session?: Partial<Plugin.Context["session"]>
}
type IntegrationMethod = ReturnType<IntegrationDraft["method"]["list"]>[number]
export function host(overrides: Overrides = {}): Plugin.Context {
return {
app: overrides.app ?? { name: "test", version: "test", channel: "test" },
@@ -279,14 +277,14 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont
update: (id, update) => draft.update(Integration.ID.make(id), update),
remove: (id) => draft.remove(Integration.ID.make(id)),
method: {
list: (id) => draft.method.list(Integration.ID.make(id)).map(method),
list: (id) => draft.method.list(Integration.ID.make(id)),
update: (input) => {
if ("authorize" in input) {
const methodID = Integration.MethodID.make(input.method.id)
const refresh = input.refresh
draft.method.update({
integrationID: Integration.ID.make(input.integrationID),
method: oauthMethod(input.method, methodID),
method: { ...input.method, id: methodID },
authorize: (answer) =>
input.authorize(answer).pipe(
Effect.map((authorization) => {
@@ -337,7 +335,7 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont
if (input.method.type === "env") {
draft.method.update({
integrationID: Integration.ID.make(input.integrationID),
method: { ...input.method, names: [...input.method.names] },
method: input.method,
})
return
}
@@ -347,14 +345,13 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont
method: {
...input.method,
id: Integration.MethodID.make(input.method.id),
command: [...input.method.command],
},
})
return
}
draft.method.update({
integrationID: Integration.ID.make(input.integrationID),
method: keyMethod(input.method),
method: input.method,
})
},
remove: (id, item) => draft.method.remove(Integration.ID.make(id), internalMethod(item)),
@@ -402,63 +399,11 @@ function oauthCredential(value: Credential.OAuth) {
return Credential.OAuth.make({ ...value, methodID: Integration.MethodID.make(value.methodID) })
}
function method(value: Integration.Method): IntegrationMethod {
if (value.type === "env") return { type: value.type, names: [...value.names] }
if (value.type === "key") return { type: value.type, label: value.label, form: mutable(value.form) }
if (value.type === "command") return { ...value, command: [...value.command] }
return {
type: value.type,
id: value.id,
label: value.label,
form: mutable(value.form),
}
}
function internalMethod(value: IntegrationMethod): Integration.Method {
if (value.type === "env") return value
if (value.type === "key") return keyMethod(value)
if (value.type === "command") {
return {
...value,
id: Integration.MethodID.make(value.id),
command: [...value.command],
}
if (value.type === "oauth" || value.type === "command") {
return { ...value, id: Integration.MethodID.make(value.id) }
}
return oauthMethod(value, Integration.MethodID.make(value.id))
}
type Mutable<Value> = Value extends readonly [infer Head, ...infer Tail]
? [Mutable<Head>, ...MutableTuple<Tail>]
: Value extends ReadonlyArray<infer Item>
? Array<Mutable<Item>>
: Value extends object
? { -readonly [Key in keyof Value]: Mutable<Value[Key]> }
: Value
type MutableTuple<Value extends ReadonlyArray<unknown>> = {
-readonly [Key in keyof Value]: Mutable<Value[Key]>
}
function mutable<Value>(value: Value): Mutable<Value>
function mutable(value: unknown): unknown {
return structuredClone(value)
}
function keyMethod(value: { readonly label?: string; readonly form?: unknown }) {
return Schema.decodeUnknownSync(Integration.KeyMethod)({
type: "key",
...(value.label === undefined ? {} : { label: value.label }),
...(value.form === undefined ? {} : { form: value.form }),
})
}
function oauthMethod(value: { readonly label: string; readonly form?: unknown }, id: Integration.MethodID) {
return Schema.decodeUnknownSync(Integration.OAuthMethod)({
id,
type: "oauth",
label: value.label,
...(value.form === undefined ? {} : { form: value.form }),
})
return value
}
function agentInfo(value: Agent.Info) {
+34 -10
View File
@@ -1,11 +1,4 @@
import type {
ConnectionInfo,
IntegrationCommandMethod,
IntegrationEnvMethod,
IntegrationKeyMethod,
IntegrationMethod,
IntegrationOAuthMethod,
} from "@opencode-ai/client"
import type { ConnectionInfo } from "@opencode-ai/client"
import type { IntegrationApi } from "@opencode-ai/client/effect/api"
import { Credential } from "@opencode-ai/schema/credential"
import { Form } from "@opencode-ai/schema/form"
@@ -14,6 +7,37 @@ import type { Transform } from "./registration.js"
type IntegrationRef = { id: string; name: string }
export interface IntegrationOAuthMethod {
readonly id: string
readonly type: "oauth"
readonly label: string
readonly form?: Form.Fields
}
export interface IntegrationCommandMethod {
readonly id: string
readonly type: "command"
readonly label: string
readonly command: ReadonlyArray<string>
}
export interface IntegrationKeyMethod {
readonly type: "key"
readonly label?: string
readonly form?: Form.Fields
}
export interface IntegrationEnvMethod {
readonly type: "env"
readonly names: ReadonlyArray<string>
}
export type IntegrationMethod =
| IntegrationOAuthMethod
| IntegrationCommandMethod
| IntegrationKeyMethod
| IntegrationEnvMethod
export type IntegrationOAuthAuthorization = {
readonly url: string
readonly instructions: string
@@ -30,7 +54,7 @@ export type IntegrationOAuthAuthorization = {
)
export type IntegrationOAuthMethodRegistration = {
readonly integrationID: string
readonly method: Omit<IntegrationOAuthMethod, "form"> & { readonly form?: Form.Fields }
readonly method: IntegrationOAuthMethod
readonly authorize: (answer: Form.Answer) => Effect.Effect<IntegrationOAuthAuthorization, unknown, Scope.Scope>
readonly refresh?: (credential: Credential.OAuth) => Effect.Effect<Credential.OAuth, unknown>
readonly label?: (credential: Credential.OAuth) => string | undefined
@@ -43,7 +67,7 @@ export type IntegrationMethodRegistration =
}
| {
readonly integrationID: string
readonly method: Omit<IntegrationKeyMethod, "form"> & { readonly form?: Form.Fields }
readonly method: IntegrationKeyMethod
}
| {
readonly integrationID: string
+34 -10
View File
@@ -1,11 +1,4 @@
import type {
ConnectionInfo,
IntegrationCommandMethod,
IntegrationEnvMethod,
IntegrationKeyMethod,
IntegrationMethod,
IntegrationOAuthMethod,
} from "@opencode-ai/client"
import type { ConnectionInfo } from "@opencode-ai/client"
import type { IntegrationApi } from "@opencode-ai/client/promise/api"
import { Credential } from "@opencode-ai/schema/credential"
import { Form } from "@opencode-ai/schema/form"
@@ -13,6 +6,37 @@ import type { Transform } from "./registration.js"
type IntegrationRef = { id: string; name: string }
export interface IntegrationOAuthMethod {
readonly id: string
readonly type: "oauth"
readonly label: string
readonly form?: Form.Fields
}
export interface IntegrationCommandMethod {
readonly id: string
readonly type: "command"
readonly label: string
readonly command: ReadonlyArray<string>
}
export interface IntegrationKeyMethod {
readonly type: "key"
readonly label?: string
readonly form?: Form.Fields
}
export interface IntegrationEnvMethod {
readonly type: "env"
readonly names: ReadonlyArray<string>
}
export type IntegrationMethod =
| IntegrationOAuthMethod
| IntegrationCommandMethod
| IntegrationKeyMethod
| IntegrationEnvMethod
export type IntegrationOAuthAuthorization = {
readonly url: string
readonly instructions: string
@@ -30,7 +54,7 @@ export type IntegrationOAuthAuthorization = {
export type IntegrationOAuthMethodRegistration = {
readonly integrationID: string
readonly method: Omit<IntegrationOAuthMethod, "form"> & { readonly form?: Form.Fields }
readonly method: IntegrationOAuthMethod
readonly authorize: (answer: Form.Answer) => Promise<IntegrationOAuthAuthorization>
readonly refresh?: (credential: Credential.OAuth) => Promise<Credential.OAuth>
readonly label?: (credential: Credential.OAuth) => string | undefined
@@ -41,7 +65,7 @@ export type IntegrationMethodRegistration =
| { readonly integrationID: string; readonly method: IntegrationCommandMethod }
| {
readonly integrationID: string
readonly method: Omit<IntegrationKeyMethod, "form"> & { readonly form?: Form.Fields }
readonly method: IntegrationKeyMethod
}
| { readonly integrationID: string; readonly method: IntegrationEnvMethod }