10xgraph-client 0.0.0-stage → 0.6.0
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 +276 -0
- package/LICENSE +21 -0
- package/README.md +400 -2
- package/dist/agent.d.ts +22 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/client.d.ts +479 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/compat.d.ts +28 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/endpoints/addThreadMessages.d.ts +22 -0
- package/dist/endpoints/addThreadMessages.d.ts.map +1 -0
- package/dist/endpoints/clearThreadState.d.ts +15 -0
- package/dist/endpoints/clearThreadState.d.ts.map +1 -0
- package/dist/endpoints/deleteMemory.d.ts +19 -0
- package/dist/endpoints/deleteMemory.d.ts.map +1 -0
- package/dist/endpoints/deleteThread.d.ts +19 -0
- package/dist/endpoints/deleteThread.d.ts.map +1 -0
- package/dist/endpoints/deleteThreadMessage.d.ts +20 -0
- package/dist/endpoints/deleteThreadMessage.d.ts.map +1 -0
- package/dist/endpoints/files.d.ts +83 -0
- package/dist/endpoints/files.d.ts.map +1 -0
- package/dist/endpoints/fixGraph.d.ts +29 -0
- package/dist/endpoints/fixGraph.d.ts.map +1 -0
- package/dist/endpoints/forgetMemories.d.ts +22 -0
- package/dist/endpoints/forgetMemories.d.ts.map +1 -0
- package/dist/endpoints/getMemory.d.ts +19 -0
- package/dist/endpoints/getMemory.d.ts.map +1 -0
- package/dist/endpoints/graph.d.ts +39 -0
- package/dist/endpoints/graph.d.ts.map +1 -0
- package/dist/endpoints/graphTools.d.ts +32 -0
- package/dist/endpoints/graphTools.d.ts.map +1 -0
- package/dist/endpoints/invoke.d.ts +59 -0
- package/dist/endpoints/invoke.d.ts.map +1 -0
- package/dist/endpoints/listMemories.d.ts +19 -0
- package/dist/endpoints/listMemories.d.ts.map +1 -0
- package/dist/endpoints/metadata.d.ts +6 -0
- package/dist/endpoints/metadata.d.ts.map +1 -0
- package/dist/endpoints/observability.d.ts +58 -0
- package/dist/endpoints/observability.d.ts.map +1 -0
- package/dist/endpoints/ping.d.ts +10 -0
- package/dist/endpoints/ping.d.ts.map +1 -0
- package/dist/endpoints/realtime.d.ts +134 -0
- package/dist/endpoints/realtime.d.ts.map +1 -0
- package/dist/endpoints/searchMemory.d.ts +57 -0
- package/dist/endpoints/searchMemory.d.ts.map +1 -0
- package/dist/endpoints/stateSchema.d.ts +50 -0
- package/dist/endpoints/stateSchema.d.ts.map +1 -0
- package/dist/endpoints/stopGraph.d.ts +26 -0
- package/dist/endpoints/stopGraph.d.ts.map +1 -0
- package/dist/endpoints/storeMemory.d.ts +33 -0
- package/dist/endpoints/storeMemory.d.ts.map +1 -0
- package/dist/endpoints/stream.d.ts +60 -0
- package/dist/endpoints/stream.d.ts.map +1 -0
- package/dist/endpoints/threadDetails.d.ts +19 -0
- package/dist/endpoints/threadDetails.d.ts.map +1 -0
- package/dist/endpoints/threadMessage.d.ts +17 -0
- package/dist/endpoints/threadMessage.d.ts.map +1 -0
- package/dist/endpoints/threadMessages.d.ts +23 -0
- package/dist/endpoints/threadMessages.d.ts.map +1 -0
- package/dist/endpoints/threadState.d.ts +14 -0
- package/dist/endpoints/threadState.d.ts.map +1 -0
- package/dist/endpoints/threads.d.ts +30 -0
- package/dist/endpoints/threads.d.ts.map +1 -0
- package/dist/endpoints/updateMemory.d.ts +21 -0
- package/dist/endpoints/updateMemory.d.ts.map +1 -0
- package/dist/endpoints/updateThreadState.d.ts +18 -0
- package/dist/endpoints/updateThreadState.d.ts.map +1 -0
- package/dist/endpoints/wsStream.d.ts +33 -0
- package/dist/endpoints/wsStream.d.ts.map +1 -0
- package/dist/errors.d.ts +155 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2664 -0
- package/dist/index.js.map +1 -0
- package/dist/message.d.ts +174 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/request.d.ts +33 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/tools.d.ts +67 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/ws.d.ts +48 -0
- package/dist/ws.d.ts.map +1 -0
- package/package.json +99 -4
- package/src/agent.ts +43 -0
- package/src/client.ts +1044 -0
- package/src/compat.ts +35 -0
- package/src/endpoints/addThreadMessages.ts +83 -0
- package/src/endpoints/clearThreadState.ts +75 -0
- package/src/endpoints/deleteMemory.ts +91 -0
- package/src/endpoints/deleteThread.ts +78 -0
- package/src/endpoints/deleteThreadMessage.ts +83 -0
- package/src/endpoints/files.ts +274 -0
- package/src/endpoints/fixGraph.ts +91 -0
- package/src/endpoints/forgetMemories.ts +95 -0
- package/src/endpoints/getMemory.ts +82 -0
- package/src/endpoints/graph.ts +97 -0
- package/src/endpoints/graphTools.ts +90 -0
- package/src/endpoints/invoke.ts +318 -0
- package/src/endpoints/listMemories.ts +97 -0
- package/src/endpoints/metadata.ts +6 -0
- package/src/endpoints/observability.ts +107 -0
- package/src/endpoints/ping.ts +58 -0
- package/src/endpoints/realtime.ts +326 -0
- package/src/endpoints/searchMemory.ts +132 -0
- package/src/endpoints/stateSchema.ts +106 -0
- package/src/endpoints/stopGraph.ts +85 -0
- package/src/endpoints/storeMemory.ts +98 -0
- package/src/endpoints/stream.ts +440 -0
- package/src/endpoints/threadDetails.ts +78 -0
- package/src/endpoints/threadMessage.ts +76 -0
- package/src/endpoints/threadMessages.ts +97 -0
- package/src/endpoints/threadState.ts +74 -0
- package/src/endpoints/threads.ts +112 -0
- package/src/endpoints/updateMemory.ts +90 -0
- package/src/endpoints/updateThreadState.ts +82 -0
- package/src/endpoints/wsStream.ts +251 -0
- package/src/errors.ts +553 -0
- package/src/index.ts +108 -0
- package/src/message.ts +436 -0
- package/src/request.ts +168 -0
- package/src/tools.ts +225 -0
- package/src/ws.ts +100 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { RequestContext } from '../request.js';
|
|
2
|
+
import { buildWsUrl, openWebSocket, WsAuthContext } from '../ws.js';
|
|
3
|
+
|
|
4
|
+
// ── Output audio sample rate (PCM16 mono @ 24 kHz), mirrors core OUTPUT_SAMPLE_RATE ──
|
|
5
|
+
export const REALTIME_OUTPUT_SAMPLE_RATE = 24000;
|
|
6
|
+
export const REALTIME_INPUT_SAMPLE_RATE = 16000;
|
|
7
|
+
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
// Wire types (mirror tenxgraph.core.realtime; init keys verified against the
|
|
10
|
+
// server's GraphService._realtime_overrides mapping)
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type ResponseModality = 'AUDIO' | 'TEXT';
|
|
14
|
+
|
|
15
|
+
export interface RealtimeVADConfig {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
start_sensitivity?: string | null;
|
|
18
|
+
end_sensitivity?: string | null;
|
|
19
|
+
prefix_padding_ms?: number | null;
|
|
20
|
+
silence_duration_ms?: number | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RealtimeInit {
|
|
24
|
+
/**
|
|
25
|
+
* e.g. "gemini-2.5-flash-live". Honoured only when the server lists it in
|
|
26
|
+
* `websocket.realtime_models`; otherwise the agent's own model is used.
|
|
27
|
+
*/
|
|
28
|
+
model?: string;
|
|
29
|
+
/** Omit to let the session generate one (used for resume). */
|
|
30
|
+
thread_id?: string;
|
|
31
|
+
voice?: string;
|
|
32
|
+
modalities?: ResponseModality | ResponseModality[];
|
|
33
|
+
vad?: RealtimeVADConfig;
|
|
34
|
+
system_prompt?: string;
|
|
35
|
+
/** Can only narrow the agent's own tag filter. */
|
|
36
|
+
tools_tags?: string[];
|
|
37
|
+
/** Forward-compatible passthrough; unknown keys are ignored by the server. */
|
|
38
|
+
[k: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Downstream JSON events. Audio arrives separately as binary via the `audio` channel. */
|
|
42
|
+
export type RealtimeEvent =
|
|
43
|
+
| { type: 'input_transcript'; text: string; finished: boolean }
|
|
44
|
+
| { type: 'output_transcript'; text: string; finished: boolean }
|
|
45
|
+
| { type: 'tool_call'; id: string; name: string; args: Record<string, unknown> }
|
|
46
|
+
| { type: 'tool_result'; id: string; result: unknown }
|
|
47
|
+
| { type: 'turn_complete' }
|
|
48
|
+
| { type: 'interrupted' }
|
|
49
|
+
| { type: 'session_update'; resumption_handle?: string | null }
|
|
50
|
+
| { type: 'go_away'; time_left?: string | null }
|
|
51
|
+
| { type: 'agent_changed'; author: string }
|
|
52
|
+
| { type: 'error'; code?: string | null; message: string; fatal: boolean };
|
|
53
|
+
|
|
54
|
+
export type RealtimeEventType = RealtimeEvent['type'];
|
|
55
|
+
|
|
56
|
+
export interface RealtimeReconnectOptions {
|
|
57
|
+
enabled?: boolean;
|
|
58
|
+
baseDelay?: number;
|
|
59
|
+
maxDelay?: number;
|
|
60
|
+
maxAttempts?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RealtimeOptions {
|
|
64
|
+
reconnect?: RealtimeReconnectOptions;
|
|
65
|
+
// Convenience handlers (sugar over `.on(...)`).
|
|
66
|
+
onAudio?: (pcm16: Uint8Array, sampleRate: number) => void;
|
|
67
|
+
onEvent?: (e: RealtimeEvent) => void;
|
|
68
|
+
onError?: (e: Extract<RealtimeEvent, { type: 'error' }>) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type RealtimeContext = RequestContext & WsAuthContext;
|
|
72
|
+
|
|
73
|
+
// ── Lifecycle/event channels the emitter understands ─────────────────────────
|
|
74
|
+
type ChannelMap = {
|
|
75
|
+
audio: (pcm16: Uint8Array, sampleRate: number) => void;
|
|
76
|
+
event: (e: RealtimeEvent) => void;
|
|
77
|
+
open: () => void;
|
|
78
|
+
close: (code: number) => void;
|
|
79
|
+
reconnecting: (attempt: number) => void;
|
|
80
|
+
reconnected: () => void;
|
|
81
|
+
} & {
|
|
82
|
+
[K in RealtimeEventType]: (e: Extract<RealtimeEvent, { type: K }>) => void;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type ChannelName = keyof ChannelMap;
|
|
86
|
+
|
|
87
|
+
// The erased listener shape used for internal storage. Public `on`/`off` stay
|
|
88
|
+
// fully typed via ChannelMap[K]; this only avoids the unsafe `Function` type.
|
|
89
|
+
type ChannelListener = (...args: never[]) => void;
|
|
90
|
+
|
|
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
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Normalize an incoming binary frame (ArrayBuffer / typed array / Buffer) to Uint8Array. */
|
|
111
|
+
function toUint8(data: unknown): Uint8Array | null {
|
|
112
|
+
if (data instanceof ArrayBuffer) {
|
|
113
|
+
return new Uint8Array(data);
|
|
114
|
+
}
|
|
115
|
+
if (ArrayBuffer.isView(data)) {
|
|
116
|
+
const view = data as ArrayBufferView;
|
|
117
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A transport-only realtime audio session over `/v1/graph/live`.
|
|
124
|
+
*
|
|
125
|
+
* Send PCM16 input via `sendAudio`; receive PCM16 output via `.on('audio', ...)`.
|
|
126
|
+
* All non-audio events arrive via `.on('event', ...)` and per-type channels.
|
|
127
|
+
* Mic capture and audio playback are the caller's responsibility.
|
|
128
|
+
*/
|
|
129
|
+
export class RealtimeSession {
|
|
130
|
+
readonly threadId: string;
|
|
131
|
+
resumptionHandle: string | null = null;
|
|
132
|
+
ready: Promise<void>;
|
|
133
|
+
|
|
134
|
+
private ctx: RealtimeContext;
|
|
135
|
+
private init: RealtimeInit;
|
|
136
|
+
private ws: WebSocket | null = null;
|
|
137
|
+
private listeners: Partial<Record<ChannelName, ChannelListener[]>> = {};
|
|
138
|
+
private resolveReady!: () => void;
|
|
139
|
+
private rejectReady!: (err: Error) => void;
|
|
140
|
+
private readyResolved = false;
|
|
141
|
+
private closedByUser = false;
|
|
142
|
+
private fatal = false;
|
|
143
|
+
private reconnectAttempts = 0;
|
|
144
|
+
private readonly reconnectCfg: Required<RealtimeReconnectOptions>;
|
|
145
|
+
|
|
146
|
+
constructor(ctx: RealtimeContext, init: RealtimeInit, options?: RealtimeOptions) {
|
|
147
|
+
this.ctx = ctx;
|
|
148
|
+
this.threadId = init.thread_id ?? genThreadId();
|
|
149
|
+
this.init = { ...init, thread_id: this.threadId };
|
|
150
|
+
|
|
151
|
+
this.reconnectCfg = {
|
|
152
|
+
enabled: options?.reconnect?.enabled ?? true,
|
|
153
|
+
baseDelay: options?.reconnect?.baseDelay ?? 0.5,
|
|
154
|
+
maxDelay: options?.reconnect?.maxDelay ?? 10,
|
|
155
|
+
maxAttempts: options?.reconnect?.maxAttempts ?? 5,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (options?.onAudio) this.on('audio', options.onAudio);
|
|
159
|
+
if (options?.onEvent) this.on('event', options.onEvent);
|
|
160
|
+
if (options?.onError) this.on('error', options.onError as ChannelMap['error']);
|
|
161
|
+
|
|
162
|
+
this.ready = new Promise<void>((resolve, reject) => {
|
|
163
|
+
this.resolveReady = resolve;
|
|
164
|
+
this.rejectReady = reject;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
this.connect();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Public send API ─────────────────────────────────────────────────────────
|
|
171
|
+
sendAudio(pcm16: Uint8Array | ArrayBuffer): void {
|
|
172
|
+
this.ws?.send(pcm16 as ArrayBuffer);
|
|
173
|
+
}
|
|
174
|
+
sendText(text: string): void {
|
|
175
|
+
this.sendControl({ type: 'text', text });
|
|
176
|
+
}
|
|
177
|
+
activityStart(): void {
|
|
178
|
+
this.sendControl({ type: 'activity_start' });
|
|
179
|
+
}
|
|
180
|
+
activityEnd(): void {
|
|
181
|
+
this.sendControl({ type: 'activity_end' });
|
|
182
|
+
}
|
|
183
|
+
close(): void {
|
|
184
|
+
this.closedByUser = true;
|
|
185
|
+
this.sendControl({ type: 'close' });
|
|
186
|
+
const ws = this.ws;
|
|
187
|
+
if (ws && (ws.readyState === 0 || ws.readyState === 1)) {
|
|
188
|
+
ws.close(1000);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Subscription API ─────────────────────────────────────────────────────────
|
|
193
|
+
on<K extends ChannelName>(event: K, listener: ChannelMap[K]): this {
|
|
194
|
+
if (!this.listeners[event]) this.listeners[event] = [];
|
|
195
|
+
(this.listeners[event] as ChannelListener[]).push(listener as ChannelListener);
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
off<K extends ChannelName>(event: K, listener: ChannelMap[K]): this {
|
|
199
|
+
this.listeners[event] = (this.listeners[event] ?? []).filter((f) => f !== listener);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Internals ────────────────────────────────────────────────────────────────
|
|
204
|
+
private emit<K extends ChannelName>(event: K, ...args: Parameters<ChannelMap[K]>): void {
|
|
205
|
+
(this.listeners[event] ?? []).forEach((f) => {
|
|
206
|
+
try {
|
|
207
|
+
(f as (...a: unknown[]) => void)(...args);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
if (this.ctx.debug) console.warn('TenxGraphClient [realtime]: listener threw', err);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private sendControl(frame: Record<string, unknown>): void {
|
|
215
|
+
this.ws?.send(JSON.stringify(frame));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private connect(): void {
|
|
219
|
+
const url = buildWsUrl(this.ctx, '/v1/graph/live');
|
|
220
|
+
if (this.ctx.debug) console.debug('TenxGraphClient [realtime]: connecting to', url);
|
|
221
|
+
|
|
222
|
+
const ws = openWebSocket(url, this.ctx);
|
|
223
|
+
try {
|
|
224
|
+
// Receive audio as ArrayBuffer rather than Blob (browser default is "blob").
|
|
225
|
+
(ws as { binaryType?: string }).binaryType = 'arraybuffer';
|
|
226
|
+
} catch {
|
|
227
|
+
/* some impls disallow setting before open; ignore */
|
|
228
|
+
}
|
|
229
|
+
this.ws = ws;
|
|
230
|
+
|
|
231
|
+
ws.addEventListener('open', () => {
|
|
232
|
+
if (this.ws !== ws) return; // stale socket from a previous connection; ignore
|
|
233
|
+
this.sendControl(this.init);
|
|
234
|
+
if (!this.readyResolved) {
|
|
235
|
+
this.readyResolved = true;
|
|
236
|
+
this.resolveReady();
|
|
237
|
+
}
|
|
238
|
+
this.emit('open');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
ws.addEventListener('message', (event: MessageEvent) => {
|
|
242
|
+
if (this.ws !== ws) return; // stale socket from a previous connection; ignore
|
|
243
|
+
this.handleMessage(event.data);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
ws.addEventListener('close', (event: CloseEvent) => {
|
|
247
|
+
if (this.ws !== ws) return; // stale socket from a previous connection; ignore
|
|
248
|
+
const code = event?.code ?? 1000;
|
|
249
|
+
this.emit('close', code);
|
|
250
|
+
this.maybeReconnect();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
ws.addEventListener('error', () => {
|
|
254
|
+
if (this.ws !== ws) return; // stale socket from a previous connection; ignore
|
|
255
|
+
if (!this.readyResolved) {
|
|
256
|
+
this.readyResolved = true;
|
|
257
|
+
this.rejectReady(new Error('Realtime WebSocket connection failed'));
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private handleMessage(data: unknown): void {
|
|
263
|
+
const audio = toUint8(data);
|
|
264
|
+
if (audio) {
|
|
265
|
+
this.emit('audio', audio, REALTIME_OUTPUT_SAMPLE_RATE);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (typeof data !== 'string') return;
|
|
269
|
+
let parsed: RealtimeEvent;
|
|
270
|
+
try {
|
|
271
|
+
parsed = JSON.parse(data) as RealtimeEvent;
|
|
272
|
+
} catch {
|
|
273
|
+
if (this.ctx.debug) console.warn('TenxGraphClient [realtime]: bad JSON frame', data);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (parsed.type === 'session_update') {
|
|
277
|
+
this.resumptionHandle = parsed.resumption_handle ?? this.resumptionHandle;
|
|
278
|
+
}
|
|
279
|
+
if (parsed.type === 'error' && parsed.fatal) {
|
|
280
|
+
this.fatal = true;
|
|
281
|
+
}
|
|
282
|
+
this.emit('event', parsed);
|
|
283
|
+
// Per-type channel. Cast is safe: the discriminant matches the channel name.
|
|
284
|
+
this.emit(parsed.type as ChannelName, parsed as never);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private maybeReconnect(): void {
|
|
288
|
+
if (this.closedByUser || this.fatal || !this.reconnectCfg.enabled) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (this.reconnectAttempts >= this.reconnectCfg.maxAttempts) {
|
|
292
|
+
// Latch fatal so a duplicate close on the final socket can't re-emit the failure.
|
|
293
|
+
this.fatal = true;
|
|
294
|
+
const failure = {
|
|
295
|
+
type: 'error' as const,
|
|
296
|
+
code: 'reconnect_failed',
|
|
297
|
+
message: `Realtime reconnect gave up after ${this.reconnectCfg.maxAttempts} attempt(s)`,
|
|
298
|
+
fatal: true,
|
|
299
|
+
};
|
|
300
|
+
this.emit('error', failure);
|
|
301
|
+
this.emit('event', failure);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.reconnectAttempts += 1;
|
|
305
|
+
const attempt = this.reconnectAttempts;
|
|
306
|
+
const delayMs =
|
|
307
|
+
Math.min(this.reconnectCfg.baseDelay * 2 ** (attempt - 1), this.reconnectCfg.maxDelay) * 1000;
|
|
308
|
+
this.emit('reconnecting', attempt);
|
|
309
|
+
setTimeout(() => this.reconnect(), delayMs);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private reconnect(): void {
|
|
313
|
+
// Forward the latest resumption handle so the provider can resume server-side.
|
|
314
|
+
this.init = {
|
|
315
|
+
...this.init,
|
|
316
|
+
thread_id: this.threadId,
|
|
317
|
+
...(this.resumptionHandle ? { resumption_handle: this.resumptionHandle } : {}),
|
|
318
|
+
};
|
|
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);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ResponseMetadata } from './metadata.js';
|
|
2
|
+
import { createErrorFromResponse } from '../errors.js';
|
|
3
|
+
import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
|
|
4
|
+
import { MemoryType } from './storeMemory.js';
|
|
5
|
+
|
|
6
|
+
export interface SearchMemoryContext extends RequestContext {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Memory retrieval strategies
|
|
10
|
+
*/
|
|
11
|
+
export enum RetrievalStrategy {
|
|
12
|
+
SIMILARITY = 'similarity', // Vector similarity search
|
|
13
|
+
TEMPORAL = 'temporal', // Time-based retrieval
|
|
14
|
+
RELEVANCE = 'relevance', // Relevance scoring
|
|
15
|
+
HYBRID = 'hybrid', // Combined approaches
|
|
16
|
+
GRAPH_TRAVERSAL = 'graph_traversal', // Knowledge graph navigation
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Supported distance metrics for vector similarity
|
|
21
|
+
*/
|
|
22
|
+
export enum DistanceMetric {
|
|
23
|
+
COSINE = 'cosine',
|
|
24
|
+
EUCLIDEAN = 'euclidean',
|
|
25
|
+
DOT_PRODUCT = 'dot_product',
|
|
26
|
+
MANHATTAN = 'manhattan',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SearchMemoryRequest {
|
|
30
|
+
config?: Record<string, any>;
|
|
31
|
+
options?: Record<string, any>;
|
|
32
|
+
query: string;
|
|
33
|
+
memory_type?: MemoryType;
|
|
34
|
+
category?: string;
|
|
35
|
+
limit?: number;
|
|
36
|
+
score_threshold?: number;
|
|
37
|
+
filters?: Record<string, any>;
|
|
38
|
+
retrieval_strategy?: RetrievalStrategy;
|
|
39
|
+
distance_metric?: DistanceMetric;
|
|
40
|
+
max_tokens?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MemoryResult {
|
|
44
|
+
id: string;
|
|
45
|
+
content: string;
|
|
46
|
+
score: number;
|
|
47
|
+
memory_type: string;
|
|
48
|
+
metadata: Record<string, any>;
|
|
49
|
+
vector: number[];
|
|
50
|
+
user_id: string;
|
|
51
|
+
thread_id: string;
|
|
52
|
+
timestamp: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SearchMemoryData {
|
|
56
|
+
results: MemoryResult[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SearchMemoryResponse {
|
|
60
|
+
data: SearchMemoryData;
|
|
61
|
+
metadata: ResponseMetadata;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function searchMemory(
|
|
65
|
+
context: SearchMemoryContext,
|
|
66
|
+
request: SearchMemoryRequest
|
|
67
|
+
): Promise<SearchMemoryResponse> {
|
|
68
|
+
try {
|
|
69
|
+
if (context.debug) {
|
|
70
|
+
console.debug('TenxGraphClient: Searching memories with query:', request.query);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
75
|
+
|
|
76
|
+
const url = `${context.baseUrl}/v1/store/search`;
|
|
77
|
+
|
|
78
|
+
// Prepare request body with defaults
|
|
79
|
+
const body = {
|
|
80
|
+
config: request.config || {},
|
|
81
|
+
options: request.options || {},
|
|
82
|
+
query: request.query,
|
|
83
|
+
memory_type: request.memory_type || MemoryType.EPISODIC,
|
|
84
|
+
category: request.category || '',
|
|
85
|
+
limit: request.limit !== undefined ? request.limit : 10,
|
|
86
|
+
score_threshold: request.score_threshold !== undefined ? request.score_threshold : 0,
|
|
87
|
+
filters: request.filters || {},
|
|
88
|
+
retrieval_strategy: request.retrieval_strategy || RetrievalStrategy.SIMILARITY,
|
|
89
|
+
distance_metric: request.distance_metric || DistanceMetric.COSINE,
|
|
90
|
+
max_tokens: request.max_tokens !== undefined ? request.max_tokens : 4000,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const response = await fetch(url, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: buildHeaders(context as RequestContext, {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
}),
|
|
98
|
+
...getRequestCredentials(context as RequestContext),
|
|
99
|
+
body: JSON.stringify(body),
|
|
100
|
+
signal: controller.signal,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
clearTimeout(timeoutId);
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
console.warn(`TenxGraphClient: Search memory failed with HTTP ${response.status}`);
|
|
107
|
+
const error = await createErrorFromResponse(
|
|
108
|
+
response,
|
|
109
|
+
'Search memory request failed',
|
|
110
|
+
'/v1/memory/search',
|
|
111
|
+
'POST'
|
|
112
|
+
);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const data: SearchMemoryResponse = await response.json();
|
|
117
|
+
|
|
118
|
+
if (context.debug) {
|
|
119
|
+
console.info('TenxGraphClient: Memory search successful', {
|
|
120
|
+
query: request.query,
|
|
121
|
+
results_count: data.data.results.length,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return data;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (context.debug) {
|
|
128
|
+
console.debug('TenxGraphClient: Search memory failed:', error);
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ResponseMetadata } from './metadata.js';
|
|
2
|
+
import { createErrorFromResponse } from '../errors.js';
|
|
3
|
+
import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
|
|
4
|
+
|
|
5
|
+
export interface StateSchemaContext extends RequestContext {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* JSON Schema definition for a field in AgentState
|
|
9
|
+
* Contains type information, description, validation rules, and default values
|
|
10
|
+
*/
|
|
11
|
+
export interface FieldSchema {
|
|
12
|
+
type?: string | string[];
|
|
13
|
+
description?: string;
|
|
14
|
+
default?: any;
|
|
15
|
+
items?: any;
|
|
16
|
+
properties?: Record<string, FieldSchema>;
|
|
17
|
+
required?: string[];
|
|
18
|
+
enum?: any[];
|
|
19
|
+
$ref?: string;
|
|
20
|
+
$defs?: Record<string, any>;
|
|
21
|
+
anyOf?: any[];
|
|
22
|
+
allOf?: any[];
|
|
23
|
+
oneOf?: any[];
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Complete JSON Schema for AgentState
|
|
29
|
+
* Describes all available fields, their types, constraints, and meanings
|
|
30
|
+
*/
|
|
31
|
+
export interface AgentStateSchema {
|
|
32
|
+
title?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
type?: string;
|
|
35
|
+
properties: Record<string, FieldSchema>;
|
|
36
|
+
required?: string[];
|
|
37
|
+
$defs?: Record<string, any>;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Response containing the complete AgentState schema
|
|
43
|
+
* Users can use this to:
|
|
44
|
+
* - Understand field types and structure
|
|
45
|
+
* - Build dynamic forms or validation
|
|
46
|
+
* - Know what fields are available and required
|
|
47
|
+
* - See field descriptions and constraints
|
|
48
|
+
*/
|
|
49
|
+
export interface StateSchemaResponse {
|
|
50
|
+
data: AgentStateSchema;
|
|
51
|
+
metadata: ResponseMetadata;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function stateSchema(context: StateSchemaContext): Promise<StateSchemaResponse> {
|
|
55
|
+
try {
|
|
56
|
+
if (context.debug) {
|
|
57
|
+
console.debug('TenxGraphClient: Fetching state schema from', context.baseUrl);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
62
|
+
|
|
63
|
+
const response = await fetch(`${context.baseUrl}/v1/graph:StateSchema`, {
|
|
64
|
+
method: 'GET',
|
|
65
|
+
headers: buildHeaders(context as RequestContext, {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
accept: 'application/json',
|
|
68
|
+
}),
|
|
69
|
+
...getRequestCredentials(context as RequestContext),
|
|
70
|
+
signal: controller.signal,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
clearTimeout(timeoutId);
|
|
74
|
+
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
console.warn(`TenxGraphClient: State schema fetch failed with HTTP ${response.status}`);
|
|
77
|
+
const error = await createErrorFromResponse(
|
|
78
|
+
response,
|
|
79
|
+
'State schema fetch failed',
|
|
80
|
+
'/v1/state_schema',
|
|
81
|
+
'GET'
|
|
82
|
+
);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const data: StateSchemaResponse = await response.json();
|
|
87
|
+
|
|
88
|
+
if (context.debug) {
|
|
89
|
+
console.info('TenxGraphClient: State schema fetch successful', data);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return data;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (context.debug) {
|
|
95
|
+
console.debug('TenxGraphClient: State schema fetch failed:', error);
|
|
96
|
+
}
|
|
97
|
+
|
|
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
|
+
console.error('TenxGraphClient: State schema fetch failed:', error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ResponseMetadata } from './metadata.js';
|
|
2
|
+
import { createErrorFromResponse } from '../errors.js';
|
|
3
|
+
import { buildHeaders, getRequestCredentials, RequestContext } from '../request.js';
|
|
4
|
+
|
|
5
|
+
export interface StopGraphContext extends RequestContext {}
|
|
6
|
+
|
|
7
|
+
export interface StopGraphRequest {
|
|
8
|
+
thread_id: string;
|
|
9
|
+
config?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StopGraphResponse {
|
|
13
|
+
data: {
|
|
14
|
+
success: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
thread_id: string;
|
|
17
|
+
stopped_at?: string;
|
|
18
|
+
};
|
|
19
|
+
metadata: ResponseMetadata;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Stop a running graph execution for a specific thread
|
|
24
|
+
*
|
|
25
|
+
* @param context - Request context with baseUrl, auth, timeout
|
|
26
|
+
* @param request - Stop request with thread_id and optional config
|
|
27
|
+
* @returns Promise<StopGraphResponse> with stop operation result
|
|
28
|
+
*/
|
|
29
|
+
export async function stopGraph(
|
|
30
|
+
context: StopGraphContext,
|
|
31
|
+
request: StopGraphRequest
|
|
32
|
+
): Promise<StopGraphResponse> {
|
|
33
|
+
try {
|
|
34
|
+
if (context.debug) {
|
|
35
|
+
console.debug('TenxGraphClient: Stopping graph execution for thread:', request.thread_id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeoutId = setTimeout(() => controller.abort(), context.timeout);
|
|
40
|
+
|
|
41
|
+
const response = await fetch(`${context.baseUrl}/v1/graph/stop`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: buildHeaders(context as RequestContext, {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
accept: 'application/json',
|
|
46
|
+
}),
|
|
47
|
+
...getRequestCredentials(context as RequestContext),
|
|
48
|
+
body: JSON.stringify(request),
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
clearTimeout(timeoutId);
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
console.warn(`TenxGraphClient: Stop graph failed with HTTP ${response.status}`);
|
|
56
|
+
const error = await createErrorFromResponse(
|
|
57
|
+
response,
|
|
58
|
+
'Stop graph execution failed',
|
|
59
|
+
'/v1/graph/stop',
|
|
60
|
+
'POST'
|
|
61
|
+
);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data: StopGraphResponse = await response.json();
|
|
66
|
+
|
|
67
|
+
if (context.debug) {
|
|
68
|
+
console.info('TenxGraphClient: Graph execution stopped successfully', data);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return data;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (context.debug) {
|
|
74
|
+
console.debug('TenxGraphClient: Stop graph failed:', error);
|
|
75
|
+
}
|
|
76
|
+
|
|
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
|
+
console.error('TenxGraphClient: Stop graph failed:', error);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|