mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-30 04:23:03 +00:00
feat(plugin): expose the current location in plugin context (#45403)
Co-authored-by: thdxr <826656+thdxr@users.noreply.github.com>
This commit is contained in:
parent
6b0613ac6f
commit
3184427c03
11 changed files with 98 additions and 0 deletions
|
|
@ -73,6 +73,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: Interface, p
|
|||
|
||||
return {
|
||||
app,
|
||||
location: locationInfo(),
|
||||
options: {},
|
||||
agent: {
|
||||
get: (input) => {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,34 @@ class Secret extends Context.Service<Secret, string>()("@opencode/test/PluginSec
|
|||
const versioned = <R>(plugin: EffectPlugin.Plugin<R>, version = "1") => ({ ...plugin, version })
|
||||
|
||||
describe("Plugin", () => {
|
||||
it.effect("exposes the current location to activated plugins", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
const location = yield* Location.Service
|
||||
const seen: Location.Info[] = []
|
||||
yield* plugins.activate([
|
||||
versioned(
|
||||
EffectPlugin.define({
|
||||
id: "location-context",
|
||||
effect: (ctx) =>
|
||||
Effect.sync(() => {
|
||||
seen.push(ctx.location)
|
||||
}),
|
||||
}),
|
||||
"1",
|
||||
),
|
||||
])
|
||||
|
||||
expect(seen).toEqual([
|
||||
new Location.Info({
|
||||
directory: location.directory,
|
||||
workspaceID: location.workspaceID,
|
||||
project: location.project,
|
||||
}),
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("exposes public events through the plugin context", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ type Overrides = Partial<Omit<Plugin.Context, "options" | "session">> & {
|
|||
export function host(overrides: Overrides = {}): Plugin.Context {
|
||||
return {
|
||||
app: overrides.app ?? { name: "test", version: "test", channel: "test" },
|
||||
location:
|
||||
overrides.location ??
|
||||
new Location.Info({
|
||||
directory: AbsolutePath.make("/workspace"),
|
||||
project: {
|
||||
id: Project.ID.global,
|
||||
directory: AbsolutePath.make("/workspace"),
|
||||
canonical: AbsolutePath.make("/workspace"),
|
||||
},
|
||||
}),
|
||||
options: {},
|
||||
agent: overrides.agent ?? {
|
||||
get: () => Effect.die("unused agent.get"),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { SessionInbox } from "@opencode-ai/core/session/inbox"
|
|||
import { Tool } from "@opencode-ai/core/tool"
|
||||
import { Provider } from "@opencode-ai/core/provider"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { Workspace } from "@opencode-ai/core/workspace"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { define } from "@opencode-ai/plugin/promise/plugin"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
|
|
@ -28,6 +29,41 @@ import { host as testHost } from "./host"
|
|||
const it = testEffect(PluginTestLayer)
|
||||
|
||||
describe("fromPromise", () => {
|
||||
it.effect("exposes the host location including workspace and project metadata", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
const location = yield* Location.Service
|
||||
const expected = new Location.Info({
|
||||
directory: AbsolutePath.make("/worktree/packages/app"),
|
||||
workspaceID: Workspace.ID.make("wrk_plugin_location"),
|
||||
project: {
|
||||
id: Project.ID.global,
|
||||
directory: AbsolutePath.make("/worktree"),
|
||||
canonical: AbsolutePath.make("/project"),
|
||||
},
|
||||
})
|
||||
const host = yield* PluginHost.make(plugins).pipe(
|
||||
Effect.provideService(Location.Service, {
|
||||
...location,
|
||||
directory: expected.directory,
|
||||
workspaceID: expected.workspaceID,
|
||||
project: expected.project,
|
||||
}),
|
||||
)
|
||||
const seen: Location.Info[] = []
|
||||
yield* PluginPromise.fromPromise(
|
||||
define({
|
||||
id: "promise-location",
|
||||
setup: (ctx) => {
|
||||
seen.push(ctx.location)
|
||||
},
|
||||
}),
|
||||
).effect(host)
|
||||
|
||||
expect(seen).toEqual([expected])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("adapts plugin storage methods", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export { Command } from "@opencode-ai/schema/command"
|
|||
export { Connection } from "@opencode-ai/schema/connection"
|
||||
export { Credential } from "@opencode-ai/schema/credential"
|
||||
export { Integration } from "@opencode-ai/schema/integration"
|
||||
export { Location } from "@opencode-ai/schema/location"
|
||||
export { Mcp } from "@opencode-ai/schema/mcp"
|
||||
export { Model } from "@opencode-ai/schema/model"
|
||||
export { Provider } from "@opencode-ai/schema/provider"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GenerateApi, PluginApi } from "@opencode-ai/client/effect/api"
|
||||
import type { Location } from "@opencode-ai/schema/location"
|
||||
import type { Effect, Scope } from "effect"
|
||||
import type { PluginOptions } from "../options.js"
|
||||
import type { VcsDiscovery } from "../vcs.js"
|
||||
|
|
@ -22,6 +23,7 @@ import type { WebSearchDomain } from "./websearch.js"
|
|||
|
||||
export interface Context {
|
||||
readonly app: App
|
||||
readonly location: Location.Info
|
||||
readonly options: PluginOptions
|
||||
readonly agent: AgentDomain
|
||||
readonly aisdk: AISDKDomain
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ export function fromPromise(plugin: Plugin) {
|
|||
|
||||
const context2: Context = {
|
||||
app: host.app,
|
||||
location: host.location,
|
||||
options: host.options,
|
||||
agent: {
|
||||
get: adaptApiMethod(AgentEndpoints["agent.get"], host.agent.get),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export { Command } from "@opencode-ai/schema/command"
|
|||
export { Connection } from "@opencode-ai/schema/connection"
|
||||
export { Credential } from "@opencode-ai/schema/credential"
|
||||
export { Integration } from "@opencode-ai/schema/integration"
|
||||
export { Location } from "@opencode-ai/schema/location"
|
||||
export { Mcp } from "@opencode-ai/schema/mcp"
|
||||
export { Model } from "@opencode-ai/schema/model"
|
||||
export { Provider } from "@opencode-ai/schema/provider"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GenerateApi, PluginApi } from "@opencode-ai/client/promise/api"
|
||||
import type { Location } from "@opencode-ai/schema/location"
|
||||
import type { PluginOptions } from "../options.js"
|
||||
import type { VcsDiscovery } from "../vcs.js"
|
||||
import type { App } from "../app.js"
|
||||
|
|
@ -21,6 +22,7 @@ import type { WebSearchDomain } from "./websearch.js"
|
|||
|
||||
export interface Context {
|
||||
readonly app: App
|
||||
readonly location: Location.Info
|
||||
readonly options: PluginOptions
|
||||
readonly agent: AgentDomain
|
||||
readonly aisdk: AISDKDomain
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Command } from "@opencode-ai/schema/command"
|
|||
import { Connection } from "@opencode-ai/schema/connection"
|
||||
import { Credential } from "@opencode-ai/schema/credential"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
import { Location } from "@opencode-ai/schema/location"
|
||||
import { Mcp } from "@opencode-ai/schema/mcp"
|
||||
import { Model } from "@opencode-ai/schema/model"
|
||||
import { Provider } from "@opencode-ai/schema/provider"
|
||||
|
|
@ -26,6 +27,7 @@ test.each([
|
|||
expect(entrypoint.Connection).toBe(Connection)
|
||||
expect(entrypoint.Credential).toBe(Credential)
|
||||
expect(entrypoint.Integration).toBe(Integration)
|
||||
expect(entrypoint.Location).toBe(Location)
|
||||
expect(entrypoint.Mcp).toBe(Mcp)
|
||||
expect(entrypoint.Model).toBe(Model)
|
||||
expect(entrypoint.Provider).toBe(Provider)
|
||||
|
|
@ -39,6 +41,7 @@ test.each([
|
|||
"Connection",
|
||||
"Credential",
|
||||
"Integration",
|
||||
"Location",
|
||||
"Mcp",
|
||||
"Model",
|
||||
"Plugin",
|
||||
|
|
|
|||
|
|
@ -72,6 +72,19 @@ setup(ctx) {
|
|||
}
|
||||
```
|
||||
|
||||
`ctx.location` describes the location where this plugin instance is loaded. It
|
||||
includes `directory`, optional `workspaceID`, and `project` metadata (`id`,
|
||||
`directory`, and `canonical`). It is available in both Promise and Effect plugins.
|
||||
This is the plugin instance's location, not the location of every session it can
|
||||
access or event it receives.
|
||||
|
||||
```ts
|
||||
setup(ctx) {
|
||||
console.log(ctx.location.directory)
|
||||
console.log(ctx.location.project.canonical)
|
||||
}
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
Pass plugin options with the object form in `opencode.json(c)`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue