@recursiv/sdk 0.7.83 → 0.7.93
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 +86 -2
- package/dist/assistant/index.d.ts +11 -3
- package/dist/assistant/index.js +11 -3
- package/dist/assistant/render.d.ts +25 -2
- package/dist/assistant/render.js +197 -16
- package/dist/assistant/subjectBundle.js +20 -3
- package/dist/assistant/turnCaps.d.ts +3 -0
- package/dist/assistant/turnCaps.js +141 -0
- package/dist/assistant/types.d.ts +223 -7
- package/dist/assistant/types.js +15 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.js +8 -2
- package/dist/privacy/facts.generated.d.ts +79 -0
- package/dist/privacy/facts.generated.js +93 -0
- package/dist/privacy/index.d.ts +48 -0
- package/dist/privacy/index.js +42 -0
- package/dist/resources/discovery-merge.d.ts +17 -1
- package/dist/resources/discovery-merge.js +84 -4
- package/dist/resources/discovery-progressive.d.ts +4 -3
- package/dist/resources/discovery-progressive.js +11 -8
- package/dist/resources/discovery.d.ts +9 -1
- package/dist/resources/discovery.js +62 -15
- package/dist/resources/feeds.d.ts +15 -6
- package/dist/resources/feeds.js +2 -1
- package/dist/resources/integrations.d.ts +3 -0
- package/dist/resources/privacy.d.ts +13 -0
- package/dist/resources/privacy.js +14 -0
- package/dist/resources/protocols.d.ts +99 -11
- package/dist/resources/protocols.js +45 -0
- package/dist/resources/sealed.d.ts +127 -7
- package/dist/resources/sealed.js +33 -6
- package/dist/resources/settings.d.ts +3 -1
- package/dist/resources/settings.js +3 -1
- package/dist/resources/signals.d.ts +129 -6
- package/dist/resources/signals.js +58 -2
- package/dist/social-protocol-ids.d.ts +28 -0
- package/dist/social-protocol-ids.js +30 -0
- package/dist/types.d.ts +45 -0
- package/llm.md +17 -6
- package/package.json +6 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { SOCIAL_PROTOCOL_IDS } from '../social-protocol-ids.js';
|
|
1
2
|
import { readFalloverSteps } from './discovery-fallover.js';
|
|
3
|
+
import { discoveryItemIdentities } from './discovery-merge.js';
|
|
2
4
|
import { createDiscoveryTopShare, createProgressiveFeed } from './discovery-progressive.js';
|
|
3
5
|
export * from './discovery-fallover.js';
|
|
4
6
|
export * from './discovery-first-screen.js';
|
|
@@ -142,7 +144,8 @@ function recommendationItem(kind, candidate) {
|
|
|
142
144
|
return { key: `rec:tag:${(nonEmpty(value.slug) ? value.slug : value.name).toLowerCase()}`, kind: 'tag', tag: value };
|
|
143
145
|
}
|
|
144
146
|
const RADAR_KINDS = new Set(['breaking', 'viral', 'story']);
|
|
145
|
-
|
|
147
|
+
/** A radar member's open network (SOCIAL_PROTOCOL_IDS, Farcaster included before the radar holds any); another kind is skipped. */
|
|
148
|
+
const RADAR_SOCIAL_KINDS = new Set(SOCIAL_PROTOCOL_IDS);
|
|
146
149
|
const httpUrl = (value) => typeof value === 'string' && value.length <= 4096 && /^https?:\/\//i.test(value);
|
|
147
150
|
const countOf = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
148
151
|
const dimension = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
@@ -271,7 +274,8 @@ export function pickRadarCluster(clusters, page, options = {}) {
|
|
|
271
274
|
}
|
|
272
275
|
return breaking[0] ?? unused[0];
|
|
273
276
|
}
|
|
274
|
-
|
|
277
|
+
/** A feed top's open-network item or lane (SOCIAL_PROTOCOL_IDS); a session keeps only the networks it reads (`readyTop`). */
|
|
278
|
+
const TOP_NETWORKS = new Set(SOCIAL_PROTOCOL_IDS);
|
|
275
279
|
const idString = (value) => typeof value === 'string' && value.length > 0 && value.length <= 4096;
|
|
276
280
|
const counted = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
277
281
|
/** The refs an app's seen set may hold for a post: its id, or '<protocol>/<nativeId>' (an ActivityPub id also without https://). */
|
|
@@ -339,6 +343,19 @@ function topItemSeenRefs(value) {
|
|
|
339
343
|
}
|
|
340
344
|
const ordinary = { version: 'discovery-layout-v1', presentation: 'posts', source: 'fallback' };
|
|
341
345
|
const key = (protocol, id) => JSON.stringify([protocol, id]);
|
|
346
|
+
/** The judgment of a read made in `category`: its category filter's selection, when that is the category asked. */
|
|
347
|
+
const judgedInto = (category, filter) => (category && filter?.selected === category ? { judged: category } : {});
|
|
348
|
+
/** The judgment of a read's item at `index`. */
|
|
349
|
+
const judgmentAt = (read, index) => (read.judgments ? read.judgments[index] : read.judged);
|
|
350
|
+
/**
|
|
351
|
+
* A featured module's theme: its lead window's, only when most of the module's own posts were judged into it (see
|
|
352
|
+
* DiscoveryModule.theme). Never `mixed`.
|
|
353
|
+
*/
|
|
354
|
+
function judgedTheme(theme, judged) {
|
|
355
|
+
if (!theme || theme === 'mixed' || !judged.length)
|
|
356
|
+
return undefined;
|
|
357
|
+
return judged.filter(category => category === theme).length * 2 > judged.length ? theme : undefined;
|
|
358
|
+
}
|
|
342
359
|
const presentations = new Set(['posts', 'visual', 'conversation', 'video', 'thread', 'longread']);
|
|
343
360
|
/** A gallery strip holds up to twelve image posts gathered from the whole page. */
|
|
344
361
|
export const GALLERY_MAX_ITEMS = 12;
|
|
@@ -464,7 +481,7 @@ export class DiscoveryFeedSession {
|
|
|
464
481
|
? { key: key(row.post.protocol, row.post.nativeId), kind: 'social', post: row.post }
|
|
465
482
|
: { key: key('minds', row.id), kind: 'minds', post: row }));
|
|
466
483
|
const read = { layout: response.meta.discovery ?? ordinary, partial: response.meta.category_filter?.status === 'partial',
|
|
467
|
-
...(response.meta.compass_filter ? { compass: response.meta.compass_filter } : {}) };
|
|
484
|
+
...(response.meta.compass_filter ? { compass: response.meta.compass_filter } : {}), ...judgedInto(this.category, response.meta.category_filter) };
|
|
468
485
|
if (!this.filtering)
|
|
469
486
|
return { items, ...read };
|
|
470
487
|
head ??= read;
|
|
@@ -518,7 +535,7 @@ export class DiscoveryFeedSession {
|
|
|
518
535
|
const kept = data.items.flatMap((post, index) => this.metNetworkPost(post) ? [] : [{ post, index }]);
|
|
519
536
|
const layout = data.discovery ?? ordinary;
|
|
520
537
|
return { items: kept.map(({ post }) => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout, partial: data.categoryFilter?.status === 'partial',
|
|
521
|
-
...(kept.length !== data.items.length ? { layouts: kept.map(({ index }) => windowLayout(layout, index)) } : {}) };
|
|
538
|
+
...(kept.length !== data.items.length ? { layouts: kept.map(({ index }) => windowLayout(layout, index)) } : {}), ...judgedInto(this.category, data.categoryFilter) };
|
|
522
539
|
}
|
|
523
540
|
/** An open-network post in the member's seen set (options.seen: '<protocol>/<nativeId>', an ActivityPub id also without https://). */
|
|
524
541
|
metNetworkPost(post) {
|
|
@@ -607,7 +624,8 @@ export class DiscoveryFeedSession {
|
|
|
607
624
|
const items = (indexed ? data.items.filter(post => !this.seen.has(key(post.protocol, post.nativeId))) : data.items).filter(post => !this.metNetworkPost(post));
|
|
608
625
|
if (items.length) {
|
|
609
626
|
stream.done = !topics.length;
|
|
610
|
-
return { items: items.map(post => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout: data.discovery ?? ordinary, partial
|
|
627
|
+
return { items: items.map(post => ({ key: key(post.protocol, post.nativeId), kind: 'social', post })), layout: data.discovery ?? ordinary, partial,
|
|
628
|
+
...judgedInto(this.category, data.categoryFilter) };
|
|
611
629
|
}
|
|
612
630
|
}
|
|
613
631
|
while (topics[0] && passedOver() && !topics[0].cursor?.startsWith(INDEX_CURSOR))
|
|
@@ -680,17 +698,25 @@ export class DiscoveryFeedSession {
|
|
|
680
698
|
// Round-robin before grouping: the largest network cannot occupy every slot.
|
|
681
699
|
// Each item carries the judgment for its own window of its source response,
|
|
682
700
|
// so one page can feature several modules instead of one per source batch.
|
|
701
|
+
// A post is shown once by every identity (discoveryItemIdentities): a second note carrying a clip already placed is
|
|
702
|
+
// the same post to a reader, on this page or a later one.
|
|
683
703
|
const mixed = [];
|
|
684
704
|
for (let index = 0; index < Math.max(0, ...reads.map(read => read.items.length)); index++)
|
|
685
705
|
for (const read of reads) {
|
|
686
706
|
const item = read.items[index];
|
|
687
|
-
if (!item
|
|
707
|
+
if (!item)
|
|
708
|
+
continue;
|
|
709
|
+
const identities = discoveryItemIdentities(item);
|
|
710
|
+
if (identities.some(identity => this.seen.has(identity)))
|
|
688
711
|
continue;
|
|
689
|
-
|
|
690
|
-
|
|
712
|
+
for (const identity of identities)
|
|
713
|
+
this.seen.add(identity);
|
|
714
|
+
const judged = judgmentAt(read, index);
|
|
715
|
+
mixed.push({ item, layout: read.layouts?.[index] ?? windowLayout(read.layout, index), ...(judged ? { judged } : {}) });
|
|
691
716
|
}
|
|
692
|
-
// Deduplication memory is bounded independently of the UI's virtual window
|
|
693
|
-
|
|
717
|
+
// Deduplication memory is bounded independently of the UI's virtual window: an open-network post holds a few
|
|
718
|
+
// identities (its key, its id and its files), so the bound that held 4096 keys holds four times as many entries.
|
|
719
|
+
while (this.seen.size > 16_384)
|
|
694
720
|
this.seen.delete(this.seen.values().next().value);
|
|
695
721
|
const modules = topItems.map((item, index) => this.topModule(this.page, index, item));
|
|
696
722
|
const page = this.page++;
|
|
@@ -707,9 +733,22 @@ export class DiscoveryFeedSession {
|
|
|
707
733
|
const onPage = remaining.filter(row => members.has(row.item.key)).map(row => row.item);
|
|
708
734
|
const carried = new Set(onPage.map(item => item.key));
|
|
709
735
|
// Members this page did not carry come from the story's own reads; one an earlier page showed stays shown there.
|
|
710
|
-
|
|
736
|
+
// They are taken one at a time in the story's member order, each adding its identities as it is kept, so two
|
|
737
|
+
// members of one read that carry one clip (a mirror note on another server) show once: the one the story lists first.
|
|
738
|
+
const order = (item) => cluster.memberKeys.indexOf(item.key);
|
|
739
|
+
const read = [];
|
|
740
|
+
for (const item of [...await this.memberPosts(cluster)].sort((a, b) => order(a) - order(b))) {
|
|
741
|
+
if (carried.has(item.key))
|
|
742
|
+
continue;
|
|
743
|
+
const identities = discoveryItemIdentities(item);
|
|
744
|
+
if (identities.some(identity => this.seen.has(identity)))
|
|
745
|
+
continue;
|
|
746
|
+
for (const identity of identities)
|
|
747
|
+
this.seen.add(identity);
|
|
748
|
+
read.push(item);
|
|
749
|
+
}
|
|
711
750
|
// Under the story, posts keep the server's member order: the lead media member first, then by member score.
|
|
712
|
-
clusterItems = [...onPage, ...read].sort((a, b) =>
|
|
751
|
+
clusterItems = [...onPage, ...read].sort((a, b) => order(a) - order(b));
|
|
713
752
|
remaining = remaining.filter(row => !members.has(row.item.key));
|
|
714
753
|
for (const memberKey of cluster.memberKeys)
|
|
715
754
|
this.seen.add(memberKey);
|
|
@@ -742,7 +781,8 @@ export class DiscoveryFeedSession {
|
|
|
742
781
|
const selected = visual ? images : discussion ? conversations : thread ? quotes : [first];
|
|
743
782
|
const items = selected.map(row => row.item);
|
|
744
783
|
const type = video ? 'DiscoveryVideo' : visual ? 'DiscoveryGallery' : discussion ? 'DiscoveryConversation' : thread ? 'DiscoveryThread' : longread ? 'DiscoveryLongRead' : 'DiscoveryPosts';
|
|
745
|
-
|
|
784
|
+
// Titled after its lead window's theme only when most of its own posts were judged into it (DiscoveryModule.theme).
|
|
785
|
+
const theme = presentation !== 'posts' ? judgedTheme(first.layout.theme, selected.map(row => row.judged)) : undefined;
|
|
746
786
|
modules.push({ id: `discovery-${page}-${index}-${items[0].key}`, presentation, source: presentation === 'posts' ? 'fallback' : 'jev', items, ...(theme ? { theme } : {}),
|
|
747
787
|
spec: { root: 'module', elements: { module: { type, props: { itemKeys: items.map(item => item.key) }, children: [] } } } });
|
|
748
788
|
const selectedKeys = new Set(items.map(item => item.key));
|
|
@@ -848,7 +888,7 @@ export class DiscoveryFeedSession {
|
|
|
848
888
|
}
|
|
849
889
|
const lanes = new Map();
|
|
850
890
|
const networks = value.networks && typeof value.networks === 'object' ? value.networks : {};
|
|
851
|
-
for (const protocol of
|
|
891
|
+
for (const protocol of SOCIAL_PROTOCOL_IDS) {
|
|
852
892
|
const entries = networks[protocol];
|
|
853
893
|
if (!streams.has(protocol) || !Array.isArray(entries))
|
|
854
894
|
continue;
|
|
@@ -917,7 +957,9 @@ export class DiscoveryFeedSession {
|
|
|
917
957
|
const read = at >= 0 ? reads[at] : null;
|
|
918
958
|
if (read) {
|
|
919
959
|
const kept = read.previews ? [] : read.items;
|
|
920
|
-
|
|
960
|
+
// The lane's posts lead the read's; they carry no judgment of the read's category.
|
|
961
|
+
reads[at] = { ...read, items: [...lane, ...kept], layouts: [...lane.map(() => ordinary), ...kept.map((_item, index) => read.layouts?.[index] ?? windowLayout(read.layout, index))],
|
|
962
|
+
judgments: [...lane.map(() => undefined), ...kept.map((_item, index) => judgmentAt(read, index))] };
|
|
921
963
|
}
|
|
922
964
|
else if (this.streams.some(stream => stream.protocol === protocol))
|
|
923
965
|
reads.push({ items: lane, layout: ordinary, partial: false });
|
|
@@ -930,10 +972,15 @@ export class DiscoveryFeedSession {
|
|
|
930
972
|
const found = top.posts.get(row.key) ?? carried.get(row.key);
|
|
931
973
|
if (!found || this.seen.has(row.key))
|
|
932
974
|
continue;
|
|
975
|
+
const identities = discoveryItemIdentities(found);
|
|
976
|
+
if (identities.some(identity => this.seen.has(identity)))
|
|
977
|
+
continue;
|
|
933
978
|
const position = top.positions.get(row.ref) ?? placed.length;
|
|
934
979
|
placed.push({ ...found, top: { ref: row.ref, position, block: position < 10 ? 'top' : 'below' } });
|
|
935
980
|
this.seen.add(row.key);
|
|
936
981
|
this.topKeys.add(row.key);
|
|
982
|
+
for (const identity of identities)
|
|
983
|
+
this.seen.add(identity);
|
|
937
984
|
}
|
|
938
985
|
return placed;
|
|
939
986
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
+
import { type SocialProtocolId } from '../social-protocol-ids.js';
|
|
2
3
|
import type { CompassFilterMeta, StoryOutlets } from '../types.js';
|
|
3
4
|
/**
|
|
4
5
|
* The top of a feed: what the member (or a visitor) is most likely to engage with, rotated by what they have seen.
|
|
@@ -8,9 +9,17 @@ import type { CompassFilterMeta, StoryOutlets } from '../types.js';
|
|
|
8
9
|
* Every tap target is in the app: a Minds post by id, an open-network post by `/social/post/<protocol>/<id>`, a story
|
|
9
10
|
* by id. Nothing here is a URL on another network.
|
|
10
11
|
*/
|
|
11
|
-
/**
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The open networks whose lanes a top may carry: every SOCIAL_PROTOCOL_IDS network (0.7.87: Farcaster before any top
|
|
14
|
+
* carries a Farcaster lane or item).
|
|
15
|
+
*/
|
|
16
|
+
export type FeedTopNetwork = SocialProtocolId;
|
|
13
17
|
export declare const FEED_TOP_NETWORKS: readonly FeedTopNetwork[];
|
|
18
|
+
/**
|
|
19
|
+
* A top's lanes, or their states, by network: Bluesky, ActivityPub and Nostr are always named; a network listed later
|
|
20
|
+
* (Farcaster) only once the API carries its lane, so read `networks.farcaster ?? []`.
|
|
21
|
+
*/
|
|
22
|
+
export type FeedTopNetworkMap<T> = Record<'bluesky' | 'activitypub' | 'nostr', T> & Partial<Record<FeedTopNetwork, T>>;
|
|
14
23
|
/** Where a tap on an item goes, always in the app. `path` is the app's own page of a network post, never its URL there. */
|
|
15
24
|
export type FeedTopTarget = {
|
|
16
25
|
kind: 'post';
|
|
@@ -103,7 +112,7 @@ export interface FeedStory {
|
|
|
103
112
|
* are politics. Hide politics (`compass: 'hide'`) leaves such a story out. Absent from a server older than 0.7.67.
|
|
104
113
|
*/
|
|
105
114
|
political?: boolean;
|
|
106
|
-
sources: Partial<Record<'outlet' | 'hackernews' |
|
|
115
|
+
sources: Partial<Record<'outlet' | 'hackernews' | SocialProtocolId | 'minds', number>>;
|
|
107
116
|
/** How many outlets reported it (StoryOutlets): the number every surface names for the story. Absent from a server older than 0.7.71. */
|
|
108
117
|
outlets?: StoryOutlets;
|
|
109
118
|
media: {
|
|
@@ -141,13 +150,13 @@ export interface FeedTopMeta {
|
|
|
141
150
|
/** 'safe' for a member on safe defaults, 'open' for one who turned them off. */
|
|
142
151
|
safety: 'safe' | 'open';
|
|
143
152
|
mindsSource: 'member' | 'anonymous' | 'none' | 'following';
|
|
144
|
-
networks:
|
|
153
|
+
networks: FeedTopNetworkMap<'ok' | 'unavailable'> | null;
|
|
145
154
|
compass_filter?: CompassFilterMeta;
|
|
146
155
|
}
|
|
147
156
|
/** GET /feeds/top. `networks` is null on a feed with no lanes (Following, a story); `lead` is null when the feed has no story. */
|
|
148
157
|
export interface FeedTopResponse {
|
|
149
158
|
items: FeedTopItem[];
|
|
150
|
-
networks:
|
|
159
|
+
networks: FeedTopNetworkMap<FeedTopNetworkEntry[]> | null;
|
|
151
160
|
lead: FeedStory | null;
|
|
152
161
|
meta: FeedTopMeta;
|
|
153
162
|
}
|
|
@@ -179,7 +188,7 @@ export interface PublicFeedTopMeta {
|
|
|
179
188
|
/** A visitor's top (`feeds.publicTop`): safe items only, the same for every visitor in one 15-minute bucket. */
|
|
180
189
|
export interface PublicFeedTopResponse {
|
|
181
190
|
items: FeedTopItem[];
|
|
182
|
-
networks:
|
|
191
|
+
networks: FeedTopNetworkMap<FeedTopNetworkEntry[]> | null;
|
|
183
192
|
lead: FeedStory | null;
|
|
184
193
|
meta: PublicFeedTopMeta;
|
|
185
194
|
}
|
package/dist/resources/feeds.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NotFoundError, RecursivError } from '../errors.js';
|
|
2
|
-
|
|
2
|
+
import { SOCIAL_PROTOCOL_IDS } from '../social-protocol-ids.js';
|
|
3
|
+
export const FEED_TOP_NETWORKS = SOCIAL_PROTOCOL_IDS;
|
|
3
4
|
export class FeedsResource {
|
|
4
5
|
http;
|
|
5
6
|
constructor(http) {
|
|
@@ -90,6 +90,9 @@ export interface UpdateAgentIntegrationInput {
|
|
|
90
90
|
allowed_tools?: string[];
|
|
91
91
|
}
|
|
92
92
|
export interface PendingToolExecution {
|
|
93
|
+
/** First-party builder identity is supplied by the server, never inferred from tool name. */
|
|
94
|
+
builder_approval?: true;
|
|
95
|
+
details_withheld?: boolean;
|
|
93
96
|
id: string;
|
|
94
97
|
conversation_id: string;
|
|
95
98
|
agent: {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HttpClient } from '../client.js';
|
|
2
|
+
import type { PrivacyCapabilities } from '../privacy/index.js';
|
|
3
|
+
import type { SingleResponse } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* What the deployed API enforces. Pair it with `@recursiv/sdk/privacy`, which says what each fact means and whether
|
|
6
|
+
* the installed SDK marks it enforced: render a privacy sentence only when both do (`isFactLive`).
|
|
7
|
+
*/
|
|
8
|
+
export declare class PrivacyResource {
|
|
9
|
+
private client;
|
|
10
|
+
constructor(client: HttpClient);
|
|
11
|
+
/** GET /privacy/capabilities: the platform facts this deployment's build enforces, and its commit. Any API key. */
|
|
12
|
+
capabilities(): Promise<SingleResponse<PrivacyCapabilities>>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the deployed API enforces. Pair it with `@recursiv/sdk/privacy`, which says what each fact means and whether
|
|
3
|
+
* the installed SDK marks it enforced: render a privacy sentence only when both do (`isFactLive`).
|
|
4
|
+
*/
|
|
5
|
+
export class PrivacyResource {
|
|
6
|
+
client;
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
/** GET /privacy/capabilities: the platform facts this deployment's build enforces, and its commit. Any API key. */
|
|
11
|
+
capabilities() {
|
|
12
|
+
return this.client.get('/privacy/capabilities');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
2
|
import type { CompassFilterMeta, DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse, StoryOutlets } from '../types.js';
|
|
3
3
|
import type { CuratorStoryPostsPage } from './curator.js';
|
|
4
|
+
import type { NativeWriteProtocol, SocialProtocolId } from '../social-protocol-ids.js';
|
|
4
5
|
/** Canonical protocol operation names returned by the adapter registry. */
|
|
5
6
|
export declare const PROTOCOL_CAPABILITY_OPERATIONS: readonly ["discover", "stream", "backfill", "search", "resolveIdentity", "fetchProfile", "fetchThread", "publish", "reply", "react", "repost", "follow", "delete", "health", "checkpoint"];
|
|
6
7
|
export declare const PROTOCOL_CAPABILITY_STATUSES: readonly ["supported", "planned", "unsupported"];
|
|
@@ -426,8 +427,55 @@ export interface FederationIdentityView {
|
|
|
426
427
|
origin: string | null;
|
|
427
428
|
identity: FederationIdentity | null;
|
|
428
429
|
}
|
|
430
|
+
/** A native network every eligible member is on by rule (Farcaster joins later). */
|
|
431
|
+
export type NativeNetwork = 'activitypub' | 'atproto' | 'nostr';
|
|
432
|
+
/**
|
|
433
|
+
* A member's state on one native network. `ready`: on by rule, set up on their first public post, first interaction
|
|
434
|
+
* there, or `activateNetwork` ("Will be set up when you first post or interact"). `pending`: being set up, or waiting
|
|
435
|
+
* for the setup budget (`retryAt`). `off`: the member turned it off (`reason: 'opted_out'`), it is not available to them
|
|
436
|
+
* yet (`not_rolled_out`), or their profile is private (`private_profile`, AT Protocol and Nostr only; `setNetwork`
|
|
437
|
+
* with `enabled: true` turns it on). `needs_action`: the member must act first (`age_band_required`,
|
|
438
|
+
* `needs_verified_email`). `locked`: held off by a rule the member cannot change.
|
|
439
|
+
*/
|
|
440
|
+
export type NativeNetworkState = 'ready' | 'on' | 'pending' | 'off' | 'error' | 'locked' | 'needs_action';
|
|
441
|
+
export interface NativeNetworkPresence {
|
|
442
|
+
network: NativeNetwork;
|
|
443
|
+
state: NativeNetworkState;
|
|
444
|
+
/** Why, as a code: `opted_out`, `not_rolled_out`, `private_profile`, `age_band_required`, `needs_verified_email`, `activation_failed`... */
|
|
445
|
+
reason: string | null;
|
|
446
|
+
/** `hosted` (a Minds-run ActivityPub actor or AT Protocol account), `managed` (a Minds-held Nostr key) or `attached`. */
|
|
447
|
+
mode: 'hosted' | 'managed' | 'attached';
|
|
448
|
+
/** The member's address on the network, set up or not, when the server can say it. */
|
|
449
|
+
handle: string | null;
|
|
450
|
+
/** Which identity a one-tap like, repost, reply or follow uses when the member also linked an outside account. */
|
|
451
|
+
engageDefault: 'native' | 'linked';
|
|
452
|
+
activatedAt: string | null;
|
|
453
|
+
optedOutAt: string | null;
|
|
454
|
+
removePast: boolean;
|
|
455
|
+
}
|
|
456
|
+
export interface NativeNetworkActivation {
|
|
457
|
+
outcome: 'activated' | 'already_on' | 'pending' | 'in_flight' | 'needs_action' | 'error' | 'not_eligible' | 'refused' | 'unavailable';
|
|
458
|
+
presence: NativeNetworkPresence;
|
|
459
|
+
/** When the setup budget frees, for `pending`. */
|
|
460
|
+
retryAt: string | null;
|
|
461
|
+
}
|
|
462
|
+
export interface NativeNetworkOff {
|
|
463
|
+
outcome: 'off';
|
|
464
|
+
presence: NativeNetworkPresence;
|
|
465
|
+
/** `failed`: the member is off, but removing what was shared did not start; call `setNetwork` again to retry it. */
|
|
466
|
+
retraction: 'queued' | 'failed' | 'none';
|
|
467
|
+
}
|
|
468
|
+
export interface SetNativeNetworkInput {
|
|
469
|
+
enabled: boolean;
|
|
470
|
+
/** Turning off: also remove what was already shared there (the Settings sheet checks this by default). */
|
|
471
|
+
removePast?: boolean;
|
|
472
|
+
}
|
|
429
473
|
export interface SocialSourceRef {
|
|
430
|
-
|
|
474
|
+
/**
|
|
475
|
+
* 0.7.87: any SOCIAL_PROTOCOL_IDS network, Farcaster (an FID, or a `<fid>:0x<hash>` CastId) before its reader serves a
|
|
476
|
+
* post. A source to show, mute, hide or report; a native write takes only a NativeWriteProtocol network.
|
|
477
|
+
*/
|
|
478
|
+
protocol: SocialProtocolId;
|
|
431
479
|
kind: 'actor' | 'post';
|
|
432
480
|
nativeId: string;
|
|
433
481
|
}
|
|
@@ -474,7 +522,7 @@ export interface NativeReplyPrepareInput {
|
|
|
474
522
|
clientMutationId: string;
|
|
475
523
|
text: string;
|
|
476
524
|
parent: {
|
|
477
|
-
protocol:
|
|
525
|
+
protocol: NativeWriteProtocol;
|
|
478
526
|
nativeId: string;
|
|
479
527
|
readRef?: string;
|
|
480
528
|
};
|
|
@@ -491,7 +539,7 @@ export interface NativeReplyPrepareInput {
|
|
|
491
539
|
}
|
|
492
540
|
export interface NativeFollowSourceInput {
|
|
493
541
|
accountId: string;
|
|
494
|
-
protocol:
|
|
542
|
+
protocol: NativeWriteProtocol;
|
|
495
543
|
nativeId: string;
|
|
496
544
|
}
|
|
497
545
|
export interface NativeFollowPrepareInput extends NativeFollowSourceInput {
|
|
@@ -536,7 +584,7 @@ export interface NativeFollowState extends NativeFollowReceipt {
|
|
|
536
584
|
/** One connected account on the source's network, as the account lists describe it. */
|
|
537
585
|
export interface NativeFollowAccount {
|
|
538
586
|
id: string;
|
|
539
|
-
protocol:
|
|
587
|
+
protocol: NativeWriteProtocol;
|
|
540
588
|
nativeId: string;
|
|
541
589
|
handle: string;
|
|
542
590
|
serverUrl: string;
|
|
@@ -545,7 +593,7 @@ export interface NativeFollowAccount {
|
|
|
545
593
|
}
|
|
546
594
|
/** Every connected account's relationship with one source, from one read. */
|
|
547
595
|
export interface NativeFollowStates {
|
|
548
|
-
protocol:
|
|
596
|
+
protocol: NativeWriteProtocol;
|
|
549
597
|
nativeId: string;
|
|
550
598
|
/** False when native follows are off for this app; accounts are still listed, and no state is read. */
|
|
551
599
|
enabled: boolean;
|
|
@@ -578,7 +626,7 @@ export interface NativeFollowReview {
|
|
|
578
626
|
}
|
|
579
627
|
export interface NativeLikeSourceInput {
|
|
580
628
|
accountId: string;
|
|
581
|
-
protocol:
|
|
629
|
+
protocol: NativeWriteProtocol;
|
|
582
630
|
nativeId: string;
|
|
583
631
|
}
|
|
584
632
|
export interface NativeLikePrepareInput extends NativeLikeSourceInput {
|
|
@@ -644,7 +692,7 @@ export interface NativeLikeReview {
|
|
|
644
692
|
}
|
|
645
693
|
export interface NativeRepostSourceInput {
|
|
646
694
|
accountId: string;
|
|
647
|
-
protocol:
|
|
695
|
+
protocol: NativeWriteProtocol;
|
|
648
696
|
nativeId: string;
|
|
649
697
|
}
|
|
650
698
|
export interface NativeRepostPrepareInput extends NativeRepostSourceInput {
|
|
@@ -773,7 +821,7 @@ export interface NativeReplyBatch {
|
|
|
773
821
|
serverUrl: string;
|
|
774
822
|
} | null;
|
|
775
823
|
parent: {
|
|
776
|
-
protocol:
|
|
824
|
+
protocol: NativeWriteProtocol;
|
|
777
825
|
nativeId: string;
|
|
778
826
|
sourceUrl: string;
|
|
779
827
|
authorId: string;
|
|
@@ -910,7 +958,7 @@ export interface NativeNostrReplyResult {
|
|
|
910
958
|
text: string;
|
|
911
959
|
/** `readRef`: a linked ActivityPub source's public reader id, for the conversation read (replies saved before it existed have none). */
|
|
912
960
|
source: {
|
|
913
|
-
protocol:
|
|
961
|
+
protocol: NativeWriteProtocol;
|
|
914
962
|
nativeId: string;
|
|
915
963
|
sourceUrl: string;
|
|
916
964
|
readRef?: string;
|
|
@@ -1321,7 +1369,8 @@ export interface SocialTrendFeed extends SocialTrendProvenance {
|
|
|
1321
1369
|
};
|
|
1322
1370
|
}
|
|
1323
1371
|
/** A member of a radar cluster: a headline or excerpt and a link only, never a body. */
|
|
1324
|
-
|
|
1372
|
+
/** 0.7.87: an open-network member's kind is any SOCIAL_PROTOCOL_IDS network, Farcaster before the radar holds any. */
|
|
1373
|
+
export type SocialRadarSourceKind = 'outlet' | 'hackernews' | SocialProtocolId | 'minds';
|
|
1325
1374
|
export interface SocialRadarMember {
|
|
1326
1375
|
kind: SocialRadarSourceKind;
|
|
1327
1376
|
/** outlet: the article url; hackernews: the `hn:<id>` candidate id; social: JSON `[protocol, nativeId]`; minds: the post id. */
|
|
@@ -1748,12 +1797,27 @@ export interface SocialConversation {
|
|
|
1748
1797
|
hiddenByViewer?: true;
|
|
1749
1798
|
viewerControls?: Array<Pick<SocialSourceControl, 'source' | 'control'>>;
|
|
1750
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* A followed account's status on a Following page, in the server's order (SocialFollowingFeed.ts
|
|
1802
|
+
* FOLLOWING_SOURCE_STATUSES). `late` (added after 0.7.82): the account's read had not answered at the page's deadline;
|
|
1803
|
+
* it is read again on a later page and is never a failure. APIs before it name such an account `unavailable` with
|
|
1804
|
+
* `late: true`.
|
|
1805
|
+
*/
|
|
1806
|
+
export declare const SOCIAL_FOLLOWING_SOURCE_STATUSES: readonly ["ok", "late", "unavailable", "rate_limited", "unsupported_protocol"];
|
|
1807
|
+
export type SocialFollowingSourceStatus = typeof SOCIAL_FOLLOWING_SOURCE_STATUSES[number];
|
|
1751
1808
|
export interface SocialFollowingSource {
|
|
1752
1809
|
subscriptionId: string;
|
|
1753
1810
|
protocol: string;
|
|
1754
1811
|
nativeId: string;
|
|
1755
|
-
|
|
1812
|
+
/**
|
|
1813
|
+
* `late`: the account's read has not answered yet; it is read again on a later page. `ok` with `late: true`: the
|
|
1814
|
+
* account's last-held first page, served while its fresh read goes on. `unavailable` is a read that failed, or one
|
|
1815
|
+
* still late after its reads again.
|
|
1816
|
+
*/
|
|
1817
|
+
status: SocialFollowingSourceStatus;
|
|
1756
1818
|
moreAvailable: boolean | null;
|
|
1819
|
+
/** The account is read again on a later page: never show it as a failure; read the next page for its posts. */
|
|
1820
|
+
late?: true;
|
|
1757
1821
|
}
|
|
1758
1822
|
/** A bounded batch of recent author windows. Continue the cursor to read more followed accounts. */
|
|
1759
1823
|
export interface SocialFollowingFeed {
|
|
@@ -1764,6 +1828,10 @@ export interface SocialFollowingFeed {
|
|
|
1764
1828
|
totalAccounts: number;
|
|
1765
1829
|
postsPerAccount: number;
|
|
1766
1830
|
coverage: 'recent_original_posts';
|
|
1831
|
+
/** The networks with an account named late on this page (absent from older APIs). */
|
|
1832
|
+
lateNetworks?: string[];
|
|
1833
|
+
/** How long this page waited for an account's first read on each network, in ms (absent from older APIs). */
|
|
1834
|
+
deadlineMs?: number;
|
|
1767
1835
|
}
|
|
1768
1836
|
export declare class ProtocolsResource {
|
|
1769
1837
|
private client;
|
|
@@ -2134,6 +2202,26 @@ export declare class ProtocolsResource {
|
|
|
2134
2202
|
disconnected: true;
|
|
2135
2203
|
providerRevocation: 'acknowledged' | 'unconfirmed';
|
|
2136
2204
|
}>>;
|
|
2205
|
+
/**
|
|
2206
|
+
* The signed-in member's native networks in the key's app (ActivityPub, AT Protocol, Nostr): each one's state, reason
|
|
2207
|
+
* and address. Read-only: it never sets anything up. Needs `users:read`.
|
|
2208
|
+
*/
|
|
2209
|
+
networks(): Promise<SingleResponse<{
|
|
2210
|
+
networks: NativeNetworkPresence[];
|
|
2211
|
+
}>>;
|
|
2212
|
+
/**
|
|
2213
|
+
* "Set up now": sets up a network that is `ready` (or `pending`, or failed) at once, instead of on the member's first
|
|
2214
|
+
* post. Never undoes their off switch (409 `network_off`) or a private profile's rule (409 `private_profile`): use
|
|
2215
|
+
* `setNetwork` for those. Over the setup budget it answers `pending` with `retryAt`. Needs `users:write`.
|
|
2216
|
+
*/
|
|
2217
|
+
activateNetwork(network: NativeNetwork): Promise<SingleResponse<NativeNetworkActivation>>;
|
|
2218
|
+
/**
|
|
2219
|
+
* The member's switch for one network. `{ enabled: false, removePast: true }` turns it off and removes what was
|
|
2220
|
+
* shared there (reversible: turning it back on serves the same identity; remote followers may need to follow again).
|
|
2221
|
+
* `{ enabled: true }` turns it back on or sets it up. Refusals: 403 `network_locked`, 409 `not_rolled_out` or
|
|
2222
|
+
* `age_band_required`. Needs `users:write`.
|
|
2223
|
+
*/
|
|
2224
|
+
setNetwork(network: NativeNetwork, input: SetNativeNetworkInput): Promise<SingleResponse<NativeNetworkActivation | NativeNetworkOff>>;
|
|
2137
2225
|
/** Current Minds-hosted ActivityPub state for this app user; never provisions. */
|
|
2138
2226
|
federationIdentity(): Promise<SingleResponse<FederationIdentityView>>;
|
|
2139
2227
|
/** Explicit opt-in; the platform provisions once and never rotates keys or re-enables a disabled identity on retry. */
|
|
@@ -28,6 +28,13 @@ export const PROTOCOL_ADAPTER_MATURITIES = Object.freeze([
|
|
|
28
28
|
'beta',
|
|
29
29
|
'stable',
|
|
30
30
|
]);
|
|
31
|
+
const NATIVE_NETWORKS = ['activitypub', 'atproto', 'nostr'];
|
|
32
|
+
function nativeNetworkPath(network) {
|
|
33
|
+
if (!NATIVE_NETWORKS.includes(network)) {
|
|
34
|
+
throw new ValidationError({ type: 'validation_error', code: 'invalid_network', message: 'network must be activitypub, atproto or nostr' });
|
|
35
|
+
}
|
|
36
|
+
return `/protocols/networks/${network}`;
|
|
37
|
+
}
|
|
31
38
|
function hostedIdentityPath(id) {
|
|
32
39
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
|
|
33
40
|
throw new ValidationError({ type: 'validation_error', code: 'invalid_identity_id', message: 'A valid hosted identity connection ID is required' });
|
|
@@ -42,6 +49,19 @@ function hostedIdentityPath(id) {
|
|
|
42
49
|
export function viralItemHasVideo(item) {
|
|
43
50
|
return item.post.media.some(media => media.type === 'video');
|
|
44
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* A followed account's status on a Following page, in the server's order (SocialFollowingFeed.ts
|
|
54
|
+
* FOLLOWING_SOURCE_STATUSES). `late` (added after 0.7.82): the account's read had not answered at the page's deadline;
|
|
55
|
+
* it is read again on a later page and is never a failure. APIs before it name such an account `unavailable` with
|
|
56
|
+
* `late: true`.
|
|
57
|
+
*/
|
|
58
|
+
export const SOCIAL_FOLLOWING_SOURCE_STATUSES = Object.freeze([
|
|
59
|
+
'ok',
|
|
60
|
+
'late',
|
|
61
|
+
'unavailable',
|
|
62
|
+
'rate_limited',
|
|
63
|
+
'unsupported_protocol',
|
|
64
|
+
]);
|
|
45
65
|
/** Only the radar's own fields travel; caller scope or destination extras never reach the query string. */
|
|
46
66
|
/** Forwarded only when given, so a default read makes exactly the call it always made. */
|
|
47
67
|
const readOptions = (options) => options ? [options] : [];
|
|
@@ -488,6 +508,31 @@ export class ProtocolsResource {
|
|
|
488
508
|
hostedIdentityPath(id);
|
|
489
509
|
return this.client.delete(`/protocols/social/accounts/${encodeURIComponent(id)}`);
|
|
490
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* The signed-in member's native networks in the key's app (ActivityPub, AT Protocol, Nostr): each one's state, reason
|
|
513
|
+
* and address. Read-only: it never sets anything up. Needs `users:read`.
|
|
514
|
+
*/
|
|
515
|
+
networks() {
|
|
516
|
+
return this.client.get('/protocols/networks');
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* "Set up now": sets up a network that is `ready` (or `pending`, or failed) at once, instead of on the member's first
|
|
520
|
+
* post. Never undoes their off switch (409 `network_off`) or a private profile's rule (409 `private_profile`): use
|
|
521
|
+
* `setNetwork` for those. Over the setup budget it answers `pending` with `retryAt`. Needs `users:write`.
|
|
522
|
+
*/
|
|
523
|
+
activateNetwork(network) {
|
|
524
|
+
return this.client.post(`${nativeNetworkPath(network)}/activate`, {});
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* The member's switch for one network. `{ enabled: false, removePast: true }` turns it off and removes what was
|
|
528
|
+
* shared there (reversible: turning it back on serves the same identity; remote followers may need to follow again).
|
|
529
|
+
* `{ enabled: true }` turns it back on or sets it up. Refusals: 403 `network_locked`, 409 `not_rolled_out` or
|
|
530
|
+
* `age_band_required`. Needs `users:write`.
|
|
531
|
+
*/
|
|
532
|
+
setNetwork(network, input) {
|
|
533
|
+
const body = input.enabled ? { enabled: true } : { enabled: false, ...(input.removePast !== undefined ? { remove_past: input.removePast } : {}) };
|
|
534
|
+
return this.client.put(nativeNetworkPath(network), body);
|
|
535
|
+
}
|
|
491
536
|
/** Current Minds-hosted ActivityPub state for this app user; never provisions. */
|
|
492
537
|
federationIdentity() {
|
|
493
538
|
return this.client.get('/protocols/social/federation/identity');
|