pi-gortex 0.2.0 → 0.4.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.
package/src/tools.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  // Tool registration: each tool the bridge lists becomes a native Pi tool whose
2
2
  // execute() forwards to tools/call.
3
3
 
4
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
5
+
6
+ import { candidatePaths, editDetails, isFileEdit, withFileDiffs } from "./diff.ts";
7
+ import type { FileDiff } from "./diff.ts";
5
8
 
6
9
  import type { ToolDescriptor } from "./mcp-client.ts";
7
10
  import { callRenderer, readCallRenderer, renderResult, resultText } from "./render.ts";
8
11
  import type { RenderCall, RenderResult } from "./render.ts";
9
- import { getClient, gortexToolNames } from "./state.ts";
12
+ import { getClient, gortexToolNames, withdrawnToolNames } from "./state.ts";
10
13
  import { toolNameMap } from "./hook.ts";
11
14
 
12
15
  // Pi's reserved built-in names (mirrors toolNameMap's keys). A bridge tool that
@@ -44,7 +47,13 @@ interface BridgeToolDefinition {
44
47
  label: string;
45
48
  description: string;
46
49
  parameters: unknown;
47
- execute(toolCallId: string, params: Record<string, unknown>): Promise<unknown>;
50
+ execute(
51
+ toolCallId: string,
52
+ params: Record<string, unknown>,
53
+ signal: AbortSignal | undefined,
54
+ onUpdate: unknown,
55
+ ctx: ExtensionContext | undefined,
56
+ ): Promise<unknown>;
48
57
  renderCall: RenderCall;
49
58
  renderResult: RenderResult;
50
59
  }
@@ -86,12 +95,20 @@ export function registerOneTool(pi: ExtensionAPI, desc: ToolDescriptor): void {
86
95
  label: name,
87
96
  description: piAliasedDescription(name, piAliasName(name), (desc.description || name).trim()),
88
97
  parameters,
89
- async execute(_id: string, params: Record<string, unknown>) {
98
+ async execute(_id, params, _signal, _onUpdate, ctx) {
90
99
  const client = getClient();
91
100
  if (!client) throw new Error(`gortex ${name}: MCP bridge is not connected`);
101
+ const args = params ?? {};
102
+ const callTool = () => client.callTool(name, args);
92
103
  let result: unknown;
104
+ let diffs: FileDiff[] = [];
93
105
  try {
94
- result = await client.callTool(name, params ?? {});
106
+ if (ctx && isFileEdit(name, args)) {
107
+ const paths = candidatePaths(args, ctx.cwd);
108
+ ({ result, diffs } = await withFileDiffs(paths, ctx.cwd, callTool, (r) => !(r as { isError?: boolean })?.isError));
109
+ } else {
110
+ result = await callTool();
111
+ }
95
112
  } catch (err) {
96
113
  throw new Error(`gortex ${name} failed: ${err instanceof Error ? err.message : String(err)}`);
97
114
  }
@@ -103,7 +120,7 @@ export function registerOneTool(pi: ExtensionAPI, desc: ToolDescriptor): void {
103
120
  }
104
121
  const text = resultText(result);
105
122
  if ((result as { isError?: boolean })?.isError) throw new Error(text || `gortex ${name} failed`);
106
- return { content: [{ type: "text", text }], details: {} };
123
+ return { content: [{ type: "text", text }], details: editDetails(diffs) };
107
124
  },
108
125
  renderCall: (name === "read" ? readCallRenderer : callRenderer)(piAliasName(name)),
109
126
  renderResult,
@@ -115,13 +132,36 @@ export function registerOneTool(pi: ExtensionAPI, desc: ToolDescriptor): void {
115
132
  export async function registerGortexTools(pi: ExtensionAPI): Promise<void> {
116
133
  const client = getClient();
117
134
  if (!client) return;
135
+ const listed = new Set<string>();
118
136
  for (const desc of await client.listTools()) {
119
- if (!desc) continue;
137
+ if (!desc?.name) continue;
120
138
  registerOneTool(pi, desc);
139
+ listed.add(piAliasName(desc.name));
140
+ }
141
+ matchActiveTools(pi, listed);
142
+ }
143
+
144
+ /** Keeps the tools offered to the model in step with the daemon's list. */
145
+ function matchActiveTools(pi: ExtensionAPI, listed: Set<string>): void {
146
+ const dropped = [...gortexToolNames].filter((name) => !listed.has(name));
147
+ const relisted = [...withdrawnToolNames].filter((name) => listed.has(name));
148
+ if (dropped.length === 0 && relisted.length === 0) return;
149
+ for (const name of dropped) {
150
+ gortexToolNames.delete(name);
151
+ withdrawnToolNames.add(name);
152
+ }
153
+ for (const name of relisted) withdrawnToolNames.delete(name);
154
+ try {
155
+ const active = new Set(pi.getActiveTools());
156
+ for (const name of dropped) active.delete(name);
157
+ for (const name of relisted) active.add(name);
158
+ pi.setActiveTools([...active]);
159
+ } catch {
160
+ // an invalidated instance keeps its last tool set.
121
161
  }
122
162
  }
123
163
 
124
- /** Re-fetches the tool list after a list_changed and registers anything new. */
164
+ /** Re-fetches the tool list after a list_changed and applies the difference. */
125
165
  export async function syncTools(pi: ExtensionAPI): Promise<void> {
126
166
  try {
127
167
  await registerGortexTools(pi);