@pi-claudian/auto-save-to-markdown 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 +9 -1
- package/README.zh.md +7 -1
- package/index.ts +48 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,10 +67,12 @@ cost: 0.023401
|
|
|
67
67
|
tokens: 18745
|
|
68
68
|
tokens_input: 15230
|
|
69
69
|
tokens_output: 3515
|
|
70
|
+
tokens_cache_read: 0
|
|
71
|
+
tokens_cache_write: 0
|
|
70
72
|
messages: 8
|
|
71
73
|
created: "2026-08-29T05:05:12.000Z"
|
|
72
74
|
updated: "2026-08-29T05:42:10.000Z"
|
|
73
|
-
|
|
75
|
+
project_root: "/Users/me/project"
|
|
74
76
|
session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.jsonl"
|
|
75
77
|
---
|
|
76
78
|
|
|
@@ -103,6 +105,12 @@ kept in a collapsible `<details>` block) and summarizes each tool call and
|
|
|
103
105
|
result in one line, so the file stays readable while still showing what the
|
|
104
106
|
agent did.
|
|
105
107
|
|
|
108
|
+
`cost` and the token fields cover the whole saved branch and include cached
|
|
109
|
+
tokens (priced at the provider's cache rates), so the totals are comparable
|
|
110
|
+
with provider-side accounting (e.g. OpenRouter activity). Requests that never
|
|
111
|
+
landed in the session tree (failed retries, other sessions sharing the same
|
|
112
|
+
API key) are necessarily excluded.
|
|
113
|
+
|
|
106
114
|
## Branch behavior
|
|
107
115
|
|
|
108
116
|
Pi sessions are trees: `/tree` navigates to an earlier point and a new prompt
|
package/README.zh.md
CHANGED
|
@@ -57,10 +57,12 @@ cost: 0.023401
|
|
|
57
57
|
tokens: 18745
|
|
58
58
|
tokens_input: 15230
|
|
59
59
|
tokens_output: 3515
|
|
60
|
+
tokens_cache_read: 0
|
|
61
|
+
tokens_cache_write: 0
|
|
60
62
|
messages: 8
|
|
61
63
|
created: "2026-08-29T05:05:12.000Z"
|
|
62
64
|
updated: "2026-08-29T05:42:10.000Z"
|
|
63
|
-
|
|
65
|
+
project_root: "/Users/me/project"
|
|
64
66
|
session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.jsonl"
|
|
65
67
|
---
|
|
66
68
|
|
|
@@ -92,6 +94,10 @@ auth 重构之后登录页一直重定向死循环……
|
|
|
92
94
|
`<details>` 块中),每个工具调用和结果各压缩成一行摘要,既可读又能看出
|
|
93
95
|
agent 做了什么。
|
|
94
96
|
|
|
97
|
+
`cost` 和 token 字段统计整条已保存分支,且包含缓存 token(按供应商缓存
|
|
98
|
+
价格计费),因此总计可与供应商侧账单(如 OpenRouter Activity)对照。未
|
|
99
|
+
进入会话树的请求(失败重试、共用同一 API key 的其他会话)不在其中。
|
|
100
|
+
|
|
95
101
|
## 分支行为
|
|
96
102
|
|
|
97
103
|
Pi 会话是树:`/tree` 导航到更早的位置后再提问就分出新的分支。每个
|
package/index.ts
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* of the deepest message entry at file creation, and <time> is the local
|
|
19
19
|
* file-creation timestamp (YYYYMMDD-HHmmss).
|
|
20
20
|
* - Frontmatter: title, session id, tree (branch key), model, provider,
|
|
21
|
-
* cumulative cost and tokens,
|
|
22
|
-
*
|
|
21
|
+
* cumulative cost and tokens (input, output, cache read/write), message
|
|
22
|
+
* count, created/updated timestamps, project root and session file.
|
|
23
23
|
* - Branching: each file records exactly ONE branch (the root→leaf path
|
|
24
24
|
* returned by sessionManager.getBranch()). State is persisted via
|
|
25
25
|
* `pi.appendEntry()` custom entries, which are part of the session tree
|
|
@@ -114,10 +114,12 @@ interface BranchMeta {
|
|
|
114
114
|
cost: number;
|
|
115
115
|
tokensInput: number;
|
|
116
116
|
tokensOutput: number;
|
|
117
|
+
tokensCacheRead: number;
|
|
118
|
+
tokensCacheWrite: number;
|
|
117
119
|
messages: number;
|
|
118
120
|
created: string;
|
|
119
121
|
updated: string;
|
|
120
|
-
|
|
122
|
+
projectRoot: string;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
export default function (pi: ExtensionAPI) {
|
|
@@ -221,23 +223,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
221
223
|
// ---------- markdown rendering ----------
|
|
222
224
|
|
|
223
225
|
function renderAssistant(m: AssistantMessage, t: string): string {
|
|
224
|
-
|
|
226
|
+
// Render blocks in their original chronological order: thinking always
|
|
227
|
+
// precedes the text it produced, instead of being grouped after the fact.
|
|
228
|
+
const header = `## Assistant · ${t}${m.model ? ` · ${m.model}` : ""}`;
|
|
229
|
+
const parts: string[] = [];
|
|
225
230
|
const thinkings: string[] = [];
|
|
231
|
+
const flushThinking = () => {
|
|
232
|
+
if (thinkings.length) {
|
|
233
|
+
parts.push(
|
|
234
|
+
`<details>\n<summary>Thinking</summary>\n\n${thinkings.join("\n\n")}\n\n</details>`,
|
|
235
|
+
);
|
|
236
|
+
thinkings.length = 0;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
226
239
|
const calls: string[] = [];
|
|
227
240
|
for (const b of m.content) {
|
|
228
|
-
if (b.type === "text")
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
parts.push(
|
|
238
|
-
`<details>\n<summary>Thinking</summary>\n\n${thinkings.join("\n\n")}\n\n</details>`,
|
|
239
|
-
);
|
|
241
|
+
if (b.type === "text") {
|
|
242
|
+
flushThinking();
|
|
243
|
+
parts.push(b.text);
|
|
244
|
+
} else if (b.type === "thinking") {
|
|
245
|
+
thinkings.push(b.thinking);
|
|
246
|
+
} else if (b.type === "toolCall") {
|
|
247
|
+
flushThinking();
|
|
248
|
+
calls.push(`- \`${b.name}\` — ${previewArgs(b.arguments)}`);
|
|
249
|
+
}
|
|
240
250
|
}
|
|
251
|
+
flushThinking();
|
|
241
252
|
if (calls.length) parts.push(`**Tool calls**\n\n${calls.join("\n")}`);
|
|
242
253
|
if (m.errorMessage) parts.push(`> Error: ${m.errorMessage.replace(/\s+/g, " ").trim()}`);
|
|
243
254
|
if (parts.length === 0) parts.push("_(empty response)_");
|
|
@@ -292,13 +303,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
292
303
|
if (meta.model) lines.push(`model: ${yamlQuote(meta.model)}`);
|
|
293
304
|
if (meta.provider) lines.push(`provider: ${yamlQuote(meta.provider)}`);
|
|
294
305
|
lines.push(`cost: ${meta.cost.toFixed(6)}`);
|
|
295
|
-
|
|
306
|
+
// `tokens` counts everything billed, including cached tokens, so it is
|
|
307
|
+
// comparable with provider-side token totals (e.g. OpenRouter activity).
|
|
308
|
+
lines.push(
|
|
309
|
+
`tokens: ${meta.tokensInput + meta.tokensOutput + meta.tokensCacheRead + meta.tokensCacheWrite}`,
|
|
310
|
+
);
|
|
296
311
|
lines.push(`tokens_input: ${meta.tokensInput}`);
|
|
297
312
|
lines.push(`tokens_output: ${meta.tokensOutput}`);
|
|
313
|
+
lines.push(`tokens_cache_read: ${meta.tokensCacheRead}`);
|
|
314
|
+
lines.push(`tokens_cache_write: ${meta.tokensCacheWrite}`);
|
|
298
315
|
lines.push(`messages: ${meta.messages}`);
|
|
299
316
|
lines.push(`created: ${yamlQuote(meta.created)}`);
|
|
300
317
|
lines.push(`updated: ${yamlQuote(meta.updated)}`);
|
|
301
|
-
lines.push(`
|
|
318
|
+
lines.push(`project_root: ${yamlQuote(meta.projectRoot)}`);
|
|
302
319
|
if (meta.sessionFile) lines.push(`session_file: ${yamlQuote(meta.sessionFile)}`);
|
|
303
320
|
lines.push("---");
|
|
304
321
|
return lines.join("\n");
|
|
@@ -323,18 +340,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
323
340
|
let cost = 0;
|
|
324
341
|
let tokensInput = 0;
|
|
325
342
|
let tokensOutput = 0;
|
|
343
|
+
let tokensCacheRead = 0;
|
|
344
|
+
let tokensCacheWrite = 0;
|
|
326
345
|
for (const e of pathMessages) {
|
|
327
346
|
const m = e.message;
|
|
347
|
+
const usage = m.role === "assistant" ? m.usage : m.role === "toolResult" ? m.usage : null;
|
|
328
348
|
if (m.role === "assistant") {
|
|
329
349
|
model = m.model;
|
|
330
350
|
provider = m.provider;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
351
|
+
}
|
|
352
|
+
if (usage) {
|
|
353
|
+
cost += usage.cost?.total ?? 0;
|
|
354
|
+
tokensInput += usage.input ?? 0;
|
|
355
|
+
tokensOutput += usage.output ?? 0;
|
|
356
|
+
tokensCacheRead += usage.cacheRead ?? 0;
|
|
357
|
+
tokensCacheWrite += usage.cacheWrite ?? 0;
|
|
338
358
|
}
|
|
339
359
|
}
|
|
340
360
|
const now = new Date().toISOString();
|
|
@@ -348,10 +368,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
348
368
|
cost,
|
|
349
369
|
tokensInput,
|
|
350
370
|
tokensOutput,
|
|
371
|
+
tokensCacheRead,
|
|
372
|
+
tokensCacheWrite,
|
|
351
373
|
messages: pathMessages.length,
|
|
352
374
|
created: created ?? now,
|
|
353
375
|
updated: now,
|
|
354
|
-
|
|
376
|
+
projectRoot: ctx.cwd,
|
|
355
377
|
};
|
|
356
378
|
}
|
|
357
379
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-claudian/auto-save-to-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Pi extension that automatically saves each completed conversation turn as a markdown file with YAML frontmatter, one file per session-tree branch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|