u1s1-cli 1.2.2 → 1.2.4

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/dist/config.js CHANGED
@@ -28,16 +28,44 @@ function defaultAliases(id) {
28
28
  const aliases = [short, ...parts.filter((p) => p.length > 1)];
29
29
  return [...new Set(aliases)];
30
30
  }
31
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
32
+ const THINKING_LEVEL_SET = new Set(THINKING_LEVELS);
33
+ function parseThinkingCapabilities(value) {
34
+ if (!value || !Array.isArray(value.levels))
35
+ return undefined;
36
+ const levels = [...new Set(value.levels)].filter((level) => THINKING_LEVEL_SET.has(level));
37
+ if (levels.length === 0 || !THINKING_LEVEL_SET.has(value.default_level) || !levels.includes(value.default_level)) {
38
+ return undefined;
39
+ }
40
+ const requestFormat = value.request_format;
41
+ if (requestFormat !== "openai" && requestFormat !== "deepseek" && requestFormat !== "qwen")
42
+ return undefined;
43
+ const levelMap = {};
44
+ for (const level of levels) {
45
+ const mapped = value.level_map?.[level];
46
+ if (typeof mapped === "string" && mapped)
47
+ levelMap[level] = mapped;
48
+ }
49
+ return {
50
+ levels,
51
+ defaultLevel: value.default_level,
52
+ canDisable: value.can_disable === true && levels.includes("off"),
53
+ levelMap,
54
+ requestFormat,
55
+ };
56
+ }
31
57
  /**
32
58
  * Convert an API model response to our internal ModelDef.
33
59
  * Aliases are derived from the short id; note is left empty (filled by /model command).
34
60
  */
