Repository navigation
Expand file tree
/
Copy pathstream_adapter.test.ts
More file actions
111 lines (100 loc) · 3.46 KB
/
Copy pathstream_adapter.test.ts
File metadata and controls
111 lines (100 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { normalizeInterrupts, parseRawStreamChunk } from '../llm/streamAdapter.js';
// Characterization tests for the raw LangGraph -> typed AgentStreamChunk conversion
// (the parsing that previously lived inside TurnStreamConsumer).
describe('parseRawStreamChunk', () => {
it('extracts text deltas from model-node messages', () => {
const out = parseRawStreamChunk([
'messages',
[{ content: [{ type: 'text', text: 'Hi' }] }, { langgraph_node: 'model' }],
]);
expect(out).toEqual([{ kind: 'text', delta: 'Hi' }]);
});
it('emits reasoning before text within a single chunk', () => {
const out = parseRawStreamChunk([
'messages',
[
{ content: [{ type: 'reasoning', reasoning: 'r' }, { type: 'text', text: 't' }] },
{ langgraph_node: 'model' },
],
]);
expect(out).toEqual([
{ kind: 'reasoning', delta: 'r' },
{ kind: 'text', delta: 't' },
]);
});
it('reads token.contentBlocks as well as token.content', () => {
const out = parseRawStreamChunk([
'messages',
[{ contentBlocks: [{ type: 'text', text: 'B' }] }, { langgraph_node: 'model_request' }],
]);
expect(out).toEqual([{ kind: 'text', delta: 'B' }]);
});
it('drops tokens emitted by non-model nodes', () => {
const out = parseRawStreamChunk([
'messages',
[{ content: [{ type: 'text', text: 'x' }] }, { langgraph_node: 'tools' }],
]);
expect(out).toEqual([]);
});
it('surfaces interrupts from update entries with normalized descriptors', () => {
const out = parseRawStreamChunk([
'updates',
{ __interrupt__: [{ id: 'i1', value: { actionRequests: [{}, {}] } }] },
]);
expect(out).toEqual([
{
kind: 'interrupt',
interrupt: [{ id: 'i1', value: { actionRequests: [{}, {}] } }],
descriptors: [{ id: 'i1', count: 2, requests: [] }],
},
]);
});
it('yields empty descriptors when the interrupt has no actionRequests', () => {
const out = parseRawStreamChunk(['updates', { __interrupt__: [{ id: 'i1' }] }]);
expect(out).toEqual([
{ kind: 'interrupt', interrupt: [{ id: 'i1' }], descriptors: [] },
]);
});
it('ignores non-interrupt update entries', () => {
expect(parseRawStreamChunk(['updates', { someNode: {} }])).toEqual([]);
});
it('returns nothing for empty content', () => {
expect(
parseRawStreamChunk(['messages', [{ content: [] }, { langgraph_node: 'model' }]]),
).toEqual([]);
});
});
describe('normalizeInterrupts', () => {
it('normalizes the pending tool calls out of the interrupt payload', () => {
expect(normalizeInterrupts([
{
id: 'i1',
value: {
actionRequests: [
{ name: 'delete_record', args: { resourceId: 'cars', primaryKey: '10' } },
],
},
},
])).toEqual([
{
id: 'i1',
count: 1,
requests: [{ toolName: 'delete_record', args: { resourceId: 'cars', primaryKey: '10' } }],
},
]);
});
it('defaults missing args and drops action requests without a tool name', () => {
expect(normalizeInterrupts({
id: 'i1',
value: { actionRequests: [{ name: 'create_record' }, { args: { a: 1 } }] },
})).toEqual([
{
id: 'i1',
// `count` stays the number of decisions the provider expects, even for the
// action request that could not be described.
count: 2,
requests: [{ toolName: 'create_record', args: {} }],
},
]);
});
});