@lythos/agent-adapter 0.13.1 → 0.13.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lythos/agent-adapter",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "main": "./src/index.ts",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -24,14 +24,18 @@ describe('parseKimiStreamJson', () => {
24
24
  '{"role":"assistant","content":"Hello world"}',
25
25
  '{"role":"user","content":"ignored"}',
26
26
  ].join('\n')
27
- expect(parseKimiStreamJson(raw)).toBe('Hello world')
27
+ const result = parseKimiStreamJson(raw)
28
+ expect(result.text).toBe('Hello world')
29
+ expect(result.checkpoints).toEqual([])
28
30
  })
29
31
 
30
32
  it('extracts text from assistant message with content blocks', () => {
31
33
  const raw = [
32
34
  '{"role":"assistant","content":[{"type":"text","text":"First"},{"type":"text","text":"Second"}]}',
33
35
  ].join('\n')
34
- expect(parseKimiStreamJson(raw)).toBe('First\nSecond')
36
+ const result = parseKimiStreamJson(raw)
37
+ expect(result.text).toBe('First\nSecond')
38
+ expect(result.checkpoints).toEqual([])
35
39
  })
36
40
 
37
41
  it('skips non-assistant roles', () => {
@@ -39,7 +43,9 @@ describe('parseKimiStreamJson', () => {
39
43
  '{"role":"system","content":"init"}',
40
44
  '{"role":"assistant","content":"only this"}',
41
45
  ].join('\n')
42
- expect(parseKimiStreamJson(raw)).toBe('only this')
46
+ const result = parseKimiStreamJson(raw)
47
+ expect(result.text).toBe('only this')
48
+ expect(result.checkpoints).toEqual([])
43
49
  })
44
50
 
45
51
  it('skips malformed JSON lines', () => {
@@ -47,15 +53,21 @@ describe('parseKimiStreamJson', () => {
47
53
  'garbage',
48
54
  '{"role":"assistant","content":"valid"}',
49
55
  ].join('\n')
50
- expect(parseKimiStreamJson(raw)).toBe('valid')
56
+ const result = parseKimiStreamJson(raw)
57
+ expect(result.text).toBe('valid')
58
+ expect(result.checkpoints).toEqual([])
51
59
  })
52
60
 
53
61
  it('returns empty string for empty input', () => {
54
- expect(parseKimiStreamJson('')).toBe('')
62
+ const result = parseKimiStreamJson('')
63
+ expect(result.text).toBe('')
64
+ expect(result.checkpoints).toEqual([])
55
65
  })
56
66
 
57
67
  it('returns empty string for whitespace-only input', () => {
58
- expect(parseKimiStreamJson(' \n \n ')).toBe('')
68
+ const result = parseKimiStreamJson(' \n \n ')
69
+ expect(result.text).toBe('')
70
+ expect(result.checkpoints).toEqual([])
59
71
  })
60
72
 
61
73
  it('concatenates multiple assistant messages', () => {
@@ -63,6 +75,24 @@ describe('parseKimiStreamJson', () => {
63
75
  '{"role":"assistant","content":"Part 1"}',
64
76
  '{"role":"assistant","content":"Part 2"}',
65
77
  ].join('\n')
66
- expect(parseKimiStreamJson(raw)).toBe('Part 1\nPart 2')
78
+ const result = parseKimiStreamJson(raw)
79
+ expect(result.text).toBe('Part 1\nPart 2')
80
+ expect(result.checkpoints).toEqual([])
81
+ })
82
+
83
+ it('extracts tool_calls into checkpoints', () => {
84
+ const raw = [
85
+ '{"role":"assistant","content":"Let me check.","tool_calls":[{"type":"function","id":"tc_1","function":{"name":"Shell","arguments":"{\\"command\\":\\"ls\\"}"}}]}',
86
+ '{"role":"tool","tool_call_id":"tc_1","content":"agent-stdout.txt\\nreport.docx"}',
87
+ '{"role":"assistant","content":"Done."}',
88
+ ].join('\n')
89
+ const result = parseKimiStreamJson(raw)
90
+ expect(result.text).toBe('Let me check.\nDone.')
91
+ expect(result.checkpoints.length).toBe(2)
92
+ expect(result.checkpoints[0].step).toBe('tool_call')
93
+ expect(result.checkpoints[0].tool).toBe('Shell')
94
+ expect(result.checkpoints[1].step).toBe('tool_result')
95
+ expect(result.checkpoints[1].tool).toBe('Shell')
96
+ expect(result.checkpoints[1].stdout_summary).toBe('agent-stdout.txt\nreport.docx')
67
97
  })
68
98
  })
@@ -1,7 +1,7 @@
1
1
  import { writeFileSync, unlinkSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { tmpdir } from 'node:os'
4
- import type { AgentAdapter, AgentRunResult } from '../types'
4
+ import type { AgentAdapter, AgentRunResult, CheckpointEntry } from '../types'
5
5
  import { readCheckpoints } from '../checkpoint'
6
6
  import { registerAgent } from '../registry'
7
7
 
@@ -13,28 +13,59 @@ export function buildKimiCommand(): string[] {
13
13
  }
14
14
 
15
15
  /**
16
- * Parse kimi stream-json output into plain text.
17
- * Each line is a JSON event; extracts text from assistant role messages.
18
- * content can be string or array of content blocks.
16
+ * Parse kimi stream-json output into plain text + checkpoint trace.
17
+ * Each line is a JSON event; extracts text from assistant role messages
18
+ * and tool_calls/tool messages into CheckpointEntry for white-box replay.
19
19
  */
20
- export function parseKimiStreamJson(raw: string): string {
21
- const lines: string[] = []
20
+ export function parseKimiStreamJson(raw: string): { text: string; checkpoints: CheckpointEntry[] } {
21
+ const textLines: string[] = []
22
+ const checkpoints: CheckpointEntry[] = []
23
+ const pendingTools = new Map<string, { name: string; args: string }>()
24
+
22
25
  for (const line of raw.split('\n')) {
23
26
  if (!line.trim()) continue
24
27
  try {
25
28
  const event = JSON.parse(line)
26
- if (event.role !== 'assistant') continue
27
- const content = event.content
28
- if (typeof content === 'string') {
29
- lines.push(content)
30
- } else if (Array.isArray(content)) {
31
- for (const block of content) {
32
- if (block.type === 'text' && block.text) lines.push(block.text)
29
+ if (event.role === 'assistant') {
30
+ const content = event.content
31
+ if (typeof content === 'string') {
32
+ textLines.push(content)
33
+ } else if (Array.isArray(content)) {
34
+ for (const block of content) {
35
+ if (block.type === 'text' && block.text) textLines.push(block.text)
36
+ }
37
+ }
38
+
39
+ if (Array.isArray(event.tool_calls)) {
40
+ for (const tc of event.tool_calls) {
41
+ if (tc.type === 'function' && tc.function) {
42
+ checkpoints.push({
43
+ step: 'tool_call',
44
+ tool: tc.function.name,
45
+ args: [tc.function.arguments],
46
+ timestamp: new Date().toISOString(),
47
+ })
48
+ pendingTools.set(tc.id, { name: tc.function.name, args: tc.function.arguments })
49
+ }
50
+ }
51
+ }
52
+ } else if (event.role === 'tool' && event.tool_call_id) {
53
+ const pending = pendingTools.get(event.tool_call_id)
54
+ if (pending) {
55
+ checkpoints.push({
56
+ step: 'tool_result',
57
+ tool: pending.name,
58
+ args: [pending.args],
59
+ stdout_summary: String(event.content ?? '').slice(0, 500),
60
+ timestamp: new Date().toISOString(),
61
+ })
62
+ pendingTools.delete(event.tool_call_id)
33
63
  }
34
64
  }
35
65
  } catch { /* skip malformed lines */ }
36
66
  }
37
- return lines.join('\n')
67
+
68
+ return { text: textLines.join('\n'), checkpoints }
38
69
  }
39
70
 
40
71
  // ── Spawn wrapper (IO, tested via BDD / arena integration) ──────────────────
@@ -67,16 +98,26 @@ async function spawnKimi(opts: {
67
98
  const stderr = await new Response(proc.stderr).text()
68
99
  const code = proc.exitCode ?? 1
69
100
 
70
- let stdout: string
101
+ let text: string
102
+ let streamCheckpoints: CheckpointEntry[]
71
103
  try {
72
- stdout = parseKimiStreamJson(rawStdout)
104
+ const parsed = parseKimiStreamJson(rawStdout)
105
+ text = parsed.text
106
+ streamCheckpoints = parsed.checkpoints
73
107
  } catch {
74
- stdout = rawStdout
108
+ text = rawStdout
109
+ streamCheckpoints = []
75
110
  }
76
111
 
77
- const checkpoints = readCheckpoints(opts.cwd)
112
+ // Preserve raw JSONL for post-hoc white-box analysis
113
+ try {
114
+ writeFileSync(join(opts.cwd, 'agent-stdout-raw.jsonl'), rawStdout, 'utf-8')
115
+ } catch { /* ignore write failures */ }
116
+
117
+ const fileCheckpoints = readCheckpoints(opts.cwd)
118
+ const checkpoints = [...streamCheckpoints, ...fileCheckpoints]
78
119
 
79
- return { stdout: stdout.trim(), stderr, code, durationMs, checkpoints }
120
+ return { stdout: text.trim(), stderr, code, durationMs, checkpoints }
80
121
  }
81
122
 
82
123
  const kimiAdapter: AgentAdapter = {