@lythos/agent-adapter 0.13.1 → 0.13.2
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 +1 -1
- package/src/adapters/kimi.test.ts +37 -7
- package/src/adapters/kimi.ts +60 -19
package/package.json
CHANGED
|
@@ -24,14 +24,18 @@ describe('parseKimiStreamJson', () => {
|
|
|
24
24
|
'{"role":"assistant","content":"Hello world"}',
|
|
25
25
|
'{"role":"user","content":"ignored"}',
|
|
26
26
|
].join('\n')
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})
|
package/src/adapters/kimi.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
|
101
|
+
let text: string
|
|
102
|
+
let streamCheckpoints: CheckpointEntry[]
|
|
71
103
|
try {
|
|
72
|
-
|
|
104
|
+
const parsed = parseKimiStreamJson(rawStdout)
|
|
105
|
+
text = parsed.text
|
|
106
|
+
streamCheckpoints = parsed.checkpoints
|
|
73
107
|
} catch {
|
|
74
|
-
|
|
108
|
+
text = rawStdout
|
|
109
|
+
streamCheckpoints = []
|
|
75
110
|
}
|
|
76
111
|
|
|
77
|
-
|
|
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:
|
|
120
|
+
return { stdout: text.trim(), stderr, code, durationMs, checkpoints }
|
|
80
121
|
}
|
|
81
122
|
|
|
82
123
|
const kimiAdapter: AgentAdapter = {
|