localpi 0.4.0 → 0.5.2
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 +29 -3
- package/dist/src/cli/cli.js +4 -2
- package/dist/src/cli/main.js +0 -0
- package/dist/src/pi/app.js +43 -2
- package/dist/src/pi/extension-sources/token-status.js +62 -14
- package/dist/src/pi/extensions.js +19 -3
- package/docs/runtime-specification.md +1 -1
- package/package.json +10 -8
- package/dist/src/pi/extension-sources/demo-mode.js +0 -52
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# localpi
|
|
2
2
|
|
|
3
|
-
Localpi is a
|
|
3
|
+
Localpi is a Swiss army knife for running Pi with local inference engines.
|
|
4
4
|
|
|
5
5
|
By default, Localpi discovers available local providers, lets you choose when more than one model is loaded, points Pi at the selected model, and writes Pi config for the other discovered models so `/model` can switch among them during the session.
|
|
6
6
|
|
|
7
|
-
Localpi
|
|
7
|
+
Localpi is meant to be the practical bridge from Pi to local inference stacks such as llama.cpp/`llama-server`, vLLM, SGLang, LM Studio, Ollama, and custom provider endpoints.
|
|
8
8
|
|
|
9
9
|
Localpi is intentionally generic. It does not contain classifier prompts, dataset workflows, GitHub routing logic, or final-schema output machinery. Structured classifier runs belong in caller tools such as `localpager-agent`.
|
|
10
10
|
|
|
@@ -18,6 +18,12 @@ See:
|
|
|
18
18
|
npm install -g localpi
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
Or the latest GitHub release directly:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g https://github.com/osolmaz/localpi/releases/latest/download/localpi.tgz
|
|
25
|
+
```
|
|
26
|
+
|
|
21
27
|
During development:
|
|
22
28
|
|
|
23
29
|
```bash
|
|
@@ -74,6 +80,14 @@ Localpi launches Pi with:
|
|
|
74
80
|
|
|
75
81
|
The approval gate makes failed or denied tool calls explicit to the model so the model does not claim that a blocked command ran.
|
|
76
82
|
|
|
83
|
+
## Diffusion Canvas Visualizer
|
|
84
|
+
|
|
85
|
+
The live diffusion canvas visualizer (watching DiffusionGemma denoise its
|
|
86
|
+
answer in the TUI) lives in its own project now:
|
|
87
|
+
[diffusionpi](https://github.com/osolmaz/diffusionpi). It is a pi-factory app
|
|
88
|
+
bundle plus a standalone Pi widget package; the widget also installs into any
|
|
89
|
+
Pi session via `pi install`.
|
|
90
|
+
|
|
77
91
|
## LM Studio Alternative
|
|
78
92
|
|
|
79
93
|
LM Studio exposes an OpenAI-compatible endpoint, usually:
|
|
@@ -115,7 +129,7 @@ Run an endless TUI demo:
|
|
|
115
129
|
localpi --demo --model gemma-e4b
|
|
116
130
|
```
|
|
117
131
|
|
|
118
|
-
Demo mode requires an explicit model, opens the normal Pi TUI, and keeps one live Pi session so followup prompts continue from the first prompt while Pi owns streaming, tok/s status, slash commands, and exit behavior.
|
|
132
|
+
Demo mode requires an explicit model, opens the normal Pi TUI, and keeps one live Pi session so followup prompts continue from the first prompt while Pi owns streaming, tok/s status, slash commands, and exit behavior. Under the hood it loads the shared [pi-demo-mode](https://github.com/osolmaz/pi-demo-mode) extension, configured through the `--demo-*` flags below.
|
|
119
133
|
|
|
120
134
|
Override the demo prompts:
|
|
121
135
|
|
|
@@ -174,6 +188,18 @@ Stop the managed `llama-server` runtime:
|
|
|
174
188
|
localpi --stop
|
|
175
189
|
```
|
|
176
190
|
|
|
191
|
+
## Demo Grid and Recording
|
|
192
|
+
|
|
193
|
+
The `localpi grid` and `localpi record` subcommands moved to
|
|
194
|
+
[demowall](https://github.com/osolmaz/demowall), a standalone tool that runs
|
|
195
|
+
N copies of any command in a tiled tmux wall and records tmux sessions to
|
|
196
|
+
video. A wall of localpi demo sessions is:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
demowall grid --concurrency 4 --start -- localpi --demo --model gemma-e4b
|
|
200
|
+
demowall record --session demowall-<timestamp> --out demo.mp4 --seconds 60
|
|
201
|
+
```
|
|
202
|
+
|
|
177
203
|
## Options
|
|
178
204
|
|
|
179
205
|
- `--runtime <auto|llama-server|lmstudio|vllm|openai-compatible>`: runtime backend. Default: `auto`
|
package/dist/src/cli/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runPiApp } from "@
|
|
1
|
+
import { runPiApp } from "@osolmaz/pi-factory";
|
|
2
2
|
import { errorMessage, fail, ok } from "../common/result.js";
|
|
3
3
|
import { parseLocalpiArgs, usage } from "../localpi/options.js";
|
|
4
4
|
import { aliasListOutput, connectionStatus, resolveRuntime, statusOutput, stopRuntime } from "../localpi/runtime.js";
|
|
@@ -22,7 +22,9 @@ export async function run(args) {
|
|
|
22
22
|
});
|
|
23
23
|
const connection = await resolveRuntime(options);
|
|
24
24
|
const selectorOptions = startupModelSelectorOptions(options, connection);
|
|
25
|
-
const extensions = await writeDefaultExtensions(options,
|
|
25
|
+
const extensions = await writeDefaultExtensions(options, {
|
|
26
|
+
...(selectorOptions === undefined ? {} : { startupModelSelector: selectorOptions })
|
|
27
|
+
});
|
|
26
28
|
const app = createLocalpiAppDefinition(options, connection, extensions);
|
|
27
29
|
return await launchResolvedRuntime(app, connection);
|
|
28
30
|
}
|
package/dist/src/cli/main.js
CHANGED
|
File without changes
|
package/dist/src/pi/app.js
CHANGED
|
@@ -4,6 +4,18 @@ const localpiAppIdentity = {
|
|
|
4
4
|
name: "localpi",
|
|
5
5
|
version: localpiVersion
|
|
6
6
|
};
|
|
7
|
+
const demoLaunchOverrideBooleanFlags = new Set([
|
|
8
|
+
"--no-tools",
|
|
9
|
+
"-nt",
|
|
10
|
+
"--no-builtin-tools",
|
|
11
|
+
"-nbt",
|
|
12
|
+
"--approve",
|
|
13
|
+
"-a",
|
|
14
|
+
"--no-approve",
|
|
15
|
+
"-na"
|
|
16
|
+
]);
|
|
17
|
+
const demoLaunchOverrideValueFlags = new Set(["--tools", "-t", "--exclude-tools", "-xt"]);
|
|
18
|
+
const demoLaunchOverrideEqualsFlags = ["--tools=", "--exclude-tools="];
|
|
7
19
|
export function createLocalpiAppDefinition(options, connection, extensions) {
|
|
8
20
|
return {
|
|
9
21
|
...localpiAppIdentity,
|
|
@@ -22,9 +34,37 @@ function appDirectories(options) {
|
|
|
22
34
|
function piCommand(options) {
|
|
23
35
|
return {
|
|
24
36
|
piCommand: options.piCommand,
|
|
25
|
-
forwardedArgs: options.forwardedArgs
|
|
37
|
+
forwardedArgs: options.demo ? demoForwardedArgs(options.forwardedArgs) : options.forwardedArgs
|
|
26
38
|
};
|
|
27
39
|
}
|
|
40
|
+
function demoForwardedArgs(args) {
|
|
41
|
+
return [...withoutConflictingDemoFlags(args), "--no-tools", "--no-approve"];
|
|
42
|
+
}
|
|
43
|
+
function withoutConflictingDemoFlags(args) {
|
|
44
|
+
const filtered = [];
|
|
45
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
46
|
+
const arg = args[index];
|
|
47
|
+
if (arg === undefined) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (isDemoLaunchOverrideFlag(arg)) {
|
|
51
|
+
if (isValueFlag(arg) && !arg.includes("=")) {
|
|
52
|
+
index += 1;
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
filtered.push(arg);
|
|
57
|
+
}
|
|
58
|
+
return filtered;
|
|
59
|
+
}
|
|
60
|
+
function isDemoLaunchOverrideFlag(arg) {
|
|
61
|
+
return (demoLaunchOverrideBooleanFlags.has(arg) ||
|
|
62
|
+
isValueFlag(arg) ||
|
|
63
|
+
demoLaunchOverrideEqualsFlags.some((flag) => arg.startsWith(flag)));
|
|
64
|
+
}
|
|
65
|
+
function isValueFlag(arg) {
|
|
66
|
+
return demoLaunchOverrideValueFlags.has(arg);
|
|
67
|
+
}
|
|
28
68
|
function runtimeSelection(options, connection) {
|
|
29
69
|
const selection = {
|
|
30
70
|
providers: providersForConnection(options, connection),
|
|
@@ -103,6 +143,7 @@ function extensionConfig(extensions) {
|
|
|
103
143
|
}
|
|
104
144
|
return {
|
|
105
145
|
extensions: extensions.paths.map((extensionPath) => ({ path: extensionPath })),
|
|
106
|
-
appendSystemPrompts: [extensions.systemPrompt]
|
|
146
|
+
appendSystemPrompts: [extensions.systemPrompt],
|
|
147
|
+
...(Object.keys(extensions.env).length === 0 ? {} : { env: extensions.env })
|
|
107
148
|
};
|
|
108
149
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
export function tokenStatusExtensionSource() {
|
|
1
|
+
export function tokenStatusExtensionSource(options = {}) {
|
|
2
|
+
const includeContext = options.includeContext ?? true;
|
|
2
3
|
return `import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
|
|
5
|
+
const includeContext = ${JSON.stringify(includeContext)};
|
|
6
|
+
|
|
4
7
|
type Usage = {
|
|
5
8
|
input?: number;
|
|
6
9
|
output?: number;
|
|
@@ -11,6 +14,7 @@ type Usage = {
|
|
|
11
14
|
|
|
12
15
|
type TurnState = {
|
|
13
16
|
startedAt: number;
|
|
17
|
+
firstOutputAt?: number;
|
|
14
18
|
outputText: string;
|
|
15
19
|
estimatedOutputTokens: number;
|
|
16
20
|
lastStatusAt: number;
|
|
@@ -33,6 +37,7 @@ export default function localpiTokenStatus(pi: ExtensionAPI): void {
|
|
|
33
37
|
if (!ctx.hasUI || state === undefined) {
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
40
|
+
const now = Date.now();
|
|
36
41
|
const update = textUpdateFromUnknown(event.assistantMessageEvent ?? event.message ?? event);
|
|
37
42
|
if (update.kind === "delta") {
|
|
38
43
|
state.outputText += update.text;
|
|
@@ -40,11 +45,14 @@ export default function localpiTokenStatus(pi: ExtensionAPI): void {
|
|
|
40
45
|
state.outputText = update.text;
|
|
41
46
|
}
|
|
42
47
|
state.estimatedOutputTokens = Math.ceil(state.outputText.length / 4);
|
|
43
|
-
if (
|
|
48
|
+
if (state.firstOutputAt === undefined && state.outputText.length > 0) {
|
|
49
|
+
state.firstOutputAt = now;
|
|
50
|
+
}
|
|
51
|
+
if (now - state.lastStatusAt < 250) {
|
|
44
52
|
return;
|
|
45
53
|
}
|
|
46
|
-
state.lastStatusAt =
|
|
47
|
-
ctx.ui.setStatus("localpi-perf", ctx.ui.theme.fg("dim", statusText(state)));
|
|
54
|
+
state.lastStatusAt = now;
|
|
55
|
+
ctx.ui.setStatus("localpi-perf", ctx.ui.theme.fg("dim", statusText(state, now)));
|
|
48
56
|
});
|
|
49
57
|
|
|
50
58
|
pi.on("turn_end", (event, ctx) => {
|
|
@@ -65,10 +73,14 @@ export default function localpiTokenStatus(pi: ExtensionAPI): void {
|
|
|
65
73
|
const input = usage?.input ?? 0;
|
|
66
74
|
const cacheRead = usage?.cacheRead ?? 0;
|
|
67
75
|
const cacheWrite = usage?.cacheWrite ?? 0;
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const elapsedSeconds = elapsed(state, now);
|
|
78
|
+
const decodeSeconds = generationElapsed(state, now);
|
|
79
|
+
const prefillText = prefillStatusText(state, input, cacheWrite);
|
|
80
|
+
const context = includeContext ? ctx.getContextUsage() : undefined;
|
|
81
|
+
const contextText = !includeContext
|
|
82
|
+
? undefined
|
|
83
|
+
: context && context.percent !== null
|
|
72
84
|
? \`ctx \${Math.round(context.percent)}%/\${Math.round(context.contextWindow / 1000)}k\`
|
|
73
85
|
: "ctx ?";
|
|
74
86
|
|
|
@@ -77,7 +89,8 @@ export default function localpiTokenStatus(pi: ExtensionAPI): void {
|
|
|
77
89
|
ctx.ui.theme.fg(
|
|
78
90
|
"dim",
|
|
79
91
|
[
|
|
80
|
-
|
|
92
|
+
\`gen \${(output / decodeSeconds).toFixed(1)} tok/s\`,
|
|
93
|
+
prefillText,
|
|
81
94
|
\`out \${output}\`,
|
|
82
95
|
\`in \${input}\`,
|
|
83
96
|
cacheRead > 0 ? \`cache \${cacheRead}\` : undefined,
|
|
@@ -98,13 +111,48 @@ export default function localpiTokenStatus(pi: ExtensionAPI): void {
|
|
|
98
111
|
});
|
|
99
112
|
}
|
|
100
113
|
|
|
101
|
-
function statusText(state: TurnState): string {
|
|
102
|
-
const elapsedSeconds = elapsed(state);
|
|
103
|
-
|
|
114
|
+
function statusText(state: TurnState, now: number): string {
|
|
115
|
+
const elapsedSeconds = elapsed(state, now);
|
|
116
|
+
if (state.firstOutputAt === undefined) {
|
|
117
|
+
return \`prefill \${elapsedSeconds.toFixed(1)}s | out ~\${state.estimatedOutputTokens}\`;
|
|
118
|
+
}
|
|
119
|
+
const decodeSeconds = generationElapsed(state, now);
|
|
120
|
+
const prefillSeconds = secondsBetween(state.startedAt, state.firstOutputAt);
|
|
121
|
+
return [
|
|
122
|
+
\`gen \${(state.estimatedOutputTokens / decodeSeconds).toFixed(1)} tok/s\`,
|
|
123
|
+
\`out ~\${state.estimatedOutputTokens}\`,
|
|
124
|
+
\`prefill \${prefillSeconds.toFixed(1)}s\`,
|
|
125
|
+
\`total \${elapsedSeconds.toFixed(1)}s\`
|
|
126
|
+
].join(" | ");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function prefillStatusText(
|
|
130
|
+
state: TurnState,
|
|
131
|
+
input: number,
|
|
132
|
+
cacheWrite: number
|
|
133
|
+
): string | undefined {
|
|
134
|
+
const tokens = prefillTokenCount(input, cacheWrite);
|
|
135
|
+
if (state.firstOutputAt === undefined || tokens <= 0) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const seconds = secondsBetween(state.startedAt, state.firstOutputAt);
|
|
139
|
+
return \`prefill \${(tokens / seconds).toFixed(1)} tok/s\`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function prefillTokenCount(input: number, cacheWrite: number): number {
|
|
143
|
+
return Math.max(input + cacheWrite, 0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function generationElapsed(state: TurnState, now: number): number {
|
|
147
|
+
return secondsBetween(state.firstOutputAt ?? state.startedAt, now);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function elapsed(state: TurnState, now: number): number {
|
|
151
|
+
return secondsBetween(state.startedAt, now);
|
|
104
152
|
}
|
|
105
153
|
|
|
106
|
-
function
|
|
107
|
-
return Math.max((
|
|
154
|
+
function secondsBetween(start: number, end: number): number {
|
|
155
|
+
return Math.max((end - start) / 1000, 0.001);
|
|
108
156
|
}
|
|
109
157
|
|
|
110
158
|
type TextUpdate = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { localpiSettingsPath } from "../localpi/settings-state.js";
|
|
4
5
|
import { resolveDemoPrompts } from "./demo.js";
|
|
5
|
-
import { demoModeExtensionSource } from "./extension-sources/demo-mode.js";
|
|
6
6
|
import { startupModelSelectorExtensionSource } from "./extension-sources/startup-model-selector.js";
|
|
7
7
|
import { thinkingControlExtensionSource } from "./extension-sources/thinking-control.js";
|
|
8
8
|
import { tokenStatusExtensionSource } from "./extension-sources/token-status.js";
|
|
@@ -11,24 +11,40 @@ export async function writeDefaultExtensions(options, extensionOptions = {}) {
|
|
|
11
11
|
const extensionDir = path.join(options.stateDir, "pi-extensions");
|
|
12
12
|
await mkdir(extensionDir, { recursive: true });
|
|
13
13
|
const paths = [];
|
|
14
|
+
let env = {};
|
|
14
15
|
if (extensionOptions.startupModelSelector !== undefined) {
|
|
15
16
|
paths.push(await writeExtension(extensionDir, "startup-model-selector.ts", startupModelSelectorExtensionSource(extensionOptions.startupModelSelector)));
|
|
16
17
|
}
|
|
17
18
|
if (options.demo) {
|
|
18
|
-
paths.push(
|
|
19
|
+
paths.push(demoModeExtensionPath());
|
|
20
|
+
const prompts = await resolveDemoPrompts(options);
|
|
21
|
+
env = {
|
|
22
|
+
...env,
|
|
23
|
+
PI_DEMO_MODE: "1",
|
|
24
|
+
PI_DEMO_INITIAL_PROMPT: prompts.initial,
|
|
25
|
+
PI_DEMO_FOLLOWUP_PROMPT: prompts.followup
|
|
26
|
+
};
|
|
19
27
|
}
|
|
20
28
|
paths.push(await writeExtension(extensionDir, "thinking-control.ts", thinkingControlExtensionSource(localpiSettingsPath(options))));
|
|
21
29
|
if (options.approval) {
|
|
22
30
|
paths.push(await writeExtension(extensionDir, "tool-approval.ts", approvalExtensionSource()));
|
|
23
31
|
}
|
|
24
32
|
if (options.tokenStatus) {
|
|
25
|
-
paths.push(await writeExtension(extensionDir, "token-status.ts", tokenStatusExtensionSource()));
|
|
33
|
+
paths.push(await writeExtension(extensionDir, "token-status.ts", tokenStatusExtensionSource({ includeContext: !options.demo })));
|
|
26
34
|
}
|
|
27
35
|
return {
|
|
28
36
|
paths,
|
|
37
|
+
env,
|
|
29
38
|
systemPrompt: localpiSystemPrompt(options.approval)
|
|
30
39
|
};
|
|
31
40
|
}
|
|
41
|
+
// Demo mode is the shared pi-demo-mode package (a git dependency), loaded
|
|
42
|
+
// straight from node_modules and configured through PI_DEMO_* env vars.
|
|
43
|
+
export function demoModeExtensionPath() {
|
|
44
|
+
const require = createRequire(import.meta.url);
|
|
45
|
+
const packageJson = require.resolve("pi-demo-mode/package.json");
|
|
46
|
+
return path.join(path.dirname(packageJson), "extensions", "demo-mode.ts");
|
|
47
|
+
}
|
|
32
48
|
async function writeExtension(extensionDir, name, source) {
|
|
33
49
|
const extensionPath = path.join(extensionDir, name);
|
|
34
50
|
await writeFile(extensionPath, source, "utf8");
|
|
@@ -150,7 +150,7 @@ session dir: ~/.local/state/localpi/sessions
|
|
|
150
150
|
Localpi installs two default extensions:
|
|
151
151
|
|
|
152
152
|
- tool approval gate: ask before each tool call, and tell the model clearly when a tool call was blocked
|
|
153
|
-
- token status: show live
|
|
153
|
+
- token status: show live generation speed while streaming, then final prefill and generation rates when usage data is available
|
|
154
154
|
|
|
155
155
|
## System Prompt
|
|
156
156
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "localpi",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "Swiss army knife for running Pi with local inference engines.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/osolmaz/localpi.git"
|
|
10
10
|
},
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/
|
|
12
|
+
"url": "https://github.com/osolmaz/localpi/issues"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.com/
|
|
14
|
+
"homepage": "https://github.com/osolmaz/localpi#readme",
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"LICENSE"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/src/cli/main.js",
|
|
30
|
+
"prepare": "npm run build",
|
|
30
31
|
"format": "prettier --check .",
|
|
31
32
|
"lint": "eslint .",
|
|
32
33
|
"typecheck": "tsc --noEmit",
|
|
@@ -48,13 +49,14 @@
|
|
|
48
49
|
"@vitest/coverage-v8": "^3.0.0",
|
|
49
50
|
"eslint": "^9.0.0",
|
|
50
51
|
"prettier": "^3.0.0",
|
|
51
|
-
"slophammer-ts": "0.4.
|
|
52
|
+
"slophammer-ts": "0.4.1",
|
|
52
53
|
"tsx": "^4.19.4",
|
|
53
54
|
"typescript": "^5.0.0",
|
|
54
55
|
"typescript-eslint": "^8.0.0",
|
|
55
56
|
"vitest": "^3.0.0"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@
|
|
59
|
+
"@osolmaz/pi-factory": "^0.1.2",
|
|
60
|
+
"pi-demo-mode": "github:osolmaz/pi-demo-mode#v0.1.0"
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export function demoModeExtensionSource(prompts) {
|
|
2
|
-
const initialPromptSource = JSON.stringify(prompts.initial);
|
|
3
|
-
const followupPromptSource = JSON.stringify(prompts.followup);
|
|
4
|
-
return `import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
|
|
6
|
-
const initialPrompt = ${initialPromptSource};
|
|
7
|
-
const followupPrompt = ${followupPromptSource};
|
|
8
|
-
|
|
9
|
-
export default function localpiDemoMode(pi: ExtensionAPI): void {
|
|
10
|
-
let started = false;
|
|
11
|
-
let stopped = false;
|
|
12
|
-
|
|
13
|
-
pi.on("session_start", (event, ctx) => {
|
|
14
|
-
if (started || stopped || event.reason !== "startup" || ctx.mode !== "tui") {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
started = true;
|
|
18
|
-
queueMicrotask(() => {
|
|
19
|
-
if (!stopped) {
|
|
20
|
-
pi.sendUserMessage(initialPrompt);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
pi.on("turn_end", (event, ctx) => {
|
|
26
|
-
if (!started || stopped || ctx.mode !== "tui") {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (event.message.role !== "assistant") {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
switch (event.message.stopReason) {
|
|
33
|
-
case "aborted":
|
|
34
|
-
case "error":
|
|
35
|
-
stopped = true;
|
|
36
|
-
return;
|
|
37
|
-
case "toolUse":
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
queueMicrotask(() => {
|
|
41
|
-
if (!stopped) {
|
|
42
|
-
pi.sendUserMessage(followupPrompt, { deliverAs: "followUp" });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
pi.on("session_shutdown", () => {
|
|
48
|
-
stopped = true;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
}
|