@evandu/super-pi 0.1.31 → 0.1.33
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/README.md +2 -2
- package/dist/client/assets/index-D6bdUvG_.css +1 -0
- package/dist/client/assets/index-dU9RhbHX.js +50 -0
- package/dist/client/index.html +2 -2
- package/dist/server/application/run-service.js +54 -11
- package/dist/server/http-server.js +19 -0
- package/dist/server/model-filter.js +3 -2
- package/dist/server/runtime.js +29 -4
- package/dist/server/terminal.d.ts +1 -2
- package/dist/server/terminal.js +2 -3
- package/dist/server/tool-calls.d.ts +21 -0
- package/dist/server/tool-calls.js +40 -0
- package/dist/server/types.d.ts +8 -1
- package/package.json +3 -2
- package/dist/client/assets/index-BVsQMo7-.css +0 -1
- package/dist/client/assets/index-Bv8kBXop.js +0 -50
package/dist/client/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="alternate icon" href="/favicon.ico" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/favicon.svg" />
|
|
10
10
|
<title>Super Pi</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-dU9RhbHX.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D6bdUvG_.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -165,9 +165,23 @@ export class RunService {
|
|
|
165
165
|
// an order that never happened. The runtime calls `onToolCall` synchronously between yields, so a
|
|
166
166
|
// counter advanced once per yielded delta is the real position of the call in the answer.
|
|
167
167
|
let spoken = 0;
|
|
168
|
+
/**
|
|
169
|
+
* ONE COUNTER FOR EVERY ROW, AND THE ONLY WAY A ROW NUMBER IS EVER MADE.
|
|
170
|
+
*
|
|
171
|
+
* Both maps below are addressed by POSITION in the message's `toolCalls` array, so both have to count
|
|
172
|
+
* in the same space. They did not: each kept its own `map.size`, and those two numbers stop agreeing
|
|
173
|
+
* the moment a turn makes two tool calls - the first call's placeholder is consumed and the map is
|
|
174
|
+
* cleared, so the SECOND placeholder is handed the row number `0` while `appendToolCall` actually put
|
|
175
|
+
* it at `1`. From there every later write lands on the wrong row: the second call's arguments overwrote
|
|
176
|
+
* the first call's finished block, and the second call's own row was never settled, so it kept
|
|
177
|
+
* 「正在生成参数…」 for the rest of the turn. A finished tool that still claims to be generating its
|
|
178
|
+
* arguments is worse than a missing one - it says the agent is stuck on work it already did.
|
|
179
|
+
*/
|
|
180
|
+
let rows = 0;
|
|
168
181
|
const rowByToolCallId = new Map();
|
|
169
182
|
/**
|
|
170
|
-
* Rows for calls the model is STILL WRITING, keyed by the contentIndex its toolcall_start named
|
|
183
|
+
* Rows for calls the model is STILL WRITING, keyed by the contentIndex its toolcall_start named, and
|
|
184
|
+
* carrying the name that index announced when it had one.
|
|
171
185
|
*
|
|
172
186
|
* The index is the only handle that exists before the arguments are parsed: the tool's id arrives
|
|
173
187
|
* with toolcall_end, and the host is told nothing until tool_execution_start. Keeping the index → row
|
|
@@ -175,34 +189,63 @@ export class RunService {
|
|
|
175
189
|
* already looking at, instead of appending a second block for the same call and making one call look
|
|
176
190
|
* like two.
|
|
177
191
|
*/
|
|
178
|
-
const
|
|
192
|
+
const preparingRows = new Map();
|
|
179
193
|
const onToolPreparing = (call) => {
|
|
180
|
-
const existing =
|
|
194
|
+
const existing = preparingRows.get(call.contentIndex);
|
|
181
195
|
if (existing !== undefined) {
|
|
196
|
+
// The line naming the call IMPROVES as the arguments arrive - a `read` learns its path from the
|
|
197
|
+
// first fragment - so it is patched rather than frozen at whatever the first frame could say. The
|
|
198
|
+
// fragment is stored as well, because that is what the reader watches grow.
|
|
182
199
|
if (call.delta)
|
|
183
|
-
enqueueTools("a tool call argument", () => this.store.updateToolCall(conversation.id, assistant.id, existing, { live: call.delta }));
|
|
200
|
+
enqueueTools("a tool call argument", () => this.store.updateToolCall(conversation.id, assistant.id, existing.row, { live: call.delta, ...(call.detail ? { detail: call.detail } : {}) }));
|
|
184
201
|
return;
|
|
185
202
|
}
|
|
186
203
|
// No name yet is the normal first token, and it is NOT a reason to wait: the block appearing with an
|
|
187
204
|
// empty name the instant the model starts writing is what tells the reader anything is happening at
|
|
188
205
|
// all, and it fills in on the next delta.
|
|
189
206
|
const at = spoken;
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
const row = rows++;
|
|
208
|
+
// The block says WHICH TOOL and WHAT IT IS AIMED AT as soon as either is known. 「工具」 as the name
|
|
209
|
+
// and 「正在生成参数…」 as the only line below it told a reader nothing for the seconds a large `edit`
|
|
210
|
+
// spends writing its replacement text - and by the time the file path was on screen, the block looked
|
|
211
|
+
// broken rather than early. The placeholder word is now only the FALLBACK for the first fragment, when
|
|
212
|
+
// Pi has not yet said what the call is or what it opens.
|
|
213
|
+
enqueueTools("a tool call being prepared", () => this.store.appendToolCall(conversation.id, assistant.id, {
|
|
214
|
+
label: call.name || "工具",
|
|
215
|
+
...(call.detail ? { detail: call.detail } : { detail: "正在生成参数…" }),
|
|
216
|
+
preparing: true,
|
|
217
|
+
at,
|
|
218
|
+
}));
|
|
219
|
+
preparingRows.set(call.contentIndex, { row, name: call.name });
|
|
192
220
|
};
|
|
193
221
|
const onToolCall = (call) => {
|
|
194
|
-
if (call.id)
|
|
195
|
-
rowByToolCallId.set(call.id, rowByToolCallId.size);
|
|
196
222
|
const at = spoken;
|
|
197
|
-
|
|
223
|
+
// THE PLACEHOLDER THAT BELONGS TO THIS CALL, IF ONE IS ON SCREEN.
|
|
224
|
+
//
|
|
225
|
+
// A model writing two tool calls writes their arguments in one unbroken stream, so more than one
|
|
226
|
+
// placeholder can be waiting when the first execution starts, and the newest is not necessarily the
|
|
227
|
+
// one being run. The NAME is the only thing that can tell them apart - `toolcall_start` carries it
|
|
228
|
+
// when Pi has parsed it - so a name match wins over recency, and recency is only the fallback for a
|
|
229
|
+
// placeholder that never got a name.
|
|
230
|
+
const pending = [...preparingRows];
|
|
231
|
+
const prepared = pending.find(([, entry]) => entry.name === call.name) ?? pending.at(-1);
|
|
198
232
|
// THE CALL THAT IS ALREADY ON SCREEN GETS FILLED IN. Appending here instead would show one call
|
|
199
233
|
// twice - once as a placeholder, once as the real thing - which reads as the agent calling a tool
|
|
200
234
|
// it never called.
|
|
201
235
|
if (prepared !== undefined) {
|
|
202
|
-
|
|
203
|
-
|
|
236
|
+
const row = prepared[1].row;
|
|
237
|
+
preparingRows.delete(prepared[0]);
|
|
238
|
+
// The id is bound to the row it was FILLED IN, not to a row of the caller's own counting. Every
|
|
239
|
+
// later update and the outcome resolve through this binding, so a wrong number here is a block
|
|
240
|
+
// that never settles and one that belongs to a different call.
|
|
241
|
+
if (call.id)
|
|
242
|
+
rowByToolCallId.set(call.id, row);
|
|
243
|
+
enqueueTools("a tool call", () => this.store.updateToolCall(conversation.id, assistant.id, row, { label: call.name, detail: call.detail, args: call.args, live: "", preparing: false }));
|
|
204
244
|
return;
|
|
205
245
|
}
|
|
246
|
+
const row = rows++;
|
|
247
|
+
if (call.id)
|
|
248
|
+
rowByToolCallId.set(call.id, row);
|
|
206
249
|
enqueueTools("a tool call", () => this.store.appendToolCall(conversation.id, assistant.id, { label: call.name, detail: call.detail, args: call.args, at }));
|
|
207
250
|
};
|
|
208
251
|
const onToolUpdate = (call) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createReadStream, existsSync } from "node:fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { readdir, stat } from "node:fs/promises";
|
|
3
4
|
import { createServer } from "node:http";
|
|
4
5
|
import { homedir, networkInterfaces } from "node:os";
|
|
@@ -191,6 +192,24 @@ export function createHttpServer(options) {
|
|
|
191
192
|
setTimeout(() => { void onShutdown("api-shutdown"); }, 50);
|
|
192
193
|
return;
|
|
193
194
|
}
|
|
195
|
+
if (url.pathname === "/api/update" && request.method === "POST") {
|
|
196
|
+
if (!isLocalOrPrivateAddress(remote)) {
|
|
197
|
+
response.writeHead(403).end("Update requires a loopback or private network POST");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
response.writeHead(202, { "content-type": "application/json; charset=utf-8" }).end(JSON.stringify({ status: "updating" }));
|
|
201
|
+
const command = process.platform === "win32" ? "pi.cmd" : "pi";
|
|
202
|
+
const child = spawn(command, ["update", "npm:@evandu/super-pi"], { cwd: rootDir, detached: true, stdio: "ignore", shell: process.platform === "win32", env: process.env });
|
|
203
|
+
child.on("close", (code) => {
|
|
204
|
+
if (code !== 0)
|
|
205
|
+
return;
|
|
206
|
+
const restart = spawn(process.execPath, [join(rootDir, "scripts", "restart-server.mjs")], { cwd: rootDir, detached: true, stdio: "ignore", env: process.env });
|
|
207
|
+
restart.unref();
|
|
208
|
+
});
|
|
209
|
+
child.on("error", () => { });
|
|
210
|
+
child.unref();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
194
213
|
if (url.pathname === "/api/health") {
|
|
195
214
|
const version = checkDiskVersion(rootDir);
|
|
196
215
|
response.writeHead(200, { "content-type": "application/json; charset=utf-8" })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Workspace
|
|
1
|
+
// Workspace paths match case-insensitively. A model is allowed when either its label
|
|
2
2
|
// or provider/modelId matches. All matching rules must allow it (intersection).
|
|
3
3
|
// Invalid regexes are skipped with a diagnostic, never interpreted as deny-all.
|
|
4
4
|
export function applicableRules(rules, workspace) {
|
|
@@ -6,7 +6,8 @@ export function applicableRules(rules, workspace) {
|
|
|
6
6
|
try {
|
|
7
7
|
const workspaceRegex = new RegExp(rule.workspacePattern, "i");
|
|
8
8
|
const modelRegex = new RegExp(rule.modelPattern, "i");
|
|
9
|
-
|
|
9
|
+
const workspacePath = workspace.path.replace(/[\\/]+/g, "/");
|
|
10
|
+
return workspaceRegex.test(workspacePath) ? [{ rule, modelRegex }] : [];
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
console.warn(`模型过滤规则 ${rule.id} 无效,已跳过:${rule.workspacePattern} → ${rule.modelPattern}:${String(error)}`);
|
package/dist/server/runtime.js
CHANGED
|
@@ -8,7 +8,7 @@ import { resolvePiCommand, shellQuote } from "./pi-command.js";
|
|
|
8
8
|
import { TASK_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
9
9
|
import { BLOCKING_UI_METHODS, EXTENSION_UI_METHODS, } from "./types.js";
|
|
10
10
|
import { loadConversationImages } from "./upload.js";
|
|
11
|
-
import { describeToolCall, describeToolCallArgs, extractToolResultText } from "./tool-calls.js";
|
|
11
|
+
import { describeToolCall, describeToolCallArgs, describeToolCallInProgress, extractToolResultText } from "./tool-calls.js";
|
|
12
12
|
/** Deterministic offline runtime used by tests and local UI development. */
|
|
13
13
|
export class WelcomeRuntime {
|
|
14
14
|
async *answer({ prompt, signal }) {
|
|
@@ -827,6 +827,18 @@ export class PiRpcRuntime {
|
|
|
827
827
|
// record is announced once per distinct type and then tallied instead of logging per event.
|
|
828
828
|
const announced = new Set();
|
|
829
829
|
const repeats = new Map();
|
|
830
|
+
/**
|
|
831
|
+
* THE ARGUMENT TEXT EACH CALL HAS PRODUCED SO FAR, keyed by the contentIndex `toolcall_start` named.
|
|
832
|
+
*
|
|
833
|
+
* Held per TURN and cleared with it, so a second turn writing a different call to the same index
|
|
834
|
+
* starts from an empty string rather than from the previous call's file path. The fragments are
|
|
835
|
+
* still handed on one at a time as `delta`; this is only what a line naming the call is read out of,
|
|
836
|
+
* and a block that could show 「正在生成参数…」 instead of the file it is about to open was telling
|
|
837
|
+
* the reader less than the record already knew.
|
|
838
|
+
*/
|
|
839
|
+
const argumentText = new Map();
|
|
840
|
+
/** The tool name `toolcall_start` announced for each index, so a later fragment can still name it. */
|
|
841
|
+
const toolNames = new Map();
|
|
830
842
|
const noteUnhandled = (key) => {
|
|
831
843
|
if (announced.has(key))
|
|
832
844
|
repeats.set(key, (repeats.get(key) ?? 0) + 1);
|
|
@@ -977,10 +989,23 @@ export class PiRpcRuntime {
|
|
|
977
989
|
// would mean guessing at a document that is not finished. It is handed on as it comes.
|
|
978
990
|
if (event?.type === "text_delta")
|
|
979
991
|
yield event.delta ?? "";
|
|
980
|
-
else if (event?.type === "toolcall_start" && typeof event.contentIndex === "number")
|
|
992
|
+
else if (event?.type === "toolcall_start" && typeof event.contentIndex === "number") {
|
|
993
|
+
// A fresh index means a fresh call, so anything the PREVIOUS call at this index wrote is
|
|
994
|
+
// dropped here rather than carried into this one's first fragment.
|
|
995
|
+
argumentText.delete(event.contentIndex);
|
|
996
|
+
toolNames.set(event.contentIndex, toolNameOf(event.partial));
|
|
981
997
|
onToolPreparing?.({ contentIndex: event.contentIndex, name: toolNameOf(event.partial) });
|
|
982
|
-
|
|
983
|
-
|
|
998
|
+
}
|
|
999
|
+
else if (event?.type === "toolcall_delta" && typeof event.contentIndex === "number") {
|
|
1000
|
+
const delta = typeof event.delta === "string" ? event.delta : "";
|
|
1001
|
+
// The NAME IS CARRIED FORWARD because the line naming the call needs it: `toolcall_start`
|
|
1002
|
+
// is the only event that carries one, and by the second fragment the mapping has to choose
|
|
1003
|
+
// between a `read`'s path and a `bash`'s command.
|
|
1004
|
+
const name = toolNames.get(event.contentIndex) ?? "";
|
|
1005
|
+
argumentText.set(event.contentIndex, `${argumentText.get(event.contentIndex) ?? ""}${delta}`);
|
|
1006
|
+
const detail = describeToolCallInProgress(name, argumentText.get(event.contentIndex) ?? "");
|
|
1007
|
+
onToolPreparing?.({ contentIndex: event.contentIndex, name, ...(delta ? { delta } : {}), ...(detail ? { detail } : {}) });
|
|
1008
|
+
}
|
|
984
1009
|
else
|
|
985
1010
|
noteUnhandled(`message_update:${JSON.stringify(event?.type ?? "(missing)")}`);
|
|
986
1011
|
continue;
|
|
@@ -99,8 +99,7 @@ export type TerminalEvent = {
|
|
|
99
99
|
reason?: string;
|
|
100
100
|
};
|
|
101
101
|
/**
|
|
102
|
-
* Reads the opt-in flag. Anything other than an explicit truthy string leaves the terminal OFF
|
|
103
|
-
* typo in a deployment config fails closed rather than silently exposing a shell.
|
|
102
|
+
* Reads the opt-in flag. Anything other than an explicit truthy string leaves the terminal OFF.
|
|
104
103
|
*/
|
|
105
104
|
export declare function isTerminalEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
106
105
|
/** The sentence a browser shows when the terminal is missing. Says WHICH of the three gates closed. */
|
package/dist/server/terminal.js
CHANGED
|
@@ -164,8 +164,7 @@ const MAX_CHUNK_BYTES = 64 * 1024;
|
|
|
164
164
|
/** Hard per-session backlog. Beyond this the session is considered runaway and is killed. */
|
|
165
165
|
const MAX_BACKLOG_BYTES = 4 * 1024 * 1024;
|
|
166
166
|
/**
|
|
167
|
-
* Reads the opt-in flag. Anything other than an explicit truthy string leaves the terminal OFF
|
|
168
|
-
* typo in a deployment config fails closed rather than silently exposing a shell.
|
|
167
|
+
* Reads the opt-in flag. Anything other than an explicit truthy string leaves the terminal OFF.
|
|
169
168
|
*/
|
|
170
169
|
export function isTerminalEnabled(env = process.env) {
|
|
171
170
|
const raw = env.SUPER_PI_TERMINAL?.trim().toLowerCase();
|
|
@@ -174,7 +173,7 @@ export function isTerminalEnabled(env = process.env) {
|
|
|
174
173
|
/** The sentence a browser shows when the terminal is missing. Says WHICH of the three gates closed. */
|
|
175
174
|
export function terminalDisabledReason(env = process.env) {
|
|
176
175
|
if (!isTerminalEnabled(env))
|
|
177
|
-
return "终端未启用。设置 SUPER_PI_TERMINAL=1
|
|
176
|
+
return "终端未启用。设置 SUPER_PI_TERMINAL=1 后重启即可使用。";
|
|
178
177
|
if (ptyLoadFailure)
|
|
179
178
|
return `node-pty 未能加载(${ptyLoadFailure})。请重新安装依赖,或确认 node-pty 版本不低于 ${MINIMUM_PTY_VERSION}。`;
|
|
180
179
|
return "";
|
|
@@ -62,6 +62,27 @@ export declare function extractToolResultText(result: unknown): ToolResultSummar
|
|
|
62
62
|
* shape can be regression-tested at all.
|
|
63
63
|
*/
|
|
64
64
|
export declare function describeToolCall(name: string, args: unknown): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* THE SAME LINE, READ OUT OF A CALL THAT IS STILL BEING WRITTEN.
|
|
67
|
+
*
|
|
68
|
+
* The block for a call appears the instant the model starts writing it, and for an `edit` that is several
|
|
69
|
+
* seconds before the arguments are complete. Showing 「工具 · 正在生成参数…」 through all of it says the
|
|
70
|
+
* reader nothing they can act on - not WHICH tool, and not what it is aimed at, which are the two things
|
|
71
|
+
* the finished block is going to say. So the same `describeToolCall` mapping runs against the fragments
|
|
72
|
+
* that have arrived so far.
|
|
73
|
+
*
|
|
74
|
+
* TWO STEPS, because a partial JSON document almost never parses. The fragments are tried whole first -
|
|
75
|
+
* that is the case where the last one closed the object. Failing that, the FIRST `"key": "value"` pair in
|
|
76
|
+
* the text is read out on its own, and its value does not have to be CLOSED - a fragment usually stops
|
|
77
|
+
* mid-string, and a path that has arrived half-written still says more than a placeholder does. A fragment
|
|
78
|
+
* that has produced neither yet yields `undefined`, and the caller decides what an unknown call says
|
|
79
|
+
* rather than this module inventing a word for it.
|
|
80
|
+
*
|
|
81
|
+
* The pair is read with a pattern rather than by parsing a repaired document: repairing JSON is a parser's
|
|
82
|
+
* job, and a half-repaired document here would risk naming the wrong file - the one thing this line must
|
|
83
|
+
* never do.
|
|
84
|
+
*/
|
|
85
|
+
export declare function describeToolCallInProgress(name: string, argumentsSoFar: string): string | undefined;
|
|
65
86
|
/**
|
|
66
87
|
* Every parameter of one call, as a bounded list of `key`/`value` pairs a block can show when expanded.
|
|
67
88
|
*
|
|
@@ -152,6 +152,46 @@ export function describeToolCall(name, args) {
|
|
|
152
152
|
}
|
|
153
153
|
return undefined;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* THE SAME LINE, READ OUT OF A CALL THAT IS STILL BEING WRITTEN.
|
|
157
|
+
*
|
|
158
|
+
* The block for a call appears the instant the model starts writing it, and for an `edit` that is several
|
|
159
|
+
* seconds before the arguments are complete. Showing 「工具 · 正在生成参数…」 through all of it says the
|
|
160
|
+
* reader nothing they can act on - not WHICH tool, and not what it is aimed at, which are the two things
|
|
161
|
+
* the finished block is going to say. So the same `describeToolCall` mapping runs against the fragments
|
|
162
|
+
* that have arrived so far.
|
|
163
|
+
*
|
|
164
|
+
* TWO STEPS, because a partial JSON document almost never parses. The fragments are tried whole first -
|
|
165
|
+
* that is the case where the last one closed the object. Failing that, the FIRST `"key": "value"` pair in
|
|
166
|
+
* the text is read out on its own, and its value does not have to be CLOSED - a fragment usually stops
|
|
167
|
+
* mid-string, and a path that has arrived half-written still says more than a placeholder does. A fragment
|
|
168
|
+
* that has produced neither yet yields `undefined`, and the caller decides what an unknown call says
|
|
169
|
+
* rather than this module inventing a word for it.
|
|
170
|
+
*
|
|
171
|
+
* The pair is read with a pattern rather than by parsing a repaired document: repairing JSON is a parser's
|
|
172
|
+
* job, and a half-repaired document here would risk naming the wrong file - the one thing this line must
|
|
173
|
+
* never do.
|
|
174
|
+
*/
|
|
175
|
+
export function describeToolCallInProgress(name, argumentsSoFar) {
|
|
176
|
+
const text = argumentsSoFar.trim();
|
|
177
|
+
if (!text)
|
|
178
|
+
return undefined;
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(text);
|
|
181
|
+
const described = describeToolCall(name, parsed);
|
|
182
|
+
if (described)
|
|
183
|
+
return described;
|
|
184
|
+
}
|
|
185
|
+
catch { /* the document is still being written, which is the normal case here */ }
|
|
186
|
+
const pair = /"([A-Za-z_][A-Za-z0-9_-]*)"\s*:\s*"((?:[^"\\]|\\.)*)/.exec(text);
|
|
187
|
+
if (!pair)
|
|
188
|
+
return undefined;
|
|
189
|
+
// The closing quote is OPTIONAL in the pattern because a fragment almost always stops mid-string, and a
|
|
190
|
+
// path that has arrived half-written is still worth more than the placeholder word. What is captured is
|
|
191
|
+
// unescaped because an escaped quote is part of the VALUE, not the end of it - without this, a file
|
|
192
|
+
// named with a quote in it would be displayed with a backslash the reader never typed.
|
|
193
|
+
return describeToolCall(name, { [pair[1]]: pair[2].replace(/\\(["\\])/g, "$1") });
|
|
194
|
+
}
|
|
155
195
|
/**
|
|
156
196
|
* How one non-string argument value is NAMED without being read.
|
|
157
197
|
*
|
package/dist/server/types.d.ts
CHANGED
|
@@ -170,11 +170,18 @@ export type ToolCallNotice = {
|
|
|
170
170
|
live?: string;
|
|
171
171
|
preparing?: boolean;
|
|
172
172
|
};
|
|
173
|
-
/** WHAT THE RUNTIME HANDS A CALLER WHEN THE MODEL STARTS, OR CONTINUES, BUILDING A TOOL CALL.
|
|
173
|
+
/** WHAT THE RUNTIME HANDS A CALLER WHEN THE MODEL STARTS, OR CONTINUES, BUILDING A TOOL CALL. *
|
|
174
|
+
*
|
|
175
|
+
* `detail` is the line naming what the call is aimed at - the file for a `read`, the command for a `bash`
|
|
176
|
+
* - read out of the arguments as far as they have arrived. It is `undefined` when nothing identifying has
|
|
177
|
+
* been written yet, which is a different fact from an empty one: the caller has a placeholder word for the
|
|
178
|
+
* second case and must not invent one for the first.
|
|
179
|
+
*/
|
|
174
180
|
export interface ToolCallPreparing {
|
|
175
181
|
contentIndex: number;
|
|
176
182
|
name: string;
|
|
177
183
|
delta?: string;
|
|
184
|
+
detail?: string;
|
|
178
185
|
}
|
|
179
186
|
/**
|
|
180
187
|
* `archivedAt` is the whole archive mechanism: a state MARKER, not a move. The conversation object never
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"scripts/queue-dock.test.mjs",
|
|
97
97
|
"scripts/md-render-browser.test.mjs",
|
|
98
98
|
"scripts/right-sidebar-browser.test.mjs",
|
|
99
|
-
"scripts/sidebar-tools-browser.test.mjs",
|
|
99
|
+
"scripts/sidebar-tools-browser.test.mjs",
|
|
100
|
+
"scripts/session-context-menu-browser.test.mjs",
|
|
100
101
|
"scripts/tool-call-blocks-browser.test.mjs",
|
|
101
102
|
"scripts/files-tab-browser.test.mjs",
|
|
102
103
|
"scripts/msg-tabs-browser.test.mjs",
|