@recursiv/sdk 0.7.50 → 0.7.52
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/README.md +69 -12
- package/dist/client.js +3 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.js +5 -1
- package/dist/realtime.d.ts +79 -9
- package/dist/realtime.js +339 -76
- package/dist/resources/admin.d.ts +13 -0
- package/dist/resources/agents.d.ts +41 -4
- package/dist/resources/agents.js +45 -24
- package/dist/resources/app-subscriptions.d.ts +102 -4
- package/dist/resources/app-subscriptions.js +43 -3
- package/dist/resources/auth.d.ts +198 -2
- package/dist/resources/auth.js +397 -3
- package/dist/resources/boosts.d.ts +170 -0
- package/dist/resources/boosts.js +75 -0
- package/dist/resources/chat.d.ts +33 -3
- package/dist/resources/chat.js +38 -2
- package/dist/resources/communities.d.ts +23 -11
- package/dist/resources/communities.js +20 -6
- package/dist/resources/curator.d.ts +24 -6
- package/dist/resources/curator.js +2 -1
- package/dist/resources/deployments.d.ts +8 -3
- package/dist/resources/discovery.d.ts +15 -1
- package/dist/resources/discovery.js +29 -1
- package/dist/resources/invite-codes-admin.d.ts +13 -0
- package/dist/resources/invite-codes.d.ts +10 -0
- package/dist/resources/invite-codes.js +6 -6
- package/dist/resources/notifications.d.ts +74 -5
- package/dist/resources/notifications.js +38 -8
- package/dist/resources/posts.d.ts +28 -5
- package/dist/resources/posts.js +13 -4
- package/dist/resources/profiles.d.ts +45 -9
- package/dist/resources/profiles.js +54 -3
- package/dist/resources/protocols.d.ts +195 -14
- package/dist/resources/protocols.js +77 -12
- package/dist/resources/settings.d.ts +25 -3
- package/dist/resources/settings.js +28 -2
- package/dist/resources/video.d.ts +19 -0
- package/dist/resources/video.js +8 -0
- package/dist/resources/wallet.d.ts +41 -1
- package/dist/resources/wallet.js +8 -0
- package/dist/types.d.ts +217 -6
- package/llm.md +68 -20
- package/package.json +2 -2
package/dist/realtime.js
CHANGED
|
@@ -16,11 +16,62 @@ async function loadIo() {
|
|
|
16
16
|
}
|
|
17
17
|
return cachedIo;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Wider and more jittered than socket.io's own 1s/5s: when an API restart
|
|
21
|
+
* drops every client at once they must not all re-fetch `/realtime/ws-token`
|
|
22
|
+
* in the same second, which rate-limits it and keeps them all offline.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_BACKOFF = {
|
|
25
|
+
reconnectionDelay: 2_000,
|
|
26
|
+
reconnectionDelayMax: 30_000,
|
|
27
|
+
randomizationFactor: 0.5,
|
|
28
|
+
};
|
|
29
|
+
/** The token source refused the credential itself; retrying cannot succeed. */
|
|
30
|
+
function isCredentialRefusal(err) {
|
|
31
|
+
const status = err?.status;
|
|
32
|
+
return status === 401 || status === 403;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* WebSocket client for chat and presence.
|
|
36
|
+
*
|
|
37
|
+
* One socket at a time, and it survives the ways a socket dies:
|
|
38
|
+
* - a transport drop (network blip, sleep, backgrounding, API restart) is
|
|
39
|
+
* retried by socket.io with jittered backoff;
|
|
40
|
+
* - the server ending the connection because its token expired, or refusing
|
|
41
|
+
* a reconnect handshake (spent or expired token, rate limit), is retried
|
|
42
|
+
* here with the same backoff, since socket.io does not retry either;
|
|
43
|
+
* - every attempt authenticates with its own token (the server accepts each
|
|
44
|
+
* token once), and a failed token fetch sends none rather than a spent one;
|
|
45
|
+
* - listeners registered with `on()`/`onMessage()`… and rooms joined with
|
|
46
|
+
* `joinConversation()` are re-applied to every connection, including a
|
|
47
|
+
* socket rebuilt by a later `connect()`.
|
|
48
|
+
*
|
|
49
|
+
* Reconnecting stops only on `disconnect()` or when the token source refuses
|
|
50
|
+
* the credential (401/403).
|
|
51
|
+
*/
|
|
19
52
|
export class RealtimeClient {
|
|
20
53
|
socket = null;
|
|
21
54
|
client;
|
|
22
55
|
connectionPromise = null;
|
|
23
56
|
tokenProvider = null;
|
|
57
|
+
backoff = { ...DEFAULT_BACKOFF };
|
|
58
|
+
/** Every subscription, attached to each socket this client builds. */
|
|
59
|
+
listeners = new Map();
|
|
60
|
+
/** Conversation rooms to join on every connect. */
|
|
61
|
+
rooms = new Set();
|
|
62
|
+
/** connect() callers waiting on a socket that is reconnecting. */
|
|
63
|
+
waiters = [];
|
|
64
|
+
recoveryTimer = null;
|
|
65
|
+
recoveryAttempts = 0;
|
|
66
|
+
credentialRefused = false;
|
|
67
|
+
/** Bumped by disconnect(), so a connect() still in flight does not revive a socket. */
|
|
68
|
+
generation = 0;
|
|
69
|
+
/**
|
|
70
|
+
* The socket whose first handshake a connect() is awaiting. Closing a socket
|
|
71
|
+
* that never connected makes socket.io emit nothing, so discardSocket()
|
|
72
|
+
* settles that connect() through this.
|
|
73
|
+
*/
|
|
74
|
+
pendingOpen = null;
|
|
24
75
|
constructor(client) {
|
|
25
76
|
this.client = client;
|
|
26
77
|
}
|
|
@@ -32,107 +83,324 @@ export class RealtimeClient {
|
|
|
32
83
|
const response = await this.client.get('/realtime/ws-token');
|
|
33
84
|
return response.data?.token || undefined;
|
|
34
85
|
}
|
|
35
|
-
catch {
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (isCredentialRefusal(err))
|
|
88
|
+
this.credentialRefused = true;
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** A token for one reconnect attempt. Never throws: no token means the attempt is refused and retried. */
|
|
93
|
+
async mintToken() {
|
|
94
|
+
try {
|
|
95
|
+
const token = this.tokenProvider ? await this.tokenProvider() : await this.fetchTokenWithClientKey();
|
|
96
|
+
if (token)
|
|
97
|
+
this.credentialRefused = false;
|
|
98
|
+
return token || undefined;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (isCredentialRefusal(err))
|
|
102
|
+
this.credentialRefused = true;
|
|
36
103
|
return undefined;
|
|
37
104
|
}
|
|
38
105
|
}
|
|
39
106
|
/**
|
|
40
107
|
* Connects to the WebSocket server using a temporary WebSocket token.
|
|
108
|
+
* Resolves with the connected socket.
|
|
41
109
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* successful connect, socket.io's auto-reconnection owns recovery and
|
|
47
|
-
* transient errors never disable it;
|
|
48
|
-
* - auth uses socket.io's function form, so every (re)connection attempt
|
|
49
|
-
* fetches a fresh token via `options.getToken` or the client's key.
|
|
110
|
+
* Safe to call as often as you like: a connected socket is returned as is,
|
|
111
|
+
* a socket that is reconnecting is waited on (the promise rejects if that
|
|
112
|
+
* attempt fails, while the client keeps retrying), and only a socket that
|
|
113
|
+
* has stopped for good is replaced.
|
|
50
114
|
*/
|
|
51
115
|
async connect(options) {
|
|
52
116
|
if (options?.getToken) {
|
|
53
117
|
this.tokenProvider = options.getToken;
|
|
54
118
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (this.socket && !this.socket.connected && !this.socket.active) {
|
|
61
|
-
this.socket.disconnect();
|
|
62
|
-
this.socket = null;
|
|
63
|
-
this.connectionPromise = null;
|
|
119
|
+
if (options)
|
|
120
|
+
this.applyBackoff(options);
|
|
121
|
+
const current = this.socket;
|
|
122
|
+
if (current?.connected) {
|
|
123
|
+
return current;
|
|
64
124
|
}
|
|
65
125
|
if (this.connectionPromise) {
|
|
66
126
|
return this.connectionPromise;
|
|
67
127
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
provider()
|
|
82
|
-
.then((fresh) => cb({ token: fresh ?? token }))
|
|
83
|
-
.catch(() => cb({ token }));
|
|
84
|
-
},
|
|
85
|
-
autoConnect: true,
|
|
86
|
-
reconnection: true,
|
|
87
|
-
});
|
|
88
|
-
this.socket = socket;
|
|
89
|
-
return await new Promise((resolve, reject) => {
|
|
90
|
-
const onConnect = () => {
|
|
91
|
-
socket.off('connect', onConnect);
|
|
92
|
-
socket.off('connect_error', onInitialError);
|
|
93
|
-
resolve(socket);
|
|
94
|
-
};
|
|
95
|
-
const onInitialError = (err) => {
|
|
96
|
-
// Initial handshake failed — tear down and surface the error.
|
|
97
|
-
// This handler is removed on first successful connect, so later
|
|
98
|
-
// reconnect errors never disable the socket.
|
|
99
|
-
socket.off('connect', onConnect);
|
|
100
|
-
socket.off('connect_error', onInitialError);
|
|
101
|
-
socket.disconnect();
|
|
102
|
-
if (this.socket === socket) {
|
|
103
|
-
this.socket = null;
|
|
104
|
-
}
|
|
105
|
-
reject(err);
|
|
106
|
-
};
|
|
107
|
-
socket.on('connect', onConnect);
|
|
108
|
-
socket.on('connect_error', onInitialError);
|
|
109
|
-
});
|
|
110
|
-
})();
|
|
128
|
+
// socket.io is retrying the transport, or a retry of a refused handshake
|
|
129
|
+
// is scheduled. Building a second socket here would leave the first one
|
|
130
|
+
// reconnecting in the background with its own listeners: duplicate events
|
|
131
|
+
// and an extra server connection per call.
|
|
132
|
+
if (current && (current.active || this.recoveryTimer)) {
|
|
133
|
+
return this.waitForConnect();
|
|
134
|
+
}
|
|
135
|
+
// Neither connected nor reconnecting: socket.io has given up on it.
|
|
136
|
+
if (current) {
|
|
137
|
+
this.discardSocket(current, new Error('Realtime connection replaced'));
|
|
138
|
+
}
|
|
139
|
+
const opening = this.openSocket();
|
|
140
|
+
this.connectionPromise = opening;
|
|
111
141
|
try {
|
|
112
|
-
return await
|
|
142
|
+
return await opening;
|
|
113
143
|
}
|
|
114
144
|
finally {
|
|
115
145
|
// Clear after settling (success OR failure): the live socket is held in
|
|
116
|
-
// this.socket; a dead one must not be served from a stale promise.
|
|
117
|
-
|
|
146
|
+
// this.socket; a dead one must not be served from a stale promise. Only
|
|
147
|
+
// clear our own: after disconnect() a later connect() may already own
|
|
148
|
+
// the slot, and forgetting it would let the next call open a second
|
|
149
|
+
// socket beside it.
|
|
150
|
+
if (this.connectionPromise === opening)
|
|
151
|
+
this.connectionPromise = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
applyBackoff(options) {
|
|
155
|
+
const next = { ...this.backoff };
|
|
156
|
+
if (typeof options.reconnectionDelay === 'number' && options.reconnectionDelay >= 0) {
|
|
157
|
+
next.reconnectionDelay = options.reconnectionDelay;
|
|
158
|
+
}
|
|
159
|
+
if (typeof options.reconnectionDelayMax === 'number' && options.reconnectionDelayMax >= 0) {
|
|
160
|
+
next.reconnectionDelayMax = options.reconnectionDelayMax;
|
|
118
161
|
}
|
|
162
|
+
if (typeof options.randomizationFactor === 'number') {
|
|
163
|
+
next.randomizationFactor = Math.min(1, Math.max(0, options.randomizationFactor));
|
|
164
|
+
}
|
|
165
|
+
this.backoff = next;
|
|
119
166
|
}
|
|
167
|
+
async openSocket() {
|
|
168
|
+
const generation = this.generation;
|
|
169
|
+
const io = await loadIo();
|
|
170
|
+
const token = this.tokenProvider ? await this.tokenProvider() : await this.fetchTokenWithClientKey();
|
|
171
|
+
if (!token) {
|
|
172
|
+
throw new Error('Failed to fetch WebSocket token');
|
|
173
|
+
}
|
|
174
|
+
if (generation !== this.generation) {
|
|
175
|
+
throw new Error('Realtime disconnected before the connection opened');
|
|
176
|
+
}
|
|
177
|
+
this.credentialRefused = false;
|
|
178
|
+
// The server accepts each token once. The first handshake spends the one
|
|
179
|
+
// fetched above; every later attempt (socket.io's transport reconnects and
|
|
180
|
+
// the recoveries below) mints its own, and sends none when that fails
|
|
181
|
+
// rather than replaying a spent token.
|
|
182
|
+
let unspent = token;
|
|
183
|
+
const socket = io(this.client.baseOrigin, {
|
|
184
|
+
path: '/socket.io',
|
|
185
|
+
transports: ['websocket', 'polling'],
|
|
186
|
+
// Function-form auth: socket.io invokes this on EVERY connection attempt.
|
|
187
|
+
auth: (cb) => {
|
|
188
|
+
const first = unspent;
|
|
189
|
+
unspent = undefined;
|
|
190
|
+
if (first) {
|
|
191
|
+
cb({ token: first });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
void this.mintToken().then((fresh) => cb(fresh ? { token: fresh } : {}));
|
|
195
|
+
},
|
|
196
|
+
autoConnect: true,
|
|
197
|
+
reconnection: true,
|
|
198
|
+
reconnectionDelay: this.backoff.reconnectionDelay,
|
|
199
|
+
reconnectionDelayMax: this.backoff.reconnectionDelayMax,
|
|
200
|
+
randomizationFactor: this.backoff.randomizationFactor,
|
|
201
|
+
});
|
|
202
|
+
this.socket = socket;
|
|
203
|
+
this.watchLifecycle(socket);
|
|
204
|
+
for (const [event, listeners] of this.listeners) {
|
|
205
|
+
for (const listener of listeners)
|
|
206
|
+
socket.on(event, listener);
|
|
207
|
+
}
|
|
208
|
+
return await new Promise((resolve, reject) => {
|
|
209
|
+
const settle = () => {
|
|
210
|
+
socket.off('connect', onConnect);
|
|
211
|
+
socket.off('connect_error', onInitialError);
|
|
212
|
+
if (this.pendingOpen?.socket === socket)
|
|
213
|
+
this.pendingOpen = null;
|
|
214
|
+
};
|
|
215
|
+
const onConnect = () => {
|
|
216
|
+
settle();
|
|
217
|
+
resolve(socket);
|
|
218
|
+
};
|
|
219
|
+
const onInitialError = (err) => {
|
|
220
|
+
// Initial handshake failed — tear down and surface the error. This
|
|
221
|
+
// handler is removed on first successful connect, so later reconnect
|
|
222
|
+
// errors never disable the socket.
|
|
223
|
+
settle();
|
|
224
|
+
this.discardSocket(socket, err);
|
|
225
|
+
reject(err);
|
|
226
|
+
};
|
|
227
|
+
// disconnect() before the first handshake completes.
|
|
228
|
+
this.pendingOpen = {
|
|
229
|
+
socket,
|
|
230
|
+
fail: (err) => {
|
|
231
|
+
settle();
|
|
232
|
+
reject(err);
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
socket.on('connect', onConnect);
|
|
236
|
+
socket.on('connect_error', onInitialError);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/** Room rejoin, waiter settlement and recovery from the disconnects socket.io leaves alone. */
|
|
240
|
+
watchLifecycle(socket) {
|
|
241
|
+
let connectedOnce = false;
|
|
242
|
+
let tokenExpired = false;
|
|
243
|
+
socket.on('connect', () => {
|
|
244
|
+
if (this.socket !== socket)
|
|
245
|
+
return;
|
|
246
|
+
connectedOnce = true;
|
|
247
|
+
tokenExpired = false;
|
|
248
|
+
this.recoveryAttempts = 0;
|
|
249
|
+
// Room membership lives on the server-side connection, so a reconnect
|
|
250
|
+
// starts in no rooms. Rejoin every room the app is in.
|
|
251
|
+
for (const conversationId of this.rooms) {
|
|
252
|
+
socket.emit('join_conversation', conversationId);
|
|
253
|
+
}
|
|
254
|
+
const waiters = this.waiters;
|
|
255
|
+
this.waiters = [];
|
|
256
|
+
for (const waiter of waiters)
|
|
257
|
+
waiter.resolve(socket);
|
|
258
|
+
});
|
|
259
|
+
// The server announces an expired token just before it ends the connection.
|
|
260
|
+
socket.on('token_expired', () => {
|
|
261
|
+
tokenExpired = true;
|
|
262
|
+
});
|
|
263
|
+
socket.on('disconnect', (reason) => {
|
|
264
|
+
if (this.socket !== socket)
|
|
265
|
+
return;
|
|
266
|
+
// socket.io never reconnects a server-side disconnect. One that follows
|
|
267
|
+
// token_expired is routine (every token TTL, 10 minutes by default), so
|
|
268
|
+
// reconnect with a fresh token. Any other server disconnect, such as an
|
|
269
|
+
// eviction for too many open sockets, is left alone; the next connect()
|
|
270
|
+
// replaces the socket.
|
|
271
|
+
if (reason === 'io server disconnect' && tokenExpired) {
|
|
272
|
+
tokenExpired = false;
|
|
273
|
+
this.scheduleRecovery(socket);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
socket.on('connect_error', (err) => {
|
|
277
|
+
if (this.socket !== socket || !connectedOnce)
|
|
278
|
+
return;
|
|
279
|
+
const waiters = this.waiters;
|
|
280
|
+
this.waiters = [];
|
|
281
|
+
for (const waiter of waiters)
|
|
282
|
+
waiter.reject(err);
|
|
283
|
+
// `active` stays true while socket.io retries a transport failure. It
|
|
284
|
+
// turns false when the server refused the handshake (spent or expired
|
|
285
|
+
// token, rate limit), which socket.io never retries.
|
|
286
|
+
if (!socket.active) {
|
|
287
|
+
this.scheduleRecovery(socket);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
scheduleRecovery(socket) {
|
|
292
|
+
if (this.socket !== socket || this.recoveryTimer)
|
|
293
|
+
return;
|
|
294
|
+
if (this.credentialRefused) {
|
|
295
|
+
// The token source refused the credential. Stop here; an explicit
|
|
296
|
+
// connect() tries again from scratch.
|
|
297
|
+
this.discardSocket(socket, new Error('Realtime stopped: the WebSocket token request was refused (401/403)'));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const delay = this.recoveryDelay(this.recoveryAttempts++);
|
|
301
|
+
this.recoveryTimer = setTimeout(() => {
|
|
302
|
+
this.recoveryTimer = null;
|
|
303
|
+
if (this.socket !== socket || socket.connected || socket.active)
|
|
304
|
+
return;
|
|
305
|
+
socket.connect();
|
|
306
|
+
}, delay);
|
|
307
|
+
}
|
|
308
|
+
/** Exponential backoff with jitter, bounded by reconnectionDelayMax. */
|
|
309
|
+
recoveryDelay(attempt) {
|
|
310
|
+
const { reconnectionDelay, reconnectionDelayMax, randomizationFactor } = this.backoff;
|
|
311
|
+
const base = Math.min(reconnectionDelayMax, reconnectionDelay * 2 ** Math.min(attempt, 20));
|
|
312
|
+
const jitter = base * randomizationFactor * (Math.random() * 2 - 1);
|
|
313
|
+
return Math.round(Math.min(reconnectionDelayMax, Math.max(0, base + jitter)));
|
|
314
|
+
}
|
|
315
|
+
waitForConnect() {
|
|
316
|
+
return new Promise((resolve, reject) => {
|
|
317
|
+
this.waiters.push({ resolve, reject });
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
discardSocket(socket, reason) {
|
|
321
|
+
if (this.recoveryTimer) {
|
|
322
|
+
clearTimeout(this.recoveryTimer);
|
|
323
|
+
this.recoveryTimer = null;
|
|
324
|
+
}
|
|
325
|
+
if (this.pendingOpen?.socket === socket) {
|
|
326
|
+
this.pendingOpen.fail(reason);
|
|
327
|
+
}
|
|
328
|
+
socket.disconnect();
|
|
329
|
+
for (const [event, listeners] of this.listeners) {
|
|
330
|
+
for (const listener of listeners)
|
|
331
|
+
socket.off(event, listener);
|
|
332
|
+
}
|
|
333
|
+
if (this.socket === socket) {
|
|
334
|
+
this.socket = null;
|
|
335
|
+
}
|
|
336
|
+
const waiters = this.waiters;
|
|
337
|
+
this.waiters = [];
|
|
338
|
+
for (const waiter of waiters)
|
|
339
|
+
waiter.reject(reason);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Close the connection and stop reconnecting. Joined rooms are forgotten;
|
|
343
|
+
* listeners stay registered and attach to the next connection.
|
|
344
|
+
*/
|
|
120
345
|
disconnect() {
|
|
346
|
+
this.generation += 1;
|
|
347
|
+
this.rooms.clear();
|
|
348
|
+
this.recoveryAttempts = 0;
|
|
349
|
+
this.credentialRefused = false;
|
|
121
350
|
if (this.socket) {
|
|
122
|
-
this.socket
|
|
123
|
-
|
|
351
|
+
this.discardSocket(this.socket, new Error('Realtime disconnected'));
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const waiters = this.waiters;
|
|
355
|
+
this.waiters = [];
|
|
356
|
+
for (const waiter of waiters)
|
|
357
|
+
waiter.reject(new Error('Realtime disconnected'));
|
|
124
358
|
}
|
|
125
359
|
this.connectionPromise = null;
|
|
126
360
|
}
|
|
127
361
|
getSocket() {
|
|
128
362
|
return this.socket;
|
|
129
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Listen for any server event, including the lifecycle events `connect`
|
|
366
|
+
* (initial and every reconnect: refetch what you may have missed) and
|
|
367
|
+
* `disconnect`. The listener stays attached across reconnects and socket
|
|
368
|
+
* replacement. Returns an unsubscribe function.
|
|
369
|
+
*
|
|
370
|
+
* Server events include `notification`, `feed_update`,
|
|
371
|
+
* `conversation_created`, `chat_message`, `chat_typing` and `agent_thinking`.
|
|
372
|
+
*/
|
|
373
|
+
on(event, listener) {
|
|
374
|
+
// Every call is its own subscription, even for a function that is already
|
|
375
|
+
// subscribed: it fires once per subscription, as socket.io's own `on`
|
|
376
|
+
// does, and its unsubscribe removes only itself, so one consumer can never
|
|
377
|
+
// cancel another's.
|
|
378
|
+
const handler = (...args) => listener(...args);
|
|
379
|
+
let set = this.listeners.get(event);
|
|
380
|
+
if (!set) {
|
|
381
|
+
set = new Set();
|
|
382
|
+
this.listeners.set(event, set);
|
|
383
|
+
}
|
|
384
|
+
set.add(handler);
|
|
385
|
+
this.socket?.on(event, handler);
|
|
386
|
+
return () => {
|
|
387
|
+
const current = this.listeners.get(event);
|
|
388
|
+
if (!current?.delete(handler))
|
|
389
|
+
return;
|
|
390
|
+
if (current.size === 0)
|
|
391
|
+
this.listeners.delete(event);
|
|
392
|
+
this.socket?.off(event, handler);
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** Join a conversation room now, and again after every reconnect, until you leave it. */
|
|
130
396
|
joinConversation(conversationId) {
|
|
397
|
+
this.rooms.add(conversationId);
|
|
131
398
|
if (this.socket?.connected) {
|
|
132
399
|
this.socket.emit('join_conversation', conversationId);
|
|
133
400
|
}
|
|
134
401
|
}
|
|
135
402
|
leaveConversation(conversationId) {
|
|
403
|
+
this.rooms.delete(conversationId);
|
|
136
404
|
if (this.socket?.connected) {
|
|
137
405
|
this.socket.emit('leave_conversation', conversationId);
|
|
138
406
|
}
|
|
@@ -143,23 +411,18 @@ export class RealtimeClient {
|
|
|
143
411
|
}
|
|
144
412
|
}
|
|
145
413
|
onMessage(callback) {
|
|
146
|
-
this.
|
|
147
|
-
return () => this.socket?.off('chat_message', callback);
|
|
414
|
+
return this.on('chat_message', callback);
|
|
148
415
|
}
|
|
149
416
|
onMessageEdit(callback) {
|
|
150
|
-
this.
|
|
151
|
-
return () => this.socket?.off('chat_message_edit', callback);
|
|
417
|
+
return this.on('chat_message_edit', callback);
|
|
152
418
|
}
|
|
153
419
|
onMessageDelete(callback) {
|
|
154
|
-
this.
|
|
155
|
-
return () => this.socket?.off('chat_message_delete', callback);
|
|
420
|
+
return this.on('chat_message_delete', callback);
|
|
156
421
|
}
|
|
157
422
|
onTyping(callback) {
|
|
158
|
-
this.
|
|
159
|
-
return () => this.socket?.off('chat_typing', callback);
|
|
423
|
+
return this.on('chat_typing', callback);
|
|
160
424
|
}
|
|
161
425
|
onAgentThinking(callback) {
|
|
162
|
-
this.
|
|
163
|
-
return () => this.socket?.off('agent_thinking', callback);
|
|
426
|
+
return this.on('agent_thinking', callback);
|
|
164
427
|
}
|
|
165
428
|
}
|
|
@@ -158,12 +158,25 @@ export interface InviteSystemStats {
|
|
|
158
158
|
used: number;
|
|
159
159
|
expired: number;
|
|
160
160
|
revoked: number;
|
|
161
|
+
/** @deprecated Use `total`. */
|
|
162
|
+
total_codes: number;
|
|
163
|
+
/** @deprecated Use `active`. */
|
|
164
|
+
active_codes: number;
|
|
165
|
+
/** @deprecated Use `used`. */
|
|
166
|
+
used_codes: number;
|
|
167
|
+
/** @deprecated Use `expired`. */
|
|
168
|
+
expired_codes: number;
|
|
169
|
+
/** @deprecated Use `revoked`. */
|
|
170
|
+
revoked_codes: number;
|
|
161
171
|
};
|
|
162
172
|
waitlist: Record<string, number>;
|
|
163
173
|
current_wave: {
|
|
164
174
|
id: string;
|
|
165
175
|
wave: number;
|
|
166
176
|
name: string;
|
|
177
|
+
max_codes: number;
|
|
178
|
+
invites_per_user: number;
|
|
179
|
+
reward_tokens: number;
|
|
167
180
|
is_active: boolean;
|
|
168
181
|
} | null;
|
|
169
182
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { PaginatedResponse, SingleResponse, DeleteResponse, Agent, AgentDetail, CreateAgentInput, CreatedAgent, UpdateAgentInput, UpdatedAgent, AgentChatInput, AgentChatResult, AgentConversation, AgentConversationMessage, PaginationParams, StreamChunk } from '../types.js';
|
|
2
|
+
import type { PaginatedResponse, SingleResponse, DeleteResponse, Agent, AgentDetail, CreateAgentInput, CreatedAgent, UpdateAgentInput, UpdatedAgent, AgentChatInput, AgentChatRetryInput, AgentChatResult, AgentConversation, AgentConversationMessage, PaginationParams, StreamChunk } from '../types.js';
|
|
3
3
|
export interface AgentMessage {
|
|
4
4
|
id: string;
|
|
5
5
|
networkId: string;
|
|
@@ -65,7 +65,7 @@ export interface SalesLead {
|
|
|
65
65
|
* Validation happens server-side too; this list lets apps validate or
|
|
66
66
|
* surface choices client-side without round-tripping the API.
|
|
67
67
|
*/
|
|
68
|
-
export declare const SUPPORTED_MODELS: readonly ["anthropic/claude-fable-5", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.8", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", "deepseek/deepseek-v3.2", "deepseek/deepseek-v4-pro", "google/gemini-3-flash-preview", "google/gemini-3.1-pro-preview", "google/gemini-3.5-flash", "minimax/minimax-m2.5", "minimax/minimax-m3", "moonshotai/kimi-k3", "openai/gpt-4o-mini", "openai/gpt-5.5", "openai/gpt-5.6-sol", "openai/gpt-5.6-terra", "x-ai/grok-4.3"];
|
|
68
|
+
export declare const SUPPORTED_MODELS: readonly ["anthropic/claude-fable-5", "anthropic/claude-fable-5.1", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.8", "anthropic/claude-opus-5", "anthropic/claude-opus-5.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-sonnet-5", "deepseek/deepseek-v3.2", "deepseek/deepseek-v4-pro", "google/gemini-3-flash-preview", "google/gemini-3.1-pro-preview", "google/gemini-3.5-flash", "google/gemini-3.8-flash", "minimax/minimax-m2.5", "minimax/minimax-m3", "moonshotai/kimi-k3", "openai/gpt-4o-mini", "openai/gpt-5.5", "openai/gpt-5.6-sol", "openai/gpt-5.6-terra", "openai/gpt-6-astra", "openai/gpt-6-sol", "x-ai/grok-4.3"];
|
|
69
69
|
export type SupportedModel = (typeof SUPPORTED_MODELS)[number];
|
|
70
70
|
export interface EnsurePersonalAgentOverrides {
|
|
71
71
|
/** Override the agent's display name. Defaults to 'Agent'. */
|
|
@@ -74,6 +74,17 @@ export interface EnsurePersonalAgentOverrides {
|
|
|
74
74
|
model?: string;
|
|
75
75
|
/** Override the system prompt. Apps like Minds can set their own voice. */
|
|
76
76
|
system_prompt?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Whether the agent may post publicly (reply to @mentions). The platform
|
|
79
|
+
* default for a new personal agent is 'chat_post'; pass 'chat_only' to keep
|
|
80
|
+
* it to private chat.
|
|
81
|
+
*/
|
|
82
|
+
social_mode?: 'chat_only' | 'chat_post';
|
|
83
|
+
/**
|
|
84
|
+
* How the agent uses tools. The platform default for a new personal agent is
|
|
85
|
+
* 'autonomous'; 'permission' asks the owner before a tool acts.
|
|
86
|
+
*/
|
|
87
|
+
tool_mode?: 'chat_only' | 'permission' | 'autonomous';
|
|
77
88
|
}
|
|
78
89
|
export interface EnsurePersonalAgentInput {
|
|
79
90
|
/**
|
|
@@ -105,7 +116,7 @@ export declare class AgentsResource {
|
|
|
105
116
|
}): Promise<PaginatedResponse<Agent>>;
|
|
106
117
|
/** Get an agent by ID */
|
|
107
118
|
get(id: string): Promise<SingleResponse<AgentDetail>>;
|
|
108
|
-
/** Create a new AI agent */
|
|
119
|
+
/** Create a new AI agent. Omit username for a server-generated collision-resistant handle. */
|
|
109
120
|
create(input: CreateAgentInput): Promise<SingleResponse<CreatedAgent>>;
|
|
110
121
|
/** Update an agent's configuration */
|
|
111
122
|
update(id: string, input: UpdateAgentInput): Promise<SingleResponse<UpdatedAgent>>;
|
|
@@ -137,7 +148,14 @@ export declare class AgentsResource {
|
|
|
137
148
|
* nothing landed, but never why.
|
|
138
149
|
*/
|
|
139
150
|
conversationMessages(id: string, conversationId: string, params?: PaginationParams): Promise<PaginatedResponse<AgentConversationMessage>>;
|
|
140
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* List the agents a member may discover: an app key gets the agents of its
|
|
153
|
+
* project and owning org, a personal key gets global and same-org agents.
|
|
154
|
+
* Only member-facing agents are returned. Agents that run a tenant (a
|
|
155
|
+
* project's Lead, swarm workers, dispatcher role agents, MCP client agents,
|
|
156
|
+
* the workspace AI, anyone's personal AI) never appear here; list your own
|
|
157
|
+
* with `list()`.
|
|
158
|
+
*/
|
|
141
159
|
listDiscoverable(params?: PaginationParams & {
|
|
142
160
|
organization_id?: string;
|
|
143
161
|
global?: boolean;
|
|
@@ -264,6 +282,16 @@ export declare class AgentsResource {
|
|
|
264
282
|
* For React Native, use `chatStreamText()` instead.
|
|
265
283
|
*/
|
|
266
284
|
chatStream(id: string, input: AgentChatInput): AsyncGenerator<StreamChunk>;
|
|
285
|
+
/**
|
|
286
|
+
* Retry a turn: stream a fresh reply to one of your existing messages
|
|
287
|
+
* without sending it again. Use it for "Try again" after a reply failed.
|
|
288
|
+
* The agent's earlier reply to that message, if any, is removed first.
|
|
289
|
+
*
|
|
290
|
+
* The message must be yours and the latest one in the conversation
|
|
291
|
+
* (409 `retry_not_latest` otherwise), and a reply still being written must
|
|
292
|
+
* finish or be stopped first (409 `run_in_progress`).
|
|
293
|
+
*/
|
|
294
|
+
retryChatStream(id: string, input: AgentChatRetryInput): AsyncGenerator<StreamChunk>;
|
|
267
295
|
/**
|
|
268
296
|
* Chat with an agent and get the full response text.
|
|
269
297
|
*
|
|
@@ -287,4 +315,13 @@ export declare class AgentsResource {
|
|
|
287
315
|
conversationId: string;
|
|
288
316
|
chunks: StreamChunk[];
|
|
289
317
|
}>;
|
|
318
|
+
/** `retryChatStream()` for platforms without `ReadableStream` (React Native). */
|
|
319
|
+
retryChatStreamText(id: string, input: AgentChatRetryInput): Promise<{
|
|
320
|
+
content: string;
|
|
321
|
+
conversationId: string;
|
|
322
|
+
chunks: StreamChunk[];
|
|
323
|
+
}>;
|
|
324
|
+
private openStream;
|
|
325
|
+
private streamChunks;
|
|
326
|
+
private streamText;
|
|
290
327
|
}
|