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
|
@@ -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 UpdateMemoryContext extends RequestContext {}
|
|
6
12
|
|
|
@@ -31,10 +37,7 @@ export async function updateMemory(
|
|
|
31
37
|
console.debug('TenxGraphClient: Updating memory with ID:', request.memoryId);
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
const
|
|
35
|
-
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
36
|
-
|
|
37
|
-
const url = `${context.baseUrl}/v1/store/memories/${request.memoryId}`;
|
|
40
|
+
const url = `${context.baseUrl}/v1/store/memories/${encodePathSegment(request.memoryId)}`;
|
|
38
41
|
|
|
39
42
|
// Prepare request body
|
|
40
43
|
const body = {
|
|
@@ -48,18 +51,15 @@ export async function updateMemory(
|
|
|
48
51
|
console.debug('TenxGraphClient: Request payload:', JSON.stringify(body, null, 2));
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
const response = await
|
|
54
|
+
const response = await fetchWithTimeout(context, url, {
|
|
52
55
|
method: 'PUT',
|
|
53
56
|
headers: buildHeaders(context as RequestContext, {
|
|
54
57
|
'Content-Type': 'application/json',
|
|
55
58
|
}),
|
|
56
59
|
...getRequestCredentials(context as RequestContext),
|
|
57
60
|
body: JSON.stringify(body),
|
|
58
|
-
signal: controller.signal,
|
|
59
61
|
});
|
|
60
62
|
|
|
61
|
-
clearTimeout(timeoutId);
|
|
62
|
-
|
|
63
63
|
if (!response.ok) {
|
|
64
64
|
console.warn(`TenxGraphClient: Update memory failed with HTTP ${response.status}`);
|
|
65
65
|
const error = await createErrorFromResponse(
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ResponseMetadata } from './metadata.js';
|
|
2
|
+
import { ThreadMessageData } from './threadMessage.js';
|
|
3
|
+
import { createErrorFromResponse } from '../errors.js';
|
|
4
|
+
import {
|
|
5
|
+
buildHeaders,
|
|
6
|
+
getRequestCredentials,
|
|
7
|
+
RequestContext,
|
|
8
|
+
fetchWithTimeout,
|
|
9
|
+
encodePathSegment,
|
|
10
|
+
} from '../request.js';
|
|
11
|
+
|
|
12
|
+
export interface UpdateThreadMessageFeedbackContext extends RequestContext {}
|
|
13
|
+
|
|
14
|
+
export interface UpdateThreadMessageFeedbackRequest {
|
|
15
|
+
threadId: string | number;
|
|
16
|
+
messageId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Any JSON object. Replaces `message.metadata.feedback`. The server rejects
|
|
19
|
+
* feedback larger than 8192 serialized bytes or nested deeper than 10 levels (HTTP 422).
|
|
20
|
+
*/
|
|
21
|
+
feedback: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UpdateThreadMessageFeedbackResponse {
|
|
25
|
+
data: ThreadMessageData;
|
|
26
|
+
metadata: ResponseMetadata;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function updateThreadMessageFeedback(
|
|
30
|
+
context: UpdateThreadMessageFeedbackContext,
|
|
31
|
+
request: UpdateThreadMessageFeedbackRequest
|
|
32
|
+
): Promise<UpdateThreadMessageFeedbackResponse> {
|
|
33
|
+
try {
|
|
34
|
+
if (context.debug) {
|
|
35
|
+
console.debug(
|
|
36
|
+
'TenxGraphClient: Updating thread message feedback',
|
|
37
|
+
`thread: ${request.threadId}`,
|
|
38
|
+
`message: ${request.messageId}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const url = `${context.baseUrl}/v1/threads/${encodePathSegment(request.threadId)}/messages/${encodePathSegment(request.messageId)}`;
|
|
43
|
+
|
|
44
|
+
const response = await fetchWithTimeout(context, url, {
|
|
45
|
+
method: 'PUT',
|
|
46
|
+
headers: buildHeaders(context as RequestContext, {
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
accept: 'application/json',
|
|
49
|
+
}),
|
|
50
|
+
...getRequestCredentials(context as RequestContext),
|
|
51
|
+
body: JSON.stringify({ feedback: request.feedback }),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
console.warn(
|
|
56
|
+
`TenxGraphClient: Update thread message feedback failed with HTTP ${response.status}`
|
|
57
|
+
);
|
|
58
|
+
const error = await createErrorFromResponse(
|
|
59
|
+
response,
|
|
60
|
+
'Update thread message feedback failed',
|
|
61
|
+
'/v1/threads/{thread_id}/messages/{message_id}',
|
|
62
|
+
'PUT'
|
|
63
|
+
);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data: UpdateThreadMessageFeedbackResponse = await response.json();
|
|
68
|
+
|
|
69
|
+
if (context.debug) {
|
|
70
|
+
console.info('TenxGraphClient: Thread message feedback updated successfully', data);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return data;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (context.debug) {
|
|
76
|
+
console.debug('TenxGraphClient: Update thread message feedback failed:', error);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,13 +1,26 @@
|
|
|
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 UpdateThreadStateContext extends RequestContext {}
|
|
7
13
|
|
|
14
|
+
/**
|
|
15
|
+
* State fields the server accepts on update. `context_summary` and `execution_meta` are
|
|
16
|
+
* rejected by the server, so they are not part of this type.
|
|
17
|
+
*/
|
|
18
|
+
export type UpdatableThreadState = Partial<Omit<AgentState, 'context_summary' | 'execution_meta'>> &
|
|
19
|
+
Record<string, any>;
|
|
20
|
+
|
|
8
21
|
export interface UpdateThreadStateRequest {
|
|
9
22
|
config: Record<string, any>;
|
|
10
|
-
state:
|
|
23
|
+
state: UpdatableThreadState;
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
export interface UpdateThreadStateData {
|
|
@@ -30,21 +43,19 @@ export async function updateThreadState(
|
|
|
30
43
|
console.debug(`TenxGraphClient: Request payload:`, JSON.stringify(request, null, 2));
|
|
31
44
|
}
|
|
32
45
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
clearTimeout(timeoutId);
|
|
46
|
+
const response = await fetchWithTimeout(
|
|
47
|
+
context,
|
|
48
|
+
`${context.baseUrl}/v1/threads/${encodePathSegment(threadId)}/state`,
|
|
49
|
+
{
|
|
50
|
+
method: 'PUT',
|
|
51
|
+
headers: buildHeaders(context as RequestContext, {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
accept: 'application/json',
|
|
54
|
+
}),
|
|
55
|
+
...getRequestCredentials(context as RequestContext),
|
|
56
|
+
body: JSON.stringify(request),
|
|
57
|
+
}
|
|
58
|
+
);
|
|
48
59
|
|
|
49
60
|
if (!response.ok) {
|
|
50
61
|
console.warn(`TenxGraphClient: Thread state update failed with HTTP ${response.status}`);
|
|
@@ -68,11 +79,6 @@ export async function updateThreadState(
|
|
|
68
79
|
|
|
69
80
|
return data;
|
|
70
81
|
} catch (error) {
|
|
71
|
-
if ((error as Error).name === 'AbortError') {
|
|
72
|
-
console.warn(`TenxGraphClient: Thread state update timeout after ${context.timeout}ms`);
|
|
73
|
-
throw new Error(`Request timeout after ${context.timeout}ms`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
82
|
if (context.debug) {
|
|
77
83
|
console.debug(`TenxGraphClient: Thread state update failed:`, error);
|
|
78
84
|
}
|
|
@@ -2,6 +2,7 @@ import { Message } from '../message.js';
|
|
|
2
2
|
import { ToolExecutor } from '../tools.js';
|
|
3
3
|
import { RequestContext } from '../request.js';
|
|
4
4
|
import { buildWsUrl, openWebSocket } from '../ws.js';
|
|
5
|
+
import { RequestTimeoutError, StreamError, WebSocketError } from '../errors.js';
|
|
5
6
|
import { StreamChunk, StreamEventType, StreamRequest, serializeMessage } from './stream.js';
|
|
6
7
|
|
|
7
8
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -10,6 +11,8 @@ import { StreamChunk, StreamEventType, StreamRequest, serializeMessage } from '.
|
|
|
10
11
|
|
|
11
12
|
export interface WsStreamContext extends RequestContext {
|
|
12
13
|
toolExecutor?: ToolExecutor;
|
|
14
|
+
/** Caller-supplied signal; aborting it closes the socket and ends the stream. */
|
|
15
|
+
signal?: AbortSignal;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -23,9 +26,17 @@ interface WsGraphInput {
|
|
|
23
26
|
initial_state?: Record<string, any>;
|
|
24
27
|
config?: Record<string, any>;
|
|
25
28
|
recursion_limit?: number;
|
|
29
|
+
on_busy?: 'wait' | 'cancel';
|
|
30
|
+
resume?: unknown;
|
|
31
|
+
interrupt_id?: string;
|
|
26
32
|
response_granularity?: string;
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
const WS_ENDPOINT = '/v1/graph/ws';
|
|
36
|
+
|
|
37
|
+
/** What the socket listeners hand to the generator. */
|
|
38
|
+
type QueueItem = StreamChunk | Error | { closed: true; code: number };
|
|
39
|
+
|
|
29
40
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
41
|
// Helpers
|
|
31
42
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -70,21 +81,28 @@ export async function* wsStreamInvoke(
|
|
|
70
81
|
context: WsStreamContext,
|
|
71
82
|
request: StreamRequest
|
|
72
83
|
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
73
|
-
|
|
84
|
+
context.signal?.throwIfAborted();
|
|
85
|
+
const url = buildWsUrl(context, WS_ENDPOINT);
|
|
74
86
|
|
|
75
87
|
if (context.debug) {
|
|
76
88
|
console.debug('TenxGraphClient [WS]: connecting to', url);
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
const ws = openWebSocket(url, context);
|
|
92
|
+
const recursion_limit = request.recursion_limit ?? 25;
|
|
93
|
+
// At least one run is always sent; the limit caps tool resumes after that.
|
|
94
|
+
const maxRuns = Math.max(1, recursion_limit);
|
|
95
|
+
// The timeout is an idle timeout: it applies while waiting for the socket to open and
|
|
96
|
+
// while waiting for the next frame, never while the caller processes a chunk.
|
|
97
|
+
const idleMs = context.timeout > 0 ? context.timeout : 0;
|
|
80
98
|
|
|
81
99
|
// ── Queue bridge ──────────────────────────────────────────────────────────
|
|
82
100
|
// WebSocket is event-based; async generators are pull-based.
|
|
83
101
|
// We buffer incoming items in a queue and wake up the generator each time.
|
|
84
|
-
const queue:
|
|
102
|
+
const queue: QueueItem[] = [];
|
|
85
103
|
let wakeUp: (() => void) | null = null;
|
|
86
104
|
|
|
87
|
-
const enqueue = (item:
|
|
105
|
+
const enqueue = (item: QueueItem): void => {
|
|
88
106
|
queue.push(item);
|
|
89
107
|
if (wakeUp) {
|
|
90
108
|
const fn = wakeUp;
|
|
@@ -102,60 +120,140 @@ export async function* wsStreamInvoke(
|
|
|
102
120
|
}
|
|
103
121
|
});
|
|
104
122
|
|
|
123
|
+
/** Next queued item, or a timeout error item when nothing arrives within the idle window. */
|
|
124
|
+
const nextItem = async (): Promise<QueueItem> => {
|
|
125
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
126
|
+
if (idleMs > 0) {
|
|
127
|
+
timer = setTimeout(() => enqueue(new RequestTimeoutError(idleMs, WS_ENDPOINT, 'WS')), idleMs);
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await waitForItem();
|
|
131
|
+
} finally {
|
|
132
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
133
|
+
}
|
|
134
|
+
return queue.shift()!;
|
|
135
|
+
};
|
|
136
|
+
|
|
105
137
|
ws.addEventListener('message', (event: MessageEvent) => {
|
|
106
138
|
try {
|
|
107
139
|
const chunk = JSON.parse(event.data as string) as StreamChunk;
|
|
108
140
|
enqueue(chunk);
|
|
109
141
|
} catch (e) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
// A frame that is not JSON means the stream is corrupt; do not skip it silently.
|
|
143
|
+
enqueue(
|
|
144
|
+
new StreamError(
|
|
145
|
+
'WebSocket frame could not be parsed as JSON',
|
|
146
|
+
'STREAM_TRUNCATED',
|
|
147
|
+
'unknown',
|
|
148
|
+
WS_ENDPOINT,
|
|
149
|
+
'WS',
|
|
150
|
+
e
|
|
151
|
+
)
|
|
152
|
+
);
|
|
113
153
|
}
|
|
114
154
|
});
|
|
115
155
|
|
|
116
156
|
ws.addEventListener('error', () => {
|
|
117
|
-
enqueue(new
|
|
157
|
+
enqueue(new WebSocketError('WebSocket error', undefined, WS_ENDPOINT));
|
|
118
158
|
});
|
|
119
159
|
|
|
120
160
|
ws.addEventListener('close', (event: CloseEvent) => {
|
|
121
161
|
if (context.debug) {
|
|
122
162
|
console.debug('TenxGraphClient [WS]: connection closed, code=', event.code);
|
|
123
163
|
}
|
|
124
|
-
enqueue(
|
|
164
|
+
enqueue({ closed: true, code: event.code });
|
|
125
165
|
});
|
|
126
166
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
167
|
+
const makeAbortError = (): Error => {
|
|
168
|
+
const err = new Error('The operation was aborted');
|
|
169
|
+
err.name = 'AbortError';
|
|
170
|
+
return err;
|
|
171
|
+
};
|
|
172
|
+
const onAbort = (): void => {
|
|
173
|
+
enqueue(makeAbortError());
|
|
174
|
+
if (ws.readyState === 1 || ws.readyState === 0) {
|
|
175
|
+
ws.close(1000);
|
|
134
176
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
once: true,
|
|
138
|
-
});
|
|
139
|
-
});
|
|
177
|
+
};
|
|
178
|
+
context.signal?.addEventListener('abort', onAbort, { once: true });
|
|
140
179
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
180
|
+
try {
|
|
181
|
+
// Wait for the connection to open (or fail). Every listener added here is removed again,
|
|
182
|
+
// whichever way the wait ends.
|
|
183
|
+
await new Promise<void>((resolve, reject) => {
|
|
184
|
+
if (context.signal?.aborted) {
|
|
185
|
+
reject(makeAbortError());
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// Numeric readyState literals (OPEN===1) instead of WebSocket.OPEN: the
|
|
189
|
+
// global WebSocket may be absent on Node < 22 when a custom impl is injected.
|
|
190
|
+
if (ws.readyState === 1) {
|
|
191
|
+
resolve();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
195
|
+
const cleanup = (): void => {
|
|
196
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
197
|
+
ws.removeEventListener('open', onOpen);
|
|
198
|
+
ws.removeEventListener('error', onError);
|
|
199
|
+
ws.removeEventListener('close', onClose);
|
|
200
|
+
context.signal?.removeEventListener('abort', onConnectAbort);
|
|
201
|
+
};
|
|
202
|
+
const onOpen = (): void => {
|
|
203
|
+
cleanup();
|
|
204
|
+
resolve();
|
|
205
|
+
};
|
|
206
|
+
const onError = (): void => {
|
|
207
|
+
cleanup();
|
|
208
|
+
reject(new WebSocketError('WebSocket connection failed', undefined, WS_ENDPOINT));
|
|
209
|
+
};
|
|
210
|
+
const onClose = (e: Event): void => {
|
|
211
|
+
cleanup();
|
|
212
|
+
const code = (e as CloseEvent)?.code;
|
|
213
|
+
reject(
|
|
214
|
+
new WebSocketError(`WebSocket closed before it opened (code ${code})`, code, WS_ENDPOINT)
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
const onConnectAbort = (): void => {
|
|
218
|
+
cleanup();
|
|
219
|
+
reject(makeAbortError());
|
|
220
|
+
};
|
|
221
|
+
ws.addEventListener('open', onOpen);
|
|
222
|
+
ws.addEventListener('error', onError);
|
|
223
|
+
ws.addEventListener('close', onClose);
|
|
224
|
+
context.signal?.addEventListener('abort', onConnectAbort, { once: true });
|
|
225
|
+
if (idleMs > 0) {
|
|
226
|
+
timer = setTimeout(() => {
|
|
227
|
+
cleanup();
|
|
228
|
+
reject(new RequestTimeoutError(idleMs, WS_ENDPOINT, 'WS'));
|
|
229
|
+
}, idleMs);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
// Anything queued while connecting (an error event that raced the open) is stale.
|
|
233
|
+
queue.length = 0;
|
|
144
234
|
|
|
145
|
-
|
|
146
|
-
|
|
235
|
+
if (context.debug) {
|
|
236
|
+
console.debug('TenxGraphClient [WS]: connected');
|
|
237
|
+
}
|
|
147
238
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
239
|
+
// ── Main loop ───────────────────────────────────────────────────────────
|
|
240
|
+
let threadId: string | undefined = (request.config as Record<string, any>)?.thread_id;
|
|
241
|
+
let runs = 0;
|
|
242
|
+
|
|
243
|
+
let nextPayload: WsGraphInput = {
|
|
244
|
+
invoke_type: 'fresh',
|
|
245
|
+
messages: request.messages,
|
|
246
|
+
initial_state: request.initial_state,
|
|
247
|
+
config: request.config,
|
|
248
|
+
recursion_limit,
|
|
249
|
+
on_busy: request.on_busy,
|
|
250
|
+
resume: request.resume,
|
|
251
|
+
interrupt_id: request.interrupt_id,
|
|
252
|
+
response_granularity: request.response_granularity,
|
|
253
|
+
};
|
|
156
254
|
|
|
157
|
-
try {
|
|
158
255
|
outerLoop: while (true) {
|
|
256
|
+
runs++;
|
|
159
257
|
if (context.debug) {
|
|
160
258
|
console.debug(
|
|
161
259
|
`TenxGraphClient [WS]: sending ${nextPayload.invoke_type} request, thread_id=${threadId ?? 'new'}`
|
|
@@ -168,12 +266,16 @@ export async function* wsStreamInvoke(
|
|
|
168
266
|
const runMessages: Message[] = [];
|
|
169
267
|
|
|
170
268
|
innerLoop: while (true) {
|
|
171
|
-
await
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
269
|
+
const item = await nextItem();
|
|
270
|
+
|
|
271
|
+
if ('closed' in item) {
|
|
272
|
+
// The server closed the socket before it finished the run (1012 restart,
|
|
273
|
+
// 1013 busy, 1008 policy, 1011 error ...). Never report that as success.
|
|
274
|
+
throw new WebSocketError(
|
|
275
|
+
`WebSocket closed before the run finished (code ${item.code})`,
|
|
276
|
+
item.code,
|
|
277
|
+
WS_ENDPOINT
|
|
278
|
+
);
|
|
177
279
|
}
|
|
178
280
|
|
|
179
281
|
if (item instanceof Error) {
|
|
@@ -202,6 +304,12 @@ export async function* wsStreamInvoke(
|
|
|
202
304
|
|
|
203
305
|
// Yield every chunk to the caller (same as HTTP stream)
|
|
204
306
|
yield chunk;
|
|
307
|
+
|
|
308
|
+
// The server sends no "done" after an error chunk (busy thread, rate limit, not
|
|
309
|
+
// authorized, shutdown cut with data.interrupted). The error ends the run.
|
|
310
|
+
if (chunk.event === StreamEventType.ERROR) {
|
|
311
|
+
break outerLoop;
|
|
312
|
+
}
|
|
205
313
|
}
|
|
206
314
|
|
|
207
315
|
if (context.debug) {
|
|
@@ -212,6 +320,21 @@ export async function* wsStreamInvoke(
|
|
|
212
320
|
|
|
213
321
|
// ── Tool-call detection & resume ──────────────────────────────────
|
|
214
322
|
if (hasRemoteToolCalls(runMessages) && context.toolExecutor) {
|
|
323
|
+
if (runs >= maxRuns) {
|
|
324
|
+
if (context.debug) {
|
|
325
|
+
console.warn(`TenxGraphClient [WS]: recursion limit of ${recursion_limit} reached`);
|
|
326
|
+
}
|
|
327
|
+
yield {
|
|
328
|
+
event: StreamEventType.ERROR,
|
|
329
|
+
data: {
|
|
330
|
+
recursion_limit_reached: true,
|
|
331
|
+
message: `Recursion limit of ${recursion_limit} reached with tool calls still pending`,
|
|
332
|
+
},
|
|
333
|
+
thread_id: threadId,
|
|
334
|
+
};
|
|
335
|
+
break outerLoop;
|
|
336
|
+
}
|
|
337
|
+
|
|
215
338
|
if (context.debug) {
|
|
216
339
|
console.debug('TenxGraphClient [WS]: remote tool calls detected, executing…');
|
|
217
340
|
}
|
|
@@ -228,7 +351,8 @@ export async function* wsStreamInvoke(
|
|
|
228
351
|
invoke_type: 'resume',
|
|
229
352
|
tool_result: toolResults.map(serializeMessage),
|
|
230
353
|
config: { ...(request.config ?? {}), thread_id: threadId },
|
|
231
|
-
recursion_limit
|
|
354
|
+
recursion_limit,
|
|
355
|
+
on_busy: request.on_busy,
|
|
232
356
|
response_granularity: request.response_granularity,
|
|
233
357
|
};
|
|
234
358
|
// continue outerLoop → send resume request
|
|
@@ -238,8 +362,9 @@ export async function* wsStreamInvoke(
|
|
|
238
362
|
}
|
|
239
363
|
}
|
|
240
364
|
} finally {
|
|
365
|
+
context.signal?.removeEventListener('abort', onAbort);
|
|
241
366
|
// CONNECTING===0, OPEN===1 (numeric literals; the global WebSocket may be
|
|
242
|
-
// absent on Node <
|
|
367
|
+
// absent on Node < 22 when a custom impl is injected).
|
|
243
368
|
if (ws.readyState === 1 || ws.readyState === 0) {
|
|
244
369
|
ws.close(1000);
|
|
245
370
|
}
|