@pi-unipi/btw 3.0.0-alpha.33 → 3.0.0-alpha.34
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/extensions/btw.ts +83 -2
- package/package.json +2 -2
package/extensions/btw.ts
CHANGED
|
@@ -154,10 +154,18 @@ export interface BtwPage {
|
|
|
154
154
|
error?: string;
|
|
155
155
|
aborted?: boolean;
|
|
156
156
|
done: boolean;
|
|
157
|
+
/** Token usage for the one assistant response (bridge forwards it in `btw_end`). */
|
|
158
|
+
usage?: { input: number; output: number; totalTokens: number };
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
const pages: BtwPage[] = [];
|
|
160
162
|
|
|
163
|
+
/** Read-only snapshot of recent pages (question, answer, error) for a
|
|
164
|
+
* `btw_list{}` reply — never mutate the result. */
|
|
165
|
+
export function listPages(): Array<{ question: string; answer: string; error?: string }> {
|
|
166
|
+
return pages.map((p) => ({ question: p.question, answer: p.answer, error: p.error }));
|
|
167
|
+
}
|
|
168
|
+
|
|
161
169
|
/** ↑/↓ paging reducer — clamp the target index into [0, count-1]. */
|
|
162
170
|
export function pageIndexAfter(current: number, delta: number, count: number): number {
|
|
163
171
|
if (count <= 0) return 0;
|
|
@@ -210,12 +218,18 @@ export function formatToolLine(
|
|
|
210
218
|
return `${mark} ${label}${detail}`;
|
|
211
219
|
}
|
|
212
220
|
|
|
221
|
+
/** Delta kinds forwarded to `onDelta` (the bridge's UI-free API streams these
|
|
222
|
+
* to the phone as `btw_delta`; the TUI panel ignores it — it re-renders
|
|
223
|
+
* `page.answer`/`toolLines` from `onUpdate` instead). */
|
|
224
|
+
export type BtwDeltaKind = "text" | "thinking" | "tool";
|
|
225
|
+
|
|
213
226
|
function runQuestion(
|
|
214
227
|
ctx: ExtensionCommandContext,
|
|
215
228
|
question: string,
|
|
216
229
|
page: BtwPage,
|
|
217
230
|
thinkingLevel: AiThinkingLevel,
|
|
218
231
|
onUpdate: () => void,
|
|
232
|
+
onDelta?: (kind: BtwDeltaKind, text: string) => void,
|
|
219
233
|
): BtwRun {
|
|
220
234
|
let session: AgentSession | null = null;
|
|
221
235
|
let unsubscribe: (() => void) | null = null;
|
|
@@ -253,8 +267,10 @@ function runQuestion(
|
|
|
253
267
|
const e = event as { toolCallId: string; toolName: string; args?: Record<string, unknown> };
|
|
254
268
|
pendingStarts.set(e.toolCallId, { args: e.args });
|
|
255
269
|
lineIndex.set(e.toolCallId, page.toolLines.length);
|
|
256
|
-
|
|
270
|
+
const line = formatToolLine(e.toolName, e.args, ctx.cwd, "running");
|
|
271
|
+
page.toolLines.push(line);
|
|
257
272
|
onUpdate();
|
|
273
|
+
onDelta?.("tool", line);
|
|
258
274
|
} else if (event.type === "tool_execution_end") {
|
|
259
275
|
const e = event as { toolCallId: string; toolName: string; isError: boolean };
|
|
260
276
|
const start = pendingStarts.get(e.toolCallId);
|
|
@@ -264,6 +280,11 @@ function runQuestion(
|
|
|
264
280
|
else page.toolLines.push(line);
|
|
265
281
|
pendingStarts.delete(e.toolCallId);
|
|
266
282
|
onUpdate();
|
|
283
|
+
onDelta?.("tool", line);
|
|
284
|
+
} else if (event.type === "message_update") {
|
|
285
|
+
const e = event as { assistantMessageEvent?: { type?: string; delta?: string } };
|
|
286
|
+
if (e.assistantMessageEvent?.type === "text_delta" && e.assistantMessageEvent.delta) onDelta?.("text", e.assistantMessageEvent.delta);
|
|
287
|
+
else if (e.assistantMessageEvent?.type === "thinking_delta" && e.assistantMessageEvent.delta) onDelta?.("thinking", e.assistantMessageEvent.delta);
|
|
267
288
|
}
|
|
268
289
|
});
|
|
269
290
|
|
|
@@ -279,6 +300,7 @@ function runQuestion(
|
|
|
279
300
|
page.error = response.errorMessage || "request failed";
|
|
280
301
|
} else {
|
|
281
302
|
page.answer = extractText(response.content, "text") || "(no text)";
|
|
303
|
+
if (response.usage) page.usage = { input: response.usage.input, output: response.usage.output, totalTokens: response.usage.totalTokens };
|
|
282
304
|
}
|
|
283
305
|
} catch (err) {
|
|
284
306
|
page.error = err instanceof Error ? err.message : String(err);
|
|
@@ -455,7 +477,64 @@ class BtwPanel implements Component {
|
|
|
455
477
|
dispose(): void {}
|
|
456
478
|
}
|
|
457
479
|
|
|
458
|
-
// ───
|
|
480
|
+
// ─── UI-free API (the app bridge, no TUI needed) ───────────────────
|
|
481
|
+
|
|
482
|
+
/** One streamed update from `BtwApi.ask` (mirrors the bridge's `btw_delta`/`btw_end`). */
|
|
483
|
+
export type BtwEvent =
|
|
484
|
+
| { type: "delta"; kind: BtwDeltaKind; text: string }
|
|
485
|
+
| { type: "end"; answer: string; error?: string; usage?: { input: number; output: number; totalTokens: number } };
|
|
486
|
+
|
|
487
|
+
export interface BtwApi {
|
|
488
|
+
/** Starts a question, streaming `onEvent` until it ends (also resolved by `finished`).
|
|
489
|
+
* Only one question runs at a time (shared with the TUI panel): a second call while
|
|
490
|
+
* one is in flight ends immediately with an error. */
|
|
491
|
+
ask(cctx: ExtensionCommandContext, question: string, onEvent: (event: BtwEvent) => void): { id: string; finished: Promise<void> };
|
|
492
|
+
/** Recent pages (question, answer, error) for this pi session — shared with the TUI panel. */
|
|
493
|
+
list(): Array<{ question: string; answer: string; error?: string }>;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const BTW_API_KEY = Symbol.for("unipi.btw.api");
|
|
497
|
+
|
|
498
|
+
/** Publishes the UI-free API on globalThis so the app bridge (a sibling
|
|
499
|
+
* extension, no shared module instance guaranteed) can ask a side question
|
|
500
|
+
* without a TUI: `(globalThis as any)[Symbol.for("unipi.btw.api")]`. */
|
|
501
|
+
function publishUiFreeApi(pi: ExtensionAPI, getActiveRun: () => BtwRun | null, setActiveRun: (run: BtwRun | null) => void): void {
|
|
502
|
+
let idSeq = 0;
|
|
503
|
+
const api: BtwApi = {
|
|
504
|
+
ask(cctx, question, onEvent) {
|
|
505
|
+
const id = `api-${process.pid}-${++idSeq}`;
|
|
506
|
+
if (getActiveRun()) {
|
|
507
|
+
const finished = Promise.resolve().then(() => onEvent({ type: "end", answer: "", error: "btw is already answering another question" }));
|
|
508
|
+
return { id, finished };
|
|
509
|
+
}
|
|
510
|
+
const page: BtwPage = { question, toolLines: [], answer: "", done: false };
|
|
511
|
+
pages.push(page);
|
|
512
|
+
const run = runQuestion(
|
|
513
|
+
cctx,
|
|
514
|
+
question,
|
|
515
|
+
page,
|
|
516
|
+
pi.getThinkingLevel() as AiThinkingLevel,
|
|
517
|
+
() => {},
|
|
518
|
+
(kind, text) => onEvent({ type: "delta", kind, text }),
|
|
519
|
+
);
|
|
520
|
+
setActiveRun(run);
|
|
521
|
+
const finished = run.finished.then(() => {
|
|
522
|
+
setActiveRun(null);
|
|
523
|
+
onEvent({ type: "end", answer: page.answer, error: page.error, usage: page.usage });
|
|
524
|
+
});
|
|
525
|
+
return { id, finished };
|
|
526
|
+
},
|
|
527
|
+
list: () => listPages(),
|
|
528
|
+
};
|
|
529
|
+
(globalThis as unknown as Record<symbol, unknown>)[BTW_API_KEY] = api;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Reads the published API, or undefined when @pi-unipi/btw is not loaded. */
|
|
533
|
+
export function getBtwApi(): BtwApi | undefined {
|
|
534
|
+
return (globalThis as unknown as Record<symbol, unknown>)[BTW_API_KEY] as BtwApi | undefined;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ─── Extension ─────────────────────────────────────────────────────────────────────
|
|
459
538
|
|
|
460
539
|
export default function btwExtension(pi: ExtensionAPI): void {
|
|
461
540
|
let panelRef: BtwPanel | null = null;
|
|
@@ -520,6 +599,8 @@ export default function btwExtension(pi: ExtensionAPI): void {
|
|
|
520
599
|
notify(ctx, page.error ? `btw failed: ${page.error}` : `btw: ${page.answer}`, page.error ? "error" : "info");
|
|
521
600
|
}
|
|
522
601
|
|
|
602
|
+
publishUiFreeApi(pi, () => activeRun, (run) => (activeRun = run));
|
|
603
|
+
|
|
523
604
|
pi.on("session_start", () => {
|
|
524
605
|
abortActiveRun();
|
|
525
606
|
clearPages();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/btw",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.34",
|
|
4
4
|
"description": "A pi extension for parallel side conversations with /unipi:btw — part of the Unipi suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "extensions/btw.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@earendil-works/pi-tui": "^0.87.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
40
|
+
"@pi-unipi/core": "3.0.0-alpha.34"
|
|
41
41
|
},
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [
|