feat(opencode): cut plugin system and remote behavior feeds; add SessionContext assembly and Runner
- delete plugin machinery (loader, install, meta, hooks) and all trigger sites - fold first-party provider auth into static registry (provider/hooks.ts) - remove remote instruction URL fetch, skills remote puller, TUI plugin host - add session/context.ts: single provenance-tagged context assembly point - add session/runner.ts: admit/context/stream/tools loop with compaction policy - relocate audit artifacts to audit/ (FORK-AUDIT, instruction docs as evidence)
This commit is contained in:
@@ -10,7 +10,6 @@ import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { testProviderConfig } from "../lib/test-provider"
|
||||
import { Env } from "@/env"
|
||||
import { Plugin } from "@/plugin"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ProviderError } from "@/provider/error"
|
||||
|
||||
@@ -19,7 +18,7 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
const it = testEffect(
|
||||
LayerNode.compile(LayerNode.group([Provider.node, Env.node, Plugin.node, CrossSpawnSpawner.node])),
|
||||
LayerNode.compile(LayerNode.group([Provider.node, Env.node, CrossSpawnSpawner.node])),
|
||||
)
|
||||
|
||||
it.live("headerTimeout does not abort delayed SSE body after headers arrive", () =>
|
||||
|
||||
@@ -9,11 +9,9 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { disposeAllInstances, provideInstanceEffect, tmpdirScoped, TestInstance } from "../fixture/fixture"
|
||||
import { markPluginDependenciesReady } from "../fixture/plugin"
|
||||
import { Auth } from "@/auth"
|
||||
import { Config } from "@/config/config"
|
||||
import { Env } from "../../src/env"
|
||||
import { Plugin } from "../../src/plugin/index"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
@@ -67,7 +65,6 @@ const providerLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Env.node,
|
||||
Config.node,
|
||||
Auth.node,
|
||||
Plugin.node,
|
||||
ModelsDev.node,
|
||||
RuntimeFlags.node,
|
||||
]),
|
||||
@@ -84,7 +81,7 @@ const paid = (providers: Record<string, { models: Record<string, { cost: { input
|
||||
|
||||
const languageBaseURL = (language: unknown) => (language as { config: { baseURL: string } }).config.baseURL
|
||||
|
||||
const it = testEffect(LayerNode.compile(LayerNode.group([Provider.node, Env.node, Plugin.node])))
|
||||
const it = testEffect(LayerNode.compile(LayerNode.group([Provider.node, Env.node])))
|
||||
const experimentalModels = testEffect(providerLayer({ enableExperimentalModels: true }))
|
||||
|
||||
const alphaProviderConfig = {
|
||||
@@ -1842,96 +1839,6 @@ const instanceStoreLayer = LayerNode.compile(InstanceStore.node, [
|
||||
const provideMultiInstance = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
|
||||
eff.pipe(Effect.provide(instanceStoreLayer), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)))
|
||||
|
||||
it.effect("plugin config providers persist after instance dispose", () =>
|
||||
Effect.gen(function* () {
|
||||
const dir = yield* tmpdirScoped()
|
||||
const configDir = path.join(dir, ".opencode")
|
||||
const root = path.join(configDir, "plugin")
|
||||
yield* Effect.promise(() => mkdir(root, { recursive: true }))
|
||||
yield* Effect.promise(() => markPluginDependenciesReady(configDir))
|
||||
yield* Effect.promise(() => markPluginDependenciesReady(Global.Path.config))
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
path.join(root, "demo-provider.ts"),
|
||||
[
|
||||
"export default {",
|
||||
' id: "demo.plugin-provider",',
|
||||
" server: async () => ({",
|
||||
" async config(cfg) {",
|
||||
" cfg.provider ??= {}",
|
||||
" cfg.provider.demo = {",
|
||||
' name: "Demo Provider",',
|
||||
' npm: "@ai-sdk/openai-compatible",',
|
||||
' api: "https://example.com/v1",',
|
||||
" models: {",
|
||||
" chat: {",
|
||||
' name: "Demo Chat",',
|
||||
" tool_call: true,",
|
||||
" limit: { context: 128000, output: 4096 },",
|
||||
" },",
|
||||
" },",
|
||||
" }",
|
||||
" },",
|
||||
" }),",
|
||||
"}",
|
||||
"",
|
||||
].join("\n"),
|
||||
),
|
||||
)
|
||||
|
||||
const loadAndList = Effect.gen(function* () {
|
||||
const plugin = yield* Plugin.Service
|
||||
const provider = yield* Provider.Service
|
||||
yield* plugin.init()
|
||||
return yield* provider.list()
|
||||
}).pipe(provideInstanceEffect(dir))
|
||||
|
||||
const first = yield* loadAndList
|
||||
expect(first[ProviderV2.ID.make("demo")]).toBeDefined()
|
||||
expect(first[ProviderV2.ID.make("demo")].models[ModelV2.ID.make("chat")]).toBeDefined()
|
||||
|
||||
yield* Effect.promise(() => disposeAllInstances())
|
||||
|
||||
const second = yield* loadAndList
|
||||
expect(second[ProviderV2.ID.make("demo")]).toBeDefined()
|
||||
expect(second[ProviderV2.ID.make("demo")].models[ModelV2.ID.make("chat")]).toBeDefined()
|
||||
}).pipe(provideMultiInstance),
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"plugin config enabled and disabled providers are honored",
|
||||
Effect.gen(function* () {
|
||||
const instance = yield* TestInstance
|
||||
const configDir = path.join(instance.directory, ".opencode")
|
||||
const root = path.join(configDir, "plugin")
|
||||
yield* Effect.promise(() => mkdir(root, { recursive: true }))
|
||||
yield* Effect.promise(() => markPluginDependenciesReady(configDir))
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
path.join(root, "provider-filter.ts"),
|
||||
[
|
||||
"export default {",
|
||||
' id: "demo.provider-filter",',
|
||||
" server: async () => ({",
|
||||
" async config(cfg) {",
|
||||
' cfg.enabled_providers = ["anthropic", "openai"]',
|
||||
' cfg.disabled_providers = ["openai"]',
|
||||
" },",
|
||||
" }),",
|
||||
"}",
|
||||
"",
|
||||
].join("\n"),
|
||||
),
|
||||
)
|
||||
|
||||
yield* set("ANTHROPIC_API_KEY", "test-anthropic-key")
|
||||
yield* set("OPENAI_API_KEY", "test-openai-key")
|
||||
const providers = yield* list
|
||||
expect(providers[ProviderV2.ID.anthropic]).toBeDefined()
|
||||
expect(providers[ProviderV2.ID.openai]).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("opencode loader keeps paid models when config apiKey is present", () =>
|
||||
Effect.gen(function* () {
|
||||
const noneDir = yield* tmpdirScoped()
|
||||
|
||||
@@ -571,11 +571,6 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
|
||||
},
|
||||
provider: { id: "azure", options: { useCompletionUrls: true } } as any,
|
||||
auth: undefined,
|
||||
plugin: {
|
||||
trigger: (_name: string, _input: unknown, output: unknown) => Effect.succeed(output),
|
||||
list: () => Effect.succeed([]),
|
||||
init: () => Effect.void,
|
||||
} as any,
|
||||
flags: { outputTokenMax: 32_000, client: "test" } as any,
|
||||
isWorkflow: false,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user