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.
Files changed (149) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +11 -5
  3. package/dist/agent.d.ts +17 -5
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/cjs/agent.d.ts +33 -0
  6. package/dist/cjs/client.d.ts +533 -0
  7. package/dist/cjs/compat.d.ts +27 -0
  8. package/dist/cjs/endpoints/addThreadMessages.d.ts +25 -0
  9. package/dist/cjs/endpoints/clearThreadState.d.ts +14 -0
  10. package/dist/cjs/endpoints/deleteFile.d.ts +13 -0
  11. package/dist/cjs/endpoints/deleteMemory.d.ts +18 -0
  12. package/dist/cjs/endpoints/deleteThread.d.ts +18 -0
  13. package/dist/cjs/endpoints/deleteThreadMessage.d.ts +19 -0
  14. package/dist/cjs/endpoints/files.d.ts +115 -0
  15. package/dist/cjs/endpoints/fixGraph.d.ts +33 -0
  16. package/dist/cjs/endpoints/forgetMemories.d.ts +21 -0
  17. package/dist/cjs/endpoints/getMemory.d.ts +18 -0
  18. package/dist/cjs/endpoints/graph.d.ts +50 -0
  19. package/dist/cjs/endpoints/graphTools.d.ts +31 -0
  20. package/dist/cjs/endpoints/invoke.d.ts +66 -0
  21. package/dist/cjs/endpoints/listMemories.d.ts +24 -0
  22. package/dist/cjs/endpoints/metadata.d.ts +5 -0
  23. package/dist/cjs/endpoints/observability.d.ts +111 -0
  24. package/dist/cjs/endpoints/ping.d.ts +9 -0
  25. package/dist/cjs/endpoints/realtime.d.ts +154 -0
  26. package/dist/cjs/endpoints/searchMemory.d.ts +56 -0
  27. package/dist/cjs/endpoints/stateSchema.d.ts +49 -0
  28. package/dist/cjs/endpoints/stopGraph.d.ts +35 -0
  29. package/dist/cjs/endpoints/storeMemory.d.ts +32 -0
  30. package/dist/cjs/endpoints/stream.d.ts +67 -0
  31. package/dist/cjs/endpoints/threadDetails.d.ts +18 -0
  32. package/dist/cjs/endpoints/threadMessage.d.ts +16 -0
  33. package/dist/cjs/endpoints/threadMessages.d.ts +28 -0
  34. package/dist/cjs/endpoints/threadState.d.ts +13 -0
  35. package/dist/cjs/endpoints/threads.d.ts +35 -0
  36. package/dist/cjs/endpoints/updateMemory.d.ts +20 -0
  37. package/dist/cjs/endpoints/updateThreadMessageFeedback.d.ts +19 -0
  38. package/dist/cjs/endpoints/updateThreadState.d.ts +22 -0
  39. package/dist/cjs/endpoints/wsStream.d.ts +34 -0
  40. package/dist/cjs/errors.d.ts +212 -0
  41. package/dist/cjs/index.d.ts +41 -0
  42. package/dist/cjs/interrupt.d.ts +45 -0
  43. package/dist/cjs/message.d.ts +173 -0
  44. package/dist/cjs/package.json +1 -0
  45. package/dist/cjs/request.d.ts +60 -0
  46. package/dist/cjs/tools.d.ts +80 -0
  47. package/dist/cjs/ws.d.ts +47 -0
  48. package/dist/client.d.ts +95 -40
  49. package/dist/client.d.ts.map +1 -1
  50. package/dist/endpoints/addThreadMessages.d.ts +4 -0
  51. package/dist/endpoints/addThreadMessages.d.ts.map +1 -1
  52. package/dist/endpoints/clearThreadState.d.ts.map +1 -1
  53. package/dist/endpoints/deleteFile.d.ts +14 -0
  54. package/dist/endpoints/deleteFile.d.ts.map +1 -0
  55. package/dist/endpoints/deleteMemory.d.ts.map +1 -1
  56. package/dist/endpoints/deleteThread.d.ts.map +1 -1
  57. package/dist/endpoints/deleteThreadMessage.d.ts.map +1 -1
  58. package/dist/endpoints/files.d.ts +33 -0
  59. package/dist/endpoints/files.d.ts.map +1 -1
  60. package/dist/endpoints/fixGraph.d.ts +8 -3
  61. package/dist/endpoints/fixGraph.d.ts.map +1 -1
  62. package/dist/endpoints/forgetMemories.d.ts.map +1 -1
  63. package/dist/endpoints/getMemory.d.ts.map +1 -1
  64. package/dist/endpoints/graph.d.ts +20 -8
  65. package/dist/endpoints/graph.d.ts.map +1 -1
  66. package/dist/endpoints/graphTools.d.ts.map +1 -1
  67. package/dist/endpoints/invoke.d.ts +8 -0
  68. package/dist/endpoints/invoke.d.ts.map +1 -1
  69. package/dist/endpoints/listMemories.d.ts +6 -0
  70. package/dist/endpoints/listMemories.d.ts.map +1 -1
  71. package/dist/endpoints/observability.d.ts +54 -0
  72. package/dist/endpoints/observability.d.ts.map +1 -1
  73. package/dist/endpoints/ping.d.ts.map +1 -1
  74. package/dist/endpoints/realtime.d.ts +25 -4
  75. package/dist/endpoints/realtime.d.ts.map +1 -1
  76. package/dist/endpoints/searchMemory.d.ts.map +1 -1
  77. package/dist/endpoints/stateSchema.d.ts.map +1 -1
  78. package/dist/endpoints/stopGraph.d.ts +13 -3
  79. package/dist/endpoints/stopGraph.d.ts.map +1 -1
  80. package/dist/endpoints/storeMemory.d.ts.map +1 -1
  81. package/dist/endpoints/stream.d.ts +8 -0
  82. package/dist/endpoints/stream.d.ts.map +1 -1
  83. package/dist/endpoints/threadDetails.d.ts.map +1 -1
  84. package/dist/endpoints/threadMessage.d.ts.map +1 -1
  85. package/dist/endpoints/threadMessages.d.ts +6 -0
  86. package/dist/endpoints/threadMessages.d.ts.map +1 -1
  87. package/dist/endpoints/threadState.d.ts.map +1 -1
  88. package/dist/endpoints/threads.d.ts +6 -0
  89. package/dist/endpoints/threads.d.ts.map +1 -1
  90. package/dist/endpoints/updateMemory.d.ts.map +1 -1
  91. package/dist/endpoints/updateThreadMessageFeedback.d.ts +20 -0
  92. package/dist/endpoints/updateThreadMessageFeedback.d.ts.map +1 -0
  93. package/dist/endpoints/updateThreadState.d.ts +6 -1
  94. package/dist/endpoints/updateThreadState.d.ts.map +1 -1
  95. package/dist/endpoints/wsStream.d.ts +2 -0
  96. package/dist/endpoints/wsStream.d.ts.map +1 -1
  97. package/dist/errors.d.ts +60 -2
  98. package/dist/errors.d.ts.map +1 -1
  99. package/dist/index.cjs +3 -3
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.d.ts +3 -0
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +1742 -1217
  104. package/dist/index.js.map +1 -1
  105. package/dist/interrupt.d.ts +46 -0
  106. package/dist/interrupt.d.ts.map +1 -0
  107. package/dist/request.d.ts +28 -0
  108. package/dist/request.d.ts.map +1 -1
  109. package/dist/tools.d.ts +14 -0
  110. package/dist/tools.d.ts.map +1 -1
  111. package/package.json +11 -6
  112. package/src/agent.ts +17 -4
  113. package/src/client.ts +248 -128
  114. package/src/endpoints/addThreadMessages.ts +15 -10
  115. package/src/endpoints/clearThreadState.ts +19 -20
  116. package/src/endpoints/deleteFile.ts +65 -0
  117. package/src/endpoints/deleteMemory.ts +9 -13
  118. package/src/endpoints/deleteThread.ts +9 -13
  119. package/src/endpoints/deleteThreadMessage.ts +9 -13
  120. package/src/endpoints/files.ts +164 -131
  121. package/src/endpoints/fixGraph.ts +15 -16
  122. package/src/endpoints/forgetMemories.ts +44 -60
  123. package/src/endpoints/getMemory.ts +9 -9
  124. package/src/endpoints/graph.ts +28 -21
  125. package/src/endpoints/graphTools.ts +7 -13
  126. package/src/endpoints/invoke.ts +105 -84
  127. package/src/endpoints/listMemories.ts +18 -13
  128. package/src/endpoints/observability.ts +77 -34
  129. package/src/endpoints/ping.ts +7 -13
  130. package/src/endpoints/realtime.ts +101 -39
  131. package/src/endpoints/searchMemory.ts +19 -13
  132. package/src/endpoints/stateSchema.ts +7 -13
  133. package/src/endpoints/stopGraph.ts +20 -16
  134. package/src/endpoints/storeMemory.ts +7 -8
  135. package/src/endpoints/stream.ts +179 -61
  136. package/src/endpoints/threadDetails.ts +9 -14
  137. package/src/endpoints/threadMessage.ts +9 -9
  138. package/src/endpoints/threadMessages.ts +15 -9
  139. package/src/endpoints/threadState.ts +19 -20
  140. package/src/endpoints/threads.ts +13 -12
  141. package/src/endpoints/updateMemory.ts +9 -9
  142. package/src/endpoints/updateThreadMessageFeedback.ts +80 -0
  143. package/src/endpoints/updateThreadState.ts +28 -22
  144. package/src/endpoints/wsStream.ts +167 -42
  145. package/src/errors.ts +332 -93
  146. package/src/index.ts +9 -0
  147. package/src/interrupt.ts +60 -0
  148. package/src/request.ts +134 -0
  149. package/src/tools.ts +95 -0
