@trim21/personal-pi-extensions 0.1.661 → 0.1.664
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 +5 -3
- package/package.json +1 -1
- package/src/codemode/declarations.ts +1 -2
- package/src/codemode/prelude.ts +44 -39
- package/src/codemode/tool.ts +21 -11
- package/src/codemode/worker.js +41 -36
package/README.md
CHANGED
|
@@ -43,10 +43,12 @@
|
|
|
43
43
|
- **只有脚本输出进上下文**:`text(value)` / `console.log(...)` 与 `return` 值进入工具结果,
|
|
44
44
|
中间的工具调用与它们的返回内容不会(也不在会话记录里留下工具调用条目)。
|
|
45
45
|
- **脚本接口**:`tools` / `ALL_TOOLS` / `text` / `image` / `exit` / `console.*` /
|
|
46
|
-
`store
|
|
46
|
+
`store.set` / `store.get` / `store.list`(会话内持久的键值表);首行可选
|
|
47
|
+
`// @options: {"max_output_tokens": 10000}`。
|
|
47
48
|
脚本没有超时:死循环由调用方中止(Esc)结束,等嵌套调用返回(含用户审批弹窗)多久都不算超时。
|
|
48
|
-
- **store**
|
|
49
|
-
|
|
49
|
+
- **store** 记在每次成功调用工具结果的 `details.store` 上(与 `src/lib/file-reads.ts` 的
|
|
50
|
+
已读记账同一套做法),下一次调用从当前分支的 toolResult 重放;输出超过 `max_output_tokens`
|
|
51
|
+
时头尾截断并把全文落到 `$TMPDIR/pi-codemode-*.txt`。
|
|
50
52
|
- **构建**:worker 入口是 esbuild 产物 `src/codemode/worker.js`(随仓库提交,
|
|
51
53
|
`pnpm run build:codemode-worker` 重新生成,pre-commit 会自动跑);`quickjs-wasi` 的
|
|
52
54
|
wasm 在注册工具时编译一次,之后每次执行复用。
|
package/package.json
CHANGED
|
@@ -97,7 +97,6 @@ export function renderDeclarations(tools: readonly ToolLike[]): string {
|
|
|
97
97
|
"declare function text(value: unknown): void;",
|
|
98
98
|
"declare function image(value: unknown): void;",
|
|
99
99
|
"declare function exit(): void;",
|
|
100
|
-
"declare
|
|
101
|
-
"declare function load(key: string): unknown;",
|
|
100
|
+
"declare const store: { set(key: string, value: unknown): void; get(key: string): unknown; list(): string[] };",
|
|
102
101
|
].join("\n");
|
|
103
102
|
}
|
package/src/codemode/prelude.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* codemode 脚本侧的 prelude:在 QuickJS VM 里先于脚本求值,构建脚本能看到的全部
|
|
3
|
-
* 能力(`tools` / `ALL_TOOLS` / `text` / `image` / `exit` / `console` / `store
|
|
4
|
-
*
|
|
3
|
+
* 能力(`tools` / `ALL_TOOLS` / `text` / `image` / `exit` / `console` / `store`),
|
|
4
|
+
* 并把宿主桥接封在闭包里——脚本拿不到 `bridge` 本身。
|
|
5
5
|
*
|
|
6
6
|
* 值与参数过桥时都是 JSON 文本,本侧负责 parse/stringify;异步调用用一个 pending
|
|
7
7
|
* 表把 id 映射到 promise,由宿主在结果到达时 settle。
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - `"done"`(ok, valueJsonOrErrorJson, writesJson)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
/** `store()` 单个值的 JSON 上限(字符数)。 */
|
|
16
|
+
/** `store.set()` 单个值的 JSON 上限(字符数)。 */
|
|
17
17
|
export const MAX_STORE_VALUE_CHARS = 256 * 1024;
|
|
18
18
|
/** 全部 store 值的 JSON 上限(字符数)。 */
|
|
19
19
|
export const MAX_STORE_TOTAL_CHARS = 1024 * 1024;
|
|
@@ -118,41 +118,47 @@ export const PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson
|
|
|
118
118
|
if (typeof key !== "string") throw new TypeErrorCtor(name + "() key must be a string");
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
stored.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
json
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
121
|
+
// 脚本侧是一个键值表:store.set / store.get / store.list,数据本身留在闭包里
|
|
122
|
+
const store = Object.freeze({
|
|
123
|
+
set(key, value) {
|
|
124
|
+
checkKey("store.set", key);
|
|
125
|
+
const previous = stored.has(key) ? key.length + stored.get(key).length : 0;
|
|
126
|
+
if (value === undefined) {
|
|
127
|
+
stored.delete(key);
|
|
128
|
+
storedChars -= previous;
|
|
129
|
+
writes.set(key, undefined);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let json;
|
|
133
|
+
try {
|
|
134
|
+
json = stringify(value);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new TypeErrorCtor("store.set(" + stringify(key) + ") value is not JSON-serializable: " + format(error));
|
|
137
|
+
}
|
|
138
|
+
if (json === undefined) {
|
|
139
|
+
throw new TypeErrorCtor("store.set(" + stringify(key) + ") value is not JSON-serializable");
|
|
140
|
+
}
|
|
141
|
+
if (json.length > ${MAX_STORE_VALUE_CHARS}) {
|
|
142
|
+
throw new RangeError("store.set(" + stringify(key) + ") value exceeds ${MAX_STORE_VALUE_CHARS} characters of JSON");
|
|
143
|
+
}
|
|
144
|
+
const next = storedChars - previous + key.length + json.length;
|
|
145
|
+
if (next > ${MAX_STORE_TOTAL_CHARS}) {
|
|
146
|
+
throw new RangeError("store is full: stored values would exceed ${MAX_STORE_TOTAL_CHARS} characters of JSON");
|
|
147
|
+
}
|
|
148
|
+
stored.set(key, json);
|
|
149
|
+
storedChars = next;
|
|
150
|
+
writes.set(key, json);
|
|
151
|
+
},
|
|
152
|
+
get(key) {
|
|
153
|
+
checkKey("store.get", key);
|
|
154
|
+
const json = stored.get(key);
|
|
155
|
+
return json === undefined ? undefined : parse(json);
|
|
156
|
+
},
|
|
157
|
+
// 升序返回当前键:上下文压缩后模型可以靠它找回自己写过的名字
|
|
158
|
+
list() {
|
|
159
|
+
return Array.from(stored.keys()).sort();
|
|
160
|
+
},
|
|
161
|
+
});
|
|
156
162
|
|
|
157
163
|
function serializeWrites() {
|
|
158
164
|
const entries = [];
|
|
@@ -161,7 +167,6 @@ export const PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson
|
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
Object.defineProperty(globalThis, "store", { value: store, enumerable: true });
|
|
164
|
-
Object.defineProperty(globalThis, "load", { value: load, enumerable: true });
|
|
165
170
|
|
|
166
171
|
// 原始值转字符串,其余 JSON 化
|
|
167
172
|
function outputText(value) {
|
package/src/codemode/tool.ts
CHANGED
|
@@ -26,9 +26,6 @@ import { CODEMODE_SOURCE_GRAMMAR, DEFAULT_OUTPUT_TOKENS, parseCodemodeSource } f
|
|
|
26
26
|
|
|
27
27
|
export const CODEMODE_TOOL_NAME = "codemode";
|
|
28
28
|
|
|
29
|
-
/** store 落在 session 的自定义 entry 上:随分支保存与恢复。 */
|
|
30
|
-
const STORE_ENTRY_TYPE = "codemode-store";
|
|
31
|
-
|
|
32
29
|
/** 估计 token 用的字符数(与 pi 一致)。 */
|
|
33
30
|
const CHARS_PER_TOKEN = 4;
|
|
34
31
|
|
|
@@ -76,7 +73,8 @@ function buildDescription(tools: readonly CallableTool[]): string {
|
|
|
76
73
|
" and a failing tool rejects with an `Error` you can catch.",
|
|
77
74
|
"- Only what the script passes to `text(value)` / `console.log(...)` and its `return` value enter",
|
|
78
75
|
" this conversation; nested calls and their results stay out of it.",
|
|
79
|
-
"- `store(key, value)` and `
|
|
76
|
+
"- `store.set(key, value)`, `store.get(key)` and `store.list()` are a small key/value store that",
|
|
77
|
+
" persists across codemode calls in this session; `store.set(key, undefined)` removes a key.",
|
|
80
78
|
"- The sandbox has no file system, network, timers or Node APIs: calling tools is its only",
|
|
81
79
|
" capability. Tool calls still go through each tool's own approvals, so a call that needs the",
|
|
82
80
|
" user's consent will ask for it.",
|
|
@@ -89,17 +87,29 @@ function buildDescription(tools: readonly CallableTool[]): string {
|
|
|
89
87
|
].join("\n");
|
|
90
88
|
}
|
|
91
89
|
|
|
90
|
+
/**
|
|
91
|
+
* store 的恢复:把当前分支上每次 codemode 调用记在工具结果 `details.store` 里的写入
|
|
92
|
+
* 按顺序重放(与 `src/lib/file-reads.ts` 从 toolResult 的 details 重放已读同一套做法)。
|
|
93
|
+
* 只认分支上的结果,所以 rewind / fork / resume 后 store 跟着分支走。
|
|
94
|
+
*/
|
|
92
95
|
function readStore(ctx: ExtensionContext): Record<string, unknown> {
|
|
93
96
|
const store = new Map<string, unknown>();
|
|
94
97
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
95
|
-
if (entry.type !== "
|
|
98
|
+
if (entry.type !== "message" || entry.message.role !== "toolResult") {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (entry.message.toolName !== CODEMODE_TOOL_NAME) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const details = entry.message.details;
|
|
105
|
+
if (typeof details !== "object" || details === null) {
|
|
96
106
|
continue;
|
|
97
107
|
}
|
|
98
|
-
const
|
|
99
|
-
if (typeof
|
|
108
|
+
const record = (details as { store?: unknown }).store;
|
|
109
|
+
if (typeof record !== "object" || record === null || Array.isArray(record)) {
|
|
100
110
|
continue;
|
|
101
111
|
}
|
|
102
|
-
const writes =
|
|
112
|
+
const writes = record as Partial<StoreWrites>;
|
|
103
113
|
for (const key of writes.delete ?? []) {
|
|
104
114
|
store.delete(key);
|
|
105
115
|
}
|
|
@@ -294,9 +304,7 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
|
|
|
294
304
|
const images = imagesOf(outcome.output);
|
|
295
305
|
if (outcome.ok) {
|
|
296
306
|
const writes = outcome.writes;
|
|
297
|
-
|
|
298
|
-
pi.appendEntry(STORE_ENTRY_TYPE, writes);
|
|
299
|
-
}
|
|
307
|
+
const hasWrites = Object.keys(writes.set).length > 0 || writes.delete.length > 0;
|
|
300
308
|
const value =
|
|
301
309
|
outcome.value === undefined ? "" : `\n\n${JSON.stringify(outcome.value, null, 2)}`;
|
|
302
310
|
const truncated = await truncateOutput(
|
|
@@ -315,6 +323,8 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
|
|
|
315
323
|
],
|
|
316
324
|
details: {
|
|
317
325
|
calls: outcome.calls,
|
|
326
|
+
// store 的写入随工具结果持久化,下一次调用从这里重放恢复
|
|
327
|
+
...(hasWrites && { store: writes }),
|
|
318
328
|
pendant: scriptPendant(code, `${outcome.calls.length} tool call(s)`),
|
|
319
329
|
...(truncated.fullOutputPath && { fullOutputPath: truncated.fullOutputPath }),
|
|
320
330
|
},
|
package/src/codemode/worker.js
CHANGED
|
@@ -103,41 +103,47 @@ var PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson) {
|
|
|
103
103
|
if (typeof key !== "string") throw new TypeErrorCtor(name + "() key must be a string");
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
stored.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
json
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
106
|
+
// 脚本侧是一个键值表:store.set / store.get / store.list,数据本身留在闭包里
|
|
107
|
+
const store = Object.freeze({
|
|
108
|
+
set(key, value) {
|
|
109
|
+
checkKey("store.set", key);
|
|
110
|
+
const previous = stored.has(key) ? key.length + stored.get(key).length : 0;
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
stored.delete(key);
|
|
113
|
+
storedChars -= previous;
|
|
114
|
+
writes.set(key, undefined);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let json;
|
|
118
|
+
try {
|
|
119
|
+
json = stringify(value);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new TypeErrorCtor("store.set(" + stringify(key) + ") value is not JSON-serializable: " + format(error));
|
|
122
|
+
}
|
|
123
|
+
if (json === undefined) {
|
|
124
|
+
throw new TypeErrorCtor("store.set(" + stringify(key) + ") value is not JSON-serializable");
|
|
125
|
+
}
|
|
126
|
+
if (json.length > ${MAX_STORE_VALUE_CHARS}) {
|
|
127
|
+
throw new RangeError("store.set(" + stringify(key) + ") value exceeds ${MAX_STORE_VALUE_CHARS} characters of JSON");
|
|
128
|
+
}
|
|
129
|
+
const next = storedChars - previous + key.length + json.length;
|
|
130
|
+
if (next > ${MAX_STORE_TOTAL_CHARS}) {
|
|
131
|
+
throw new RangeError("store is full: stored values would exceed ${MAX_STORE_TOTAL_CHARS} characters of JSON");
|
|
132
|
+
}
|
|
133
|
+
stored.set(key, json);
|
|
134
|
+
storedChars = next;
|
|
135
|
+
writes.set(key, json);
|
|
136
|
+
},
|
|
137
|
+
get(key) {
|
|
138
|
+
checkKey("store.get", key);
|
|
139
|
+
const json = stored.get(key);
|
|
140
|
+
return json === undefined ? undefined : parse(json);
|
|
141
|
+
},
|
|
142
|
+
// 升序返回当前键:上下文压缩后模型可以靠它找回自己写过的名字
|
|
143
|
+
list() {
|
|
144
|
+
return Array.from(stored.keys()).sort();
|
|
145
|
+
},
|
|
146
|
+
});
|
|
141
147
|
|
|
142
148
|
function serializeWrites() {
|
|
143
149
|
const entries = [];
|
|
@@ -146,7 +152,6 @@ var PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson) {
|
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
Object.defineProperty(globalThis, "store", { value: store, enumerable: true });
|
|
149
|
-
Object.defineProperty(globalThis, "load", { value: load, enumerable: true });
|
|
150
155
|
|
|
151
156
|
// 原始值转字符串,其余 JSON 化
|
|
152
157
|
function outputText(value) {
|