pi-repl-py 0.1.0 → 0.1.1
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 +17 -5
- package/{ARCHITECTURE.md → docs/ARCHITECTURE.md} +6 -5
- package/docs/how-to-functions.md +6 -5
- package/docs/philosophy.md +1 -1
- package/index.ts +5 -10
- package/package.json +9 -2
- package/scripts/setup-venv.mjs +2 -2
- package/src/engine/guest.py +4 -1
- package/src/engine/index.ts +15 -22
- package/src/engine/toolbox/edit.py +1 -1
- package/src/engine/toolbox/read.py +1 -1
- package/src/engine/toolbox/write.py +1 -1
- package/src/extension/config.ts +3 -4
- package/src/extension/preview/candidates.ts +205 -0
- package/src/extension/preview/descriptor.ts +28 -0
- package/src/extension/preview/index.ts +39 -0
- package/src/extension/preview/scan.ts +59 -0
- package/src/extension/preview/shell.ts +156 -0
- package/src/extension/preview/types.ts +23 -0
- package/src/extension/preview-core.ts +2 -518
- package/src/extension/prompt.ts +54 -0
- package/src/extension/render-core.ts +7 -23
- package/src/extension/tool-meta.ts +7 -50
- package/src/extension/toolbox.ts +34 -10
|
@@ -1,58 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* pi's default system prompt is used as-is; all REPL knowledge rides on the
|
|
5
|
-
* tool via these fields, so index.ts stays thin.
|
|
6
|
-
*
|
|
7
|
-
* - description — working summary (schema card).
|
|
8
|
-
* - promptSnippet — one line in the default `Available tools`.
|
|
9
|
-
* - promptGuidelines — the function doctrine + tokens + safety.
|
|
10
|
-
*
|
|
11
|
-
* The function map is derived from the toolbox source via buildToolboxMap()
|
|
12
|
-
* (function_description docstring + def-signature regex), so it always matches
|
|
13
|
-
* what the kernel loads.
|
|
14
|
-
*/
|
|
1
|
+
// --- tool-meta: thin surface assembling the execute tool's prompt from pure modules ---
|
|
2
|
+
// --- the model contract lives in prompt.ts; only the toolbox wiring stays here ---
|
|
15
3
|
|
|
4
|
+
import { buildPromptGuidelines, executePromptSnippet, executeToolDescription } from "./prompt.js";
|
|
16
5
|
import { buildToolboxMap } from "./toolbox.js";
|
|
17
6
|
|
|
18
|
-
export const EXECUTE_DESCRIPTION =
|
|
19
|
-
|
|
20
|
-
"Variables, imports, functions, and data survive across calls. There are no " +
|
|
21
|
-
"separate file or shell tools; read, write, edit, bash, and anything you build " +
|
|
22
|
-
"are Python functions you call inside a cell. A cell returns its final " +
|
|
23
|
-
"expression; anything else is printed. Build one reusable function per routine " +
|
|
24
|
-
"and call it by arguments, since a new def overwrites the previous one; don't " +
|
|
25
|
-
"narrate that machinery to the user. Runs in a project-local venv, so a command " +
|
|
26
|
-
"that starts python or pip must target that venv.";
|
|
7
|
+
export const EXECUTE_DESCRIPTION = executeToolDescription;
|
|
8
|
+
export const EXECUTE_PROMPT_SNIPPET = executePromptSnippet;
|
|
27
9
|
|
|
28
|
-
|
|
29
|
-
"Execute Python in a persistent evaluator whose variables, imports, and functions " +
|
|
30
|
-
"survive across calls; preloaded functions plus any you define and reuse as " +
|
|
31
|
-
"callable tools; ls() lists them, help(name) shows usage";
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* promptGuidelines for the execute tool. `toolboxDir` is optional; it defaults to
|
|
35
|
-
* the shipped toolbox.
|
|
36
|
-
*/
|
|
10
|
+
// --- build the guidelines from the toolbox, falling back when nothing is preloaded ---
|
|
37
11
|
export function buildExecutePromptGuidelines(toolboxDir?: string): string[] {
|
|
38
12
|
const map = buildToolboxMap(toolboxDir);
|
|
39
13
|
const preloaded = map.length > 0 ? map : ["(none preloaded: define your own)"];
|
|
40
|
-
return
|
|
41
|
-
"Preloaded functions available in every kernel:",
|
|
42
|
-
...preloaded,
|
|
43
|
-
"ls() prints what is loaded; help(name) shows a function's signature and notes. Use them instead of guessing.",
|
|
44
|
-
"Functions you define are reusable tools: one parameterized helper per task, call it by arguments. Never write a routine twice and never fork a duplicate; extend the existing `def` (a new `def` of the same name overwrites).",
|
|
45
|
-
"Before a multi-line cell, ask whether you will run that shape again with different inputs. If yes, define the function now so each later request is one call.",
|
|
46
|
-
"Good, defined once then called by arguments only:",
|
|
47
|
-
"def fetch_news(query, hl='en', gl='US', ceid='US:en', limit=15):\n <fetch + parse to a list>\nfetch_news('Turkey')\nfetch_news('Nigeria', hl='en-NG')",
|
|
48
|
-
"Don't build a near-copy (avoid fetch_news and fetch_news_region); add the varying bits to the original `def` and let it supersede the old.",
|
|
49
|
-
"Other reusable shapes build the same way:",
|
|
50
|
-
"def find_files(pred, root='.'):\n <walk root, filter by pred>\nfind_files('*.csv')\nfind_files('*.py', root='src')\ndef count_lines(paths): ... # compose: count_lines(find_files('*.csv'))",
|
|
51
|
-
"Use functions proportionally: build one when it will be reused, otherwise run it in a plain cell. Don't wrap a one-off and don't over-engineer.",
|
|
52
|
-
"Never narrate your mechanism to the user (don't say 'I defined a function' or 'I built a tool'). Do the job, then answer with the result.",
|
|
53
|
-
"Be token efficient: everything a cell prints is context for the rest of the turn. When reading or searching, print slices, matches, or counts rather than whole files, and keep large values in variables.",
|
|
54
|
-
"For whole-filesystem or large-dir scans, use the kernel's tools via bash, not a Python walk: find, du, fd, grep. Chain them (find -xdev -type f -size +100M | sort -rn | head; du -x | sort -h | tail) and prune descent by skipping node_modules, .git, caches, venvs. A Python os.walk + lstat loop pays a slow syscall per file and runs minutes to 10+ min on a big tree; reserve Python for analysing the results, not for enumerating the disk.",
|
|
55
|
-
"If the output starts with <rlm_engine_reset>, the kernel was rebuilt: only data is restored, your functions are gone. Recreate any helper you need and re-verify a variable before trusting it.",
|
|
56
|
-
"Don't install packages into the evaluator; the standard library is available. Run out-of-tree projects through their own environment.",
|
|
57
|
-
];
|
|
14
|
+
return buildPromptGuidelines(preloaded);
|
|
58
15
|
}
|
package/src/extension/toolbox.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
|
|
14
15
|
interface ToolEntry {
|
|
@@ -41,34 +42,57 @@ function defaultToolboxDir(): string {
|
|
|
41
42
|
return join(import.meta.dirname, "..", "..", "src", "engine", "toolbox");
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
/**
|
|
45
|
+
/** Expand a leading `~` to the user's home, matching the guest's expanduser. */
|
|
46
|
+
function expandTilde(p: string): string {
|
|
47
|
+
const home = homedir();
|
|
48
|
+
if (p === "~" || p === "~/") return home;
|
|
49
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return join(home, p.slice(2));
|
|
50
|
+
return p;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Resolve a toolbox dir the way the config intends (expand ~, missing → shipped default). */
|
|
54
|
+
function toolboxDir(dir: string | undefined): string {
|
|
55
|
+
return dir && dir.trim().length > 0 ? expandTilde(dir.trim()) : defaultToolboxDir();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Load {function_name → entry} for each non-underscore *.py in `dir`. */
|
|
45
59
|
function loadToolboxEntries(dir: string | undefined): ToolEntry[] {
|
|
46
|
-
const d = dir
|
|
60
|
+
const d = toolboxDir(dir);
|
|
47
61
|
if (!existsSync(d)) return [];
|
|
48
62
|
const entries: ToolEntry[] = [];
|
|
49
63
|
for (const file of readdirSync(d).sort()) {
|
|
50
64
|
if (!file.endsWith(".py")) continue;
|
|
51
65
|
const name = file.slice(0, -3);
|
|
52
66
|
if (!/^[A-Za-z_]\w*$/.test(name)) continue;
|
|
53
|
-
//
|
|
54
|
-
// so it must never be advertised either.
|
|
67
|
+
// --- underscore-prefixed files are neither loaded nor advertised ---
|
|
55
68
|
if (name.startsWith("_")) continue;
|
|
56
69
|
try {
|
|
57
70
|
const source = readFileSync(join(d, file), "utf8");
|
|
58
71
|
const call = parseDefCall(source);
|
|
59
72
|
if (!call) continue;
|
|
60
73
|
entries.push({ name, call, description: parseDescription(source) });
|
|
61
|
-
} catch {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
74
|
+
} catch {}
|
|
64
75
|
}
|
|
65
76
|
return entries;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
/**
|
|
69
|
-
* The
|
|
70
|
-
|
|
80
|
+
* The shipped built-in function set, always present (the canonical toolbox).
|
|
81
|
+
*/
|
|
82
|
+
function builtInEntries(): ToolEntry[] {
|
|
83
|
+
return loadToolboxEntries(undefined);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The effective function map: the shipped built-ins are supreme (always
|
|
88
|
+
* present); the config `toolboxDir`, if set, adds any extra function and, when
|
|
89
|
+
* a name collides, overrides the built-in one. `~` is resolved like the guest.
|
|
71
90
|
*/
|
|
72
91
|
export function buildToolboxMap(dir: string | undefined): string[] {
|
|
73
|
-
|
|
92
|
+
const byName = new Map<string, ToolEntry>();
|
|
93
|
+
for (const e of builtInEntries()) byName.set(e.name, e);
|
|
94
|
+
if (dir && dir.trim().length > 0 && toolboxDir(dir) !== defaultToolboxDir()) {
|
|
95
|
+
for (const e of loadToolboxEntries(dir)) byName.set(e.name, e);
|
|
96
|
+
}
|
|
97
|
+
return [...byName.values()].map((t) => (t.description ? `- ${t.call}: ${t.description}` : `- ${t.call}`));
|
|
74
98
|
}
|