@nac3/forge-cli 1.0.36 → 1.0.38
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/chat/claude.d.ts +8 -0
- package/dist/chat/claude.d.ts.map +1 -1
- package/dist/chat/claude.js +34 -1
- package/dist/chat/claude.js.map +1 -1
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +65 -1
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +13 -0
- package/dist/chat/server.js.map +1 -1
- package/dist/llm/sse_stream.d.ts +46 -0
- package/dist/llm/sse_stream.d.ts.map +1 -0
- package/dist/llm/sse_stream.js +134 -0
- package/dist/llm/sse_stream.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pendiente B (2026-06-22) -- token-level streaming parsers.
|
|
3
|
+
*
|
|
4
|
+
* Each brain client today does `fetch(...) -> await res.json()` and only
|
|
5
|
+
* surfaces the assistant text whole, at the end. These helpers parse the
|
|
6
|
+
* provider's streaming SSE body instead, calling `onText(delta)` as each
|
|
7
|
+
* text fragment arrives, and reconstruct the SAME response shape the
|
|
8
|
+
* client's buffered path already consumes -- so the downstream tool-loop,
|
|
9
|
+
* caching, truncation detection, etc. stay byte-identical. The only change
|
|
10
|
+
* at the call site is swapping the transport.
|
|
11
|
+
*
|
|
12
|
+
* Provider coverage:
|
|
13
|
+
* - Anthropic (/v1/messages, stream:true) -- readAnthropicStream. DONE.
|
|
14
|
+
* - OpenAI-compat + Gemini parsers are the documented next step
|
|
15
|
+
* (docs/PND_STREAMING_B_TOKEN_LEVEL.md); the panel + server plumbing
|
|
16
|
+
* here is provider-agnostic (any client that emits assistant_delta
|
|
17
|
+
* streams live), so adding them is mechanical.
|
|
18
|
+
*
|
|
19
|
+
* ASCII-only.
|
|
20
|
+
*/
|
|
21
|
+
/** Generic block shape Anthropic's buffered path consumes. */
|
|
22
|
+
export interface AnthropicStreamResult {
|
|
23
|
+
content: Array<{
|
|
24
|
+
type: 'text';
|
|
25
|
+
text: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'tool_use';
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
input: Record<string, unknown>;
|
|
31
|
+
}>;
|
|
32
|
+
usage: {
|
|
33
|
+
input_tokens: number;
|
|
34
|
+
output_tokens: number;
|
|
35
|
+
};
|
|
36
|
+
model?: string;
|
|
37
|
+
stop_reason?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse an Anthropic streaming /v1/messages body into the buffered shape,
|
|
41
|
+
* emitting each text delta via onText. Mirrors the SSE event sequence:
|
|
42
|
+
* message_start -> (content_block_start -> content_block_delta* ->
|
|
43
|
+
* content_block_stop)* -> message_delta -> message_stop.
|
|
44
|
+
*/
|
|
45
|
+
export declare function readAnthropicStream(body: ReadableStream<Uint8Array>, onText: (delta: string) => void): Promise<AnthropicStreamResult>;
|
|
46
|
+
//# sourceMappingURL=sse_stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse_stream.d.ts","sourceRoot":"","sources":["../../src/llm/sse_stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,KAAK,CACV;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC9B;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CACjF,CAAC;IACF,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA+BD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAyDhC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pendiente B (2026-06-22) -- token-level streaming parsers.
|
|
3
|
+
*
|
|
4
|
+
* Each brain client today does `fetch(...) -> await res.json()` and only
|
|
5
|
+
* surfaces the assistant text whole, at the end. These helpers parse the
|
|
6
|
+
* provider's streaming SSE body instead, calling `onText(delta)` as each
|
|
7
|
+
* text fragment arrives, and reconstruct the SAME response shape the
|
|
8
|
+
* client's buffered path already consumes -- so the downstream tool-loop,
|
|
9
|
+
* caching, truncation detection, etc. stay byte-identical. The only change
|
|
10
|
+
* at the call site is swapping the transport.
|
|
11
|
+
*
|
|
12
|
+
* Provider coverage:
|
|
13
|
+
* - Anthropic (/v1/messages, stream:true) -- readAnthropicStream. DONE.
|
|
14
|
+
* - OpenAI-compat + Gemini parsers are the documented next step
|
|
15
|
+
* (docs/PND_STREAMING_B_TOKEN_LEVEL.md); the panel + server plumbing
|
|
16
|
+
* here is provider-agnostic (any client that emits assistant_delta
|
|
17
|
+
* streams live), so adding them is mechanical.
|
|
18
|
+
*
|
|
19
|
+
* ASCII-only.
|
|
20
|
+
*/
|
|
21
|
+
/** Read one SSE-framed Response body, invoking `onFrame` per `data:` JSON
|
|
22
|
+
* object. Tolerant of multi-line data fields and keep-alive `:` comments. */
|
|
23
|
+
async function forEachSseFrame(body, onFrame) {
|
|
24
|
+
const reader = body.getReader();
|
|
25
|
+
const decoder = new TextDecoder('utf-8');
|
|
26
|
+
let buf = '';
|
|
27
|
+
for (;;) {
|
|
28
|
+
const { done, value } = await reader.read();
|
|
29
|
+
if (done)
|
|
30
|
+
break;
|
|
31
|
+
buf += decoder.decode(value, { stream: true });
|
|
32
|
+
let idx;
|
|
33
|
+
while ((idx = buf.indexOf('\n\n')) >= 0) {
|
|
34
|
+
const frame = buf.slice(0, idx);
|
|
35
|
+
buf = buf.slice(idx + 2);
|
|
36
|
+
let dataStr = '';
|
|
37
|
+
for (const line of frame.split('\n')) {
|
|
38
|
+
if (line.startsWith('data:'))
|
|
39
|
+
dataStr += line.slice(5).trim();
|
|
40
|
+
}
|
|
41
|
+
if (!dataStr || dataStr === '[DONE]')
|
|
42
|
+
continue;
|
|
43
|
+
let obj;
|
|
44
|
+
try {
|
|
45
|
+
obj = JSON.parse(dataStr);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
onFrame(obj);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse an Anthropic streaming /v1/messages body into the buffered shape,
|
|
56
|
+
* emitting each text delta via onText. Mirrors the SSE event sequence:
|
|
57
|
+
* message_start -> (content_block_start -> content_block_delta* ->
|
|
58
|
+
* content_block_stop)* -> message_delta -> message_stop.
|
|
59
|
+
*/
|
|
60
|
+
export async function readAnthropicStream(body, onText) {
|
|
61
|
+
const blocks = [];
|
|
62
|
+
const partialJson = {};
|
|
63
|
+
let model;
|
|
64
|
+
let stopReason;
|
|
65
|
+
let inputTokens = 0;
|
|
66
|
+
let outputTokens = 0;
|
|
67
|
+
await forEachSseFrame(body, (evt) => {
|
|
68
|
+
const t = evt['type'];
|
|
69
|
+
if (t === 'message_start') {
|
|
70
|
+
const msg = evt['message'];
|
|
71
|
+
inputTokens = msg?.usage?.input_tokens ?? 0;
|
|
72
|
+
model = msg?.model;
|
|
73
|
+
}
|
|
74
|
+
else if (t === 'content_block_start') {
|
|
75
|
+
const i = Number(evt['index']) || 0;
|
|
76
|
+
const cb = evt['content_block'];
|
|
77
|
+
if (cb?.type === 'text') {
|
|
78
|
+
blocks[i] = { type: 'text', text: '' };
|
|
79
|
+
}
|
|
80
|
+
else if (cb?.type === 'tool_use') {
|
|
81
|
+
blocks[i] = { type: 'tool_use', id: String(cb.id || ''), name: String(cb.name || ''), input: {} };
|
|
82
|
+
partialJson[i] = '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (t === 'content_block_delta') {
|
|
86
|
+
const i = Number(evt['index']) || 0;
|
|
87
|
+
const d = evt['delta'];
|
|
88
|
+
if (d?.type === 'text_delta') {
|
|
89
|
+
const b = blocks[i];
|
|
90
|
+
if (b && b.type === 'text')
|
|
91
|
+
b.text += d.text || '';
|
|
92
|
+
if (d.text)
|
|
93
|
+
onText(d.text);
|
|
94
|
+
}
|
|
95
|
+
else if (d?.type === 'input_json_delta') {
|
|
96
|
+
partialJson[i] = (partialJson[i] || '') + (d.partial_json || '');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (t === 'content_block_stop') {
|
|
100
|
+
const i = Number(evt['index']) || 0;
|
|
101
|
+
const b = blocks[i];
|
|
102
|
+
if (b && b.type === 'tool_use') {
|
|
103
|
+
const raw = partialJson[i];
|
|
104
|
+
if (raw) {
|
|
105
|
+
try {
|
|
106
|
+
b.input = JSON.parse(raw);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
b.input = {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (t === 'message_delta') {
|
|
115
|
+
const d = evt['delta'];
|
|
116
|
+
if (d?.stop_reason)
|
|
117
|
+
stopReason = d.stop_reason;
|
|
118
|
+
const u = evt['usage'];
|
|
119
|
+
if (u && typeof u.output_tokens === 'number')
|
|
120
|
+
outputTokens = u.output_tokens;
|
|
121
|
+
}
|
|
122
|
+
else if (t === 'error') {
|
|
123
|
+
const e = evt['error'];
|
|
124
|
+
throw new Error('anthropic stream error: ' + (e?.message || 'unknown'));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
content: blocks.filter(Boolean),
|
|
129
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
130
|
+
model,
|
|
131
|
+
stop_reason: stopReason,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=sse_stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse_stream.js","sourceRoot":"","sources":["../../src/llm/sse_stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAaH;8EAC8E;AAC9E,KAAK,UAAU,eAAe,CAC5B,IAAgC,EAChC,OAA+C;IAE/C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,SAAS,CAAC;QACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,IAAI;YAAE,MAAM;QAChB,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAChC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,OAAO,GAAG,EAAE,CAAC;YACjB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;oBAAE,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAChE,CAAC;YACD,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ;gBAAE,SAAS;YAC/C,IAAI,GAA4B,CAAC;YACjC,IAAI,CAAC;gBAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,SAAS;YAAC,CAAC;YACjF,OAAO,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAgC,EAChC,MAA+B;IAE/B,MAAM,MAAM,GAAqC,EAAE,CAAC;IACpD,MAAM,WAAW,GAA2B,EAAE,CAAC;IAC/C,IAAI,KAAyB,CAAC;IAC9B,IAAI,UAA8B,CAAC;IACnC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,MAAM,eAAe,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;QAClC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,eAAe,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAsE,CAAC;YAChG,WAAW,GAAG,GAAG,EAAE,KAAK,EAAE,YAAY,IAAI,CAAC,CAAC;YAC5C,KAAK,GAAG,GAAG,EAAE,KAAK,CAAC;QACrB,CAAC;aAAM,IAAI,CAAC,KAAK,qBAAqB,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,EAAE,GAAG,GAAG,CAAC,eAAe,CAA8D,CAAC;YAC7F,IAAI,EAAE,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxB,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YACzC,CAAC;iBAAM,IAAI,EAAE,EAAE,IAAI,KAAK,UAAU,EAAE,CAAC;gBACnC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;gBAClG,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACtB,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,KAAK,qBAAqB,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAwE,CAAC;YAC9F,IAAI,CAAC,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;oBAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;gBACnD,IAAI,CAAC,CAAC,IAAI;oBAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;iBAAM,IAAI,CAAC,EAAE,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBAC1C,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,KAAK,oBAAoB,EAAE,CAAC;YACtC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC/B,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;gBAC3B,IAAI,GAAG,EAAE,CAAC;oBAAC,IAAI,CAAC;wBAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC;wBAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBAAC,CAAC;gBAAC,CAAC;YACpG,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,KAAK,eAAe,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAyC,CAAC;YAC/D,IAAI,CAAC,EAAE,WAAW;gBAAE,UAAU,GAAG,CAAC,CAAC,WAAW,CAAC;YAC/C,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAA2C,CAAC;YACjE,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ;gBAAE,YAAY,GAAG,CAAC,CAAC,aAAa,CAAC;QAC/E,CAAC;aAAM,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAqC,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,CAAC,EAAE,OAAO,IAAI,SAAS,CAAC,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAC/B,KAAK,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE;QACjE,KAAK;QACL,WAAW,EAAE,UAAU;KACxB,CAAC;AACJ,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Yujin Forge CLI version. Kept in sync with package.json by the npm
|
|
3
3
|
* "version" lifecycle (scripts/sync_version.mjs) so the two never drift.
|
|
4
4
|
*/
|
|
5
|
-
export declare const VERSION = "1.0.
|
|
5
|
+
export declare const VERSION = "1.0.38";
|
|
6
6
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nac3/forge-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.38",
|
|
4
4
|
"description": "Yujin Forge -- voice-first NAC-3 React development framework. CLI + chat panel + spec ingest + 10-format document reader + voice loop.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Pablo Kuschnirof <pablo@rpaforce.com>",
|