@bytexbyte/nxtlinq-ai-agent-core-development 0.2.0 → 0.2.1
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/api/nxtlinq-api.d.ts.map +1 -1
- package/dist/api/nxtlinq-api.js +10 -6
- package/dist/api/parse-sse.d.ts +18 -2
- package/dist/api/parse-sse.d.ts.map +1 -1
- package/dist/api/parse-sse.js +41 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/api/nxtlinq-api.ts +13 -5
- package/src/api/parse-sse.ts +64 -13
- package/src/index.ts +6 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nxtlinq-api.d.ts","sourceRoot":"","sources":["../../src/api/nxtlinq-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAI5D,OAAO,EAAe,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE5D,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,MAAM,GAAG,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"nxtlinq-api.d.ts","sourceRoot":"","sources":["../../src/api/nxtlinq-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAI5D,OAAO,EAAe,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE5D,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,MAAM,GAAG,aAAa,CAAC,CAAC;AA0nBpF,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,aAAa,GAClB,UAAU,CAYZ"}
|
package/dist/api/nxtlinq-api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { STORAGE_KEYS } from '../constants/storageKeys';
|
|
2
2
|
import { createDefaultHttpPort } from '../ports/HttpPort';
|
|
3
3
|
import { getAiAgentApiHost, getAitServiceApiHost } from './hosts';
|
|
4
|
-
import { parseSSEText } from './parse-sse';
|
|
4
|
+
import { parseSSEResponse, parseSSEText } from './parse-sse';
|
|
5
5
|
import { postTextTts } from './tts';
|
|
6
6
|
export { postTextTts, buildTextTtsDataUri } from './tts';
|
|
7
7
|
/** RN/Hermes 的 URLSearchParams 常缺少 set/append,改用手動組 query。 */
|
|
@@ -128,7 +128,7 @@ const createAgentApi = (helpers) => ({
|
|
|
128
128
|
: {}),
|
|
129
129
|
};
|
|
130
130
|
const useSSE = Boolean(params.onPiiProgress || params.onStreamDelta || model.endsWith('-stream'));
|
|
131
|
-
const
|
|
131
|
+
const fetchInit = {
|
|
132
132
|
method: 'POST',
|
|
133
133
|
headers: {
|
|
134
134
|
'Content-Type': 'application/json',
|
|
@@ -138,7 +138,10 @@ const createAgentApi = (helpers) => ({
|
|
|
138
138
|
...(params.onPiiProgress ? { 'X-Stream-PII': '1' } : {}),
|
|
139
139
|
},
|
|
140
140
|
body: JSON.stringify(requestBody),
|
|
141
|
-
|
|
141
|
+
// React Native: enable incremental body reads for `*-stream` SSE routes.
|
|
142
|
+
...(useSSE ? { reactNative: { textStreaming: true } } : {}),
|
|
143
|
+
};
|
|
144
|
+
const response = await helpers.fetchFn(`${getAiAgentApiHost()}/api/${model}`, fetchInit);
|
|
142
145
|
if (!response.ok) {
|
|
143
146
|
throw new Error('Failed to send message');
|
|
144
147
|
}
|
|
@@ -148,13 +151,14 @@ const createAgentApi = (helpers) => ({
|
|
|
148
151
|
onPiiProgress: params.onPiiProgress,
|
|
149
152
|
onStreamDelta: params.onStreamDelta,
|
|
150
153
|
};
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
const bodyStream = response.body;
|
|
155
|
+
if (bodyStream?.getReader) {
|
|
156
|
+
return await parseSSEResponse(bodyStream, sseHandlers);
|
|
157
|
+
}
|
|
153
158
|
const rawText = await response.text();
|
|
154
159
|
if (rawText.trimStart().startsWith('event:') || rawText.includes('\nevent:')) {
|
|
155
160
|
return parseSSEText(rawText, sseHandlers);
|
|
156
161
|
}
|
|
157
|
-
// Gateway returned JSON despite SSE content-type.
|
|
158
162
|
return rawText ? JSON.parse(rawText) : {};
|
|
159
163
|
}
|
|
160
164
|
return await response.json();
|
package/dist/api/parse-sse.d.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
export type SSEHandlers = {
|
|
2
2
|
onPiiProgress?: (step: 'scan_start' | 'scan_complete' | 'send_start' | 'done', data?: unknown) => void;
|
|
3
|
+
/** Token chunk from a `text_delta` event. */
|
|
3
4
|
onStreamDelta?: (text: string) => void;
|
|
5
|
+
/** Accumulated assistant text after each `text_delta` (convenience for chat UIs). */
|
|
6
|
+
onStreamText?: (fullText: string) => void;
|
|
4
7
|
};
|
|
5
|
-
|
|
8
|
+
type SSEParseState = {
|
|
9
|
+
streamText: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Parse complete SSE blocks from `buffer`, dispatch events, return trailing incomplete bytes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function consumeSSEBuffer(buffer: string, handlers: SSEHandlers, finalDataRef: {
|
|
15
|
+
value: unknown | null;
|
|
16
|
+
}, streamState?: SSEParseState): string;
|
|
17
|
+
/** Parse a complete SSE payload (e.g. React Native fallback after `response.text()`). */
|
|
6
18
|
export declare function parseSSEText(text: string, handlers: SSEHandlers): unknown;
|
|
7
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Incrementally parse Agent SSE from a ReadableStream (`text_delta`, PII steps, final `done`).
|
|
21
|
+
* Dispatches handlers as chunks arrive (required for RN chat UIs).
|
|
22
|
+
*/
|
|
8
23
|
export declare function parseSSEResponse(body: ReadableStream<Uint8Array>, handlers: SSEHandlers): Promise<unknown>;
|
|
24
|
+
export {};
|
|
9
25
|
//# sourceMappingURL=parse-sse.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-sse.d.ts","sourceRoot":"","sources":["../../src/api/parse-sse.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,eAAe,GAAG,YAAY,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACvG,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"parse-sse.d.ts","sourceRoot":"","sources":["../../src/api/parse-sse.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,eAAe,GAAG,YAAY,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACvG,6CAA6C;IAC7C,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qFAAqF;IACrF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAkEF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,WAAW,EACrB,YAAY,EAAE;IAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,EACvC,WAAW,GAAE,aAAkC,GAC9C,MAAM,CAUR;AA6BD,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,OAAO,CAEzE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,OAAO,CAAC,CAmBlB"}
|
package/dist/api/parse-sse.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
function splitSSEMessages(raw) {
|
|
3
|
-
const normalized = raw.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
4
|
-
return normalized.split('\n\n').filter((block) => block.trim().length > 0);
|
|
5
|
-
}
|
|
6
|
-
function dispatchSSEMessage(msg, handlers, finalDataRef) {
|
|
1
|
+
function dispatchSSEMessage(msg, handlers, finalDataRef, streamState) {
|
|
7
2
|
if (!msg.trim())
|
|
8
3
|
return;
|
|
9
4
|
let eventType = '';
|
|
@@ -46,7 +41,11 @@ function dispatchSSEMessage(msg, handlers, finalDataRef) {
|
|
|
46
41
|
/* ignore */
|
|
47
42
|
}
|
|
48
43
|
const chunk = typeof parsed.text === 'string' ? parsed.text : '';
|
|
49
|
-
|
|
44
|
+
if (chunk) {
|
|
45
|
+
handlers.onStreamDelta?.(chunk);
|
|
46
|
+
streamState.streamText += chunk;
|
|
47
|
+
handlers.onStreamText?.(streamState.streamText);
|
|
48
|
+
}
|
|
50
49
|
}
|
|
51
50
|
else if (eventType === 'error') {
|
|
52
51
|
let parsed = {};
|
|
@@ -67,22 +66,47 @@ function dispatchSSEMessage(msg, handlers, finalDataRef) {
|
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Parse complete SSE blocks from `buffer`, dispatch events, return trailing incomplete bytes.
|
|
71
|
+
*/
|
|
72
|
+
export function consumeSSEBuffer(buffer, handlers, finalDataRef, streamState = { streamText: '' }) {
|
|
73
|
+
const normalized = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
74
|
+
const parts = normalized.split('\n\n');
|
|
75
|
+
const remainder = parts.pop() ?? '';
|
|
76
|
+
for (const block of parts) {
|
|
77
|
+
dispatchSSEMessage(block, handlers, finalDataRef, streamState);
|
|
74
78
|
}
|
|
79
|
+
return remainder;
|
|
80
|
+
}
|
|
81
|
+
function flushSSEBuffer(remainder, handlers, finalDataRef, streamState) {
|
|
82
|
+
if (!remainder.trim())
|
|
83
|
+
return;
|
|
84
|
+
dispatchSSEMessage(remainder, handlers, finalDataRef, streamState);
|
|
85
|
+
}
|
|
86
|
+
function finalizeSSEParse(finalDataRef) {
|
|
75
87
|
if (finalDataRef.value !== null && finalDataRef.value !== undefined) {
|
|
76
88
|
return finalDataRef.value;
|
|
77
89
|
}
|
|
78
90
|
throw new Error('SSE stream ended without done event');
|
|
79
91
|
}
|
|
80
|
-
|
|
92
|
+
function parseSSEPayload(raw, handlers) {
|
|
93
|
+
const finalDataRef = { value: null };
|
|
94
|
+
const streamState = { streamText: '' };
|
|
95
|
+
const remainder = consumeSSEBuffer(raw, handlers, finalDataRef, streamState);
|
|
96
|
+
flushSSEBuffer(remainder, handlers, finalDataRef, streamState);
|
|
97
|
+
return finalizeSSEParse(finalDataRef);
|
|
98
|
+
}
|
|
99
|
+
/** Parse a complete SSE payload (e.g. React Native fallback after `response.text()`). */
|
|
81
100
|
export function parseSSEText(text, handlers) {
|
|
82
101
|
return parseSSEPayload(text, handlers);
|
|
83
102
|
}
|
|
84
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Incrementally parse Agent SSE from a ReadableStream (`text_delta`, PII steps, final `done`).
|
|
105
|
+
* Dispatches handlers as chunks arrive (required for RN chat UIs).
|
|
106
|
+
*/
|
|
85
107
|
export async function parseSSEResponse(body, handlers) {
|
|
108
|
+
const finalDataRef = { value: null };
|
|
109
|
+
const streamState = { streamText: '' };
|
|
86
110
|
const reader = body.getReader();
|
|
87
111
|
const decoder = new TextDecoder();
|
|
88
112
|
let buffer = '';
|
|
@@ -91,7 +115,10 @@ export async function parseSSEResponse(body, handlers) {
|
|
|
91
115
|
if (done)
|
|
92
116
|
break;
|
|
93
117
|
buffer += decoder.decode(value, { stream: true });
|
|
118
|
+
buffer = consumeSSEBuffer(buffer, handlers, finalDataRef, streamState);
|
|
94
119
|
}
|
|
95
120
|
buffer += decoder.decode();
|
|
96
|
-
|
|
121
|
+
buffer = consumeSSEBuffer(buffer, handlers, finalDataRef, streamState);
|
|
122
|
+
flushSSEBuffer(buffer, handlers, finalDataRef, streamState);
|
|
123
|
+
return finalizeSSEParse(finalDataRef);
|
|
97
124
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { setApiHosts, getAiAgentApiHost, getAitServiceApiHost } from './api/hosts';
|
|
2
2
|
export { createNxtlinqApiWithDeps, type CoreAITApi, type ApiClientDeps, } from './api/nxtlinq-api';
|
|
3
|
-
export { parseSSEResponse, parseSSEText } from './api/parse-sse';
|
|
3
|
+
export { consumeSSEBuffer, parseSSEResponse, parseSSEText, type SSEHandlers, } from './api/parse-sse';
|
|
4
4
|
export { postTextTts, buildTextTtsDataUri, type PostTextTtsParams, type PostTextTtsResult, } from './api/tts';
|
|
5
5
|
export * from './ports';
|
|
6
6
|
export { STORAGE_KEYS } from './constants/storageKeys';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAGnB,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,mBAAmB,iBAAiB,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGnF,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,cAAc,eAAe,CAAC;AAC9B,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,6BAA6B,GAC9B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,yBAAyB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,YAAY,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EACL,4BAA4B,EAC5B,mCAAmC,EACnC,yBAAyB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,2BAA2B,EAC3B,mCAAmC,EACnC,gCAAgC,EAChC,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAGtE,OAAO,EAAE,YAAY,EAAE,KAAK,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACxG,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// API
|
|
2
2
|
export { setApiHosts, getAiAgentApiHost, getAitServiceApiHost } from './api/hosts';
|
|
3
3
|
export { createNxtlinqApiWithDeps, } from './api/nxtlinq-api';
|
|
4
|
-
export { parseSSEResponse, parseSSEText } from './api/parse-sse';
|
|
4
|
+
export { consumeSSEBuffer, parseSSEResponse, parseSSEText, } from './api/parse-sse';
|
|
5
5
|
export { postTextTts, buildTextTtsDataUri, } from './api/tts';
|
|
6
6
|
// Ports
|
|
7
7
|
export * from './ports';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytexbyte/nxtlinq-ai-agent-core-development",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Platform-agnostic nxtlinq AI Agent core — API client, types, and orchestration ports",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
package/src/api/nxtlinq-api.ts
CHANGED
|
@@ -220,7 +220,7 @@ const createAgentApi = (helpers: ReturnType<typeof createApiHelpers>) => ({
|
|
|
220
220
|
params.onPiiProgress || params.onStreamDelta || model.endsWith('-stream')
|
|
221
221
|
);
|
|
222
222
|
|
|
223
|
-
const
|
|
223
|
+
const fetchInit: RequestInit = {
|
|
224
224
|
method: 'POST',
|
|
225
225
|
headers: {
|
|
226
226
|
'Content-Type': 'application/json',
|
|
@@ -230,7 +230,14 @@ const createAgentApi = (helpers: ReturnType<typeof createApiHelpers>) => ({
|
|
|
230
230
|
...(params.onPiiProgress ? { 'X-Stream-PII': '1' } : {}),
|
|
231
231
|
},
|
|
232
232
|
body: JSON.stringify(requestBody),
|
|
233
|
-
|
|
233
|
+
// React Native: enable incremental body reads for `*-stream` SSE routes.
|
|
234
|
+
...(useSSE ? { reactNative: { textStreaming: true } } : {}),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const response = await helpers.fetchFn(
|
|
238
|
+
`${getAiAgentApiHost()}/api/${model}`,
|
|
239
|
+
fetchInit as RequestInit,
|
|
240
|
+
);
|
|
234
241
|
|
|
235
242
|
if (!response.ok) {
|
|
236
243
|
throw new Error('Failed to send message');
|
|
@@ -243,13 +250,14 @@ const createAgentApi = (helpers: ReturnType<typeof createApiHelpers>) => ({
|
|
|
243
250
|
onPiiProgress: params.onPiiProgress,
|
|
244
251
|
onStreamDelta: params.onStreamDelta,
|
|
245
252
|
};
|
|
246
|
-
|
|
247
|
-
|
|
253
|
+
const bodyStream = response.body;
|
|
254
|
+
if (bodyStream?.getReader) {
|
|
255
|
+
return await parseSSEResponse(bodyStream, sseHandlers);
|
|
256
|
+
}
|
|
248
257
|
const rawText = await response.text();
|
|
249
258
|
if (rawText.trimStart().startsWith('event:') || rawText.includes('\nevent:')) {
|
|
250
259
|
return parseSSEText(rawText, sseHandlers);
|
|
251
260
|
}
|
|
252
|
-
// Gateway returned JSON despite SSE content-type.
|
|
253
261
|
return rawText ? JSON.parse(rawText) : {};
|
|
254
262
|
}
|
|
255
263
|
|
package/src/api/parse-sse.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
export type SSEHandlers = {
|
|
2
2
|
onPiiProgress?: (step: 'scan_start' | 'scan_complete' | 'send_start' | 'done', data?: unknown) => void;
|
|
3
|
+
/** Token chunk from a `text_delta` event. */
|
|
3
4
|
onStreamDelta?: (text: string) => void;
|
|
5
|
+
/** Accumulated assistant text after each `text_delta` (convenience for chat UIs). */
|
|
6
|
+
onStreamText?: (fullText: string) => void;
|
|
4
7
|
};
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return normalized.split('\n\n').filter((block) => block.trim().length > 0);
|
|
10
|
-
}
|
|
9
|
+
type SSEParseState = {
|
|
10
|
+
streamText: string;
|
|
11
|
+
};
|
|
11
12
|
|
|
12
13
|
function dispatchSSEMessage(
|
|
13
14
|
msg: string,
|
|
14
15
|
handlers: SSEHandlers,
|
|
15
16
|
finalDataRef: { value: unknown | null },
|
|
17
|
+
streamState: SSEParseState,
|
|
16
18
|
): void {
|
|
17
19
|
if (!msg.trim()) return;
|
|
18
20
|
let eventType = '';
|
|
@@ -50,7 +52,11 @@ function dispatchSSEMessage(
|
|
|
50
52
|
/* ignore */
|
|
51
53
|
}
|
|
52
54
|
const chunk = typeof parsed.text === 'string' ? parsed.text : '';
|
|
53
|
-
|
|
55
|
+
if (chunk) {
|
|
56
|
+
handlers.onStreamDelta?.(chunk);
|
|
57
|
+
streamState.streamText += chunk;
|
|
58
|
+
handlers.onStreamText?.(streamState.streamText);
|
|
59
|
+
}
|
|
54
60
|
} else if (eventType === 'error') {
|
|
55
61
|
let parsed: { error?: string } = {};
|
|
56
62
|
try {
|
|
@@ -68,27 +74,68 @@ function dispatchSSEMessage(
|
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Parse complete SSE blocks from `buffer`, dispatch events, return trailing incomplete bytes.
|
|
79
|
+
*/
|
|
80
|
+
export function consumeSSEBuffer(
|
|
81
|
+
buffer: string,
|
|
82
|
+
handlers: SSEHandlers,
|
|
83
|
+
finalDataRef: { value: unknown | null },
|
|
84
|
+
streamState: SSEParseState = { streamText: '' },
|
|
85
|
+
): string {
|
|
86
|
+
const normalized = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
87
|
+
const parts = normalized.split('\n\n');
|
|
88
|
+
const remainder = parts.pop() ?? '';
|
|
89
|
+
|
|
90
|
+
for (const block of parts) {
|
|
91
|
+
dispatchSSEMessage(block, handlers, finalDataRef, streamState);
|
|
75
92
|
}
|
|
93
|
+
|
|
94
|
+
return remainder;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function flushSSEBuffer(
|
|
98
|
+
remainder: string,
|
|
99
|
+
handlers: SSEHandlers,
|
|
100
|
+
finalDataRef: { value: unknown | null },
|
|
101
|
+
streamState: SSEParseState,
|
|
102
|
+
): void {
|
|
103
|
+
if (!remainder.trim()) return;
|
|
104
|
+
dispatchSSEMessage(remainder, handlers, finalDataRef, streamState);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function finalizeSSEParse(
|
|
108
|
+
finalDataRef: { value: unknown | null },
|
|
109
|
+
): unknown {
|
|
76
110
|
if (finalDataRef.value !== null && finalDataRef.value !== undefined) {
|
|
77
111
|
return finalDataRef.value;
|
|
78
112
|
}
|
|
79
113
|
throw new Error('SSE stream ended without done event');
|
|
80
114
|
}
|
|
81
115
|
|
|
82
|
-
|
|
116
|
+
function parseSSEPayload(raw: string, handlers: SSEHandlers): unknown {
|
|
117
|
+
const finalDataRef = { value: null as unknown | null };
|
|
118
|
+
const streamState: SSEParseState = { streamText: '' };
|
|
119
|
+
const remainder = consumeSSEBuffer(raw, handlers, finalDataRef, streamState);
|
|
120
|
+
flushSSEBuffer(remainder, handlers, finalDataRef, streamState);
|
|
121
|
+
return finalizeSSEParse(finalDataRef);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Parse a complete SSE payload (e.g. React Native fallback after `response.text()`). */
|
|
83
125
|
export function parseSSEText(text: string, handlers: SSEHandlers): unknown {
|
|
84
126
|
return parseSSEPayload(text, handlers);
|
|
85
127
|
}
|
|
86
128
|
|
|
87
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Incrementally parse Agent SSE from a ReadableStream (`text_delta`, PII steps, final `done`).
|
|
131
|
+
* Dispatches handlers as chunks arrive (required for RN chat UIs).
|
|
132
|
+
*/
|
|
88
133
|
export async function parseSSEResponse(
|
|
89
134
|
body: ReadableStream<Uint8Array>,
|
|
90
135
|
handlers: SSEHandlers,
|
|
91
136
|
): Promise<unknown> {
|
|
137
|
+
const finalDataRef = { value: null as unknown | null };
|
|
138
|
+
const streamState: SSEParseState = { streamText: '' };
|
|
92
139
|
const reader = body.getReader();
|
|
93
140
|
const decoder = new TextDecoder();
|
|
94
141
|
let buffer = '';
|
|
@@ -97,8 +144,12 @@ export async function parseSSEResponse(
|
|
|
97
144
|
const { done, value } = await reader.read();
|
|
98
145
|
if (done) break;
|
|
99
146
|
buffer += decoder.decode(value, { stream: true });
|
|
147
|
+
buffer = consumeSSEBuffer(buffer, handlers, finalDataRef, streamState);
|
|
100
148
|
}
|
|
101
149
|
|
|
102
150
|
buffer += decoder.decode();
|
|
103
|
-
|
|
151
|
+
buffer = consumeSSEBuffer(buffer, handlers, finalDataRef, streamState);
|
|
152
|
+
flushSSEBuffer(buffer, handlers, finalDataRef, streamState);
|
|
153
|
+
|
|
154
|
+
return finalizeSSEParse(finalDataRef);
|
|
104
155
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,12 @@ export {
|
|
|
5
5
|
type CoreAITApi,
|
|
6
6
|
type ApiClientDeps,
|
|
7
7
|
} from './api/nxtlinq-api';
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
consumeSSEBuffer,
|
|
10
|
+
parseSSEResponse,
|
|
11
|
+
parseSSEText,
|
|
12
|
+
type SSEHandlers,
|
|
13
|
+
} from './api/parse-sse';
|
|
9
14
|
export {
|
|
10
15
|
postTextTts,
|
|
11
16
|
buildTextTtsDataUri,
|