35
61
  export function apiModelToDef(m) {
62
+ const thinking = parseThinkingCapabilities(m.thinking);
36
63
  return {
37
64
  id: m.id,
38
65
  name: m.name,
39
66
  aliases: defaultAliases(m.id),
40
- reasoning: m.reasoning,
67
+ reasoning: m.reasoning || thinking !== undefined,
68
+ thinking,
41
69
  vision: m.vision === true,
42
70
  contextWindow: m.context_length,
43
71
  maxTokens: m.max_tokens,
@@ -55,7 +83,14 @@ export const MODELS = [
55
83
  id: "deepseek-v4-flash",
56
84
  name: "DeepSeek V4 Flash (u1s1)",
57
85
  aliases: ["deepseek", "flash", "v4-flash"],
58
- reasoning: false,
86
+ reasoning: true,
87
+ thinking: {
88
+ levels: ["off", "low", "high", "max"],
89
+ defaultLevel: "high",
90
+ canDisable: true,
91
+ levelMap: { off: "none", low: "low", high: "high", max: "max" },
92
+ requestFormat: "deepseek",
93
+ },
59
94
  vision: false,
60
95
  contextWindow: 1_048_576,
61
96
  maxTokens: 384_000,
package/dist/deploy.js CHANGED
@@ -123,13 +123,13 @@ export function parseDeployArgs(args) {
123
123
  }
124
124
  return parsed;
125
125
  }
126
- async function api(cfg, method, path, body) {
126
+ async function api(cfg, request) {
127
127
  let resp;
128
128
  try {
129
- resp = await authorizedFetch(cfg, `${cfg.baseUrl}${path}`, {
130
- method,
129
+ resp = await authorizedFetch(cfg, `${cfg.baseUrl}${request.path}`, {
130
+ method: request.method,
131
131
  headers: { "x-u1s1-version": VERSION, "content-type": "application/json" },
132
- body: body === undefined ? undefined : JSON.stringify(body),
132
+ body: request.body === undefined ? undefined : JSON.stringify(request.body),
133
133
  signal: AbortSignal.timeout(30_000),
134
134
  });
135
135
  }
@@ -208,35 +208,30 @@ async function promptVisibility() {
208
208
  rl.close();
209
209
  }
210
210
  }
211
- export async function deployCommand(cfg, args) {
212
- // 参数:[dir] [--name xxx] [--public|--private];u1s1 deploy list 列出已有站点
213
- if (args[0] === "list") {
214
- const { sites } = await api(cfg, "GET", "/deploy/sites");
215
- if (!sites.length) {
216
- console.log(" 还没有部署过站点。在网页目录里跑 u1s1 deploy 试试。");
217
- return;
218
- }
219
- // 服务端给的是 UTC 时间戳,转成本地时间展示
220
- const fmtTime = (ts) => {
221
- const d = new Date(ts.includes("T") ? ts : `${ts.replace(" ", "T")}Z`);
222
- return Number.isNaN(d.getTime()) ? ts : d.toLocaleString("zh-CN", { hour12: false });
223
- };
224
- console.log("");
225
- for (const s of sites) {
226
- const visibility = s.visibility === "private" ? "私密" : s.community_listed ? "公开(社区)" : "公开(未展示)";
227
- console.log(` ${s.deployed ? "●" : "○"} ${s.url} ${visibility} · ${fmtBytes(s.total_bytes)} · ${s.updated_at} UTC`);
228
- }
229
- console.log("");
211
+ async function listDeployments(cfg) {
212
+ const { sites } = await api(cfg, { method: "GET", path: "/deploy/sites" });
213
+ if (!sites.length) {
214
+ console.log(" 还没有部署过站点。在网页目录里跑 u1s1 deploy 试试。");
230
215
  return;
231
216
  }
217
+ console.log("");
218
+ for (const site of sites) {
219
+ const visibility = site.visibility === "private"
220
+ ? "私密"
221
+ : site.community_listed ? "公开(社区)" : "公开(未展示)";
222
+ console.log(` ${site.deployed ? "●" : "○"} ${site.url} ${visibility} · ${fmtBytes(site.total_bytes)} · ${site.updated_at} UTC`);
223
+ }
224
+ console.log("");
225
+ }
226
+ async function selectDeployment(args) {
232
227
  const parsed = parseDeployArgs(args);
233
228
  let { name, dirArg, visibility } = parsed;
234
229
  const dir = resolveSiteDir(dirArg);
235
230
  const files = collectFiles(dir);
236
- if (!files.some((f) => f.path === "index.html")) {
231
+ if (!files.some((file) => file.path === "index.html")) {
237
232
  throw new Error(`${dir} 里没有 index.html,网站需要一个首页`);
238
233
  }
239
- const totalBytes = files.reduce((s, f) => s + f.bytes, 0);
234
+ const totalBytes = files.reduce((sum, file) => sum + file.bytes, 0);
240
235
  console.log("");
241
236
  console.log(` 部署目录 ${dir}`);
242
237
  console.log(` 文件 ${files.length} 个,共 ${fmtBytes(totalBytes)}`);
@@ -252,55 +247,61 @@ export async function deployCommand(cfg, args) {
252
247
  // 取更安全的 private,用户仍可用 --public 明确公开。
253
248
  if (!visibility && remembered !== name)
254
249
  visibility = await promptVisibility();
255
- let start;
256
- for (let attempt = 0; !start; attempt++) {
250
+ return { dir, files, name, totalBytes, visibility };
251
+ }
252
+ async function startDeployment(cfg, initialName) {
253
+ let name = initialName;
254
+ for (let attempt = 0;; attempt++) {
257
255
  try {
258
- start = await api(cfg, "POST", "/deploy/start", { site: name });
256
+ const start = await api(cfg, {
257
+ method: "POST",
258
+ path: "/deploy/start",
259
+ body: { site: name },
260
+ });
261
+ return { name, start };
259
262
  }
260
- catch (e) {
261
- const code = e.code;
263
+ catch (error) {
264
+ const code = error.code;
262
265
  const retriable = code === "site_name_taken" || code === "invalid_site_name";
263
266
  if (!retriable || attempt >= 3)
264
- throw e;
265
- console.log(` ${e.message}`);
267
+ throw error;
268
+ console.log(` ${error.message}`);
269
+ const suggested = `${name.slice(0, 25)}-${Math.random().toString(36).slice(2, 6)}`;
266
270
  if (!process.stdin.isTTY) {
267
271
  // 非交互环境自动加后缀重试一次
268
272
  if (attempt > 0)
269
- throw e;
270
- name = `${name.slice(0, 25)}-${Math.random().toString(36).slice(2, 6)}`;
273
+ throw error;
274
+ name = suggested;
271
275
  }
272
276
  else {
273
- name = await promptSiteName(`${name.slice(0, 25)}-${Math.random().toString(36).slice(2, 6)}`);
277
+ name = await promptSiteName(suggested);
274
278
  }
275
279
  }
276
280
  }
277
- const tooBig = files.filter((f) => f.bytes > start.limits.max_file_bytes);
281
+ }
282
+ function validateDeploymentLimits(start, files, totalBytes) {
283
+ const tooBig = files.filter((file) => file.bytes > start.limits.max_file_bytes);
278
284
  if (tooBig.length) {
279
285
  throw new Error(`这些文件超过单文件上限 ${fmtBytes(start.limits.max_file_bytes)}:\n` +
280
- tooBig.map((f) => ` ${f.path}(${fmtBytes(f.bytes)})`).join("\n"));
286
+ tooBig.map((file) => ` ${file.path}(${fmtBytes(file.bytes)})`).join("\n"));
281
287
  }
282
288
  if (files.length > start.limits.max_files || totalBytes > start.limits.max_total_bytes) {
283
289
  throw new Error(`超出配额:最多 ${start.limits.max_files} 个文件 / ${fmtBytes(start.limits.max_total_bytes)}。` +
284
290
  `当前 ${files.length} 个 / ${fmtBytes(totalBytes)}`);
285
291
  }
286
- await uploadAll(cfg, start, files);
287
- const fin = await api(cfg, "POST", "/deploy/finish", {
288
- site: start.site,
289
- deploy_id: start.deploy_id,
290
- visibility,
291
- });
292
- rememberSite(dir, start.slug || name || start.site);
293
- console.log(` ✅ 部署完成,${fin.file_count} 个文件已上线`);
292
+ }
293
+ function printDeploymentResult(result, start) {
294
+ console.log(` ✅ 部署完成,${result.file_count} 个文件已上线`);
294
295
  console.log("");
295
- console.log(` ${fin.visibility === "private" ? "🔒" : "🌐"} ${fin.url}`);
296
+ console.log(` ${result.visibility === "private" ? "🔒" : "🌐"} ${result.url}`);
296
297
  console.log("");
297
- if ((fin.hostname || start.hostname || "").split(".").length > 3) {
298
+ if ((result.hostname || start.hostname || "").split(".").length > 3) {
298
299
  console.log(" 新 hostname 的 HTTPS 证书会由 Total TLS 自动签发,首次访问可能需要等待几分钟。");
299
300
  }
300
- if (fin.visibility === "private") {
301
+ if (result.visibility === "private") {
301
302
  console.log(" 私密站点仅你可见,请从 https://u1s1.io/dashboard#sec-sites 打开。");
302
303
  }
303
- else if (fin.community_listed) {
304
+ else if (result.community_listed) {
304
305
  console.log(" 已进入 https://u1s1.io/community ,把网址发给朋友就能看。");
305
306
  }
306
307
  else {
@@ -309,3 +310,25 @@ export async function deployCommand(cfg, args) {
309
310
  console.log(" 改完代码再跑一次 u1s1 deploy 即可更新。");
310
311
  console.log("");
311
312
  }
313
+ export async function deployCommand(cfg, args) {
314
+ // 参数:[dir] [--name xxx] [--public|--private];u1s1 deploy list 列出已有站点
315
+ if (args[0] === "list") {
316
+ await listDeployments(cfg);
317
+ return;
318
+ }
319
+ const selection = await selectDeployment(args);
320
+ const { name, start } = await startDeployment(cfg, selection.name);
321
+ validateDeploymentLimits(start, selection.files, selection.totalBytes);
322
+ await uploadAll(cfg, start, selection.files);
323
+ const result = await api(cfg, {
324
+ method: "POST",
325
+ path: "/deploy/finish",
326
+ body: {
327
+ site: start.site,
328
+ deploy_id: start.deploy_id,
329
+ visibility: selection.visibility,
330
+ },
331
+ });
332
+ rememberSite(selection.dir, start.slug || name || start.site);
333
+ printDeploymentResult(result, start);
334
+ }
@@ -1,3 +1,4 @@
1
- import type { SourceAdapter, SourceSession } from "./types.js";
1
+ import type { ConvertedSession, SourceAdapter, SourceSession } from "./types.js";
2
2
  export declare function hydrateClaudeSession(session: SourceSession): Promise<SourceSession>;
3
+ export declare function convertClaudeSession(session: SourceSession): ConvertedSession;
3
4
  export declare const claudeAdapter: SourceAdapter;
@@ -12,6 +12,7 @@ const CLAUDE_TOOL_MAP = {
12
12
  grep: "grep",
13
13
  ls: "ls",
14
14
  };
15
+ const MAX_CLAUDE_LINE_CHARS = 8_000_000;
15
16
  function claudeHomes() {
16
17
  return uniqueExistingDirs([
17
18
  process.env["CLAUDE_CONFIG_DIR"],
@@ -152,14 +153,25 @@ function firstUserPreview(obj) {
152
153
  return undefined;
153
154
  return oneLine(firstMeaningfulLine(text) || text);
154
155
  }
156
+ function updateClaudeHydration(state, obj) {
157
+ state.sourceId ||= asString(obj["sessionId"]) ?? "";
158
+ state.cwd ||= extractCwd(obj) ?? "";
159
+ state.title ??= extractTitle(obj);
160
+ state.startedAt ??= parseTime(obj["timestamp"]);
161
+ state.preview ??= firstUserPreview(obj);
162
+ }
163
+ function hasClaudeHydrationIdentity(state) {
164
+ return Boolean(state.cwd && (state.title || state.preview) && state.sourceId);
165
+ }
155
166
  export async function hydrateClaudeSession(session) {
156
167
  const stream = createReadStream(session.sourcePath, { encoding: "utf8" });
157
168
  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;
169
+ const state = {
170
+ cwd: "",
171
+ title: session.title,
172
+ sourceId: session.sourceId,
173
+ startedAt: session.startedAt,
174
+ };
163
175
  let lines = 0;
164
176
  try {
165
177
  for await (const line of rl) {
@@ -167,17 +179,8 @@ export async function hydrateClaudeSession(session) {
167
179
  const obj = parseJsonLine(line);
168
180
  if (!obj)
169
181
  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)
182
+ updateClaudeHydration(state, obj);
183
+ if (hasClaudeHydrationIdentity(state) && lines > 80)
181
184
  break;
182
185
  if (lines > 400)
183
186
  break;
@@ -189,10 +192,148 @@ export async function hydrateClaudeSession(session) {
189
192
  }
190
193
  return {
191
194
  ...session,
192
- sourceId: sourceId || session.sourceId,
193
- cwd: cwd || session.cwd,
194
- title: title || preview || session.title,
195
- startedAt: startedAt ?? session.startedAt,
195
+ sourceId: state.sourceId || session.sourceId,
196
+ cwd: state.cwd || session.cwd,
197
+ title: state.title || state.preview || session.title,
198
+ startedAt: state.startedAt ?? session.startedAt,
199
+ };
200
+ }
201
+ function readClaudeTranscript(path) {
202
+ try {
203
+ return readFileSync(path, "utf8");
204
+ }
205
+ catch {
206
+ return undefined;
207
+ }
208
+ }
209
+ function appendClaudeToolResults(state, blocks, timestamp) {
210
+ for (const raw of blocks) {
211
+ const block = asRecord(raw);
212
+ if (!block || block["type"] !== "tool_result")
213
+ continue;
214
+ const callId = asString(block["tool_use_id"]);
215
+ if (!callId)
216
+ continue;
217
+ state.messages.push({
218
+ role: "toolResult",
219
+ toolCallId: callId,
220
+ toolName: state.pendingTools.get(callId) ?? "unknown",
221
+ text: truncateText(flattenToolResult(block["content"]), MAX_TOOL_RESULT_CHARS),
222
+ isError: block["is_error"] === true,
223
+ timestamp,
224
+ });
225
+ }
226
+ }
227
+ function appendClaudeUser(state, obj) {
228
+ const message = asRecord(obj["message"]);
229
+ if (!message)
230
+ return;
231
+ const rawContent = message["content"];
232
+ const timestamp = parseTime(obj["timestamp"]) ?? Date.now();
233
+ if (isToolResultContent(rawContent) && Array.isArray(rawContent)) {
234
+ appendClaudeToolResults(state, rawContent, timestamp);
235
+ return;
236
+ }
237
+ const text = truncateText(flattenClaudeContent(rawContent), MAX_TEXT_CHARS).trim();
238
+ if (!text)
239
+ return;
240
+ state.title ||= oneLine(firstMeaningfulLine(text) || text);
241
+ state.messages.push({ role: "user", text, timestamp });
242
+ }
243
+ function convertClaudeContentBlock(raw, pendingTools) {
244
+ const block = asRecord(raw);
245
+ if (!block)
246
+ return undefined;
247
+ const blockType = asString(block["type"]);
248
+ if (blockType === "thinking") {
249
+ const thinking = asString(block["thinking"]) ?? "";
250
+ return thinking.trim()
251
+ ? { type: "thinking", thinking: truncateText(thinking, MAX_TEXT_CHARS) }
252
+ : undefined;
253
+ }
254
+ if (blockType === "text") {
255
+ const text = asString(block["text"]) ?? "";
256
+ return text.trim() ? { type: "text", text: truncateText(text, MAX_TEXT_CHARS) } : undefined;
257
+ }
258
+ if (blockType !== "tool_use")
259
+ return undefined;
260
+ const id = asString(block["id"]);
261
+ if (!id)
262
+ return undefined;
263
+ const name = asString(block["name"]) ?? "unknown";
264
+ const mappedName = mapToolName(name);
265
+ pendingTools.set(id, mappedName);
266
+ return {
267
+ type: "toolCall",
268
+ id,
269
+ name: mappedName,
270
+ arguments: remapToolArgs(name, asRecord(block["input"]) ?? {}),
271
+ };
272
+ }
273
+ function claudeStopReason(content, rawStopReason) {
274
+ if (content.some((block) => block.type === "toolCall") || rawStopReason === "tool_use") {
275
+ return "toolUse";
276
+ }
277
+ return rawStopReason === "max_tokens" ? "length" : "stop";
278
+ }
279
+ function appendClaudeAssistant(state, obj) {
280
+ const message = asRecord(obj["message"]);
281
+ if (!message)
282
+ return;
283
+ const rawBlocks = Array.isArray(message["content"]) ? message["content"] : [];
284
+ const content = rawBlocks
285
+ .map((block) => convertClaudeContentBlock(block, state.pendingTools))
286
+ .filter((block) => block !== undefined);
287
+ if (content.length === 0)
288
+ return;
289
+ state.messages.push({
290
+ role: "assistant",
291
+ content,
292
+ provider: "anthropic",
293
+ model: asString(message["model"]) ?? "claude",
294
+ api: "anthropic-messages",
295
+ stopReason: claudeStopReason(content, message["stop_reason"]),
296
+ usage: usageFromClaude(message["usage"]),
297
+ timestamp: parseTime(obj["timestamp"]) ?? Date.now(),
298
+ });
299
+ }
300
+ function consumeClaudeEntry(state, obj) {
301
+ const type = asString(obj["type"]);
302
+ state.cwd ||= extractCwd(obj) ?? "";
303
+ if (!state.title && type === "ai-title") {
304
+ state.title = extractTitle(obj);
305
+ }
306
+ if (obj["isSidechain"] === true)
307
+ return;
308
+ if (type === "user") {
309
+ appendClaudeUser(state, obj);
310
+ }
311
+ else if (type === "assistant") {
312
+ appendClaudeAssistant(state, obj);
313
+ }
314
+ }
315
+ export function convertClaudeSession(session) {
316
+ const text = readClaudeTranscript(session.sourcePath);
317
+ if (text === undefined) {
318
+ return { cwd: session.cwd || process.cwd(), title: session.title, messages: [] };
319
+ }
320
+ const state = {
321
+ messages: [],
322
+ cwd: "",
323
+ title: session.title,
324
+ pendingTools: new Map(),
325
+ };
326
+ for (const line of text.split(/\r?\n/)) {
327
+ if (line.length > MAX_CLAUDE_LINE_CHARS)
328
+ continue;
329
+ const obj = parseJsonLine(line);
330
+ if (obj)
331
+ consumeClaudeEntry(state, obj);
332
+ }
333
+ return {
334
+ cwd: state.cwd || session.cwd || process.cwd(),
335
+ title: state.title,
336
+ messages: state.messages,
196
337
  };
197
338
  }
198
339
  export const claudeAdapter = {
@@ -259,114 +400,6 @@ export const claudeAdapter = {
259
400
  return found;
260
401
  },
261
402
  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 };
403
+ return convertClaudeSession(session);
371
404
  },
372
405
  };
@@ -1,3 +1,4 @@
1
- import type { SourceAdapter, SourceSession } from "./types.js";
1
+ import type { ConvertedSession, SourceAdapter, SourceSession } from "./types.js";
2
2
  export declare function hydrateCodexSession(session: SourceSession): Promise<SourceSession>;
3
+ export declare function convertCodexSession(session: SourceSession): ConvertedSession;
3
4
  export declare const codexAdapter: SourceAdapter;