@zeroroot-ai/zerocool 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,175 +0,0 @@
1
- import { tool } from "@opencode-ai/plugin";
2
- import { callGibsonTool, listGibsonPlugins, listGibsonTools, queryGibsonPlugin, } from "@zeroroot-ai/sdk";
3
- /**
4
- * Gibson tools and plugins as opencode tools (zerocool-plugins#9).
5
- *
6
- * Discovered tools are registered **individually** — one opencode tool per
7
- * Gibson tool, named `gibson_<tool>` — so the model sees them in its tool list
8
- * with their own descriptions, rather than having to know that a generic
9
- * dispatcher exists and what to pass it.
10
- *
11
- * ON SCHEMAS — the catalog carries no JSON Schema. `ToolDescriptorProto` only
12
- * names the proto message types of a tool's input and output
13
- * (`gibson.tool.v1.ScanRequest`), and there is no RPC that resolves those to a
14
- * schema. So a per-tool wrapper takes a single free-form `input` object and
15
- * names the expected message type in its description. Inventing a Zod schema
16
- * from a message type name would be a guess, and a wrong guess would reject
17
- * valid input before it ever reached the tool.
18
- *
19
- * A generic `gibson_call_tool` is always registered as well: discovery is
20
- * `Unimplemented` on the current daemon (gibson#1186), and without it there
21
- * would be no way to reach a tool whose name the operator already knows.
22
- */
23
- /** Sanitise a Gibson tool name into an opencode tool key. */
24
- export function toolKey(name) {
25
- return `gibson_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
26
- }
27
- /** Wrap one discovered Gibson tool as an opencode tool. */
28
- export function wrapGibsonTool(session, t) {
29
- const z = tool.schema;
30
- const schemaNote = t.inputMessageType
31
- ? ` Input must match the Gibson message type ${t.inputMessageType}.`
32
- : "";
33
- return tool({
34
- description: `${t.description || `Gibson tool "${t.name}"`}${schemaNote} Runs through the Gibson harness (authorized and metered).`,
35
- args: {
36
- input: z
37
- .record(z.string(), z.any())
38
- .describe(`Input object for the ${t.name} tool${t.inputMessageType ? ` (${t.inputMessageType})` : ""}.`),
39
- timeout_ms: z.number().optional().describe("Per-call timeout in milliseconds."),
40
- },
41
- async execute(args) {
42
- const result = await callGibsonTool(session.clients.component, {
43
- name: t.name,
44
- input: args.input,
45
- ...(args.timeout_ms ? { timeoutMs: args.timeout_ms } : {}),
46
- });
47
- return formatToolResult(t.name, result);
48
- },
49
- });
50
- }
51
- /** The always-present dispatcher for tools the catalog did not surface. */
52
- export function callToolTool(session) {
53
- const z = tool.schema;
54
- return tool({
55
- description: "Invoke a Gibson tool by name through the harness. Use this when you know a " +
56
- "tool's name but it is not in your tool list — Gibson tool discovery is not " +
57
- "available on every daemon.",
58
- args: {
59
- name: z.string().describe("Registered Gibson tool name."),
60
- input: z.record(z.string(), z.any()).describe("Input object matching the tool's input schema."),
61
- timeout_ms: z.number().optional().describe("Per-call timeout in milliseconds."),
62
- },
63
- async execute(args) {
64
- const result = await callGibsonTool(session.clients.component, {
65
- name: args.name,
66
- input: args.input,
67
- ...(args.timeout_ms ? { timeoutMs: args.timeout_ms } : {}),
68
- });
69
- return formatToolResult(args.name, result);
70
- },
71
- });
72
- }
73
- /** List what Gibson exposes to this tenant — tools and enabled plugins. */
74
- export function listToolsTool(session) {
75
- return tool({
76
- description: "List the Gibson tools and plugins available to this tenant, with the input " +
77
- "message type each tool expects.",
78
- args: {},
79
- async execute() {
80
- const [discovery, plugins] = await Promise.all([
81
- listGibsonTools(session.clients.component),
82
- listGibsonPlugins(session.clients.component).catch(() => []),
83
- ]);
84
- const lines = [];
85
- if (discovery.unavailable) {
86
- lines.push(`Tool discovery is unavailable: ${discovery.unavailable}.`);
87
- }
88
- else if (discovery.tools.length === 0) {
89
- lines.push("No Gibson tools are registered for this tenant.");
90
- }
91
- else {
92
- lines.push("Gibson tools:");
93
- for (const t of discovery.tools) {
94
- lines.push(`- ${t.name} (${t.version}) — ${t.description || "no description"}`);
95
- if (t.inputMessageType)
96
- lines.push(` input: ${t.inputMessageType}`);
97
- }
98
- }
99
- const enabled = plugins.filter((p) => p.enabled);
100
- if (enabled.length > 0) {
101
- lines.push("", "Enabled plugins:");
102
- for (const p of enabled) {
103
- lines.push(`- ${p.name} (${p.version}) — methods: ${p.methods.join(", ") || "none"}`);
104
- }
105
- }
106
- return {
107
- title: `${discovery.tools.length} tools, ${enabled.length} plugins`,
108
- output: lines.join("\n"),
109
- metadata: { tool_count: discovery.tools.length, plugin_count: enabled.length },
110
- };
111
- },
112
- });
113
- }
114
- /** Invoke a method on an enabled Gibson plugin. */
115
- export function queryPluginTool(session) {
116
- const z = tool.schema;
117
- return tool({
118
- description: "Call a method on an enabled Gibson plugin through the harness. Use " +
119
- "gibson_list_tools first to see which plugins and methods are available.",
120
- args: {
121
- plugin: z.string().describe("Registered plugin name."),
122
- method: z.string().describe("Method to invoke on the plugin."),
123
- params: z.record(z.string(), z.any()).optional().describe("Method parameters."),
124
- timeout_ms: z.number().optional().describe("Per-call timeout in milliseconds."),
125
- },
126
- async execute(args) {
127
- const result = await queryGibsonPlugin(session.clients.component, {
128
- plugin: args.plugin,
129
- method: args.method,
130
- ...(args.params ? { params: args.params } : {}),
131
- ...(args.timeout_ms ? { timeoutMs: args.timeout_ms } : {}),
132
- });
133
- return formatToolResult(`${args.plugin}.${args.method}`, result);
134
- },
135
- });
136
- }
137
- /**
138
- * Discover the tenant's tools and build one opencode tool for each, plus the
139
- * always-present dispatcher, catalog and plugin tools.
140
- *
141
- * Discovery runs once at plugin load. opencode reads the `tool` hook as a
142
- * static object, so a tool registered later in the session would not be visible
143
- * to the model anyway.
144
- */
145
- export async function buildGibsonTools(session) {
146
- const tools = {
147
- gibson_call_tool: callToolTool(session),
148
- gibson_list_tools: listToolsTool(session),
149
- gibson_query_plugin: queryPluginTool(session),
150
- };
151
- const discovery = await listGibsonTools(session.clients.component);
152
- for (const t of discovery.tools) {
153
- // Never let a discovered tool shadow the built-ins.
154
- const key = toolKey(t.name);
155
- if (!(key in tools))
156
- tools[key] = wrapGibsonTool(session, t);
157
- }
158
- return {
159
- tools,
160
- discovered: discovery.tools.length,
161
- ...(discovery.unavailable ? { note: discovery.unavailable } : {}),
162
- };
163
- }
164
- /** Render a harness tool result, keeping an in-band tool error visible. */
165
- function formatToolResult(name, result) {
166
- if (result.error) {
167
- return {
168
- title: `${name} failed`,
169
- output: `Gibson tool ${name} failed [${result.error.code}]: ${result.error.message}${result.error.retryable ? " (retryable)" : ""}`,
170
- metadata: { error_code: result.error.code, retryable: result.error.retryable },
171
- };
172
- }
173
- const text = typeof result.output === "string" ? result.output : JSON.stringify(result.output, null, 2);
174
- return { title: name, output: text ?? "(no output)" };
175
- }