@juspay/shooter 1.16.0 → 1.17.0
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/.claude/hooks/codex-hooks.example.json +75 -0
- package/.claude/hooks/notifier.cjs +158 -8
- package/build/client/_app/immutable/assets/{0.DEfoFaGR.css → 0.B0O0vCnX.css} +1 -1
- package/build/client/_app/immutable/assets/0.B0O0vCnX.css.br +0 -0
- package/build/client/_app/immutable/assets/0.B0O0vCnX.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BctvtE4d.js +1 -0
- package/build/client/_app/immutable/chunks/BctvtE4d.js.br +0 -0
- package/build/client/_app/immutable/chunks/BctvtE4d.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BxFShcQO.js +1 -0
- package/build/client/_app/immutable/chunks/BxFShcQO.js.br +0 -0
- package/build/client/_app/immutable/chunks/BxFShcQO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DOHhmtDH.js → ByzqAuXw.js} +1 -1
- package/build/client/_app/immutable/chunks/ByzqAuXw.js.br +0 -0
- package/build/client/_app/immutable/chunks/ByzqAuXw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DlS3abGJ.js → CjfxuHdN.js} +1 -1
- package/build/client/_app/immutable/chunks/CjfxuHdN.js.br +0 -0
- package/build/client/_app/immutable/chunks/CjfxuHdN.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CSJG7N9H.js → app.CNaTe-zm.js} +2 -2
- package/build/client/_app/immutable/entry/app.CNaTe-zm.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CNaTe-zm.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.hxYnjcDu.js +1 -0
- package/build/client/_app/immutable/entry/start.hxYnjcDu.js.br +0 -0
- package/build/client/_app/immutable/entry/start.hxYnjcDu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.qOL7xtFn.js → 0.C3ELOf4c.js} +1 -1
- package/build/client/_app/immutable/nodes/0.C3ELOf4c.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.C3ELOf4c.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.Di708Ago.js → 1.Fqso94b3.js} +1 -1
- package/build/client/_app/immutable/nodes/1.Fqso94b3.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Fqso94b3.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.DSM1znqa.js → 2.BusCVJWk.js} +1 -1
- package/build/client/_app/immutable/nodes/2.BusCVJWk.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BusCVJWk.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.BPa5fh75.js → 3.DUlpocIc.js} +1 -1
- package/build/client/_app/immutable/nodes/3.DUlpocIc.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DUlpocIc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.CG4eKRH0.js +1 -0
- package/build/client/_app/immutable/nodes/6.CG4eKRH0.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.CG4eKRH0.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.B7UJd8GQ.js → 7.DHilxD1o.js} +3 -3
- package/build/client/_app/immutable/nodes/7.DHilxD1o.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DHilxD1o.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.BjKgvSie.js +2 -0
- package/build/client/_app/immutable/nodes/8.BjKgvSie.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.BjKgvSie.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.BRT6HOXB.js +2 -0
- package/build/client/_app/immutable/nodes/9.BRT6HOXB.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.BRT6HOXB.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-D8uPamNd.js → 0-BWFSL107.js} +3 -3
- package/build/server/chunks/{0-D8uPamNd.js.map → 0-BWFSL107.js.map} +1 -1
- package/build/server/chunks/{1-DhtioHbs.js → 1-Bw5KlAjL.js} +2 -2
- package/build/server/chunks/{1-DhtioHbs.js.map → 1-Bw5KlAjL.js.map} +1 -1
- package/build/server/chunks/{2-Cgh7ZFgE.js → 2-CQ3yYSVK.js} +2 -2
- package/build/server/chunks/{2-Cgh7ZFgE.js.map → 2-CQ3yYSVK.js.map} +1 -1
- package/build/server/chunks/{3-I6hnjssH.js → 3-DZ4H9hPs.js} +2 -2
- package/build/server/chunks/{3-I6hnjssH.js.map → 3-DZ4H9hPs.js.map} +1 -1
- package/build/server/chunks/{6-bPDbH1_W.js → 6-BZ0enR6b.js} +2 -2
- package/build/server/chunks/6-BZ0enR6b.js.map +1 -0
- package/build/server/chunks/{7-CpBrOkxQ.js → 7-Lg8imTZn.js} +2 -2
- package/build/server/chunks/7-Lg8imTZn.js.map +1 -0
- package/build/server/chunks/{8-BRGAVfze.js → 8-DKs4yOL7.js} +2 -2
- package/build/server/chunks/8-DKs4yOL7.js.map +1 -0
- package/build/server/chunks/{9-C6xuAb_Y.js → 9-UNmpUWDY.js} +2 -2
- package/build/server/chunks/9-UNmpUWDY.js.map +1 -0
- package/build/server/chunks/{_server.ts-C6xbNz6d.js → _server.ts-5wx4ZppI.js} +3 -3
- package/build/server/chunks/{_server.ts-C6xbNz6d.js.map → _server.ts-5wx4ZppI.js.map} +1 -1
- package/build/server/chunks/{_server.ts-BrRZXr-8.js → _server.ts-B1z0q6qZ.js} +10 -9
- package/build/server/chunks/_server.ts-B1z0q6qZ.js.map +1 -0
- package/build/server/chunks/_server.ts-BMMTS86y.js +82 -0
- package/build/server/chunks/_server.ts-BMMTS86y.js.map +1 -0
- package/build/server/chunks/{_server.ts-CjK0g9dO.js → _server.ts-Bt7EAfjo.js} +34 -2
- package/build/server/chunks/_server.ts-Bt7EAfjo.js.map +1 -0
- package/build/server/chunks/{_server.ts-Cq9_scaV.js → _server.ts-CKXVBbwb.js} +16 -16
- package/build/server/chunks/_server.ts-CKXVBbwb.js.map +1 -0
- package/build/server/chunks/{_server.ts-Dekgb6Hx.js → _server.ts-CgHc1Zpx.js} +3 -3
- package/build/server/chunks/{_server.ts-Dekgb6Hx.js.map → _server.ts-CgHc1Zpx.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CFX-S_8q.js → _server.ts-DZ5naqSL.js} +2 -2
- package/build/server/chunks/{_server.ts-CFX-S_8q.js.map → _server.ts-DZ5naqSL.js.map} +1 -1
- package/build/server/chunks/{opencode-db-path-CRgzBK5U.js → opencode-db-path-BwaPufWf.js} +41 -32
- package/build/server/chunks/opencode-db-path-BwaPufWf.js.map +1 -0
- package/build/server/chunks/{pty-manager-aFpChJah.js → pty-manager-RmhVe2Ez.js} +2 -2
- package/build/server/chunks/{pty-manager-aFpChJah.js.map → pty-manager-RmhVe2Ez.js.map} +1 -1
- package/build/server/chunks/qwen-reader-2fTFuC_D.js +622 -0
- package/build/server/chunks/qwen-reader-2fTFuC_D.js.map +1 -0
- package/build/server/chunks/{_server.ts-D--_NXt2.js → registry-DzJj2E6I.js} +89 -95
- package/build/server/chunks/registry-DzJj2E6I.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +16 -16
- package/build/server/manifest.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/modules/client/common/index.ts +1 -1
- package/src/lib/modules/client/common/provider.ts +13 -0
- package/src/lib/modules/client/terminal/LaunchSheet.svelte +1 -0
- package/src/lib/modules/server/sessions/codex-reader.ts +34 -33
- package/src/lib/modules/server/sessions/gemini-reader.ts +571 -0
- package/src/lib/modules/server/sessions/opencode-db-path.ts +19 -10
- package/src/lib/modules/server/sessions/process-detector.ts +44 -0
- package/src/lib/modules/server/sessions/qwen-reader.ts +310 -0
- package/src/lib/modules/server/sessions/registry.ts +137 -0
- package/src/lib/modules/server/terminal/codex-watcher.ts +4 -1
- package/src/lib/modules/server/ws/session-handler.ts +23 -19
- package/src/lib/theme.css +33 -0
- package/src/lib/types/gemini.ts +100 -0
- package/src/lib/types/generated/Sessions.ts +20 -1
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/sessions.ts +23 -2
- package/src/routes/api/sessions/+server.ts +5 -52
- package/src/routes/api/sessions/connect/+server.ts +13 -10
- package/src/routes/api/terminals/+server.ts +7 -5
- package/src/routes/terminals/+page.svelte +7 -2
- package/src/routes/terminals/[id]/+page.svelte +1 -2
- package/build/client/_app/immutable/assets/0.DEfoFaGR.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DEfoFaGR.css.gz +0 -0
- package/build/client/_app/immutable/chunks/Bkqjn62J.js +0 -1
- package/build/client/_app/immutable/chunks/Bkqjn62J.js.br +0 -1
- package/build/client/_app/immutable/chunks/Bkqjn62J.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DOHhmtDH.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOHhmtDH.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DlS3abGJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/DlS3abGJ.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CSJG7N9H.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CSJG7N9H.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CTt1901T.js +0 -1
- package/build/client/_app/immutable/entry/start.CTt1901T.js.br +0 -2
- package/build/client/_app/immutable/entry/start.CTt1901T.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.qOL7xtFn.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.qOL7xtFn.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Di708Ago.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Di708Ago.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DSM1znqa.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DSM1znqa.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.BPa5fh75.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BPa5fh75.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.B1LwwEF-.js +0 -1
- package/build/client/_app/immutable/nodes/6.B1LwwEF-.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.B1LwwEF-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.B7UJd8GQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.B7UJd8GQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.CG0mrgBU.js +0 -2
- package/build/client/_app/immutable/nodes/8.CG0mrgBU.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.CG0mrgBU.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.KwzWaMHj.js +0 -2
- package/build/client/_app/immutable/nodes/9.KwzWaMHj.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.KwzWaMHj.js.gz +0 -0
- package/build/server/chunks/6-bPDbH1_W.js.map +0 -1
- package/build/server/chunks/7-CpBrOkxQ.js.map +0 -1
- package/build/server/chunks/8-BRGAVfze.js.map +0 -1
- package/build/server/chunks/9-C6xuAb_Y.js.map +0 -1
- package/build/server/chunks/_server.ts-BrRZXr-8.js.map +0 -1
- package/build/server/chunks/_server.ts-CjK0g9dO.js.map +0 -1
- package/build/server/chunks/_server.ts-Cq9_scaV.js.map +0 -1
- package/build/server/chunks/_server.ts-D--_NXt2.js.map +0 -1
- package/build/server/chunks/opencode-db-path-CRgzBK5U.js.map +0 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qwen Code session reader.
|
|
3
|
+
*
|
|
4
|
+
* Qwen Code (a Gemini-CLI fork) writes a HYBRID format: a Claude-style JSONL
|
|
5
|
+
* envelope (uuid/parentUuid/type per line) carrying a Gemini-style message body
|
|
6
|
+
* (`message: { role, parts: [{text}|{thought}|{functionCall}|{functionResponse}] }`).
|
|
7
|
+
* Stored at ~/.qwen/projects/<encoded-cwd>/chats/<id>.jsonl. So we parse the
|
|
8
|
+
* envelope ourselves and map the Gemini-style parts (NOT the Claude parser,
|
|
9
|
+
* which expects message.content).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ConversationMessage, MessagePart, ProjectGroup, SessionInfo } from '$lib/types';
|
|
13
|
+
|
|
14
|
+
import * as crypto from 'crypto';
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import { homedir } from 'os';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
|
|
19
|
+
const QWEN_PROJECTS = path.join(homedir(), '.qwen', 'projects');
|
|
20
|
+
const PREFIX_BYTES = 64 * 1024;
|
|
21
|
+
/** Cap conversation reads at 16 MB; oversized files are tail-read (matches the Codex reader). */
|
|
22
|
+
const MAX_QWEN_FILE_BYTES = 16 * 1024 * 1024;
|
|
23
|
+
const SYSTEM_TAG_PREFIXES = [
|
|
24
|
+
'<command-name>',
|
|
25
|
+
'<local-command',
|
|
26
|
+
'<system-reminder>',
|
|
27
|
+
'<task-notification>',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** Find Qwen sessions whose file changed within `thresholdMs` — i.e. currently or recently active. */
|
|
31
|
+
export function detectActiveQwenSessions(
|
|
32
|
+
thresholdMs: number
|
|
33
|
+
): { cwd: string; id: string; startedAt: number }[] {
|
|
34
|
+
const cutoff = Date.now() - thresholdMs;
|
|
35
|
+
const out: { cwd: string; id: string; startedAt: number }[] = [];
|
|
36
|
+
for (const filePath of collectQwenFiles()) {
|
|
37
|
+
try {
|
|
38
|
+
const stat = fs.statSync(filePath);
|
|
39
|
+
if (stat.mtimeMs < cutoff) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const meta = readMeta(readPrefix(filePath));
|
|
43
|
+
if (meta) {
|
|
44
|
+
out.push({ cwd: meta.cwd, id: meta.id, startedAt: stat.birthtimeMs });
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// skip
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return a page of a Qwen session's conversation. With `offset` 0 the most recent
|
|
55
|
+
* `limit` messages are returned, backed up to a user-message boundary so turns
|
|
56
|
+
* aren't clipped; otherwise the `offset`..`offset + limit` slice is returned.
|
|
57
|
+
*/
|
|
58
|
+
export function getQwenConversation(
|
|
59
|
+
sessionId: string,
|
|
60
|
+
offset = 0,
|
|
61
|
+
limit = 200
|
|
62
|
+
): ConversationMessage[] {
|
|
63
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const filePath = collectQwenFiles().find((p) => path.basename(p) === `${sessionId}.jsonl`);
|
|
67
|
+
if (!filePath) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const messages: ConversationMessage[] = [];
|
|
72
|
+
for (const line of readQwenTextBounded(filePath).split('\n')) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const msg = qwenLineToMessage(JSON.parse(trimmed) as Record<string, unknown>);
|
|
79
|
+
if (msg) {
|
|
80
|
+
messages.push(msg);
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// skip malformed line
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (offset === 0 && messages.length > limit) {
|
|
87
|
+
let startIdx = messages.length - limit;
|
|
88
|
+
while (startIdx > 0 && messages[startIdx].role !== 'user') {
|
|
89
|
+
startIdx--;
|
|
90
|
+
}
|
|
91
|
+
return messages.slice(startIdx);
|
|
92
|
+
}
|
|
93
|
+
return messages.slice(offset, offset + limit);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('[qwen] Failed to read conversation:', error);
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** List all Qwen sessions grouped by working directory, most-recently-modified first. */
|
|
101
|
+
export function listQwenProjects(): ProjectGroup[] {
|
|
102
|
+
const byCwd = new Map<string, SessionInfo[]>();
|
|
103
|
+
for (const filePath of collectQwenFiles()) {
|
|
104
|
+
let stat: fs.Stats;
|
|
105
|
+
try {
|
|
106
|
+
stat = fs.statSync(filePath);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const meta = readMeta(readPrefix(filePath));
|
|
111
|
+
if (!meta) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const session: SessionInfo = {
|
|
115
|
+
created: meta.started || stat.birthtime.toISOString(),
|
|
116
|
+
gitBranch: meta.gitBranch,
|
|
117
|
+
id: meta.id,
|
|
118
|
+
messageCount: 0,
|
|
119
|
+
modified: stat.mtime.toISOString(),
|
|
120
|
+
projectPath: meta.cwd,
|
|
121
|
+
source: 'qwen' as const,
|
|
122
|
+
summary: '',
|
|
123
|
+
title: meta.title || 'Untitled Session',
|
|
124
|
+
};
|
|
125
|
+
const bucket = byCwd.get(meta.cwd);
|
|
126
|
+
if (bucket) {
|
|
127
|
+
bucket.push(session);
|
|
128
|
+
} else {
|
|
129
|
+
byCwd.set(meta.cwd, [session]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const projects: ProjectGroup[] = [];
|
|
134
|
+
for (const [cwd, sessions] of byCwd) {
|
|
135
|
+
sessions.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
136
|
+
const segments = cwd.split('/').filter(Boolean);
|
|
137
|
+
projects.push({
|
|
138
|
+
fullPath: cwd,
|
|
139
|
+
id: shortHash(cwd),
|
|
140
|
+
lastModified: sessions[0]?.modified ?? '',
|
|
141
|
+
name: segments.slice(-2).join('/'),
|
|
142
|
+
sessionCount: sessions.length,
|
|
143
|
+
sessions,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return projects.sort(
|
|
147
|
+
(a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** All chats/*.jsonl files under ~/.qwen/projects/<encoded-cwd>/chats/. */
|
|
152
|
+
function collectQwenFiles(): string[] {
|
|
153
|
+
const out: string[] = [];
|
|
154
|
+
let projectDirs: fs.Dirent[];
|
|
155
|
+
try {
|
|
156
|
+
projectDirs = fs.readdirSync(QWEN_PROJECTS, { withFileTypes: true });
|
|
157
|
+
} catch {
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
for (const dir of projectDirs) {
|
|
161
|
+
if (!dir.isDirectory()) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const chatsDir = path.join(QWEN_PROJECTS, dir.name, 'chats');
|
|
165
|
+
try {
|
|
166
|
+
for (const f of fs.readdirSync(chatsDir)) {
|
|
167
|
+
if (f.endsWith('.jsonl')) {
|
|
168
|
+
out.push(path.join(chatsDir, f));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
// no chats dir
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Map a Qwen JSONL line (Claude envelope + Gemini message.parts) to a ConversationMessage. */
|
|
179
|
+
function qwenLineToMessage(entry: Record<string, unknown>): ConversationMessage | null {
|
|
180
|
+
const type = entry.type;
|
|
181
|
+
if (type !== 'user' && type !== 'assistant') {
|
|
182
|
+
return null; // skip system/control records
|
|
183
|
+
}
|
|
184
|
+
const message = (entry.message ?? {}) as { content?: unknown; parts?: unknown };
|
|
185
|
+
const rawParts = Array.isArray(message.parts) ? message.parts : [];
|
|
186
|
+
const parts: MessagePart[] = [];
|
|
187
|
+
for (const raw of rawParts) {
|
|
188
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const p = raw as Record<string, unknown>;
|
|
192
|
+
if (p.functionCall && typeof p.functionCall === 'object') {
|
|
193
|
+
const fc = p.functionCall as Record<string, unknown>;
|
|
194
|
+
const toolName = typeof fc.name === 'string' ? fc.name : 'tool';
|
|
195
|
+
parts.push({
|
|
196
|
+
id: typeof fc.id === 'string' ? fc.id : toolName,
|
|
197
|
+
input: (fc.args as Record<string, unknown>) ?? {},
|
|
198
|
+
toolName,
|
|
199
|
+
type: 'tool_use',
|
|
200
|
+
});
|
|
201
|
+
} else if (p.thought === true && typeof p.text === 'string') {
|
|
202
|
+
parts.push({ content: p.text, type: 'thinking' });
|
|
203
|
+
} else if (typeof p.text === 'string') {
|
|
204
|
+
parts.push({ content: p.text, type: 'text' });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Fallback for any Claude-style string content.
|
|
208
|
+
if (parts.length === 0 && typeof message.content === 'string' && message.content) {
|
|
209
|
+
parts.push({ content: message.content, type: 'text' });
|
|
210
|
+
}
|
|
211
|
+
if (parts.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
id: typeof entry.uuid === 'string' ? entry.uuid : `qwen-${type}`,
|
|
216
|
+
parts,
|
|
217
|
+
role: type === 'user' ? 'user' : 'assistant',
|
|
218
|
+
timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : '',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Extract {cwd, sessionId, gitBranch, started} + first real user prompt from a Qwen session prefix. */
|
|
223
|
+
function readMeta(
|
|
224
|
+
prefix: string
|
|
225
|
+
): null | { cwd: string; gitBranch: string; id: string; started: string; title: string } {
|
|
226
|
+
let cwd = '';
|
|
227
|
+
let id = '';
|
|
228
|
+
let gitBranch = '';
|
|
229
|
+
let started = '';
|
|
230
|
+
let title = '';
|
|
231
|
+
for (const line of prefix.split('\n')) {
|
|
232
|
+
const trimmed = line.trim();
|
|
233
|
+
if (!trimmed) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
let entry: Record<string, unknown>;
|
|
237
|
+
try {
|
|
238
|
+
entry = JSON.parse(trimmed) as Record<string, unknown>;
|
|
239
|
+
} catch {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (!cwd && typeof entry.cwd === 'string') {
|
|
243
|
+
cwd = entry.cwd;
|
|
244
|
+
}
|
|
245
|
+
if (!id && typeof entry.sessionId === 'string') {
|
|
246
|
+
id = entry.sessionId;
|
|
247
|
+
}
|
|
248
|
+
if (!gitBranch && typeof entry.gitBranch === 'string') {
|
|
249
|
+
gitBranch = entry.gitBranch;
|
|
250
|
+
}
|
|
251
|
+
if (!started && typeof entry.timestamp === 'string') {
|
|
252
|
+
started = entry.timestamp;
|
|
253
|
+
}
|
|
254
|
+
if (!title && entry.type === 'user') {
|
|
255
|
+
const msg = entry.message as undefined | { content?: unknown; parts?: unknown };
|
|
256
|
+
let text = typeof msg?.content === 'string' ? msg.content : '';
|
|
257
|
+
if (!text && Array.isArray(msg?.parts)) {
|
|
258
|
+
text = msg.parts
|
|
259
|
+
.map((p) =>
|
|
260
|
+
p && typeof p === 'object' && typeof (p as { text?: unknown }).text === 'string'
|
|
261
|
+
? (p as { text: string }).text
|
|
262
|
+
: ''
|
|
263
|
+
)
|
|
264
|
+
.join(' ')
|
|
265
|
+
.trim();
|
|
266
|
+
}
|
|
267
|
+
if (text && !SYSTEM_TAG_PREFIXES.some((p) => text.startsWith(p))) {
|
|
268
|
+
title = text.split('\n')[0]?.slice(0, 80) ?? '';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return id && cwd ? { cwd, gitBranch, id, started, title } : null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Read the head of a file (complete lines only). */
|
|
276
|
+
function readPrefix(filePath: string): string {
|
|
277
|
+
const fd = fs.openSync(filePath, 'r');
|
|
278
|
+
try {
|
|
279
|
+
const buf = Buffer.alloc(PREFIX_BYTES);
|
|
280
|
+
const n = fs.readSync(fd, buf, 0, PREFIX_BYTES, 0);
|
|
281
|
+
const text = buf.toString('utf-8', 0, n);
|
|
282
|
+
const lastNl = text.lastIndexOf('\n');
|
|
283
|
+
return lastNl === -1 ? text : text.slice(0, lastNl);
|
|
284
|
+
} finally {
|
|
285
|
+
fs.closeSync(fd);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Read a Qwen session file, bounded to the tail for oversized files to cap memory. */
|
|
290
|
+
function readQwenTextBounded(filePath: string): string {
|
|
291
|
+
const size = fs.statSync(filePath).size;
|
|
292
|
+
if (size <= MAX_QWEN_FILE_BYTES) {
|
|
293
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
294
|
+
}
|
|
295
|
+
// Oversized: read only the trailing window (most recent messages), dropping the
|
|
296
|
+
// first (likely partial) line so JSON.parse never sees a fragment.
|
|
297
|
+
const fd = fs.openSync(filePath, 'r');
|
|
298
|
+
try {
|
|
299
|
+
const buf = Buffer.alloc(MAX_QWEN_FILE_BYTES);
|
|
300
|
+
const n = fs.readSync(fd, buf, 0, MAX_QWEN_FILE_BYTES, size - MAX_QWEN_FILE_BYTES);
|
|
301
|
+
const tail = buf.toString('utf-8', 0, n);
|
|
302
|
+
return tail.slice(tail.indexOf('\n') + 1);
|
|
303
|
+
} finally {
|
|
304
|
+
fs.closeSync(fd);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function shortHash(input: string): string {
|
|
309
|
+
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
|
|
310
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registry — the single place that knows every AI-agent provider.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by wesm/agentsview's AgentDef registry: instead of branching on
|
|
5
|
+
* `command === 'claude'` across ~14 call sites, the session API, the connect
|
|
6
|
+
* route, and the terminal allowlist all derive from this list. Adding a
|
|
7
|
+
* provider is one entry here (+ its reader) rather than edits everywhere.
|
|
8
|
+
*
|
|
9
|
+
* Detection (process-detector) and live watching (server.ts adapter) stay
|
|
10
|
+
* provider-specific because their mechanisms differ too much to unify cheaply.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ConversationMessage, ProjectGroup, ProviderDef } from '$lib/types';
|
|
14
|
+
|
|
15
|
+
import { getCodexConversation, listCodexProjects } from './codex-reader';
|
|
16
|
+
import { getGeminiConversation, listGeminiProjects } from './gemini-reader';
|
|
17
|
+
import { getSessionConversation, listProjectsWithSessions } from './jsonl-reader';
|
|
18
|
+
import { getOpenCodeConversation, listOpenCodeProjects } from './opencode-reader';
|
|
19
|
+
import { getQwenConversation, listQwenProjects } from './qwen-reader';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Every AI-agent provider, in merge order. `claude-code` MUST stay first: its
|
|
23
|
+
* projects seed the merge map so the canonical (decoded) project path/name wins
|
|
24
|
+
* over other providers' guesses.
|
|
25
|
+
*/
|
|
26
|
+
export const PROVIDERS: ProviderDef[] = [
|
|
27
|
+
{
|
|
28
|
+
command: 'claude',
|
|
29
|
+
getConversation: (id, offset, limit) => getSessionConversation(id, offset, limit),
|
|
30
|
+
isAI: true,
|
|
31
|
+
label: 'Claude Code',
|
|
32
|
+
listProjects: listProjectsWithSessions,
|
|
33
|
+
resumeArgs: (id) => ['--resume', id],
|
|
34
|
+
source: 'claude-code',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
command: 'opencode',
|
|
38
|
+
getConversation: getOpenCodeConversation,
|
|
39
|
+
isAI: true,
|
|
40
|
+
label: 'OpenCode',
|
|
41
|
+
listProjects: listOpenCodeProjects,
|
|
42
|
+
nameSuffix: ' (OpenCode)',
|
|
43
|
+
resumeArgs: (id) => ['--session', id],
|
|
44
|
+
source: 'opencode',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
command: 'codex',
|
|
48
|
+
getConversation: getCodexConversation,
|
|
49
|
+
isAI: true,
|
|
50
|
+
label: 'Codex',
|
|
51
|
+
listProjects: listCodexProjects,
|
|
52
|
+
resumeArgs: (id) => ['resume', id],
|
|
53
|
+
source: 'codex',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
command: 'gemini',
|
|
57
|
+
getConversation: getGeminiConversation,
|
|
58
|
+
isAI: true,
|
|
59
|
+
label: 'Gemini',
|
|
60
|
+
listProjects: listGeminiProjects,
|
|
61
|
+
resumeArgs: () => [], // Gemini CLI has no session-resume flag
|
|
62
|
+
source: 'gemini',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
command: 'qwen',
|
|
66
|
+
getConversation: getQwenConversation,
|
|
67
|
+
isAI: true,
|
|
68
|
+
label: 'Qwen',
|
|
69
|
+
listProjects: listQwenProjects,
|
|
70
|
+
resumeArgs: () => [],
|
|
71
|
+
source: 'qwen',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
/** AI-agent binary names (for AI_COMMANDS-style checks). */
|
|
76
|
+
export const AI_COMMANDS: string[] = PROVIDERS.filter((p) => p.isAI).map((p) => p.command);
|
|
77
|
+
|
|
78
|
+
/** All provider binary names (for the terminal allowlist + connect validation). */
|
|
79
|
+
export const PROVIDER_COMMANDS: string[] = PROVIDERS.map((p) => p.command);
|
|
80
|
+
|
|
81
|
+
/** Resolve a session's conversation across providers (Claude first, with its project dir). */
|
|
82
|
+
export function getProviderConversation(
|
|
83
|
+
sessionId: string,
|
|
84
|
+
offset: number,
|
|
85
|
+
limit: number,
|
|
86
|
+
claudeProjectDir?: string
|
|
87
|
+
): ConversationMessage[] {
|
|
88
|
+
const claude = getSessionConversation(sessionId, offset, limit, claudeProjectDir);
|
|
89
|
+
if (claude.length > 0) {
|
|
90
|
+
return claude;
|
|
91
|
+
}
|
|
92
|
+
for (const provider of PROVIDERS) {
|
|
93
|
+
if (provider.source === 'claude-code') {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const messages = provider.getConversation(sessionId, offset, limit);
|
|
97
|
+
if (messages.length > 0) {
|
|
98
|
+
return messages;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Merge every provider's projects, deduplicating by absolute path. */
|
|
105
|
+
export function listAllProviderProjects(): ProjectGroup[] {
|
|
106
|
+
const byPath = new Map<string, ProjectGroup>();
|
|
107
|
+
for (const provider of PROVIDERS) {
|
|
108
|
+
let groups: ProjectGroup[];
|
|
109
|
+
try {
|
|
110
|
+
groups = provider.listProjects();
|
|
111
|
+
} catch {
|
|
112
|
+
continue; // a broken provider must not take down the whole listing
|
|
113
|
+
}
|
|
114
|
+
for (const group of groups) {
|
|
115
|
+
const name = provider.nameSuffix ? group.name.replace(provider.nameSuffix, '') : group.name;
|
|
116
|
+
const existing = byPath.get(group.fullPath);
|
|
117
|
+
if (existing) {
|
|
118
|
+
existing.sessions.push(...group.sessions);
|
|
119
|
+
existing.sessions.sort(
|
|
120
|
+
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
|
|
121
|
+
);
|
|
122
|
+
existing.sessionCount = existing.sessions.length;
|
|
123
|
+
existing.lastModified = existing.sessions[0]?.modified || existing.lastModified;
|
|
124
|
+
} else {
|
|
125
|
+
byPath.set(group.fullPath, { ...group, name });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return [...byPath.values()].sort(
|
|
130
|
+
(a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Resume args for a launched command (e.g. `codex resume <id>`). */
|
|
135
|
+
export function resumeArgsForCommand(command: string, sessionId: string): string[] {
|
|
136
|
+
return PROVIDERS.find((p) => p.command === command)?.resumeArgs(sessionId) ?? [];
|
|
137
|
+
}
|
|
@@ -15,6 +15,7 @@ import { watch as chokidarWatch } from 'chokidar';
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
|
|
17
17
|
import { CodexStreamParser, parseCodexRollout } from '../sessions/codex-parser';
|
|
18
|
+
import { readBoundedRolloutText } from '../sessions/codex-reader';
|
|
18
19
|
|
|
19
20
|
/** Flush the open run after this many ms without a write. */
|
|
20
21
|
const IDLE_FLUSH_MS = 1500;
|
|
@@ -28,7 +29,9 @@ class CodexWatcher {
|
|
|
28
29
|
return [];
|
|
29
30
|
}
|
|
30
31
|
try {
|
|
31
|
-
|
|
32
|
+
// Bounded read — rollout files can be hundreds of MB; this is called on
|
|
33
|
+
// every WS session connection (mirrors getCodexConversation).
|
|
34
|
+
return parseCodexRollout(readBoundedRolloutText(filePath)).messages;
|
|
32
35
|
} catch (error) {
|
|
33
36
|
console.error(`[codex-watcher] Failed to read history for ${filePath}:`, error);
|
|
34
37
|
return [];
|
|
@@ -22,6 +22,8 @@ import type { WebSocket } from 'ws';
|
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
24
24
|
|
|
25
|
+
import { findCodexRolloutById } from '../sessions/codex-reader';
|
|
26
|
+
|
|
25
27
|
// ── Module-level references ──────────────────────────────────────────
|
|
26
28
|
|
|
27
29
|
let _ptyManager: null | PtyManagerLike = null;
|
|
@@ -171,34 +173,36 @@ function conversationToLive(msg: ConversationMessage): ServerMessage[] {
|
|
|
171
173
|
* Returns the absolute path if found, or null.
|
|
172
174
|
*/
|
|
173
175
|
function findJsonlFileForSession(sessionId: string): null | string {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
176
|
+
// Reject anything that could traverse out of the session directories before
|
|
177
|
+
// it is interpolated into a filesystem path.
|
|
178
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
|
|
177
179
|
return null;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
const claudeProjectsDir = path.join(process.env.HOME || '', '.claude', 'projects');
|
|
183
|
+
if (fs.existsSync(claudeProjectsDir)) {
|
|
184
|
+
try {
|
|
185
|
+
for (const dir of fs.readdirSync(claudeProjectsDir)) {
|
|
186
|
+
const fullDir = path.join(claudeProjectsDir, dir);
|
|
187
|
+
try {
|
|
188
|
+
if (!fs.statSync(fullDir).isDirectory()) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
186
192
|
continue;
|
|
187
193
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const jsonlPath = path.join(fullDir, `${sessionId}.jsonl`);
|
|
193
|
-
if (fs.existsSync(jsonlPath)) {
|
|
194
|
-
return jsonlPath;
|
|
194
|
+
const jsonlPath = path.join(fullDir, `${sessionId}.jsonl`);
|
|
195
|
+
if (fs.existsSync(jsonlPath)) {
|
|
196
|
+
return jsonlPath;
|
|
197
|
+
}
|
|
195
198
|
}
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore filesystem errors
|
|
196
201
|
}
|
|
197
|
-
} catch {
|
|
198
|
-
// Ignore filesystem errors
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
|
|
204
|
+
// Fall back to an external Codex session: ~/.codex/sessions/**/rollout-*-<id>.jsonl
|
|
205
|
+
return findCodexRolloutById(sessionId);
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
// ── Helpers ──────────────────────────────────────────────────────────
|
package/src/lib/theme.css
CHANGED
|
@@ -526,6 +526,39 @@
|
|
|
526
526
|
--pill-cursor: inherit;
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
+
/* Additional providers reuse the existing colour families (cosmetic only). */
|
|
530
|
+
.pill-source-qwen,
|
|
531
|
+
.pill-source-iflow {
|
|
532
|
+
--pill-background: var(--ds-red-100);
|
|
533
|
+
--pill-color: var(--ds-red-900);
|
|
534
|
+
--pill-font-size: 10px;
|
|
535
|
+
--pill-padding: 2px 8px;
|
|
536
|
+
--pill-hover-background: var(--ds-red-100);
|
|
537
|
+
--pill-hover-color: var(--ds-red-900);
|
|
538
|
+
--pill-cursor: inherit;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.pill-source-cursor {
|
|
542
|
+
--pill-background: var(--ds-blue-100);
|
|
543
|
+
--pill-color: var(--ds-blue-900);
|
|
544
|
+
--pill-font-size: 10px;
|
|
545
|
+
--pill-padding: 2px 8px;
|
|
546
|
+
--pill-hover-background: var(--ds-blue-100);
|
|
547
|
+
--pill-hover-color: var(--ds-blue-900);
|
|
548
|
+
--pill-cursor: inherit;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.pill-source-copilot,
|
|
552
|
+
.pill-source-amp {
|
|
553
|
+
--pill-background: var(--ds-amber-100);
|
|
554
|
+
--pill-color: var(--ds-amber-900);
|
|
555
|
+
--pill-font-size: 10px;
|
|
556
|
+
--pill-padding: 2px 8px;
|
|
557
|
+
--pill-hover-background: var(--ds-amber-100);
|
|
558
|
+
--pill-hover-color: var(--ds-amber-900);
|
|
559
|
+
--pill-cursor: inherit;
|
|
560
|
+
}
|
|
561
|
+
|
|
529
562
|
/* ===== Icon Sizes ===== */
|
|
530
563
|
.icon-14 {
|
|
531
564
|
--icon-width: 14px;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Gemini CLI session types (hand-written: the on-disk formats are
|
|
2
|
+
// provider-specific and not worth round-tripping through the YAML codegen).
|
|
3
|
+
// Gemini CLI stores user messages in ~/.gemini/tmp/<projectHash>/logs.json
|
|
4
|
+
// and full conversation records in
|
|
5
|
+
// ~/.gemini/tmp/<projectHash>/chats/session-*.json (newer versions only).
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// logs.json — user-messages-only format (all versions)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/** Union of all part shapes that appear in a ConversationRecord message. */
|
|
12
|
+
export type GeminiContentPart = GeminiFunctionCallPart | GeminiTextPart | GeminiThoughtPart;
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// chats/session-*.json — full ConversationRecord (newer versions)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** Full conversation record stored in chats/session-*.json. */
|
|
19
|
+
export interface GeminiConversationRecord {
|
|
20
|
+
directories?: string[];
|
|
21
|
+
kind?: 'main' | 'subagent';
|
|
22
|
+
lastUpdated: string;
|
|
23
|
+
messages: GeminiMessageRecord[];
|
|
24
|
+
projectHash: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
startTime: string;
|
|
27
|
+
summary?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Inline function-call part from the Google GenAI SDK. */
|
|
31
|
+
export interface GeminiFunctionCallPart {
|
|
32
|
+
functionCall: {
|
|
33
|
+
args: Record<string, unknown>;
|
|
34
|
+
id?: string;
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A single entry in ~/.gemini/tmp/<projectHash>/logs.json. */
|
|
40
|
+
export interface GeminiLogEntry {
|
|
41
|
+
message: string;
|
|
42
|
+
messageId: number;
|
|
43
|
+
sessionId: string;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
type: 'user';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A single message record in a full ConversationRecord. */
|
|
49
|
+
export type GeminiMessageRecord =
|
|
50
|
+
| {
|
|
51
|
+
content: GeminiContentPart[] | string;
|
|
52
|
+
id: string;
|
|
53
|
+
thoughts?: GeminiThoughtSummary[];
|
|
54
|
+
timestamp: string;
|
|
55
|
+
toolCalls?: GeminiToolCallRecord[];
|
|
56
|
+
type: 'gemini';
|
|
57
|
+
}
|
|
58
|
+
| {
|
|
59
|
+
content: GeminiContentPart[] | string;
|
|
60
|
+
id: string;
|
|
61
|
+
timestamp: string;
|
|
62
|
+
type: 'error' | 'info' | 'user' | 'warning';
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Contents of ~/.gemini/projects.json (present only in newer gemini-cli). */
|
|
66
|
+
export type GeminiProjectsJson = Record<string, string>;
|
|
67
|
+
|
|
68
|
+
/** Plain-text content part from the Google GenAI SDK. */
|
|
69
|
+
export interface GeminiTextPart {
|
|
70
|
+
text: string;
|
|
71
|
+
thought?: false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Inline thinking/reasoning part from the Google GenAI SDK. */
|
|
75
|
+
export interface GeminiThoughtPart {
|
|
76
|
+
text: string;
|
|
77
|
+
thought: true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A thought-summary entry attached to a 'gemini'-type message. */
|
|
81
|
+
export interface GeminiThoughtSummary {
|
|
82
|
+
summary?: string;
|
|
83
|
+
timestamp: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// projects.json — project slug → absolute path registry (newer versions)
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
/** A single tool-call record attached to a 'gemini'-type message. */
|
|
91
|
+
export interface GeminiToolCallRecord {
|
|
92
|
+
args: Record<string, unknown>;
|
|
93
|
+
description?: string;
|
|
94
|
+
displayName?: string;
|
|
95
|
+
id: string;
|
|
96
|
+
name: string;
|
|
97
|
+
result?: unknown;
|
|
98
|
+
status: 'cancelled' | 'error' | 'pending' | 'success';
|
|
99
|
+
timestamp: string;
|
|
100
|
+
}
|