fix(plugin): restore package publishing
This commit is contained in:
parent
b452368b3b
commit
761f37370f
20 changed files with 67 additions and 204 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
export * as PluginHooks from "./hooks"
|
export * as PluginHooks from "./hooks"
|
||||||
|
|
||||||
import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk"
|
import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk"
|
||||||
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
|
|
||||||
import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool"
|
import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool"
|
||||||
import { Context, Effect, Layer, Scope } from "effect"
|
import { Context, Effect, Layer, Scope } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
import { makeLocationNode } from "../effect/app-node"
|
||||||
|
|
@ -9,7 +8,6 @@ import { State } from "../state"
|
||||||
|
|
||||||
export interface Domains {
|
export interface Domains {
|
||||||
readonly aisdk: AISDKHooks
|
readonly aisdk: AISDKHooks
|
||||||
readonly session: SessionHooks
|
|
||||||
readonly tool: ToolHooks
|
readonly tool: ToolHooks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,6 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
hook: (name, callback) => hooks.register("session", name, callback),
|
|
||||||
create: (input) =>
|
create: (input) =>
|
||||||
runtime.session.create({
|
runtime.session.create({
|
||||||
id: input?.id,
|
id: input?.id,
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,6 @@ export function fromPromise(plugin: PromisePlugin) {
|
||||||
prompt: (input) => run(host.session.prompt(input)),
|
prompt: (input) => run(host.session.prompt(input)),
|
||||||
command: (input) => run(host.session.command(input)),
|
command: (input) => run(host.session.command(input)),
|
||||||
interrupt: (input) => run(host.session.interrupt(input)),
|
interrupt: (input) => run(host.session.interrupt(input)),
|
||||||
hook: (name, callback) =>
|
|
||||||
register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,6 @@ import { llmClient } from "../../effect/app-node-platform"
|
||||||
import { AgentNotFoundError, StepFailedError } from "../error"
|
import { AgentNotFoundError, StepFailedError } from "../error"
|
||||||
import { toSessionError } from "../to-session-error"
|
import { toSessionError } from "../to-session-error"
|
||||||
import { SessionRunnerRetry } from "./retry"
|
import { SessionRunnerRetry } from "./retry"
|
||||||
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
|
|
||||||
import { PluginHooks } from "../../plugin/hooks"
|
|
||||||
import { PluginSupervisor } from "../../plugin/supervisor"
|
import { PluginSupervisor } from "../../plugin/supervisor"
|
||||||
|
|
||||||
type StepTokens = {
|
type StepTokens = {
|
||||||
|
|
@ -91,7 +89,6 @@ const layer = Layer.effect(
|
||||||
const llm = yield* LLMClient.Service
|
const llm = yield* LLMClient.Service
|
||||||
const agents = yield* AgentV2.Service
|
const agents = yield* AgentV2.Service
|
||||||
const tools = yield* ToolRegistry.Service
|
const tools = yield* ToolRegistry.Service
|
||||||
const hooks = yield* PluginHooks.Service
|
|
||||||
const models = yield* SessionRunnerModel.Service
|
const models = yield* SessionRunnerModel.Service
|
||||||
const store = yield* SessionStore.Service
|
const store = yield* SessionStore.Service
|
||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
|
|
@ -212,30 +209,7 @@ const layer = Layer.effect(
|
||||||
const toolFibers = yield* FiberSet.make<void, ToolOutputStore.Error>()
|
const toolFibers = yield* FiberSet.make<void, ToolOutputStore.Error>()
|
||||||
const ownedToolFibers: Array<Fiber.Fiber<void, ToolOutputStore.Error>> = []
|
const ownedToolFibers: Array<Fiber.Fiber<void, ToolOutputStore.Error>> = []
|
||||||
let needsContinuation = false
|
let needsContinuation = false
|
||||||
const availableTools = new Map(request.tools.map((tool) => [tool.name, tool]))
|
const hookedRequest = request
|
||||||
const requestEvent: SessionHooks["request"] = {
|
|
||||||
sessionID: session.id,
|
|
||||||
agent: agent.id,
|
|
||||||
model: resolved.ref,
|
|
||||||
system: [...request.system],
|
|
||||||
messages: [...request.messages],
|
|
||||||
tools: Object.fromEntries(
|
|
||||||
request.tools.map((tool) => [tool.name, { description: tool.description, input: { ...tool.inputSchema } }]),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
// Plugins may reshape the draft, but cannot advertise tools excluded earlier
|
|
||||||
// by permissions or registration state.
|
|
||||||
yield* hooks.trigger("session", "request", requestEvent)
|
|
||||||
const hookedRequest = LLM.updateRequest(request, {
|
|
||||||
system: requestEvent.system,
|
|
||||||
messages: requestEvent.messages,
|
|
||||||
tools: Object.entries(requestEvent.tools).flatMap(([name, tool]) => {
|
|
||||||
const registered = availableTools.get(name)
|
|
||||||
if (!registered) return []
|
|
||||||
return [{ ...registered, description: tool.description, inputSchema: tool.input }]
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
const advertisedTools = new Set(hookedRequest.tools.map((tool) => tool.name))
|
|
||||||
// Automatic compaction completed; rebuild the request from compacted history.
|
// Automatic compaction completed; rebuild the request from compacted history.
|
||||||
if (
|
if (
|
||||||
!(yield* SessionPending.compaction(db, session.id)) &&
|
!(yield* SessionPending.compaction(db, session.id)) &&
|
||||||
|
|
@ -280,21 +254,6 @@ const layer = Layer.effect(
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// A request hook hid this registered tool from the current request. Fail only
|
|
||||||
// this call durably and continue so the model can react, instead of executing
|
|
||||||
// a tool that was not advertised. Unregistered tools flow through settle, which
|
|
||||||
// durably fails them as unknown.
|
|
||||||
if (!advertisedTools.has(event.name) && availableTools.has(event.name)) {
|
|
||||||
needsContinuation = true
|
|
||||||
yield* publish(
|
|
||||||
LLMEvent.toolError({
|
|
||||||
id: event.id,
|
|
||||||
name: event.name,
|
|
||||||
message: `Tool is not available for this request: ${event.name}`,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
needsContinuation = true
|
needsContinuation = true
|
||||||
const assistantMessageID = yield* publisher.assistantMessageID(event.id)
|
const assistantMessageID = yield* publisher.assistantMessageID(event.id)
|
||||||
ownedToolFibers.push(
|
ownedToolFibers.push(
|
||||||
|
|
@ -624,7 +583,6 @@ export const node = makeLocationNode({
|
||||||
llmClient,
|
llmClient,
|
||||||
AgentV2.node,
|
AgentV2.node,
|
||||||
ToolRegistry.node,
|
ToolRegistry.node,
|
||||||
PluginHooks.node,
|
|
||||||
SessionRunnerModel.node,
|
SessionRunnerModel.node,
|
||||||
SessionStore.node,
|
SessionStore.node,
|
||||||
Location.node,
|
Location.node,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { LLMError, ToolFailure } from "@opencode-ai/llm"
|
import { LLMError, ToolFailure } from "@opencode-ai/llm"
|
||||||
|
import { Tool } from "@opencode-ai/plugin/v2/effect/tool"
|
||||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { QuestionV2 } from "../question"
|
import { QuestionV2 } from "../question"
|
||||||
|
|
@ -38,7 +39,7 @@ export function toSessionError(cause: unknown): SessionError.Error {
|
||||||
}
|
}
|
||||||
if (cause instanceof PermissionV2.BlockedError) return { type: "permission.rejected", message: cause.message }
|
if (cause instanceof PermissionV2.BlockedError) return { type: "permission.rejected", message: cause.message }
|
||||||
if (cause instanceof QuestionV2.RejectedError) return { type: "aborted", message: cause.message }
|
if (cause instanceof QuestionV2.RejectedError) return { type: "aborted", message: cause.message }
|
||||||
if (cause instanceof ToolFailure)
|
if (cause instanceof ToolFailure || cause instanceof Tool.Failure)
|
||||||
return cause.error === undefined ? { type: "tool.execution", message: cause.message } : toSessionError(cause.error)
|
return cause.error === undefined ? { type: "tool.execution", message: cause.message } : toSessionError(cause.error)
|
||||||
if (cause instanceof StepFailedError) return cause.error
|
if (cause instanceof StepFailedError) return cause.error
|
||||||
if (cause instanceof AgentNotFoundError) return { type: "unknown", message: cause.message }
|
if (cause instanceof AgentNotFoundError) return { type: "unknown", message: cause.message }
|
||||||
|
|
|
||||||
|
|
@ -194,19 +194,6 @@ export const Plugin = {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
|
|
||||||
yield* ctx.session.hook("request", (event) =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
const usePatch =
|
|
||||||
event.model.providerID.toLowerCase() === "openai" || event.model.id.toLowerCase().includes("gpt")
|
|
||||||
if (usePatch) {
|
|
||||||
delete event.tools.edit
|
|
||||||
delete event.tools.write
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete event.tools.patch
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -192,32 +192,5 @@ export const Plugin = {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
|
|
||||||
yield* ctx.session.hook("request", (event) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const tool = event.tools[name]
|
|
||||||
if (!tool) return
|
|
||||||
const selected = yield* agents.resolve(event.agent)
|
|
||||||
if (!selected) return
|
|
||||||
const available = (yield* agents.list())
|
|
||||||
.filter(
|
|
||||||
(agent) =>
|
|
||||||
agent.mode !== "primary" &&
|
|
||||||
!agent.hidden &&
|
|
||||||
PermissionV2.evaluate(name, agent.id, selected.permissions).effect !== "deny",
|
|
||||||
)
|
|
||||||
.toSorted((a, b) => a.id.localeCompare(b.id))
|
|
||||||
if (available.length === 0) return
|
|
||||||
tool.description = [
|
|
||||||
tool.description,
|
|
||||||
"",
|
|
||||||
"Available subagents:",
|
|
||||||
...available.map(
|
|
||||||
(agent) =>
|
|
||||||
`- ${agent.id}: ${agent.description ?? "This subagent should only be called when explicitly requested."}`,
|
|
||||||
),
|
|
||||||
].join("\n")
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import { describe, expect, it } from "bun:test"
|
|
||||||
import { Message, SystemPart } from "@opencode-ai/llm"
|
|
||||||
import { Agent } from "@opencode-ai/schema/agent"
|
|
||||||
import { Model } from "@opencode-ai/schema/model"
|
|
||||||
import { Provider } from "@opencode-ai/schema/provider"
|
|
||||||
import { Session } from "@opencode-ai/schema/session"
|
|
||||||
import { Effect, Layer } from "effect"
|
|
||||||
import { PluginHooks } from "../src/plugin/hooks"
|
|
||||||
|
|
||||||
describe("PluginHooks", () => {
|
|
||||||
it("registers scoped domain hooks and triggers them sequentially", async () => {
|
|
||||||
const seen: string[] = []
|
|
||||||
const program = Effect.gen(function* () {
|
|
||||||
const hooks = yield* PluginHooks.Service
|
|
||||||
yield* hooks.register("session", "request", (event) =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
seen.push("first")
|
|
||||||
event.system.push(SystemPart.make("second"))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
yield* hooks.register("session", "request", (event) =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
seen.push(event.system[1]?.text ?? "missing")
|
|
||||||
event.messages = [Message.user("changed")]
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
const event = {
|
|
||||||
sessionID: Session.ID.make("ses_hooks"),
|
|
||||||
agent: Agent.ID.make("build"),
|
|
||||||
model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make("model") }),
|
|
||||||
system: [SystemPart.make("first")],
|
|
||||||
messages: [Message.user("original")],
|
|
||||||
tools: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(yield* hooks.trigger("session", "request", event)).toBe(event)
|
|
||||||
expect(seen).toEqual(["first", "second"])
|
|
||||||
expect(event.messages).toEqual([Message.user("changed")])
|
|
||||||
})
|
|
||||||
|
|
||||||
await Effect.runPromise(
|
|
||||||
Effect.scoped(program).pipe(
|
|
||||||
Effect.provide(PluginHooks.node.implementation as Layer.Layer<PluginHooks.Service>),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -83,9 +83,6 @@ export function host(overrides: Overrides = {}): PluginContext {
|
||||||
prompt: () => Effect.die("unused session.prompt"),
|
prompt: () => Effect.die("unused session.prompt"),
|
||||||
command: () => Effect.die("unused session.command"),
|
command: () => Effect.die("unused session.command"),
|
||||||
interrupt: () => Effect.die("unused session.interrupt"),
|
interrupt: () => Effect.die("unused session.interrupt"),
|
||||||
// Plugins register session hooks during setup, so a bare host accepts the
|
|
||||||
// registration; the callback only runs when a test triggers the request pipeline.
|
|
||||||
hook: () => Effect.succeed({ dispose: Effect.void }),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
ToolFailure,
|
ToolFailure,
|
||||||
} from "@opencode-ai/llm"
|
} from "@opencode-ai/llm"
|
||||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||||
|
import { Tool } from "@opencode-ai/plugin/v2/effect/tool"
|
||||||
import { toSessionError } from "@opencode-ai/core/session/to-session-error"
|
import { toSessionError } from "@opencode-ai/core/session/to-session-error"
|
||||||
import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry"
|
import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry"
|
||||||
|
|
||||||
|
|
@ -64,6 +65,10 @@ describe("toSessionError", () => {
|
||||||
type: "permission.rejected",
|
type: "permission.rejected",
|
||||||
message: "Permission denied: external_directory",
|
message: "Permission denied: external_directory",
|
||||||
})
|
})
|
||||||
|
expect(toSessionError(new Tool.Failure({ message: "failed" }))).toEqual({
|
||||||
|
type: "tool.execution",
|
||||||
|
message: "failed",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("retries only rate limits, provider-internal failures, and transport failures", () => {
|
test("retries only rate limits, provider-internal failures, and transport failures", () => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "bun test --timeout 5000",
|
"test": "bun test --timeout 5000",
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
"build": "tsc"
|
"build": "tsc -p tsconfig.build.json"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "3.0.8",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/llm": "workspace:*",
|
|
||||||
"@opencode-ai/protocol": "workspace:*",
|
"@opencode-ai/protocol": "workspace:*",
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ async function published(name: string, version: string) {
|
||||||
return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
|
return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
await $`bun tsc`
|
await $`bun run build`
|
||||||
const originalText = await Bun.file("package.json").text()
|
const originalText = await Bun.file("package.json").text()
|
||||||
const pkg = JSON.parse(originalText) as {
|
const pkg = JSON.parse(originalText) as {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,3 @@
|
||||||
import type { SessionApi } from "@opencode-ai/client/effect/api"
|
import type { SessionApi } from "@opencode-ai/client/effect/api"
|
||||||
import type { Message, SystemPart } from "@opencode-ai/llm"
|
|
||||||
import type { Agent } from "@opencode-ai/schema/agent"
|
|
||||||
import type { Model } from "@opencode-ai/schema/model"
|
|
||||||
import type { Session } from "@opencode-ai/schema/session"
|
|
||||||
import type { JsonSchema } from "effect"
|
|
||||||
import type { Hooks } from "./registration.js"
|
|
||||||
|
|
||||||
export interface SessionRequestBeforeEvent {
|
export type SessionDomain = Pick<SessionApi<unknown>, "create" | "get" | "prompt" | "command" | "interrupt">
|
||||||
readonly sessionID: Session.ID
|
|
||||||
readonly agent: Agent.ID
|
|
||||||
readonly model: Model.Ref
|
|
||||||
system: Array<SystemPart>
|
|
||||||
messages: Array<Message>
|
|
||||||
tools: Record<string, { description: string; input: JsonSchema.JsonSchema }>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionHooks {
|
|
||||||
readonly request: SessionRequestBeforeEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionDomain
|
|
||||||
extends Pick<SessionApi<unknown>, "create" | "get" | "prompt" | "command" | "interrupt"> {
|
|
||||||
readonly hook: Hooks<SessionHooks>
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export * as Tool from "./tool.js"
|
export * as Tool from "./tool.js"
|
||||||
|
|
||||||
import { ToolDefinition, ToolFailure, ToolOutput, type ToolCall, type ToolResultValue } from "@opencode-ai/llm"
|
|
||||||
import { Agent } from "@opencode-ai/schema/agent"
|
import { Agent } from "@opencode-ai/schema/agent"
|
||||||
|
import type { LLM } from "@opencode-ai/schema/llm"
|
||||||
import { Session } from "@opencode-ai/schema/session"
|
import { Session } from "@opencode-ai/schema/session"
|
||||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||||
import { Effect, JsonSchema, Schema, type Scope } from "effect"
|
import { Effect, JsonSchema, Schema, type Scope } from "effect"
|
||||||
|
|
@ -16,6 +16,29 @@ export interface Context {
|
||||||
|
|
||||||
export type SchemaType<A> = Schema.Codec<A, any>
|
export type SchemaType<A> = Schema.Codec<A, any>
|
||||||
|
|
||||||
|
type ToolDefinition = {
|
||||||
|
readonly name: string
|
||||||
|
readonly description: string
|
||||||
|
readonly inputSchema: JsonSchema.JsonSchema
|
||||||
|
readonly outputSchema?: JsonSchema.JsonSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolCall = {
|
||||||
|
readonly input: unknown
|
||||||
|
readonly [key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolResultValue =
|
||||||
|
| { readonly type: "json"; readonly value: unknown }
|
||||||
|
| { readonly type: "text"; readonly value: unknown }
|
||||||
|
| { readonly type: "error"; readonly value: unknown }
|
||||||
|
| { readonly type: "content"; readonly value: ReadonlyArray<LLM.ToolContent> }
|
||||||
|
|
||||||
|
type ToolOutput = {
|
||||||
|
readonly structured: unknown
|
||||||
|
readonly content: ReadonlyArray<LLM.ToolContent>
|
||||||
|
}
|
||||||
|
|
||||||
declare const TypeId: unique symbol
|
declare const TypeId: unique symbol
|
||||||
|
|
||||||
export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
|
export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
|
||||||
|
|
@ -26,8 +49,11 @@ export interface Definition<Input extends SchemaType<any>, Output extends Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyTool = Definition<any, any>
|
export type AnyTool = Definition<any, any>
|
||||||
export const Failure = ToolFailure
|
export class Failure extends Schema.TaggedErrorClass<Failure>()("LLM.ToolFailure", {
|
||||||
export type Failure = ToolFailure
|
message: Schema.String,
|
||||||
|
error: Schema.optional(Schema.Defect()),
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||||
|
}) {}
|
||||||
|
|
||||||
export class RegistrationError extends Schema.TaggedErrorClass<RegistrationError>()("Tool.RegistrationError", {
|
export class RegistrationError extends Schema.TaggedErrorClass<RegistrationError>()("Tool.RegistrationError", {
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
|
|
@ -54,7 +80,7 @@ type Config<
|
||||||
readonly execute: (
|
readonly execute: (
|
||||||
input: Schema.Schema.Type<Input>,
|
input: Schema.Schema.Type<Input>,
|
||||||
context: Context,
|
context: Context,
|
||||||
) => Effect.Effect<Schema.Schema.Type<Output>, ToolFailure>
|
) => Effect.Effect<Schema.Schema.Type<Output>, Failure>
|
||||||
readonly toModelOutput?: (input: {
|
readonly toModelOutput?: (input: {
|
||||||
readonly input: Schema.Schema.Type<Input>
|
readonly input: Schema.Schema.Type<Input>
|
||||||
readonly output: Output["Encoded"]
|
readonly output: Output["Encoded"]
|
||||||
|
|
@ -75,13 +101,13 @@ type DynamicConfig = {
|
||||||
readonly description: string
|
readonly description: string
|
||||||
readonly jsonSchema: JsonSchema.JsonSchema
|
readonly jsonSchema: JsonSchema.JsonSchema
|
||||||
readonly outputSchema?: JsonSchema.JsonSchema
|
readonly outputSchema?: JsonSchema.JsonSchema
|
||||||
readonly execute: (input: unknown, context: Context) => Effect.Effect<DynamicOutput, ToolFailure>
|
readonly execute: (input: unknown, context: Context) => Effect.Effect<DynamicOutput, Failure>
|
||||||
}
|
}
|
||||||
|
|
||||||
type Runtime = {
|
type Runtime = {
|
||||||
readonly permission?: string
|
readonly permission?: string
|
||||||
readonly definition: (name: string) => ToolDefinition
|
readonly definition: (name: string) => ToolDefinition
|
||||||
readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, ToolFailure>
|
readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, Failure>
|
||||||
}
|
}
|
||||||
|
|
||||||
const runtimes = new WeakMap<AnyTool, Runtime>()
|
const runtimes = new WeakMap<AnyTool, Runtime>()
|
||||||
|
|
@ -108,18 +134,18 @@ function makeTyped<
|
||||||
definition: (name) => {
|
definition: (name) => {
|
||||||
const cached = definitions.get(name)
|
const cached = definitions.get(name)
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
const definition = new ToolDefinition({
|
const definition: ToolDefinition = {
|
||||||
name,
|
name,
|
||||||
description: config.description,
|
description: config.description,
|
||||||
inputSchema: toJsonSchema(config.input),
|
inputSchema: toJsonSchema(config.input),
|
||||||
outputSchema: toJsonSchema(config.structured ?? config.output),
|
outputSchema: toJsonSchema(config.structured ?? config.output),
|
||||||
})
|
}
|
||||||
definitions.set(name, definition)
|
definitions.set(name, definition)
|
||||||
return definition
|
return definition
|
||||||
},
|
},
|
||||||
settle: (call, context) =>
|
settle: (call, context) =>
|
||||||
Schema.decodeUnknownEffect(config.input)(call.input).pipe(
|
Schema.decodeUnknownEffect(config.input)(call.input).pipe(
|
||||||
Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })),
|
Effect.mapError((error) => new Failure({ message: `Invalid tool input: ${error.message}` })),
|
||||||
Effect.flatMap((input) =>
|
Effect.flatMap((input) =>
|
||||||
config.execute(input, context).pipe(
|
config.execute(input, context).pipe(
|
||||||
Effect.flatMap((output) =>
|
Effect.flatMap((output) =>
|
||||||
|
|
@ -133,7 +159,7 @@ function makeTyped<
|
||||||
}),
|
}),
|
||||||
Effect.mapError(
|
Effect.mapError(
|
||||||
(error) =>
|
(error) =>
|
||||||
new ToolFailure({
|
new Failure({
|
||||||
message: `Tool returned an invalid value for its output schema: ${error.message}`,
|
message: `Tool returned an invalid value for its output schema: ${error.message}`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
@ -159,12 +185,12 @@ function makeDynamic(config: DynamicConfig): AnyTool {
|
||||||
definition: (name) => {
|
definition: (name) => {
|
||||||
const cached = definitions.get(name)
|
const cached = definitions.get(name)
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
const definition = new ToolDefinition({
|
const definition: ToolDefinition = {
|
||||||
name,
|
name,
|
||||||
description: config.description,
|
description: config.description,
|
||||||
inputSchema: config.jsonSchema,
|
inputSchema: config.jsonSchema,
|
||||||
outputSchema: config.outputSchema,
|
outputSchema: config.outputSchema,
|
||||||
})
|
}
|
||||||
definitions.set(name, definition)
|
definitions.set(name, definition)
|
||||||
return definition
|
return definition
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
export type { PluginOptions } from "../options.js"
|
export type { PluginOptions } from "../options.js"
|
||||||
export * as Plugin from "./plugin.js"
|
export * as Plugin from "./plugin.js"
|
||||||
|
|
||||||
|
export { Tool } from "./tool.js"
|
||||||
|
|
||||||
export { Agent } from "@opencode-ai/schema/agent"
|
export { Agent } from "@opencode-ai/schema/agent"
|
||||||
export { Command } from "@opencode-ai/schema/command"
|
export { Command } from "@opencode-ai/schema/command"
|
||||||
export { Connection } from "@opencode-ai/schema/connection"
|
export { Connection } from "@opencode-ai/schema/connection"
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,3 @@
|
||||||
import type { SessionApi } from "@opencode-ai/client/promise/api"
|
import type { SessionApi } from "@opencode-ai/client/promise/api"
|
||||||
import type { Message, SystemPart } from "@opencode-ai/llm"
|
|
||||||
import type { Agent } from "@opencode-ai/schema/agent"
|
|
||||||
import type { Model } from "@opencode-ai/schema/model"
|
|
||||||
import type { Session } from "@opencode-ai/schema/session"
|
|
||||||
import type { JsonSchema } from "effect"
|
|
||||||
import type { Hooks } from "./registration.js"
|
|
||||||
|
|
||||||
export interface SessionRequestBeforeEvent {
|
export type SessionDomain = Pick<SessionApi, "create" | "get" | "prompt" | "command" | "interrupt">
|
||||||
readonly sessionID: Session.ID
|
|
||||||
readonly agent: Agent.ID
|
|
||||||
readonly model: Model.Ref
|
|
||||||
system: Array<SystemPart>
|
|
||||||
messages: Array<Message>
|
|
||||||
tools: Record<string, { description: string; input: JsonSchema.JsonSchema }>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionHooks {
|
|
||||||
readonly request: SessionRequestBeforeEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionDomain extends Pick<SessionApi, "create" | "get" | "prompt" | "command" | "interrupt"> {
|
|
||||||
readonly hook: Hooks<SessionHooks>
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
export * as Tool from "./tool.js"
|
export * as Tool from "./tool.js"
|
||||||
|
|
||||||
import { Tool } from "../effect/tool.js"
|
import { Tool } from "../effect/tool.js"
|
||||||
import type { ToolOutput, ToolResultValue } from "@opencode-ai/llm"
|
|
||||||
import type { Agent } from "@opencode-ai/schema/agent"
|
import type { Agent } from "@opencode-ai/schema/agent"
|
||||||
import type { Session } from "@opencode-ai/schema/session"
|
import type { Session } from "@opencode-ai/schema/session"
|
||||||
import type { SessionMessage } from "@opencode-ai/schema/session-message"
|
import type { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||||
|
|
@ -85,8 +84,8 @@ export interface ToolExecuteAfterEvent {
|
||||||
readonly assistantMessageID: SessionMessage.ID
|
readonly assistantMessageID: SessionMessage.ID
|
||||||
readonly toolCallID: string
|
readonly toolCallID: string
|
||||||
readonly input: unknown
|
readonly input: unknown
|
||||||
result: ToolResultValue
|
result: Tool.ToolExecuteAfterEvent["result"]
|
||||||
output?: ToolOutput
|
output?: Tool.ToolExecuteAfterEvent["output"]
|
||||||
outputPaths?: ReadonlyArray<string>
|
outputPaths?: ReadonlyArray<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ test.each([
|
||||||
"Credential",
|
"Credential",
|
||||||
"Integration",
|
"Integration",
|
||||||
"Model",
|
"Model",
|
||||||
|
"Plugin",
|
||||||
"Provider",
|
"Provider",
|
||||||
"Reference",
|
"Reference",
|
||||||
"Skill",
|
"Skill",
|
||||||
...(name === "effect" ? ["Tool"] : []),
|
...(name === "promise" ? ["Tool"] : []),
|
||||||
"define",
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
|
||||||
8
packages/plugin/tsconfig.build.json
Normal file
8
packages/plugin/tsconfig.build.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"noEmit": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,9 @@ await $`bun ./packages/cli/script/publish.ts`
|
||||||
console.log("\n=== sdk ===\n")
|
console.log("\n=== sdk ===\n")
|
||||||
await $`bun ./packages/sdk/js/script/publish.ts`
|
await $`bun ./packages/sdk/js/script/publish.ts`
|
||||||
|
|
||||||
|
console.log("\n=== plugin ===\n")
|
||||||
|
await $`bun ./packages/plugin/script/publish.ts`
|
||||||
|
|
||||||
console.log("\n=== ui ===\n")
|
console.log("\n=== ui ===\n")
|
||||||
await $`bun ./packages/ui/script/publish.ts`
|
await $`bun ./packages/ui/script/publish.ts`
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue