@evandu/super-pi 0.1.29 → 0.1.31
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/dist/client/assets/{index-DoUhMucG.css → index-BVsQMo7-.css} +1 -1
- package/dist/client/assets/index-Bv8kBXop.js +50 -0
- package/dist/client/index.html +2 -2
- package/dist/server/application/run-service.js +34 -0
- package/dist/server/runtime.d.ts +10 -3
- package/dist/server/runtime.js +27 -4
- package/dist/server/store.d.ts +3 -0
- package/dist/server/store.js +6 -0
- package/dist/server/types.d.ts +11 -0
- package/package.json +1 -1
- package/dist/client/assets/index-_xNokuGH.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-Bv8kBXop.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BVsQMo7-.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -166,10 +166,43 @@ export class RunService {
|
|
|
166
166
|
// counter advanced once per yielded delta is the real position of the call in the answer.
|
|
167
167
|
let spoken = 0;
|
|
168
168
|
const rowByToolCallId = new Map();
|
|
169
|
+
/**
|
|
170
|
+
* Rows for calls the model is STILL WRITING, keyed by the contentIndex its toolcall_start named.
|
|
171
|
+
*
|
|
172
|
+
* The index is the only handle that exists before the arguments are parsed: the tool's id arrives
|
|
173
|
+
* with toolcall_end, and the host is told nothing until tool_execution_start. Keeping the index → row
|
|
174
|
+
* map here rather than in the store is what lets tool_execution_start FILL IN the row the reader is
|
|
175
|
+
* already looking at, instead of appending a second block for the same call and making one call look
|
|
176
|
+
* like two.
|
|
177
|
+
*/
|
|
178
|
+
const preparingRowByIndex = new Map();
|
|
179
|
+
const onToolPreparing = (call) => {
|
|
180
|
+
const existing = preparingRowByIndex.get(call.contentIndex);
|
|
181
|
+
if (existing !== undefined) {
|
|
182
|
+
if (call.delta)
|
|
183
|
+
enqueueTools("a tool call argument", () => this.store.updateToolCall(conversation.id, assistant.id, existing, { live: call.delta }));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// No name yet is the normal first token, and it is NOT a reason to wait: the block appearing with an
|
|
187
|
+
// empty name the instant the model starts writing is what tells the reader anything is happening at
|
|
188
|
+
// all, and it fills in on the next delta.
|
|
189
|
+
const at = spoken;
|
|
190
|
+
enqueueTools("a tool call being prepared", () => this.store.appendToolCall(conversation.id, assistant.id, { label: call.name || "工具", detail: "正在生成参数…", preparing: true, at }));
|
|
191
|
+
preparingRowByIndex.set(call.contentIndex, preparingRowByIndex.size);
|
|
192
|
+
};
|
|
169
193
|
const onToolCall = (call) => {
|
|
170
194
|
if (call.id)
|
|
171
195
|
rowByToolCallId.set(call.id, rowByToolCallId.size);
|
|
172
196
|
const at = spoken;
|
|
197
|
+
const prepared = [...preparingRowByIndex.values()].at(-1);
|
|
198
|
+
// THE CALL THAT IS ALREADY ON SCREEN GETS FILLED IN. Appending here instead would show one call
|
|
199
|
+
// twice - once as a placeholder, once as the real thing - which reads as the agent calling a tool
|
|
200
|
+
// it never called.
|
|
201
|
+
if (prepared !== undefined) {
|
|
202
|
+
preparingRowByIndex.clear();
|
|
203
|
+
enqueueTools("a tool call", () => this.store.updateToolCall(conversation.id, assistant.id, prepared, { label: call.name, detail: call.detail, args: call.args, live: "", preparing: false }));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
173
206
|
enqueueTools("a tool call", () => this.store.appendToolCall(conversation.id, assistant.id, { label: call.name, detail: call.detail, args: call.args, at }));
|
|
174
207
|
};
|
|
175
208
|
const onToolUpdate = (call) => {
|
|
@@ -193,6 +226,7 @@ export class RunService {
|
|
|
193
226
|
onPiQueue: (snapshot) => this.onPiQueue(conversation.id, snapshot),
|
|
194
227
|
onUserEntry: (entryId) => this.store.setMessagePiEntryId(conversation.id, userMessageId, entryId),
|
|
195
228
|
onToolCall,
|
|
229
|
+
onToolPreparing,
|
|
196
230
|
onToolUpdate,
|
|
197
231
|
onToolEnd,
|
|
198
232
|
images,
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
2
|
import type { ActiveMemory } from "./active-memory.js";
|
|
3
|
-
import { type AgentDefinition, type Conversation, type ExtensionUiAnswer, type ExtensionUiRequest, type MessageImage, type ModelCatalog, type ModelOption, type ModelSelection, type PiQueueSnapshot, type ToolCallNotice } from "./types.js";
|
|
3
|
+
import { type AgentDefinition, type Conversation, type ExtensionUiAnswer, type ExtensionUiRequest, type MessageImage, type ModelCatalog, type ModelOption, type ModelSelection, type PiQueueSnapshot, type ToolCallNotice, type ToolCallPreparing } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Who answers Pi's Extension UI questions. The runtime never invents an answer: a blocking method
|
|
6
6
|
* either comes back from `ask`, or it is cancelled. That is the whole point of Pi's third response
|
|
@@ -90,6 +90,12 @@ export interface AnswerInput {
|
|
|
90
90
|
* from a frozen agent.
|
|
91
91
|
*/
|
|
92
92
|
onToolUpdate?(call: ToolCallNotice): void;
|
|
93
|
+
/**
|
|
94
|
+
* Called as the model BUILDS a tool call, before the host has been told anything about it. It is the
|
|
95
|
+
* difference between a transcript that is quiet for the seconds an 'edit' takes to write its
|
|
96
|
+
* replacement text, and one that shows the call arriving.
|
|
97
|
+
*/
|
|
98
|
+
onToolPreparing?(call: ToolCallPreparing): void;
|
|
93
99
|
}
|
|
94
100
|
/**
|
|
95
101
|
* What Pi answered when a `steer` was written to it, from the `disposition` of that command's response
|
|
@@ -273,6 +279,8 @@ export type RpcRecord = {
|
|
|
273
279
|
assistantMessageEvent?: {
|
|
274
280
|
type?: string;
|
|
275
281
|
delta?: string;
|
|
282
|
+
contentIndex?: number;
|
|
283
|
+
partial?: unknown;
|
|
276
284
|
reason?: string;
|
|
277
285
|
message?: {
|
|
278
286
|
role?: string;
|
|
@@ -558,7 +566,6 @@ export declare const catalogKey: (selection?: ModelSelection) => string;
|
|
|
558
566
|
* warning on a path that is genuinely optional rather than a startup crash on a build that skipped it.
|
|
559
567
|
*/
|
|
560
568
|
export declare function taskExtensionPath(from?: string): string;
|
|
561
|
-
/** Bridges a Pi session to Super Pi through Pi's documented JSONL RPC protocol. */
|
|
562
569
|
export declare class PiRpcRuntime implements AgentRuntime {
|
|
563
570
|
private readonly sessionDir;
|
|
564
571
|
/**
|
|
@@ -696,7 +703,7 @@ export declare class PiRpcRuntime implements AgentRuntime {
|
|
|
696
703
|
* own address, so it is the same string on every turn and is read once from the constructor's options.
|
|
697
704
|
*/
|
|
698
705
|
private taskExtensionEnv;
|
|
699
|
-
answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onToolCall, onToolUpdate, onToolEnd, images, memoryReview, memorySourceConversationId }: AnswerInput): AsyncIterable<string>;
|
|
706
|
+
answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onToolCall, onToolUpdate, onToolPreparing, onToolEnd, images, memoryReview, memorySourceConversationId }: AnswerInput): AsyncIterable<string>;
|
|
700
707
|
/**
|
|
701
708
|
* Reads back the id of the user message this turn just created, and hands it to the caller.
|
|
702
709
|
*
|
package/dist/server/runtime.js
CHANGED
|
@@ -549,6 +549,16 @@ export function taskExtensionPath(from = import.meta.dirname) {
|
|
|
549
549
|
return resolve(from, "../../agents/super-pi-task/index.ts");
|
|
550
550
|
}
|
|
551
551
|
/** Bridges a Pi session to Super Pi through Pi's documented JSONL RPC protocol. */
|
|
552
|
+
/**
|
|
553
|
+
* The tool's name as far as the stream has revealed it so far, or an empty string while the model is
|
|
554
|
+
* still on the first token. Deliberately NOT a placeholder: a block labelled 「工具」 tells the reader
|
|
555
|
+
* nothing they cannot already see, whereas one labelled 'edit' one second early is the whole point.
|
|
556
|
+
*/
|
|
557
|
+
function toolNameOf(partial) {
|
|
558
|
+
const content = partial?.content;
|
|
559
|
+
const block = Array.isArray(content) ? content.find((item) => item?.type === "toolCall") : undefined;
|
|
560
|
+
return typeof block?.name === "string" ? block.name : "";
|
|
561
|
+
}
|
|
552
562
|
export class PiRpcRuntime {
|
|
553
563
|
sessionDir;
|
|
554
564
|
agents;
|
|
@@ -700,7 +710,7 @@ export class PiRpcRuntime {
|
|
|
700
710
|
[TASK_CAPABILITY_ENV]: mintCapability(conversationId),
|
|
701
711
|
};
|
|
702
712
|
}
|
|
703
|
-
async *answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onToolCall, onToolUpdate, onToolEnd, images, memoryReview, memorySourceConversationId }) {
|
|
713
|
+
async *answer({ conversation, prompt, signal, cwd, host, onPiQueue, onUserEntry, onToolCall, onToolUpdate, onToolPreparing, onToolEnd, images, memoryReview, memorySourceConversationId }) {
|
|
704
714
|
await mkdir(this.sessionDir, { recursive: true });
|
|
705
715
|
// Normalize first: the picker can hand over forward slashes or a trailing separator, and `resolve`
|
|
706
716
|
// turns both into one absolute native path. The directory must exist on disk before spawn - a
|
|
@@ -953,11 +963,24 @@ export class PiRpcRuntime {
|
|
|
953
963
|
}
|
|
954
964
|
if (record.type === "message_update") {
|
|
955
965
|
const event = record.assistantMessageEvent;
|
|
956
|
-
// text_delta is
|
|
957
|
-
//
|
|
958
|
-
//
|
|
966
|
+
// text_delta is what drives the visible answer, and toolcall_start/delta/end are what make a
|
|
967
|
+
// call VISIBLE WHILE IT IS BEING WRITTEN.
|
|
968
|
+
//
|
|
969
|
+
// Those two used to be dropped here by a comment that named them - "the rest (thinking, tool
|
|
970
|
+
// calls, message_start/end) are consumed here". Consuming them was the bug, not the design: an
|
|
971
|
+
// `edit` call's arguments ARE the old text and the new text, so the model spends many seconds
|
|
972
|
+
// and thousands of tokens emitting them, and for every one of those seconds the screen showed
|
|
973
|
+
// nothing at all. A frozen transcript and a slow one look identical from the outside; a block
|
|
974
|
+
// that appears the instant the model starts writing its arguments is the difference.
|
|
975
|
+
//
|
|
976
|
+
// The text is what arrives - `toolcall_delta` is a raw JSON fragment, and parsing it early
|
|
977
|
+
// would mean guessing at a document that is not finished. It is handed on as it comes.
|
|
959
978
|
if (event?.type === "text_delta")
|
|
960
979
|
yield event.delta ?? "";
|
|
980
|
+
else if (event?.type === "toolcall_start" && typeof event.contentIndex === "number")
|
|
981
|
+
onToolPreparing?.({ contentIndex: event.contentIndex, name: toolNameOf(event.partial) });
|
|
982
|
+
else if (event?.type === "toolcall_delta" && typeof event.contentIndex === "number")
|
|
983
|
+
onToolPreparing?.({ contentIndex: event.contentIndex, name: "", ...(typeof event.delta === "string" ? { delta: event.delta } : {}) });
|
|
961
984
|
else
|
|
962
985
|
noteUnhandled(`message_update:${JSON.stringify(event?.type ?? "(missing)")}`);
|
|
963
986
|
continue;
|
package/dist/server/store.d.ts
CHANGED
|
@@ -264,8 +264,11 @@ export declare class Store {
|
|
|
264
264
|
* values a tool can genuinely produce, and treating them as "not reported" would erase a real answer.
|
|
265
265
|
*/
|
|
266
266
|
updateToolCall(conversationId: string, messageId: string, index: number, patch: {
|
|
267
|
+
label?: string;
|
|
268
|
+
detail?: string;
|
|
267
269
|
args?: ToolCallArg[];
|
|
268
270
|
live?: string;
|
|
271
|
+
preparing?: boolean;
|
|
269
272
|
}): Promise<ToolCallBlock[]>;
|
|
270
273
|
setStatus(conversationId: string, status: Conversation["status"]): Promise<void>;
|
|
271
274
|
/** The plan a revert would carry out, without carrying it out. The server reads this to stop children first. */
|
package/dist/server/store.js
CHANGED
|
@@ -947,6 +947,12 @@ export class Store {
|
|
|
947
947
|
// whatever the latest update happened to carry loses every key the update left out, which on the very
|
|
948
948
|
// first update is all of them. Merging can only ever add, so a sparse update can no longer erase what
|
|
949
949
|
// an earlier one already reported - and the cap is re-applied here rather than trusted from outside.
|
|
950
|
+
if (patch.label !== undefined)
|
|
951
|
+
call.label = patch.label;
|
|
952
|
+
if (patch.detail !== undefined)
|
|
953
|
+
call.detail = patch.detail;
|
|
954
|
+
if (patch.preparing !== undefined)
|
|
955
|
+
call.preparing = patch.preparing;
|
|
950
956
|
if (patch.args !== undefined) {
|
|
951
957
|
const merged = new Map((call.args ?? []).map((arg) => [arg.key, arg]));
|
|
952
958
|
for (const arg of patch.args)
|
package/dist/server/types.d.ts
CHANGED
|
@@ -131,6 +131,10 @@ export type ToolCallBlock = {
|
|
|
131
131
|
* distinction that matters when a reader copies it.
|
|
132
132
|
*/
|
|
133
133
|
live?: string;
|
|
134
|
+
/** The model is still GENERATING this call's arguments. Nothing has reached the host yet, so this is
|
|
135
|
+
* the only thing on screen during a long parameter stream - an 'edit' block carries a whole file's
|
|
136
|
+
* replacement text, and without this the screen is blank until the last token arrives. */
|
|
137
|
+
preparing?: boolean;
|
|
134
138
|
at?: number;
|
|
135
139
|
};
|
|
136
140
|
/**
|
|
@@ -164,7 +168,14 @@ export type ToolCallNotice = {
|
|
|
164
168
|
durationMs?: number;
|
|
165
169
|
result?: ToolResultSummary;
|
|
166
170
|
live?: string;
|
|
171
|
+
preparing?: boolean;
|
|
167
172
|
};
|
|
173
|
+
/** WHAT THE RUNTIME HANDS A CALLER WHEN THE MODEL STARTS, OR CONTINUES, BUILDING A TOOL CALL. */
|
|
174
|
+
export interface ToolCallPreparing {
|
|
175
|
+
contentIndex: number;
|
|
176
|
+
name: string;
|
|
177
|
+
delta?: string;
|
|
178
|
+
}
|
|
168
179
|
/**
|
|
169
180
|
* `archivedAt` is the whole archive mechanism: a state MARKER, not a move. The conversation object never
|
|
170
181
|
* changes workspace or position, so archiving and restoring are a stamp and a clear of one optional field.
|