| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- export * as PluginV2 from "./plugin"
- import { createDraft, finishDraft, type Draft } from "immer"
- import type { LanguageModelV3 } from "@ai-sdk/provider"
- import { Context, Effect, Exit, Layer, PubSub, Schema, Scope, Stream } from "effect"
- import type { ModelV2 } from "./model"
- import type { AgentV2 } from "./agent"
- import type { Catalog } from "./catalog"
- export const ID = Schema.String.pipe(Schema.brand("Plugin.ID"))
- export type ID = typeof ID.Type
- type HookSpec = {
- "catalog.transform": {
- input: Catalog.Context
- output: {}
- }
- "account.switched": {
- input: {
- serviceID: import("./account").AccountV2.ServiceID
- from?: import("./account").AccountV2.ID
- to?: import("./account").AccountV2.ID
- }
- output: {}
- }
- "aisdk.language": {
- input: {
- model: ModelV2.Info
- sdk: any
- options: Record<string, any>
- }
- output: {
- language?: LanguageModelV3
- }
- }
- "aisdk.sdk": {
- input: {
- model: ModelV2.Info
- package: string
- options: Record<string, any>
- }
- output: {
- sdk?: any
- }
- }
- "agent.update": {
- input: {}
- output: {
- agent: AgentV2.Info
- cancel: boolean
- }
- }
- "agent.remove": {
- input: {
- agent: AgentV2.Info
- }
- output: {
- cancel: boolean
- }
- }
- "agent.default": {
- input: {}
- output: {
- agent?: AgentV2.ID
- }
- }
- }
- export type Hooks = {
- [Name in keyof HookSpec]: Readonly<HookSpec[Name]["input"]> & {
- -readonly [Field in keyof HookSpec[Name]["output"]]: HookSpec[Name]["output"][Field] extends object
- ? Draft<HookSpec[Name]["output"][Field]>
- : HookSpec[Name]["output"][Field]
- }
- }
- export type HookFunctions = {
- [key in keyof Hooks]?: (input: Hooks[key]) => Effect.Effect<void>
- }
- export type HookInput<Name extends keyof Hooks> = HookSpec[Name]["input"]
- export type HookOutput<Name extends keyof Hooks> = HookSpec[Name]["output"]
- export type Effect<R = never> = Effect.Effect<HookFunctions | void, never, R | Scope.Scope>
- export function define<R>(input: { id: ID; effect: Effect.Effect<HookFunctions | void, never, R> }) {
- return input
- }
- export interface Interface {
- readonly add: (input: {
- id: ID
- effect: Effect.Effect<void | HookFunctions, never, Scope.Scope>
- }) => Effect.Effect<void, never, never>
- readonly remove: (id: ID) => Effect.Effect<void>
- readonly added: () => Stream.Stream<ID>
- readonly triggerFor: <Name extends keyof Hooks>(
- id: ID,
- name: Name,
- input: HookInput<Name>,
- output: HookOutput<Name>,
- ) => Effect.Effect<HookInput<Name> & HookOutput<Name>>
- readonly trigger: <Name extends keyof Hooks>(
- name: Name,
- input: HookInput<Name>,
- output: HookOutput<Name>,
- ) => Effect.Effect<HookInput<Name> & HookOutput<Name>>
- }
- export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Plugin") {}
- export const layer = Layer.effect(
- Service,
- Effect.gen(function* () {
- let hooks: {
- id: ID
- hooks: HookFunctions
- scope: Scope.Closeable
- }[] = []
- const added = yield* PubSub.unbounded<ID>()
- yield* Effect.addFinalizer(() => PubSub.shutdown(added))
- const svc = Service.of({
- add: Effect.fn("Plugin.add")(function* (input) {
- const existing = hooks.find((item) => item.id === input.id)
- if (existing) yield* Scope.close(existing.scope, Exit.void).pipe(Effect.ignore)
- const scope = yield* Scope.make()
- const result = yield* input.effect.pipe(Scope.provide(scope))
- hooks = [
- ...hooks.filter((item) => item.id !== input.id),
- {
- id: input.id,
- hooks: result ?? {},
- scope,
- },
- ]
- yield* PubSub.publish(added, input.id)
- }),
- added: () => Stream.fromPubSub(added),
- trigger: Effect.fn("Plugin.trigger")(function* (name, input, output) {
- return yield* svc.triggerFor(ID.make("*"), name, input, output)
- }),
- triggerFor: Effect.fn("Plugin.triggerFor")(function* (id, name, input, output) {
- const draftEntries = new Map<string, ReturnType<typeof createDraft>>()
- const event = {
- ...input,
- ...output,
- } as Record<string, unknown>
- for (const [field, value] of Object.entries(output)) {
- if (value && typeof value === "object") {
- draftEntries.set(field, createDraft(value))
- event[field] = draftEntries.get(field)
- }
- }
- for (const item of hooks) {
- if (id !== ID.make("*") && item.id !== id) continue
- const match = item.hooks[name]
- if (!match) continue
- yield* match(event as any).pipe(
- Effect.withSpan(`Plugin.hook.${name}`, {
- attributes: {
- plugin: item.id,
- hook: name,
- },
- }),
- )
- }
- for (const [field, draft] of draftEntries) {
- event[field] = finishDraft(draft)
- }
- return event as any
- }),
- remove: Effect.fn("Plugin.remove")(function* (id) {
- const existing = hooks.find((item) => item.id === id)
- hooks = hooks.filter((item) => item.id !== id)
- if (existing) yield* Scope.close(existing.scope, Exit.void).pipe(Effect.ignore)
- }),
- })
- return svc
- }),
- )
- export const defaultLayer = layer
- // opencode
- // sdcok
|