10xgraph-client 0.6.0 → 1.0.0-rc.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/CHANGELOG.md +207 -0
- package/README.md +11 -5
- package/dist/agent.d.ts +17 -5
- package/dist/agent.d.ts.map +1 -1
- package/dist/cjs/agent.d.ts +33 -0
- package/dist/cjs/client.d.ts +533 -0
- package/dist/cjs/compat.d.ts +27 -0
- package/dist/cjs/endpoints/addThreadMessages.d.ts +25 -0
- package/dist/cjs/endpoints/clearThreadState.d.ts +14 -0
- package/dist/cjs/endpoints/deleteFile.d.ts +13 -0
- package/dist/cjs/endpoints/deleteMemory.d.ts +18 -0
- package/dist/cjs/endpoints/deleteThread.d.ts +18 -0
- package/dist/cjs/endpoints/deleteThreadMessage.d.ts +19 -0
- package/dist/cjs/endpoints/files.d.ts +115 -0
- package/dist/cjs/endpoints/fixGraph.d.ts +33 -0
- package/dist/cjs/endpoints/forgetMemories.d.ts +21 -0
- package/dist/cjs/endpoints/getMemory.d.ts +18 -0
- package/dist/cjs/endpoints/graph.d.ts +50 -0
- package/dist/cjs/endpoints/graphTools.d.ts +31 -0
- package/dist/cjs/endpoints/invoke.d.ts +66 -0
- package/dist/cjs/endpoints/listMemories.d.ts +24 -0
- package/dist/cjs/endpoints/metadata.d.ts +5 -0
- package/dist/cjs/endpoints/observability.d.ts +111 -0
- package/dist/cjs/endpoints/ping.d.ts +9 -0
- package/dist/cjs/endpoints/realtime.d.ts +154 -0
- package/dist/cjs/endpoints/searchMemory.d.ts +56 -0
- package/dist/cjs/endpoints/stateSchema.d.ts +49 -0
- package/dist/cjs/endpoints/stopGraph.d.ts +35 -0
- package/dist/cjs/endpoints/storeMemory.d.ts +32 -0
- package/dist/cjs/endpoints/stream.d.ts +67 -0
- package/dist/cjs/endpoints/threadDetails.d.ts +18 -0
- package/dist/cjs/endpoints/threadMessage.d.ts +16 -0
- package/dist/cjs/endpoints/threadMessages.d.ts +28 -0
- package/dist/cjs/endpoints/threadState.d.ts +13 -0
- package/dist/cjs/endpoints/threads.d.ts +35 -0
- package/dist/cjs/endpoints/updateMemory.d.ts +20 -0
- package/dist/cjs/endpoints/updateThreadMessageFeedback.d.ts +19 -0
- package/dist/cjs/endpoints/updateThreadState.d.ts +22 -0
- package/dist/cjs/endpoints/wsStream.d.ts +34 -0
- package/dist/cjs/errors.d.ts +212 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/interrupt.d.ts +45 -0
- package/dist/cjs/message.d.ts +173 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/request.d.ts +60 -0
- package/dist/cjs/tools.d.ts +80 -0
- package/dist/cjs/ws.d.ts +47 -0
- package/dist/client.d.ts +95 -40
- package/dist/client.d.ts.map +1 -1
- package/dist/endpoints/addThreadMessages.d.ts +4 -0
- package/dist/endpoints/addThreadMessages.d.ts.map +1 -1
- package/dist/endpoints/clearThreadState.d.ts.map +1 -1
- package/dist/endpoints/deleteFile.d.ts +14 -0
- package/dist/endpoints/deleteFile.d.ts.map +1 -0
- package/dist/endpoints/deleteMemory.d.ts.map +1 -1
- package/dist/endpoints/deleteThread.d.ts.map +1 -1
- package/dist/endpoints/deleteThreadMessage.d.ts.map +1 -1
- package/dist/endpoints/files.d.ts +33 -0
- package/dist/endpoints/files.d.ts.map +1 -1
- package/dist/endpoints/fixGraph.d.ts +8 -3
- package/dist/endpoints/fixGraph.d.ts.map +1 -1
- package/dist/endpoints/forgetMemories.d.ts.map +1 -1
- package/dist/endpoints/getMemory.d.ts.map +1 -1
- package/dist/endpoints/graph.d.ts +20 -8
- package/dist/endpoints/graph.d.ts.map +1 -1
- package/dist/endpoints/graphTools.d.ts.map +1 -1
- package/dist/endpoints/invoke.d.ts +8 -0
- package/dist/endpoints/invoke.d.ts.map +1 -1
- package/dist/endpoints/listMemories.d.ts +6 -0
- package/dist/endpoints/listMemories.d.ts.map +1 -1
- package/dist/endpoints/observability.d.ts +54 -0
- package/dist/endpoints/observability.d.ts.map +1 -1
- package/dist/endpoints/ping.d.ts.map +1 -1
- package/dist/endpoints/realtime.d.ts +25 -4
- package/dist/endpoints/realtime.d.ts.map +1 -1
- package/dist/endpoints/searchMemory.d.ts.map +1 -1
- package/dist/endpoints/stateSchema.d.ts.map +1 -1
- package/dist/endpoints/stopGraph.d.ts +13 -3
- package/dist/endpoints/stopGraph.d.ts.map +1 -1
- package/dist/endpoints/storeMemory.d.ts.map +1 -1
- package/dist/endpoints/stream.d.ts +8 -0
- package/dist/endpoints/stream.d.ts.map +1 -1
- package/dist/endpoints/threadDetails.d.ts.map +1 -1
- package/dist/endpoints/threadMessage.d.ts.map +1 -1
- package/dist/endpoints/threadMessages.d.ts +6 -0
- package/dist/endpoints/threadMessages.d.ts.map +1 -1
- package/dist/endpoints/threadState.d.ts.map +1 -1
- package/dist/endpoints/threads.d.ts +6 -0
- package/dist/endpoints/threads.d.ts.map +1 -1
- package/dist/endpoints/updateMemory.d.ts.map +1 -1
- package/dist/endpoints/updateThreadMessageFeedback.d.ts +20 -0
- package/dist/endpoints/updateThreadMessageFeedback.d.ts.map +1 -0
- package/dist/endpoints/updateThreadState.d.ts +6 -1
- package/dist/endpoints/updateThreadState.d.ts.map +1 -1
- package/dist/endpoints/wsStream.d.ts +2 -0
- package/dist/endpoints/wsStream.d.ts.map +1 -1
- package/dist/errors.d.ts +60 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1742 -1217
- package/dist/index.js.map +1 -1
- package/dist/interrupt.d.ts +46 -0
- package/dist/interrupt.d.ts.map +1 -0
- package/dist/request.d.ts +28 -0
- package/dist/request.d.ts.map +1 -1
- package/dist/tools.d.ts +14 -0
- package/dist/tools.d.ts.map +1 -1
- package/package.json +11 -6
- package/src/agent.ts +17 -4
- package/src/client.ts +248 -128
- package/src/endpoints/addThreadMessages.ts +15 -10
- package/src/endpoints/clearThreadState.ts +19 -20
- package/src/endpoints/deleteFile.ts +65 -0
- package/src/endpoints/deleteMemory.ts +9 -13
- package/src/endpoints/deleteThread.ts +9 -13
- package/src/endpoints/deleteThreadMessage.ts +9 -13
- package/src/endpoints/files.ts +164 -131
- package/src/endpoints/fixGraph.ts +15 -16
- package/src/endpoints/forgetMemories.ts +44 -60
- package/src/endpoints/getMemory.ts +9 -9
- package/src/endpoints/graph.ts +28 -21
- package/src/endpoints/graphTools.ts +7 -13
- package/src/endpoints/invoke.ts +105 -84
- package/src/endpoints/listMemories.ts +18 -13
- package/src/endpoints/observability.ts +77 -34
- package/src/endpoints/ping.ts +7 -13
- package/src/endpoints/realtime.ts +101 -39
- package/src/endpoints/searchMemory.ts +19 -13
- package/src/endpoints/stateSchema.ts +7 -13
- package/src/endpoints/stopGraph.ts +20 -16
- package/src/endpoints/storeMemory.ts +7 -8
- package/src/endpoints/stream.ts +179 -61
- package/src/endpoints/threadDetails.ts +9 -14
- package/src/endpoints/threadMessage.ts +9 -9
- package/src/endpoints/threadMessages.ts +15 -9
- package/src/endpoints/threadState.ts +19 -20
- package/src/endpoints/threads.ts +13 -12
- package/src/endpoints/updateMemory.ts +9 -9
- package/src/endpoints/updateThreadMessageFeedback.ts +80 -0
- package/src/endpoints/updateThreadState.ts +28 -22
- package/src/endpoints/wsStream.ts +167 -42
- package/src/errors.ts +332 -93
- package/src/index.ts +9 -0
- package/src/interrupt.ts +60 -0
- package/src/request.ts +134 -0
- package/src/tools.ts +95 -0
package/src/endpoints/stream.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { Message } from '../message.js';
|
|
2
2
|
import { AgentState } from '../agent.js';
|
|
3
3
|
import { ToolExecutor } from '../tools.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
createErrorFromResponse,
|
|
6
|
+
NetworkError,
|
|
7
|
+
RequestTimeoutError,
|
|
8
|
+
StreamError,
|
|
9
|
+
} from '../errors.js';
|
|
10
|
+
import {
|
|
11
|
+
buildHeaders,
|
|
12
|
+
getRequestCredentials,
|
|
13
|
+
linkAbortSignal,
|
|
14
|
+
RequestContext,
|
|
15
|
+
} from '../request.js';
|
|
6
16
|
|
|
7
17
|
export interface StreamContext extends RequestContext {
|
|
8
18
|
toolExecutor?: ToolExecutor;
|
|
19
|
+
/** Caller-supplied signal; aborting it cancels the request, including the open stream. */
|
|
20
|
+
signal?: AbortSignal;
|
|
9
21
|
}
|
|
10
22
|
|
|
11
23
|
// Stream Request payload (same as Invoke)
|
|
@@ -14,6 +26,12 @@ export interface StreamRequest {
|
|
|
14
26
|
initial_state?: Record<string, any>;
|
|
15
27
|
config?: Record<string, any>;
|
|
16
28
|
recursion_limit?: number;
|
|
29
|
+
/** What to do when another run is active on the same thread (server default: 'wait'). */
|
|
30
|
+
on_busy?: 'wait' | 'cancel';
|
|
31
|
+
/** Value that answers an open interrupt. Send together with `interrupt_id`. */
|
|
32
|
+
resume?: unknown;
|
|
33
|
+
/** Id of the interrupt being answered by `resume`. */
|
|
34
|
+
interrupt_id?: string;
|
|
17
35
|
response_granularity?: 'full' | 'partial' | 'low';
|
|
18
36
|
}
|
|
19
37
|
|
|
@@ -44,27 +62,60 @@ export interface StreamChunk {
|
|
|
44
62
|
timestamp?: number;
|
|
45
63
|
}
|
|
46
64
|
|
|
65
|
+
/** Hooks that let the caller run an idle timer only while it waits on the network. */
|
|
66
|
+
interface ReadHooks {
|
|
67
|
+
beforeRead: () => void;
|
|
68
|
+
afterRead: () => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const STREAM_ENDPOINT = '/v1/graph/stream';
|
|
72
|
+
|
|
47
73
|
/**
|
|
48
74
|
* Parse NDJSON stream and yield individual chunks immediately as they arrive
|
|
49
75
|
* Handles streaming response body as async iterable
|
|
50
76
|
* Supports both newline-separated and concatenated JSON objects
|
|
77
|
+
*
|
|
78
|
+
* Leaving the loop early (break, throw, return) cancels the reader so the connection closes.
|
|
79
|
+
* A body that is cut mid-chunk, or a line that is not JSON, throws StreamError instead of
|
|
80
|
+
* ending as if the run had succeeded.
|
|
51
81
|
*/
|
|
52
82
|
async function* parseStreamChunks(
|
|
53
83
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
54
|
-
debug: boolean
|
|
84
|
+
debug: boolean,
|
|
85
|
+
hooks?: ReadHooks
|
|
55
86
|
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
56
87
|
const decoder = new TextDecoder();
|
|
57
88
|
let buffer = '';
|
|
89
|
+
let completed = false;
|
|
58
90
|
|
|
59
91
|
try {
|
|
60
92
|
while (true) {
|
|
61
|
-
|
|
93
|
+
let result: ReadableStreamReadResult<Uint8Array>;
|
|
94
|
+
hooks?.beforeRead();
|
|
95
|
+
try {
|
|
96
|
+
result = await reader.read();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if ((error as Error)?.name === 'AbortError') throw error;
|
|
99
|
+
throw new StreamError(
|
|
100
|
+
`Stream connection lost: ${(error as Error)?.message ?? String(error)}`,
|
|
101
|
+
'STREAM_INTERRUPTED',
|
|
102
|
+
'unknown',
|
|
103
|
+
STREAM_ENDPOINT,
|
|
104
|
+
'POST',
|
|
105
|
+
error
|
|
106
|
+
);
|
|
107
|
+
} finally {
|
|
108
|
+
hooks?.afterRead();
|
|
109
|
+
}
|
|
110
|
+
const { done, value } = result;
|
|
62
111
|
|
|
63
112
|
if (done) {
|
|
64
113
|
// Process any remaining data in buffer
|
|
114
|
+
buffer += decoder.decode();
|
|
65
115
|
if (buffer.trim()) {
|
|
66
116
|
yield* processBuffer(buffer, debug, true);
|
|
67
117
|
}
|
|
118
|
+
completed = true;
|
|
68
119
|
break;
|
|
69
120
|
}
|
|
70
121
|
|
|
@@ -72,14 +123,33 @@ async function* parseStreamChunks(
|
|
|
72
123
|
buffer += decoder.decode(value, { stream: true });
|
|
73
124
|
|
|
74
125
|
// Immediately process and yield all complete chunks
|
|
75
|
-
const
|
|
76
|
-
buffer =
|
|
126
|
+
const processed = yield* processBuffer(buffer, debug, false);
|
|
127
|
+
buffer = processed.remaining;
|
|
77
128
|
}
|
|
78
129
|
} finally {
|
|
130
|
+
if (!completed) {
|
|
131
|
+
// Early exit: tell the server side to stop sending so the connection is released.
|
|
132
|
+
try {
|
|
133
|
+
await reader.cancel();
|
|
134
|
+
} catch {
|
|
135
|
+
/* already errored or closed */
|
|
136
|
+
}
|
|
137
|
+
}
|
|
79
138
|
reader.releaseLock();
|
|
80
139
|
}
|
|
81
140
|
}
|
|
82
141
|
|
|
142
|
+
function badChunk(text: string, cause: unknown): StreamError {
|
|
143
|
+
return new StreamError(
|
|
144
|
+
`Stream data could not be parsed (${text.slice(0, 100)}); the stream was cut or corrupted`,
|
|
145
|
+
'STREAM_TRUNCATED',
|
|
146
|
+
'unknown',
|
|
147
|
+
STREAM_ENDPOINT,
|
|
148
|
+
'POST',
|
|
149
|
+
cause
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
83
153
|
/**
|
|
84
154
|
* Process buffer and yield all complete JSON chunks
|
|
85
155
|
* Returns the remaining incomplete buffer
|
|
@@ -103,9 +173,7 @@ function* processBuffer(
|
|
|
103
173
|
}
|
|
104
174
|
yield chunk;
|
|
105
175
|
} catch (error) {
|
|
106
|
-
|
|
107
|
-
console.warn('TenxGraphClient: Failed to parse NDJSON line:', line.slice(0, 100), error);
|
|
108
|
-
}
|
|
176
|
+
throw badChunk(line, error);
|
|
109
177
|
}
|
|
110
178
|
}
|
|
111
179
|
}
|
|
@@ -122,14 +190,7 @@ function* processBuffer(
|
|
|
122
190
|
yield chunk;
|
|
123
191
|
buffer = extracted.remaining;
|
|
124
192
|
} catch (error) {
|
|
125
|
-
|
|
126
|
-
console.warn(
|
|
127
|
-
'TenxGraphClient: Failed to parse concatenated JSON:',
|
|
128
|
-
extracted.json.slice(0, 100),
|
|
129
|
-
error
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
193
|
+
throw badChunk(extracted.json, error);
|
|
133
194
|
}
|
|
134
195
|
} else {
|
|
135
196
|
// Incomplete JSON, keep in buffer
|
|
@@ -143,13 +204,7 @@ function* processBuffer(
|
|
|
143
204
|
yield chunk;
|
|
144
205
|
buffer = '';
|
|
145
206
|
} catch (error) {
|
|
146
|
-
|
|
147
|
-
console.warn(
|
|
148
|
-
'TenxGraphClient: Failed to parse final buffer:',
|
|
149
|
-
buffer.slice(0, 100),
|
|
150
|
-
error
|
|
151
|
-
);
|
|
152
|
-
}
|
|
207
|
+
throw badChunk(buffer.trim(), error);
|
|
153
208
|
}
|
|
154
209
|
}
|
|
155
210
|
break;
|
|
@@ -246,7 +301,10 @@ export async function* streamInvoke(
|
|
|
246
301
|
context: StreamContext,
|
|
247
302
|
request: StreamRequest
|
|
248
303
|
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
249
|
-
const recursion_limit = request.recursion_limit
|
|
304
|
+
const recursion_limit = request.recursion_limit ?? 25;
|
|
305
|
+
// At least one request is always sent; the limit caps tool round trips after that.
|
|
306
|
+
const maxRuns = Math.max(1, recursion_limit);
|
|
307
|
+
const timeoutMs = context.timeout > 0 ? context.timeout : 0;
|
|
250
308
|
|
|
251
309
|
if (context.debug) {
|
|
252
310
|
console.debug('TenxGraphClient: Starting stream invoke with recursion_limit:', recursion_limit);
|
|
@@ -256,15 +314,20 @@ export async function* streamInvoke(
|
|
|
256
314
|
let currentMessages = request.messages;
|
|
257
315
|
let iterations = 0;
|
|
258
316
|
const initial_state = request.initial_state;
|
|
259
|
-
|
|
317
|
+
// Copied so a server-assigned thread_id can be added without touching the caller's object
|
|
318
|
+
let config = request.config;
|
|
260
319
|
const response_granularity = request.response_granularity;
|
|
320
|
+
const on_busy = request.on_busy;
|
|
321
|
+
const resume = request.resume;
|
|
322
|
+
const interrupt_id = request.interrupt_id;
|
|
261
323
|
|
|
262
324
|
// Main recursion loop (same as invoke, but using streaming)
|
|
263
|
-
while (
|
|
325
|
+
while (true) {
|
|
326
|
+
context.signal?.throwIfAborted();
|
|
264
327
|
iterations++;
|
|
265
328
|
|
|
266
329
|
if (context.debug) {
|
|
267
|
-
console.debug(`TenxGraphClient: Stream iteration ${iterations}/${
|
|
330
|
+
console.debug(`TenxGraphClient: Stream iteration ${iterations}/${maxRuns}`);
|
|
268
331
|
}
|
|
269
332
|
|
|
270
333
|
// Prepare request for this iteration
|
|
@@ -273,6 +336,9 @@ export async function* streamInvoke(
|
|
|
273
336
|
initial_state: iterations === 1 ? initial_state : undefined,
|
|
274
337
|
config,
|
|
275
338
|
recursion_limit,
|
|
339
|
+
on_busy,
|
|
340
|
+
resume: iterations === 1 ? resume : undefined,
|
|
341
|
+
interrupt_id: iterations === 1 ? interrupt_id : undefined,
|
|
276
342
|
response_granularity,
|
|
277
343
|
};
|
|
278
344
|
|
|
@@ -283,30 +349,52 @@ export async function* streamInvoke(
|
|
|
283
349
|
);
|
|
284
350
|
}
|
|
285
351
|
|
|
286
|
-
// Open the stream and yield all chunks
|
|
352
|
+
// Open the stream and yield all chunks. The timeout is an idle timeout: it runs while the
|
|
353
|
+
// client waits on the network (headers, then each body read) and is paused while the
|
|
354
|
+
// caller processes a chunk, so a stalled proxy is cut but a slow consumer is not.
|
|
287
355
|
const controller = new AbortController();
|
|
288
|
-
|
|
356
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
357
|
+
const disarm = (): void => {
|
|
358
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
359
|
+
timer = undefined;
|
|
360
|
+
};
|
|
361
|
+
const arm = (): void => {
|
|
362
|
+
disarm();
|
|
363
|
+
if (timeoutMs > 0) timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
364
|
+
};
|
|
365
|
+
const unlink = linkAbortSignal(controller, context.signal);
|
|
366
|
+
let finished = false;
|
|
367
|
+
let sawError = false;
|
|
289
368
|
|
|
290
369
|
try {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
370
|
+
arm();
|
|
371
|
+
let response: Response;
|
|
372
|
+
try {
|
|
373
|
+
response = await fetch(`${context.baseUrl}${STREAM_ENDPOINT}`, {
|
|
374
|
+
method: 'POST',
|
|
375
|
+
headers: buildHeaders(context as RequestContext, {
|
|
376
|
+
'Content-Type': 'application/json',
|
|
377
|
+
Accept: 'application/x-ndjson, application/json',
|
|
378
|
+
}),
|
|
379
|
+
...getRequestCredentials(context as RequestContext),
|
|
380
|
+
body: JSON.stringify(iterationRequest),
|
|
381
|
+
signal: controller.signal,
|
|
382
|
+
});
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (error instanceof TypeError) {
|
|
385
|
+
throw new NetworkError(`Network error: ${error.message}`, STREAM_ENDPOINT, 'POST', error);
|
|
386
|
+
}
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
disarm();
|
|
303
390
|
|
|
304
391
|
if (!response.ok) {
|
|
305
392
|
console.warn(`TenxGraphClient: Stream failed with HTTP ${response.status}`);
|
|
393
|
+
arm();
|
|
306
394
|
const error = await createErrorFromResponse(
|
|
307
395
|
response,
|
|
308
396
|
'Stream request failed',
|
|
309
|
-
|
|
397
|
+
STREAM_ENDPOINT,
|
|
310
398
|
'POST'
|
|
311
399
|
);
|
|
312
400
|
throw error;
|
|
@@ -320,15 +408,31 @@ export async function* streamInvoke(
|
|
|
320
408
|
const responseMessages: Message[] = [];
|
|
321
409
|
const reader = response.body.getReader();
|
|
322
410
|
|
|
323
|
-
for await (const chunk of parseStreamChunks(reader, context.debug
|
|
411
|
+
for await (const chunk of parseStreamChunks(reader, context.debug, {
|
|
412
|
+
beforeRead: arm,
|
|
413
|
+
afterRead: disarm,
|
|
414
|
+
})) {
|
|
324
415
|
// Yield every chunk to the caller
|
|
325
416
|
yield chunk;
|
|
326
417
|
|
|
418
|
+
// The server assigns a thread id when the caller omits one; reuse it so the
|
|
419
|
+
// tool round-trip stays on the same thread. A caller-supplied id is never replaced.
|
|
420
|
+
const returnedThreadId: string | undefined = chunk.thread_id || chunk.metadata?.thread_id;
|
|
421
|
+
if (returnedThreadId && !config?.thread_id) {
|
|
422
|
+
config = { ...(config ?? {}), thread_id: returnedThreadId };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// An error chunk (including a shutdown cut with data.interrupted) ends the run.
|
|
426
|
+
if (chunk.event === StreamEventType.ERROR) {
|
|
427
|
+
sawError = true;
|
|
428
|
+
}
|
|
429
|
+
|
|
327
430
|
// Also collect messages for tool execution check
|
|
328
431
|
if (chunk.message) {
|
|
329
432
|
responseMessages.push(chunk.message);
|
|
330
433
|
}
|
|
331
434
|
}
|
|
435
|
+
finished = true;
|
|
332
436
|
|
|
333
437
|
if (context.debug) {
|
|
334
438
|
console.info('TenxGraphClient: Stream iteration completed');
|
|
@@ -338,7 +442,24 @@ export async function* streamInvoke(
|
|
|
338
442
|
// Check if there are remote tool calls to execute
|
|
339
443
|
const hasToolCalls = hasRemoteToolCalls(responseMessages);
|
|
340
444
|
|
|
341
|
-
if (hasToolCalls && context.toolExecutor) {
|
|
445
|
+
if (hasToolCalls && context.toolExecutor && !sawError) {
|
|
446
|
+
if (iterations >= maxRuns) {
|
|
447
|
+
// The limit stops the loop with the server thread still waiting for tool results.
|
|
448
|
+
// Say so in the stream instead of ending as if the run were complete.
|
|
449
|
+
if (context.debug) {
|
|
450
|
+
console.warn(`TenxGraphClient: Recursion limit of ${recursion_limit} reached`);
|
|
451
|
+
}
|
|
452
|
+
yield {
|
|
453
|
+
event: StreamEventType.ERROR,
|
|
454
|
+
data: {
|
|
455
|
+
recursion_limit_reached: true,
|
|
456
|
+
message: `Recursion limit of ${recursion_limit} reached with tool calls still pending`,
|
|
457
|
+
},
|
|
458
|
+
thread_id: config?.thread_id,
|
|
459
|
+
};
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
|
|
342
463
|
if (context.debug) {
|
|
343
464
|
console.debug('TenxGraphClient: Found remote tool calls, executing...');
|
|
344
465
|
}
|
|
@@ -363,22 +484,23 @@ export async function* streamInvoke(
|
|
|
363
484
|
break;
|
|
364
485
|
}
|
|
365
486
|
} catch (error) {
|
|
366
|
-
clearTimeout(timeoutId);
|
|
367
|
-
|
|
368
487
|
if ((error as Error).name === 'AbortError') {
|
|
488
|
+
if (context.signal?.aborted) {
|
|
489
|
+
// Cancelled by the caller, not a timeout: surface the abort as is.
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
369
492
|
console.warn(`TenxGraphClient: Stream timeout after ${context.timeout}ms`);
|
|
370
|
-
throw new
|
|
493
|
+
throw new RequestTimeoutError(context.timeout, STREAM_ENDPOINT, 'POST', error);
|
|
371
494
|
}
|
|
372
495
|
|
|
373
496
|
console.error('TenxGraphClient: Stream failed:', error);
|
|
374
497
|
throw error;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
console.warn(`TenxGraphClient: Recursion limit of ${recursion_limit} reached`);
|
|
498
|
+
} finally {
|
|
499
|
+
disarm();
|
|
500
|
+
unlink();
|
|
501
|
+
// Early exit (the caller broke out of the loop, or an error): cancel the request so
|
|
502
|
+
// the connection closes and the server run releases its thread lock.
|
|
503
|
+
if (!finished) controller.abort();
|
|
382
504
|
}
|
|
383
505
|
}
|
|
384
506
|
|
|
@@ -388,7 +510,7 @@ export async function* streamInvoke(
|
|
|
388
510
|
}
|
|
389
511
|
|
|
390
512
|
/**
|
|
391
|
-
* Clean content blocks by removing empty arrays
|
|
513
|
+
* Clean content blocks by removing undefined values (empty arrays are kept)
|
|
392
514
|
* Server requires array of content blocks, not simplified string
|
|
393
515
|
*/
|
|
394
516
|
function cleanContent(content: any[]): any[] {
|
|
@@ -397,11 +519,7 @@ function cleanContent(content: any[]): any[] {
|
|
|
397
519
|
const cleaned: any = {};
|
|
398
520
|
|
|
399
521
|
for (const [key, value] of Object.entries(block)) {
|
|
400
|
-
// Skip empty arrays
|
|
401
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
// Skip undefined values
|
|
522
|
+
// Skip undefined values (empty arrays are real data, e.g. a tool result of [])
|
|
405
523
|
if (value === undefined) {
|
|
406
524
|
continue;
|
|
407
525
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ResponseMetadata } from './metadata.js';
|
|
2
2
|
import { createErrorFromResponse } from '../errors.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildHeaders,
|
|
5
|
+
getRequestCredentials,
|
|
6
|
+
RequestContext,
|
|
7
|
+
fetchWithTimeout,
|
|
8
|
+
encodePathSegment,
|
|
9
|
+
} from '../request.js';
|
|
4
10
|
|
|
5
11
|
export interface ThreadDetailsContext extends RequestContext {}
|
|
6
12
|
|
|
@@ -28,23 +34,17 @@ export async function threadDetails(
|
|
|
28
34
|
console.debug(`TenxGraphClient: Fetching thread details for thread ${threadId}`);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
const
|
|
32
|
-
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
37
|
+
const url = `${context.baseUrl}/v1/threads/${encodePathSegment(threadId)}`;
|
|
33
38
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const response = await fetch(url, {
|
|
39
|
+
const response = await fetchWithTimeout(context, url, {
|
|
37
40
|
method: 'GET',
|
|
38
41
|
headers: buildHeaders(context as RequestContext, {
|
|
39
42
|
'Content-Type': 'application/json',
|
|
40
43
|
accept: 'application/json',
|
|
41
44
|
}),
|
|
42
45
|
...getRequestCredentials(context as RequestContext),
|
|
43
|
-
signal: controller.signal,
|
|
44
46
|
});
|
|
45
47
|
|
|
46
|
-
clearTimeout(timeoutId);
|
|
47
|
-
|
|
48
48
|
if (!response.ok) {
|
|
49
49
|
console.warn(`TenxGraphClient: Thread details fetch failed with HTTP ${response.status}`);
|
|
50
50
|
const error = await createErrorFromResponse(
|
|
@@ -64,11 +64,6 @@ export async function threadDetails(
|
|
|
64
64
|
|
|
65
65
|
return data;
|
|
66
66
|
} catch (error) {
|
|
67
|
-
if ((error as Error).name === 'AbortError') {
|
|
68
|
-
console.warn(`TenxGraphClient: Thread details fetch timeout after ${context.timeout}ms`);
|
|
69
|
-
throw new Error(`Request timeout after ${context.timeout}ms`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
67
|
if (context.debug) {
|
|
73
68
|
console.debug('TenxGraphClient: Thread details fetch failed:', error);
|
|
74
69
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { ResponseMetadata } from './metadata.js';
|
|
2
2
|
import { Message } from '../message.js';
|
|
3
3
|
import { createErrorFromResponse } from '../errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildHeaders,
|
|
6
|
+
getRequestCredentials,
|
|
7
|
+
RequestContext,
|
|
8
|
+
fetchWithTimeout,
|
|
9
|
+
encodePathSegment,
|
|
10
|
+
} from '../request.js';
|
|
5
11
|
|
|
6
12
|
export interface ThreadMessageContext extends RequestContext {}
|
|
7
13
|
|
|
@@ -33,22 +39,16 @@ export async function threadMessage(
|
|
|
33
39
|
);
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
const
|
|
37
|
-
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
42
|
+
const url = `${context.baseUrl}/v1/threads/${encodePathSegment(request.threadId)}/messages/${encodePathSegment(request.messageId)}`;
|
|
38
43
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const response = await fetch(url, {
|
|
44
|
+
const response = await fetchWithTimeout(context, url, {
|
|
42
45
|
method: 'GET',
|
|
43
46
|
headers: buildHeaders(context as RequestContext, {
|
|
44
47
|
'Content-Type': 'application/json',
|
|
45
48
|
}),
|
|
46
49
|
...getRequestCredentials(context as RequestContext),
|
|
47
|
-
signal: controller.signal,
|
|
48
50
|
});
|
|
49
51
|
|
|
50
|
-
clearTimeout(timeoutId);
|
|
51
|
-
|
|
52
52
|
if (!response.ok) {
|
|
53
53
|
console.warn(`TenxGraphClient: Thread message fetch failed with HTTP ${response.status}`);
|
|
54
54
|
const error = await createErrorFromResponse(
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { ResponseMetadata } from './metadata.js';
|
|
2
2
|
import { Message } from '../message.js';
|
|
3
3
|
import { createErrorFromResponse } from '../errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildHeaders,
|
|
6
|
+
getRequestCredentials,
|
|
7
|
+
RequestContext,
|
|
8
|
+
fetchWithTimeout,
|
|
9
|
+
encodePathSegment,
|
|
10
|
+
} from '../request.js';
|
|
5
11
|
|
|
6
12
|
export interface ThreadMessagesContext extends RequestContext {}
|
|
7
13
|
|
|
@@ -14,6 +20,12 @@ export interface ThreadMessagesRequest {
|
|
|
14
20
|
|
|
15
21
|
export interface ThreadMessagesData {
|
|
16
22
|
messages: Message[];
|
|
23
|
+
/** True when more messages exist after this page. Absent on servers older than 1.0.0. */
|
|
24
|
+
has_more?: boolean;
|
|
25
|
+
/** Offset the server applied. */
|
|
26
|
+
offset?: number;
|
|
27
|
+
/** Limit the server applied (after its default and maximum clamp). */
|
|
28
|
+
limit?: number;
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
// Backward compatibility alias
|
|
@@ -36,9 +48,6 @@ export async function threadMessages(
|
|
|
36
48
|
console.debug('TenxGraphClient: Fetching thread messages for thread', request.threadId);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
const controller = new AbortController();
|
|
40
|
-
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
41
|
-
|
|
42
51
|
// Build query parameters
|
|
43
52
|
const params = new URLSearchParams();
|
|
44
53
|
if (request.search !== undefined) {
|
|
@@ -52,19 +61,16 @@ export async function threadMessages(
|
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
const queryString = params.toString();
|
|
55
|
-
const url = `${context.baseUrl}/v1/threads/${request.threadId}/messages${queryString ? '?' + queryString : ''}`;
|
|
64
|
+
const url = `${context.baseUrl}/v1/threads/${encodePathSegment(request.threadId)}/messages${queryString ? '?' + queryString : ''}`;
|
|
56
65
|
|
|
57
|
-
const response = await
|
|
66
|
+
const response = await fetchWithTimeout(context, url, {
|
|
58
67
|
method: 'GET',
|
|
59
68
|
headers: buildHeaders(context as RequestContext, {
|
|
60
69
|
'Content-Type': 'application/json',
|
|
61
70
|
}),
|
|
62
71
|
...getRequestCredentials(context as RequestContext),
|
|
63
|
-
signal: controller.signal,
|
|
64
72
|
});
|
|
65
73
|
|
|
66
|
-
clearTimeout(timeoutId);
|
|
67
|
-
|
|
68
74
|
if (!response.ok) {
|
|
69
75
|
console.warn(
|
|
70
76
|
`TenxGraphClient: Checkpoint messages fetch failed with HTTP ${response.status}`
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { ResponseMetadata } from './metadata.js';
|
|
2
2
|
import { AgentState } from '../agent.js';
|
|
3
3
|
import { createErrorFromResponse } from '../errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildHeaders,
|
|
6
|
+
getRequestCredentials,
|
|
7
|
+
RequestContext,
|
|
8
|
+
fetchWithTimeout,
|
|
9
|
+
encodePathSegment,
|
|
10
|
+
} from '../request.js';
|
|
5
11
|
|
|
6
12
|
export interface ThreadStateContext extends RequestContext {}
|
|
7
13
|
|
|
@@ -23,20 +29,18 @@ export async function threadState(
|
|
|
23
29
|
console.debug(`TenxGraphClient: Fetching thread state for thread ${threadId}`);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
clearTimeout(timeoutId);
|
|
32
|
+
const response = await fetchWithTimeout(
|
|
33
|
+
context,
|
|
34
|
+
`${context.baseUrl}/v1/threads/${encodePathSegment(threadId)}/state`,
|
|
35
|
+
{
|
|
36
|
+
method: 'GET',
|
|
37
|
+
headers: buildHeaders(context as RequestContext, {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
accept: 'application/json',
|
|
40
|
+
}),
|
|
41
|
+
...getRequestCredentials(context as RequestContext),
|
|
42
|
+
}
|
|
43
|
+
);
|
|
40
44
|
|
|
41
45
|
if (!response.ok) {
|
|
42
46
|
console.warn(`TenxGraphClient: Thread state fetch failed with HTTP ${response.status}`);
|
|
@@ -60,11 +64,6 @@ export async function threadState(
|
|
|
60
64
|
|
|
61
65
|
return data;
|
|
62
66
|
} catch (error) {
|
|
63
|
-
if ((error as Error).name === 'AbortError') {
|
|
64
|
-
console.warn(`TenxGraphClient: Thread state fetch timeout after ${context.timeout}ms`);
|
|
65
|
-
throw new Error(`Request timeout after ${context.timeout}ms`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
67
|
if (context.debug) {
|
|
69
68
|
console.debug(`TenxGraphClient: Thread state fetch failed:`, error);
|
|
70
69
|
}
|
package/src/endpoints/threads.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { ResponseMetadata } from './metadata.js';
|
|
2
2
|
import { createErrorFromResponse } from '../errors.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buildHeaders,
|
|
5
|
+
getRequestCredentials,
|
|
6
|
+
RequestContext,
|
|
7
|
+
fetchWithTimeout,
|
|
8
|
+
} from '../request.js';
|
|
4
9
|
|
|
5
10
|
export interface ThreadsContext extends RequestContext {}
|
|
6
11
|
|
|
@@ -21,6 +26,12 @@ export interface ThreadItem {
|
|
|
21
26
|
|
|
22
27
|
export interface ThreadsData {
|
|
23
28
|
threads: ThreadItem[];
|
|
29
|
+
/** True when more threads exist after this page. Absent on servers older than 1.0.0. */
|
|
30
|
+
has_more?: boolean;
|
|
31
|
+
/** Offset the server applied. */
|
|
32
|
+
offset?: number;
|
|
33
|
+
/** Limit the server applied (after its default and maximum clamp). */
|
|
34
|
+
limit?: number;
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
export interface ThreadsResponse {
|
|
@@ -50,9 +61,6 @@ export async function threads(
|
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
55
|
-
|
|
56
64
|
// Build query parameters
|
|
57
65
|
const params = new URLSearchParams();
|
|
58
66
|
if (request?.search) {
|
|
@@ -68,18 +76,15 @@ export async function threads(
|
|
|
68
76
|
const queryString = params.toString();
|
|
69
77
|
const url = `${context.baseUrl}/v1/threads${queryString ? `?${queryString}` : ''}`;
|
|
70
78
|
|
|
71
|
-
const response = await
|
|
79
|
+
const response = await fetchWithTimeout(context, url, {
|
|
72
80
|
method: 'GET',
|
|
73
81
|
headers: buildHeaders(context as RequestContext, {
|
|
74
82
|
'Content-Type': 'application/json',
|
|
75
83
|
accept: 'application/json',
|
|
76
84
|
}),
|
|
77
85
|
...getRequestCredentials(context as RequestContext),
|
|
78
|
-
signal: controller.signal,
|
|
79
86
|
});
|
|
80
87
|
|
|
81
|
-
clearTimeout(timeoutId);
|
|
82
|
-
|
|
83
88
|
if (!response.ok) {
|
|
84
89
|
console.warn(`TenxGraphClient: Threads list fetch failed with HTTP ${response.status}`);
|
|
85
90
|
const error = await createErrorFromResponse(
|
|
@@ -100,10 +105,6 @@ export async function threads(
|
|
|
100
105
|
|
|
101
106
|
return data;
|
|
102
107
|
} catch (error) {
|
|
103
|
-
if ((error as Error).name === 'AbortError') {
|
|
104
|
-
console.warn(`TenxGraphClient: Threads list fetch timeout after ${context.timeout}ms`);
|
|
105
|
-
throw new Error(`Request timeout after ${context.timeout}ms`);
|
|
106
|
-
}
|
|
107
108
|
if (context.debug) {
|
|
108
109
|
console.debug('TenxGraphClient: Threads list fetch failed:', error);
|
|
109
110
|
}
|