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
@@ -22,11 +22,15 @@ export interface RealtimeVADConfig {
22
22
 
23
23
  export interface RealtimeInit {
24
24
  /**
25
- * e.g. "gemini-2.5-flash-live". Honoured only when the server lists it in
25
+ * e.g. "gemini-3.8-live". Honoured only when the server lists it in
26
26
  * `websocket.realtime_models`; otherwise the agent's own model is used.
27
27
  */
28
28
  model?: string;
29
- /** Omit to let the session generate one (used for resume). */
29
+ /**
30
+ * Omit to let the server create the thread; the id arrives in the `session_started` event and
31
+ * is read from `session.threadId`. Pass an id to continue a thread (the server must allow it:
32
+ * under the default `runs.thread_ids: "server"` it must be one the server already knows).
33
+ */
30
34
  thread_id?: string;
31
35
  voice?: string;
32
36
  modalities?: ResponseModality | ResponseModality[];
@@ -40,6 +44,7 @@ export interface RealtimeInit {
40
44
 
41
45
  /** Downstream JSON events. Audio arrives separately as binary via the `audio` channel. */
42
46
  export type RealtimeEvent =
47
+ | { type: 'session_started'; thread_id: string }
43
48
  | { type: 'input_transcript'; text: string; finished: boolean }
44
49
  | { type: 'output_transcript'; text: string; finished: boolean }
45
50
  | { type: 'tool_call'; id: string; name: string; args: Record<string, unknown> }
@@ -68,7 +73,11 @@ export interface RealtimeOptions {
68
73
  onError?: (e: Extract<RealtimeEvent, { type: 'error' }>) => void;
69
74
  }
70
75
 
71
- export type RealtimeContext = RequestContext & WsAuthContext;
76
+ export type RealtimeContext = RequestContext &
77
+ WsAuthContext & {
78
+ /** Resolves the current bearer token; called on every (re)connect so a refreshed token is used. */
79
+ getAuthToken?: () => string | null | undefined;
80
+ };
72
81
 
73
82
  // ── Lifecycle/event channels the emitter understands ─────────────────────────
74
83
  type ChannelMap = {
@@ -88,24 +97,8 @@ type ChannelName = keyof ChannelMap;
88
97
  // fully typed via ChannelMap[K]; this only avoids the unsafe `Function` type.
89
98
  type ChannelListener = (...args: never[]) => void;
90
99
 
91
- function genThreadId(): string {
92
- const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;
93
- if (c?.randomUUID) {
94
- return c.randomUUID();
95
- }
96
- // Fallback: timestamp-free, index-free pseudo-id is not required here because a
97
- // dropped session reuses this same id; uniqueness across sessions is enough.
98
- return 'rt-' + Math.abs(hashString(String(Date.now()))).toString(36);
99
- }
100
-
101
- function hashString(s: string): number {
102
- let h = 0;
103
- for (let i = 0; i < s.length; i++) {
104
- h = (h << 5) - h + s.charCodeAt(i);
105
- h |= 0;
106
- }
107
- return h;
108
- }
100
+ /** How long a connection must stay open before the reconnect budget is restored. */
101
+ const STABLE_CONNECTION_MS = 10_000;
109
102
 
110
103
  /** Normalize an incoming binary frame (ArrayBuffer / typed array / Buffer) to Uint8Array. */
111
104
  function toUint8(data: unknown): Uint8Array | null {
@@ -127,7 +120,11 @@ function toUint8(data: unknown): Uint8Array | null {
127
120
  * Mic capture and audio playback are the caller's responsibility.
128
121
  */
129
122
  export class RealtimeSession {
130
- readonly threadId: string;
123
+ /**
124
+ * The thread this session runs on: the caller's `thread_id`, or the id the server created.
125
+ * `null` until the `session_started` event arrives, when the caller gave none.
126
+ */
127
+ threadId: string | null;
131
128
  resumptionHandle: string | null = null;
132
129
  ready: Promise<void>;
133
130
 
@@ -141,12 +138,15 @@ export class RealtimeSession {
141
138
  private closedByUser = false;
142
139
  private fatal = false;
143
140
  private reconnectAttempts = 0;
141
+ private reconnectTimer: ReturnType<typeof setTimeout> | undefined;
142
+ private stableTimer: ReturnType<typeof setTimeout> | undefined;
144
143
  private readonly reconnectCfg: Required<RealtimeReconnectOptions>;
145
144
 
146
145
  constructor(ctx: RealtimeContext, init: RealtimeInit, options?: RealtimeOptions) {
147
146
  this.ctx = ctx;
148
- this.threadId = init.thread_id ?? genThreadId();
149
- this.init = { ...init, thread_id: this.threadId };
147
+ this.threadId = init.thread_id ?? null;
148
+ this.init = { ...init };
149
+ if (this.threadId === null) delete this.init.thread_id;
150
150
 
151
151
  this.reconnectCfg = {
152
152
  enabled: options?.reconnect?.enabled ?? true,
@@ -164,12 +164,21 @@ export class RealtimeSession {
164
164
  this.rejectReady = reject;
165
165
  });
166
166
 
167
+ // The events API invites callers not to await `ready`; a failed connection must not
168
+ // become an unhandled rejection that ends the process. Awaiting it still rejects.
169
+ this.ready.catch(() => {});
170
+
167
171
  this.connect();
168
172
  }
169
173
 
170
174
  // ── Public send API ─────────────────────────────────────────────────────────
175
+ /**
176
+ * Frames are dropped unless the socket is open; await `session.ready` (or the `open`
177
+ * event) before sending, so mic callbacks that fire early do not throw.
178
+ */
171
179
  sendAudio(pcm16: Uint8Array | ArrayBuffer): void {
172
- this.ws?.send(pcm16 as ArrayBuffer);
180
+ const ws = this.ws;
181
+ if (ws && ws.readyState === 1) ws.send(pcm16 as ArrayBuffer);
173
182
  }
174
183
  sendText(text: string): void {
175
184
  this.sendControl({ type: 'text', text });
@@ -182,6 +191,8 @@ export class RealtimeSession {
182
191
  }
183
192
  close(): void {
184
193
  this.closedByUser = true;
194
+ this.clearTimers();
195
+ // A close frame only goes out on an open socket; a connecting one is just closed.
185
196
  this.sendControl({ type: 'close' });
186
197
  const ws = this.ws;
187
198
  if (ws && (ws.readyState === 0 || ws.readyState === 1)) {
@@ -212,14 +223,23 @@ export class RealtimeSession {
212
223
  }
213
224
 
214
225
  private sendControl(frame: Record<string, unknown>): void {
215
- this.ws?.send(JSON.stringify(frame));
226
+ const ws = this.ws;
227
+ if (ws && ws.readyState === 1) ws.send(JSON.stringify(frame));
228
+ }
229
+
230
+ private clearTimers(): void {
231
+ if (this.reconnectTimer !== undefined) clearTimeout(this.reconnectTimer);
232
+ if (this.stableTimer !== undefined) clearTimeout(this.stableTimer);
233
+ this.reconnectTimer = undefined;
234
+ this.stableTimer = undefined;
216
235
  }
217
236
 
218
237
  private connect(): void {
219
238
  const url = buildWsUrl(this.ctx, '/v1/graph/live');
220
239
  if (this.ctx.debug) console.debug('TenxGraphClient [realtime]: connecting to', url);
221
240
 
222
- const ws = openWebSocket(url, this.ctx);
241
+ const getToken = this.ctx.getAuthToken;
242
+ const ws = openWebSocket(url, getToken ? { ...this.ctx, authToken: getToken() } : this.ctx);
223
243
  try {
224
244
  // Receive audio as ArrayBuffer rather than Blob (browser default is "blob").
225
245
  (ws as { binaryType?: string }).binaryType = 'arraybuffer';
@@ -236,6 +256,13 @@ export class RealtimeSession {
236
256
  this.resolveReady();
237
257
  }
238
258
  this.emit('open');
259
+ // The attempt budget is restored only after the link has stayed up for a while, so a
260
+ // server that accepts and drops at once cannot keep resetting it.
261
+ if (this.stableTimer !== undefined) clearTimeout(this.stableTimer);
262
+ this.stableTimer = setTimeout(() => {
263
+ this.stableTimer = undefined;
264
+ this.reconnectAttempts = 0;
265
+ }, STABLE_CONNECTION_MS);
239
266
  });
240
267
 
241
268
  ws.addEventListener('message', (event: MessageEvent) => {
@@ -246,8 +273,24 @@ export class RealtimeSession {
246
273
  ws.addEventListener('close', (event: CloseEvent) => {
247
274
  if (this.ws !== ws) return; // stale socket from a previous connection; ignore
248
275
  const code = event?.code ?? 1000;
276
+ if (this.stableTimer !== undefined) clearTimeout(this.stableTimer);
277
+ this.stableTimer = undefined;
249
278
  this.emit('close', code);
250
- this.maybeReconnect();
279
+ // 1008 is a policy violation (for example an expired or rejected token): retrying
280
+ // with the same credentials cannot succeed.
281
+ if (code === 1008 && !this.closedByUser && !this.fatal) {
282
+ this.fatal = true;
283
+ const failure = {
284
+ type: 'error' as const,
285
+ code: 'policy_violation',
286
+ message: 'Realtime connection closed by the server (1008, policy violation)',
287
+ fatal: true,
288
+ };
289
+ this.emit('error', failure);
290
+ this.emit('event', failure);
291
+ return;
292
+ }
293
+ this.maybeReconnect(code);
251
294
  });
252
295
 
253
296
  ws.addEventListener('error', () => {
@@ -273,6 +316,9 @@ export class RealtimeSession {
273
316
  if (this.ctx.debug) console.warn('TenxGraphClient [realtime]: bad JSON frame', data);
274
317
  return;
275
318
  }
319
+ if (parsed.type === 'session_started' && parsed.thread_id) {
320
+ this.threadId = parsed.thread_id;
321
+ }
276
322
  if (parsed.type === 'session_update') {
277
323
  this.resumptionHandle = parsed.resumption_handle ?? this.resumptionHandle;
278
324
  }
@@ -284,7 +330,7 @@ export class RealtimeSession {
284
330
  this.emit(parsed.type as ChannelName, parsed as never);
285
331
  }
286
332
 
287
- private maybeReconnect(): void {
333
+ private maybeReconnect(closeCode?: number): void {
288
334
  if (this.closedByUser || this.fatal || !this.reconnectCfg.enabled) {
289
335
  return;
290
336
  }
@@ -303,24 +349,40 @@ export class RealtimeSession {
303
349
  }
304
350
  this.reconnectAttempts += 1;
305
351
  const attempt = this.reconnectAttempts;
306
- const delayMs =
307
- Math.min(this.reconnectCfg.baseDelay * 2 ** (attempt - 1), this.reconnectCfg.maxDelay) * 1000;
352
+ // 1013 means the server is overloaded: go straight to the longest delay. Jitter keeps
353
+ // many clients from retrying in step.
354
+ const backoff =
355
+ closeCode === 1013
356
+ ? this.reconnectCfg.maxDelay
357
+ : Math.min(this.reconnectCfg.baseDelay * 2 ** (attempt - 1), this.reconnectCfg.maxDelay);
358
+ const delayMs = backoff * 1000 * (0.5 + Math.random() * 0.5);
308
359
  this.emit('reconnecting', attempt);
309
- setTimeout(() => this.reconnect(), delayMs);
360
+ this.reconnectTimer = setTimeout(() => {
361
+ this.reconnectTimer = undefined;
362
+ this.reconnect();
363
+ }, delayMs);
310
364
  }
311
365
 
312
366
  private reconnect(): void {
367
+ if (this.closedByUser) return;
313
368
  // Forward the latest resumption handle so the provider can resume server-side.
314
369
  this.init = {
315
370
  ...this.init,
316
- thread_id: this.threadId,
371
+ // The server-given id, so the same thread continues; none yet means the first connect
372
+ // never got `session_started`, and the server makes a new one.
373
+ ...(this.threadId ? { thread_id: this.threadId } : {}),
317
374
  ...(this.resumptionHandle ? { resumption_handle: this.resumptionHandle } : {}),
318
375
  };
319
- const onceOpen = () => {
320
- this.reconnectAttempts = 0;
321
- this.emit('reconnected');
322
- };
323
- this.connect();
324
- this.ws?.addEventListener('open', onceOpen, { once: true } as AddEventListenerOptions);
376
+ try {
377
+ this.connect();
378
+ this.ws?.addEventListener('open', () => this.emit('reconnected'), {
379
+ once: true,
380
+ } as AddEventListenerOptions);
381
+ } catch (err) {
382
+ // The socket constructor threw inside a timer; count it as a failed attempt instead
383
+ // of letting it escape as an uncaught exception.
384
+ if (this.ctx.debug) console.warn('TenxGraphClient [realtime]: reconnect failed', err);
385
+ this.maybeReconnect();
386
+ }
325
387
  }
326
388
  }
@@ -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 { MemoryType } from './storeMemory.js';
5
10
 
6
11
  export interface SearchMemoryContext extends RequestContext {}
@@ -70,38 +75,39 @@ export async function searchMemory(
70
75
  console.debug('TenxGraphClient: Searching memories with query:', request.query);
71
76
  }
72
77
 
73
- const controller = new AbortController();
74
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
75
-
76
78
  const url = `${context.baseUrl}/v1/store/search`;
77
79
 
78
- // Prepare request body with defaults
79
- const body = {
80
+ // Prepare request body. memory_type, category and score_threshold have no default:
81
+ // when unset they are omitted so the server applies no filter on them.
82
+ const body: Record<string, unknown> = {
80
83
  config: request.config || {},
81
84
  options: request.options || {},
82
85
  query: request.query,
83
- memory_type: request.memory_type || MemoryType.EPISODIC,
84
- category: request.category || '',
85
86
  limit: request.limit !== undefined ? request.limit : 10,
86
- score_threshold: request.score_threshold !== undefined ? request.score_threshold : 0,
87
87
  filters: request.filters || {},
88
88
  retrieval_strategy: request.retrieval_strategy || RetrievalStrategy.SIMILARITY,
89
89
  distance_metric: request.distance_metric || DistanceMetric.COSINE,
90
90
  max_tokens: request.max_tokens !== undefined ? request.max_tokens : 4000,
91
91
  };
92
+ if (request.memory_type !== undefined && request.memory_type !== null) {
93
+ body.memory_type = request.memory_type;
94
+ }
95
+ if (request.category !== undefined && request.category !== null) {
96
+ body.category = request.category;
97
+ }
98
+ if (request.score_threshold !== undefined && request.score_threshold !== null) {
99
+ body.score_threshold = request.score_threshold;
100
+ }
92
101
 
93
- const response = await fetch(url, {
102
+ const response = await fetchWithTimeout(context, url, {
94
103
  method: 'POST',
95
104
  headers: buildHeaders(context as RequestContext, {
96
105
  'Content-Type': 'application/json',
97
106
  }),
98
107
  ...getRequestCredentials(context as RequestContext),
99
108
  body: JSON.stringify(body),
100
- signal: controller.signal,
101
109
  });
102
110
 
103
- clearTimeout(timeoutId);
104
-
105
111
  if (!response.ok) {
106
112
  console.warn(`TenxGraphClient: Search memory failed with HTTP ${response.status}`);
107
113
  const error = await createErrorFromResponse(
@@ -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 StateSchemaContext extends RequestContext {}
6
11
 
@@ -57,21 +62,15 @@ export async function stateSchema(context: StateSchemaContext): Promise<StateSch
57
62
  console.debug('TenxGraphClient: Fetching state schema from', context.baseUrl);
58
63
  }
59
64
 
60
- const controller = new AbortController();
61
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
62
-
63
- const response = await fetch(`${context.baseUrl}/v1/graph:StateSchema`, {
65
+ const response = await fetchWithTimeout(context, `${context.baseUrl}/v1/graph:StateSchema`, {
64
66
  method: 'GET',
65
67
  headers: buildHeaders(context as RequestContext, {
66
68
  'Content-Type': 'application/json',
67
69
  accept: 'application/json',
68
70
  }),
69
71
  ...getRequestCredentials(context as RequestContext),
70
- signal: controller.signal,
71
72
  });
72
73
 
73
- clearTimeout(timeoutId);
74
-
75
74
  if (!response.ok) {
76
75
  console.warn(`TenxGraphClient: State schema fetch failed with HTTP ${response.status}`);
77
76
  const error = await createErrorFromResponse(
@@ -95,11 +94,6 @@ export async function stateSchema(context: StateSchemaContext): Promise<StateSch
95
94
  console.debug('TenxGraphClient: State schema fetch failed:', error);
96
95
  }
97
96
 
98
- if ((error as Error).name === 'AbortError') {
99
- console.warn(`TenxGraphClient: State schema fetch timeout after ${context.timeout}ms`);
100
- throw new Error(`Request timeout after ${context.timeout}ms`);
101
- }
102
-
103
97
  console.error('TenxGraphClient: State schema fetch failed:', error);
104
98
  throw error;
105
99
  }
@@ -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 StopGraphContext extends RequestContext {}
6
11
 
@@ -11,9 +16,19 @@ export interface StopGraphRequest {
11
16
 
12
17
  export interface StopGraphResponse {
13
18
  data: {
14
- success: boolean;
15
- message: string;
16
- thread_id: string;
19
+ /** False when the thread has no checkpointer or no saved state. */
20
+ ok: boolean;
21
+ /** True when a run was active and a stop was requested. */
22
+ running?: boolean;
23
+ /** Why nothing was stopped, e.g. 'no-checkpointer' or 'no-state'. */
24
+ reason?: string;
25
+ /** @deprecated The server never sends this field. Use `ok`. */
26
+ success?: boolean;
27
+ /** @deprecated The server never sends this field. */
28
+ message?: string;
29
+ /** @deprecated The server never sends this field. */
30
+ thread_id?: string;
31
+ /** @deprecated The server never sends this field. */
17
32
  stopped_at?: string;
18
33
  };
19
34
  metadata: ResponseMetadata;
@@ -35,10 +50,7 @@ export async function stopGraph(
35
50
  console.debug('TenxGraphClient: Stopping graph execution for thread:', request.thread_id);
36
51
  }
37
52
 
38
- const controller = new AbortController();
39
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
40
-
41
- const response = await fetch(`${context.baseUrl}/v1/graph/stop`, {
53
+ const response = await fetchWithTimeout(context, `${context.baseUrl}/v1/graph/stop`, {
42
54
  method: 'POST',
43
55
  headers: buildHeaders(context as RequestContext, {
44
56
  'Content-Type': 'application/json',
@@ -46,11 +58,8 @@ export async function stopGraph(
46
58
  }),
47
59
  ...getRequestCredentials(context as RequestContext),
48
60
  body: JSON.stringify(request),
49
- signal: controller.signal,
50
61
  });
51
62
 
52
- clearTimeout(timeoutId);
53
-
54
63
  if (!response.ok) {
55
64
  console.warn(`TenxGraphClient: Stop graph failed with HTTP ${response.status}`);
56
65
  const error = await createErrorFromResponse(
@@ -74,11 +83,6 @@ export async function stopGraph(
74
83
  console.debug('TenxGraphClient: Stop graph failed:', error);
75
84
  }
76
85
 
77
- if ((error as Error).name === 'AbortError') {
78
- console.warn(`TenxGraphClient: Stop graph timeout after ${context.timeout}ms`);
79
- throw new Error(`Request timeout after ${context.timeout}ms`);
80
- }
81
-
82
86
  console.error('TenxGraphClient: Stop graph failed:', error);
83
87
  throw error;
84
88
  }
@@ -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 StoreMemoryContext extends RequestContext {}
6
11
 
@@ -44,9 +49,6 @@ export async function storeMemory(
44
49
  console.debug('TenxGraphClient: Storing memory with type', request.memory_type);
45
50
  }
46
51
 
47
- const controller = new AbortController();
48
- const timeoutId = setTimeout(() => controller.abort(), context.timeout);
49
-
50
52
  const url = `${context.baseUrl}/v1/store/memories`;
51
53
 
52
54
  // Prepare request body
@@ -59,18 +61,15 @@ export async function storeMemory(
59
61
  metadata: request.metadata || {},
60
62
  };
61
63
 
62
- const response = await fetch(url, {
64
+ const response = await fetchWithTimeout(context, url, {
63
65
  method: 'POST',
64
66
  headers: buildHeaders(context as RequestContext, {
65
67
  'Content-Type': 'application/json',
66
68
  }),
67
69
  ...getRequestCredentials(context as RequestContext),
68
70
  body: JSON.stringify(body),
69
- signal: controller.signal,
70
71
  });
71
72
 
72
- clearTimeout(timeoutId);
73
-
74
73
  if (!response.ok) {
75
74
  console.warn(`TenxGraphClient: Store memory failed with HTTP ${response.status}`);
76
75
  const error = await createErrorFromResponse(