@omelhorsite/sdk 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -130
- package/dist/index.js +1045 -1242
- package/dist/types/index.d.ts +1 -0
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/resources/account.d.ts +5 -2
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +17 -13
- package/dist/types/resources/auth/sessions.d.ts +6 -9
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +41 -236
- package/dist/types/resources/jobs.d.ts +7 -4
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +20 -22
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +27 -53
- package/dist/types/resources/music/artists.d.ts +3 -1
- package/dist/types/resources/music/imports.d.ts +9 -8
- package/dist/types/resources/music/playlists.d.ts +8 -7
- package/dist/types/resources/music/songs.d.ts +5 -21
- package/dist/types/resources/shortLinks.d.ts +5 -2
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +21 -3
- package/dist/types/resources/storage.d.ts +24 -6
- package/dist/types/resources/tickets.d.ts +19 -10
- package/package.json +1 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/** The feedback box and its admin queue. */
|
|
2
|
+
import { Resource } from "../../http";
|
|
3
|
+
import type { ListParams } from "../../listing";
|
|
4
|
+
import type { FileOutput, Id, Paginated, RequestOptions, Timestamp } from "../../types";
|
|
5
|
+
/** Filter columns of `GET /feedbacks`, on top of {@link BASE_FILTER_COLUMNS}. */
|
|
6
|
+
export declare const FEEDBACK_FILTER_COLUMNS: readonly ["status", "user_id"];
|
|
7
|
+
/** Filters for {@link FeedbacksNamespace.list}. */
|
|
8
|
+
export type ListFeedbacksParams = ListParams<(typeof FEEDBACK_FILTER_COLUMNS)[number]>;
|
|
9
|
+
/**
|
|
10
|
+
* Primary key of a feedback report. A STRING, not an integer: `feedbacks` is
|
|
11
|
+
* one of the tables that moved to opaque random ids, and it is the only one in
|
|
12
|
+
* this file that did.
|
|
13
|
+
*/
|
|
14
|
+
export type FeedbackId = Id;
|
|
15
|
+
/** Triage state of a report. */
|
|
16
|
+
export declare const FEEDBACK_STATUSES: readonly ["new", "read", "archived"];
|
|
17
|
+
/** One of {@link FEEDBACK_STATUSES}. */
|
|
18
|
+
export type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];
|
|
19
|
+
/** Longest report the endpoint accepts, in BYTES. */
|
|
20
|
+
export declare const FEEDBACK_CONTENT_MAX_BYTES = 5000;
|
|
21
|
+
/** How many attachments survive one submission. Mirrors `MAX_ATTACHMENTS_COUNT`. */
|
|
22
|
+
export declare const FEEDBACK_MAX_ATTACHMENTS = 6;
|
|
23
|
+
/** Combined decoded size of the attachments that get stored. 10 MiB. */
|
|
24
|
+
export declare const FEEDBACK_MAX_ATTACHMENTS_TOTAL_BYTES: number;
|
|
25
|
+
/** Longest single `data:` URL the attacher will decode. 15 MiB of base64. */
|
|
26
|
+
export declare const FEEDBACK_MAX_ATTACHMENT_DATA_URL_BYTES: number;
|
|
27
|
+
/** Anonymous submissions allowed per hour per IP, before rack-attack answers 429. */
|
|
28
|
+
export declare const FEEDBACK_CREATE_RATE_LIMIT_PER_HOUR = 5;
|
|
29
|
+
/** How long an identical report from the same IP is folded into the first one. */
|
|
30
|
+
export declare const FEEDBACK_DUPLICATE_WINDOW_MS: number;
|
|
31
|
+
/** A stored attachment, as it appears on a report. Admin-visible only. */
|
|
32
|
+
export interface FeedbackAttachment {
|
|
33
|
+
/** ActiveStorage blob id. An INTEGER, and the segment `attachmentUrl` needs. */
|
|
34
|
+
readonly blob_id: number;
|
|
35
|
+
readonly filename: string;
|
|
36
|
+
readonly content_type: string;
|
|
37
|
+
readonly byte_size: number;
|
|
38
|
+
}
|
|
39
|
+
/** The submitter, when they were signed in. Carries their email, so admin-only. */
|
|
40
|
+
export interface FeedbackSubmitter {
|
|
41
|
+
readonly id: Id;
|
|
42
|
+
readonly handle: string;
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly email: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A feedback report, as an admin reads it.
|
|
48
|
+
*
|
|
49
|
+
* Nobody else ever sees this shape: `viewable_by` is `user&.admin? ? all : none`,
|
|
50
|
+
* so a non-admin's listing is empty and a non-admin's `show` is a 404. The
|
|
51
|
+
* submitter cannot read back what they sent - {@link FeedbacksNamespace.create}
|
|
52
|
+
* answers with an id and nothing else.
|
|
53
|
+
*/
|
|
54
|
+
export interface Feedback {
|
|
55
|
+
readonly id: FeedbackId;
|
|
56
|
+
/** What the person wrote. Up to {@link FEEDBACK_CONTENT_MAX_BYTES} bytes. */
|
|
57
|
+
readonly content: string;
|
|
58
|
+
readonly status: FeedbackStatus;
|
|
59
|
+
/**
|
|
60
|
+
* The three context keys the controller keeps (`path`, `source`,
|
|
61
|
+
* `user_agent`); everything else the client sent is dropped before the row
|
|
62
|
+
* is written. `{}` when nothing was sent.
|
|
63
|
+
*/
|
|
64
|
+
readonly context: Record<string, string>;
|
|
65
|
+
/** The account that submitted it, or `null` for an anonymous report. */
|
|
66
|
+
readonly user_id: Id | null;
|
|
67
|
+
/** Reply address for an anonymous report, or `null`. */
|
|
68
|
+
readonly email: string | null;
|
|
69
|
+
/**
|
|
70
|
+
* ISO country resolved from the submitter's IP by `FeedbackIntakeJob`.
|
|
71
|
+
*
|
|
72
|
+
* Written by a background job AFTER the response, so it is `null` on a row
|
|
73
|
+
* read immediately after submission and fills in a moment later. Same for
|
|
74
|
+
* {@link device_name}.
|
|
75
|
+
*/
|
|
76
|
+
readonly country: string | null;
|
|
77
|
+
/** Device name parsed out of the user agent, by the same background job. */
|
|
78
|
+
readonly device_name: string | null;
|
|
79
|
+
/** Expanded account, or `null` when the report was anonymous. */
|
|
80
|
+
readonly user: FeedbackSubmitter | null;
|
|
81
|
+
/** Screenshots, in submission order. Empty when none survived the filters. */
|
|
82
|
+
readonly attachments: FeedbackAttachment[];
|
|
83
|
+
readonly created_at: Timestamp;
|
|
84
|
+
readonly updated_at: Timestamp;
|
|
85
|
+
}
|
|
86
|
+
/** One screenshot, sent inline as a `data:` URL rather than as multipart. */
|
|
87
|
+
export interface FeedbackAttachmentInput {
|
|
88
|
+
/**
|
|
89
|
+
* A full `data:<mime>;base64,<payload>` URL. Anything that does not match
|
|
90
|
+
* that exact regex - a bare base64 string, a `data:` URL that is not base64 -
|
|
91
|
+
* is skipped in silence.
|
|
92
|
+
*/
|
|
93
|
+
readonly data_url: string;
|
|
94
|
+
/** Name to store. Sanitised server-side; defaults to `feedback-<id>-attachment-<n>.<ext>`. */
|
|
95
|
+
readonly filename?: string;
|
|
96
|
+
}
|
|
97
|
+
/** Arguments for {@link FeedbacksNamespace.create}. */
|
|
98
|
+
export interface CreateFeedbackInput {
|
|
99
|
+
/**
|
|
100
|
+
* The report. Trimmed, and rejected when blank
|
|
101
|
+
* (`400 "Feedback can't be empty"`) or over
|
|
102
|
+
* {@link FEEDBACK_CONTENT_MAX_BYTES} BYTES - bytes, not characters, so
|
|
103
|
+
* accented text runs out sooner than the number suggests
|
|
104
|
+
* (`400 "Feedback is too long"`).
|
|
105
|
+
*/
|
|
106
|
+
readonly content: string;
|
|
107
|
+
/**
|
|
108
|
+
* Reply address. Only meaningful for an anonymous report: a signed-in
|
|
109
|
+
* submitter is linked by `user_id` and their account email is what the admin
|
|
110
|
+
* sees. Validated against `URI::MailTo::EMAIL_REGEXP` when present.
|
|
111
|
+
*/
|
|
112
|
+
readonly email?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Where the report came from. Only `path`, `source` and `user_agent`
|
|
115
|
+
* survive; every other key is dropped without an error.
|
|
116
|
+
*/
|
|
117
|
+
readonly context?: {
|
|
118
|
+
readonly path?: string;
|
|
119
|
+
readonly source?: string;
|
|
120
|
+
readonly user_agent?: string;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Screenshots, at most {@link FEEDBACK_MAX_ATTACHMENTS}.
|
|
124
|
+
*
|
|
125
|
+
* Every rule here fails SILENTLY - the attacher logs and moves on, and the
|
|
126
|
+
* submission still answers `201`. An attachment is dropped when it is not a
|
|
127
|
+
* base64 `data:` URL, when its MIME type is not `image/*`, when the URL is
|
|
128
|
+
* over {@link FEEDBACK_MAX_ATTACHMENT_DATA_URL_BYTES}, or when the running
|
|
129
|
+
* decoded total passes {@link FEEDBACK_MAX_ATTACHMENTS_TOTAL_BYTES} (which
|
|
130
|
+
* drops that one AND every one after it). Anything past the sixth is
|
|
131
|
+
* discarded before the loop even starts. So do not treat a `201` as proof
|
|
132
|
+
* the screenshots arrived; only an admin reading {@link Feedback.attachments}
|
|
133
|
+
* can confirm that.
|
|
134
|
+
*
|
|
135
|
+
* Base64 is roughly 4/3 the size of the bytes, and the whole thing travels
|
|
136
|
+
* inside one JSON body: production sits behind Cloudflare's ~100 MB request
|
|
137
|
+
* cap, which rejects an oversized body with a `413` of its own before Rails
|
|
138
|
+
* sees it.
|
|
139
|
+
*/
|
|
140
|
+
readonly attachments?: readonly FeedbackAttachmentInput[];
|
|
141
|
+
/**
|
|
142
|
+
* Cloudflare Turnstile token. REQUIRED for an anonymous submission and
|
|
143
|
+
* ignored for a signed-in one.
|
|
144
|
+
*
|
|
145
|
+
* Get the site key from {@link SiteConfigNamespace.get} first. Missing is
|
|
146
|
+
* `400 "Captcha token missing"`; present but not verifying is
|
|
147
|
+
* `403 "Captcha verification failed"`. A token is single-use at Cloudflare,
|
|
148
|
+
* so it cannot be replayed - which also means an SDK-level retry of a failed
|
|
149
|
+
* anonymous submission needs a FRESH token, not the same one.
|
|
150
|
+
*/
|
|
151
|
+
readonly cf_turnstile_token?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The `feedbacks` namespace: the site's feedback box, plus its admin queue.
|
|
155
|
+
*
|
|
156
|
+
* Two audiences and one route table. {@link create} is the only thing a normal
|
|
157
|
+
* caller can reach, and it is deliberately anonymous-friendly; everything else
|
|
158
|
+
* is `before_action :require_admin!` and answers `401` with a `null` body to
|
|
159
|
+
* anyone else.
|
|
160
|
+
*/
|
|
161
|
+
export declare class FeedbacksNamespace extends Resource {
|
|
162
|
+
/**
|
|
163
|
+
* `POST /feedbacks` - submits a report. `201` with `{"id": "..."}`, which is
|
|
164
|
+
* unwrapped here to the id string.
|
|
165
|
+
*
|
|
166
|
+
* The response carries the id ALONE. There is no way to read the row back
|
|
167
|
+
* without being an admin, so the id is only useful for correlating with a
|
|
168
|
+
* support conversation.
|
|
169
|
+
*
|
|
170
|
+
* ## The ceilings, and why they are there
|
|
171
|
+
*
|
|
172
|
+
* This route has the only dedicated rack-attack bucket in this file:
|
|
173
|
+
* **{@link FEEDBACK_CREATE_RATE_LIMIT_PER_HOUR} per hour, keyed on the IP**,
|
|
174
|
+
* added after a bot pushed roughly 200 admin notification emails through it
|
|
175
|
+
* in a single burst. It is keyed on the IP for EVERY caller, signed in or
|
|
176
|
+
* not, so a shared egress address (an office, a mobile carrier's NAT, a
|
|
177
|
+
* corporate VPN) shares the budget. Over it, `429` with
|
|
178
|
+
* `{"error":"rate_limited"}`, which arrives here as an {@link OmsQuotaError}.
|
|
179
|
+
*
|
|
180
|
+
* On top of that the controller de-duplicates: the same `content` from the
|
|
181
|
+
* same IP inside {@link FEEDBACK_DUPLICATE_WINDOW_MS} returns the id of the
|
|
182
|
+
* EXISTING row with a `201` and writes nothing, attaches nothing and sends
|
|
183
|
+
* no email. So a double-submitted form is harmless, and a retry inside the
|
|
184
|
+
* window is genuinely idempotent - but note the flip side: a user who
|
|
185
|
+
* legitimately sends the same short sentence twice in three minutes gets one
|
|
186
|
+
* report, and the second submission's ATTACHMENTS are silently lost, because
|
|
187
|
+
* the de-duplication branch returns before the attacher runs.
|
|
188
|
+
*
|
|
189
|
+
* ## What happens after the 201
|
|
190
|
+
*
|
|
191
|
+
* `FeedbackIntakeJob` runs on the queue: geo-locates the IP into
|
|
192
|
+
* {@link Feedback.country}, parses the user agent into
|
|
193
|
+
* {@link Feedback.device_name}, sends one coalesced email to every admin,
|
|
194
|
+
* and fires a Discord alert. None of it blocks the response, and none of it
|
|
195
|
+
* can fail the submission.
|
|
196
|
+
*
|
|
197
|
+
* The submitter's IP and user agent are stored on the row regardless of
|
|
198
|
+
* whether they signed in. Say so in your UI if that matters.
|
|
199
|
+
*
|
|
200
|
+
* @throws {OmsApiError} 400 `"Feedback can't be empty"` / `"Feedback is too long"`
|
|
201
|
+
* / `"Captcha token missing"`; 403 `"Captcha verification failed"`.
|
|
202
|
+
* @throws {OmsQuotaError} 429 once the per-IP hourly budget is spent.
|
|
203
|
+
*/
|
|
204
|
+
create(input: CreateFeedbackInput, options?: RequestOptions): Promise<FeedbackId>;
|
|
205
|
+
/**
|
|
206
|
+
* `GET /feedbacks` - the admin triage queue. **Admin only.**
|
|
207
|
+
*
|
|
208
|
+
* A non-admin is stopped by `before_action :require_admin!` with a `401`
|
|
209
|
+
* whose body is `null` - no message to show the user, so write your own.
|
|
210
|
+
* `Feedback.viewable_by` collapsing to `none` for a non-admin is the second
|
|
211
|
+
* layer behind that, not the one you will hit.
|
|
212
|
+
*
|
|
213
|
+
* Filterable on `status` and `user_id`, plus the three defaults (`id`,
|
|
214
|
+
* `created_at`, `updated_at`). Any other key is `400`.
|
|
215
|
+
*
|
|
216
|
+
* The scope is ordered `created_at DESC` before the DSL runs, and
|
|
217
|
+
* `modifiers[order]` uses `reorder`, so passing {@link ListParams.order}
|
|
218
|
+
* REPLACES that default rather than refining it.
|
|
219
|
+
*/
|
|
220
|
+
list(params?: ListFeedbacksParams, options?: RequestOptions): Promise<Paginated<Feedback>>;
|
|
221
|
+
/**
|
|
222
|
+
* `GET /feedbacks/:id` - one report in full. **Admin only**; anybody else
|
|
223
|
+
* gets `401` with a `null` body.
|
|
224
|
+
*/
|
|
225
|
+
get(id: FeedbackId, options?: RequestOptions): Promise<Feedback>;
|
|
226
|
+
/**
|
|
227
|
+
* `PATCH /feedbacks/:id` - moves a report through triage. **Admin only.**
|
|
228
|
+
*
|
|
229
|
+
* `status` is the only writable field: `update_params :status` is the whole
|
|
230
|
+
* allowlist, so `content` and `email` cannot be edited, and a value outside
|
|
231
|
+
* {@link FEEDBACK_STATUSES} is rejected by a `before_update` hook with
|
|
232
|
+
* `400 "Invalid status"` before the model is touched.
|
|
233
|
+
*/
|
|
234
|
+
setStatus(id: FeedbackId, status: FeedbackStatus, options?: RequestOptions): Promise<Feedback>;
|
|
235
|
+
/**
|
|
236
|
+
* `DELETE /feedbacks/:id` - permanent, attachments included. `204`.
|
|
237
|
+
* **Admin only.**
|
|
238
|
+
*/
|
|
239
|
+
destroy(id: FeedbackId, options?: RequestOptions): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* `GET /feedbacks/:id/attachment/:blobId` - downloads one screenshot.
|
|
242
|
+
* **Admin only.**
|
|
243
|
+
*
|
|
244
|
+
* The endpoint answers a `302` into object storage, not the bytes, so this
|
|
245
|
+
* follows the redirect and buffers the result. That works in Bun and in
|
|
246
|
+
* React Native; in a BROWSER it is the same CORS trap `account.picture`
|
|
247
|
+
* documents - the redirect target does not accept a credentialed
|
|
248
|
+
* cross-origin request, and the fetch fails after the 302. A web client
|
|
249
|
+
* should point an `<img>` at {@link attachmentUrl} instead and let the
|
|
250
|
+
* browser follow the redirect without credentials.
|
|
251
|
+
*/
|
|
252
|
+
attachment(id: FeedbackId, blobId: number, options?: RequestOptions): Promise<FileOutput>;
|
|
253
|
+
/**
|
|
254
|
+
* The absolute URL of an attachment, for an `<img src>` or an `<a href>`.
|
|
255
|
+
*
|
|
256
|
+
* Builds the string and makes no request, so it carries whatever credential
|
|
257
|
+
* the BROWSER attaches - which for a cookie session on the API's own origin
|
|
258
|
+
* is the session cookie, and for a bearer-token client is nothing at all. A
|
|
259
|
+
* token-authenticated host has to fetch the bytes with {@link attachment}
|
|
260
|
+
* instead; there is no query-string credential this SDK will mint for you.
|
|
261
|
+
*/
|
|
262
|
+
attachmentUrl(id: FeedbackId, blobId: number): string;
|
|
263
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** The `content` namespace and everything under it. */
|
|
2
|
+
import { type ApiClient, Resource } from "../../http";
|
|
3
|
+
import { AnalysisNamespace } from "./analysis";
|
|
4
|
+
import { BlogsNamespace } from "./blogs";
|
|
5
|
+
import { FeedbacksNamespace } from "./feedbacks";
|
|
6
|
+
import { IntelNamespace } from "./intel/index";
|
|
7
|
+
import { JokesNamespace } from "./jokes";
|
|
8
|
+
import { NotificationsNamespace } from "./notifications";
|
|
9
|
+
import { ServiceUsagesNamespace } from "./serviceUsages";
|
|
10
|
+
import { ServicesStatusNamespace } from "./servicesStatus";
|
|
11
|
+
import { SiteConfigNamespace } from "./siteConfig";
|
|
12
|
+
import { SpaceInvadersNamespace } from "./spaceInvaders";
|
|
13
|
+
export * from "./analysis";
|
|
14
|
+
export * from "./blogs";
|
|
15
|
+
export * from "./feedbacks";
|
|
16
|
+
export * from "./intel/index";
|
|
17
|
+
export * from "./jokes";
|
|
18
|
+
export * from "./notifications";
|
|
19
|
+
export * from "./serviceUsages";
|
|
20
|
+
export * from "./servicesStatus";
|
|
21
|
+
export * from "./siteConfig";
|
|
22
|
+
export * from "./spaceInvaders";
|
|
23
|
+
/**
|
|
24
|
+
* The `content` namespace, reachable as `oms.content`.
|
|
25
|
+
*
|
|
26
|
+
* An umbrella over ten unrelated corners of the API. Nothing is shared between
|
|
27
|
+
* them, so mount the sub-namespaces directly if a flatter surface reads better
|
|
28
|
+
* - each one is exported on its own.
|
|
29
|
+
*/
|
|
30
|
+
export declare class ContentNamespace extends Resource {
|
|
31
|
+
/** Blogs, blog posts and subscriptions. `.posts` hangs off it. */
|
|
32
|
+
readonly blogs: BlogsNamespace;
|
|
33
|
+
/** The notification inbox. HTTP half only; the cable pushes the rest. */
|
|
34
|
+
readonly notifications: NotificationsNamespace;
|
|
35
|
+
/** The feedback box, and its admin queue. */
|
|
36
|
+
readonly feedbacks: FeedbacksNamespace;
|
|
37
|
+
/** The joke table behind the loading screens. */
|
|
38
|
+
readonly jokes: JokesNamespace;
|
|
39
|
+
/** The public config blob a client reads before it has a credential. */
|
|
40
|
+
readonly config: SiteConfigNamespace;
|
|
41
|
+
/** The status page: live probes and the 90-day uptime report. */
|
|
42
|
+
readonly status: ServicesStatusNamespace;
|
|
43
|
+
/** Per-user "which parts of the site do you open" counters. */
|
|
44
|
+
readonly serviceUsages: ServiceUsagesNamespace;
|
|
45
|
+
/** Two admin storage reports. */
|
|
46
|
+
readonly analysis: AnalysisNamespace;
|
|
47
|
+
/** The Space Invaders leaderboard. */
|
|
48
|
+
readonly spaceInvaders: SpaceInvadersNamespace;
|
|
49
|
+
/** Intel: articles, reports, sources, scripts, items, config and stats. See {@link IntelNamespace}. */
|
|
50
|
+
readonly intel: IntelNamespace;
|
|
51
|
+
constructor(http: ApiClient);
|
|
52
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/** Intel stories: the analysed, grouped, scored output. */
|
|
2
|
+
import { Resource } from "../../../http";
|
|
3
|
+
import type { ListParams } from "../../../listing";
|
|
4
|
+
import type { Id, Paginated, RequestOptions, Timestamp } from "../../../types";
|
|
5
|
+
import type { IntelArticleCategory, IntelReportKind } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* A story: several raw items about the same event, grouped, scored and
|
|
8
|
+
* categorised by the analysis pipeline.
|
|
9
|
+
*
|
|
10
|
+
* This is the shape an INDEX row has. `GET /intel_articles/:id` renders
|
|
11
|
+
* `:extended`, which is this plus four more keys - see
|
|
12
|
+
* {@link IntelArticleDetail}. The detail is always a superset, never a
|
|
13
|
+
* different record.
|
|
14
|
+
*/
|
|
15
|
+
export interface IntelArticle {
|
|
16
|
+
readonly id: Id;
|
|
17
|
+
readonly created_at: Timestamp;
|
|
18
|
+
readonly updated_at: Timestamp;
|
|
19
|
+
/** Headline the model wrote. Nullable: the column has no `NOT NULL`. */
|
|
20
|
+
readonly title: string | null;
|
|
21
|
+
/** One-paragraph summary. Nullable for the same reason. */
|
|
22
|
+
readonly summary: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* 0-10, validated `only_integer, in: 0..10`. The buckets the dashboard uses
|
|
25
|
+
* are in {@link IntelStats.by_importance} and they are NOT evenly spaced:
|
|
26
|
+
* >=9 critical, 7-8 high, 5-6 medium, 3-4 low, <3 noise.
|
|
27
|
+
*/
|
|
28
|
+
readonly importance: number;
|
|
29
|
+
/** See {@link IntelArticleCategory}. `null` when unclassified. */
|
|
30
|
+
readonly category: IntelArticleCategory | null;
|
|
31
|
+
/**
|
|
32
|
+
* Free-form tags. The column defaults to `[]`, but it is nullable, so a row
|
|
33
|
+
* written before the default landed can still hand you `null`. Do not map
|
|
34
|
+
* over it without a guard.
|
|
35
|
+
*/
|
|
36
|
+
readonly tags: string[] | null;
|
|
37
|
+
/**
|
|
38
|
+
* The `og:image` of one of the story's sources, stored RAW and uncompressed
|
|
39
|
+
* - it points at whatever news site published it, not at this API. Render it
|
|
40
|
+
* through {@link intelArticleImageUrl} rather than directly; that helper
|
|
41
|
+
* explains the trade it makes.
|
|
42
|
+
*/
|
|
43
|
+
readonly image_url: string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the web-search enrichment pass has run on this story.
|
|
46
|
+
*
|
|
47
|
+
* `false` is not a failure, it is a queue position: `AnalyzeUserJob` enriches
|
|
48
|
+
* at most three stories per run, only those at or above
|
|
49
|
+
* {@link IntelConfig.enrich_min_importance}, and only while
|
|
50
|
+
* {@link IntelConfig.web_search} is on. A low-importance story stays `false`
|
|
51
|
+
* for ever, by design.
|
|
52
|
+
*/
|
|
53
|
+
readonly enriched: boolean;
|
|
54
|
+
/** When the story was first built. */
|
|
55
|
+
readonly first_seen_at: Timestamp;
|
|
56
|
+
/** Touched every time a new item joins the story. This is the "recency" clock. */
|
|
57
|
+
readonly last_seen_at: Timestamp;
|
|
58
|
+
/**
|
|
59
|
+
* How many raw items back this story.
|
|
60
|
+
*
|
|
61
|
+
* Costs one COUNT query per row on the listing. A page of 500 stories is
|
|
62
|
+
* 500 extra queries. This is the reason to keep `pageSize` modest on
|
|
63
|
+
* {@link IntelArticlesNamespace.list}.
|
|
64
|
+
*/
|
|
65
|
+
readonly n_sources: number;
|
|
66
|
+
}
|
|
67
|
+
/** One raw item cited by a story, as `:extended` inlines it. */
|
|
68
|
+
export interface IntelArticleSourceRef {
|
|
69
|
+
/** Id of the {@link IntelItem}. Fetch the full row with `items.get(id)`. */
|
|
70
|
+
readonly id: Id;
|
|
71
|
+
/** Name of the {@link IntelSource} the item came from, or `null` if it was deleted. */
|
|
72
|
+
readonly source_name: string | null;
|
|
73
|
+
readonly title: string | null;
|
|
74
|
+
readonly url: string | null;
|
|
75
|
+
readonly published_at: Timestamp | null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A story related to this one, as `:extended` inlines it.
|
|
79
|
+
*
|
|
80
|
+
* "Related" is not "duplicate": duplicates are merged during dedup and never
|
|
81
|
+
* become two rows. `IntelArticleLink` is an undirected edge between two
|
|
82
|
+
* DISTINCT stories, which is why {@link relation} is one label describing the
|
|
83
|
+
* pair rather than a direction.
|
|
84
|
+
*/
|
|
85
|
+
export interface IntelRelatedArticleRef {
|
|
86
|
+
readonly id: Id;
|
|
87
|
+
readonly title: string | null;
|
|
88
|
+
readonly importance: number;
|
|
89
|
+
readonly category: IntelArticleCategory | null;
|
|
90
|
+
/** Free text the model wrote for the edge, e.g. a pattern name. Nullable. */
|
|
91
|
+
readonly relation: string | null;
|
|
92
|
+
}
|
|
93
|
+
/** A report this story appears in, as `:extended` inlines it. Newest period first. */
|
|
94
|
+
export interface IntelArticleReportRef {
|
|
95
|
+
readonly id: Id;
|
|
96
|
+
readonly kind: IntelReportKind;
|
|
97
|
+
readonly title: string | null;
|
|
98
|
+
readonly period_end: Timestamp;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* `GET /intel_articles/:id` - the `:extended` view.
|
|
102
|
+
*
|
|
103
|
+
* Four keys the listing does not carry, and all four are joins run inline:
|
|
104
|
+
* `sources` walks `intel_items`, `related` walks the link table in
|
|
105
|
+
* BOTH directions, `reports` orders the report join by `period_end`. There is
|
|
106
|
+
* no paging on any of them, so a story that has been running for a week can
|
|
107
|
+
* inline a lot of rows.
|
|
108
|
+
*/
|
|
109
|
+
export interface IntelArticleDetail extends IntelArticle {
|
|
110
|
+
/** The long body. `null` until the enrichment pass writes one. */
|
|
111
|
+
readonly details: string | null;
|
|
112
|
+
/** Every raw item behind the story. Length matches {@link IntelArticle.n_sources}. */
|
|
113
|
+
readonly sources: IntelArticleSourceRef[];
|
|
114
|
+
/** Stories linked to this one. `[]` when the linker found nothing. */
|
|
115
|
+
readonly related: IntelRelatedArticleRef[];
|
|
116
|
+
/** Reports that cited this story, newest period first. */
|
|
117
|
+
readonly reports: IntelArticleReportRef[];
|
|
118
|
+
}
|
|
119
|
+
/** Filter columns of `GET /intel_articles`, on top of {@link BASE_FILTER_COLUMNS}. */
|
|
120
|
+
export declare const INTEL_ARTICLE_FILTER_COLUMNS: readonly ["title", "summary", "category", "importance", "enriched"];
|
|
121
|
+
/** Filters for {@link IntelArticlesNamespace.list}. */
|
|
122
|
+
export interface ListIntelArticlesParams extends ListParams<(typeof INTEL_ARTICLE_FILTER_COLUMNS)[number]> {
|
|
123
|
+
/**
|
|
124
|
+
* Free-text search over `title`, `summary` AND `details`.
|
|
125
|
+
*
|
|
126
|
+
* A TOP-LEVEL parameter, not a `search` key: the controller reads
|
|
127
|
+
* `params[:q]` itself, which is why it can reach `details` (a column that is
|
|
128
|
+
* not in `search_params` at all) and why an unknown-filter 400 cannot
|
|
129
|
+
* happen for it.
|
|
130
|
+
*
|
|
131
|
+
* Three ways it differs from {@link ListParams.search}:
|
|
132
|
+
*
|
|
133
|
+
* - it is **accent-SENSITIVE**. The controller does `LOWER(col) LIKE
|
|
134
|
+
* LOWER(term)`, with no unaccenting, while the list DSL's `search` strips
|
|
135
|
+
* accents on both sides. `"policia"` will not find `"polícia"` here.
|
|
136
|
+
* - `%` and `_` in your term are **not escaped**. The controller wraps the
|
|
137
|
+
* term as `"%#{q}%"` and binds it, so a term containing `%` is a wildcard,
|
|
138
|
+
* not a literal percent sign. Not an injection - it is a bound parameter -
|
|
139
|
+
* but a surprise. Strip them if you are passing user input through.
|
|
140
|
+
* - it is an unanchored `LIKE` over three text columns with no index, so it
|
|
141
|
+
* is a sequential scan of your stories. Fine for thousands, not for
|
|
142
|
+
* millions.
|
|
143
|
+
*/
|
|
144
|
+
readonly q?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Keep only stories at or above this importance. Also top-level.
|
|
147
|
+
*
|
|
148
|
+
* Sent through Ruby's `String#to_i`, which does NOT raise: `"high"` becomes
|
|
149
|
+
* `0` and the filter silently matches everything. Pass a number and let the
|
|
150
|
+
* SDK stringify it.
|
|
151
|
+
*/
|
|
152
|
+
readonly minImportance?: number;
|
|
153
|
+
/**
|
|
154
|
+
* `"recent"` orders by `last_seen_at` descending. Anything else - including
|
|
155
|
+
* omitting it - orders by `importance` descending, then `last_seen_at`
|
|
156
|
+
* descending. There is no third value and no ascending variant.
|
|
157
|
+
*
|
|
158
|
+
* If you ALSO pass {@link PageParams.order}, both apply and yours wins: the
|
|
159
|
+
* controller appends its ordering after the list DSL has applied
|
|
160
|
+
* `modifiers[order]`, so your column becomes the primary sort key and the
|
|
161
|
+
* controller's becomes the tie-breaker. That is the opposite of what the
|
|
162
|
+
* parameter names suggest.
|
|
163
|
+
*/
|
|
164
|
+
readonly sort?: "recent" | "importance";
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* `GET /intel_articles` and friends: the stories the pipeline built.
|
|
168
|
+
*
|
|
169
|
+
* Read-only plus a delete. There is no create and no update route -
|
|
170
|
+
* `IntelArticle#creatable_by?` and `#updatable_by?` both return `false`
|
|
171
|
+
* unconditionally, and the route is `only: [:index, :show, :destroy]`. Stories
|
|
172
|
+
* come from `Intel::ArticleBuilder`, never from a client.
|
|
173
|
+
*/
|
|
174
|
+
export declare class IntelArticlesNamespace extends Resource {
|
|
175
|
+
/**
|
|
176
|
+
* `GET /intel_articles` - your stories, most important first.
|
|
177
|
+
*
|
|
178
|
+
* Ordering is the controller's, not yours by default: `importance DESC,
|
|
179
|
+
* last_seen_at DESC`, or `last_seen_at DESC` alone with `sort: "recent"`.
|
|
180
|
+
* See {@link ListIntelArticlesParams.sort} for what happens when you pass
|
|
181
|
+
* `order` as well - it is not what the names imply.
|
|
182
|
+
*
|
|
183
|
+
* Filter keys this controller declares for `search` / `exactSearch`:
|
|
184
|
+
* `title`, `summary`, `category`, `importance`, `enriched`, plus the
|
|
185
|
+
* inherited `id`, `created_at`, `updated_at`. Anything else is
|
|
186
|
+
* `400 "Unknown search filter: x"` - fail-closed, never a wider result. The
|
|
187
|
+
* free-text and importance filters are top-level instead: `q` and
|
|
188
|
+
* `minImportance`.
|
|
189
|
+
*
|
|
190
|
+
* **Cost.** Every row runs its own `COUNT` for
|
|
191
|
+
* {@link IntelArticle.n_sources}. Keep `pageSize` in the tens, not at 500.
|
|
192
|
+
*
|
|
193
|
+
* The response carries an `ETag` and can answer `304` - except when
|
|
194
|
+
* `random` is set, which short-circuits `resources_stale?`.
|
|
195
|
+
*
|
|
196
|
+
* @throws {OmsAuthError} 401 when anonymous.
|
|
197
|
+
* @throws {OmsApiError} 403 `"Intel access is restricted."` for a signed-in
|
|
198
|
+
* account outside the allowlist; 400 for an unrecognised filter key.
|
|
199
|
+
*/
|
|
200
|
+
list(params?: ListIntelArticlesParams, options?: RequestOptions): Promise<Paginated<IntelArticle>>;
|
|
201
|
+
/**
|
|
202
|
+
* `GET /intel_articles/:id` - one story with its body, its sources, its
|
|
203
|
+
* related stories and the reports that cited it.
|
|
204
|
+
*
|
|
205
|
+
* The `:extended` view, so it is a strict superset of the listing row. All
|
|
206
|
+
* four extras are inlined without paging; see {@link IntelArticleDetail}.
|
|
207
|
+
*
|
|
208
|
+
* @throws {OmsApiError} 404 `"Resource not found"` when the id is not one of
|
|
209
|
+
* yours - the lookup is `viewable_by(Current.user).find_by(id:)`, so
|
|
210
|
+
* somebody else's story is indistinguishable from a typo, which is the
|
|
211
|
+
* point.
|
|
212
|
+
*/
|
|
213
|
+
get(id: Id, options?: RequestOptions): Promise<IntelArticleDetail>;
|
|
214
|
+
/**
|
|
215
|
+
* `DELETE /intel_articles/:id` - drops a story. `204`, empty body.
|
|
216
|
+
*
|
|
217
|
+
* The story's links to items are removed with it (`dependent: :destroy` on
|
|
218
|
+
* `intel_article_sources`), but the {@link IntelItem} rows themselves SURVIVE
|
|
219
|
+
* - they belong to the source, not to the story. They are also still marked
|
|
220
|
+
* `processed_at`, so deleting a story does not make the pipeline rebuild it.
|
|
221
|
+
* This is a hide, not an undo.
|
|
222
|
+
*
|
|
223
|
+
* @throws {OmsApiError} 404 when the story is not yours. 401
|
|
224
|
+
* `"You are not authorized to destroy this resource"` cannot happen here -
|
|
225
|
+
* `destroyable_by?` is `user == self.user` and the lookup already scoped it
|
|
226
|
+
* - but note the API's habit of answering 401 rather than 403 for a failed
|
|
227
|
+
* authorisation check, which the scripts routes DO hit.
|
|
228
|
+
*/
|
|
229
|
+
delete(id: Id, options?: RequestOptions): Promise<void>;
|
|
230
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Intel config: the per-user pipeline settings. */
|
|
2
|
+
import { Resource } from "../../../http";
|
|
3
|
+
import type { Id, RequestOptions, Timestamp } from "../../../types";
|
|
4
|
+
/**
|
|
5
|
+
* The only keys {@link IntelConfig.prompts} accepts.
|
|
6
|
+
*
|
|
7
|
+
* Any other key fails the whole `PATCH` with
|
|
8
|
+
* `400 "Prompts unknown keys: <the offenders>"`. A key that is present but
|
|
9
|
+
* empty is not the same as an absent one: absent means "use the platform
|
|
10
|
+
* default", present-and-empty means the pipeline gets an empty prompt.
|
|
11
|
+
*/
|
|
12
|
+
export declare const INTEL_PROMPT_KEYS: readonly ["build", "enrich_plan", "enrich_actors", "enrich_synth", "report"];
|
|
13
|
+
/** One overridable prompt in the analysis pipeline. */
|
|
14
|
+
export type IntelPromptKey = (typeof INTEL_PROMPT_KEYS)[number];
|
|
15
|
+
/**
|
|
16
|
+
* The per-user knobs on the analysis pipeline. One row per user, created on
|
|
17
|
+
* demand - see {@link IntelConfigNamespace.get}.
|
|
18
|
+
*/
|
|
19
|
+
export interface IntelConfig {
|
|
20
|
+
readonly id: Id;
|
|
21
|
+
readonly created_at: Timestamp;
|
|
22
|
+
readonly updated_at: Timestamp;
|
|
23
|
+
/**
|
|
24
|
+
* Free text telling the classifier what "important" means for you. `null`
|
|
25
|
+
* falls back to the platform default. This is the single highest-leverage
|
|
26
|
+
* field here: everything else is a threshold applied to the score this
|
|
27
|
+
* produces.
|
|
28
|
+
*/
|
|
29
|
+
readonly rubric: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Prompt overrides, keyed by {@link IntelPromptKey}. `{}` means "platform
|
|
32
|
+
* defaults everywhere"; a key present means that one stage is overridden.
|
|
33
|
+
*
|
|
34
|
+
* Nullable at the database level even though it defaults to `{}`.
|
|
35
|
+
*/
|
|
36
|
+
readonly prompts: Partial<Record<IntelPromptKey, string>> | null;
|
|
37
|
+
/** LLM for the story-building pass. `null` uses the platform default. */
|
|
38
|
+
readonly build_model: string | null;
|
|
39
|
+
/** LLM for report generation. `null` uses the platform default. */
|
|
40
|
+
readonly report_model: string | null;
|
|
41
|
+
/** Stories below this importance are left out of reports. 0-10, default 4. */
|
|
42
|
+
readonly report_min_importance: number;
|
|
43
|
+
/**
|
|
44
|
+
* Stories below this importance are never web-enriched. 0-10, default 6.
|
|
45
|
+
*
|
|
46
|
+
* Lowering it does not enrich the backlog quickly: the job does three
|
|
47
|
+
* stories per run, highest importance first.
|
|
48
|
+
*/
|
|
49
|
+
readonly enrich_min_importance: number;
|
|
50
|
+
/**
|
|
51
|
+
* Master switch for the enrichment pass. `false` leaves every story at
|
|
52
|
+
* `enriched: false` and `details: null` for ever.
|
|
53
|
+
*/
|
|
54
|
+
readonly web_search: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* How many {@link IntelSource} rows you may own. 1-500, default 50.
|
|
57
|
+
*
|
|
58
|
+
* Enforced on CREATE only (`validate :within_source_quota, on: :create`), so
|
|
59
|
+
* lowering it below your current count does not delete anything - it just
|
|
60
|
+
* stops the next create with `400 "Source limit reached (N)"`.
|
|
61
|
+
*/
|
|
62
|
+
readonly max_sources: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Arguments for {@link IntelConfigNamespace.update}.
|
|
66
|
+
*
|
|
67
|
+
* Every key is optional and only the keys you send are written -
|
|
68
|
+
* `assign_attributes` over a permitted hash - so this is a genuine partial
|
|
69
|
+
* update, unlike {@link UpdateIntelSourceInput.config}.
|
|
70
|
+
*/
|
|
71
|
+
export interface UpdateIntelConfigInput {
|
|
72
|
+
readonly rubric?: string | null;
|
|
73
|
+
/**
|
|
74
|
+
* REPLACES the whole prompts object. Same trap as
|
|
75
|
+
* {@link UpdateIntelSourceInput.config}: it is one JSON column, so a partial
|
|
76
|
+
* object drops the keys you left out. Spread the current value.
|
|
77
|
+
*
|
|
78
|
+
* Only {@link INTEL_PROMPT_KEYS} are accepted; anything else fails the whole
|
|
79
|
+
* request with a 400 naming the offenders.
|
|
80
|
+
*/
|
|
81
|
+
readonly prompts?: Partial<Record<IntelPromptKey, string>>;
|
|
82
|
+
readonly buildModel?: string | null;
|
|
83
|
+
readonly reportModel?: string | null;
|
|
84
|
+
/** 0-10. Outside the range is a 400, not a clamp. */
|
|
85
|
+
readonly reportMinImportance?: number;
|
|
86
|
+
/** 0-10. Outside the range is a 400, not a clamp. */
|
|
87
|
+
readonly enrichMinImportance?: number;
|
|
88
|
+
readonly webSearch?: boolean;
|
|
89
|
+
/** 1-500. Outside the range is a 400, not a clamp. */
|
|
90
|
+
readonly maxSources?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `/intel_config` - the per-user pipeline settings.
|
|
94
|
+
*
|
|
95
|
+
* A Rails SINGULAR resource (`resource :intel_config`), so the path has no id
|
|
96
|
+
* and there is no listing: `GET /intel_config` and `PATCH /intel_config` are
|
|
97
|
+
* the whole surface. Both act on the caller's own row and there is no way to
|
|
98
|
+
* address anybody else's.
|
|
99
|
+
*/
|
|
100
|
+
export declare class IntelConfigNamespace extends Resource {
|
|
101
|
+
/**
|
|
102
|
+
* `GET /intel_config` - your settings.
|
|
103
|
+
*
|
|
104
|
+
* **This read WRITES.** The controller calls `IntelConfig.for(Current.user)`,
|
|
105
|
+
* which is `find_or_create_by!`, so a first call inserts the row with the
|
|
106
|
+
* column defaults and returns it. Consequences worth knowing: it is not safe
|
|
107
|
+
* to fire at high frequency (two concurrent first calls race on the unique
|
|
108
|
+
* index and one raises), the response is a `200` even when it just created
|
|
109
|
+
* something, and `created_at` on a "read" can be now.
|
|
110
|
+
*
|
|
111
|
+
* @throws {OmsApiError} 403 `"Intel access is restricted."` outside the
|
|
112
|
+
* allowlist - checked before the row is created, so a refused caller does
|
|
113
|
+
* not leave a row behind.
|
|
114
|
+
*/
|
|
115
|
+
get(options?: RequestOptions): Promise<IntelConfig>;
|
|
116
|
+
/**
|
|
117
|
+
* `PATCH /intel_config` - changes settings. Answers with the whole row.
|
|
118
|
+
*
|
|
119
|
+
* A genuine partial update for the scalar fields, and a whole-object replace
|
|
120
|
+
* for `prompts` - see {@link UpdateIntelConfigInput.prompts}.
|
|
121
|
+
*
|
|
122
|
+
* The route also accepts `PUT`, and it means exactly the same thing: Rails
|
|
123
|
+
* maps both onto `update` and the controller does not read the verb. There is
|
|
124
|
+
* no "replace the whole config" call.
|
|
125
|
+
*
|
|
126
|
+
* Failures are a `400` whose body is ONE sentence, not a field map:
|
|
127
|
+
* `ApplicationRecord#error_messages` is `errors.full_messages.to_sentence`,
|
|
128
|
+
* so several violations arrive joined by commas and "and". Parse it for
|
|
129
|
+
* humans, not for code.
|
|
130
|
+
*
|
|
131
|
+
* @throws {OmsApiError} 400 for a threshold outside `0..10`, a `max_sources`
|
|
132
|
+
* outside `1..500`, or a `prompts` key outside {@link INTEL_PROMPT_KEYS}.
|
|
133
|
+
*/
|
|
134
|
+
update(input: UpdateIntelConfigInput, options?: RequestOptions): Promise<IntelConfig>;
|
|
135
|
+
}
|