@recursiv/sdk 0.7.53 → 0.7.55
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 +41 -3
- package/dist/client.d.ts +1 -0
- package/dist/client.js +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +17 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/resources/auth.d.ts +63 -13
- package/dist/resources/auth.js +106 -40
- package/dist/resources/commands.d.ts +14 -1
- package/dist/resources/commands.js +7 -1
- package/dist/resources/curator.d.ts +1 -1
- package/dist/resources/discovery.d.ts +78 -1
- package/dist/resources/discovery.js +55 -6
- package/dist/resources/e2ee.d.ts +52 -6
- package/dist/resources/e2ee.js +33 -0
- package/dist/resources/human-input.d.ts +4 -1
- package/dist/resources/human-input.js +7 -2
- package/dist/resources/moderation.d.ts +5 -1
- package/dist/resources/moderation.js +6 -2
- package/dist/resources/notifications.d.ts +19 -0
- package/dist/resources/profiles.d.ts +7 -3
- package/dist/resources/profiles.js +5 -1
- package/dist/resources/protocols.d.ts +34 -2
- package/dist/resources/protocols.js +8 -2
- package/dist/resources/reports.d.ts +4 -0
- package/dist/resources/reports.js +5 -1
- package/dist/types.d.ts +54 -0
- package/llm.md +13 -7
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { DiscoveryLayout, Post } from '../types.js';
|
|
2
|
+
import type { DiscoveryLayout, FalloverStep, Post } from '../types.js';
|
|
3
3
|
import type { CuratorResource } from './curator.js';
|
|
4
4
|
import type { DiscoveryLearning } from './discovery-learning.js';
|
|
5
5
|
import type { PostsResource } from './posts.js';
|
|
@@ -66,6 +66,61 @@ export interface EntityImpressionsInput {
|
|
|
66
66
|
/** 'shown' when a suggestion was on screen; 'engaged' on a tap, follow or join. Three showings without a tap rest a suggestion for fourteen days; engaging clears it. */
|
|
67
67
|
event: 'shown' | 'engaged';
|
|
68
68
|
}
|
|
69
|
+
/** One section of a category surface: bounded, read under its own deadline; empty with a `reason` when it timed out, failed, is off, or the recommender said why. */
|
|
70
|
+
export interface CategorySurfaceSection<T> {
|
|
71
|
+
items: T[];
|
|
72
|
+
ms: number;
|
|
73
|
+
reason?: string;
|
|
74
|
+
}
|
|
75
|
+
/** A radar cluster themed into the category, with up to six members (headline or excerpt and link only). */
|
|
76
|
+
export type CategoryStory = Pick<SocialRadar['clusters'][number], 'id' | 'label' | 'kind' | 'theme' | 'sources' | 'corroboration' | 'laneMix' | 'velocity' | 'media' | 'freshness' | 'score' | 'members'>;
|
|
77
|
+
export interface CategoryClip {
|
|
78
|
+
id: string;
|
|
79
|
+
title: string | null;
|
|
80
|
+
excerpt: string;
|
|
81
|
+
created_at: string;
|
|
82
|
+
author: {
|
|
83
|
+
id: string;
|
|
84
|
+
username: string | null;
|
|
85
|
+
name: string;
|
|
86
|
+
image: string | null;
|
|
87
|
+
};
|
|
88
|
+
media: Array<{
|
|
89
|
+
id: string;
|
|
90
|
+
url: string;
|
|
91
|
+
type: string;
|
|
92
|
+
thumbnail_url: string | null;
|
|
93
|
+
unplayable?: boolean;
|
|
94
|
+
}>;
|
|
95
|
+
engagement: {
|
|
96
|
+
score: number;
|
|
97
|
+
like_count: number;
|
|
98
|
+
reply_count: number;
|
|
99
|
+
repost_count: number;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export interface CategorySurface {
|
|
103
|
+
category: string;
|
|
104
|
+
/** When the oldest cached section served (stories, network, clips) was built. */
|
|
105
|
+
as_of: string;
|
|
106
|
+
stories: CategorySurfaceSection<CategoryStory>;
|
|
107
|
+
network: CategorySurfaceSection<SocialPost> & {
|
|
108
|
+
by_protocol: Record<string, number>;
|
|
109
|
+
};
|
|
110
|
+
clips: CategorySurfaceSection<CategoryClip>;
|
|
111
|
+
people: CategorySurfaceSection<EntityRecommendation>;
|
|
112
|
+
groups: CategorySurfaceSection<EntityRecommendation>;
|
|
113
|
+
}
|
|
114
|
+
export interface CategorySurfaceResponse {
|
|
115
|
+
/** Null when the key has no app (meta.reason `no_app`). */
|
|
116
|
+
data: CategorySurface | null;
|
|
117
|
+
meta: {
|
|
118
|
+
category: string;
|
|
119
|
+
as_of?: string;
|
|
120
|
+
reason?: 'no_app';
|
|
121
|
+
personalized: boolean;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
69
124
|
export interface DiscoveryGroupCandidate {
|
|
70
125
|
id: string;
|
|
71
126
|
name: string;
|
|
@@ -219,6 +274,12 @@ export interface DiscoveryFeedPage {
|
|
|
219
274
|
hasMore: boolean;
|
|
220
275
|
failedSources: string[];
|
|
221
276
|
partialCategories: boolean;
|
|
277
|
+
/**
|
|
278
|
+
* The Minds feed's fall-through, once its category feed has ended a stage (the server's
|
|
279
|
+
* `meta.fallover`): where to read next when this session has no more. Absent until then, and
|
|
280
|
+
* on servers that predate it.
|
|
281
|
+
*/
|
|
282
|
+
fallover?: FalloverStep[];
|
|
222
283
|
}
|
|
223
284
|
/** A gallery strip holds up to twelve image posts gathered from the whole page. */
|
|
224
285
|
export declare const GALLERY_MAX_ITEMS = 12;
|
|
@@ -292,6 +353,22 @@ export declare class DiscoveryResource {
|
|
|
292
353
|
reason?: 'disabled' | 'other_app' | 'unavailable' | 'no_app';
|
|
293
354
|
};
|
|
294
355
|
}>;
|
|
356
|
+
/**
|
|
357
|
+
* A category's surface in one call: the modules All has, scoped to the category. `stories` (radar
|
|
358
|
+
* clusters themed into it), `network` (open-network posts judged into it, newest first) and `clips`
|
|
359
|
+
* (public video posts among its hot window) are shared per app and category (refreshed after 30 s, kept up to 300 s, so a copy
|
|
360
|
+
* may be that old) and filtered for the viewer on every call: the cached safety verdicts and the promotion term list again, the
|
|
361
|
+
* viewer's blocks and mutes, and for stories and network the member's own hidden sources. `people` and `groups` are the
|
|
362
|
+
* viewer's suggestions in it. A section that timed out, failed or is off is empty
|
|
363
|
+
* with a `reason` and never delays the others. The posts feed itself is `posts.list({ category })`,
|
|
364
|
+
* whose pages carry `meta.next` and `meta.fallover`.
|
|
365
|
+
*/
|
|
366
|
+
category(params: {
|
|
367
|
+
category: string;
|
|
368
|
+
session?: string;
|
|
369
|
+
}, options?: {
|
|
370
|
+
retry?: boolean;
|
|
371
|
+
}): Promise<CategorySurfaceResponse>;
|
|
295
372
|
/**
|
|
296
373
|
* A For you Discover feed: the member's For You ranking (asked for Discover's surface, with layouts and a
|
|
297
374
|
* snapshot cursor) in a session's Minds slot. With a category, the session also reads open-network posts
|
|
@@ -22,6 +22,16 @@ const isGone = (error) => typeof error === 'object' && error !== null && error.c
|
|
|
22
22
|
const TOPIC_WALK = 5;
|
|
23
23
|
/** The walk starts no topic read after this long: well inside the page deadline, so it commits its progress rather than timing the network out. */
|
|
24
24
|
const TOPIC_WALK_MS = 8000;
|
|
25
|
+
/**
|
|
26
|
+
* A topic page the server answered from its open-network source index (`sourceIndex`): in a category, a
|
|
27
|
+
* topic that is gone or holds nothing in it is served the network's indexed posts in the category, which
|
|
28
|
+
* are the same for every such topic. While the provider has more of the topic the page carries the
|
|
29
|
+
* provider's own cursor, which the walk keeps like any other; a topic that is gone or ended carries an
|
|
30
|
+
* `idx:tf:` cursor, the one index stream of the network. So a walk starts that stream at most once per
|
|
31
|
+
* session (a second start would re-read what it already showed), and an index page that shows nothing
|
|
32
|
+
* new counts as an empty topic.
|
|
33
|
+
*/
|
|
34
|
+
const INDEX_CURSOR = 'idx:tf:';
|
|
25
35
|
const RECOMMENDATION_KINDS = ['groups', 'people', 'tags'];
|
|
26
36
|
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0 && value.length <= 512;
|
|
27
37
|
function recommendationItem(kind, candidate) {
|
|
@@ -188,6 +198,9 @@ export class DiscoveryFeedSession {
|
|
|
188
198
|
if (stream.cursor)
|
|
189
199
|
stream.used = [...stream.used, stream.cursor].slice(-32);
|
|
190
200
|
stream.cursor = next;
|
|
201
|
+
// A category feed that ended a stage says where to go next; kept on the stream and reported on every page from here.
|
|
202
|
+
if (Array.isArray(response.meta.fallover))
|
|
203
|
+
stream.fallover = response.meta.fallover;
|
|
191
204
|
return { items: response.data.map(post => ({ key: key('minds', post.id), kind: 'minds', post })),
|
|
192
205
|
layout: response.meta.discovery ?? ordinary, partial: response.meta.category_filter?.status === 'partial' };
|
|
193
206
|
}
|
|
@@ -237,9 +250,20 @@ export class DiscoveryFeedSession {
|
|
|
237
250
|
const topic = topics[0];
|
|
238
251
|
if (!topic)
|
|
239
252
|
break;
|
|
253
|
+
// A failed read of the network's index stream (the server's index busy, or its safety cache down) ends that
|
|
254
|
+
// continuation, not the network: the stream may start again from a later topic's page. A provider topic
|
|
255
|
+
// read that fails any other way than gone is still the network's failure.
|
|
256
|
+
const indexContinuation = !!topic.cursor?.startsWith(INDEX_CURSOR);
|
|
240
257
|
const response = await this.protocols.getPublicTrendFeed({ ...params, protocol: stream.protocol, topic: topic.topic, cursor: topic.cursor }, NO_RETRY)
|
|
241
|
-
.catch((error) => {
|
|
242
|
-
|
|
258
|
+
.catch((error) => {
|
|
259
|
+
if (isGone(error))
|
|
260
|
+
return null;
|
|
261
|
+
if (indexContinuation) {
|
|
262
|
+
stream.indexStarted = false;
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
throw error;
|
|
266
|
+
});
|
|
243
267
|
topics.shift();
|
|
244
268
|
if (!response)
|
|
245
269
|
continue;
|
|
@@ -248,12 +272,20 @@ export class DiscoveryFeedSession {
|
|
|
248
272
|
if (data.trend.protocol !== stream.protocol || data.trend.nativeId !== topic.topic)
|
|
249
273
|
throw new Error('Invalid discovery topic');
|
|
250
274
|
const next = data.cursor;
|
|
251
|
-
|
|
275
|
+
// A provider cursor is always kept. An index cursor from a provider page starts the network's one index
|
|
276
|
+
// stream: queued the first time only, since every later start would re-read the same stream from its top.
|
|
277
|
+
const indexed = !!data.sourceIndex;
|
|
278
|
+
const startsIndex = !!next?.startsWith(INDEX_CURSOR) && !topic.cursor?.startsWith(INDEX_CURSOR);
|
|
279
|
+
if (next && next !== topic.cursor && !topic.used.includes(next) && !(startsIndex && stream.indexStarted)) {
|
|
252
280
|
topics.push({ topic: topic.topic, cursor: next, used: [...topic.used, ...(topic.cursor ? [topic.cursor] : [])].slice(-32) });
|
|
281
|
+
if (startsIndex)
|
|
282
|
+
stream.indexStarted = true;
|
|
283
|
+
}
|
|
253
284
|
partial ||= data.categoryFilter?.status === 'partial';
|
|
254
|
-
|
|
285
|
+
const items = indexed ? data.items.filter(post => !this.seen.has(key(post.protocol, post.nativeId))) : data.items;
|
|
286
|
+
if (items.length) {
|
|
255
287
|
stream.done = !topics.length;
|
|
256
|
-
return { items:
|
|
288
|
+
return { items: items.map(post => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout: data.discovery ?? ordinary, partial };
|
|
257
289
|
}
|
|
258
290
|
}
|
|
259
291
|
stream.done = !topics.length;
|
|
@@ -369,8 +401,10 @@ export class DiscoveryFeedSession {
|
|
|
369
401
|
modules.splice(Math.min(2, modules.length), 0, recommendation);
|
|
370
402
|
if (cluster)
|
|
371
403
|
modules.splice(page === 0 ? 0 : Math.min(2, modules.length), 0, this.radarModule(page, cluster, clusterItems));
|
|
404
|
+
const fallover = this.streams.find(stream => stream.protocol === 'minds')?.fallover;
|
|
372
405
|
return { modules, hasMore: this.streams.some(stream => !stream.done && !stream.failed),
|
|
373
|
-
failedSources: this.streams.filter(stream => stream.failed).map(stream => stream.protocol), partialCategories: reads.some(read => read.partial)
|
|
406
|
+
failedSources: this.streams.filter(stream => stream.failed).map(stream => stream.protocol), partialCategories: reads.some(read => read.partial),
|
|
407
|
+
...(fallover ? { fallover } : {}) };
|
|
374
408
|
}
|
|
375
409
|
/** Page 0: the top breaking cluster, else the top cluster. Every second later page: the next unused story or viral cluster. */
|
|
376
410
|
async radarCluster(page) {
|
|
@@ -534,6 +568,21 @@ export class DiscoveryResource {
|
|
|
534
568
|
throw new Error('recordImpressions needs the HTTP client');
|
|
535
569
|
return this.http.post('/discover/impressions', input);
|
|
536
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* A category's surface in one call: the modules All has, scoped to the category. `stories` (radar
|
|
573
|
+
* clusters themed into it), `network` (open-network posts judged into it, newest first) and `clips`
|
|
574
|
+
* (public video posts among its hot window) are shared per app and category (refreshed after 30 s, kept up to 300 s, so a copy
|
|
575
|
+
* may be that old) and filtered for the viewer on every call: the cached safety verdicts and the promotion term list again, the
|
|
576
|
+
* viewer's blocks and mutes, and for stories and network the member's own hidden sources. `people` and `groups` are the
|
|
577
|
+
* viewer's suggestions in it. A section that timed out, failed or is off is empty
|
|
578
|
+
* with a `reason` and never delays the others. The posts feed itself is `posts.list({ category })`,
|
|
579
|
+
* whose pages carry `meta.next` and `meta.fallover`.
|
|
580
|
+
*/
|
|
581
|
+
category(params, options) {
|
|
582
|
+
if (!this.http)
|
|
583
|
+
throw new Error('category needs the HTTP client');
|
|
584
|
+
return this.http.get('/discover/category', params, options);
|
|
585
|
+
}
|
|
537
586
|
/**
|
|
538
587
|
* A For you Discover feed: the member's For You ranking (asked for Discover's surface, with layouts and a
|
|
539
588
|
* snapshot cursor) in a session's Minds slot. With a category, the session also reads open-network posts
|
package/dist/resources/e2ee.d.ts
CHANGED
|
@@ -37,12 +37,6 @@ export interface E2eeKeyPackageStatus {
|
|
|
37
37
|
/** Of `available`, how many expire within 7 days. */
|
|
38
38
|
expiringSoon: number;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
* E2EE v2 ciphertext transport. The server relays envelopes and public
|
|
42
|
-
* credentials only; encryption happens in the shared core on each device.
|
|
43
|
-
* Requires the `e2eeV2` network feature flag and a key acting as the account
|
|
44
|
-
* owner (agent-scoped keys are refused). The realm is the network id.
|
|
45
|
-
*/
|
|
46
40
|
/** A reserved encrypted attachment object: PUT exactly `length` bytes of ciphertext to `uploadUrl`, then complete it. */
|
|
47
41
|
export interface E2eeObjectUpload {
|
|
48
42
|
reference: string;
|
|
@@ -56,9 +50,45 @@ export interface E2eeObjectDownload {
|
|
|
56
50
|
downloadUrl: string;
|
|
57
51
|
length: number;
|
|
58
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Why this account cannot use the E2EE relay on its network:
|
|
55
|
+
* - `network_off`: the network has not turned E2EE v2 on.
|
|
56
|
+
* - `not_allowlisted`: it is on, but only for pilot accounts, and this is not one.
|
|
57
|
+
*/
|
|
58
|
+
export type E2eeStatusReason = 'network_off' | 'not_allowlisted';
|
|
59
|
+
/** Whether the calling account is admitted to the E2EE relay on its network. */
|
|
60
|
+
export interface E2eeStatus {
|
|
61
|
+
/**
|
|
62
|
+
* True: this account may enroll devices, publish key packages, send, and
|
|
63
|
+
* start encryption in an eligible direct conversation. False: the relay is
|
|
64
|
+
* frozen for it. Conversations that already carry encryption stay readable
|
|
65
|
+
* (fetch, acknowledge, object downloads), its own devices can be listed and
|
|
66
|
+
* revoked, and nothing new can be sent or enrolled. It never falls back to
|
|
67
|
+
* plaintext.
|
|
68
|
+
*/
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
/** Present only when `enabled` is false. Treat any unrecognised reason as off. */
|
|
71
|
+
reason?: E2eeStatusReason;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* E2EE v2 ciphertext transport. The server relays envelopes and public
|
|
75
|
+
* credentials only; encryption happens in the shared core on each device.
|
|
76
|
+
* Requires the `e2eeV2` network feature flag and a key acting as the account
|
|
77
|
+
* owner (agent-scoped keys are refused). The realm is the network id.
|
|
78
|
+
*/
|
|
59
79
|
export declare class E2eeResource {
|
|
60
80
|
private client;
|
|
61
81
|
constructor(client: HttpClient);
|
|
82
|
+
/**
|
|
83
|
+
* Whether this account is admitted to encrypted chat on its network, without
|
|
84
|
+
* attempting a relay call. `{ enabled: true }`, or `{ enabled: false, reason }`
|
|
85
|
+
* with `reason` `'network_off'` or `'not_allowlisted'`. Only the caller's own
|
|
86
|
+
* account is described; the network comes from the key or session. Enrolling
|
|
87
|
+
* a device still needs an accepted two-person DM (or an already-encrypted
|
|
88
|
+
* conversation) in this key's scope. Agent-scoped keys get
|
|
89
|
+
* `agent_key_not_allowed`.
|
|
90
|
+
*/
|
|
91
|
+
status(): Promise<SingleResponse<E2eeStatus>>;
|
|
62
92
|
/** Register this device's public identity and its signed approval chain. */
|
|
63
93
|
registerDevice(input: {
|
|
64
94
|
device_name: string;
|
|
@@ -96,12 +126,28 @@ export declare class E2eeResource {
|
|
|
96
126
|
bytes: string;
|
|
97
127
|
device: E2eeDevice;
|
|
98
128
|
}>>;
|
|
129
|
+
/**
|
|
130
|
+
* Append one MLS envelope to an encrypted conversation.
|
|
131
|
+
*
|
|
132
|
+
* `silent: true` marks a message that changes an earlier one (a reaction,
|
|
133
|
+
* an edit, a delete for everyone): the relay sends no push (so no unread
|
|
134
|
+
* badge either) and does not move the conversation up the chat list. The
|
|
135
|
+
* `e2ee:event` doorbell still rings exactly as for any message: on the
|
|
136
|
+
* sockets of every member, the sender's own account included, except
|
|
137
|
+
* across a block. So every listener hears a quiet event, not only an open
|
|
138
|
+
* thread: a chat list that pulls on the doorbell pulls it too, and should
|
|
139
|
+
* leave the row where it is once it has decrypted a reaction, an edit or a
|
|
140
|
+
* delete. A commit ignores the flag. The relay learns only that the event
|
|
141
|
+
* is quiet; the flag is not stored. A relay that predates it drops the
|
|
142
|
+
* key, so the event is simply not quiet there.
|
|
143
|
+
*/
|
|
99
144
|
appendEvent(conversationId: string, input: {
|
|
100
145
|
device_id: string;
|
|
101
146
|
envelope: E2eeEnvelope;
|
|
102
147
|
welcome?: string;
|
|
103
148
|
welcome_for_device_id?: string;
|
|
104
149
|
expected_commit_seq?: number;
|
|
150
|
+
silent?: boolean;
|
|
105
151
|
}): Promise<SingleResponse<{
|
|
106
152
|
seq: number;
|
|
107
153
|
duplicate: boolean;
|
package/dist/resources/e2ee.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2EE v2 ciphertext transport. The server relays envelopes and public
|
|
3
|
+
* credentials only; encryption happens in the shared core on each device.
|
|
4
|
+
* Requires the `e2eeV2` network feature flag and a key acting as the account
|
|
5
|
+
* owner (agent-scoped keys are refused). The realm is the network id.
|
|
6
|
+
*/
|
|
1
7
|
export class E2eeResource {
|
|
2
8
|
client;
|
|
3
9
|
constructor(client) {
|
|
4
10
|
this.client = client;
|
|
5
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Whether this account is admitted to encrypted chat on its network, without
|
|
14
|
+
* attempting a relay call. `{ enabled: true }`, or `{ enabled: false, reason }`
|
|
15
|
+
* with `reason` `'network_off'` or `'not_allowlisted'`. Only the caller's own
|
|
16
|
+
* account is described; the network comes from the key or session. Enrolling
|
|
17
|
+
* a device still needs an accepted two-person DM (or an already-encrypted
|
|
18
|
+
* conversation) in this key's scope. Agent-scoped keys get
|
|
19
|
+
* `agent_key_not_allowed`.
|
|
20
|
+
*/
|
|
21
|
+
status() {
|
|
22
|
+
return this.client.get('/e2ee/status');
|
|
23
|
+
}
|
|
6
24
|
/** Register this device's public identity and its signed approval chain. */
|
|
7
25
|
registerDevice(input) {
|
|
8
26
|
return this.client.post('/e2ee/devices', input);
|
|
@@ -28,6 +46,21 @@ export class E2eeResource {
|
|
|
28
46
|
claimKeyPackage(input) {
|
|
29
47
|
return this.client.post('/e2ee/key-packages/claim', input);
|
|
30
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Append one MLS envelope to an encrypted conversation.
|
|
51
|
+
*
|
|
52
|
+
* `silent: true` marks a message that changes an earlier one (a reaction,
|
|
53
|
+
* an edit, a delete for everyone): the relay sends no push (so no unread
|
|
54
|
+
* badge either) and does not move the conversation up the chat list. The
|
|
55
|
+
* `e2ee:event` doorbell still rings exactly as for any message: on the
|
|
56
|
+
* sockets of every member, the sender's own account included, except
|
|
57
|
+
* across a block. So every listener hears a quiet event, not only an open
|
|
58
|
+
* thread: a chat list that pulls on the doorbell pulls it too, and should
|
|
59
|
+
* leave the row where it is once it has decrypted a reaction, an edit or a
|
|
60
|
+
* delete. A commit ignores the flag. The relay learns only that the event
|
|
61
|
+
* is quiet; the flag is not stored. A relay that predates it drops the
|
|
62
|
+
* key, so the event is simply not quiet there.
|
|
63
|
+
*/
|
|
31
64
|
appendEvent(conversationId, input) {
|
|
32
65
|
// Retries are safe: the server is idempotent on (conversation, device, event id).
|
|
33
66
|
return this.client.post(`/e2ee/conversations/${encodeURIComponent(conversationId)}/events`, input, { retry: true });
|
|
@@ -15,6 +15,7 @@ export interface RequestHumanInputInput {
|
|
|
15
15
|
export interface HumanInputRequest {
|
|
16
16
|
conversation_id: string;
|
|
17
17
|
message_id: string;
|
|
18
|
+
human_ask_id: string | null;
|
|
18
19
|
/** Where to read the answer. MCP is stateless, so the agent polls. */
|
|
19
20
|
poll: string;
|
|
20
21
|
poll_after_seconds: number;
|
|
@@ -45,7 +46,9 @@ export declare class HumanInputResource {
|
|
|
45
46
|
private client;
|
|
46
47
|
constructor(client: HttpClient);
|
|
47
48
|
/** Raise a blocking question and notify the human wherever they are. */
|
|
48
|
-
request(input: RequestHumanInputInput
|
|
49
|
+
request(input: RequestHumanInputInput, options?: {
|
|
50
|
+
requestId?: string;
|
|
51
|
+
}): Promise<SingleResponse<HumanInputRequest>>;
|
|
49
52
|
/** Has the human answered yet? */
|
|
50
53
|
answers(conversationId: string): Promise<SingleResponse<HumanInputAnswers>>;
|
|
51
54
|
}
|
|
@@ -17,8 +17,13 @@ export class HumanInputResource {
|
|
|
17
17
|
this.client = client;
|
|
18
18
|
}
|
|
19
19
|
/** Raise a blocking question and notify the human wherever they are. */
|
|
20
|
-
request(input) {
|
|
21
|
-
|
|
20
|
+
request(input, options) {
|
|
21
|
+
// Never automatically replay: chat delivery may have committed before a
|
|
22
|
+
// response was lost, even when a keyed workspace ask was recorded once.
|
|
23
|
+
// Callers can reuse a requestId with the exact same input after an
|
|
24
|
+
// uncertain response. The server claims it against the durable
|
|
25
|
+
// organization-owned ask row; changed fields need a new requestId.
|
|
26
|
+
return this.client.post('/human-input', input, { retry: false, idempotencyKey: options?.requestId });
|
|
22
27
|
}
|
|
23
28
|
/** Has the human answered yet? */
|
|
24
29
|
answers(conversationId) {
|
|
@@ -81,7 +81,11 @@ export declare class ModerationResource {
|
|
|
81
81
|
* reachable through a valid key while the account is suspended or banned.
|
|
82
82
|
*/
|
|
83
83
|
mine(): Promise<SingleResponse<OwnModerationLog>>;
|
|
84
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Appeal a decision affecting the caller or content they own. Sent once,
|
|
86
|
+
* never retried automatically: a 429 past the member's appeal budget
|
|
87
|
+
* (`RateLimitError`, `retryAfter`) is for the member to see.
|
|
88
|
+
*/
|
|
85
89
|
appeal(actionId: string, statement: string): Promise<SingleResponse<{
|
|
86
90
|
success: true;
|
|
87
91
|
id: string;
|
|
@@ -22,9 +22,13 @@ export class ModerationResource {
|
|
|
22
22
|
mine() {
|
|
23
23
|
return this.client.get('/appeals/moderation-actions');
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Appeal a decision affecting the caller or content they own. Sent once,
|
|
27
|
+
* never retried automatically: a 429 past the member's appeal budget
|
|
28
|
+
* (`RateLimitError`, `retryAfter`) is for the member to see.
|
|
29
|
+
*/
|
|
26
30
|
appeal(actionId, statement) {
|
|
27
|
-
return this.client.post(`/appeals/${encodeURIComponent(actionId)}`, { statement });
|
|
31
|
+
return this.client.post(`/appeals/${encodeURIComponent(actionId)}`, { statement }, { retry: false });
|
|
28
32
|
}
|
|
29
33
|
/** Admin review: uphold or overturn an appeal. */
|
|
30
34
|
resolveAppeal(appealId, input) {
|
|
@@ -19,6 +19,11 @@ export interface NotificationActor {
|
|
|
19
19
|
* state so inline Upvote and Repost can start in the right position. `null`
|
|
20
20
|
* when the post is gone or no longer visible to the viewer.
|
|
21
21
|
*/
|
|
22
|
+
/** A post's tag as a notification's post preview carries it. */
|
|
23
|
+
export interface NotificationPostTag {
|
|
24
|
+
name: string;
|
|
25
|
+
slug: string;
|
|
26
|
+
}
|
|
22
27
|
export interface NotificationPostPreview {
|
|
23
28
|
id: string;
|
|
24
29
|
/**
|
|
@@ -35,6 +40,20 @@ export interface NotificationPostPreview {
|
|
|
35
40
|
excerpt_truncated?: boolean;
|
|
36
41
|
media_url: string | null;
|
|
37
42
|
media_type: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* The post's own NSFW flag. With `tags`, it says whether the post is
|
|
45
|
+
* sensitive: a client that masks a sensitive post's card masks its excerpt
|
|
46
|
+
* and picture here too, and holds the picture back while this is absent
|
|
47
|
+
* (servers older than this field).
|
|
48
|
+
*/
|
|
49
|
+
is_nsfw?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The post's tags, each with its name as written and the slug the tag API
|
|
52
|
+
* derived from it ("nsfw", "nude", "porn" and the like mark it sensitive).
|
|
53
|
+
* Match both: a tag created as "NSFW!" is slugged "nsfw". Absent from
|
|
54
|
+
* servers older than this field.
|
|
55
|
+
*/
|
|
56
|
+
tags?: NotificationPostTag[];
|
|
38
57
|
reactions_count?: number;
|
|
39
58
|
score?: number;
|
|
40
59
|
upvote_count?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { PaginatedResponse, SingleResponse, SuccessResponse, Profile, ProfileDetail, ProfileMe, LeaderboardEntry, UpdateProfileInput, SearchProfilesParams, ProfileSearchResult, SearchMentionsParams, MentionCandidate, FollowListParams, FollowListProfile, BlockedProfile, MutedProfile, ModerationListParams,
|
|
2
|
+
import type { PaginatedResponse, SingleResponse, SuccessResponse, Profile, ProfileDetail, ProfileMe, LeaderboardEntry, UpdateProfileInput, SearchProfilesParams, ProfileSearchResult, SearchMentionsParams, MentionCandidate, FollowListParams, FollowListProfile, BlockedProfile, MutedProfile, ModerationListParams, ProfileRelationship } from '../types.js';
|
|
3
3
|
export declare class ProfilesResource {
|
|
4
4
|
private client;
|
|
5
5
|
constructor(client: HttpClient);
|
|
@@ -60,8 +60,12 @@ export declare class ProfilesResource {
|
|
|
60
60
|
cursor?: string;
|
|
61
61
|
limit?: number;
|
|
62
62
|
}): Promise<PaginatedResponse<string>>;
|
|
63
|
-
/**
|
|
64
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The caller's relationship with another user: follows in both directions
|
|
65
|
+
* (mutual when both are true), and whether the caller blocks (`is_blocking`)
|
|
66
|
+
* or mutes (`is_muting`) them, left out when the server cannot say.
|
|
67
|
+
*/
|
|
68
|
+
isFollowing(userId: string): Promise<SingleResponse<ProfileRelationship>>;
|
|
65
69
|
/**
|
|
66
70
|
* Block a user. A block is a two-way wall: it severs follows in both
|
|
67
71
|
* directions and hides each side's posts from the other.
|
|
@@ -76,7 +76,11 @@ export class ProfilesResource {
|
|
|
76
76
|
followingIds(params) {
|
|
77
77
|
return this.client.get('/profiles/me/following/ids', params);
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* The caller's relationship with another user: follows in both directions
|
|
81
|
+
* (mutual when both are true), and whether the caller blocks (`is_blocking`)
|
|
82
|
+
* or mutes (`is_muting`) them, left out when the server cannot say.
|
|
83
|
+
*/
|
|
80
84
|
isFollowing(userId) {
|
|
81
85
|
return this.client.get(`/profiles/${userId}/is-following`);
|
|
82
86
|
}
|
|
@@ -928,6 +928,8 @@ export interface SocialProfile {
|
|
|
928
928
|
followersCount: number | null;
|
|
929
929
|
followingCount: number | null;
|
|
930
930
|
postsCount: number | null;
|
|
931
|
+
/** Nostr only: the NIP-05 name its domain has confirmed for this key ("lyn@primal.net"); `handle` stays the key. */
|
|
932
|
+
nip05?: string;
|
|
931
933
|
subscription: SocialSubscription | null;
|
|
932
934
|
}
|
|
933
935
|
export interface SocialSubscription {
|
|
@@ -1073,6 +1075,20 @@ export interface SocialTrendFeed extends SocialTrendProvenance {
|
|
|
1073
1075
|
trend: SocialTrend;
|
|
1074
1076
|
items: SocialPost[];
|
|
1075
1077
|
cursor: string | null;
|
|
1078
|
+
/**
|
|
1079
|
+
* Present when the page came from the app's open-network source index instead of the provider:
|
|
1080
|
+
* with a category, a topic the provider no longer lists (`gone`) or whose feed held nothing in the
|
|
1081
|
+
* category (`empty`) is served the network's indexed posts in the category. While the provider has
|
|
1082
|
+
* more of the topic, `cursor` is the provider's own continuation (the topic's deeper pages may hold
|
|
1083
|
+
* the category); once it has none, or the topic is gone, it is an `idx:tf:` cursor that continues in
|
|
1084
|
+
* the index (`cursor`), the same stream for every topic of the network. The `trend` of a gone topic
|
|
1085
|
+
* carries its id as its title. Pass the cursor back unchanged like any other. When the index cannot
|
|
1086
|
+
* answer, the page is what the provider answered (the empty page with its cursor, or the gone topic's
|
|
1087
|
+
* 404); only an `idx:tf:` page is 503 `unavailable`, which ends that continuation, not the network.
|
|
1088
|
+
*/
|
|
1089
|
+
sourceIndex?: SocialTrendSourceIndex & {
|
|
1090
|
+
reason: 'gone' | 'empty' | 'cursor';
|
|
1091
|
+
};
|
|
1076
1092
|
}
|
|
1077
1093
|
/** A member of a radar cluster: a headline or excerpt and a link only, never a body. */
|
|
1078
1094
|
export type SocialRadarSourceKind = 'outlet' | 'hackernews' | 'bluesky' | 'activitypub' | 'nostr' | 'minds';
|
|
@@ -1103,6 +1119,15 @@ export interface SocialRadarMember {
|
|
|
1103
1119
|
height?: number;
|
|
1104
1120
|
};
|
|
1105
1121
|
providerRank?: number;
|
|
1122
|
+
/**
|
|
1123
|
+
* Present on a post member whose image or video a signed-out visitor may see (an open-network post with a cached
|
|
1124
|
+
* safe verdict, or a Minds post that passes the visitor's floor); absent on every other post member, and on members
|
|
1125
|
+
* of a radar built before the server recorded it. An outlet's media needs no mark. The keyless routes
|
|
1126
|
+
* (`getSharedRadar`, `getSharedRadarPosts`, `getSharedRadarStory`) serve media only from marked members and outlets.
|
|
1127
|
+
* A signed-in read (`getPublicRadar` and friends) keeps every member's media, so use only an outlet's or a marked
|
|
1128
|
+
* member's media on a signed-out page or a share card.
|
|
1129
|
+
*/
|
|
1130
|
+
visitorMedia?: true;
|
|
1106
1131
|
}
|
|
1107
1132
|
/** One story on the radar. `breaking` is outlet corroboration inside a short window; `viral` is social velocity above its network's norm. */
|
|
1108
1133
|
export interface SocialRadarCluster {
|
|
@@ -1127,6 +1152,7 @@ export interface SocialRadarCluster {
|
|
|
1127
1152
|
normalized: number;
|
|
1128
1153
|
samples: number;
|
|
1129
1154
|
};
|
|
1155
|
+
/** On a signed-in read the lead may belong to a member without `visitorMedia`; find it by `memberKey` before a share card uses it. */
|
|
1130
1156
|
media: {
|
|
1131
1157
|
images: number;
|
|
1132
1158
|
videos: number;
|
|
@@ -1317,7 +1343,10 @@ export declare class ProtocolsResource {
|
|
|
1317
1343
|
category?: string;
|
|
1318
1344
|
discovery?: boolean;
|
|
1319
1345
|
}): Promise<SingleResponse<SocialTrendFeed>>;
|
|
1320
|
-
/**
|
|
1346
|
+
/**
|
|
1347
|
+
* Breaking and viral stories clustered across outlets, open networks and Minds, through the same public-only app boundary.
|
|
1348
|
+
* Images and videos come only from outlets and from members marked `visitorMedia`; every other member is text.
|
|
1349
|
+
*/
|
|
1321
1350
|
getSharedRadar(projectId: string, params?: SocialRadarParams): Promise<SingleResponse<SocialRadar>>;
|
|
1322
1351
|
/** One radar story's social member posts without a key, so a signed-out story card can show the posts it counts. */
|
|
1323
1352
|
getSharedRadarPosts(projectId: string, params: {
|
|
@@ -1379,7 +1408,10 @@ export declare class ProtocolsResource {
|
|
|
1379
1408
|
category?: string;
|
|
1380
1409
|
discovery?: boolean;
|
|
1381
1410
|
}, options?: SocialReadOptions): Promise<SingleResponse<SocialTrendFeed>>;
|
|
1382
|
-
/**
|
|
1411
|
+
/**
|
|
1412
|
+
* The app's topic radar under its own key: the same snapshot the keyless route serves, for a signed-in reader, so
|
|
1413
|
+
* every member keeps its media (the keyless route serves only outlets' and `visitorMedia` members'). Never triggers a build.
|
|
1414
|
+
*/
|
|
1383
1415
|
getPublicRadar(params?: SocialRadarParams, options?: SocialReadOptions): Promise<SingleResponse<SocialRadar>>;
|
|
1384
1416
|
/** One radar story's social member posts, so its card can show the posts it counts. Minds members are read with `posts.get`. */
|
|
1385
1417
|
getPublicRadarPosts(params: {
|
|
@@ -102,7 +102,10 @@ export class ProtocolsResource {
|
|
|
102
102
|
return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/trend-feed`, { protocol: params.protocol, topic: params.topic,
|
|
103
103
|
limit: params.limit, cursor: params.cursor, category: params.category, discovery: params.discovery });
|
|
104
104
|
}
|
|
105
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Breaking and viral stories clustered across outlets, open networks and Minds, through the same public-only app boundary.
|
|
107
|
+
* Images and videos come only from outlets and from members marked `visitorMedia`; every other member is text.
|
|
108
|
+
*/
|
|
106
109
|
getSharedRadar(projectId, params = {}) {
|
|
107
110
|
return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/radar`, radarQuery(params));
|
|
108
111
|
}
|
|
@@ -167,7 +170,10 @@ export class ProtocolsResource {
|
|
|
167
170
|
...(params.category !== undefined ? { category: params.category } : {}),
|
|
168
171
|
...(params.discovery !== undefined ? { discovery: params.discovery } : {}) }, ...readOptions(options));
|
|
169
172
|
}
|
|
170
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* The app's topic radar under its own key: the same snapshot the keyless route serves, for a signed-in reader, so
|
|
175
|
+
* every member keeps its media (the keyless route serves only outlets' and `visitorMedia` members'). Never triggers a build.
|
|
176
|
+
*/
|
|
171
177
|
getPublicRadar(params = {}, options) {
|
|
172
178
|
return this.client.get('/protocols/social/radar', radarQuery(params), ...readOptions(options));
|
|
173
179
|
}
|
|
@@ -35,6 +35,10 @@ export declare class ReportsResource {
|
|
|
35
35
|
* Submit a content report. Targets a post, user, community, or message
|
|
36
36
|
* by ID, with a short reason and optional free-text details. Reporter
|
|
37
37
|
* is the authenticated user.
|
|
38
|
+
*
|
|
39
|
+
* Sent once, never retried automatically: past the member's report budget
|
|
40
|
+
* the server answers 429 (`RateLimitError`, `retryAfter` = seconds until
|
|
41
|
+
* the window opens), and that refusal is for the member to see.
|
|
38
42
|
*/
|
|
39
43
|
create(input: CreateReportInput): Promise<SingleResponse<{
|
|
40
44
|
success: true;
|
|
@@ -7,9 +7,13 @@ export class ReportsResource {
|
|
|
7
7
|
* Submit a content report. Targets a post, user, community, or message
|
|
8
8
|
* by ID, with a short reason and optional free-text details. Reporter
|
|
9
9
|
* is the authenticated user.
|
|
10
|
+
*
|
|
11
|
+
* Sent once, never retried automatically: past the member's report budget
|
|
12
|
+
* the server answers 429 (`RateLimitError`, `retryAfter` = seconds until
|
|
13
|
+
* the window opens), and that refusal is for the member to see.
|
|
10
14
|
*/
|
|
11
15
|
create(input) {
|
|
12
|
-
return this.client.post('/reports', input);
|
|
16
|
+
return this.client.post('/reports', input, { retry: false });
|
|
13
17
|
}
|
|
14
18
|
/** List recent reports in the caller's project/network (admin only). */
|
|
15
19
|
list() {
|