@recursiv/sdk 0.7.72 → 0.7.73
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 +11 -0
- package/dist/resources/protocols.d.ts +15 -1
- package/dist/resources/protocols.js +12 -3
- package/llm.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -468,6 +468,17 @@ opaque; topic catalogs have no pagination and the Mastodon post window stops at
|
|
|
468
468
|
current human project-bound app key. Nostr does not advertise trends. Provider
|
|
469
469
|
failures propagate as SDK errors, and neither method ingests or publishes posts.
|
|
470
470
|
|
|
471
|
+
A clip player scoped to one network asks for its video window instead (0.7.73):
|
|
472
|
+
`getPublicTrends({ protocol: 'bluesky', limit: 25, media: 'video', cursor })`, or keyless
|
|
473
|
+
`getSharedTrends(projectId, { protocol: 'bluesky', limit: 25, media: 'video', cursor })`.
|
|
474
|
+
The answer is a `kind: 'posts'` page holding only posts that carry a video (Bluesky: What's
|
|
475
|
+
Hot's videos, one provider request a page), paged by its opaque cursor until it is `null`;
|
|
476
|
+
a page may be empty while its cursor goes on. It takes no `category` and judges no layout;
|
|
477
|
+
`compass: 'hide'` (0.7.72) leaves the political clips out of it as it does from trends.
|
|
478
|
+
A signed-out read holds only clips whose video the visitor may see, and counts the rest in
|
|
479
|
+
`mediaFilter.withheld`. A network without a video window answers 404 `unsupported_protocol`;
|
|
480
|
+
an API that predates `media` answers 400 `invalid_input`, so fall back to its trends.
|
|
481
|
+
|
|
471
482
|
### Native Nostr signing
|
|
472
483
|
|
|
473
484
|
Use `protocols.nativeNostrAccount()` to check the app's explicit native-write
|
|
@@ -1289,6 +1289,11 @@ export type SocialTrends = SocialTrendProvenance & ({
|
|
|
1289
1289
|
safetyFilter?: {
|
|
1290
1290
|
withheld: number;
|
|
1291
1291
|
};
|
|
1292
|
+
/** A `media: 'video'` page read signed out: the clips left off because the visitor may not see their video yet (counts only). */
|
|
1293
|
+
mediaFilter?: {
|
|
1294
|
+
media: 'video';
|
|
1295
|
+
withheld: number;
|
|
1296
|
+
};
|
|
1292
1297
|
}) & {
|
|
1293
1298
|
/** Present when `getPublicTrends` named a filtering `compass`: what Hide politics did to this page (counts only). */
|
|
1294
1299
|
meta?: {
|
|
@@ -1773,6 +1778,11 @@ export declare class ProtocolsResource {
|
|
|
1773
1778
|
* out the topics and posts whose cached browse category is politics, for every visitor, and the page says what it did
|
|
1774
1779
|
* in `meta.compass_filter` (`basis: 'flags'` until the operator serves visitors the full filter). An API older than
|
|
1775
1780
|
* 0.7.72 refuses `compass` here with a 400.
|
|
1781
|
+
* `media: 'video'` (0.7.73; Bluesky) reads the network's own window of video posts instead of its trends: a `posts`
|
|
1782
|
+
* page paged by `cursor`, with no category and no layout, and `compass: 'hide'` applies to it as to trends. A visitor
|
|
1783
|
+
* is served only the clips whose video they may see; `mediaFilter.withheld` counts the rest. A network without a video
|
|
1784
|
+
* window answers 404 `unsupported_protocol`, and an API that predates the parameter 400 `invalid_input`: read its
|
|
1785
|
+
* trends instead.
|
|
1776
1786
|
*/
|
|
1777
1787
|
getSharedTrends(projectId: string, params: {
|
|
1778
1788
|
protocol: string;
|
|
@@ -1782,6 +1792,7 @@ export declare class ProtocolsResource {
|
|
|
1782
1792
|
category?: string;
|
|
1783
1793
|
discovery?: boolean;
|
|
1784
1794
|
compass?: string;
|
|
1795
|
+
media?: 'video';
|
|
1785
1796
|
}): Promise<SingleResponse<SocialTrends>>;
|
|
1786
1797
|
/** Open a provider-listed topic through the same public-only app boundary, with the same safety floor. */
|
|
1787
1798
|
getSharedTrendFeed(projectId: string, params: {
|
|
@@ -1850,7 +1861,9 @@ export declare class ProtocolsResource {
|
|
|
1850
1861
|
* choice, spelled as `posts.list` takes it): Hide politics (`hide`) leaves out the topics and posts whose cached
|
|
1851
1862
|
* browse category is politics, for every member (from 0.7.72; before, only where the filter was served to them), with
|
|
1852
1863
|
* `meta.compass_filter.basis` 'flags' where the full filter is not served; a side or Mix is answered unfiltered. The
|
|
1853
|
-
* page then says what it did in `meta.compass_filter`.
|
|
1864
|
+
* page then says what it did in `meta.compass_filter`. `media: 'video'` (0.7.73; Bluesky) reads the network's own window
|
|
1865
|
+
* of video posts instead of its trends, as `getSharedTrends` does, with every video kept for a member and Hide politics
|
|
1866
|
+
* applied where asked.
|
|
1854
1867
|
*/
|
|
1855
1868
|
getPublicTrends(params: {
|
|
1856
1869
|
protocol: string;
|
|
@@ -1860,6 +1873,7 @@ export declare class ProtocolsResource {
|
|
|
1860
1873
|
category?: string;
|
|
1861
1874
|
discovery?: boolean;
|
|
1862
1875
|
compass?: string;
|
|
1876
|
+
media?: 'video';
|
|
1863
1877
|
}, options?: SocialReadOptions): Promise<SingleResponse<SocialTrends>>;
|
|
1864
1878
|
/** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. Screened like `getPublicTrends`. */
|
|
1865
1879
|
getPublicTrendFeed(params: {
|
|
@@ -128,10 +128,16 @@ export class ProtocolsResource {
|
|
|
128
128
|
* out the topics and posts whose cached browse category is politics, for every visitor, and the page says what it did
|
|
129
129
|
* in `meta.compass_filter` (`basis: 'flags'` until the operator serves visitors the full filter). An API older than
|
|
130
130
|
* 0.7.72 refuses `compass` here with a 400.
|
|
131
|
+
* `media: 'video'` (0.7.73; Bluesky) reads the network's own window of video posts instead of its trends: a `posts`
|
|
132
|
+
* page paged by `cursor`, with no category and no layout, and `compass: 'hide'` applies to it as to trends. A visitor
|
|
133
|
+
* is served only the clips whose video they may see; `mediaFilter.withheld` counts the rest. A network without a video
|
|
134
|
+
* window answers 404 `unsupported_protocol`, and an API that predates the parameter 400 `invalid_input`: read its
|
|
135
|
+
* trends instead.
|
|
131
136
|
*/
|
|
132
137
|
getSharedTrends(projectId, params) {
|
|
133
138
|
return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/trends`, { protocol: params.protocol, limit: params.limit, cursor: params.cursor,
|
|
134
|
-
includePreviews: params.includePreviews, category: params.category, discovery: params.discovery, compass: params.compass
|
|
139
|
+
includePreviews: params.includePreviews, category: params.category, discovery: params.discovery, compass: params.compass,
|
|
140
|
+
...(params.media !== undefined ? { media: params.media } : {}) });
|
|
135
141
|
}
|
|
136
142
|
/** Open a provider-listed topic through the same public-only app boundary, with the same safety floor. */
|
|
137
143
|
getSharedTrendFeed(projectId, params) {
|
|
@@ -213,14 +219,17 @@ export class ProtocolsResource {
|
|
|
213
219
|
* choice, spelled as `posts.list` takes it): Hide politics (`hide`) leaves out the topics and posts whose cached
|
|
214
220
|
* browse category is politics, for every member (from 0.7.72; before, only where the filter was served to them), with
|
|
215
221
|
* `meta.compass_filter.basis` 'flags' where the full filter is not served; a side or Mix is answered unfiltered. The
|
|
216
|
-
* page then says what it did in `meta.compass_filter`.
|
|
222
|
+
* page then says what it did in `meta.compass_filter`. `media: 'video'` (0.7.73; Bluesky) reads the network's own window
|
|
223
|
+
* of video posts instead of its trends, as `getSharedTrends` does, with every video kept for a member and Hide politics
|
|
224
|
+
* applied where asked.
|
|
217
225
|
*/
|
|
218
226
|
getPublicTrends(params, options) {
|
|
219
227
|
return this.client.get('/protocols/social/trends', { protocol: params.protocol, limit: params.limit, cursor: params.cursor,
|
|
220
228
|
...(params.includePreviews !== undefined ? { includePreviews: params.includePreviews } : {}),
|
|
221
229
|
...(params.category !== undefined ? { category: params.category } : {}),
|
|
222
230
|
...(params.discovery !== undefined ? { discovery: params.discovery } : {}),
|
|
223
|
-
...(params.compass !== undefined ? { compass: params.compass } : {})
|
|
231
|
+
...(params.compass !== undefined ? { compass: params.compass } : {}),
|
|
232
|
+
...(params.media !== undefined ? { media: params.media } : {}) }, ...readOptions(options));
|
|
224
233
|
}
|
|
225
234
|
/** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. Screened like `getPublicTrends`. */
|
|
226
235
|
getPublicTrendFeed(params, options) {
|
package/llm.md
CHANGED
|
@@ -832,7 +832,7 @@ await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorati
|
|
|
832
832
|
- Anonymous discovery: `const visitor = new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false }); const { data } = await visitor.protocols.getSharedTrends(projectId, { protocol: 'nostr', limit: 20, discovery: true })`. Use `sharedNetworks(projectId)` capabilities; open a returned topic with `getSharedTrendFeed(projectId, { protocol: 'bluesky', topic: trend.nativeId, cursor })`. Configured apps only; no account authority or writes. App moderation and per-IP/app rate limits apply. Categories and Jev layouts reuse cached judgments without initiating paid inference; handle partial categories and finite provider windows. Category depth: `r.posts.list({ category: 'science', sort: 'hot', discovery: true })` pages the hot window by offset and then by keyset (`meta.next` is `{ offset }`, `{ cursor }` or `null`; pass a cursor back as `cursor`); when a stage ends, `meta.fallover` lists the next feeds in order (`category_older`, `category_network`, `category` with `via`, `for_you`, `all`; `feed` with `ref` is declared in 0.7.60 but not sent yet, so skip kinds you cannot open). One call for a category's modules: `const { data } = await r.discovery.category({ category: 'science' })` returns `stories`, `network`, `clips`, `people`, `groups`, each `{ items, ms, reason? }`. Watch's Minds videos: `const { data, meta } = await r.discovery.videos({ limit: 12 })` returns public video posts in hot order from a list shared per app (rebuilt about once a minute), each re-checked per call for the viewer; page on `meta.next_cursor`.
|
|
833
833
|
- Progressive Discover feed (0.7.71): `const feed = r.discovery.createProgressiveFeed({ networks, category, recommend, top: { ref: 'builtin:all' }, seen, seenClusters }); const round = await feed.next(part => draw(part))` opens one `createFeed` session per source (Minds with the radar and suggestions, one per network with `public_trends`) and hands each source's page on as a `DiscoveryPart` (`{ source, round, modules, composed?, mixed? }`) the moment it lands, instead of a page that waits for its slowest network (up to 15 s); a round waits `DISCOVERY_STRAGGLER_MS` past its first answer and rejects only when every source failed. The first load is held until Minds and one other network answered (or `FIRST_SCREEN_MS`, 2.5 s, after the first answer; `firstScreenMs: VISITOR_FIRST_SCREEN_MS` for a visitor) and arrives as one `composed: true` part from `composeFirstScreen`: the top's first post when it is a playable video, else a picture lead from the best 8 by engagement (never a story, rail, video strip, column, `isSensitivePost` post or `linksAdultSite` porn link), then every network's posts by engagement (`DiscoveryEngagementMerge`) with row 2 from another network than the lead's when one is left (`FIRST_SCREEN_NETWORK_ROWS`), a story as row 3 and a rail as row 5, nothing dropped. Merge later `mixed` parts with `mergeDiscoveryArrival(rows, arriving, reachedIds)`. `top`/`seen`/`seenClusters`/`bucket` reach every session; the top is read once per open. Also `retryFailed()`, `onStatus(listener)`, `setNetworks(catalog)`; settings `perSource`, `firstScreen`, `onLanded`. For you: `createProgressiveFeed({ createFeed: o => r.discovery.createPersonalFeed(o) }, options, { perSource: false })`. A session's `seen` now also leaves seen network posts out of trend pages, previews and topic pages; in a category a topic page with nothing new ends its topic and after `DISCOVERY_EMPTY_TOPIC_PAGES` (3) in a row the network's other topics are skipped.
|
|
834
834
|
- Viral read (0.7.59; off until the server enables it for the app): `const { data } = await r.protocols.getPublicViral({ media: 'video', category: 'science', limit: 20 })` (member key, users:read + posts:read, never cached) or keyless `visitor.protocols.getSharedViral(projectId, { media: 'video', compass: 'hide' })` (visitors send compass `all` or `hide` only). Params, all optional: `network` (all, minds, bluesky, activitypub, nostr), `category`, `media: 'video'`, `limit` 5/10/20/40, `cursor` (the last page's; a cursor whose 30-minute build expired ends the list), `compass`. Each item: `key` (stable; drop ones already shown), `protocol`, `post` (checking `protocol` narrows it: `ViralMindsCard` for minds, open at /post/:id; otherwise a `SocialPost`), `label` viral/rising/hot, `score`, `rank`, `category`, `velocity` (show counts with `countsReportedBy` as reported, never verified), `spread`. A visitor's read serves media only where the server cleared it, so even with `media: 'video'` some items arrive with the video withheld (empty `media`): `import { viralItemHasVideo } from '@recursiv/sdk'` and keep `data.items.filter(viralItemHasVideo)` before a vertical video player. `status` ok/building/screening/off: off resolves with no items, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401/403 (never off), so catch it and skip that step.
|
|
835
|
-
- Public trends: require `public_trends` from `publicNetworks()`, then `getPublicTrends({ protocol, limit?, cursor?, compass? })` (`compass: 'hide'` leaves out politics, 0.7.62); render `kind: topics` as source topics and `kind: posts` as source posts. With `trend_feed`, open only a returned current topic using `getPublicTrendFeed({ protocol, topic: trend.nativeId, limit?, cursor? })`. Show provider, observedAt and provider_ranked_window coverage; null rankingWindow is unknown, not a daily/global ranking. Preserve source order, canonical IDs and readRef, and opaque cursors. Topic catalogs do not paginate; Mastodon trends stop at 200 source positions. Both methods require users:read plus posts:read on the current human app key; Nostr trends are unsupported. Propagate errors; trend_unavailable means refresh the topic list. No ingestion or native writes.
|
|
835
|
+
- Public trends: require `public_trends` from `publicNetworks()`, then `getPublicTrends({ protocol, limit?, cursor?, compass?, media? })` (`compass: 'hide'` leaves out politics, 0.7.62; `media: 'video'`, 0.7.73, Bluesky: the network's own window of video posts, a `kind: posts` page paged by its cursor, no category or layout, 404 `unsupported_protocol` for a network without one, 400 on an API that predates it; keyless `getSharedTrends(projectId, { ..., media: 'video' })` holds only clips a visitor may see and counts the rest in `mediaFilter.withheld`); render `kind: topics` as source topics and `kind: posts` as source posts. With `trend_feed`, open only a returned current topic using `getPublicTrendFeed({ protocol, topic: trend.nativeId, limit?, cursor? })`. Show provider, observedAt and provider_ranked_window coverage; null rankingWindow is unknown, not a daily/global ranking. Preserve source order, canonical IDs and readRef, and opaque cursors. Topic catalogs do not paginate; Mastodon trends stop at 200 source positions. Both methods require users:read plus posts:read on the current human app key; Nostr trends are unsupported. Propagate errors; trend_unavailable means refresh the topic list. No ingestion or native writes.
|
|
836
836
|
- One outlet count per story (0.7.71): a radar story (list, story read, archive), a World Today story (`outlets`, with `outlet_count` its total and `lane_mix` its lanes), a category strip story, a `feeds.top`/`feeds.stories` story and a search story carry `outlets: { total, mainstream, independent, other }`, the distinct outlets of the articles the story lists (an allow-listed outlet once, an unlisted host in `other`; the buckets sum to `total`). Show `outlets.total`; `sources.outlet` counts articles. `DiscoveryCluster.outletCount` reads it. An older server omits it.
|
|
837
837
|
- Political stories (0.7.67): every radar story (`getPublicRadar`, `getSharedRadar`, a story read) and every World Today story (`r.curator.today()`) carries `political`: true when a quarter of its judged posts are politics, its theme or topic is politics, or its headline or summary names an election, a war or troop deployment, a court or justice, a legislature, or a party or politician. `compass: 'hide'` leaves those out whatever their theme; `r.curator.today({ compass: 'hide' })` does the same to the front page, the next story leads, and `meta.compass_filter` counts what was left out. A story from `r.feeds.stories()` or a `r.feeds.top()` lead carries `political` too, and both reads, and `r.curator.forYouWorld()`, leave political stories out under `compass: 'hide'` whatever their theme. An older server omits `political`: decide by `theme` as before.
|
|
838
838
|
- Hide politics as a floor (0.7.72): the radar, `r.curator.today()`, `getPublicTrends`, keyless `visitor.protocols.getSharedTrends(projectId, { protocol, compass: 'hide' })` (visitors send `all` or `hide` only; an older API answers 400 to `compass` there), the viral read, `r.discovery.videos` and a visitor's Discover posts apply `compass: 'hide'` for every caller. Where the operator does not serve the caller the full filter, the answer's `meta.compass_filter` is `{ status: 'applied', basis: 'flags', ... }`: left out by the server's own `political` flags and browse categories, no judgment read. Treat `applied` with or without `basis` as filtered. Choose sides and Mix sides still answer `unavailable` until the operator serves them, and the catalog's `compass_filter` capability still says when to offer Politics in Filters.
|