Merge remote-tracking branch 'origin/v2' into nxl/vcs-plugin-api
# Conflicts: # packages/core/src/config.ts # packages/core/src/plugin/internal.ts # packages/core/src/project.ts # packages/core/test/project.test.ts # packages/plugin/src/v2/effect/context.ts # packages/plugin/src/v2/effect/index.ts
This commit is contained in:
commit
8ede176244
599 changed files with 83198 additions and 11394 deletions
|
|
@ -23,7 +23,11 @@ describe("CommandPlugin.Plugin", () => {
|
|||
const command = yield* CommandV2.Service
|
||||
yield* CommandPlugin.Plugin.effect(
|
||||
host({
|
||||
command: { transform: command.transform, reload: command.reload },
|
||||
command: {
|
||||
list: () => Effect.die("unused command.list"),
|
||||
transform: command.transform,
|
||||
reload: command.reload,
|
||||
},
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provideService(
|
||||
|
|
|
|||
7
packages/core/test/plugin/fixtures/failing-plugin.ts
Normal file
7
packages/core/test/plugin/fixtures/failing-plugin.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { define } from "@opencode-ai/plugin/v2/effect"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export default define({
|
||||
id: "failing-plugin",
|
||||
effect: () => Effect.die("plugin failed"),
|
||||
})
|
||||
29
packages/core/test/plugin/fixtures/variant-source-plugin.ts
Normal file
29
packages/core/test/plugin/fixtures/variant-source-plugin.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { define } from "@opencode-ai/plugin/v2/effect"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export default define({
|
||||
id: "variant-source",
|
||||
effect: (ctx) =>
|
||||
ctx.catalog
|
||||
.transform((catalog) => {
|
||||
catalog.provider.update("configured", (provider) => {
|
||||
provider.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
||||
})
|
||||
catalog.model.update("configured", "glm-5.2", (model) => {
|
||||
model.api = {
|
||||
id: "glm-5.2",
|
||||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
}
|
||||
model.variants = [
|
||||
{
|
||||
id: "high",
|
||||
settings: {},
|
||||
headers: { custom: "true" },
|
||||
body: {},
|
||||
},
|
||||
]
|
||||
})
|
||||
})
|
||||
.pipe(Effect.asVoid),
|
||||
})
|
||||
|
|
@ -6,7 +6,7 @@ import { Integration } from "@opencode-ai/core/integration"
|
|||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import type { IntegrationEnvMethod, IntegrationKeyMethod, IntegrationOAuthMethod } from "@opencode-ai/sdk/v2/types"
|
||||
import { Effect } from "effect"
|
||||
import { Effect, Stream } from "effect"
|
||||
|
||||
type Overrides = Partial<Omit<PluginContext, "options">>
|
||||
|
||||
|
|
@ -23,14 +23,33 @@ export function host(overrides: Overrides = {}): PluginContext {
|
|||
language: () => Effect.die("unused aisdk.language"),
|
||||
},
|
||||
catalog: overrides.catalog ?? {
|
||||
provider: {
|
||||
list: () => Effect.die("unused catalog.provider.list"),
|
||||
get: () => Effect.die("unused catalog.provider.get"),
|
||||
},
|
||||
model: {
|
||||
list: () => Effect.die("unused catalog.model.list"),
|
||||
default: () => Effect.die("unused catalog.model.default"),
|
||||
},
|
||||
transform: () => Effect.die("unused catalog.transform"),
|
||||
reload: () => Effect.die("unused catalog.reload"),
|
||||
},
|
||||
command: overrides.command ?? {
|
||||
list: () => Effect.die("unused command.list"),
|
||||
transform: () => Effect.die("unused command.transform"),
|
||||
reload: () => Effect.die("unused command.reload"),
|
||||
},
|
||||
event: overrides.event ?? {
|
||||
subscribe: () => Stream.empty,
|
||||
},
|
||||
integration: overrides.integration ?? {
|
||||
list: () => Effect.die("unused integration.list"),
|
||||
get: () => Effect.die("unused integration.get"),
|
||||
connectKey: () => Effect.die("unused integration.connectKey"),
|
||||
connectOauth: () => Effect.die("unused integration.connectOauth"),
|
||||
attemptStatus: () => Effect.die("unused integration.attemptStatus"),
|
||||
attemptComplete: () => Effect.die("unused integration.attemptComplete"),
|
||||
attemptCancel: () => Effect.die("unused integration.attemptCancel"),
|
||||
transform: () => Effect.die("unused integration.transform"),
|
||||
reload: () => Effect.die("unused integration.reload"),
|
||||
connection: {
|
||||
|
|
@ -39,14 +58,15 @@ export function host(overrides: Overrides = {}): PluginContext {
|
|||
},
|
||||
},
|
||||
plugin: overrides.plugin ?? {
|
||||
add: () => Effect.die("unused plugin.add"),
|
||||
remove: () => Effect.die("unused plugin.remove"),
|
||||
list: () => Effect.die("unused plugin.list"),
|
||||
},
|
||||
reference: overrides.reference ?? {
|
||||
list: () => Effect.die("unused reference.list"),
|
||||
transform: () => Effect.die("unused reference.transform"),
|
||||
reload: () => Effect.die("unused reference.reload"),
|
||||
},
|
||||
skill: overrides.skill ?? {
|
||||
list: () => Effect.die("unused skill.list"),
|
||||
transform: () => Effect.die("unused skill.transform"),
|
||||
reload: () => Effect.die("unused skill.reload"),
|
||||
},
|
||||
|
|
@ -97,6 +117,14 @@ export function agentHost(agent: AgentV2.Interface): PluginContext["agent"] {
|
|||
|
||||
export function catalogHost(catalog: Catalog.Interface): PluginContext["catalog"] {
|
||||
return {
|
||||
provider: {
|
||||
list: () => Effect.die("unused catalog.provider.list"),
|
||||
get: () => Effect.die("unused catalog.provider.get"),
|
||||
},
|
||||
model: {
|
||||
list: () => Effect.die("unused catalog.model.list"),
|
||||
default: () => Effect.die("unused catalog.model.default"),
|
||||
},
|
||||
reload: catalog.reload,
|
||||
transform: (callback) =>
|
||||
catalog.transform((draft) =>
|
||||
|
|
@ -161,6 +189,13 @@ export function catalogHost(catalog: Catalog.Interface): PluginContext["catalog"
|
|||
|
||||
export function integrationHost(integration: Integration.Interface): PluginContext["integration"] {
|
||||
return {
|
||||
list: () => Effect.die("unused integration.list"),
|
||||
get: () => Effect.die("unused integration.get"),
|
||||
connectKey: () => Effect.die("unused integration.connectKey"),
|
||||
connectOauth: () => Effect.die("unused integration.connectOauth"),
|
||||
attemptStatus: () => Effect.die("unused integration.attemptStatus"),
|
||||
attemptComplete: () => Effect.die("unused integration.attemptComplete"),
|
||||
attemptCancel: () => Effect.die("unused integration.attemptCancel"),
|
||||
reload: integration.reload,
|
||||
connection: {
|
||||
active: (id) => integration.connection.active(Integration.ID.make(id)),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,35 @@ import { PluginTestLayer } from "./fixture"
|
|||
const it = testEffect(PluginTestLayer)
|
||||
|
||||
describe("fromPromise", () => {
|
||||
it.effect("forwards standard client reads", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugin = yield* PluginV2.Service
|
||||
const host = yield* PluginHost.make(plugin)
|
||||
const seen: string[] = []
|
||||
const promisePlugin = define({
|
||||
id: "promise-client-reads",
|
||||
setup: async (ctx) => {
|
||||
const results = await Promise.all([
|
||||
ctx.agent.list(),
|
||||
ctx.catalog.provider.list(),
|
||||
ctx.catalog.model.list(),
|
||||
ctx.command.list(),
|
||||
ctx.integration.list(),
|
||||
ctx.plugin.list(),
|
||||
ctx.reference.list(),
|
||||
ctx.skill.list(),
|
||||
])
|
||||
seen.push(...results.map((result) => result.location.directory))
|
||||
},
|
||||
})
|
||||
|
||||
yield* PluginPromise.fromPromise(promisePlugin).effect(host)
|
||||
|
||||
expect(seen).toHaveLength(8)
|
||||
expect(new Set(seen).size).toBe(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("loads a promise plugin and registers a transform hook", () =>
|
||||
Effect.gen(function* () {
|
||||
const agents = yield* AgentV2.Service
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const addPlugin = Effect.fn(function* () {
|
|||
|
||||
describe("KiloPlugin", () => {
|
||||
it.effect("is registered so legacy referer headers can be applied", () =>
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("kilo"))),
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.kilo")),
|
||||
)
|
||||
|
||||
it.effect("applies legacy referer headers only to kilo", () =>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const addPlugin = Effect.fn(function* () {
|
|||
|
||||
describe("LLMGatewayPlugin", () => {
|
||||
it.effect("is registered so legacy referer headers can be applied", () =>
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("llmgateway"))),
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.llmgateway")),
|
||||
)
|
||||
|
||||
it.effect("applies legacy referer headers only to enabled llmgateway", () =>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const addPlugin = Effect.fn(function* () {
|
|||
|
||||
describe("NvidiaPlugin", () => {
|
||||
it.effect("is registered so legacy referer headers can be applied", () =>
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("nvidia"))),
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.nvidia")),
|
||||
)
|
||||
|
||||
it.effect("applies NVIDIA tracking headers only to nvidia", () =>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const addPlugin = Effect.fn(function* () {
|
|||
|
||||
describe("OpenRouterPlugin", () => {
|
||||
it.effect("is registered so legacy OpenRouter behavior can be applied", () =>
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("openrouter"))),
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.openrouter")),
|
||||
)
|
||||
|
||||
it.effect("applies legacy referer headers only to openrouter", () =>
|
||||
|
|
|
|||
|
|
@ -43,9 +43,11 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
|
|||
describe("SnowflakeCortexPlugin", () => {
|
||||
it.effect("is registered in ProviderPlugins before OpenAICompatiblePlugin", () =>
|
||||
Effect.sync(() => {
|
||||
expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("snowflake-cortex"))
|
||||
expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.snowflake-cortex")
|
||||
const ids = ProviderPlugins.map((p) => p.id)
|
||||
expect(ids.indexOf("snowflake-cortex")).toBeLessThan(ids.indexOf("openai-compatible"))
|
||||
expect(ids.indexOf("opencode.provider.snowflake-cortex")).toBeLessThan(
|
||||
ids.indexOf("opencode.provider.openai-compatible"),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function required<T>(value: T | undefined): T {
|
|||
|
||||
describe("ZenmuxPlugin", () => {
|
||||
it.effect("is registered so legacy referer headers can be applied", () =>
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain(PluginV2.ID.make("zenmux"))),
|
||||
Effect.sync(() => expect(ProviderPlugins.map((item) => item.id)).toContain("opencode.provider.zenmux")),
|
||||
)
|
||||
|
||||
it.effect("applies the exact legacy Zenmux headers", () =>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,15 @@ describe("SkillPlugin.Plugin", () => {
|
|||
it.effect("registers built-in skills", () =>
|
||||
Effect.gen(function* () {
|
||||
const skill = yield* SkillV2.Service
|
||||
yield* SkillPlugin.Plugin.effect(host({ skill: { ...skill, reload: skill.reload } })).pipe(
|
||||
yield* SkillPlugin.Plugin.effect(
|
||||
host({
|
||||
skill: {
|
||||
list: () => Effect.die("unused skill.list"),
|
||||
transform: skill.transform,
|
||||
reload: skill.reload,
|
||||
},
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provideService(Config.Service, Config.Service.of({ entries: () => Effect.succeed([]) })),
|
||||
Effect.provideService(
|
||||
Location.Service,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue