@recursiv/sdk 0.7.123 → 0.7.195

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.
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from '../client.js';
2
- import type { PaginatedResponse, SingleResponse } from '../types.js';
2
+ import type { PaginatedResponse, Post, SingleResponse } from '../types.js';
3
3
  /** Campaign lifecycle axis. */
4
4
  export type BoostStatus = 'active' | 'paused' | 'completed' | 'refunded';
5
5
  /** Campaign moderation axis. A campaign delivers only when `active` AND `approved`. */
@@ -59,12 +59,30 @@ export interface BoostPlanCredit {
59
59
  */
60
60
  owed_micros?: number;
61
61
  }
62
+ /** Paying for Boost with tokens. */
63
+ export interface BoostTokenTopupInfo {
64
+ /** False until the platform sets the price of a token. */
65
+ available: boolean;
66
+ /** Micro-USD of Boost balance one token buys; null when unavailable. */
67
+ usd_micros_per_token: number | null;
68
+ /** Most whole tokens one conversion takes. */
69
+ max_tokens: number;
70
+ }
71
+ export interface BoostTokenTopupResult {
72
+ /** False when this request_id was already credited: nothing new was written. */
73
+ applied: boolean;
74
+ tokens: number;
75
+ credited_micros: number;
76
+ balance_micros: number;
77
+ }
62
78
  export interface BoostBalanceResponse extends SingleResponse<BoostBalance> {
63
79
  /** `plan_credit` is null when the app's plans include no boost credit (absent from older servers). */
64
80
  meta: {
65
81
  min_cpm_micros: number;
66
82
  topup?: BoostTopupLimits;
67
83
  plan_credit?: BoostPlanCredit | null;
84
+ /** Absent from older servers. */
85
+ token_topup?: BoostTokenTopupInfo;
68
86
  };
69
87
  }
70
88
  export interface BoostCampaignResponse extends SingleResponse<BoostCampaign> {
@@ -80,6 +98,27 @@ export interface BoostEventResult {
80
98
  spent_micros: number;
81
99
  impressions_served: number;
82
100
  }
101
+ /** One UTC day of a campaign's results. */
102
+ export interface BoostDailyStats {
103
+ /** `YYYY-MM-DD`, UTC. */
104
+ date: string;
105
+ impressions: number;
106
+ clicks: number;
107
+ }
108
+ /** What a campaign delivered: totals and a per-day series (days with no events are absent). */
109
+ export interface BoostCampaignStats {
110
+ campaign: BoostCampaign;
111
+ totals: {
112
+ impressions: number;
113
+ clicks: number;
114
+ /** clicks / impressions, or null before the first view. */
115
+ click_rate: number | null;
116
+ spent_micros: number;
117
+ /** Spend per 1,000 views in micro-USD, or null before the first view. */
118
+ cost_per_1000_micros: number | null;
119
+ };
120
+ daily: BoostDailyStats[];
121
+ }
83
122
  export interface BoostTopupCheckout {
84
123
  /** Stripe Checkout URL to open. */
85
124
  url: string;
@@ -92,6 +131,47 @@ export interface BoostTopupConfirmation {
92
131
  credited_micros: number;
93
132
  balance_micros: number | null;
94
133
  }
134
+ /** A feed whose boosts the app splices into its pages itself: All, a category, or the big-screen grid of either. */
135
+ export type BoostSlotSurface = 'all' | 'category' | 'grid';
136
+ export interface BoostSlotsParams {
137
+ surface: BoostSlotSurface;
138
+ /** A browse category id: required for `category`, allowed for `grid`, refused for `all`. */
139
+ category?: string;
140
+ /** How many posts the page has before its boosts (1..50, default 20). */
141
+ limit?: number;
142
+ /** Up to 50 post ids already on the page (and the originals they carry): never served as a boost. */
143
+ exclude?: readonly string[];
144
+ /** The Politics choice the page is served under: under one that leaves posts out, only boosts judged not political. */
145
+ compass?: string;
146
+ }
147
+ export interface PublicBoostSlotsParams extends BoostSlotsParams {
148
+ /** The public app whose feed it is. */
149
+ projectId: string;
150
+ }
151
+ /** One boost of a page: where it stands in the page as served, and the boosted post (labelled `boosted: true`). */
152
+ export interface BoostSlotItem {
153
+ place: number;
154
+ post: Post;
155
+ }
156
+ export interface BoostSlotsResponse {
157
+ data: BoostSlotItem[];
158
+ meta: {
159
+ surface: BoostSlotSurface;
160
+ category: string | null;
161
+ limit: number;
162
+ /** One boost per `every` posts. */
163
+ every: number;
164
+ /** No boost before this place (0-based): the first two places are never a boost. */
165
+ first: number;
166
+ /** Why there are none: the key is bound to no app, or (a visitor's read) the app is not public. */
167
+ reason?: 'no_app' | 'not_public';
168
+ };
169
+ }
170
+ /**
171
+ * A page with its boosts in their places: each slot's post goes in at its `place`, in order, and one whose place the
172
+ * page does not reach is left out, so a short page never pulls a boost forward. The page's own posts keep their order.
173
+ */
174
+ export declare function spliceBoostSlots<T>(page: readonly T[], slots: readonly BoostSlotItem[]): Array<T | Post>;
95
175
  /**
96
176
  * Boost: pay to promote your own post into the For You feed. CPM campaigns
97
177
  * funded from a prepaid balance with an append-only ledger. Top-ups run on the
@@ -126,6 +206,35 @@ export declare class BoostsResource {
126
206
  resume(campaignId: string): Promise<BoostCampaignResponse>;
127
207
  /** Cancel a campaign and refund its unspent budget to your balance (`meta.refunded_micros`). */
128
208
  cancel(campaignId: string): Promise<BoostCampaignResponse>;
209
+ /**
210
+ * One page's boosts for a feed the app splices them into itself (All, a category, or the big-screen grid), read beside
211
+ * the page: pass the page's own post count as `limit` and its post ids as `exclude`, then put each item's post in at
212
+ * its `place` (`spliceBoostSlots`). Each post is labelled `boosted: true` with `boost_campaign_id` and
213
+ * `boost_disclosure`; report what was seen with `recordEvent`. Never the member's own campaign, one they hid or one
214
+ * walled from them. Watch's boosts come on its own pages.
215
+ */
216
+ slots(params: BoostSlotsParams, options?: {
217
+ retry?: boolean;
218
+ }): Promise<BoostSlotsResponse>;
219
+ /**
220
+ * A visitor's boosts for one page of a public app's feed (tRPC `publicDiscovery.boostSlots`), as `slots` answers a
221
+ * member: read with no credential at all (no key, no cookie: `credentials: 'omit'`). An app that is not public answers
222
+ * none (`meta.reason` `not_public`).
223
+ */
224
+ publicSlots(params: PublicBoostSlotsParams, options?: {
225
+ timeoutMs?: number;
226
+ }): Promise<BoostSlotsResponse>;
227
+ /** Results for one of your campaigns: views, clicks, click rate, spend and a daily series. */
228
+ stats(campaignId: string): Promise<SingleResponse<BoostCampaignStats>>;
229
+ /**
230
+ * Turn whole tokens into Boost balance at the platform's price (`balance().meta.token_topup`).
231
+ * `request_id` (8 to 100 of A-Z a-z 0-9 - _) makes a retry safe: the same id debits once.
232
+ * Not retried automatically.
233
+ */
234
+ topupWithTokens(input: {
235
+ tokens: number;
236
+ request_id: string;
237
+ }): Promise<SingleResponse<BoostTokenTopupResult>>;
129
238
  /** Report an impression or click on a boosted feed entry. Idempotent per viewer per UTC day. */
130
239
  recordEvent(campaignId: string, kind: 'impression' | 'click'): Promise<SingleResponse<BoostEventResult>>;
131
240
  /**
@@ -1,3 +1,17 @@
1
+ import { NotFoundError, RecursivError } from '../errors.js';
2
+ /**
3
+ * A page with its boosts in their places: each slot's post goes in at its `place`, in order, and one whose place the
4
+ * page does not reach is left out, so a short page never pulls a boost forward. The page's own posts keep their order.
5
+ */
6
+ export function spliceBoostSlots(page, slots) {
7
+ const out = [...page];
8
+ for (const slot of [...slots].sort((a, b) => a.place - b.place)) {
9
+ if (!Number.isInteger(slot.place) || slot.place < 0 || slot.place > out.length)
10
+ continue;
11
+ out.splice(slot.place, 0, slot.post);
12
+ }
13
+ return out;
14
+ }
1
15
  /**
2
16
  * Boost: pay to promote your own post into the For You feed. CPM campaigns
3
17
  * funded from a prepaid balance with an append-only ledger. Top-ups run on the
@@ -39,6 +53,72 @@ export class BoostsResource {
39
53
  cancel(campaignId) {
40
54
  return this.client.post(`/boosts/${encodeURIComponent(campaignId)}/cancel`, {});
41
55
  }
56
+ /**
57
+ * One page's boosts for a feed the app splices them into itself (All, a category, or the big-screen grid), read beside
58
+ * the page: pass the page's own post count as `limit` and its post ids as `exclude`, then put each item's post in at
59
+ * its `place` (`spliceBoostSlots`). Each post is labelled `boosted: true` with `boost_campaign_id` and
60
+ * `boost_disclosure`; report what was seen with `recordEvent`. Never the member's own campaign, one they hid or one
61
+ * walled from them. Watch's boosts come on its own pages.
62
+ */
63
+ slots(params, options) {
64
+ return this.client.get('/boosts/slots', {
65
+ surface: params.surface, category: params.category, limit: params.limit,
66
+ exclude: params.exclude?.length ? params.exclude.join(',') : undefined, compass: params.compass,
67
+ }, options);
68
+ }
69
+ /**
70
+ * A visitor's boosts for one page of a public app's feed (tRPC `publicDiscovery.boostSlots`), as `slots` answers a
71
+ * member: read with no credential at all (no key, no cookie: `credentials: 'omit'`). An app that is not public answers
72
+ * none (`meta.reason` `not_public`).
73
+ */
74
+ async publicSlots(params, options) {
75
+ const input = { projectId: params.projectId, surface: params.surface, ...(params.category ? { category: params.category } : {}),
76
+ ...(params.limit !== undefined ? { limit: params.limit } : {}), ...(params.exclude?.length ? { exclude: [...params.exclude] } : {}),
77
+ ...(params.compass ? { compass: params.compass } : {}) };
78
+ const url = `${this.client.baseOrigin}/api/trpc/publicDiscovery.boostSlots?input=${encodeURIComponent(JSON.stringify(input))}`;
79
+ const controller = new AbortController();
80
+ const timer = setTimeout(() => controller.abort(), options?.timeoutMs ?? this.client.requestTimeoutMs);
81
+ try {
82
+ let response;
83
+ try {
84
+ response = await fetch(url, { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'omit', signal: controller.signal });
85
+ }
86
+ catch (error) {
87
+ throw new RecursivError(0, { type: 'api_error', code: controller.signal.aborted ? 'timeout' : 'network_error',
88
+ message: controller.signal.aborted ? 'The visitor boost slots did not answer in time.' : `The visitor boost slots could not be reached (${error instanceof Error ? error.message : String(error)}).` });
89
+ }
90
+ let body = null;
91
+ try {
92
+ body = await response.json();
93
+ }
94
+ catch {
95
+ body = null;
96
+ }
97
+ const data = body?.result?.data;
98
+ if (response.ok && data && typeof data === 'object')
99
+ return data;
100
+ const error = body?.error && typeof body.error === 'object' ? body.error : undefined;
101
+ const said = [error?.json?.message, error?.message].find((value) => typeof value === 'string' && value.length > 0);
102
+ const code = typeof error?.data?.code === 'string' ? error.data.code.toLowerCase() : 'unknown_error';
103
+ const failure = { type: response.status === 404 ? 'not_found_error' : 'api_error', code, message: said ?? `HTTP ${response.status}` };
104
+ throw response.status === 404 ? new NotFoundError(failure) : new RecursivError(response.ok ? 502 : response.status, failure);
105
+ }
106
+ finally {
107
+ clearTimeout(timer);
108
+ }
109
+ }
110
+ /** Results for one of your campaigns: views, clicks, click rate, spend and a daily series. */
111
+ stats(campaignId) {
112
+ return this.client.get(`/boosts/${encodeURIComponent(campaignId)}/stats`);
113
+ }
114
+ /**
115
+ * Turn whole tokens into Boost balance at the platform's price (`balance().meta.token_topup`).
116
+ * `request_id` (8 to 100 of A-Z a-z 0-9 - _) makes a retry safe: the same id debits once.
117
+ * Not retried automatically.
118
+ */
119
+ topupWithTokens(input) {
120
+ return this.client.post('/boosts/topup/tokens', input, { retry: false });
121
+ }
42
122
  /** Report an impression or click on a boosted feed entry. Idempotent per viewer per UTC day. */
43
123
  recordEvent(campaignId, kind) {
44
124
  return this.client.post(`/boosts/${encodeURIComponent(campaignId)}/events`, { kind });
@@ -43,6 +43,10 @@ export interface DeploymentReleaseInput {
43
43
  run_id: number;
44
44
  attempt: number;
45
45
  }
46
+ export interface DeploymentReleaseReservationInput extends DeploymentReleaseInput {
47
+ /** Optional initial-run GitHub OIDC proof. Requires compatible server support; never log it or retry a refused proof. */
48
+ coordinator_identity_token?: string;
49
+ }
46
50
  export interface DeploymentRelease {
47
51
  deployment_id: string;
48
52
  status: string;
@@ -183,7 +187,7 @@ export declare class DeploymentsResource {
183
187
  private client;
184
188
  constructor(client: HttpClient);
185
189
  /** Atomically block competing production deployments before promoting the API. Retry explicitly with the SAME capability. */
186
- reserveRelease(input: DeploymentReleaseInput): Promise<SingleResponse<DeploymentRelease>>;
190
+ reserveRelease(input: DeploymentReleaseReservationInput): Promise<SingleResponse<DeploymentRelease>>;
187
191
  getRelease(deploymentId: string, projectId: string): Promise<SingleResponse<DeploymentRelease>>;
188
192
  /** Mark immediately before API mutation, then only after the existing API health gates pass. */
189
193
  advanceRelease(deploymentId: string, input: DeploymentReleaseOperation & {
@@ -175,8 +175,11 @@ export interface CategorySurfaceSection<T> {
175
175
  ms: number;
176
176
  reason?: string;
177
177
  }
178
- /** A radar cluster themed into the category, with up to six members (headline or excerpt and link only). */
179
- export type CategoryStory = Pick<SocialRadar['clusters'][number], 'id' | 'label' | 'kind' | 'theme' | 'sources' | 'corroboration' | 'laneMix' | 'outlets' | 'velocity' | 'media' | 'freshness' | 'score' | 'members'>;
178
+ /**
179
+ * A radar cluster themed into the category, with up to six members (headline or excerpt and link only). `political`
180
+ * (0.7.165) is the radar's own mark, what Hide politics reads; absent from an older server or a copy cached before it.
181
+ */
182
+ export type CategoryStory = Pick<SocialRadar['clusters'][number], 'id' | 'label' | 'kind' | 'theme' | 'political' | 'sources' | 'corroboration' | 'laneMix' | 'outlets' | 'velocity' | 'media' | 'freshness' | 'score' | 'members'>;
180
183
  export interface CategoryClip {
181
184
  id: string;
182
185
  title: string | null;
@@ -27,12 +27,14 @@ export interface CreateEvalInput {
27
27
  }
28
28
  export interface SimulateInput {
29
29
  target: EvidenceTarget;
30
- /** The batch of cases to run through the target. */
31
- cases: Array<{
30
+ /** The batch of cases to run through the target. Pass this or `eval_set_id`. */
31
+ cases?: Array<{
32
32
  input: string;
33
33
  expected?: Record<string, unknown>;
34
34
  label?: string;
35
35
  }>;
36
+ /** Run a saved set from {@link EvidenceResource.createEvalSet} instead of inline cases. */
37
+ eval_set_id?: string;
36
38
  /** Optionally attach evals for this run (also saved to the target). */
37
39
  evals?: Array<Omit<CreateEvalInput, 'target'>>;
38
40
  run_type?: 'test' | 'real';
@@ -41,6 +43,40 @@ export interface SimulateInput {
41
43
  /** Attach the run to a project so it appears there, not only on the agent. */
42
44
  project_id?: string;
43
45
  }
46
+ /** A reusable set of cases with known answers, run with `simulate({ eval_set_id })`. */
47
+ export interface CreateEvalSetInput {
48
+ name: string;
49
+ /** 1 to 50 cases. `weight` 1-100 (default 1); `trap` marks a case built to catch a wrong answer. */
50
+ cases: Array<{
51
+ input: string;
52
+ expected?: Record<string, unknown>;
53
+ weight?: number;
54
+ trap?: boolean;
55
+ label?: string;
56
+ }>;
57
+ description?: string;
58
+ scoring_mode?: 'exact' | 'judge';
59
+ /** For judge scoring: what a good answer looks like. */
60
+ rubric?: string;
61
+ /** Defaults to the key's organization; omit both for a personal set. */
62
+ organization_id?: string;
63
+ project_id?: string;
64
+ }
65
+ export interface EvalSetRow {
66
+ id: string;
67
+ name: string;
68
+ description: string | null;
69
+ scoringMode: string;
70
+ organizationId: string | null;
71
+ projectId: string | null;
72
+ createdAt: string;
73
+ }
74
+ /** A run's result matrix as a file body. */
75
+ export interface EvalRunExport {
76
+ filename: string;
77
+ mimeType: string;
78
+ content: string;
79
+ }
44
80
  export interface EvalRunRow {
45
81
  id: string;
46
82
  targetType: string;
@@ -167,6 +203,25 @@ export declare class EvidenceResource {
167
203
  reason: string;
168
204
  }>;
169
205
  }>>;
206
+ /**
207
+ * Save a reusable set of cases with known answers. Run it as often as you
208
+ * like with `simulate({ target, eval_set_id })`, so runs compare like with like.
209
+ */
210
+ createEvalSet(input: CreateEvalSetInput): Promise<SingleResponse<{
211
+ eval_set_id: string;
212
+ case_count: number;
213
+ }>>;
214
+ /**
215
+ * The saved sets you can run: an organization's (the key's own by default),
216
+ * or, for a personal key with no organization, the ones you made.
217
+ */
218
+ listEvalSets(opts?: {
219
+ organizationId?: string;
220
+ }): Promise<{
221
+ data: EvalSetRow[];
222
+ }>;
223
+ /** A run's result matrix as CSV (default) or JSON, for offline analysis. */
224
+ exportRun(runId: string, format?: 'csv' | 'json'): Promise<SingleResponse<EvalRunExport>>;
170
225
  /** Recent runs for a target. */
171
226
  listRuns(target: EvidenceTarget, limit?: number): Promise<{
172
227
  data: EvalRunRow[];
@@ -26,6 +26,25 @@ export class EvidenceResource {
26
26
  runTaskChecks(taskId) {
27
27
  return this.client.post('/evidence/task-checks/run', { task_id: taskId });
28
28
  }
29
+ /**
30
+ * Save a reusable set of cases with known answers. Run it as often as you
31
+ * like with `simulate({ target, eval_set_id })`, so runs compare like with like.
32
+ */
33
+ createEvalSet(input) {
34
+ return this.client.post('/evidence/eval-sets', input);
35
+ }
36
+ /**
37
+ * The saved sets you can run: an organization's (the key's own by default),
38
+ * or, for a personal key with no organization, the ones you made.
39
+ */
40
+ listEvalSets(opts = {}) {
41
+ const query = opts.organizationId ? `?organization_id=${encodeURIComponent(opts.organizationId)}` : '';
42
+ return this.client.get(`/evidence/eval-sets${query}`);
43
+ }
44
+ /** A run's result matrix as CSV (default) or JSON, for offline analysis. */
45
+ exportRun(runId, format = 'csv') {
46
+ return this.client.get(`/evidence/runs/${runId}/export?format=${format}`);
47
+ }
29
48
  /** Recent runs for a target. */
30
49
  listRuns(target, limit = 20) {
31
50
  return this.client.get(`/evidence/runs?target_type=${target.type}&target_id=${encodeURIComponent(target.id)}&limit=${limit}`);
@@ -160,6 +160,57 @@ export interface FeedTopResponse {
160
160
  lead: FeedStory | null;
161
161
  meta: FeedTopMeta;
162
162
  }
163
+ /** The networks a firehose item comes from: Minds, and the open networks the top's lanes carry. */
164
+ export type FeedFirehoseNetwork = 'minds' | 'bluesky' | 'activitypub' | 'nostr';
165
+ /**
166
+ * One firehose item (0.7.195): an item of the top of a feed, with its network, its state, when it was published and when
167
+ * the server chose it. An open-network post carries its `card` to draw (no URL on its network: open it by `target.path`).
168
+ */
169
+ export interface FeedFirehoseItem extends FeedTopItem {
170
+ network: FeedFirehoseNetwork;
171
+ /** 'breaking' (its radar story broke in the last 6 hours) or 'viral'; at most one, else null. */
172
+ state: 'breaking' | 'viral' | null;
173
+ /** When the post was published (ISO). */
174
+ publishedAt: string;
175
+ /** When the server chose it for this answer (ISO, the server's clock). */
176
+ chosenAt: string;
177
+ /** An open-network post's card. */
178
+ card?: FeedTopNetworkCard;
179
+ }
180
+ export interface FeedFirehoseMeta {
181
+ /** The soonest to read again, in ms: longer when the answer was short or the server is busy. */
182
+ refillAfterMs: number;
183
+ /** The oldest shared snapshot the stream's pool was composed from (ISO). */
184
+ poolBuiltAt: string;
185
+ poolSize: number;
186
+ /** 'safe' for a member on safe defaults, 'open' for one who turned them off. */
187
+ safety: 'safe' | 'open';
188
+ /** Fewer items than asked for: supply is thin, so read again after `refillAfterMs`. */
189
+ short: boolean;
190
+ networks: Record<'bluesky' | 'activitypub' | 'nostr', 'ok' | 'unavailable'>;
191
+ /**
192
+ * Whether the server kept this session from repeating: 'on'; 'full' when the session's record is at its cap; and
193
+ * 'unavailable' when it could not (guard against repeats in the app, over more of what it showed).
194
+ */
195
+ sent: 'on' | 'full' | 'unavailable';
196
+ compass_filter?: CompassFilterMeta;
197
+ }
198
+ /** GET /feeds/firehose. `retract` names refs the session was sent that were taken down since: remove them wherever they are held. */
199
+ export interface FeedFirehoseResponse {
200
+ items: FeedFirehoseItem[];
201
+ retract: string[];
202
+ meta: FeedFirehoseMeta;
203
+ }
204
+ export interface FeedFirehoseParams {
205
+ /** Opaque per app session ([A-Za-z0-9_-], up to 64), required: nothing is sent twice within one session. */
206
+ session: string;
207
+ /** 1..25, default 20. */
208
+ limit?: number;
209
+ /** A Politics choice, as `posts.list` takes it. */
210
+ compass?: string;
211
+ /** Only one network's items: `all` (default), `minds`, `bluesky`, `activitypub` or `nostr`. */
212
+ network?: 'all' | FeedFirehoseNetwork;
213
+ }
163
214
  export interface FeedStoriesResponse {
164
215
  data: FeedStory[];
165
216
  meta: {
@@ -230,6 +281,18 @@ export declare class FeedsResource {
230
281
  top(params: FeedTopParams, options?: {
231
282
  retry?: boolean;
232
283
  }): Promise<FeedTopResponse>;
284
+ /**
285
+ * The firehose for the signed-in member, with their own key of the app (0.7.195): the next items of their live stream of
286
+ * the most engaged, viral and breaking posts of Minds and the open networks, every one re-checked for them on the
287
+ * server, none sent twice in one `session`. Reveal them one at a time; read again no sooner than `meta.refillAfterMs`.
288
+ * Off (404) until the app's operator turns it on; 429 `rate_limited` and 503 `firehose_unavailable` carry Retry-After.
289
+ * A Mix or Choose sides `compass` spends one of the member's hourly sided Politics reads at most once per `session` and
290
+ * choice in ten minutes; 429 `compass_rate_limited` when they are spent (choose Everything or Hide politics, or wait).
291
+ * Always private: never cached for anyone else.
292
+ */
293
+ firehose(params: FeedFirehoseParams, options?: {
294
+ retry?: boolean;
295
+ }): Promise<FeedFirehoseResponse>;
233
296
  /** The stories the member should meet next (the story tail), by the same ranker as the top's lead. */
234
297
  stories(params?: FeedStoriesParams, options?: {
235
298
  retry?: boolean;
@@ -14,6 +14,18 @@ export class FeedsResource {
14
14
  top(params, options) {
15
15
  return this.http.get('/feeds/top', { ref: params.ref, limit: params.limit, session: params.session, compass: params.compass }, options);
16
16
  }
17
+ /**
18
+ * The firehose for the signed-in member, with their own key of the app (0.7.195): the next items of their live stream of
19
+ * the most engaged, viral and breaking posts of Minds and the open networks, every one re-checked for them on the
20
+ * server, none sent twice in one `session`. Reveal them one at a time; read again no sooner than `meta.refillAfterMs`.
21
+ * Off (404) until the app's operator turns it on; 429 `rate_limited` and 503 `firehose_unavailable` carry Retry-After.
22
+ * A Mix or Choose sides `compass` spends one of the member's hourly sided Politics reads at most once per `session` and
23
+ * choice in ten minutes; 429 `compass_rate_limited` when they are spent (choose Everything or Hide politics, or wait).
24
+ * Always private: never cached for anyone else.
25
+ */
26
+ firehose(params, options) {
27
+ return this.http.get('/feeds/firehose', { session: params.session, limit: params.limit, compass: params.compass, network: params.network }, options);
28
+ }
17
29
  /** The stories the member should meet next (the story tail), by the same ranker as the top's lead. */
18
30
  stories(params = {}, options) {
19
31
  return this.http.get('/feeds/stories', { after: params.after, limit: params.limit, compass: params.compass }, options);
@@ -0,0 +1,24 @@
1
+ /** A starting intent; private_ai records interest without changing chat protection or providers. */
2
+ export type ExperienceGoal = 'build' | 'create' | 'explore' | 'connect' | 'private_ai' | 'earn';
3
+ export type ExperienceCategory = 'news' | 'technology' | 'science' | 'business' | 'sports' | 'entertainment' | 'gaming' | 'life' | 'politics' | 'ai' | 'crypto' | 'finance' | 'space' | 'nature' | 'health' | 'fitness' | 'music' | 'film-tv' | 'art' | 'photography' | 'books' | 'food' | 'travel' | 'animals' | 'education' | 'comedy';
4
+ export interface MemberExperience {
5
+ goals: ExperienceGoal[];
6
+ interests: ExperienceCategory[];
7
+ /** These categories cannot become the saved starting point; this is not a whole-feed filter. */
8
+ avoid_categories: ExperienceCategory[];
9
+ version: number;
10
+ can_undo: boolean;
11
+ applied_scope: 'saved_starting_point';
12
+ /** A trusted relative route into existing category results; null without a non-avoided interest. */
13
+ discovery: {
14
+ href: string;
15
+ category: ExperienceCategory;
16
+ } | null;
17
+ }
18
+ export interface UpdateMemberExperienceInput {
19
+ /** Required current version from getExperience; a stale save is refused with 409 stale_version. */
20
+ version: number;
21
+ goals?: ExperienceGoal[];
22
+ interests?: ExperienceCategory[];
23
+ avoid_categories?: ExperienceCategory[];
24
+ }
@@ -0,0 +1 @@
1
+ export {};