u1s1-cli 0.3.0 → 0.4.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/README.md +1 -0
- package/dist/brand.js +56 -20
- package/dist/import/claude.js +372 -0
- package/dist/import/codex.js +362 -0
- package/dist/import/index.js +354 -0
- package/dist/import/types.js +1 -0
- package/dist/import/util.js +231 -0
- package/dist/import/write.js +95 -0
- package/dist/index.js +14 -0
- package/dist/login.js +6 -3
- package/dist/style.js +6 -284
- package/dist/themes.js +6 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ u1s1
|
|
|
25
25
|
| `u1s1 usage` | 看额度用了多少 |
|
|
26
26
|
| `u1s1 model` | 看/切默认模型:`u1s1 model grok` 切 Grok 4.6,`u1s1 model deepseek` 切回(对话里 `/model` 同样会记住) |
|
|
27
27
|
| `u1s1 update` | 升级 u1s1 到最新版 |
|
|
28
|
+
| `u1s1 import` | 从 Claude Code / Codex 导入历史对话,之后 `/resume` 就能接着聊 |
|
|
28
29
|
| `u1s1 login` / `u1s1 logout` | 登录 / 退出 |
|
|
29
30
|
|
|
30
31
|
## 有一说一
|
package/dist/brand.js
CHANGED
|
@@ -14,28 +14,64 @@ export function formatHomePath(path) {
|
|
|
14
14
|
}
|
|
15
15
|
return path;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
theme.fg("accent"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
// "u1s1" in FIGlet ANSI Shadow. Rows are joined per glyph so widths stay aligned.
|
|
18
|
+
const GLYPH_U = ["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "];
|
|
19
|
+
const GLYPH_1 = [" ██╗", "███║", "╚██║", " ██║", " ██║", " ╚═╝"];
|
|
20
|
+
const GLYPH_S = ["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"];
|
|
21
|
+
export const HERO_ART = GLYPH_U.map((_, r) => [GLYPH_U[r], GLYPH_1[r], GLYPH_S[r], GLYPH_1[r]].join(" "));
|
|
22
|
+
const ART_WIDTH = HERO_ART[0].length;
|
|
23
|
+
/** Two-tone wordmark: solid blocks in accent, box-drawing "shadow" in dim. */
|
|
24
|
+
function paintArt(theme, line) {
|
|
25
|
+
let out = "";
|
|
26
|
+
let run = "";
|
|
27
|
+
let runIsBlock = null;
|
|
28
|
+
const flush = () => {
|
|
29
|
+
if (!run)
|
|
30
|
+
return;
|
|
31
|
+
out += runIsBlock === null ? run : theme.fg(runIsBlock ? "accent" : "dim", run);
|
|
32
|
+
run = "";
|
|
33
|
+
};
|
|
34
|
+
for (const ch of line) {
|
|
35
|
+
const kind = ch === " " ? null : ch === "█" ? true : false;
|
|
36
|
+
if (kind !== runIsBlock) {
|
|
37
|
+
flush();
|
|
38
|
+
runIsBlock = kind;
|
|
39
|
+
}
|
|
40
|
+
run += ch;
|
|
41
|
+
}
|
|
42
|
+
flush();
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Startup hero, responsive to terminal width:
|
|
47
|
+
* wide → wordmark with info column beside it; medium → stacked; narrow → one-liner.
|
|
48
|
+
*/
|
|
49
|
+
export function renderBrandHeader(theme, version, cwd, width) {
|
|
50
|
+
const name = theme.bold(theme.fg("text", `${BRAND_NAME} v${version}`));
|
|
51
|
+
const brand = theme.fg("muted", `${BRAND_CN} · ${BRAND_TAGLINE}`);
|
|
52
|
+
const dir = theme.fg("dim", `cwd: ${formatHomePath(cwd)}`);
|
|
53
|
+
const hints = theme.fg("dim", "/help 看命令 · Shift+Enter 换行 · Esc 中断");
|
|
54
|
+
if (width < ART_WIDTH + 4) {
|
|
55
|
+
return ["", ` ${theme.fg("accent", "✻")} ${name} ${theme.fg("muted", BRAND_CN)}`, ` ${dir}`, ""];
|
|
56
|
+
}
|
|
57
|
+
const art = HERO_ART.map((line) => ` ${paintArt(theme, line)}`);
|
|
58
|
+
// widest info row (hints) needs 46 cols beside the 28-col wordmark
|
|
59
|
+
if (width < ART_WIDTH + 46) {
|
|
60
|
+
return ["", ...art, "", ` ${name} ${brand}`, ` ${dir}`, ""];
|
|
61
|
+
}
|
|
62
|
+
const rows = [...art];
|
|
63
|
+
const gap = " ";
|
|
64
|
+
rows[1] += `${gap}${name}`;
|
|
65
|
+
rows[2] += `${gap}${brand}`;
|
|
66
|
+
rows[3] += `${gap}${dir}`;
|
|
67
|
+
rows[4] += `${gap}${hints}`;
|
|
68
|
+
return ["", ...rows, ""];
|
|
35
69
|
}
|
|
36
70
|
export function printConsoleBanner(version) {
|
|
37
71
|
console.log("");
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
for (const line of HERO_ART)
|
|
73
|
+
console.log(` ${line}`);
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log(` ${BRAND_NAME} v${version} — ${BRAND_CN},${BRAND_TAGLINE}`);
|
|
40
76
|
console.log("");
|
|
41
77
|
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { createReadStream, existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { asRecord, asString, encodeClaudeProjectDir, fileMtimeMs, firstMeaningfulLine, listHomeClaudeDirs, MAX_TEXT_CHARS, MAX_TOOL_RESULT_CHARS, oneLine, parseJsonLine, parseTime, projectAncestors, projectRoot, truncateText, uniqueExistingDirs, } from "./util.js";
|
|
6
|
+
const CLAUDE_TOOL_MAP = {
|
|
7
|
+
bash: "bash",
|
|
8
|
+
read: "read",
|
|
9
|
+
edit: "edit",
|
|
10
|
+
write: "write",
|
|
11
|
+
glob: "find",
|
|
12
|
+
grep: "grep",
|
|
13
|
+
ls: "ls",
|
|
14
|
+
};
|
|
15
|
+
function claudeHomes() {
|
|
16
|
+
return uniqueExistingDirs([
|
|
17
|
+
process.env["CLAUDE_CONFIG_DIR"],
|
|
18
|
+
...listHomeClaudeDirs(),
|
|
19
|
+
join(homedir(), ".claude"),
|
|
20
|
+
]);
|
|
21
|
+
}
|
|
22
|
+
function decodeProjectDirName(name) {
|
|
23
|
+
if (!name.startsWith("-"))
|
|
24
|
+
return undefined;
|
|
25
|
+
return `/${name.slice(1).replace(/-/g, "/")}`;
|
|
26
|
+
}
|
|
27
|
+
function claudeDirMatchesCwd(dirName, cwd) {
|
|
28
|
+
const encoded = new Set(projectAncestors(cwd).map(encodeClaudeProjectDir));
|
|
29
|
+
if (encoded.has(dirName))
|
|
30
|
+
return true;
|
|
31
|
+
const root = projectRoot(cwd);
|
|
32
|
+
if (!root)
|
|
33
|
+
return false;
|
|
34
|
+
// sessions that happened in a subfolder of this repo
|
|
35
|
+
return dirName.startsWith(`${encodeClaudeProjectDir(root)}-`);
|
|
36
|
+
}
|
|
37
|
+
function extractCwd(obj) {
|
|
38
|
+
return asString(obj["cwd"]);
|
|
39
|
+
}
|
|
40
|
+
function extractTitle(obj) {
|
|
41
|
+
return asString(obj["aiTitle"]) ?? asString(obj["title"]);
|
|
42
|
+
}
|
|
43
|
+
function isToolResultContent(content) {
|
|
44
|
+
if (!Array.isArray(content))
|
|
45
|
+
return false;
|
|
46
|
+
return content.some((block) => asRecord(block)?.["type"] === "tool_result");
|
|
47
|
+
}
|
|
48
|
+
function flattenClaudeContent(content) {
|
|
49
|
+
if (typeof content === "string")
|
|
50
|
+
return content;
|
|
51
|
+
if (!Array.isArray(content))
|
|
52
|
+
return "";
|
|
53
|
+
const parts = [];
|
|
54
|
+
for (const raw of content) {
|
|
55
|
+
const block = asRecord(raw);
|
|
56
|
+
if (!block)
|
|
57
|
+
continue;
|
|
58
|
+
const type = asString(block["type"]);
|
|
59
|
+
if (type === "text" || type === "input_text" || type === "output_text") {
|
|
60
|
+
const text = asString(block["text"]);
|
|
61
|
+
if (text)
|
|
62
|
+
parts.push(text);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return parts.join("\n");
|
|
66
|
+
}
|
|
67
|
+
function flattenToolResult(content) {
|
|
68
|
+
if (typeof content === "string")
|
|
69
|
+
return content;
|
|
70
|
+
if (!Array.isArray(content)) {
|
|
71
|
+
if (content == null)
|
|
72
|
+
return "";
|
|
73
|
+
try {
|
|
74
|
+
return JSON.stringify(content);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return String(content);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const parts = [];
|
|
81
|
+
for (const raw of content) {
|
|
82
|
+
if (typeof raw === "string") {
|
|
83
|
+
parts.push(raw);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const block = asRecord(raw);
|
|
87
|
+
if (!block)
|
|
88
|
+
continue;
|
|
89
|
+
const text = asString(block["text"]) ?? asString(block["content"]);
|
|
90
|
+
if (text)
|
|
91
|
+
parts.push(text);
|
|
92
|
+
}
|
|
93
|
+
return parts.join("\n");
|
|
94
|
+
}
|
|
95
|
+
function mapToolName(name) {
|
|
96
|
+
return CLAUDE_TOOL_MAP[name.toLowerCase()] ?? name;
|
|
97
|
+
}
|
|
98
|
+
function omit(obj, keys) {
|
|
99
|
+
const skip = new Set(keys);
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
102
|
+
if (!skip.has(k))
|
|
103
|
+
out[k] = v;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function remapToolArgs(name, input) {
|
|
108
|
+
const mapped = mapToolName(name);
|
|
109
|
+
if (mapped === "read") {
|
|
110
|
+
const path = asString(input["file_path"]) ?? asString(input["path"]);
|
|
111
|
+
return path ? { path, ...omit(input, ["file_path", "path"]) } : input;
|
|
112
|
+
}
|
|
113
|
+
if (mapped === "write") {
|
|
114
|
+
const path = asString(input["file_path"]) ?? asString(input["path"]);
|
|
115
|
+
const content = asString(input["content"]) ?? asString(input["contents"]);
|
|
116
|
+
return {
|
|
117
|
+
...(path ? { path } : {}),
|
|
118
|
+
...(content !== undefined ? { content } : {}),
|
|
119
|
+
...omit(input, ["file_path", "path", "content", "contents"]),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (mapped === "edit") {
|
|
123
|
+
const path = asString(input["file_path"]) ?? asString(input["path"]);
|
|
124
|
+
return path ? { path, ...omit(input, ["file_path", "path"]) } : input;
|
|
125
|
+
}
|
|
126
|
+
if (mapped === "find") {
|
|
127
|
+
const pattern = asString(input["pattern"]) ?? asString(input["glob"]);
|
|
128
|
+
return pattern ? { pattern, ...omit(input, ["pattern", "glob"]) } : input;
|
|
129
|
+
}
|
|
130
|
+
return input;
|
|
131
|
+
}
|
|
132
|
+
function usageFromClaude(raw) {
|
|
133
|
+
const usage = asRecord(raw);
|
|
134
|
+
if (!usage)
|
|
135
|
+
return undefined;
|
|
136
|
+
const input = Number(usage["input_tokens"] ?? 0);
|
|
137
|
+
const output = Number(usage["output_tokens"] ?? 0);
|
|
138
|
+
const cacheRead = Number(usage["cache_read_input_tokens"] ?? 0);
|
|
139
|
+
const cacheWrite = Number(usage["cache_creation_input_tokens"] ?? 0);
|
|
140
|
+
if (![input, output, cacheRead, cacheWrite].some((n) => n > 0))
|
|
141
|
+
return undefined;
|
|
142
|
+
return { input, output, cacheRead, cacheWrite };
|
|
143
|
+
}
|
|
144
|
+
function firstUserPreview(obj) {
|
|
145
|
+
if (obj["type"] !== "user" || obj["isSidechain"] === true)
|
|
146
|
+
return undefined;
|
|
147
|
+
const msg = asRecord(obj["message"]);
|
|
148
|
+
if (!msg || isToolResultContent(msg["content"]))
|
|
149
|
+
return undefined;
|
|
150
|
+
const text = flattenClaudeContent(msg["content"]).trim();
|
|
151
|
+
if (!text)
|
|
152
|
+
return undefined;
|
|
153
|
+
return oneLine(firstMeaningfulLine(text) || text);
|
|
154
|
+
}
|
|
155
|
+
export async function hydrateClaudeSession(session) {
|
|
156
|
+
const stream = createReadStream(session.sourcePath, { encoding: "utf8" });
|
|
157
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
158
|
+
let cwd = "";
|
|
159
|
+
let title = session.title;
|
|
160
|
+
let sourceId = session.sourceId;
|
|
161
|
+
let startedAt = session.startedAt;
|
|
162
|
+
let preview;
|
|
163
|
+
let lines = 0;
|
|
164
|
+
try {
|
|
165
|
+
for await (const line of rl) {
|
|
166
|
+
lines += 1;
|
|
167
|
+
const obj = parseJsonLine(line);
|
|
168
|
+
if (!obj)
|
|
169
|
+
continue;
|
|
170
|
+
if (!sourceId)
|
|
171
|
+
sourceId = asString(obj["sessionId"]) ?? sourceId;
|
|
172
|
+
if (!cwd)
|
|
173
|
+
cwd = extractCwd(obj) ?? "";
|
|
174
|
+
if (!title)
|
|
175
|
+
title = extractTitle(obj) ?? title;
|
|
176
|
+
if (!startedAt)
|
|
177
|
+
startedAt = parseTime(obj["timestamp"]);
|
|
178
|
+
if (!preview)
|
|
179
|
+
preview = firstUserPreview(obj);
|
|
180
|
+
if (cwd && (title || preview) && sourceId && lines > 80)
|
|
181
|
+
break;
|
|
182
|
+
if (lines > 400)
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
rl.close();
|
|
188
|
+
stream.destroy();
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...session,
|
|
192
|
+
sourceId: sourceId || session.sourceId,
|
|
193
|
+
cwd: cwd || session.cwd,
|
|
194
|
+
title: title || preview || session.title,
|
|
195
|
+
startedAt: startedAt ?? session.startedAt,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
export const claudeAdapter = {
|
|
199
|
+
id: "claude",
|
|
200
|
+
label: "Claude Code",
|
|
201
|
+
discover(opts) {
|
|
202
|
+
const wanted = opts.cwd;
|
|
203
|
+
const seen = new Set();
|
|
204
|
+
const found = [];
|
|
205
|
+
for (const home of claudeHomes()) {
|
|
206
|
+
const projectsRoot = join(home, "projects");
|
|
207
|
+
if (!existsSync(projectsRoot))
|
|
208
|
+
continue;
|
|
209
|
+
let projectDirs = [];
|
|
210
|
+
try {
|
|
211
|
+
projectDirs = readdirSync(projectsRoot)
|
|
212
|
+
.map((name) => join(projectsRoot, name))
|
|
213
|
+
.filter((dir) => {
|
|
214
|
+
try {
|
|
215
|
+
return statSync(dir).isDirectory();
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
for (const projectDir of projectDirs) {
|
|
226
|
+
const dirName = basename(projectDir);
|
|
227
|
+
if (wanted && !claudeDirMatchesCwd(dirName, wanted))
|
|
228
|
+
continue;
|
|
229
|
+
let files = [];
|
|
230
|
+
try {
|
|
231
|
+
files = readdirSync(projectDir)
|
|
232
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
233
|
+
.map((name) => join(projectDir, name));
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
let real = file;
|
|
240
|
+
try {
|
|
241
|
+
real = realpathSync(file);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// keep original
|
|
245
|
+
}
|
|
246
|
+
if (seen.has(real))
|
|
247
|
+
continue;
|
|
248
|
+
seen.add(real);
|
|
249
|
+
found.push({
|
|
250
|
+
source: "claude",
|
|
251
|
+
sourceId: basename(file, ".jsonl"),
|
|
252
|
+
sourcePath: real,
|
|
253
|
+
cwd: wanted ?? decodeProjectDirName(dirName) ?? "",
|
|
254
|
+
mtimeMs: fileMtimeMs(real),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return found;
|
|
260
|
+
},
|
|
261
|
+
convert(session) {
|
|
262
|
+
let text = "";
|
|
263
|
+
try {
|
|
264
|
+
// Large Claude transcripts can be tens of MB; still fine as a one-shot import.
|
|
265
|
+
text = readFileSync(session.sourcePath, "utf8");
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return { cwd: session.cwd || process.cwd(), title: session.title, messages: [] };
|
|
269
|
+
}
|
|
270
|
+
const messages = [];
|
|
271
|
+
let cwd = "";
|
|
272
|
+
let title = session.title;
|
|
273
|
+
const pendingTools = new Map();
|
|
274
|
+
for (const line of text.split(/\r?\n/)) {
|
|
275
|
+
if (line.length > 8_000_000)
|
|
276
|
+
continue;
|
|
277
|
+
const obj = parseJsonLine(line);
|
|
278
|
+
if (!obj)
|
|
279
|
+
continue;
|
|
280
|
+
const type = asString(obj["type"]);
|
|
281
|
+
if (!cwd)
|
|
282
|
+
cwd = extractCwd(obj) ?? cwd;
|
|
283
|
+
if (!title && type === "ai-title")
|
|
284
|
+
title = extractTitle(obj) ?? title;
|
|
285
|
+
if (obj["isSidechain"] === true)
|
|
286
|
+
continue;
|
|
287
|
+
if (type === "user") {
|
|
288
|
+
const msg = asRecord(obj["message"]);
|
|
289
|
+
if (!msg)
|
|
290
|
+
continue;
|
|
291
|
+
const content = msg["content"];
|
|
292
|
+
const ts = parseTime(obj["timestamp"]) ?? Date.now();
|
|
293
|
+
if (isToolResultContent(content) && Array.isArray(content)) {
|
|
294
|
+
for (const raw of content) {
|
|
295
|
+
const block = asRecord(raw);
|
|
296
|
+
if (!block || block["type"] !== "tool_result")
|
|
297
|
+
continue;
|
|
298
|
+
const callId = asString(block["tool_use_id"]) ?? "";
|
|
299
|
+
if (!callId)
|
|
300
|
+
continue;
|
|
301
|
+
messages.push({
|
|
302
|
+
role: "toolResult",
|
|
303
|
+
toolCallId: callId,
|
|
304
|
+
toolName: pendingTools.get(callId) ?? "unknown",
|
|
305
|
+
text: truncateText(flattenToolResult(block["content"]), MAX_TOOL_RESULT_CHARS),
|
|
306
|
+
isError: block["is_error"] === true,
|
|
307
|
+
timestamp: ts,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const userText = truncateText(flattenClaudeContent(content), MAX_TEXT_CHARS).trim();
|
|
313
|
+
if (!userText)
|
|
314
|
+
continue;
|
|
315
|
+
if (!title)
|
|
316
|
+
title = oneLine(firstMeaningfulLine(userText) || userText);
|
|
317
|
+
messages.push({ role: "user", text: userText, timestamp: ts });
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (type !== "assistant")
|
|
321
|
+
continue;
|
|
322
|
+
const msg = asRecord(obj["message"]);
|
|
323
|
+
if (!msg)
|
|
324
|
+
continue;
|
|
325
|
+
const ts = parseTime(obj["timestamp"]) ?? Date.now();
|
|
326
|
+
const blocks = Array.isArray(msg["content"]) ? msg["content"] : [];
|
|
327
|
+
const content = [];
|
|
328
|
+
let sawTool = false;
|
|
329
|
+
for (const raw of blocks) {
|
|
330
|
+
const block = asRecord(raw);
|
|
331
|
+
if (!block)
|
|
332
|
+
continue;
|
|
333
|
+
const btype = asString(block["type"]);
|
|
334
|
+
if (btype === "thinking") {
|
|
335
|
+
const thinking = asString(block["thinking"]) ?? "";
|
|
336
|
+
if (thinking.trim())
|
|
337
|
+
content.push({ type: "thinking", thinking: truncateText(thinking, MAX_TEXT_CHARS) });
|
|
338
|
+
}
|
|
339
|
+
else if (btype === "text") {
|
|
340
|
+
const textBlock = asString(block["text"]) ?? "";
|
|
341
|
+
if (textBlock.trim())
|
|
342
|
+
content.push({ type: "text", text: truncateText(textBlock, MAX_TEXT_CHARS) });
|
|
343
|
+
}
|
|
344
|
+
else if (btype === "tool_use") {
|
|
345
|
+
const id = asString(block["id"]) ?? "";
|
|
346
|
+
const name = asString(block["name"]) ?? "unknown";
|
|
347
|
+
if (!id)
|
|
348
|
+
continue;
|
|
349
|
+
const input = asRecord(block["input"]) ?? {};
|
|
350
|
+
const mapped = mapToolName(name);
|
|
351
|
+
pendingTools.set(id, mapped);
|
|
352
|
+
content.push({ type: "toolCall", id, name: mapped, arguments: remapToolArgs(name, input) });
|
|
353
|
+
sawTool = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (content.length === 0)
|
|
357
|
+
continue;
|
|
358
|
+
const stop = asString(msg["stop_reason"]);
|
|
359
|
+
messages.push({
|
|
360
|
+
role: "assistant",
|
|
361
|
+
content,
|
|
362
|
+
provider: "anthropic",
|
|
363
|
+
model: asString(msg["model"]) ?? "claude",
|
|
364
|
+
api: "anthropic-messages",
|
|
365
|
+
stopReason: sawTool || stop === "tool_use" ? "toolUse" : stop === "max_tokens" ? "length" : "stop",
|
|
366
|
+
usage: usageFromClaude(msg["usage"]),
|
|
367
|
+
timestamp: ts,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return { cwd: cwd || session.cwd || process.cwd(), title, messages };
|
|
371
|
+
},
|
|
372
|
+
};
|