@@ -1,6 +1,11 @@
1
1
  import { ResponseMetadata } from './metadata.js';
2
2
  import { createErrorFromResponse } from '../errors.js';
3
- import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
3
+ import {
4
+ buildHeaders,
5
+ getRequestCredentials,
6
+ RequestContext,
7
+ fetchWithTimeout,
8
+ } from '../request.js';
4
9
 
5
10
  export interface GraphToolsContext extends RequestContext {}
6
11
 
@@ -41,21 +46,15 @@ export async function graphTools(context: GraphToolsContext): Promise<GraphTools
41
46
  console.debug('TenxGraphClient: Fetching graph tools from', context.baseUrl);
42
47
  }
43
48
 
44
- const controller = new AbortController();
45
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
46
-
47
- const response = await fetch(`${context.baseUrl}/v1/graph/tools`, {
49
+ const response = await fetchWithTimeout(context, `${context.baseUrl}/v1/graph/tools`, {
48
50
  method: 'GET',
49
51
  headers: buildHeaders(context as RequestContext, {
50
52
  'Content-Type': 'application/json',
51
53
  accept: 'application/json',
52
54
  }),
53
55
  ...getRequestCredentials(context as RequestContext),
54
- signal: controller.signal,
55
56
  });
56
57
 
57
- clearTimeout(timeoutId);
58
-
59
58
  if (!response.ok) {
60
59
  console.warn(`TenxGraphClient: Graph tools fetch failed with HTTP ${response.status}`);
61
60
  const error = await createErrorFromResponse(
@@ -79,11 +78,6 @@ export async function graphTools(context: GraphToolsContext): Promise<GraphTools
79
78
  console.debug('TenxGraphClient: Graph tools fetch failed:', error);
80
79
  }
81
80
 
82
- if ((error as Error).name === 'AbortError') {
83
- console.warn(`TenxGraphClient: Graph tools fetch timeout after ${context.timeout}ms`);
84
- throw new Error(`Request timeout after ${context.timeout}ms`);
85
- }
86
-
87
81
  console.error('TenxGraphClient: Graph tools fetch failed:', error);
88
82
  throw error;
89
83
  }
@@ -1,11 +1,24 @@
1
1
  import { Message } from '../message.js';
2
2
  import { AgentState } from '../agent.js';
3
3
  import { ToolExecutor } from '../tools.js';
4
- import { createErrorFromResponse } from '../errors.js';
5
- import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
4
+ import {
5
+ createErrorFromResponse,
6
+ NetworkError,
7
+ RequestTimeoutError,
8
+ StreamError,
9
+ } from '../errors.js';
10
+ import { serializeMessage } from './stream.js';
11
+ import {
12
+ buildHeaders,
13
+ getRequestCredentials,
14
+ linkAbortSignal,
15
+ RequestContext,
16
+ } from '../request.js';
6
17
 
7
18
  export interface InvokeContext extends RequestContext {
8
19
  toolExecutor?: ToolExecutor;
20
+ /** Caller-supplied signal; aborting it cancels the in-flight request. */
21
+ signal?: AbortSignal;
9
22
  }
10
23
 
11
24
  // Invoke Request payload
@@ -14,6 +27,12 @@ export interface InvokeRequest {
14
27
  initial_state?: Record<string, any>;
15
28
  config?: Record<string, any>;
16
29
  recursion_limit?: number;
30
+ /** What to do when another run is active on the same thread (server default: 'wait'). */
31
+ on_busy?: 'wait' | 'cancel';
32
+ /** Value that answers an open interrupt. Send together with `interrupt_id`. */
33
+ resume?: unknown;
34
+ /** Id of the interrupt being answered by `resume`. */
35
+ interrupt_id?: string;
17
36
  response_granularity?: 'full' | 'partial' | 'low';
18
37
  }
19
38
 
@@ -74,45 +93,78 @@ async function makeSingleInvokeCall(
74
93
  request: InvokeRequest
75
94
  ): Promise<InvokeResponse> {
76
95
  const controller = new AbortController();
77
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
96
+ // The timeout and the caller's signal stay armed until the body is read, so a server that
97
+ // sends headers and then stalls is cut off too. A timeout of 0 means no timeout.
98
+ const timeoutId =
99
+ context.timeout > 0 ? setTimeout(() => controller.abort(), context.timeout) : undefined;
100
+ const unlink = linkAbortSignal(controller, context.signal);
78
101
 
79
102
  try {
80
- const response = await fetch(`${context.baseUrl}/v1/graph/invoke`, {
81
- method: 'POST',
82
- headers: buildHeaders(context as RequestContext, {
83
- 'Content-Type': 'application/json',
84
- accept: 'application/json',
85
- }),
86
- ...getRequestCredentials(context as RequestContext),
87
- body: JSON.stringify(request),
88
- signal: controller.signal,
89
- });
90
-
91
- clearTimeout(timeoutId);
103
+ let response: Response;
104
+ try {
105
+ response = await fetch(`${context.baseUrl}/v1/graph/invoke`, {
106
+ method: 'POST',
107
+ headers: buildHeaders(context as RequestContext, {
108
+ 'Content-Type': 'application/json',
109
+ accept: 'application/json',
110
+ }),
111
+ ...getRequestCredentials(context as RequestContext),
112
+ body: JSON.stringify(request),
113
+ signal: controller.signal,
114
+ });
115
+ } catch (error) {
116
+ if (error instanceof TypeError) {
117
+ throw new NetworkError(
118
+ `Network error: ${error.message}`,
119
+ '/v1/graph/invoke',
120
+ 'POST',
121
+ error
122
+ );
123
+ }
124
+ throw error;
125
+ }
92
126
 
93
127
  if (!response.ok) {
94
128
  console.warn(`TenxGraphClient: Invoke failed with HTTP ${response.status}`);
95
129
  const error = await createErrorFromResponse(
96
130
  response,
97
131
  'Invoke request failed',
98
- '/v1/invoke',
132
+ '/v1/graph/invoke',
99
133
  'POST'
100
134
  );
101
135
  throw error;
102
136
  }
103
137
 
104
138
  const data: InvokeResponse = await response.json();
139
+ // A body without `data` (or without `messages`) must not crash the tool loop later.
140
+ if (!data || typeof data !== 'object' || !data.data || typeof data.data !== 'object') {
141
+ throw new StreamError(
142
+ 'Invoke response had no data object',
143
+ 'INVALID_RESPONSE',
144
+ 'unknown',
145
+ '/v1/graph/invoke',
146
+ 'POST'
147
+ );
148
+ }
149
+ if (!Array.isArray(data.data.messages)) {
150
+ data.data.messages = [];
151
+ }
105
152
  return data;
106
153
  } catch (error) {
107
- clearTimeout(timeoutId);
108
-
109
154
  if ((error as Error).name === 'AbortError') {
155
+ if (context.signal?.aborted) {
156
+ // Cancelled by the caller, not a timeout: surface the abort as is.
157
+ throw error;
158
+ }
110
159
  console.warn(`TenxGraphClient: Invoke timeout after ${context.timeout}ms`);
111
- throw new Error(`Request timeout after ${context.timeout}ms`);
160
+ throw new RequestTimeoutError(context.timeout, '/v1/graph/invoke', 'POST', error);
112
161
  }
113
162
 
114
163
  console.error('TenxGraphClient: Invoke failed:', error);
115
164
  throw error;
165
+ } finally {
166
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
167
+ unlink();
116
168
  }
117
169
  }
118
170
 
@@ -137,7 +189,9 @@ export async function invoke(
137
189
  request: InvokeRequest,
138
190
  onPartialResult?: InvokeCallback
139
191
  ): Promise<InvokeResult> {
140
- const recursion_limit = request.recursion_limit || 25;
192
+ const recursion_limit = request.recursion_limit ?? 25;
193
+ // At least one request is always sent; the limit caps tool round trips after that.
194
+ const maxRuns = Math.max(1, recursion_limit);
141
195
 
142
196
  if (context.debug) {
143
197
  console.debug('TenxGraphClient: Starting invoke with recursion_limit:', recursion_limit);
@@ -151,15 +205,20 @@ export async function invoke(
151
205
  let recursionLimitReached = false;
152
206
  let lastResponse: InvokeResponse | undefined;
153
207
  const initial_state = request.initial_state;
154
- const config = request.config;
208
+ // Copied so a server-assigned thread_id can be added without touching the caller's object
209
+ let config = request.config;
155
210
  const response_granularity = request.response_granularity;
211
+ const on_busy = request.on_busy;
212
+ const resume = request.resume;
213
+ const interrupt_id = request.interrupt_id;
156
214
 
157
215
  // Main recursion loop
158
- while (iterations < recursion_limit) {
216
+ while (true) {
217
+ context.signal?.throwIfAborted();
159
218
  iterations++;
160
219
 
161
220
  if (context.debug) {
162
- console.debug(`TenxGraphClient: Iteration ${iterations}/${recursion_limit}`);
221
+ console.debug(`TenxGraphClient: Iteration ${iterations}/${maxRuns}`);
163
222
  }
164
223
 
165
224
  // Prepare request for this iteration
@@ -168,6 +227,9 @@ export async function invoke(
168
227
  initial_state: iterations === 1 ? initial_state : undefined,
169
228
  config,
170
229
  recursion_limit,
230
+ on_busy,
231
+ resume: iterations === 1 ? resume : undefined,
232
+ interrupt_id: iterations === 1 ? interrupt_id : undefined,
171
233
  response_granularity,
172
234
  };
173
235
 
@@ -179,6 +241,13 @@ export async function invoke(
179
241
  const response = await makeSingleInvokeCall(context, iterationRequest);
180
242
  lastResponse = response;
181
243
 
244
+ // The server assigns a thread id when the caller omits one; reuse it so the
245
+ // tool round-trip stays on the same thread. A caller-supplied id is never replaced.
246
+ const returnedThreadId = response.data.meta?.thread_id;
247
+ if (returnedThreadId && !config?.thread_id) {
248
+ config = { ...(config ?? {}), thread_id: returnedThreadId };
249
+ }
250
+
182
251
  if (context.debug) {
183
252
  console.info('TenxGraphClient: Invoke successful');
184
253
  console.debug('TenxGraphClient: Response:', JSON.stringify(response, null, 2));
@@ -192,6 +261,9 @@ export async function invoke(
192
261
  // Check if there are remote tool calls to execute
193
262
  const hasToolCalls = hasRemoteToolCalls(response);
194
263
 
264
+ // A tool round trip follows only while there is an executor and room under the limit
265
+ const willContinue = hasToolCalls && !!context.toolExecutor && iterations < maxRuns;
266
+
195
267
  // Send partial result immediately if callback provided
196
268
  if (onPartialResult) {
197
269
  const partialResult: InvokePartialResult = {
@@ -202,12 +274,21 @@ export async function invoke(
202
274
  summary: response.data.summary,
203
275
  meta: response.data.meta,
204
276
  has_tool_calls: hasToolCalls,
205
- is_final: !hasToolCalls,
277
+ is_final: !willContinue,
206
278
  };
207
279
 
208
280
  await onPartialResult(partialResult);
209
281
  }
210
282
 
283
+ if (hasToolCalls && context.toolExecutor && iterations >= maxRuns) {
284
+ // The limit stopped a loop that still wanted tools: the server thread is left waiting
285
+ recursionLimitReached = true;
286
+ if (context.debug) {
287
+ console.warn(`TenxGraphClient: Recursion limit of ${recursion_limit} reached`);
288
+ }
289
+ break;
290
+ }
291
+
211
292
  if (hasToolCalls && context.toolExecutor) {
212
293
  if (context.debug) {
213
294
  console.debug('TenxGraphClient: Found remote tool calls, executing...');
@@ -237,14 +318,6 @@ export async function invoke(
237
318
  }
238
319
  }
239
320
 
240
- // Check if we hit the recursion limit
241
- if (iterations >= recursion_limit) {
242
- recursionLimitReached = true;
243
- if (context.debug) {
244
- console.warn(`TenxGraphClient: Recursion limit of ${recursion_limit} reached`);
245
- }
246
- }
247
-
248
321
  // Construct the result
249
322
  const result: InvokeResult = {
250
323
  messages: lastResponse?.data.messages || [],
@@ -264,55 +337,3 @@ export async function invoke(
264
337
 
265
338
  return result;
266
339
  }
267
-
268
- /**
269
- * Clean content blocks by removing empty arrays and undefined values
270
- * Server requires array of content blocks
271
- */
272
- function cleanContent(content: any[]): any[] {
273
- // Always return as array of blocks (server requires this format)
274
- return content.map((block) => {
275
- const cleaned: any = {};
276
-
277
- for (const [key, value] of Object.entries(block)) {
278
- // Skip empty arrays
279
- if (Array.isArray(value) && value.length === 0) {
280
- continue;
281
- }
282
- // Skip undefined values
283
- if (value === undefined) {
284
- continue;
285
- }
286
- cleaned[key] = value;
287
- }
288
-
289
- return cleaned;
290
- });
291
- }
292
-
293
- /**
294
- * Serialize a Message object for API transmission
295
- */
296
- function serializeMessage(message: Message): any {
297
- const serialized: any = {
298
- role: message.role,
299
- content: cleanContent(message.content),
300
- };
301
-
302
- // message_id: use "0" if not set (server will generate one)
303
- if (message.message_id !== null && message.message_id !== undefined) {
304
- serialized.message_id = String(message.message_id);
305
- } else {
306
- serialized.message_id = '0';
307
- }
308
-
309
- // Only include optional fields if they are explicitly set
310
- if (message.tools_calls) {
311
- serialized.tools_calls = message.tools_calls;
312
- }
313
- if (message.metadata && Object.keys(message.metadata).length > 0) {
314
- serialized.metadata = message.metadata;
315
- }
316
-
317
- return serialized;
318
- }
@@ -1,6 +1,11 @@
1
1
  import { ResponseMetadata } from './metadata.js';
2
2
  import { createErrorFromResponse } from '../errors.js';
3
- import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
3
+ import {
4
+ buildHeaders,
5
+ getRequestCredentials,
6
+ RequestContext,
7
+ fetchWithTimeout,
8
+ } from '../request.js';
4
9
  import { MemoryResult } from './searchMemory.js';
5
10
 
6
11
  export interface ListMemoriesContext extends RequestContext {}
@@ -9,10 +14,16 @@ export interface ListMemoriesRequest {
9
14
  config?: Record<string, any>;
10
15
  options?: Record<string, any>;
11
16
  limit?: number;
17
+ /** Memories to skip, for paging. Use `next_offset` from the previous page. Needs 10xgraph-api with paging support. */
18
+ offset?: number;
12
19
  }
13
20
 
14
21
  export interface ListMemoriesData {
15
22
  memories: MemoryResult[];
23
+ /** True when more memories follow this page. Absent on servers without paging. */
24
+ has_more?: boolean;
25
+ /** Offset to request next, or null on the last page. */
26
+ next_offset?: number | null;
16
27
  }
17
28
 
18
29
  export interface ListMemoriesResponse {
@@ -31,17 +42,18 @@ export async function listMemories(
31
42
  });
32
43
  }
33
44
 
34
- const controller = new AbortController();
35
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
36
-
37
45
  const url = `${context.baseUrl}/v1/store/memories/list`;
38
46
 
39
47
  // Prepare request body
40
- const body = {
48
+ // `offset` is sent only when given, so a call without it matches older servers.
49
+ const body: Record<string, unknown> = {
41
50
  config: request.config || {},
42
51
  options: request.options || {},
43
52
  limit: request.limit || 100,
44
53
  };
54
+ if (request.offset !== undefined) {
55
+ body.offset = request.offset;
56
+ }
45
57
 
46
58
  if (context.debug) {
47
59
  console.debug(
@@ -50,7 +62,7 @@ export async function listMemories(
50
62
  );
51
63
  }
52
64
 
53
- const response = await fetch(url, {
65
+ const response = await fetchWithTimeout(context, url, {
54
66
  method: 'POST',
55
67
  headers: buildHeaders(context as RequestContext, {
56
68
  'Content-Type': 'application/json',
@@ -58,11 +70,8 @@ export async function listMemories(
58
70
  }),
59
71
  ...getRequestCredentials(context as RequestContext),
60
72
  body: JSON.stringify(body),
61
- signal: controller.signal,
62
73
  });
63
74
 
64
- clearTimeout(timeoutId);
65
-
66
75
  if (!response.ok) {
67
76
  console.warn(`TenxGraphClient: List memories failed with HTTP ${response.status}`);
68
77
  const error = await createErrorFromResponse(
@@ -85,10 +94,6 @@ export async function listMemories(
85
94
 
86
95
  return data;
87
96
  } catch (error) {
88
- if ((error as Error).name === 'AbortError') {
89
- console.warn(`TenxGraphClient: List memories timeout after ${context.timeout}ms`);
90
- throw new Error(`Request timeout after ${context.timeout}ms`);
91
- }
92
97
  if (context.debug) {
93
98
  console.debug('TenxGraphClient: List memories failed:', error);
94
99
  }
@@ -1,9 +1,34 @@
1
1
  import { ResponseMetadata } from './metadata.js';
2
2
  import { createErrorFromResponse } from '../errors.js';
3
- import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
3
+ import {
4
+ buildHeaders,
5
+ getRequestCredentials,
6
+ RequestContext,
7
+ fetchWithTimeout,
8
+ } from '../request.js';
4
9
 
5
10
  export interface ObservabilityContext extends RequestContext {}
6
11
 
12
+ /** Compact view of a complete message seen during a node visit. */
13
+ export interface ObsMessageSummary {
14
+ role: 'user' | 'assistant' | 'tool' | 'system' | string;
15
+ text?: string;
16
+ tool_calls?: Array<{ id: string; name: string }>;
17
+ tool_results?: Array<{ call_id: string; is_error: boolean }>;
18
+ }
19
+
20
+ /**
21
+ * One span in the run tree: `root` > `node` (one per visit) > `llm` and `tool`.
22
+ * The tree is built from `parent`. Fields marked 1.0.0 are absent on older servers.
23
+ * Values in `inputs` / `outputs` are clipped at 4000 characters
24
+ * (a clipped value is a string ending in `...[truncated N chars]`).
25
+ *
26
+ * `inputs` / `outputs` by kind:
27
+ * - root: `{ user_message }` / `{ assistant_message }`
28
+ * - node: `{ incoming_message }` (approximate) / `{ messages: ObsMessageSummary[] }`
29
+ * - llm: `null` (the prompt is not recorded) / `{ text?, tool_calls? }`
30
+ * - tool: `{ args }` / `{ result }`
31
+ */
7
32
  export interface ObsSpan {
8
33
  id: string;
9
34
  name: string;
@@ -14,6 +39,21 @@ export interface ObsSpan {
14
39
  model?: string | null;
15
40
  input_tokens?: number | null;
16
41
  output_tokens?: number | null;
42
+ /** ok | error | running. */
43
+ status?: 'ok' | 'error' | 'running';
44
+ /** Error text when `status` is `error`. */
45
+ error?: string | null;
46
+ inputs?: Record<string, unknown> | null;
47
+ outputs?: Record<string, unknown> | null;
48
+ /** Graph step (node spans), else null. */
49
+ step?: number | null;
50
+ /** 1-based visit count of this node name (node spans), else null. */
51
+ visit?: number | null;
52
+ total_tokens?: number | null;
53
+ reasoning_tokens?: number | null;
54
+ /** Tool spans only. */
55
+ tool_name?: string | null;
56
+ tool_call_id?: string | null;
17
57
  }
18
58
 
19
59
  export interface ObsEvent {
@@ -43,14 +83,29 @@ export interface ObsRun {
43
83
  usage: ObsTokenUsage;
44
84
  llm_calls: number;
45
85
  tool_calls: number;
86
+ /** Number of node visits (distinct node names on servers older than 1.0.0). */
46
87
  iterations: number;
88
+ /** First error text seen in the run, else null. */
89
+ error?: string | null;
90
+ /** First model seen in the run, else null. */
91
+ model?: string | null;
92
+ models?: string[];
93
+ /** Human-readable limits of this run's trace (may be empty). */
94
+ notes?: string[];
47
95
  }
48
96
 
97
+ /** Why `run` is null. */
98
+ export type ObsEmptyReason = 'disabled_in_production' | 'store_unavailable' | 'no_runs';
99
+
49
100
  export interface ObservabilityData {
50
101
  thread_id: string;
51
102
  run_count: number;
52
103
  run_ids: string[];
53
104
  run: ObsRun | null;
105
+ /** False when the local trace store is off (for example MODE=production). */
106
+ enabled?: boolean;
107
+ /** Why `run` is null: tracing off, store missing, or no runs captured yet. */
108
+ reason?: ObsEmptyReason | null;
54
109
  }
55
110
 
56
111
  export interface ObservabilityResponse {
@@ -67,41 +122,29 @@ export async function observability(
67
122
  threadId: string,
68
123
  runId?: string
69
124
  ): Promise<ObservabilityResponse> {
70
- try {
71
- const controller = new AbortController();
72
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
73
-
74
- const query = runId ? `?run_id=${encodeURIComponent(runId)}` : '';
75
- const response = await fetch(
76
- `${context.baseUrl}/v1/observability/${encodeURIComponent(threadId)}${query}`,
77
- {
78
- method: 'GET',
79
- headers: buildHeaders(context as RequestContext, {
80
- 'Content-Type': 'application/json',
81
- accept: 'application/json',
82
- }),
83
- ...getRequestCredentials(context as RequestContext),
84
- signal: controller.signal,
85
- }
86
- );
87
-
88
- clearTimeout(timeoutId);
89
-
90
- if (!response.ok) {
91
- const error = await createErrorFromResponse(
92
- response,
93
- 'Observability fetch failed',
94
- `/v1/observability/${threadId}`,
95
- 'GET'
96
- );
97
- throw error;
125
+ const query = runId ? `?run_id=${encodeURIComponent(runId)}` : '';
126
+ const response = await fetchWithTimeout(
127
+ context,
128
+ `${context.baseUrl}/v1/observability/${encodeURIComponent(threadId)}${query}`,
129
+ {
130
+ method: 'GET',
131
+ headers: buildHeaders(context as RequestContext, {
132
+ 'Content-Type': 'application/json',
133
+ accept: 'application/json',
134
+ }),
135
+ ...getRequestCredentials(context as RequestContext),
98
136
  }
137
+ );
99
138
 
100
- return (await response.json()) as ObservabilityResponse;
101
- } catch (error) {
102
- if ((error as Error).name === 'AbortError') {
103
- throw new Error(`Request timeout after ${context.timeout}ms`);
104
- }
139
+ if (!response.ok) {
140
+ const error = await createErrorFromResponse(
141
+ response,
142
+ 'Observability fetch failed',
143
+ `/v1/observability/${threadId}`,
144
+ 'GET'
145
+ );
105
146
  throw error;
106
147
  }
148
+
149
+ return (await response.json()) as ObservabilityResponse;
107
150
  }
@@ -1,6 +1,11 @@
1
1
  import { ResponseMetadata } from './metadata.js';
2
2
  import { createErrorFromResponse } from '../errors.js';
3
- import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
3
+ import {
4
+ buildHeaders,
5
+ getRequestCredentials,
6
+ RequestContext,
7
+ fetchWithTimeout,
8
+ } from '../request.js';
4
9
 
5
10
  export interface PingContext extends RequestContext {}
6
11
 
@@ -15,20 +20,14 @@ export async function ping(context: PingContext): Promise<PingResponse> {
15
20
  console.debug('TenxGraphClient: Pinging server at', context.baseUrl);
16
21
  }
17
22
 
18
- const controller = new AbortController();
19
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
20
-
21
- const response = await fetch(`${context.baseUrl}/ping`, {
23
+ const response = await fetchWithTimeout(context, `${context.baseUrl}/ping`, {
22
24
  method: 'GET',
23
25
  headers: buildHeaders(context as RequestContext, {
24
26
  'Content-Type': 'application/json',
25
27
  }),
26
28
  ...getRequestCredentials(context as RequestContext),
27
- signal: controller.signal,
28
29
  });
29
30
 
30
- clearTimeout(timeoutId);
31
-
32
31
  if (!response.ok) {
33
32
  console.warn(`TenxGraphClient: Ping failed with HTTP ${response.status}`);
34
33
  const error = await createErrorFromResponse(response, 'Ping request failed', '/ping', 'GET');
@@ -47,11 +46,6 @@ export async function ping(context: PingContext): Promise<PingResponse> {
47
46
  console.debug('TenxGraphClient: Ping failed:', error);
48
47
  }
49
48
 
50
- if ((error as Error).name === 'AbortError') {
51
- console.warn(`TenxGraphClient: Ping timeout after ${context.timeout}ms`);
52
- throw new Error(`Request timeout after ${context.timeout}ms`);
53
- }
54
-
55
49
  console.error('TenxGraphClient: Ping failed:', error);
56
50
  throw error;
57
51
  }