@rexvane/piweb 0.3.11 → 0.3.13
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +8 -6
- package/.next/build-manifest.json +6 -6
- package/.next/prerender-manifest.json +7 -7
- package/.next/react-loadable-manifest.json +7 -7
- package/.next/routes-manifest.json +12 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/git/route.js +2 -2
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js +1 -0
- package/.next/server/app/api/prompts/route.js.nft.json +1 -0
- package/.next/server/app/api/prompts/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +6 -0
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/import/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/web-auth/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -6
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -0
- package/.next/server/chunks/686.js +2 -2
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/2155.2f27fe7098987699.js +1 -0
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
- package/.next/static/chunks/2716.a9e808190a4e2424.js +1 -0
- package/.next/static/chunks/{794-d1a40dcf24d651d9.js → 3794-3679f1c05160977c.js} +1 -1
- package/.next/static/chunks/4632.d574c210ad78d065.js +1 -0
- package/.next/static/chunks/4673.d3d0a7f2599080fb.js +1 -0
- package/.next/static/chunks/4686-0a4155f8488314b8.js +1 -0
- package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/_not-found/page-2d40d1d7d916666a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/layout-50d06e2078d6ce76.js +1 -0
- package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +1 -0
- package/.next/static/chunks/app/page-9a5440047c5a415c.js +1 -0
- package/.next/static/chunks/framework-c4e651fe89edfa91.js +1 -0
- package/.next/static/chunks/{main-a315d7c58b599962.js → main-6159b22a8cb0b7c5.js} +2 -2
- package/.next/static/chunks/main-app-e9b53f0e1c6591b2.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-a7e260f8cc2582d7.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/webpack-b1a572e319353f5d.js +1 -0
- package/.next/static/olV7jxKRN5VhfEK012WhV/_buildManifest.js +1 -0
- package/.next/types/app/api/prompts/route.ts +353 -0
- package/.next/types/app/api/sessions/import/route.ts +353 -0
- package/.next/types/routes.d.ts +3 -1
- package/.next/types/validator.ts +18 -0
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/app/api/prompts/route.ts +51 -0
- package/src/app/api/sessions/import/route.ts +74 -0
- package/src/components/AppShell.tsx +42 -19
- package/src/components/ChatWindow.tsx +20 -35
- package/src/components/PromptPanel.tsx +259 -0
- package/src/components/SessionImportSection.tsx +333 -0
- package/src/components/SettingsPanel.tsx +63 -57
- package/src/i18n.tsx +98 -4
- package/src/lib/agent-manager.ts +35 -8
- package/src/lib/clipboard.ts +27 -0
- package/src/lib/command-validation.ts +1 -0
- package/src/lib/prompt-sources.ts +229 -0
- package/src/lib/session-import/claude.ts +219 -0
- package/src/lib/session-import/codex.ts +206 -0
- package/src/lib/session-import/dsh-zstd.ts +69 -0
- package/src/lib/session-import/dsh.ts +248 -0
- package/src/lib/session-import/files.ts +84 -0
- package/src/lib/session-import/grok.ts +166 -0
- package/src/lib/session-import/index.ts +229 -0
- package/src/lib/session-import/oc-family.ts +275 -0
- package/src/lib/session-import/opencode.ts +7 -0
- package/src/lib/session-import/paths.ts +37 -0
- package/src/lib/session-import/sqlite.ts +89 -0
- package/src/lib/session-import/types.ts +171 -0
- package/src/lib/session-import/writer.ts +259 -0
- package/src/lib/session-import/zcode.ts +8 -0
- package/src/lib/types.ts +1 -0
- package/.next/server/chunks/118.js +0 -1
- package/.next/static/222KLUIvefiGf7M0Ov-Qz/_buildManifest.js +0 -1
- package/.next/static/chunks/127.c31e688e156605e3.js +0 -1
- package/.next/static/chunks/155.a3c40ae481da1358.js +0 -1
- package/.next/static/chunks/4bd1b696-92152b0f5947070d.js +0 -1
- package/.next/static/chunks/608.5b8aae42a9998ee7.js +0 -1
- package/.next/static/chunks/673.93445c8600bc1dd9.js +0 -1
- package/.next/static/chunks/686-9f15e4e370d26815.js +0 -1
- package/.next/static/chunks/716.5266fe10b5578591.js +0 -1
- package/.next/static/chunks/app/_global-error/page-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/_not-found/page-9de30f6f614fce02.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/config/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/files/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/git/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/growth/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/health/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/models/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/security/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/skills/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/update/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/version/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/layout-eb62bcd99abe79f9.js +0 -1
- package/.next/static/chunks/app/login/page-d2308b48d013d1a6.js +0 -1
- package/.next/static/chunks/app/page-708cd3ee4cfe8af5.js +0 -1
- package/.next/static/chunks/framework-0f597d6e68ffafed.js +0 -1
- package/.next/static/chunks/main-app-1a92e20ec190a32b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-7bb5c4afcbc1772b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/webpack-9d84b382d4dec27a.js +0 -1
- /package/.next/static/{222KLUIvefiGf7M0Ov-Qz → olV7jxKRN5VhfEK012WhV}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 各来源在本机的固定位置(一处收口,方便核对与测试替身)。
|
|
3
|
+
*
|
|
4
|
+
* 全部基于用户主目录;这些目录**只读**——导入过程中不往源目录写任何东西。
|
|
5
|
+
*/
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/** Claude Code:~/.claude/projects/<项目 slug>/<会话 uuid>.jsonl */
|
|
10
|
+
export function claudeRoot(): string {
|
|
11
|
+
return path.join(os.homedir(), ".claude", "projects");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Codex CLI:~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl */
|
|
15
|
+
export function codexRoot(): string {
|
|
16
|
+
return path.join(os.homedir(), ".codex", "sessions");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Grok CLI:~/.grok/sessions/<url 编码 cwd>/<id>/{summary.json,chat_history.jsonl} */
|
|
20
|
+
export function grokRoot(): string {
|
|
21
|
+
return path.join(os.homedir(), ".grok", "sessions");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** dsh:~/.dsh/sessions/<编码 cwd>/session-<uuid>/session.jsonl.zstd */
|
|
25
|
+
export function dshRoot(): string {
|
|
26
|
+
return path.join(os.homedir(), ".dsh", "sessions");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** ZCode:~/.zcode/cli/db/db.sqlite */
|
|
30
|
+
export function zcodeDb(): string {
|
|
31
|
+
return path.join(os.homedir(), ".zcode", "cli", "db", "db.sqlite");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** opencode:~/.local/share/opencode/opencode.db(Windows 上也是这个 XDG 风格路径) */
|
|
35
|
+
export function opencodeDb(): string {
|
|
36
|
+
return path.join(os.homedir(), ".local", "share", "opencode", "opencode.db");
|
|
37
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 只读 SQLite 封装(node:sqlite 的 DatabaseSync,Node 24 起无需 flag)。
|
|
3
|
+
*
|
|
4
|
+
* 导入过程中**绝不能写源库**:ZCode / opencode 可能正在跑,写一下就是数据损坏。
|
|
5
|
+
* 这里三重保险——`readOnly: true`、`PRAGMA query_only = 1`、只暴露查询方法(没有 exec/run 出口)。
|
|
6
|
+
*
|
|
7
|
+
* `node:sqlite` 是实验特性,所以**按需**动态载入:载不进来(Node 太老)时只有 ZCode/opencode
|
|
8
|
+
* 这两个来源报错,文件类的四个来源照常可用——scanAllSources 会把单来源失败隔离掉。
|
|
9
|
+
*/
|
|
10
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
11
|
+
|
|
12
|
+
export interface ReadOnlyDb {
|
|
13
|
+
all(sql: string, params?: Array<string | number | null>): Record<string, unknown>[];
|
|
14
|
+
get(sql: string, params?: Array<string | number | null>): Record<string, unknown> | undefined;
|
|
15
|
+
hasTable(name: string): boolean;
|
|
16
|
+
hasColumn(table: string, column: string): boolean;
|
|
17
|
+
close(): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function openReadOnly(file: string): Promise<ReadOnlyDb | null> {
|
|
21
|
+
const DatabaseSyncClass = await loadDriver();
|
|
22
|
+
if (!DatabaseSyncClass) return null;
|
|
23
|
+
let db: DatabaseSync;
|
|
24
|
+
try {
|
|
25
|
+
db = new DatabaseSyncClass(file, { readOnly: true });
|
|
26
|
+
} catch {
|
|
27
|
+
return null; // 文件不存在 / 不是 SQLite / 被占用到无法只读打开
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
db.exec("PRAGMA query_only = 1");
|
|
31
|
+
} catch {
|
|
32
|
+
// 老版本不支持该 pragma:readOnly 仍然生效
|
|
33
|
+
}
|
|
34
|
+
const all = (sql: string, params: Array<string | number | null> = []) => {
|
|
35
|
+
try {
|
|
36
|
+
return db.prepare(sql).all(...params) as Record<string, unknown>[];
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
all,
|
|
43
|
+
get: (sql, params) => all(sql, params)[0],
|
|
44
|
+
hasTable: (name) => Boolean(all("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", [name]).length),
|
|
45
|
+
hasColumn: (table, column) => {
|
|
46
|
+
try {
|
|
47
|
+
return (db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).some((row) => row.name === column);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
close: () => {
|
|
53
|
+
try {
|
|
54
|
+
db.close();
|
|
55
|
+
} catch {
|
|
56
|
+
// 已经关了:无事可做
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type DatabaseSyncCtor = typeof DatabaseSync;
|
|
63
|
+
|
|
64
|
+
let cachedDriver: Promise<DatabaseSyncCtor | null> | null = null;
|
|
65
|
+
|
|
66
|
+
/** 动态载入 node:sqlite;失败只影响 ZCode/opencode 这两个来源 */
|
|
67
|
+
function loadDriver(): Promise<DatabaseSyncCtor | null> {
|
|
68
|
+
if (!cachedDriver) {
|
|
69
|
+
cachedDriver = import("node:sqlite").then(
|
|
70
|
+
(module) => module.DatabaseSync,
|
|
71
|
+
(error) => {
|
|
72
|
+
if (process.env.PIWEB_DEBUG_PROMPT) console.warn("[piweb] node:sqlite 不可用:", error instanceof Error ? error.message : error);
|
|
73
|
+
return null;
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return cachedDriver;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** data 列里的 JSON:坏行返回 null 而不是抛错 */
|
|
81
|
+
export function parseJsonColumn(value: unknown): Record<string, unknown> | null {
|
|
82
|
+
if (typeof value !== "string" || !value) return null;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(value);
|
|
85
|
+
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地会话导入:外部工具的对话 → pi 会话。
|
|
3
|
+
*
|
|
4
|
+
* 这里定义"中间层"的形状——它刻意贴着 pi 的会话 schema(角色、content 块、
|
|
5
|
+
* 时间戳、用量),而不是拍平成纯文本:思考、工具调用/结果、图片、用量都要能带过去。
|
|
6
|
+
* 每个来源只负责把自己的格式解析成这些类型,写入合法性由 writer 统一保证。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 支持导入的来源 */
|
|
10
|
+
export type ImportSource = "claude" | "codex" | "grok" | "zcode" | "dsh" | "opencode";
|
|
11
|
+
|
|
12
|
+
export const IMPORT_SOURCES: ImportSource[] = ["claude", "codex", "grok", "zcode", "dsh", "opencode"];
|
|
13
|
+
|
|
14
|
+
export interface ImportedUsage {
|
|
15
|
+
input?: number;
|
|
16
|
+
output?: number;
|
|
17
|
+
cacheRead?: number;
|
|
18
|
+
cacheWrite?: number;
|
|
19
|
+
reasoning?: number;
|
|
20
|
+
totalTokens?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 与 pi 的 content 块对齐(够用即可:其余类型在读取时被忽略) */
|
|
24
|
+
export type ImportedBlock =
|
|
25
|
+
| { type: "text"; text: string }
|
|
26
|
+
| { type: "thinking"; thinking: string }
|
|
27
|
+
| { type: "toolCall"; id: string; name: string; arguments?: unknown }
|
|
28
|
+
| { type: "image"; data: string; mimeType: string };
|
|
29
|
+
|
|
30
|
+
export type ImportedMessage =
|
|
31
|
+
| { role: "user"; content: ImportedBlock[]; timestamp: number }
|
|
32
|
+
| {
|
|
33
|
+
role: "assistant";
|
|
34
|
+
content: ImportedBlock[];
|
|
35
|
+
timestamp: number;
|
|
36
|
+
api?: string;
|
|
37
|
+
provider?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
usage?: ImportedUsage;
|
|
40
|
+
stopReason?: string;
|
|
41
|
+
errorMessage?: string;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
role: "toolResult";
|
|
45
|
+
toolCallId: string;
|
|
46
|
+
toolName?: string;
|
|
47
|
+
content: ImportedBlock[];
|
|
48
|
+
isError?: boolean;
|
|
49
|
+
timestamp: number;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ImportedEntry =
|
|
53
|
+
| { type: "message"; message: ImportedMessage }
|
|
54
|
+
| { type: "session_info"; name: string }
|
|
55
|
+
| { type: "model_change"; provider: string; modelId: string };
|
|
56
|
+
|
|
57
|
+
/** 扫描结果里的一行(会发给前端;location 属服务端内部信息,路由层要剥掉) */
|
|
58
|
+
export interface ExternalSessionSummary {
|
|
59
|
+
source: ImportSource;
|
|
60
|
+
/** 源里的稳定标识(文件名/uuid/DB 行 id):幂等键的一半 */
|
|
61
|
+
externalId: string;
|
|
62
|
+
title?: string;
|
|
63
|
+
/** 源会话原本的项目目录(本机存在就导入回该工作区) */
|
|
64
|
+
projectPath?: string;
|
|
65
|
+
provider?: string;
|
|
66
|
+
model?: string;
|
|
67
|
+
createdAt?: number;
|
|
68
|
+
updatedAt?: number;
|
|
69
|
+
/** 未统计时为 undefined(界面显示 —) */
|
|
70
|
+
messageCount?: number;
|
|
71
|
+
/** 服务端内部定位信息(文件路径 / 库+id),不下发 */
|
|
72
|
+
location: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ImportedSession {
|
|
76
|
+
summary: ExternalSessionSummary;
|
|
77
|
+
entries: ImportedEntry[];
|
|
78
|
+
/** 跳过内容的原因(侧链、合成消息等),用于导入报告 */
|
|
79
|
+
skipped?: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ScanOptions {
|
|
83
|
+
/**
|
|
84
|
+
* 每个来源最多返回多少条(按最近活动倒序)。界面只列最近的若干条,所以扫描阶段就该停手:
|
|
85
|
+
* 文件类的来源按 mtime 排序后只读前若干个头,SQLite 类的直接 LIMIT,不把上千个会话全读一遍。
|
|
86
|
+
* 传 0 或省略表示不限(导入时按 id 反查需要看到全部)。
|
|
87
|
+
*/
|
|
88
|
+
limit?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ImportSourceModule {
|
|
92
|
+
id: ImportSource;
|
|
93
|
+
/** 列出该来源在本机可导入的会话(只读扫描) */
|
|
94
|
+
scan(options?: ScanOptions): Promise<ExternalSessionSummary[]>;
|
|
95
|
+
/** 读取单个会话并转成 pi 条目;读不到返回 null */
|
|
96
|
+
read(summary: ExternalSessionSummary): Promise<ImportedSession | null>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 时间戳容错:非法值不回落到"现在"(那会伪造历史),由调用方决定兜底值 */
|
|
100
|
+
export function toEpochMs(value: unknown): number | undefined {
|
|
101
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
102
|
+
// 秒级时间戳(如 1789273099)按秒理解
|
|
103
|
+
return value < 1e12 ? Math.round(value * 1000) : Math.round(value);
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === "string" && value.trim()) {
|
|
106
|
+
const parsed = Date.parse(value);
|
|
107
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
108
|
+
const numeric = Number(value);
|
|
109
|
+
if (Number.isFinite(numeric)) return toEpochMs(numeric);
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 文本块:空串直接丢掉,避免产出空 content */
|
|
115
|
+
export function textBlock(value: unknown): ImportedBlock | null {
|
|
116
|
+
if (typeof value !== "string") return null;
|
|
117
|
+
return value.trim() ? { type: "text", text: value } : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function compactBlocks(blocks: Array<ImportedBlock | null | undefined>): ImportedBlock[] {
|
|
121
|
+
return blocks.filter((block): block is ImportedBlock => Boolean(block));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 工具返回值可能是字符串、对象或数组,统一成内容块 */
|
|
125
|
+
export function outputBlocks(value: unknown): ImportedBlock[] {
|
|
126
|
+
if (typeof value === "string") return compactBlocks([textBlock(value)]);
|
|
127
|
+
if (Array.isArray(value)) return compactBlocks(value.flatMap((item) => outputBlocks(item)));
|
|
128
|
+
if (value && typeof value === "object") {
|
|
129
|
+
const record = value as Record<string, unknown>;
|
|
130
|
+
if (typeof record.text === "string") return compactBlocks([textBlock(record.text)]);
|
|
131
|
+
if (typeof record.content === "string") return compactBlocks([textBlock(record.content)]);
|
|
132
|
+
try {
|
|
133
|
+
return compactBlocks([textBlock(JSON.stringify(value, null, 2))]);
|
|
134
|
+
} catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 参数可能是 JSON 字符串(codex/dsh 就是),尽量还原成对象 */
|
|
142
|
+
export function parseArguments(value: unknown): unknown {
|
|
143
|
+
if (typeof value !== "string") return value ?? {};
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(value);
|
|
146
|
+
} catch {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 标题候选:源标题 → 首条用户消息前 48 字 */
|
|
152
|
+
export function fallbackTitle(entries: ImportedEntry[], explicit?: string): string {
|
|
153
|
+
const trimmed = explicit?.replace(/\s+/g, " ").trim();
|
|
154
|
+
if (trimmed) return trimmed.slice(0, 120);
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (entry.type !== "message" || entry.message.role !== "user") continue;
|
|
157
|
+
const text = entry.message.content.map((block) => (block.type === "text" ? block.text : "")).join(" ").replace(/\s+/g, " ").trim();
|
|
158
|
+
if (text) return text.slice(0, 48);
|
|
159
|
+
}
|
|
160
|
+
return "";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 标题上限:列表只用来辨认会话,几百字的提示词原样塞进接口响应只是浪费带宽 */
|
|
164
|
+
export const TITLE_LIMIT = 120;
|
|
165
|
+
|
|
166
|
+
/** 把任意来源的标题字段压成一行、限长;空串按"没有标题"处理 */
|
|
167
|
+
export function cleanTitle(value: unknown): string | undefined {
|
|
168
|
+
if (typeof value !== "string") return undefined;
|
|
169
|
+
const flat = value.replace(/\s+/g, " ").trim();
|
|
170
|
+
return flat ? flat.slice(0, TITLE_LIMIT) : undefined;
|
|
171
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把导入的条目写成**合法**的 pi 会话文件。
|
|
3
|
+
*
|
|
4
|
+
* 这个文件的每条规则都对应 SDK 的一处硬要求,写错就会出现"列表里有、打开报错"或
|
|
5
|
+
* "模型请求 400"这类问题:
|
|
6
|
+
* 1. 首行 header 且 version=3(小于 3 会被 SDK 迁移,条目 id 全被重写)
|
|
7
|
+
* 2. 条目 parentId 串成单链,首条为 null
|
|
8
|
+
* 3. 每个 toolResult.toolCallId 必须能对上前面某条 assistant 的 toolCall.id,
|
|
9
|
+
* 源里缺调用记录时补一条占位 toolCall(否则压缩/请求阶段会挂)
|
|
10
|
+
* 4. content 永远是数组;message.timestamp 是毫秒数
|
|
11
|
+
* 5. 文件落在 <agentDir>/sessions/<编码 cwd>/ 下,header.cwd 必须是真实存在的目录
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import type { ImportedBlock, ImportedEntry, ImportedMessage, ImportedSession, ImportSource } from "./types";
|
|
17
|
+
import { fallbackTitle } from "./types";
|
|
18
|
+
|
|
19
|
+
/** 来源前缀:标题里标出来源,侧栏一眼能看出是导入的 */
|
|
20
|
+
const SOURCE_LABEL: Record<ImportSource, string> = {
|
|
21
|
+
claude: "Claude",
|
|
22
|
+
codex: "Codex",
|
|
23
|
+
grok: "Grok",
|
|
24
|
+
zcode: "ZCode",
|
|
25
|
+
dsh: "dsh",
|
|
26
|
+
opencode: "opencode",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const PROVENANCE_TYPE = "piweb-import";
|
|
30
|
+
|
|
31
|
+
const entryId = () => randomUUID().replace(/-/g, "").slice(0, 8);
|
|
32
|
+
|
|
33
|
+
/** 会话目录编码:与 SDK 的 SessionManager 完全一致(--D--AIApp-PiWeb--) */
|
|
34
|
+
export function encodeSessionDir(cwd: string): string {
|
|
35
|
+
return `--${path.resolve(cwd).replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function safeId(value: string): string {
|
|
39
|
+
const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
40
|
+
return cleaned.slice(0, 80) || "session";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 文件名里带来源与外部 id → "是否导入过"只需看文件名,O(1) 且不依赖任何数据库 */
|
|
44
|
+
export function importedFileName(source: ImportSource, externalId: string, createdAt?: number): string {
|
|
45
|
+
const stamp = new Date(createdAt ?? Date.now()).toISOString().replace(/[:.]/g, "-");
|
|
46
|
+
return `${stamp}_import-${source}-${safeId(externalId)}.jsonl`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 幂等检查:本机是否已经导入过这条外部会话 */
|
|
50
|
+
export async function findImported(agentDir: string, source: ImportSource, externalId: string): Promise<string | null> {
|
|
51
|
+
const suffix = `_import-${source}-${safeId(externalId)}.jsonl`;
|
|
52
|
+
const root = path.join(agentDir, "sessions");
|
|
53
|
+
let dirs: string[] = [];
|
|
54
|
+
try {
|
|
55
|
+
dirs = await fs.readdir(root);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
for (const dir of dirs) {
|
|
60
|
+
// 文件名带时间戳前缀,只能列目录后按后缀匹配
|
|
61
|
+
let files: string[] = [];
|
|
62
|
+
try {
|
|
63
|
+
files = await fs.readdir(path.join(root, dir));
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const hit = files.find((name) => name.endsWith(suffix));
|
|
68
|
+
if (hit) return path.join(root, dir, hit);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 一次列目录得到所有已导入的 `source:externalId`:扫描几百条会话时逐条 findImported 要把
|
|
75
|
+
* sessions 目录翻几百遍(每次 O(目录数)),这里聚合一次给界面标记"已导入"。
|
|
76
|
+
*/
|
|
77
|
+
export async function listImportedKeys(agentDir: string): Promise<Set<string>> {
|
|
78
|
+
const keys = new Set<string>();
|
|
79
|
+
const root = path.join(agentDir, "sessions");
|
|
80
|
+
let dirs: string[] = [];
|
|
81
|
+
try {
|
|
82
|
+
dirs = await fs.readdir(root);
|
|
83
|
+
} catch {
|
|
84
|
+
return keys;
|
|
85
|
+
}
|
|
86
|
+
for (const dir of dirs) {
|
|
87
|
+
let files: string[] = [];
|
|
88
|
+
try {
|
|
89
|
+
files = await fs.readdir(path.join(root, dir));
|
|
90
|
+
} catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
for (const name of files) {
|
|
94
|
+
const match = /_import-([A-Za-z0-9]+)-(.+)\.jsonl$/.exec(name);
|
|
95
|
+
if (match) keys.add(`${match[1]}:${match[2]}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return keys;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 与 findImported / importedFileName 共用同一个键(避免两边规则不一致导致重复导入) */
|
|
102
|
+
export function importedKey(source: ImportSource, externalId: string): string {
|
|
103
|
+
return `${source}:${safeId(externalId)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 内容块清洗:toolCall 必须有 id/name;toolResult 文本转块 */
|
|
107
|
+
function cleanBlocks(blocks: ImportedBlock[]): ImportedBlock[] {
|
|
108
|
+
const out: ImportedBlock[] = [];
|
|
109
|
+
for (const block of blocks) {
|
|
110
|
+
if (!block) continue;
|
|
111
|
+
if (block.type === "text") {
|
|
112
|
+
if (block.text) out.push(block);
|
|
113
|
+
} else if (block.type === "thinking") {
|
|
114
|
+
if (block.thinking) out.push(block);
|
|
115
|
+
} else if (block.type === "toolCall") {
|
|
116
|
+
if (block.id && block.name) out.push({ ...block, arguments: block.arguments ?? {} });
|
|
117
|
+
} else if (block.type === "image") {
|
|
118
|
+
if (block.data && block.mimeType) out.push(block);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 保证 toolResult 有对应的 toolCall:源里常见"只留了结果、调用被截断"的情况,
|
|
126
|
+
* 缺了就补一条占位 assistant 消息,否则 pi 侧组装请求时会因为孤立 toolResult 报错。
|
|
127
|
+
*/
|
|
128
|
+
function repairToolPairing(messages: ImportedMessage[]): { messages: ImportedMessage[]; repaired: number } {
|
|
129
|
+
const known = new Set<string>();
|
|
130
|
+
const out: ImportedMessage[] = [];
|
|
131
|
+
let repaired = 0;
|
|
132
|
+
for (const message of messages) {
|
|
133
|
+
if (message.role === "assistant") {
|
|
134
|
+
for (const block of message.content) if (block.type === "toolCall") known.add(block.id);
|
|
135
|
+
out.push(message);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (message.role !== "toolResult" || known.has(message.toolCallId)) {
|
|
139
|
+
out.push(message);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
known.add(message.toolCallId);
|
|
143
|
+
repaired += 1;
|
|
144
|
+
out.push({
|
|
145
|
+
role: "assistant",
|
|
146
|
+
content: [{ type: "toolCall", id: message.toolCallId, name: message.toolName || "tool", arguments: {} }],
|
|
147
|
+
timestamp: Math.max(0, message.timestamp - 1),
|
|
148
|
+
stopReason: "toolUse",
|
|
149
|
+
});
|
|
150
|
+
out.push(message);
|
|
151
|
+
}
|
|
152
|
+
return { messages: out, repaired };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface WriteImportedOptions {
|
|
156
|
+
/** 目标工作区目录(必须真实存在——调用方负责校验与兜底) */
|
|
157
|
+
cwd: string;
|
|
158
|
+
agentDir: string;
|
|
159
|
+
now?: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface WriteImportedResult {
|
|
163
|
+
path: string;
|
|
164
|
+
messageCount: number;
|
|
165
|
+
skipped: string[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** 写一个导入会话;调用方需先确认该外部会话没有导入过 */
|
|
169
|
+
export async function writeImportedSession(session: ImportedSession, options: WriteImportedOptions): Promise<WriteImportedResult> {
|
|
170
|
+
const { summary, entries } = session;
|
|
171
|
+
const now = options.now ?? Date.now();
|
|
172
|
+
const createdAt = summary.createdAt ?? now;
|
|
173
|
+
|
|
174
|
+
const messages = repairToolPairing(entries.filter((entry): entry is { type: "message"; message: ImportedMessage } => entry.type === "message").map((entry) => entry.message));
|
|
175
|
+
const skipped = [...(session.skipped ?? [])];
|
|
176
|
+
if (messages.repaired > 0) skipped.push(`补齐 ${messages.repaired} 条缺失的工具调用记录`);
|
|
177
|
+
|
|
178
|
+
const lines: string[] = [];
|
|
179
|
+
lines.push(JSON.stringify({ type: "session", version: 3, id: randomUUID(), timestamp: new Date(createdAt).toISOString(), cwd: path.resolve(options.cwd) }));
|
|
180
|
+
|
|
181
|
+
let parentId: string | null = null;
|
|
182
|
+
const push = (body: Record<string, unknown>, at: number) => {
|
|
183
|
+
const id = entryId();
|
|
184
|
+
lines.push(JSON.stringify({ ...body, id, parentId, timestamp: new Date(at).toISOString() }));
|
|
185
|
+
parentId = id;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
if (summary.provider && summary.model) push({ type: "model_change", provider: summary.provider, modelId: summary.model }, createdAt);
|
|
189
|
+
|
|
190
|
+
let lastAt = createdAt;
|
|
191
|
+
for (const message of messages.messages) {
|
|
192
|
+
const at = Number.isFinite(message.timestamp) ? message.timestamp : lastAt;
|
|
193
|
+
lastAt = at;
|
|
194
|
+
const content = cleanBlocks(message.content);
|
|
195
|
+
if (!content.length && message.role !== "toolResult") continue;
|
|
196
|
+
if (message.role === "assistant") {
|
|
197
|
+
push({
|
|
198
|
+
type: "message",
|
|
199
|
+
message: {
|
|
200
|
+
role: "assistant",
|
|
201
|
+
content,
|
|
202
|
+
timestamp: at,
|
|
203
|
+
stopReason: message.stopReason ?? "stop",
|
|
204
|
+
...(message.api ? { api: message.api } : {}),
|
|
205
|
+
...(message.provider ? { provider: message.provider } : summary.provider ? { provider: summary.provider } : {}),
|
|
206
|
+
...(message.model ? { model: message.model } : summary.model ? { model: summary.model } : {}),
|
|
207
|
+
...(message.usage ? { usage: normalizeUsage(message.usage) } : {}),
|
|
208
|
+
...(message.errorMessage ? { errorMessage: message.errorMessage } : {}),
|
|
209
|
+
},
|
|
210
|
+
}, at);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (message.role === "toolResult") {
|
|
214
|
+
push({
|
|
215
|
+
type: "message",
|
|
216
|
+
message: {
|
|
217
|
+
role: "toolResult",
|
|
218
|
+
toolCallId: message.toolCallId,
|
|
219
|
+
...(message.toolName ? { toolName: message.toolName } : {}),
|
|
220
|
+
content,
|
|
221
|
+
isError: Boolean(message.isError),
|
|
222
|
+
timestamp: at,
|
|
223
|
+
},
|
|
224
|
+
}, at);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
push({ type: "message", message: { role: "user", content, timestamp: at } }, at);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const title = fallbackTitle(entries, summary.title);
|
|
231
|
+
if (title) push({ type: "session_info", name: `【${SOURCE_LABEL[summary.source]}】${title}` }, lastAt);
|
|
232
|
+
// 来源凭据:两边都会忽略未知 custom 条目,不影响上下文
|
|
233
|
+
push({ type: "custom", customType: PROVENANCE_TYPE, data: { source: summary.source, externalId: summary.externalId, sourcePath: summary.location, projectPath: summary.projectPath ?? null, importedAt: new Date(now).toISOString() } }, lastAt);
|
|
234
|
+
|
|
235
|
+
const dir = path.join(options.agentDir, "sessions", encodeSessionDir(options.cwd));
|
|
236
|
+
await fs.mkdir(dir, { recursive: true });
|
|
237
|
+
const target = path.join(dir, importedFileName(summary.source, summary.externalId, createdAt));
|
|
238
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
239
|
+
await fs.writeFile(temporary, `${lines.join("\n")}\n`, "utf8");
|
|
240
|
+
await fs.rename(temporary, target);
|
|
241
|
+
return { path: target, messageCount: messages.messages.length, skipped };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** pi 的 usage 形状:缺的字段补 0,cost 恒为 0(导入的历史没有真实计费) */
|
|
245
|
+
function normalizeUsage(usage: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; reasoning?: number; totalTokens?: number }) {
|
|
246
|
+
const input = usage.input ?? 0;
|
|
247
|
+
const output = usage.output ?? 0;
|
|
248
|
+
const cacheRead = usage.cacheRead ?? 0;
|
|
249
|
+
const cacheWrite = usage.cacheWrite ?? 0;
|
|
250
|
+
return {
|
|
251
|
+
input,
|
|
252
|
+
output,
|
|
253
|
+
cacheRead,
|
|
254
|
+
cacheWrite,
|
|
255
|
+
...(usage.reasoning ? { reasoning: usage.reasoning } : {}),
|
|
256
|
+
totalTokens: usage.totalTokens ?? input + output + cacheRead + cacheWrite,
|
|
257
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZCode:`~/.zcode/cli/db/db.sqlite`(session / message / part 三表)。
|
|
3
|
+
* 与 opencode 共用适配器,差异只在库路径与排序字段。
|
|
4
|
+
*/
|
|
5
|
+
import { zcodeDb } from "./paths";
|
|
6
|
+
import { createOcFamilySource } from "./oc-family";
|
|
7
|
+
|
|
8
|
+
export const zcodeSource = createOcFamilySource("zcode", zcodeDb);
|
package/src/lib/types.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
exports.id=118,exports.ids=[118],exports.modules={49904:(a,b,c)=>{"use strict";c.d(b,{Ax:()=>k,Jt:()=>m,_i:()=>l});var d=c(51455),e=c.n(d),f=c(76760),g=c.n(f),h=c(80974),i=c.n(h);let j=new Map;function k(a,b){let c=g().resolve(a),d="win32"===process.platform?c.toLowerCase():c,e=(j.get(d)??Promise.resolve()).then(b,b),f=e.then(()=>void 0,()=>void 0);return j.set(d,f),f.then(()=>{j.get(d)===f&&j.delete(d)}),e}async function l(a,b){await e().mkdir(g().dirname(a),{recursive:!0});let c=await i().lock(a,{realpath:!1,retries:{retries:20,minTimeout:20,maxTimeout:100}});try{return await b()}finally{await c()}}async function m(a){await a.flush();let b=a.drainErrors();if(b.length)throw Error(`Pi settings failed: ${b.map(({scope:a,error:b})=>`${a}: ${b.message}`).join("; ")}`,{cause:b[0].error})}},75554:(a,b,c)=>{"use strict";c.a(a,async(a,d)=>{try{c.d(b,{DY:()=>p,Gh:()=>u,J0:()=>o});var e=c(51455),f=c.n(e),g=c(76760),h=c.n(g),i=c(77598),j=c(87982),k=c(49904),l=a([j]);j=(l.then?(await l)():l)[0];let q=16384,r=2e4,s=3,t=2e3;async function m(){let a;try{a=await f().readFile(h().join((0,j.$M)(),"settings.json"),"utf8")}catch(a){if("ENOENT"===a.code)return{};throw a}let b=JSON.parse(a.replace(/^\uFEFF/,""));if(!b||"object"!=typeof b||Array.isArray(b))throw Error("settings.json must contain a JSON object");return b}function n(a,b){let c=a[b];return c&&"object"==typeof c&&!Array.isArray(c)?c:{}}async function o(){let a=await m(),b=a.compaction??{},c=a.retry??{},d=(a,b)=>"number"==typeof a&&Number.isSafeInteger(a)&&a>=0?a:b;return{compaction:{enabled:!1!==b.enabled,reserveTokens:d(b.reserveTokens,q),keepRecentTokens:d(b.keepRecentTokens,r)},retry:{enabled:!1!==c.enabled,maxRetries:d(c.maxRetries,s),baseDelayMs:d(c.baseDelayMs,t)}}}class u extends Error{}async function p(a){let b=function(a){if(!a||"object"!=typeof a||Array.isArray(a))throw new u("invalid settings patch");if(Object.keys(a).some(a=>"compaction"!==a&&"retry"!==a))throw new u("unknown settings field");let b={};for(let c of["compaction","retry"]){if(void 0===a[c])continue;let d=a[c];if(!d||"object"!=typeof d||Array.isArray(d))throw new u(`invalid ${c} settings`);let e="compaction"===c?["enabled","reserveTokens","keepRecentTokens"]:["enabled","maxRetries","baseDelayMs"];for(let[a,b]of Object.entries(d)){if(!e.includes(a))throw new u(`unknown ${c} field: ${a}`);if("enabled"===a){if("boolean"!=typeof b)throw new u(`${c}.${a} must be boolean`)}else if("number"!=typeof b||!Number.isSafeInteger(b)||b<0||b>1e9)throw new u(`${c}.${a} must be a non-negative integer`)}"compaction"===c?b.compaction=d:b.retry=d}return b}(a),c=h().join((0,j.$M)(),"settings.json");await (0,k.Ax)(c,()=>(0,k._i)(c,async()=>{let a=await m();b.compaction&&(a.compaction={...n(a,"compaction"),...b.compaction}),b.retry&&(a.retry={...n(a,"retry"),...b.retry}),await f().mkdir(h().dirname(c),{recursive:!0});let d=`${c}.${process.pid}.${(0,i.randomUUID)()}.tmp`;try{await f().writeFile(d,JSON.stringify(a,null," "),"utf8"),await f().rename(d,c)}finally{await f().rm(d,{force:!0}).catch(()=>void 0)}})),await (0,j.BW)()}d()}catch(a){d(a)}})},78335:()=>{},87982:(a,b,c)=>{"use strict";c.a(a,async(a,d)=>{try{c.d(b,{$M:()=>i.getAgentDir,Ax:()=>Q,BW:()=>D,Cv:()=>i.SessionManager,Dc:()=>z,Df:()=>x,Ec:()=>w,G1:()=>J,Gg:()=>H,QR:()=>F,Sd:()=>I,X1:()=>E,YW:()=>r,Yb:()=>t,af:()=>n,az:()=>i.createAgentSession,fm:()=>p,jz:()=>A,rC:()=>l,uU:()=>k,v9:()=>B,vT:()=>q,wK:()=>o,wj:()=>G,xD:()=>C,ye:()=>u});var e=c(76760),f=c.n(e),g=c(73024),h=c.n(g),i=c(75430),j=a([i]);i=(j.then?(await j)():j)[0],process.env.PYTHONUTF8??="1",process.env.PYTHONIOENCODING??="utf-8";let K=null;function k(){return K||(K=i.ModelRuntime.create().catch(a=>{throw K=null,a})),K}function l(){K=null}let L=new Map,M=null;function m(){return M||(M=new i.ProjectTrustStore((0,i.getAgentDir)())),M}function n(a){let b=f().resolve(a),c=!1;try{c=(0,i.hasTrustRequiringProjectResources)(b)}catch{c=!1}if(!c)return{required:!1,trusted:!0,source:"not-required"};let d=m().get(b);if(null!==d)return{required:!0,trusted:d,source:"remembered"};let e=function(){try{let a=JSON.parse(h().readFileSync(f().join((0,i.getAgentDir)(),"settings.json"),"utf8"));return"always"===a.defaultProjectTrust||"never"===a.defaultProjectTrust?a.defaultProjectTrust:"ask"}catch{return"ask"}}();return"always"===e?{required:!0,trusted:!0,source:"default-always"}:"never"===e?{required:!0,trusted:!1,source:"default-never"}:{required:!0,trusted:!1,source:"undecided"}}function o(a,b){let c=f().resolve(a);m().set(c,b);let d=n(c),e="win32"===process.platform?c.toLowerCase():c,g=L.get(e);return g&&g.setProjectTrusted(d.trusted),d}function p(){for(let[a,b]of(M=null,L))try{b.setProjectTrusted(n(a).trusted)}catch{}}function q(a){let b=f().resolve(a),c="win32"===process.platform?b.toLowerCase():b,d=L.get(c);return d||(d=i.SettingsManager.create(b,(0,i.getAgentDir)(),{projectTrusted:n(b).trusted}),L.set(c,d)),d}let N=new Map,O=Promise.resolve();function r(a){let b=O.then(a,a);return O=b.then(()=>void 0,()=>void 0),b}function s(a){let b=f().resolve(a);return"win32"===process.platform?b.toLowerCase():b}async function t(a){let b=q(a);b.setProjectTrusted(n(a).trusted);let c=new i.DefaultResourceLoader({cwd:f().resolve(a),agentDir:(0,i.getAgentDir)(),settingsManager:b}),d=c.reload.bind(c);c.reload=async c=>{b.setProjectTrusted(n(a).trusted),await d(c)};try{return await r(()=>c.reload()),c}catch(a){throw c.getExtensions().runtime.invalidate(),a}}function u(a){let b=s(a),c=N.get(b);return c||(c={loader:new i.DefaultResourceLoader({cwd:f().resolve(a),agentDir:(0,i.getAgentDir)(),settingsManager:q(a)}),ready:Promise.resolve(),version:0,fingerprint:""},N.set(b,c),v(a,c)),c.loader}function v(a,b){let c=r(async()=>{q(a).setProjectTrusted(n(a).trusted),b.loader.getExtensions().runtime.invalidate();let c=y(a);await b.loader.reload(),b.fingerprint=c,b.version+=1});return b.ready=c,c.catch(()=>void 0),c}function w(a){return N.get(s(a))?.version??0}async function x(a){u(a),await N.get(s(a)).ready}function y(a){let b,c,d=[String(n(a).trusted)],e=(a,b)=>{try{let c=h().statSync(a);if(d.push(`${a}:${c.mtimeMs}:${c.ctimeMs}:${c.size}`),c.isDirectory()&&b<2)for(let c of h().readdirSync(a,{withFileTypes:!0}).sort((a,b)=>a.name.localeCompare(b.name)))"node_modules"!==c.name&&e(f().join(a,c.name),b+1)}catch{d.push(`${a}:missing`)}};for(let d of(b=(0,i.getAgentDir)(),c=f().join(f().resolve(a),".pi"),[f().join(b,"prompts"),f().join(b,"skills"),f().join(b,"extensions"),f().join(c,"prompts"),f().join(c,"skills"),f().join(c,"extensions")]))e(d,0);for(let b of[f().join((0,i.getAgentDir)(),"settings.json"),f().join(a,".pi","settings.json")])e(b,2);return d.join("\n")}function z(a){let b=N.get(s(a));return!b||y(a)!==b.fingerprint}async function A(a){await x(a);let b=N.get(s(a));return!!b&&y(a)!==b.fingerprint&&(await C(a),!0)}function B(a){return N.get(s(a))?.loader}async function C(a){let b=N.get(s(a));b?await v(a,b):await x(a)}async function D(){for(let a of L.values())try{await a.reload()}catch{}}async function E(){for(let a of L.values())try{await a.reload()}catch{}P.clear();let a=[];for(let[b,c]of N)try{await v(b,c),a.push(b)}catch{}return a}let P=new Map;function F(a){let b=f().resolve(a),c="win32"===process.platform?b.toLowerCase():b,d=P.get(c);return d||(d=new i.DefaultPackageManager({cwd:b,agentDir:(0,i.getAgentDir)(),settingsManager:q(b)}),P.set(c,d)),d}function G(a){return Buffer.from(a,"utf8").toString("base64url")}function H(a){return Buffer.from(a,"base64url").toString("utf8")}function I(a){return/^[A-Za-z0-9_-]+$/.test(a)&&a.length>8}function J(a){return i.SessionManager.open(a)}let Q={readonly:["read","grep","find","ls"],standard:["read","grep","find","ls","bash","edit","write"],full:[]};d()}catch(a){d(a)}})},96487:()=>{}};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:21,errorRate:1e-4,numBits:403,numHashes:14,bitArray:[0,1,1,0,1,1,0,0,0,0,0,0,1,1,1,0,1,1,0,0,0,0,1,1,0,0,0,1,0,1,1,1,1,0,0,0,0,1,1,0,1,1,0,0,0,1,0,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,1,1,0,1,1,0,0,0,0,1,0,1,1,0,1,1,1,1,1,0,0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,0,1,1,1,0,1,0,1,0,1,0,1,1,0,1,1,0,0,1,1,0,0,0,0,1,0,1,0,0,1,1,1,0,0,1,1,0,1,1,1,0,0,1,0,1,0,0,1,1,1,0,1,0,1,0,0,1,1,0,0,1,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,1,0,0,1,1,0,0,0,1,0,1,0,0,1,0,1,1,0,1,1,1,0,0,1,1,1,0,0,1,1,0,0,1,0,1,1,0,1,1,1,1,1,0,1,0,0,0,0,0,1,1,0,1,1,0,1,0,0,1,0,0,0,1,1,1,0,0,1,1,0,1,0,1,0,1,1,0,0,1,1,0,1,1,1,1,1,1,0,0,0,0,1,1,0,1,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,0,1,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,1,1,0,0,1,0,1,1,0,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,1,1,0,1,0,0,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,1,0,0,1,0,1,1,1,0,1,1,1,0,1,0,0,0,0,1,1,1,1,0,0,0]},__routerFilterDynamic:{numItems:2,errorRate:1e-4,numBits:39,numHashes:14,bitArray:[1,0,1,0,0,0,0,0,1,1,0,1,1,1,1,1,0,0,1,0,0,1,0,0,0,1,0,1,1,0,1,1,0,1,0,1,1,0,0]},sortedPages:["/_app"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|