@100xprompt/brain 0.0.0-stage → 0.1.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/LICENSE +48 -0
- package/README.md +172 -2
- package/package.json +48 -4
- package/src/adapters/aider/index.ts +8 -0
- package/src/adapters/aider/reader.ts +129 -0
- package/src/adapters/aider/records.ts +220 -0
- package/src/adapters/aider/writer.ts +269 -0
- package/src/adapters/amazon-q-cli/index.ts +8 -0
- package/src/adapters/amazon-q-cli/reader.ts +84 -0
- package/src/adapters/amazon-q-cli/records.ts +191 -0
- package/src/adapters/amazon-q-cli/writer.ts +370 -0
- package/src/adapters/amazon-q-ide/index.ts +9 -0
- package/src/adapters/amazon-q-ide/reader.ts +131 -0
- package/src/adapters/amazon-q-ide/records.ts +141 -0
- package/src/adapters/amazon-q-ide/writer.ts +325 -0
- package/src/adapters/antigravity/index.ts +9 -0
- package/src/adapters/antigravity/proto.ts +194 -0
- package/src/adapters/antigravity/reader.ts +424 -0
- package/src/adapters/antigravity/records.ts +236 -0
- package/src/adapters/antigravity/writer.ts +463 -0
- package/src/adapters/auggie/index.ts +36 -0
- package/src/adapters/claude-code/index.ts +8 -0
- package/src/adapters/claude-code/reader.ts +639 -0
- package/src/adapters/claude-code/records.ts +88 -0
- package/src/adapters/claude-code/writer.ts +329 -0
- package/src/adapters/cline/index.ts +12 -0
- package/src/adapters/cline/reader.ts +432 -0
- package/src/adapters/cline/records.ts +151 -0
- package/src/adapters/cline/vscdb.ts +56 -0
- package/src/adapters/cline/writer.ts +656 -0
- package/src/adapters/cline/xml.ts +177 -0
- package/src/adapters/cline4/index.ts +9 -0
- package/src/adapters/cline4/reader.ts +179 -0
- package/src/adapters/cline4/records.ts +92 -0
- package/src/adapters/cline4/writer.ts +361 -0
- package/src/adapters/codebuff/index.ts +8 -0
- package/src/adapters/codebuff/project.ts +207 -0
- package/src/adapters/codebuff/reader.ts +215 -0
- package/src/adapters/codebuff/records.ts +173 -0
- package/src/adapters/codebuff/writer.ts +383 -0
- package/src/adapters/codex/index.ts +8 -0
- package/src/adapters/codex/native.ts +239 -0
- package/src/adapters/codex/reader.ts +747 -0
- package/src/adapters/codex/records.ts +114 -0
- package/src/adapters/codex/writer.ts +479 -0
- package/src/adapters/continue/index.ts +9 -0
- package/src/adapters/continue/reader.ts +68 -0
- package/src/adapters/continue/records.ts +418 -0
- package/src/adapters/continue/writer.ts +162 -0
- package/src/adapters/copilot-cli/index.ts +9 -0
- package/src/adapters/copilot-cli/reader.ts +186 -0
- package/src/adapters/copilot-cli/records.ts +241 -0
- package/src/adapters/copilot-cli/writer.ts +353 -0
- package/src/adapters/crush/index.ts +8 -0
- package/src/adapters/crush/reader.ts +180 -0
- package/src/adapters/crush/records.ts +169 -0
- package/src/adapters/crush/writer.ts +333 -0
- package/src/adapters/cursor-cli/index.ts +405 -0
- package/src/adapters/cursor-ide/agentkv.ts +95 -0
- package/src/adapters/cursor-ide/index.ts +501 -0
- package/src/adapters/cursor-ide/transcripts.ts +122 -0
- package/src/adapters/cursor-ide/workspaces.ts +119 -0
- package/src/adapters/cursor-ide/writer.ts +170 -0
- package/src/adapters/devin/index.ts +8 -0
- package/src/adapters/devin/reader.ts +305 -0
- package/src/adapters/devin/records.ts +277 -0
- package/src/adapters/devin/writer.ts +523 -0
- package/src/adapters/factory-droid/index.ts +12 -0
- package/src/adapters/factory-droid/reader.ts +184 -0
- package/src/adapters/factory-droid/records.ts +79 -0
- package/src/adapters/factory-droid/writer.ts +350 -0
- package/src/adapters/gemini-cli/exchanges.ts +228 -0
- package/src/adapters/gemini-cli/index.ts +396 -0
- package/src/adapters/gemini-cli/records.ts +521 -0
- package/src/adapters/gemini-cli/writer.ts +354 -0
- package/src/adapters/goose/index.ts +8 -0
- package/src/adapters/goose/reader.ts +138 -0
- package/src/adapters/goose/records.ts +199 -0
- package/src/adapters/goose/writer.ts +340 -0
- package/src/adapters/grok-cli/image.ts +44 -0
- package/src/adapters/grok-cli/index.ts +8 -0
- package/src/adapters/grok-cli/reader.ts +302 -0
- package/src/adapters/grok-cli/records.ts +172 -0
- package/src/adapters/grok-cli/writer.ts +329 -0
- package/src/adapters/hermes/index.ts +8 -0
- package/src/adapters/hermes/reader.ts +249 -0
- package/src/adapters/hermes/records.ts +285 -0
- package/src/adapters/hermes/writer.ts +452 -0
- package/src/adapters/iflow-cli/index.ts +72 -0
- package/src/adapters/iflow-cli/records.ts +215 -0
- package/src/adapters/iflow-cli/writer.ts +204 -0
- package/src/adapters/index.ts +112 -0
- package/src/adapters/kilo-cli/index.ts +21 -0
- package/src/adapters/kilo-code/index.ts +11 -0
- package/src/adapters/kilo-code/writer.ts +92 -0
- package/src/adapters/kimi/index.ts +9 -0
- package/src/adapters/kimi/reader.ts +92 -0
- package/src/adapters/kimi/records.ts +221 -0
- package/src/adapters/kimi/writer.ts +367 -0
- package/src/adapters/kiro-cli/index.ts +8 -0
- package/src/adapters/kiro-cli/reader.ts +87 -0
- package/src/adapters/kiro-cli/records.ts +260 -0
- package/src/adapters/kiro-cli/writer.ts +305 -0
- package/src/adapters/kiro-ide/index.ts +8 -0
- package/src/adapters/kiro-ide/writer.ts +52 -0
- package/src/adapters/letta/index.ts +8 -0
- package/src/adapters/letta/reader.ts +177 -0
- package/src/adapters/letta/records.ts +119 -0
- package/src/adapters/letta/writer.ts +413 -0
- package/src/adapters/mastracode/index.ts +8 -0
- package/src/adapters/mastracode/reader.ts +323 -0
- package/src/adapters/mastracode/records.ts +336 -0
- package/src/adapters/mastracode/writer.ts +385 -0
- package/src/adapters/mistral-vibe/index.ts +9 -0
- package/src/adapters/mistral-vibe/reader.ts +62 -0
- package/src/adapters/mistral-vibe/records.ts +104 -0
- package/src/adapters/mistral-vibe/writer.ts +359 -0
- package/src/adapters/muse/index.ts +10 -0
- package/src/adapters/muse/reader.ts +332 -0
- package/src/adapters/muse/records.ts +104 -0
- package/src/adapters/muse/writer.ts +387 -0
- package/src/adapters/omp/index.ts +13 -0
- package/src/adapters/omp/reader.ts +314 -0
- package/src/adapters/omp/records.ts +153 -0
- package/src/adapters/omp/writer.ts +216 -0
- package/src/adapters/opencode/ids.ts +52 -0
- package/src/adapters/opencode/index.ts +31 -0
- package/src/adapters/opencode/note.ts +8 -0
- package/src/adapters/opencode/reader.ts +452 -0
- package/src/adapters/opencode/tools.ts +174 -0
- package/src/adapters/opencode/v2.ts +367 -0
- package/src/adapters/opencode/writer.ts +379 -0
- package/src/adapters/openhands/index.ts +8 -0
- package/src/adapters/openhands/reader.ts +222 -0
- package/src/adapters/openhands/records.ts +136 -0
- package/src/adapters/openhands/writer.ts +404 -0
- package/src/adapters/pearai/index.ts +10 -0
- package/src/adapters/pi/index.ts +28 -0
- package/src/adapters/pi/reader.ts +261 -0
- package/src/adapters/pi/records.ts +99 -0
- package/src/adapters/pi/writer.ts +362 -0
- package/src/adapters/qoder-cli/index.ts +8 -0
- package/src/adapters/qoder-cli/reader.ts +83 -0
- package/src/adapters/qoder-cli/records.ts +45 -0
- package/src/adapters/qoder-cli/writer.ts +72 -0
- package/src/adapters/qwen-code/index.ts +84 -0
- package/src/adapters/qwen-code/records.ts +194 -0
- package/src/adapters/qwen-code/writer.ts +224 -0
- package/src/adapters/tabnine-cli/index.ts +8 -0
- package/src/adapters/tabnine-cli/reader.ts +106 -0
- package/src/adapters/tabnine-cli/records.ts +33 -0
- package/src/adapters/tabnine-cli/writer.ts +156 -0
- package/src/adapters/types.ts +73 -0
- package/src/adapters/void/index.ts +9 -0
- package/src/adapters/void/reader.ts +48 -0
- package/src/adapters/void/records.ts +285 -0
- package/src/adapters/void/writer.ts +93 -0
- package/src/adapters/vscode-copilot/index.ts +9 -0
- package/src/adapters/vscode-copilot/reader.ts +212 -0
- package/src/adapters/vscode-copilot/records.ts +388 -0
- package/src/adapters/vscode-copilot/shared.ts +68 -0
- package/src/adapters/vscode-copilot/writer.ts +546 -0
- package/src/adapters/zed/index.ts +8 -0
- package/src/adapters/zed/reader.ts +198 -0
- package/src/adapters/zed/records.ts +87 -0
- package/src/adapters/zed/writer.ts +341 -0
- package/src/brief-writers.ts +71 -0
- package/src/brief.ts +238 -0
- package/src/cli.ts +326 -0
- package/src/fsutil.ts +133 -0
- package/src/gateway/connect.ts +845 -0
- package/src/gateway/signin.ts +323 -0
- package/src/handoff.ts +104 -0
- package/src/hooks.ts +455 -0
- package/src/memory/audit.ts +514 -0
- package/src/memory/blobs.ts +94 -0
- package/src/memory/cli.ts +117 -0
- package/src/memory/distill.ts +381 -0
- package/src/memory/externalize.ts +435 -0
- package/src/memory/lookups.ts +70 -0
- package/src/memory/mcp-install.ts +401 -0
- package/src/memory/mcp-server.ts +296 -0
- package/src/memory/prepare.ts +179 -0
- package/src/memory/preview.ts +154 -0
- package/src/memory/project.ts +35 -0
- package/src/memory/redact.ts +72 -0
- package/src/memory/relevance.ts +187 -0
- package/src/memory/repomap.ts +516 -0
- package/src/memory/store.ts +270 -0
- package/src/memory/sync.ts +166 -0
- package/src/memory/tidy.ts +193 -0
- package/src/memory/types.ts +118 -0
- package/src/org/admin-ops.ts +96 -0
- package/src/org/auth.ts +112 -0
- package/src/org/backup.ts +115 -0
- package/src/org/cli.ts +438 -0
- package/src/org/client.ts +404 -0
- package/src/org/config.ts +44 -0
- package/src/org/crypto.ts +537 -0
- package/src/org/db.ts +435 -0
- package/src/org/graph.ts +318 -0
- package/src/org/oidc.ts +435 -0
- package/src/org/policy.ts +398 -0
- package/src/org/server.ts +920 -0
- package/src/org/sso.ts +316 -0
- package/src/org/tls.ts +66 -0
- package/src/org/types.ts +193 -0
- package/src/self.ts +68 -0
- package/src/session/attachments.ts +53 -0
- package/src/session/peekcache.ts +50 -0
- package/src/session/types.ts +104 -0
- package/src/session/util.ts +130 -0
- package/src/sqlite.ts +48 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Reader, Writer } from "../types.ts"
|
|
2
|
+
import { reader } from "./reader.ts"
|
|
3
|
+
import { kimiCodeNativeWriter } from "./writer.ts"
|
|
4
|
+
|
|
5
|
+
// Kimi Code (Moonshot, the TypeScript successor of the archived Kimi CLI): reads ~/.kimi-code sessions, and
|
|
6
|
+
// continues any session as a new native Kimi Code session that `kimi --session <id>` opens.
|
|
7
|
+
export { reader }
|
|
8
|
+
export const readers: Reader[] = [reader]
|
|
9
|
+
export const writers: Writer[] = [kimiCodeNativeWriter]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { statSync } from "node:fs"
|
|
2
|
+
import { join, resolve } from "node:path"
|
|
3
|
+
import { exists, gitInfo, isDir, ls, readJson, readJsonl } from "../../fsutil.ts"
|
|
4
|
+
import { header, iso, lossCounter } from "../../session/util.ts"
|
|
5
|
+
import type { Reader, SessionRef } from "../types.ts"
|
|
6
|
+
import { LABEL, str, TOOL, wireToTurns } from "./records.ts"
|
|
7
|
+
|
|
8
|
+
// Kimi Code keeps each session in ~/.kimi-code/sessions/<wd_slug_hash>/<sessionId>/: state.json and the main
|
|
9
|
+
// agent's journal, agents/main/wire.jsonl (records.ts). Subagents have journals of their own beside it.
|
|
10
|
+
// The archived Python Kimi CLI (~/.kimi) is not read: Kimi Code's own `kimi migrate` converts it.
|
|
11
|
+
|
|
12
|
+
/** Wire protocol versions this reader was checked against (Kimi Code migrates 1.0–1.4 up to 1.5). */
|
|
13
|
+
const KNOWN_WIRE = new Set(["1.0", "1.1", "1.2", "1.3", "1.4", "1.5"])
|
|
14
|
+
|
|
15
|
+
type State = Record<string, any>
|
|
16
|
+
|
|
17
|
+
function sessionDirs(home: string): string[] {
|
|
18
|
+
const root = join(home, ".kimi-code", "sessions")
|
|
19
|
+
const out: string[] = []
|
|
20
|
+
for (const ws of ls(root)) {
|
|
21
|
+
if (!ws.startsWith("wd_") || !isDir(join(root, ws))) continue
|
|
22
|
+
for (const id of ls(join(root, ws))) {
|
|
23
|
+
const dir = join(root, ws, id)
|
|
24
|
+
if (exists(join(dir, "state.json"))) out.push(dir)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mainWire = (dir: string, state: State | undefined) => {
|
|
31
|
+
const given = str(state?.agents?.main?.homedir)
|
|
32
|
+
// Kimi Code 0.38.0 records the agent folder relative to the session folder ("agents/main"); older ones an absolute path.
|
|
33
|
+
const home = given ? resolve(dir, given) : undefined
|
|
34
|
+
return home && exists(join(home, "wire.jsonl")) ? join(home, "wire.jsonl") : join(dir, "agents", "main", "wire.jsonl")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const reader: Reader = {
|
|
38
|
+
tool: TOOL,
|
|
39
|
+
label: LABEL,
|
|
40
|
+
|
|
41
|
+
async list(home) {
|
|
42
|
+
const refs: SessionRef[] = []
|
|
43
|
+
for (const dir of sessionDirs(home)) {
|
|
44
|
+
const state = readJson<State>(join(dir, "state.json"))
|
|
45
|
+
const wire = mainWire(dir, state)
|
|
46
|
+
let size = 0
|
|
47
|
+
try {
|
|
48
|
+
size = statSync(wire).size
|
|
49
|
+
} catch {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
// A session never prompted has only its metadata line.
|
|
53
|
+
if (!str(state?.lastPrompt) && size < 256) continue
|
|
54
|
+
const sourceId = str(state?.id) ?? dir.split("/").pop()!
|
|
55
|
+
const title = str(state?.title)
|
|
56
|
+
const cwd = str(state?.cwd)
|
|
57
|
+
const updatedAt = iso(state?.updatedAt)
|
|
58
|
+
refs.push({ tool: TOOL, sourceId, path: wire, ...(title ? { title } : {}), ...(cwd ? { cwd } : {}), ...(updatedAt ? { updatedAt } : {}), extra: { dir } })
|
|
59
|
+
}
|
|
60
|
+
return refs.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async read(ref, home) {
|
|
64
|
+
if (!exists(ref.path)) throw new Error(`${ref.path} does not exist`)
|
|
65
|
+
const dir = typeof ref.extra?.dir === "string" ? ref.extra.dir : join(ref.path, "..", "..", "..")
|
|
66
|
+
const state = readJson<State>(join(dir, "state.json"))
|
|
67
|
+
const { rows, bad } = readJsonl(ref.path)
|
|
68
|
+
if (!rows.length) throw new Error(`${ref.path} has no Kimi Code wire records`)
|
|
69
|
+
const lost = lossCounter()
|
|
70
|
+
if (bad) lost.add("unreadable lines", bad)
|
|
71
|
+
const meta = rows[0]?.type === "metadata" ? rows[0] : undefined
|
|
72
|
+
if (meta && !KNOWN_WIRE.has(String(meta.protocol_version))) lost.add(`unverified wire protocol ${String(meta.protocol_version)}`)
|
|
73
|
+
|
|
74
|
+
const cwd = str(state?.cwd) ?? ref.cwd
|
|
75
|
+
const title = str(state?.title)
|
|
76
|
+
const startedAt = iso(state?.createdAt) ?? iso(meta?.created_at)
|
|
77
|
+
const updatedAt = iso(state?.updatedAt)
|
|
78
|
+
const h = header(TOOL, str(state?.id) ?? ref.sourceId, {
|
|
79
|
+
sourcePath: ref.path,
|
|
80
|
+
...(title ? { title } : {}),
|
|
81
|
+
project: { ...(cwd ? { cwd } : {}), ...gitInfo(cwd, home) },
|
|
82
|
+
...(startedAt ? { startedAt } : {}),
|
|
83
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
84
|
+
})
|
|
85
|
+
const turns = wireToTurns(rows, h, lost)
|
|
86
|
+
// Subagents keep their own journals; their results stay in the parent's tool results.
|
|
87
|
+
const subs = Object.entries(state?.agents ?? {}).filter(([id, a]) => id !== "main" && (a as State)?.type === "sub").length
|
|
88
|
+
if (subs) lost.add("subagent transcripts (their results stay in the parent's tool results)", subs)
|
|
89
|
+
lost.flush(h)
|
|
90
|
+
return { header: h, turns }
|
|
91
|
+
},
|
|
92
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { keepImage, parseDataUrl } from "../../session/attachments.ts"
|
|
3
|
+
import type { Attachment, SessionHeader, Turn } from "../../session/types.ts"
|
|
4
|
+
import { clip, iso, noteModel, toolKindOf, type lossCounter } from "../../session/util.ts"
|
|
5
|
+
|
|
6
|
+
// Facts about Kimi Code's session store, shared by its reader and writer. They come from Kimi Code's source
|
|
7
|
+
// (MoonshotAI/kimi-code, packages/agent-core-v2: wire/, agent/contextMemory/, agent/replayBuilder/fold.ts,
|
|
8
|
+
// _base/utils/workdir-slug.ts; packages/migration-legacy: the writer Kimi uses for sessions converted from
|
|
9
|
+
// elsewhere) and from the bundle of @moonshot-ai/kimi-code 2.5.2, which wrote and listed sessions here.
|
|
10
|
+
//
|
|
11
|
+
// ~/.kimi-code/sessions/<wd_slug_hash>/<sessionId>/ holds state.json (title, cwd, times, agents) and
|
|
12
|
+
// agents/<agentId>/wire.jsonl, the journal every resume replays: a metadata line, then turn.* records and
|
|
13
|
+
// context.* records. Messages arrive two ways: context.append_message (user prompts, and every message of
|
|
14
|
+
// a converted session), and context.append_loop_event (a live model step: step.begin, content.part,
|
|
15
|
+
// tool.call, step.end, tool.result). ~/.kimi-code/session_index.jsonl maps ids to folders.
|
|
16
|
+
|
|
17
|
+
export const TOOL = "kimi-code"
|
|
18
|
+
export const LABEL = "Kimi Code"
|
|
19
|
+
|
|
20
|
+
/** The wire protocol Kimi Code 2.5.2 writes (createWireMetadataRecord). */
|
|
21
|
+
export const WIRE_PROTOCOL = "1.5"
|
|
22
|
+
/** state.json's `version` in Kimi Code 2.5.2. */
|
|
23
|
+
export const META_VERSION = 2
|
|
24
|
+
|
|
25
|
+
type Lost = ReturnType<typeof lossCounter>
|
|
26
|
+
type Json = Record<string, any>
|
|
27
|
+
const isObj = (v: unknown): v is Json => typeof v === "object" && v !== null && !Array.isArray(v)
|
|
28
|
+
|
|
29
|
+
/** The bucket folder for a working directory, `wd_<slug>_<sha256[:12]>`, as encodeWorkDirKey makes it. */
|
|
30
|
+
export function workDirKey(workDir: string): string {
|
|
31
|
+
const normalized = workDir.replaceAll("\\", "/").replace(/\/+$/, "")
|
|
32
|
+
const base = normalized.split("/").pop() ?? normalized
|
|
33
|
+
let slug = base
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replaceAll(/[^a-z0-9._-]+/g, "-")
|
|
36
|
+
.replaceAll(/^-+|-+$/g, "")
|
|
37
|
+
.slice(0, 40)
|
|
38
|
+
.replaceAll(/^-+|-+$/g, "")
|
|
39
|
+
if (slug === "" || slug === "." || slug === "..") slug = "workspace"
|
|
40
|
+
return `wd_${slug}_${createHash("sha256").update(normalized).digest("hex").slice(0, 12)}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Origins whose user-role messages are not anyone's request: reminders and other injected context
|
|
45
|
+
* (injection), system wake-ups, hook output, task notices and retries. A compaction summary is its own turn.
|
|
46
|
+
*/
|
|
47
|
+
const CONTEXT_ORIGINS = new Set(["injection", "system_trigger", "hook_result", "task", "retry", "cron_missed"])
|
|
48
|
+
|
|
49
|
+
/** Tool calls as wire 1.1+ stores them ({name, arguments}) or as 1.0 did ({function: {name, arguments}}). */
|
|
50
|
+
function callOf(c: Json): { id: string; name: string; input: unknown } | undefined {
|
|
51
|
+
if (typeof c.id !== "string" || !c.id) return undefined
|
|
52
|
+
const fn = isObj(c.function) ? c.function : c
|
|
53
|
+
const name = typeof fn.name === "string" && fn.name ? fn.name : "unknown"
|
|
54
|
+
return { id: c.id, name, input: parseArgs(fn.arguments ?? fn.args) }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseArgs(a: unknown): unknown {
|
|
58
|
+
if (typeof a !== "string") return a ?? {}
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(a)
|
|
61
|
+
} catch {
|
|
62
|
+
return { arguments: a }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Text of a message's parts; the other kinds are counted as lost. */
|
|
67
|
+
function partsText(parts: unknown, lost: Lost, kinds: { think?: string[] } = {}): { text: string; attachments: Attachment[] } {
|
|
68
|
+
const texts: string[] = []
|
|
69
|
+
const attachments: Attachment[] = []
|
|
70
|
+
for (const p of Array.isArray(parts) ? parts : typeof parts === "string" ? [{ type: "text", text: parts }] : []) {
|
|
71
|
+
if (!isObj(p)) continue
|
|
72
|
+
if (p.type === "text" && typeof p.text === "string") texts.push(p.text)
|
|
73
|
+
else if (p.type === "think") {
|
|
74
|
+
if (typeof p.encrypted === "string" && p.encrypted) lost.add("signed or encrypted reasoning")
|
|
75
|
+
else if (typeof p.think === "string" && p.think.trim() && kinds.think) kinds.think.push(p.think)
|
|
76
|
+
} else if (p.type === "image_url") {
|
|
77
|
+
const url = isObj(p.imageUrl) ? p.imageUrl.url : undefined
|
|
78
|
+
const d = parseDataUrl(url)
|
|
79
|
+
const kept = d ? keepImage(d.base64, d.mime) : undefined
|
|
80
|
+
if (kept) attachments.push(kept)
|
|
81
|
+
else {
|
|
82
|
+
attachments.push({ kind: "image" })
|
|
83
|
+
lost.add("images")
|
|
84
|
+
}
|
|
85
|
+
} else if (p.type === "audio_url" || p.type === "video_url") lost.add("audio and video parts")
|
|
86
|
+
}
|
|
87
|
+
return { text: texts.join(""), attachments }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const toolOutput = (output: unknown, lost: Lost) => (typeof output === "string" ? output : partsText(output, lost).text)
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The turns of one agent's wire records, in order: messages appended directly and the model steps folded
|
|
94
|
+
* from loop events, compactions as compaction turns, undone exchanges removed.
|
|
95
|
+
*/
|
|
96
|
+
export function wireToTurns(records: Json[], h: SessionHeader, lost: Lost): Turn[] {
|
|
97
|
+
const turns: Turn[] = []
|
|
98
|
+
const calls = new Set<string>()
|
|
99
|
+
const answered = new Set<string>()
|
|
100
|
+
let step: Extract<Turn, { kind: "assistant" }> | undefined
|
|
101
|
+
const at = (r: Json) => iso(typeof r.time === "number" ? r.time : undefined)
|
|
102
|
+
|
|
103
|
+
const call = (id: string, name: string, input: unknown, when?: string) => {
|
|
104
|
+
if (calls.has(id)) return
|
|
105
|
+
calls.add(id)
|
|
106
|
+
turns.push({ kind: "tool_call", ...(when ? { at: when } : {}), id, name, toolKind: toolKindOf(name), input })
|
|
107
|
+
}
|
|
108
|
+
const result = (id: string, output: string, ok: boolean, when?: string) => {
|
|
109
|
+
if (answered.has(id) || !calls.has(id)) return
|
|
110
|
+
answered.add(id)
|
|
111
|
+
const c = clip(output)
|
|
112
|
+
turns.push({ kind: "tool_result", ...(when ? { at: when } : {}), callId: id, ok, output: c.text, ...(c.truncated ? { truncated: true } : {}) })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const r of records) {
|
|
116
|
+
const when = at(r)
|
|
117
|
+
switch (r.type) {
|
|
118
|
+
// A live session records the provider model on each request (messages carry none).
|
|
119
|
+
case "llm.request":
|
|
120
|
+
if (typeof r.model === "string") noteModel(h, r.model)
|
|
121
|
+
break
|
|
122
|
+
case "context.append_message": {
|
|
123
|
+
const m = isObj(r.message) ? r.message : {}
|
|
124
|
+
if (m.role === "user") {
|
|
125
|
+
const origin = isObj(m.origin) ? m.origin.kind : undefined
|
|
126
|
+
if (origin === "compaction_summary") {
|
|
127
|
+
const s = partsText(m.content, lost).text
|
|
128
|
+
if (s.trim()) turns.push({ kind: "compaction", ...(when ? { at: when } : {}), summary: s })
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
if (typeof origin === "string" && CONTEXT_ORIGINS.has(origin)) break
|
|
132
|
+
const { text, attachments } = partsText(m.content, lost)
|
|
133
|
+
// Sessions migrated from the Python Kimi CLI carry its reminders as plain user messages.
|
|
134
|
+
if (/^\s*<system-reminder>[\s\S]*<\/system-reminder>\s*$/.test(text)) break
|
|
135
|
+
if (text.trim() || attachments.length) turns.push({ kind: "user", ...(when ? { at: when } : {}), text, ...(attachments.length ? { attachments } : {}) })
|
|
136
|
+
} else if (m.role === "assistant") {
|
|
137
|
+
const think: string[] = []
|
|
138
|
+
const { text } = partsText(m.content, lost, { think })
|
|
139
|
+
for (const t of think) turns.push({ kind: "reasoning", ...(when ? { at: when } : {}), text: t })
|
|
140
|
+
if (text.trim()) turns.push({ kind: "assistant", ...(when ? { at: when } : {}), text })
|
|
141
|
+
for (const c of Array.isArray(m.toolCalls) ? m.toolCalls : []) {
|
|
142
|
+
const v = isObj(c) ? callOf(c) : undefined
|
|
143
|
+
if (v) call(v.id, v.name, v.input, when)
|
|
144
|
+
}
|
|
145
|
+
} else if (m.role === "tool" && typeof m.toolCallId === "string") {
|
|
146
|
+
const out = toolOutput(m.content, lost)
|
|
147
|
+
// The Python Kimi CLI marked a failure in the text itself; migrated sessions keep that.
|
|
148
|
+
result(m.toolCallId, out, m.isError !== true && !out.startsWith("<system>ERROR"), when)
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
case "context.append_loop_event": {
|
|
153
|
+
const e = isObj(r.event) ? r.event : {}
|
|
154
|
+
if (e.type === "step.begin") step = undefined
|
|
155
|
+
else if (e.type === "content.part" && isObj(e.part)) {
|
|
156
|
+
const p = e.part
|
|
157
|
+
if (p.type === "text" && typeof p.text === "string") {
|
|
158
|
+
if (!step) {
|
|
159
|
+
step = { kind: "assistant", ...(when ? { at: when } : {}), text: "" }
|
|
160
|
+
turns.push(step)
|
|
161
|
+
}
|
|
162
|
+
step.text += p.text
|
|
163
|
+
} else {
|
|
164
|
+
const think: string[] = []
|
|
165
|
+
partsText([p], lost, { think })
|
|
166
|
+
for (const t of think) turns.push({ kind: "reasoning", ...(when ? { at: when } : {}), text: t })
|
|
167
|
+
}
|
|
168
|
+
} else if (e.type === "tool.call" && typeof e.toolCallId === "string") {
|
|
169
|
+
call(e.toolCallId, typeof e.name === "string" ? e.name : "unknown", parseArgs(e.args), when)
|
|
170
|
+
} else if (e.type === "tool.result" && typeof e.toolCallId === "string" && isObj(e.result)) {
|
|
171
|
+
result(e.toolCallId, toolOutput(e.result.output, lost), e.result.isError !== true, when)
|
|
172
|
+
} else if (e.type === "step.end") {
|
|
173
|
+
if (step && isObj(e.usage) && step.text.trim()) {
|
|
174
|
+
const u = { input: e.usage.inputOther, output: e.usage.output, cacheRead: e.usage.inputCacheRead, cacheWrite: e.usage.inputCacheCreation }
|
|
175
|
+
const usage = Object.fromEntries(Object.entries(u).filter(([, v]) => typeof v === "number"))
|
|
176
|
+
if (Object.keys(usage).length) step.usage = usage
|
|
177
|
+
}
|
|
178
|
+
if (step && !step.text.trim()) turns.splice(turns.indexOf(step), 1)
|
|
179
|
+
step = undefined
|
|
180
|
+
}
|
|
181
|
+
break
|
|
182
|
+
}
|
|
183
|
+
case "context.apply_compaction": {
|
|
184
|
+
const s = typeof r.contextSummary === "string" ? r.contextSummary : typeof r.summary === "string" ? r.summary : isObj(r.summary) ? partsText(r.summary.content, lost).text : ""
|
|
185
|
+
if (s.trim()) turns.push({ kind: "compaction", ...(when ? { at: when } : {}), summary: s })
|
|
186
|
+
break
|
|
187
|
+
}
|
|
188
|
+
case "context.undo": {
|
|
189
|
+
// Undo removes the last `count` exchanges, each back to its user message.
|
|
190
|
+
let left = typeof r.count === "number" ? r.count : 0
|
|
191
|
+
while (left > 0 && turns.length) {
|
|
192
|
+
const t = turns.pop()!
|
|
193
|
+
if (t.kind === "compaction") {
|
|
194
|
+
turns.push(t)
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
if (t.kind === "user") left--
|
|
198
|
+
}
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
case "context.clear":
|
|
202
|
+
lost.add("context clears (the turns before them are kept)")
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Calls the journal never answered get a result saying so, as Kimi does on resume, so every call is paired.
|
|
207
|
+
const open = [...calls].filter((id) => !answered.has(id) && turns.some((t) => t.kind === "tool_call" && t.id === id))
|
|
208
|
+
if (open.length) lost.add("tool results not recorded", open.length)
|
|
209
|
+
for (const id of open) {
|
|
210
|
+
const i = turns.findIndex((t) => t.kind === "tool_call" && t.id === id)
|
|
211
|
+
turns.splice(i + 1, 0, { kind: "tool_result", callId: id, ok: false, output: "(no result recorded)" })
|
|
212
|
+
}
|
|
213
|
+
if (!h.title) {
|
|
214
|
+
const first = turns.find((t) => t.kind === "user")
|
|
215
|
+
if (first?.kind === "user") h.title = first.text.replace(/\s+/g, " ").trim().slice(0, 120) || undefined
|
|
216
|
+
}
|
|
217
|
+
return turns
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** A state.json value as text, for titles and paths. */
|
|
221
|
+
export const str = (v: unknown) => (typeof v === "string" && v.trim() ? v : undefined)
|