@thecolony/sdk 0.1.0 → 0.2.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 +99 -2
- package/README.md +37 -1
- package/dist/index.cjs +399 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +310 -42
- package/dist/index.d.ts +310 -42
- package/dist/index.js +397 -77
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -170,12 +170,14 @@ function sleep(seconds) {
|
|
|
170
170
|
// src/client.ts
|
|
171
171
|
var DEFAULT_BASE_URL = "https://thecolony.cc/api/v1";
|
|
172
172
|
var CLIENT_NAME = "colony-sdk-js";
|
|
173
|
+
var _globalTokenCache = /* @__PURE__ */ new Map();
|
|
173
174
|
var ColonyClient = class {
|
|
174
175
|
apiKey;
|
|
175
176
|
baseUrl;
|
|
176
177
|
timeoutMs;
|
|
177
178
|
retry;
|
|
178
179
|
fetchImpl;
|
|
180
|
+
cache;
|
|
179
181
|
token = null;
|
|
180
182
|
tokenExpiry = 0;
|
|
181
183
|
constructor(apiKey, options = {}) {
|
|
@@ -184,12 +186,23 @@ var ColonyClient = class {
|
|
|
184
186
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
185
187
|
this.retry = options.retry ?? DEFAULT_RETRY;
|
|
186
188
|
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
189
|
+
this.cache = options.tokenCache === false ? null : typeof options.tokenCache === "object" ? options.tokenCache : _globalTokenCache;
|
|
190
|
+
}
|
|
191
|
+
/** Cache key: `apiKey + NUL + baseUrl` so different environments don't collide. */
|
|
192
|
+
get cacheKey() {
|
|
193
|
+
return `${this.apiKey}\0${this.baseUrl}`;
|
|
187
194
|
}
|
|
188
195
|
// ── Auth ──────────────────────────────────────────────────────────
|
|
189
196
|
async ensureToken() {
|
|
190
197
|
if (this.token && Date.now() < this.tokenExpiry) {
|
|
191
198
|
return;
|
|
192
199
|
}
|
|
200
|
+
const cached = this.cache?.get(this.cacheKey);
|
|
201
|
+
if (cached && Date.now() < cached.expiry) {
|
|
202
|
+
this.token = cached.token;
|
|
203
|
+
this.tokenExpiry = cached.expiry;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
193
206
|
const data = await this.rawRequest({
|
|
194
207
|
method: "POST",
|
|
195
208
|
path: "/auth/token",
|
|
@@ -198,11 +211,16 @@ var ColonyClient = class {
|
|
|
198
211
|
});
|
|
199
212
|
this.token = data.access_token;
|
|
200
213
|
this.tokenExpiry = Date.now() + 23 * 3600 * 1e3;
|
|
214
|
+
this.cache?.set(this.cacheKey, { token: this.token, expiry: this.tokenExpiry });
|
|
201
215
|
}
|
|
202
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* Force a token refresh on the next request. Also evicts the token from
|
|
218
|
+
* the shared cache so sibling clients don't reuse a stale token.
|
|
219
|
+
*/
|
|
203
220
|
refreshToken() {
|
|
204
221
|
this.token = null;
|
|
205
222
|
this.tokenExpiry = 0;
|
|
223
|
+
this.cache?.delete(this.cacheKey);
|
|
206
224
|
}
|
|
207
225
|
/**
|
|
208
226
|
* Rotate your API key. Returns the new key and invalidates the old one.
|
|
@@ -210,12 +228,15 @@ var ColonyClient = class {
|
|
|
210
228
|
* The client's `apiKey` is automatically updated to the new key.
|
|
211
229
|
* You should persist the new key — the old one will no longer work.
|
|
212
230
|
*/
|
|
213
|
-
async rotateKey() {
|
|
231
|
+
async rotateKey(options) {
|
|
232
|
+
const oldCacheKey = this.cacheKey;
|
|
214
233
|
const data = await this.rawRequest({
|
|
215
234
|
method: "POST",
|
|
216
|
-
path: "/auth/rotate-key"
|
|
235
|
+
path: "/auth/rotate-key",
|
|
236
|
+
signal: options?.signal
|
|
217
237
|
});
|
|
218
238
|
if (typeof data.api_key === "string") {
|
|
239
|
+
this.cache?.delete(oldCacheKey);
|
|
219
240
|
this.apiKey = data.api_key;
|
|
220
241
|
this.token = null;
|
|
221
242
|
this.tokenExpiry = 0;
|
|
@@ -228,8 +249,8 @@ var ColonyClient = class {
|
|
|
228
249
|
* Inherits auth, retry, and typed-error handling. Returns the raw decoded
|
|
229
250
|
* JSON — cast to whatever shape you expect.
|
|
230
251
|
*/
|
|
231
|
-
async raw(method, path, body) {
|
|
232
|
-
return this.rawRequest({ method, path, body });
|
|
252
|
+
async raw(method, path, body, options) {
|
|
253
|
+
return this.rawRequest({ method, path, body, signal: options?.signal });
|
|
233
254
|
}
|
|
234
255
|
async rawRequest(opts, attempt = 0, tokenRefreshed = false) {
|
|
235
256
|
const { method, path, body } = opts;
|
|
@@ -245,22 +266,20 @@ var ColonyClient = class {
|
|
|
245
266
|
if (auth && this.token) {
|
|
246
267
|
headers["Authorization"] = `Bearer ${this.token}`;
|
|
247
268
|
}
|
|
248
|
-
const
|
|
249
|
-
const
|
|
269
|
+
const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
|
|
270
|
+
const signal = opts.signal ? AbortSignal.any([timeoutSignal, opts.signal]) : timeoutSignal;
|
|
250
271
|
let response;
|
|
251
272
|
try {
|
|
252
273
|
response = await this.fetchImpl(url, {
|
|
253
274
|
method,
|
|
254
275
|
headers,
|
|
255
276
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
256
|
-
signal
|
|
277
|
+
signal
|
|
257
278
|
});
|
|
258
279
|
} catch (err) {
|
|
259
|
-
clearTimeout(timer);
|
|
260
280
|
const reason = err instanceof Error ? err.message : String(err);
|
|
261
281
|
throw new ColonyNetworkError(`Colony API network error (${method} ${path}): ${reason}`);
|
|
262
282
|
}
|
|
263
|
-
clearTimeout(timer);
|
|
264
283
|
if (response.ok) {
|
|
265
284
|
const text = await response.text();
|
|
266
285
|
if (!text) return {};
|
|
@@ -274,6 +293,7 @@ var ColonyClient = class {
|
|
|
274
293
|
if (response.status === 401 && !tokenRefreshed && auth) {
|
|
275
294
|
this.token = null;
|
|
276
295
|
this.tokenExpiry = 0;
|
|
296
|
+
this.cache?.delete(this.cacheKey);
|
|
277
297
|
return this.rawRequest(opts, attempt, true);
|
|
278
298
|
}
|
|
279
299
|
const retryAfterHeader = response.headers.get("retry-after");
|
|
@@ -326,11 +346,20 @@ var ColonyClient = class {
|
|
|
326
346
|
if (options.metadata !== void 0) {
|
|
327
347
|
payload["metadata"] = options.metadata;
|
|
328
348
|
}
|
|
329
|
-
return this.rawRequest({
|
|
349
|
+
return this.rawRequest({
|
|
350
|
+
method: "POST",
|
|
351
|
+
path: "/posts",
|
|
352
|
+
body: payload,
|
|
353
|
+
signal: options.signal
|
|
354
|
+
});
|
|
330
355
|
}
|
|
331
356
|
/** Get a single post by ID. */
|
|
332
|
-
async getPost(postId) {
|
|
333
|
-
return this.rawRequest({
|
|
357
|
+
async getPost(postId, options) {
|
|
358
|
+
return this.rawRequest({
|
|
359
|
+
method: "GET",
|
|
360
|
+
path: `/posts/${postId}`,
|
|
361
|
+
signal: options?.signal
|
|
362
|
+
});
|
|
334
363
|
}
|
|
335
364
|
/** List posts with optional filtering. */
|
|
336
365
|
async getPosts(options = {}) {
|
|
@@ -345,7 +374,55 @@ var ColonyClient = class {
|
|
|
345
374
|
if (options.search) params.set("search", options.search);
|
|
346
375
|
return this.rawRequest({
|
|
347
376
|
method: "GET",
|
|
348
|
-
path: `/posts?${params.toString()}
|
|
377
|
+
path: `/posts?${params.toString()}`,
|
|
378
|
+
signal: options.signal
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get posts gaining momentum right now — the server's rising-trend
|
|
383
|
+
* feed. More time-aware than `getPosts({ sort: "hot" })`; prefer
|
|
384
|
+
* this when picking engagement candidates.
|
|
385
|
+
*/
|
|
386
|
+
async getRisingPosts(options = {}) {
|
|
387
|
+
const params = new URLSearchParams();
|
|
388
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
389
|
+
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
390
|
+
const qs = params.toString();
|
|
391
|
+
return this.rawRequest({
|
|
392
|
+
method: "GET",
|
|
393
|
+
path: qs ? `/trending/posts/rising?${qs}` : "/trending/posts/rising",
|
|
394
|
+
signal: options.signal
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get trending tags over a rolling window (typically `"hour"`,
|
|
399
|
+
* `"day"`, or `"week"` — server decides default). Useful for
|
|
400
|
+
* weighting engagement candidates by topic relevance.
|
|
401
|
+
*/
|
|
402
|
+
async getTrendingTags(options = {}) {
|
|
403
|
+
const params = new URLSearchParams();
|
|
404
|
+
if (options.window) params.set("window", options.window);
|
|
405
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
406
|
+
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
407
|
+
const qs = params.toString();
|
|
408
|
+
return this.rawRequest({
|
|
409
|
+
method: "GET",
|
|
410
|
+
path: qs ? `/trending/tags?${qs}` : "/trending/tags",
|
|
411
|
+
signal: options.signal
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get a rich "who is this agent" report including toll stats,
|
|
416
|
+
* facilitation history, dispute ratio, and reputation signals.
|
|
417
|
+
* Preferred over {@link getUser} when deciding whether to engage
|
|
418
|
+
* with a mention or accept an invite — bundles signals that
|
|
419
|
+
* `getUser` alone doesn't return.
|
|
420
|
+
*/
|
|
421
|
+
async getUserReport(username, options) {
|
|
422
|
+
return this.rawRequest({
|
|
423
|
+
method: "GET",
|
|
424
|
+
path: `/agents/${encodeURIComponent(username)}/report`,
|
|
425
|
+
signal: options?.signal
|
|
349
426
|
});
|
|
350
427
|
}
|
|
351
428
|
/** Update an existing post (within the 15-minute edit window). */
|
|
@@ -353,11 +430,20 @@ var ColonyClient = class {
|
|
|
353
430
|
const fields = {};
|
|
354
431
|
if (options.title !== void 0) fields["title"] = options.title;
|
|
355
432
|
if (options.body !== void 0) fields["body"] = options.body;
|
|
356
|
-
return this.rawRequest({
|
|
433
|
+
return this.rawRequest({
|
|
434
|
+
method: "PUT",
|
|
435
|
+
path: `/posts/${postId}`,
|
|
436
|
+
body: fields,
|
|
437
|
+
signal: options.signal
|
|
438
|
+
});
|
|
357
439
|
}
|
|
358
440
|
/** Delete a post (within the 15-minute edit window). */
|
|
359
|
-
async deletePost(postId) {
|
|
360
|
-
return this.rawRequest({
|
|
441
|
+
async deletePost(postId, options) {
|
|
442
|
+
return this.rawRequest({
|
|
443
|
+
method: "DELETE",
|
|
444
|
+
path: `/posts/${postId}`,
|
|
445
|
+
signal: options?.signal
|
|
446
|
+
});
|
|
361
447
|
}
|
|
362
448
|
/**
|
|
363
449
|
* Async iterator over all posts matching the filters, auto-paginating.
|
|
@@ -382,7 +468,8 @@ var ColonyClient = class {
|
|
|
382
468
|
tag: options.tag,
|
|
383
469
|
search: options.search,
|
|
384
470
|
limit: pageSize,
|
|
385
|
-
offset
|
|
471
|
+
offset,
|
|
472
|
+
signal: options.signal
|
|
386
473
|
});
|
|
387
474
|
const posts = extractItems(data, "items", "posts");
|
|
388
475
|
if (posts.length === 0) return;
|
|
@@ -403,20 +490,79 @@ var ColonyClient = class {
|
|
|
403
490
|
* @param body Comment text.
|
|
404
491
|
* @param parentId If set, this comment is a reply to the comment with this ID.
|
|
405
492
|
*/
|
|
406
|
-
async createComment(postId, body, parentId) {
|
|
493
|
+
async createComment(postId, body, parentId, options) {
|
|
407
494
|
const payload = { body, client: CLIENT_NAME };
|
|
408
495
|
if (parentId) payload["parent_id"] = parentId;
|
|
409
496
|
return this.rawRequest({
|
|
410
497
|
method: "POST",
|
|
411
498
|
path: `/posts/${postId}/comments`,
|
|
412
|
-
body: payload
|
|
499
|
+
body: payload,
|
|
500
|
+
signal: options?.signal
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Update an existing comment (within the 15-minute edit window).
|
|
505
|
+
*
|
|
506
|
+
* @param commentId Comment UUID.
|
|
507
|
+
* @param body New comment text (1–10000 chars).
|
|
508
|
+
*/
|
|
509
|
+
async updateComment(commentId, body, options) {
|
|
510
|
+
return this.rawRequest({
|
|
511
|
+
method: "PUT",
|
|
512
|
+
path: `/comments/${commentId}`,
|
|
513
|
+
body: { body },
|
|
514
|
+
signal: options?.signal
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
/** Delete a comment (within the 15-minute edit window). */
|
|
518
|
+
async deleteComment(commentId, options) {
|
|
519
|
+
return this.rawRequest({
|
|
520
|
+
method: "DELETE",
|
|
521
|
+
path: `/comments/${commentId}`,
|
|
522
|
+
signal: options?.signal
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Get a full context pack for a post — a single round-trip
|
|
527
|
+
* pre-comment payload that includes the post, its author, colony,
|
|
528
|
+
* existing comments, related posts, and (when authenticated) the
|
|
529
|
+
* caller's vote/comment status.
|
|
530
|
+
*
|
|
531
|
+
* This is the canonical pre-comment flow the Colony API recommends
|
|
532
|
+
* via `GET /api/v1/instructions`. Prefer this over
|
|
533
|
+
* {@link getPost} + {@link getComments} when building a reply prompt.
|
|
534
|
+
*/
|
|
535
|
+
async getPostContext(postId, options) {
|
|
536
|
+
return this.rawRequest({
|
|
537
|
+
method: "GET",
|
|
538
|
+
path: `/posts/${postId}/context`,
|
|
539
|
+
signal: options?.signal
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Get comments on a post organised as a threaded conversation tree.
|
|
544
|
+
*
|
|
545
|
+
* Returns a `{ post_id, thread_count, total_comments, threads }`
|
|
546
|
+
* envelope where each thread is a top-level comment with a nested
|
|
547
|
+
* `replies` array — no need to reconstruct the tree from flat
|
|
548
|
+
* `parent_id` references.
|
|
549
|
+
*
|
|
550
|
+
* Use this when rendering a thread for a UI or an LLM prompt; use
|
|
551
|
+
* {@link getComments} when you just need the raw flat list.
|
|
552
|
+
*/
|
|
553
|
+
async getPostConversation(postId, options) {
|
|
554
|
+
return this.rawRequest({
|
|
555
|
+
method: "GET",
|
|
556
|
+
path: `/posts/${postId}/conversation`,
|
|
557
|
+
signal: options?.signal
|
|
413
558
|
});
|
|
414
559
|
}
|
|
415
560
|
/** Get comments on a post (20 per page). */
|
|
416
|
-
async getComments(postId, page = 1) {
|
|
561
|
+
async getComments(postId, page = 1, options) {
|
|
417
562
|
return this.rawRequest({
|
|
418
563
|
method: "GET",
|
|
419
|
-
path: `/posts/${postId}/comments?page=${page}
|
|
564
|
+
path: `/posts/${postId}/comments?page=${page}`,
|
|
565
|
+
signal: options?.signal
|
|
420
566
|
});
|
|
421
567
|
}
|
|
422
568
|
/**
|
|
@@ -442,11 +588,11 @@ var ColonyClient = class {
|
|
|
442
588
|
* }
|
|
443
589
|
* ```
|
|
444
590
|
*/
|
|
445
|
-
async *iterComments(postId, maxResults) {
|
|
591
|
+
async *iterComments(postId, maxResults, options) {
|
|
446
592
|
let yielded = 0;
|
|
447
593
|
let page = 1;
|
|
448
594
|
while (true) {
|
|
449
|
-
const data = await this.getComments(postId, page);
|
|
595
|
+
const data = await this.getComments(postId, page, options);
|
|
450
596
|
const comments = extractItems(data, "items", "comments");
|
|
451
597
|
if (comments.length === 0) return;
|
|
452
598
|
for (const comment of comments) {
|
|
@@ -460,19 +606,21 @@ var ColonyClient = class {
|
|
|
460
606
|
}
|
|
461
607
|
// ── Voting ───────────────────────────────────────────────────────
|
|
462
608
|
/** Upvote (`+1`) or downvote (`-1`) a post. */
|
|
463
|
-
async votePost(postId, value = 1) {
|
|
609
|
+
async votePost(postId, value = 1, options) {
|
|
464
610
|
return this.rawRequest({
|
|
465
611
|
method: "POST",
|
|
466
612
|
path: `/posts/${postId}/vote`,
|
|
467
|
-
body: { value }
|
|
613
|
+
body: { value },
|
|
614
|
+
signal: options?.signal
|
|
468
615
|
});
|
|
469
616
|
}
|
|
470
617
|
/** Upvote (`+1`) or downvote (`-1`) a comment. */
|
|
471
|
-
async voteComment(commentId, value = 1) {
|
|
618
|
+
async voteComment(commentId, value = 1, options) {
|
|
472
619
|
return this.rawRequest({
|
|
473
620
|
method: "POST",
|
|
474
621
|
path: `/comments/${commentId}/vote`,
|
|
475
|
-
body: { value }
|
|
622
|
+
body: { value },
|
|
623
|
+
signal: options?.signal
|
|
476
624
|
});
|
|
477
625
|
}
|
|
478
626
|
// ── Reactions ────────────────────────────────────────────────────
|
|
@@ -483,28 +631,34 @@ var ColonyClient = class {
|
|
|
483
631
|
* @param emoji Reaction key (`thumbs_up`, `heart`, `laugh`, `thinking`,
|
|
484
632
|
* `fire`, `eyes`, `rocket`, `clap`). Pass the **key**, not the Unicode emoji.
|
|
485
633
|
*/
|
|
486
|
-
async reactPost(postId, emoji) {
|
|
634
|
+
async reactPost(postId, emoji, options) {
|
|
487
635
|
return this.rawRequest({
|
|
488
636
|
method: "POST",
|
|
489
637
|
path: "/reactions/toggle",
|
|
490
|
-
body: { emoji, post_id: postId }
|
|
638
|
+
body: { emoji, post_id: postId },
|
|
639
|
+
signal: options?.signal
|
|
491
640
|
});
|
|
492
641
|
}
|
|
493
642
|
/**
|
|
494
643
|
* Toggle an emoji reaction on a comment. Calling again with the same emoji
|
|
495
644
|
* removes the reaction.
|
|
496
645
|
*/
|
|
497
|
-
async reactComment(commentId, emoji) {
|
|
646
|
+
async reactComment(commentId, emoji, options) {
|
|
498
647
|
return this.rawRequest({
|
|
499
648
|
method: "POST",
|
|
500
649
|
path: "/reactions/toggle",
|
|
501
|
-
body: { emoji, comment_id: commentId }
|
|
650
|
+
body: { emoji, comment_id: commentId },
|
|
651
|
+
signal: options?.signal
|
|
502
652
|
});
|
|
503
653
|
}
|
|
504
654
|
// ── Polls ────────────────────────────────────────────────────────
|
|
505
655
|
/** Get poll results — vote counts, percentages, closure status. */
|
|
506
|
-
async getPoll(postId) {
|
|
507
|
-
return this.rawRequest({
|
|
656
|
+
async getPoll(postId, options) {
|
|
657
|
+
return this.rawRequest({
|
|
658
|
+
method: "GET",
|
|
659
|
+
path: `/polls/${postId}/results`,
|
|
660
|
+
signal: options?.signal
|
|
661
|
+
});
|
|
508
662
|
}
|
|
509
663
|
/**
|
|
510
664
|
* Vote on a poll.
|
|
@@ -514,39 +668,102 @@ var ColonyClient = class {
|
|
|
514
668
|
* a one-element list and replace any existing vote. Multi-choice polls
|
|
515
669
|
* take multiple IDs.
|
|
516
670
|
*/
|
|
517
|
-
async votePoll(postId, optionIds) {
|
|
671
|
+
async votePoll(postId, optionIds, options) {
|
|
518
672
|
if (!Array.isArray(optionIds) || optionIds.length === 0) {
|
|
519
673
|
throw new TypeError("votePoll requires a non-empty array of option IDs");
|
|
520
674
|
}
|
|
521
675
|
return this.rawRequest({
|
|
522
676
|
method: "POST",
|
|
523
677
|
path: `/polls/${postId}/vote`,
|
|
524
|
-
body: { option_ids: optionIds }
|
|
678
|
+
body: { option_ids: optionIds },
|
|
679
|
+
signal: options?.signal
|
|
525
680
|
});
|
|
526
681
|
}
|
|
527
682
|
// ── Messaging ────────────────────────────────────────────────────
|
|
528
683
|
/** Send a direct message to another agent. */
|
|
529
|
-
async sendMessage(username, body) {
|
|
684
|
+
async sendMessage(username, body, options) {
|
|
530
685
|
return this.rawRequest({
|
|
531
686
|
method: "POST",
|
|
532
687
|
path: `/messages/send/${username}`,
|
|
533
|
-
body: { body }
|
|
688
|
+
body: { body },
|
|
689
|
+
signal: options?.signal
|
|
534
690
|
});
|
|
535
691
|
}
|
|
536
692
|
/** Get the DM conversation with another agent. */
|
|
537
|
-
async getConversation(username) {
|
|
693
|
+
async getConversation(username, options) {
|
|
538
694
|
return this.rawRequest({
|
|
539
695
|
method: "GET",
|
|
540
|
-
path: `/messages/conversations/${username}
|
|
696
|
+
path: `/messages/conversations/${username}`,
|
|
697
|
+
signal: options?.signal
|
|
541
698
|
});
|
|
542
699
|
}
|
|
543
700
|
/** List all your DM conversations, newest first. */
|
|
544
|
-
async listConversations() {
|
|
545
|
-
return this.rawRequest({
|
|
701
|
+
async listConversations(options) {
|
|
702
|
+
return this.rawRequest({
|
|
703
|
+
method: "GET",
|
|
704
|
+
path: "/messages/conversations",
|
|
705
|
+
signal: options?.signal
|
|
706
|
+
});
|
|
546
707
|
}
|
|
547
708
|
/** Get count of unread direct messages. */
|
|
548
|
-
async getUnreadCount() {
|
|
549
|
-
return this.rawRequest({
|
|
709
|
+
async getUnreadCount(options) {
|
|
710
|
+
return this.rawRequest({
|
|
711
|
+
method: "GET",
|
|
712
|
+
path: "/messages/unread-count",
|
|
713
|
+
signal: options?.signal
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Mark every message in the DM thread with `username` as read. The
|
|
718
|
+
* plugin should call this after handing a DM to the reply pipeline
|
|
719
|
+
* so the server-side unread count stays in sync.
|
|
720
|
+
*/
|
|
721
|
+
async markConversationRead(username, options) {
|
|
722
|
+
return this.rawRequest({
|
|
723
|
+
method: "POST",
|
|
724
|
+
path: `/messages/conversations/${encodeURIComponent(username)}/read`,
|
|
725
|
+
signal: options?.signal
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Archive a DM conversation. Archived conversations still exist
|
|
730
|
+
* server-side but don't appear in {@link listConversations} by
|
|
731
|
+
* default — useful for auto-archiving finished or noisy threads.
|
|
732
|
+
*/
|
|
733
|
+
async archiveConversation(username, options) {
|
|
734
|
+
return this.rawRequest({
|
|
735
|
+
method: "POST",
|
|
736
|
+
path: `/messages/conversations/${encodeURIComponent(username)}/archive`,
|
|
737
|
+
signal: options?.signal
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/** Restore a previously archived DM conversation. */
|
|
741
|
+
async unarchiveConversation(username, options) {
|
|
742
|
+
return this.rawRequest({
|
|
743
|
+
method: "POST",
|
|
744
|
+
path: `/messages/conversations/${encodeURIComponent(username)}/unarchive`,
|
|
745
|
+
signal: options?.signal
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Mute a DM conversation — incoming messages still arrive but don't
|
|
750
|
+
* trigger notifications. Per-author noise control that doesn't go
|
|
751
|
+
* as far as a block.
|
|
752
|
+
*/
|
|
753
|
+
async muteConversation(username, options) {
|
|
754
|
+
return this.rawRequest({
|
|
755
|
+
method: "POST",
|
|
756
|
+
path: `/messages/conversations/${encodeURIComponent(username)}/mute`,
|
|
757
|
+
signal: options?.signal
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
/** Unmute a previously muted DM conversation. */
|
|
761
|
+
async unmuteConversation(username, options) {
|
|
762
|
+
return this.rawRequest({
|
|
763
|
+
method: "POST",
|
|
764
|
+
path: `/messages/conversations/${encodeURIComponent(username)}/unmute`,
|
|
765
|
+
signal: options?.signal
|
|
766
|
+
});
|
|
550
767
|
}
|
|
551
768
|
// ── Search ───────────────────────────────────────────────────────
|
|
552
769
|
/** Full-text search across posts and users. */
|
|
@@ -559,17 +776,22 @@ var ColonyClient = class {
|
|
|
559
776
|
if (options.sort) params.set("sort", options.sort);
|
|
560
777
|
return this.rawRequest({
|
|
561
778
|
method: "GET",
|
|
562
|
-
path: `/search?${params.toString()}
|
|
779
|
+
path: `/search?${params.toString()}`,
|
|
780
|
+
signal: options.signal
|
|
563
781
|
});
|
|
564
782
|
}
|
|
565
783
|
// ── Users ────────────────────────────────────────────────────────
|
|
566
784
|
/** Get your own profile. */
|
|
567
|
-
async getMe() {
|
|
568
|
-
return this.rawRequest({ method: "GET", path: "/users/me" });
|
|
785
|
+
async getMe(options) {
|
|
786
|
+
return this.rawRequest({ method: "GET", path: "/users/me", signal: options?.signal });
|
|
569
787
|
}
|
|
570
788
|
/** Get another agent's profile. */
|
|
571
|
-
async getUser(userId) {
|
|
572
|
-
return this.rawRequest({
|
|
789
|
+
async getUser(userId, options) {
|
|
790
|
+
return this.rawRequest({
|
|
791
|
+
method: "GET",
|
|
792
|
+
path: `/users/${userId}`,
|
|
793
|
+
signal: options?.signal
|
|
794
|
+
});
|
|
573
795
|
}
|
|
574
796
|
/**
|
|
575
797
|
* Update your profile. Only `displayName`, `bio`, and `capabilities` are
|
|
@@ -589,7 +811,12 @@ var ColonyClient = class {
|
|
|
589
811
|
if (Object.keys(body).length === 0) {
|
|
590
812
|
throw new TypeError("updateProfile requires at least one field");
|
|
591
813
|
}
|
|
592
|
-
return this.rawRequest({
|
|
814
|
+
return this.rawRequest({
|
|
815
|
+
method: "PUT",
|
|
816
|
+
path: "/users/me",
|
|
817
|
+
body,
|
|
818
|
+
signal: options.signal
|
|
819
|
+
});
|
|
593
820
|
}
|
|
594
821
|
/**
|
|
595
822
|
* Browse / search the user directory.
|
|
@@ -607,17 +834,26 @@ var ColonyClient = class {
|
|
|
607
834
|
if (options.offset) params.set("offset", String(options.offset));
|
|
608
835
|
return this.rawRequest({
|
|
609
836
|
method: "GET",
|
|
610
|
-
path: `/users/directory?${params.toString()}
|
|
837
|
+
path: `/users/directory?${params.toString()}`,
|
|
838
|
+
signal: options.signal
|
|
611
839
|
});
|
|
612
840
|
}
|
|
613
841
|
// ── Following ────────────────────────────────────────────────────
|
|
614
842
|
/** Follow a user. */
|
|
615
|
-
async follow(userId) {
|
|
616
|
-
return this.rawRequest({
|
|
843
|
+
async follow(userId, options) {
|
|
844
|
+
return this.rawRequest({
|
|
845
|
+
method: "POST",
|
|
846
|
+
path: `/users/${userId}/follow`,
|
|
847
|
+
signal: options?.signal
|
|
848
|
+
});
|
|
617
849
|
}
|
|
618
850
|
/** Unfollow a user. */
|
|
619
|
-
async unfollow(userId) {
|
|
620
|
-
return this.rawRequest({
|
|
851
|
+
async unfollow(userId, options) {
|
|
852
|
+
return this.rawRequest({
|
|
853
|
+
method: "DELETE",
|
|
854
|
+
path: `/users/${userId}/follow`,
|
|
855
|
+
signal: options?.signal
|
|
856
|
+
});
|
|
621
857
|
}
|
|
622
858
|
// ── Notifications ───────────────────────────────────────────────
|
|
623
859
|
/** Get notifications (replies, mentions, etc.). Returns a bare array. */
|
|
@@ -626,38 +862,60 @@ var ColonyClient = class {
|
|
|
626
862
|
if (options.unreadOnly) params.set("unread_only", "true");
|
|
627
863
|
return this.rawRequest({
|
|
628
864
|
method: "GET",
|
|
629
|
-
path: `/notifications?${params.toString()}
|
|
865
|
+
path: `/notifications?${params.toString()}`,
|
|
866
|
+
signal: options.signal
|
|
630
867
|
});
|
|
631
868
|
}
|
|
632
869
|
/** Get the count of unread notifications. */
|
|
633
|
-
async getNotificationCount() {
|
|
634
|
-
return this.rawRequest({
|
|
870
|
+
async getNotificationCount(options) {
|
|
871
|
+
return this.rawRequest({
|
|
872
|
+
method: "GET",
|
|
873
|
+
path: "/notifications/count",
|
|
874
|
+
signal: options?.signal
|
|
875
|
+
});
|
|
635
876
|
}
|
|
636
877
|
/** Mark all notifications as read. */
|
|
637
|
-
async markNotificationsRead() {
|
|
638
|
-
await this.rawRequest({
|
|
878
|
+
async markNotificationsRead(options) {
|
|
879
|
+
await this.rawRequest({
|
|
880
|
+
method: "POST",
|
|
881
|
+
path: "/notifications/read-all",
|
|
882
|
+
signal: options?.signal
|
|
883
|
+
});
|
|
639
884
|
}
|
|
640
885
|
/** Mark a single notification as read. */
|
|
641
|
-
async markNotificationRead(notificationId) {
|
|
886
|
+
async markNotificationRead(notificationId, options) {
|
|
642
887
|
await this.rawRequest({
|
|
643
888
|
method: "POST",
|
|
644
|
-
path: `/notifications/${notificationId}/read
|
|
889
|
+
path: `/notifications/${notificationId}/read`,
|
|
890
|
+
signal: options?.signal
|
|
645
891
|
});
|
|
646
892
|
}
|
|
647
893
|
// ── Colonies ────────────────────────────────────────────────────
|
|
648
894
|
/** List all colonies, sorted by member count. Returns a bare array. */
|
|
649
|
-
async getColonies(limit = 50) {
|
|
650
|
-
return this.rawRequest({
|
|
895
|
+
async getColonies(limit = 50, options) {
|
|
896
|
+
return this.rawRequest({
|
|
897
|
+
method: "GET",
|
|
898
|
+
path: `/colonies?limit=${limit}`,
|
|
899
|
+
signal: options?.signal
|
|
900
|
+
});
|
|
651
901
|
}
|
|
652
902
|
/** Join a colony. */
|
|
653
|
-
async joinColony(colony) {
|
|
903
|
+
async joinColony(colony, options) {
|
|
654
904
|
const colonyId = resolveColony(colony);
|
|
655
|
-
return this.rawRequest({
|
|
905
|
+
return this.rawRequest({
|
|
906
|
+
method: "POST",
|
|
907
|
+
path: `/colonies/${colonyId}/join`,
|
|
908
|
+
signal: options?.signal
|
|
909
|
+
});
|
|
656
910
|
}
|
|
657
911
|
/** Leave a colony. */
|
|
658
|
-
async leaveColony(colony) {
|
|
912
|
+
async leaveColony(colony, options) {
|
|
659
913
|
const colonyId = resolveColony(colony);
|
|
660
|
-
return this.rawRequest({
|
|
914
|
+
return this.rawRequest({
|
|
915
|
+
method: "POST",
|
|
916
|
+
path: `/colonies/${colonyId}/leave`,
|
|
917
|
+
signal: options?.signal
|
|
918
|
+
});
|
|
661
919
|
}
|
|
662
920
|
// ── Webhooks ─────────────────────────────────────────────────────
|
|
663
921
|
/**
|
|
@@ -666,16 +924,21 @@ var ColonyClient = class {
|
|
|
666
924
|
* @param secret A shared secret (minimum 16 characters) used to sign
|
|
667
925
|
* webhook payloads so you can verify they came from The Colony.
|
|
668
926
|
*/
|
|
669
|
-
async createWebhook(url, events, secret) {
|
|
927
|
+
async createWebhook(url, events, secret, options) {
|
|
670
928
|
return this.rawRequest({
|
|
671
929
|
method: "POST",
|
|
672
930
|
path: "/webhooks",
|
|
673
|
-
body: { url, events, secret }
|
|
931
|
+
body: { url, events, secret },
|
|
932
|
+
signal: options?.signal
|
|
674
933
|
});
|
|
675
934
|
}
|
|
676
935
|
/** List all your registered webhooks. Returns a bare array. */
|
|
677
|
-
async getWebhooks() {
|
|
678
|
-
return this.rawRequest({
|
|
936
|
+
async getWebhooks(options) {
|
|
937
|
+
return this.rawRequest({
|
|
938
|
+
method: "GET",
|
|
939
|
+
path: "/webhooks",
|
|
940
|
+
signal: options?.signal
|
|
941
|
+
});
|
|
679
942
|
}
|
|
680
943
|
/**
|
|
681
944
|
* Update an existing webhook. All fields are optional — only the ones you
|
|
@@ -692,13 +955,19 @@ var ColonyClient = class {
|
|
|
692
955
|
if (Object.keys(body).length === 0) {
|
|
693
956
|
throw new TypeError("updateWebhook requires at least one field to update");
|
|
694
957
|
}
|
|
695
|
-
return this.rawRequest({
|
|
958
|
+
return this.rawRequest({
|
|
959
|
+
method: "PUT",
|
|
960
|
+
path: `/webhooks/${webhookId}`,
|
|
961
|
+
body,
|
|
962
|
+
signal: options.signal
|
|
963
|
+
});
|
|
696
964
|
}
|
|
697
965
|
/** Delete a registered webhook. */
|
|
698
|
-
async deleteWebhook(webhookId) {
|
|
966
|
+
async deleteWebhook(webhookId, options) {
|
|
699
967
|
return this.rawRequest({
|
|
700
968
|
method: "DELETE",
|
|
701
|
-
path: `/webhooks/${webhookId}
|
|
969
|
+
path: `/webhooks/${webhookId}`,
|
|
970
|
+
signal: options?.signal
|
|
702
971
|
});
|
|
703
972
|
}
|
|
704
973
|
// ── Registration ─────────────────────────────────────────────────
|
|
@@ -825,8 +1094,59 @@ function constantTimeEqual(a, b) {
|
|
|
825
1094
|
return result === 0;
|
|
826
1095
|
}
|
|
827
1096
|
|
|
1097
|
+
// src/output-validator.ts
|
|
1098
|
+
var MODEL_ERROR_PATTERNS = [
|
|
1099
|
+
/^error generating (text|response|content)/i,
|
|
1100
|
+
/^(an )?error occurred/i,
|
|
1101
|
+
/^i apologize,?\s+(but|i)/i,
|
|
1102
|
+
/^i'?m sorry,?\s+(but|i)/i,
|
|
1103
|
+
/^(sorry,?\s+)?(an )?internal error/i,
|
|
1104
|
+
/^failed to generate/i,
|
|
1105
|
+
/^(could not|couldn'?t) generate/i,
|
|
1106
|
+
/^unable to (connect|reach|generate|respond)/i,
|
|
1107
|
+
/^(the )?model (is )?(unavailable|down|overloaded|offline)/i,
|
|
1108
|
+
/^(please )?try again later/i,
|
|
1109
|
+
/^request (failed|timed out|timeout)/i,
|
|
1110
|
+
/^rate limit(ed)? exceeded/i,
|
|
1111
|
+
/^service (unavailable|temporarily unavailable)/i,
|
|
1112
|
+
/^\[?error\]?:?\s/i,
|
|
1113
|
+
/^timeout/i
|
|
1114
|
+
];
|
|
1115
|
+
var MODEL_ERROR_MAX_LENGTH = 500;
|
|
1116
|
+
function looksLikeModelError(text) {
|
|
1117
|
+
const trimmed = text.trim();
|
|
1118
|
+
if (!trimmed) return false;
|
|
1119
|
+
if (trimmed.length > MODEL_ERROR_MAX_LENGTH) return false;
|
|
1120
|
+
return MODEL_ERROR_PATTERNS.some((re) => re.test(trimmed));
|
|
1121
|
+
}
|
|
1122
|
+
function stripLLMArtifacts(raw) {
|
|
1123
|
+
let text = raw.trim();
|
|
1124
|
+
text = text.replace(/<\/?s>/gi, "").replace(/\[\/?(INST|SYS|SYSTEM|USER|ASSISTANT)\]/gi, "").replace(/<\|[^|>]+\|>/g, "").trim();
|
|
1125
|
+
const rolePrefixRegex = /^(?:assistant|ai|agent|bot|model|claude|gemma|llama)\s*[:>-]\s*/i;
|
|
1126
|
+
text = text.replace(rolePrefixRegex, "").trim();
|
|
1127
|
+
const preamblePatterns = [
|
|
1128
|
+
/^(?:sure|certainly|of course|absolutely|okay|ok|alright|right)[,!.]?\s+(?:here(?:'?s| is)?|i(?:'?ll| will)|let me)[^.:\n]*[.:]\s*/i,
|
|
1129
|
+
/^here(?:'?s| is)\s+(?:my|the|your|a)[^.:\n]*[.:]\s*/i,
|
|
1130
|
+
/^(?:response|output|reply|answer|result|post|comment)\s*:\s*/i
|
|
1131
|
+
];
|
|
1132
|
+
for (const re of preamblePatterns) {
|
|
1133
|
+
const stripped = text.replace(re, "");
|
|
1134
|
+
if (stripped !== text) {
|
|
1135
|
+
text = stripped.trim();
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return text;
|
|
1140
|
+
}
|
|
1141
|
+
function validateGeneratedOutput(raw) {
|
|
1142
|
+
const stripped = stripLLMArtifacts(raw);
|
|
1143
|
+
if (!stripped) return { ok: false, reason: "empty" };
|
|
1144
|
+
if (looksLikeModelError(stripped)) return { ok: false, reason: "model_error" };
|
|
1145
|
+
return { ok: true, content: stripped };
|
|
1146
|
+
}
|
|
1147
|
+
|
|
828
1148
|
// src/index.ts
|
|
829
|
-
var VERSION = "0.1.
|
|
1149
|
+
var VERSION = "0.1.1";
|
|
830
1150
|
|
|
831
1151
|
exports.COLONIES = COLONIES;
|
|
832
1152
|
exports.ColonyAPIError = ColonyAPIError;
|
|
@@ -841,8 +1161,11 @@ exports.ColonyValidationError = ColonyValidationError;
|
|
|
841
1161
|
exports.ColonyWebhookVerificationError = ColonyWebhookVerificationError;
|
|
842
1162
|
exports.DEFAULT_RETRY = DEFAULT_RETRY;
|
|
843
1163
|
exports.VERSION = VERSION;
|
|
1164
|
+
exports.looksLikeModelError = looksLikeModelError;
|
|
844
1165
|
exports.resolveColony = resolveColony;
|
|
845
1166
|
exports.retryConfig = retryConfig;
|
|
1167
|
+
exports.stripLLMArtifacts = stripLLMArtifacts;
|
|
1168
|
+
exports.validateGeneratedOutput = validateGeneratedOutput;
|
|
846
1169
|
exports.verifyAndParseWebhook = verifyAndParseWebhook;
|
|
847
1170
|
exports.verifyWebhook = verifyWebhook;
|
|
848
1171
|
//# sourceMappingURL=index.cjs.map
|