@omelhorsite/sdk 0.13.0 → 0.16.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.
Files changed (29) hide show
  1. package/README.md +26 -3
  2. package/dist/index.js +399 -170
  3. package/dist/types/auth/tokens.d.ts +1 -1
  4. package/dist/types/client.d.ts +3 -0
  5. package/dist/types/resources/admin/llm.d.ts +9 -1
  6. package/dist/types/resources/admin/quotas.d.ts +14 -0
  7. package/dist/types/resources/content/blogs.d.ts +111 -31
  8. package/dist/types/resources/content/index.d.ts +4 -4
  9. package/dist/types/resources/content/news/feeds.d.ts +56 -0
  10. package/dist/types/resources/content/news/index.d.ts +36 -0
  11. package/dist/types/resources/content/news/items.d.ts +115 -0
  12. package/dist/types/resources/content/{intel → news}/scripts.d.ts +35 -35
  13. package/dist/types/resources/content/{intel → news}/sources.d.ts +62 -59
  14. package/dist/types/resources/content/notifications.d.ts +2 -2
  15. package/dist/types/resources/cron.d.ts +209 -0
  16. package/dist/types/resources/index.d.ts +1 -0
  17. package/dist/types/resources/llm.d.ts +71 -0
  18. package/dist/types/resources/music/social.d.ts +121 -81
  19. package/dist/types/resources/music/songs.d.ts +39 -1
  20. package/dist/types/resources/quotas.d.ts +8 -5
  21. package/dist/types/resources/search.d.ts +36 -0
  22. package/package.json +1 -1
  23. package/dist/types/resources/content/intel/articles.d.ts +0 -230
  24. package/dist/types/resources/content/intel/config.d.ts +0 -135
  25. package/dist/types/resources/content/intel/index.d.ts +0 -53
  26. package/dist/types/resources/content/intel/items.d.ts +0 -91
  27. package/dist/types/resources/content/intel/reports.d.ts +0 -108
  28. package/dist/types/resources/content/intel/stats.d.ts +0 -105
  29. package/dist/types/resources/content/intel/types.d.ts +0 -86
@@ -0,0 +1,209 @@
1
+ /**
2
+ * The `cron` namespace: TypeScript scripts of the signed-in person that the
3
+ * server runs on a schedule.
4
+ *
5
+ * A script is an ES module exporting `run(ctx)`. It runs with nothing but the
6
+ * API: no filesystem, no environment, no processes, and no network beyond
7
+ * this SDK (`ctx.oms`, authenticated as the owner with the job's scopes) and,
8
+ * when the job has `network` on, a `ctx.fetch` that goes through the server's
9
+ * guard against private addresses. What it keeps in `ctx.state` is stored
10
+ * when a run ends well and handed back on the next one; what it returns is
11
+ * the run's `result`, and `result.summary` is what the listing shows.
12
+ *
13
+ * Needs the `cron:read` scope to read and `cron:write` to change anything.
14
+ * A job's own token never carries either: a script cannot edit jobs.
15
+ */
16
+ import { Resource, type ApiClient } from "../http";
17
+ import type { ListParams } from "../listing";
18
+ import type { Id, Json, Paginated, RequestOptions, Timestamp } from "../types";
19
+ /** Scopes a job may ask for its token. Anything else answers `400`. */
20
+ export declare const CRON_JOB_SCOPES: readonly ["profile", "news:read", "news:write", "storage:read", "storage:write", "llm", "tools:read", "tools:write", "blogs:read", "blogs:write"];
21
+ export type CronJobScope = (typeof CRON_JOB_SCOPES)[number];
22
+ export declare const CRON_JOB_HEALTHS: readonly ["unknown", "ok", "error"];
23
+ export type CronJobHealth = (typeof CRON_JOB_HEALTHS)[number];
24
+ /** Consecutive failures after which a job switches itself off. Turn `enabled` back on to revive it. */
25
+ export declare const CRON_JOB_DISABLE_AFTER_FAILURES = 10;
26
+ /** Code longer than this answers `400`. */
27
+ export declare const CRON_JOB_MAX_CODE_BYTES: number;
28
+ /** `state` and `config` ceilings. A run whose state grows past this fails and the state is not stored. */
29
+ export declare const CRON_JOB_MAX_STATE_BYTES: number;
30
+ export declare const CRON_JOB_MAX_CONFIG_BYTES: number;
31
+ /** Two runs of one job must be at least this far apart. */
32
+ export declare const CRON_JOB_MIN_INTERVAL_MINUTES = 5;
33
+ /** `timeout_seconds` range; above 120 the account needs a trusted tier, like `network`. */
34
+ export declare const CRON_JOB_MIN_TIMEOUT_SECONDS = 5;
35
+ export declare const CRON_JOB_MAX_TIMEOUT_SECONDS = 1200;
36
+ export declare const CRON_JOB_BASE_MAX_TIMEOUT_SECONDS = 120;
37
+ export interface CronJob {
38
+ readonly id: Id;
39
+ readonly created_at: Timestamp;
40
+ readonly updated_at: Timestamp;
41
+ /** Up to 120 characters, unique per account. */
42
+ readonly name: string;
43
+ readonly description: string | null;
44
+ /** Five-field cron expression, read in `timezone`. */
45
+ readonly schedule: string;
46
+ /** An IANA zone such as `"Europe/Lisbon"` (the default). */
47
+ readonly timezone: string;
48
+ /** The scopes the run's token carries. */
49
+ readonly scopes: CronJobScope[];
50
+ /** Names of the stored secrets. The values never leave the server. */
51
+ readonly secret_keys: string[];
52
+ /** Whether `ctx.fetch` exists in the script. */
53
+ readonly network: boolean;
54
+ readonly timeout_seconds: number;
55
+ /** A folder of the owner's storage handed to the script as `ctx.job.outputDirId`. */
56
+ readonly output_dir_id: Id | null;
57
+ readonly enabled: boolean;
58
+ readonly next_run_at: Timestamp | null;
59
+ readonly last_run_at: Timestamp | null;
60
+ readonly last_success_at: Timestamp | null;
61
+ readonly health: CronJobHealth;
62
+ readonly consecutive_failures: number;
63
+ readonly notify_on_failure: boolean;
64
+ readonly notify_on_success: boolean;
65
+ /** The template the job was created from, if any. */
66
+ readonly template_slug: string | null;
67
+ /** Whether a run is queued or running right now. */
68
+ readonly running: boolean;
69
+ }
70
+ /** `GET /cron_jobs/:id` (and every write) adds the code, the config and the state. */
71
+ export interface CronJobDetail extends CronJob {
72
+ readonly code: string;
73
+ readonly config: Record<string, Json>;
74
+ readonly state: Record<string, Json>;
75
+ }
76
+ export declare const CRON_JOB_FILTER_COLUMNS: readonly ["name", "enabled", "health", "template_slug"];
77
+ export interface ListCronJobsParams extends ListParams<(typeof CRON_JOB_FILTER_COLUMNS)[number]> {
78
+ }
79
+ export interface CreateCronJobInput {
80
+ readonly name: string;
81
+ readonly description?: string | null;
82
+ /** Five fields. Two runs must be at least {@link CRON_JOB_MIN_INTERVAL_MINUTES} apart. */
83
+ readonly schedule: string;
84
+ readonly timezone?: string;
85
+ /** TypeScript. Checked for syntax on the server before it is stored. */
86
+ readonly code: string;
87
+ /** Defaults to every scope but `news:write` and `tools:write`. */
88
+ readonly scopes?: readonly CronJobScope[];
89
+ readonly config?: Record<string, Json>;
90
+ /** Stored encrypted; on update the object is MERGED, and a `null` value removes that key. */
91
+ readonly secrets?: Record<string, string | null>;
92
+ readonly state?: Record<string, Json>;
93
+ readonly network?: boolean;
94
+ readonly timeoutSeconds?: number;
95
+ readonly outputDirId?: Id | null;
96
+ readonly enabled?: boolean;
97
+ readonly notifyOnFailure?: boolean;
98
+ readonly notifyOnSuccess?: boolean;
99
+ readonly templateSlug?: string | null;
100
+ }
101
+ export type UpdateCronJobInput = Partial<Omit<CreateCronJobInput, "templateSlug">>;
102
+ export declare const CRON_RUN_STATUSES: readonly ["queued", "running", "ok", "error", "timeout", "skipped"];
103
+ export type CronRunStatus = (typeof CRON_RUN_STATUSES)[number];
104
+ export declare const CRON_RUN_TRIGGERS: readonly ["schedule", "manual", "test"];
105
+ export type CronRunTrigger = (typeof CRON_RUN_TRIGGERS)[number];
106
+ export interface CronRun {
107
+ readonly id: Id;
108
+ readonly created_at: Timestamp;
109
+ readonly updated_at: Timestamp;
110
+ readonly cron_job_id: Id;
111
+ readonly status: CronRunStatus;
112
+ /** `test` runs never write the state back and never count as failures. */
113
+ readonly trigger: CronRunTrigger;
114
+ readonly scheduled_at: Timestamp;
115
+ readonly started_at: Timestamp | null;
116
+ readonly finished_at: Timestamp | null;
117
+ readonly duration_ms: number | null;
118
+ readonly error: string | null;
119
+ readonly error_name: string | null;
120
+ /** Model calls made with the run's token, and what they cost. */
121
+ readonly llm_requests: number;
122
+ readonly llm_cost: number;
123
+ /** Requests the script made, through the SDK and `ctx.fetch`. */
124
+ readonly http_calls: number;
125
+ /** `result.summary`, when the script returned one. */
126
+ readonly summary: string | null;
127
+ }
128
+ /** `GET /cron_runs/:id` adds the logs and the whole result. */
129
+ export interface CronRunDetail extends CronRun {
130
+ /** What `ctx.log` and `console.log` wrote, one line per entry, capped at 64 KB. */
131
+ readonly logs: string | null;
132
+ readonly result: Json | null;
133
+ }
134
+ export declare const CRON_RUN_FILTER_COLUMNS: readonly ["cron_job_id", "status", "trigger"];
135
+ export interface ListCronRunsParams extends ListParams<(typeof CRON_RUN_FILTER_COLUMNS)[number]> {
136
+ readonly jobId?: Id;
137
+ readonly status?: CronRunStatus;
138
+ }
139
+ /** A ready-made script: copy it into a job with {@link CronJobsNamespace.create}. */
140
+ export interface CronTemplate {
141
+ readonly slug: string;
142
+ readonly name: string;
143
+ readonly description: string;
144
+ /** The schedule the template expects. */
145
+ readonly schedule: string;
146
+ readonly scopes: CronJobScope[];
147
+ readonly network: boolean;
148
+ /** The config the script reads, with defaults. */
149
+ readonly config: Record<string, Json>;
150
+ /** SHA-256 of the code, so a client can tell an edited copy from a pristine one. */
151
+ readonly hash: string;
152
+ }
153
+ export interface CronTemplateDetail extends CronTemplate {
154
+ readonly code: string;
155
+ }
156
+ /** What the syntax check answers. */
157
+ export interface CronCheckResult {
158
+ readonly ok: boolean;
159
+ readonly error?: string;
160
+ }
161
+ export declare class CronJobsNamespace extends Resource {
162
+ list(params?: ListCronJobsParams, options?: RequestOptions): Promise<Paginated<CronJob>>;
163
+ get(id: Id, options?: RequestOptions): Promise<CronJobDetail>;
164
+ /**
165
+ * `POST /cron_jobs`. `201` with the full job.
166
+ *
167
+ * @throws {OmsApiError} 400 with the validation sentence: a bad or too
168
+ * frequent schedule, an unknown timezone, a scope outside
169
+ * {@link CRON_JOB_SCOPES}, `network` or a timeout above 120 s on an
170
+ * account without a trusted tier, an `Invalid script: ...` syntax error,
171
+ * or `job limit reached (N)` (the `cron_jobs` quota).
172
+ */
173
+ create(input: CreateCronJobInput, options?: RequestOptions): Promise<CronJobDetail>;
174
+ /** `PATCH /cron_jobs/:id`. Secrets merge; a `null` value removes a key. Changing the schedule recomputes `next_run_at`. */
175
+ update(id: Id, input: UpdateCronJobInput, options?: RequestOptions): Promise<CronJobDetail>;
176
+ /** `DELETE /cron_jobs/:id` - the job and its runs. `204`. */
177
+ delete(id: Id, options?: RequestOptions): Promise<void>;
178
+ /**
179
+ * `POST /cron_jobs/:id/run` - runs now, outside the schedule, as a normal
180
+ * run (the state is stored). `202` with the queued run.
181
+ *
182
+ * @throws {OmsApiError} 400 `this job is already running`; 429
183
+ * `error: "limit"` when the day's `cron_run_seconds` are spent.
184
+ */
185
+ run(id: Id, options?: RequestOptions): Promise<CronRun>;
186
+ /** `POST /cron_jobs/:id/test` - runs now WITHOUT storing the state or counting a failure. `202`. */
187
+ test(id: Id, options?: RequestOptions): Promise<CronRun>;
188
+ /** `POST /cron_jobs/check` - the syntax check the server runs before storing code, on its own. */
189
+ check(code: string, options?: RequestOptions): Promise<CronCheckResult>;
190
+ }
191
+ export declare class CronRunsNamespace extends Resource {
192
+ /** `GET /cron_runs` - newest first. */
193
+ list(params?: ListCronRunsParams, options?: RequestOptions): Promise<Paginated<CronRun>>;
194
+ get(id: Id, options?: RequestOptions): Promise<CronRunDetail>;
195
+ delete(id: Id, options?: RequestOptions): Promise<void>;
196
+ }
197
+ export declare class CronTemplatesNamespace extends Resource {
198
+ /** `GET /cron_templates` - the built-in scripts, without their code. */
199
+ list(options?: RequestOptions): Promise<CronTemplate[]>;
200
+ /** `GET /cron_templates/:slug` - one template with its code. */
201
+ get(slug: string, options?: RequestOptions): Promise<CronTemplateDetail>;
202
+ }
203
+ /** The `cron` namespace, reachable as `oms.cron`. */
204
+ export declare class CronNamespace extends Resource {
205
+ readonly jobs: CronJobsNamespace;
206
+ readonly runs: CronRunsNamespace;
207
+ readonly templates: CronTemplatesNamespace;
208
+ constructor(http: ApiClient);
209
+ }
@@ -22,6 +22,7 @@ export * from "./admin";
22
22
  export * from "./auth/index";
23
23
  export * from "./chests";
24
24
  export * from "./content";
25
+ export * from "./cron";
25
26
  export * from "./dynamicQrs";
26
27
  export * from "./forms";
27
28
  export * from "./ipLookup";
@@ -41,6 +41,8 @@ export interface LlmModelChoice {
41
41
  readonly name: string;
42
42
  readonly provider_slug: string;
43
43
  readonly free: boolean;
44
+ /** `0` everyone, `1` trusted accounts, `2` administrators. Only models at or below the account's tier are listed. */
45
+ readonly tier: number;
44
46
  readonly context_window: number | null;
45
47
  readonly max_output_tokens: number | null;
46
48
  readonly input_price_per_million: number | null;
@@ -285,12 +287,81 @@ export declare class LlmChatsNamespace extends Resource {
285
287
  /** Removes the chat and every message in it. */
286
288
  delete(id: Id, options?: RequestOptions): Promise<void>;
287
289
  }
290
+ /** The tools a completion may hand the model. Each call the model makes counts as one search or one page read. */
291
+ export declare const LLM_COMPLETION_TOOLS: readonly ["web_search", "read_url"];
292
+ export type LlmCompletionTool = (typeof LLM_COMPLETION_TOOLS)[number];
293
+ /** At most this many messages in one completion. */
294
+ export declare const LLM_COMPLETION_MAX_MESSAGES = 200;
295
+ /** Longer contents answer `400`. */
296
+ export declare const LLM_COMPLETION_MAX_MESSAGE_CHARS = 100000;
297
+ /** All contents together. */
298
+ export declare const LLM_COMPLETION_MAX_TOTAL_CHARS = 400000;
299
+ /** `maxTokens` above this answers `400`. */
300
+ export declare const LLM_COMPLETION_MAX_OUTPUT_TOKENS = 16000;
301
+ /** How many tool calls one completion may make before the model is told to answer with what it has. */
302
+ export declare const LLM_COMPLETION_MAX_TOOL_CALLS = 6;
303
+ export type LlmCompletionRole = "system" | "user" | "assistant";
304
+ export interface LlmCompletionMessage {
305
+ readonly role: LlmCompletionRole;
306
+ /** Plain text. Images and audio are not accepted here. */
307
+ readonly content: string;
308
+ }
309
+ export interface LlmCompletionInput {
310
+ /** In order. At least one `user` or `assistant` turn; `system` turns alone are a `400`. */
311
+ readonly messages: readonly LlmCompletionMessage[];
312
+ /**
313
+ * A model the caller may choose (an `id` or a `model_id` from {@link LlmNamespace.models}).
314
+ * Omitted, the server's own choice for API completions answers, with its fallbacks.
315
+ */
316
+ readonly model?: string;
317
+ /** Ask for a JSON object. A request, not a guarantee: parse defensively. */
318
+ readonly json?: boolean;
319
+ /** `0` to `2`. */
320
+ readonly temperature?: number;
321
+ /** `1` to {@link LLM_COMPLETION_MAX_OUTPUT_TOKENS}. */
322
+ readonly maxTokens?: number;
323
+ /** Tools the model may use while answering. Their calls come back in {@link LlmCompletion.tool_calls}. */
324
+ readonly tools?: readonly LlmCompletionTool[];
325
+ /** Language of the web searches the model runs (`"pt-PT"`, `"en"`); defaults to Portuguese. */
326
+ readonly language?: string;
327
+ }
328
+ /** One finished completion. Tokens and cost are `null` when the provider did not report them. */
329
+ export interface LlmCompletion {
330
+ readonly text: string;
331
+ /** The provider's identifier of the model that answered (a fallback may differ from the one asked for). */
332
+ readonly model_id: string | null;
333
+ readonly input_tokens: number | null;
334
+ readonly output_tokens: number | null;
335
+ /** In the provider's currency (USD for the hosted ones). */
336
+ readonly cost: number | null;
337
+ /** What the model did with its tools, in order. Empty without `tools`. */
338
+ readonly tool_calls: readonly LlmToolCall[];
339
+ /** From the request to the answer, in milliseconds. */
340
+ readonly duration_ms: number;
341
+ }
288
342
  export declare class LlmNamespace extends Resource {
289
343
  /** Conversations with the assistant. */
290
344
  readonly chats: LlmChatsNamespace;
291
345
  constructor(http: ApiClient);
292
346
  /** The models the caller may choose, with today's remaining allowance on each. */
293
347
  models(options?: RequestOptions): Promise<LlmModelChoice[]>;
348
+ /**
349
+ * `POST /llm/completions` - one answer to a list of messages, with nothing
350
+ * remembered between calls. For programs; people talk to the assistant
351
+ * through {@link chats}.
352
+ *
353
+ * Every completion counts on the account's daily ceilings (`llm_requests`
354
+ * and `llm_cost_microusd` in `oms.quotas.list()`) and on the chosen model's
355
+ * own daily limits. Needs the `llm` scope on an OAuth token.
356
+ *
357
+ * @throws {OmsApiError} 400 for malformed messages, an unknown model
358
+ * (`error: "unknown_model"`) or an unknown tool; 403 `error: "model_not_allowed"`
359
+ * for a model above the account's tier; 429 `error: "limit"` when a
360
+ * daily ceiling is reached, or above 30 completions a minute; 502
361
+ * `error: "unavailable"` when no model answered; 503 `error: "busy"`
362
+ * when the provider has no free slot, worth a retry in a moment.
363
+ */
364
+ complete(input: LlmCompletionInput, options?: RequestOptions): Promise<LlmCompletion>;
294
365
  /** The caller's own usage. */
295
366
  usage(input?: LlmUsageQuery, options?: RequestOptions): Promise<LlmUsageSummary>;
296
367
  }
@@ -501,57 +501,99 @@ export interface SendMusicAssistantMessageInput {
501
501
  readonly player?: MusicAssistantPlayerContext;
502
502
  }
503
503
  /**
504
- * Generations per user per hour, shared by `/music_dj` and `/music_dj/batch`.
504
+ * Turns per user per hour on `POST /music_dj/next`.
505
505
  *
506
- * The counter is incremented by EVERY request - including the ones it then
507
- * refuses with `429`. A retry loop therefore drives the count further past the
508
- * cap and can never recover inside the hour, which is why
509
- * {@link MusicDjNamespace} passes `retry: false` unless you override it.
506
+ * A turn is one song, so an hour of listening spends about 17 of them and the
507
+ * rest of the budget absorbs skipping. The counter is incremented by EVERY
508
+ * request - including the ones it then refuses with `429` - so a retry loop
509
+ * drives the count further past the cap and can never recover inside the hour,
510
+ * which is why {@link MusicDjNamespace} passes `retry: false` unless you
511
+ * override it.
510
512
  */
511
- export declare const MUSIC_DJ_HOURLY_CAP = 40;
513
+ export declare const MUSIC_DJ_HOURLY_CAP = 90;
512
514
  /**
513
- * Default deadline for one DJ generation, in milliseconds. Writing the script
514
- * and speaking it takes well past the client's 60 s default.
515
+ * Default deadline for one DJ turn, in milliseconds. Choosing the song and
516
+ * speaking it takes well past the client's 60 s default.
515
517
  */
516
518
  export declare const MUSIC_DJ_TIMEOUT_MS = 120000;
517
- /** Songs one `/music_dj/batch` set plans at most. */
518
- export declare const MUSIC_DJ_BATCH_SIZE = 4;
519
- /** `POST /music_dj`: one spoken link between two tracks. */
520
- export interface MusicDjInterstitial {
521
- /** The script as text, at most 320 characters. Worth showing while audio loads. */
522
- readonly text: string;
519
+ /** One entry in a DJ session's transcript. */
520
+ export interface MusicDjTurn {
521
+ /** `"dj"` when he played or spoke, `"listener"` when they asked for something. */
522
+ readonly role: "dj" | "listener";
523
523
  /**
524
- * The same script spoken, base64, NOT a data URL. Decode with
525
- * {@link musicDjAudioBytes} or wrap with {@link musicDjAudioDataUrl}.
524
+ * What was said. `null` on a DJ turn where he stayed quiet - he speaks on
525
+ * the first song, whenever the listener asks for something, and every third
526
+ * song after that, so most turns carry a song and no words.
526
527
  */
527
- readonly audio_base64: string;
528
- /** Container of the decoded bytes. `"wav"` today, and typed wide on purpose. */
529
- readonly format: string;
528
+ readonly text: string | null;
529
+ /** The song that turn played, when it played one. */
530
+ readonly song_id: number | null;
531
+ readonly created_at: Timestamp;
530
532
  }
531
- /** `POST /music_dj/batch`: a whole set - what to play next, and the words for it. */
532
- export interface MusicDjBatch extends MusicDjInterstitial {
533
+ /** `GET /music_dj/session`: the session still on air, if there is one. */
534
+ export interface MusicDjSession {
535
+ readonly id: number;
536
+ /** The listener's standing request. It holds until they ask for something else. */
537
+ readonly request: string | null;
538
+ readonly turns: MusicDjTurn[];
533
539
  /**
534
- * The planned tracks, in play order, in the full `GET /songs` shape and
535
- * already scoped to what the caller may play. Between 1 and
536
- * {@link MUSIC_DJ_BATCH_SIZE}: the server fails rather than answer with an
537
- * empty set, so this is never `[]`.
540
+ * Every song the transcript refers to, in the full `GET /songs` shape, so a
541
+ * client can rebuild the session without a lookup per turn. Unordered: match
542
+ * them to {@link MusicDjTurn.song_id}.
538
543
  */
539
544
  readonly songs: Song[];
540
545
  }
541
- /** Everything `POST /music_dj/batch` accepts. All of it optional. */
542
- export interface MusicDjBatchInput {
543
- /** A free-text steer ("something calmer"). Truncated to 300 characters. */
546
+ /** `POST /music_dj/next`: the next song, and the words for it when there are any. */
547
+ export interface MusicDjNext {
548
+ readonly session_id: number;
549
+ /** What to play now. Never one the session already played. */
550
+ readonly song: Song;
551
+ /** Two to five words naming this stretch of the show, for a screen. */
552
+ readonly theme: string | null;
553
+ /**
554
+ * The spoken link, present ONLY on the turns where he speaks. Its absence is
555
+ * the normal case, not an error: play the song straight away.
556
+ */
557
+ readonly text?: string;
558
+ /**
559
+ * The same words spoken, base64, NOT a data URL. Decode with
560
+ * {@link musicDjAudioBytes} or wrap with {@link musicDjAudioDataUrl}.
561
+ *
562
+ * Absent when he stayed quiet AND when the voice service failed while he had
563
+ * something to say - in that second case `text` is there without it, and the
564
+ * song still plays. Never block playback on this field.
565
+ */
566
+ readonly audio_base64?: string;
567
+ /** Container of the decoded bytes. `"wav"` today, and typed wide on purpose. */
568
+ readonly format?: string;
569
+ }
570
+ /** Everything `POST /music_dj/next` accepts. All of it optional. */
571
+ export interface MusicDjNextInput {
572
+ /**
573
+ * Continue this session. Omit to continue the caller's own session, which
574
+ * the server finds by itself - a session goes stale after two hours of
575
+ * silence and the next turn starts a fresh one.
576
+ */
577
+ readonly sessionId?: number;
578
+ /**
579
+ * A steer ("something calmer", "fados"), truncated to 300 characters.
580
+ *
581
+ * STANDING ORDERS, not a one-off: it holds for every following turn until
582
+ * another request replaces it, and it makes him speak on this turn even if
583
+ * it was not his turn to.
584
+ */
544
585
  readonly request?: string;
586
+ /** The song the listener just skipped, as a direction to leave. */
587
+ readonly skippedSongId?: SongId;
545
588
  /**
546
- * Recently played ids. Only the last 60 are read, and the planner SUBTRACTS
547
- * them from its own picks - so a list that covers the whole library leaves
548
- * nothing playable and the call fails with a `502`.
589
+ * Make him speak on this turn even if it was not his turn to.
590
+ *
591
+ * This is the DJ button: the listener wants to hear him now. It changes
592
+ * nothing about which song comes next.
549
593
  */
550
- readonly recentSongIds?: SongId[];
551
- /** Ids the listener skipped, as negative signal. Only the last 20 are read. */
552
- readonly skippedSongIds?: SongId[];
553
- /** Which set of the session this is, so the script can vary its opening. */
554
- readonly batchIndex?: number;
594
+ readonly speak?: boolean;
595
+ /** Start a new session even though one is still on air. */
596
+ readonly restart?: boolean;
555
597
  }
556
598
  /**
557
599
  * Jams over HTTP. The realtime half lives on the WebSocket stream and is not in
@@ -887,12 +929,16 @@ export declare class MusicAssistantNamespace extends Resource {
887
929
  ask(messages: MusicAssistantMessage[], player?: MusicAssistantPlayerContext, options?: RequestOptions): Promise<MusicAssistantAnswer>;
888
930
  }
889
931
  /**
890
- * "O Melhor DJ": a written-and-spoken link between tracks, and a whole planned
891
- * set.
932
+ * "O Melhor DJ": a radio session for one listener, one song at a time.
933
+ *
934
+ * The server holds the session - what played, what was skipped, what he said,
935
+ * what the listener asked for - so a client only ever says what just happened
936
+ * and gets the next song back. Ask for one when the current song ends, when
937
+ * the listener skips, or when they want something else.
892
938
  *
893
- * Both methods pass `retry: false` by default. That is not caution about
939
+ * {@link next} passes `retry: false` by default. That is not caution about
894
940
  * duplicates - the transport does not replay a `POST` anyway - it is about the
895
- * one thing it DOES replay: a `429`. The hourly cap here counts every request
941
+ * one thing it DOES replay: a `429`. The hourly cap counts every request
896
942
  * including the refused ones, so waiting out a `Retry-After` and asking again
897
943
  * pushes the count further past the cap and cannot succeed inside the hour.
898
944
  * Pass `retry: {}` to opt back in if you are sure the `429` came from the
@@ -900,57 +946,46 @@ export declare class MusicAssistantNamespace extends Resource {
900
946
  */
901
947
  export declare class MusicDjNamespace extends Resource {
902
948
  /**
903
- * `POST /music_dj` - the DJ introduces the next track.
949
+ * `POST /music_dj/next` - the next song, chosen with the whole session in
950
+ * view.
904
951
  *
905
- * Returns the script AND the spoken audio in one answer, base64 in the JSON
906
- * body rather than as a URL, because the clip is small and ephemeral and
907
- * nothing stores it. Decode with {@link musicDjAudioBytes}, or hand
908
- * {@link musicDjAudioDataUrl} to a player that takes a URI.
952
+ * `next({})` is a valid cold start: with no session on air the server opens
953
+ * one. The answer always carries a `song`; `text` and `audio_base64` come
954
+ * only on the turns where he speaks (the first song, after a request, and
955
+ * every third song), so treat their absence as normal and play the song.
909
956
  *
910
- * Both ids are resolved against what the caller may play, so a followed
911
- * playlist's track works and a stranger's does not. `previousSongId` is genuinely optional and
912
- * is what lets the script say goodbye to the outgoing track.
957
+ * A song the session already played is never returned, which is what lets a
958
+ * client keep the whole session in its queue and walk back through it.
913
959
  *
914
- * Generation takes seconds; the deadline defaults to
915
- * {@link MUSIC_DJ_TIMEOUT_MS}. The
916
- * intended pattern is to ask for the clip while the current track is still
917
- * playing and drop it on the boundary, ideally over the outgoing
918
- * instrumental.
960
+ * Choosing takes seconds and speaking takes more; the deadline defaults to
961
+ * {@link MUSIC_DJ_TIMEOUT_MS}. Ask for the next song while the current one
962
+ * is still playing, or the listener hears the gap.
919
963
  *
920
964
  * @throws {OmsError} `401 "Session required"` when unauthenticated.
921
- * @throws {OmsError} `404 "Song not found"` for either id.
922
965
  * @throws {OmsError} `429 "DJ limit reached, try again later"` past
923
966
  * {@link MUSIC_DJ_HOURLY_CAP}.
924
- * @throws {OmsError} `502 "DJ is unavailable right now"` when the script
925
- * failed, `503 "DJ voice is unavailable right now"` when the voice did.
926
- * The split is deliberate: a `503` means the words exist but nothing can
927
- * say them.
967
+ * @throws {OmsError} `502 "DJ is unavailable right now"` when no song could
968
+ * be chosen - including the honest case where the library has nothing left
969
+ * that the session has not already played.
928
970
  */
929
- interstitial(input: {
930
- readonly nextSongId: SongId;
931
- readonly previousSongId?: SongId | null;
932
- }, options?: RequestOptions): Promise<MusicDjInterstitial>;
971
+ next(input?: MusicDjNextInput, options?: RequestOptions): Promise<MusicDjNext>;
933
972
  /**
934
- * `POST /music_dj/batch` - a whole set: what to play next AND the words
935
- * introducing it, from one model call.
973
+ * `GET /music_dj/session` - the session still on air, or `null`.
936
974
  *
937
- * The intended cadence is a real station's: take the set, play it, and come
938
- * back when about two tracks remain. Every field is optional, so
939
- * `batch({})` is a valid cold start.
940
- *
941
- * `recentSongIds` is a filter, not just a hint - the planner subtracts those
942
- * ids from its own picks. Send a list covering the whole library and the
943
- * planner has nothing left, which surfaces as `502 "DJ is unavailable right
944
- * now"` rather than as an empty set. Keep it to a genuine recent window; only
945
- * the last 60 are read anyway.
946
- *
947
- * `songs` comes back in play order, in the full `GET /songs` shape, already
948
- * scoped to what the caller may play. It is never empty.
975
+ * `null` is the ordinary answer for someone who has not listened today: a
976
+ * session goes stale after two hours of silence and stops being served here.
977
+ * Use it to rebuild the transcript when a screen opens, not to decide
978
+ * whether {@link next} may be called - it always may.
979
+ */
980
+ session(options?: RequestOptions): Promise<MusicDjSession | null>;
981
+ /**
982
+ * `DELETE /music_dj/session` - end the session on air.
949
983
  *
950
- * Shares {@link MUSIC_DJ_HOURLY_CAP} with {@link interstitial}. Same
951
- * timeouts, same error shapes.
984
+ * Idempotent: with nothing on air it still answers `204`. The next
985
+ * {@link next} starts a session with no memory of this one, which is the
986
+ * point - it is how a listener says "forget what I asked for".
952
987
  */
953
- batch(input?: MusicDjBatchInput, options?: RequestOptions): Promise<MusicDjBatch>;
988
+ end(options?: RequestOptions): Promise<void>;
954
989
  }
955
990
  /**
956
991
  * The `music.social` entry point, holding the five families as sub-namespaces.
@@ -1045,7 +1080,9 @@ export declare function musicStorageAffords(usage: MusicStorageUsage, bytes: num
1045
1080
  * slightly short audio is a better failure than one that throws inside a
1046
1081
  * playback callback.
1047
1082
  */
1048
- export declare function musicDjAudioBytes(clip: Pick<MusicDjInterstitial, "audio_base64">): Uint8Array;
1083
+ export declare function musicDjAudioBytes(clip: {
1084
+ readonly audio_base64: string;
1085
+ }): Uint8Array;
1049
1086
  /**
1050
1087
  * Wraps a DJ clip as a `data:` URI, for a player that takes a URI rather than
1051
1088
  * bytes - which is most of them.
@@ -1054,4 +1091,7 @@ export declare function musicDjAudioBytes(clip: Pick<MusicDjInterstitial, "audio
1054
1091
  * in memory, so this is cheap in every sense that matters at this size. It is
1055
1092
  * not a URL anything can fetch twice - it is the bytes, spelled differently.
1056
1093
  */
1057
- export declare function musicDjAudioDataUrl(clip: MusicDjInterstitial): string;
1094
+ export declare function musicDjAudioDataUrl(clip: {
1095
+ readonly audio_base64: string;
1096
+ readonly format?: string;
1097
+ }): string;
@@ -143,6 +143,35 @@ export interface Song extends Omit<BaseRecord, "id"> {
143
143
  readonly source_id: string | null;
144
144
  /** Recording identifier, when the importer resolved one. */
145
145
  readonly isrc: string | null;
146
+ /**
147
+ * ISO 639-1 code of the language SUNG on the track (`"es"`, `"pt"`, `"ja"`),
148
+ * or `null`.
149
+ *
150
+ * Derived, not declared: the server runs a detector over the stored lyrics,
151
+ * and falls back to unambiguous Last.fm tags (`"j-pop"`, `"fado"`) when
152
+ * there are none. So `null` means "not known yet" - a song whose lyrics were
153
+ * never fetched, or an instrumental - and NEVER "no language". Treat it as
154
+ * unknown rather than quietly dropping it from a language view.
155
+ */
156
+ readonly language: string | null;
157
+ /**
158
+ * Lowercased Last.fm tags for the track, most-voted first, at most six.
159
+ *
160
+ * Crowd tags, not a controlled vocabulary: expect `"rock"` and
161
+ * `"female vocalists"` in the same list, and expect `[]` for anything
162
+ * obscure. The server drops the junk ones ("seen live", "albums i own") and
163
+ * falls back to the artist's tags when the track has none of its own, which
164
+ * is why two songs by one artist often carry identical lists.
165
+ */
166
+ readonly tags: string[];
167
+ /**
168
+ * Tempo in beats per minute, or `null` when it is not known.
169
+ *
170
+ * Comes from the recording's own registry entry, matched by ISRC, so a
171
+ * track with no ISRC never gets one and a remix is never given the
172
+ * original's tempo. `null` is common and means nothing about the music.
173
+ */
174
+ readonly bpm: number | null;
146
175
  readonly original_filename: string | null;
147
176
  readonly audio_codec: string | null;
148
177
  readonly audio_bitrate_kbps: number | null;
@@ -445,7 +474,7 @@ export interface MusicExternalSearchResult {
445
474
  * `artist` is on the list but is not a column - see
446
475
  * {@link ListSongsParams.artist}.
447
476
  */
448
- export declare const SONG_FILTER_COLUMNS: readonly ["id", "created_at", "updated_at", "title", "album", "position", "year", "artist"];
477
+ export declare const SONG_FILTER_COLUMNS: readonly ["id", "created_at", "updated_at", "title", "album", "position", "year", "artist", "language"];
449
478
  /** Columns the backend will accept in `modifiers[order]`. */
450
479
  export declare const SONG_ORDER_COLUMNS: readonly string[];
451
480
  /** Filters shared by `GET /songs` and `GET /songs/albums`. */
@@ -503,6 +532,15 @@ export interface SongFilters {
503
532
  * excluded even if they also appear as a feature.
504
533
  */
505
534
  readonly artistRole?: SongArtistRole;
535
+ /**
536
+ * Exact language code, or a list of them, sent as `exact_search[language]`.
537
+ *
538
+ * Matches {@link Song.language}, with the same caveat: rows the server could
539
+ * not classify hold `null` and are NOT returned by any code you ask for.
540
+ * Pass `null` to get exactly those - the unclassified pile - which is a
541
+ * different question from "songs with no words".
542
+ */
543
+ readonly language?: string | string[] | null;
506
544
  }
507
545
  /** Arguments for {@link MusicSongsNamespace.list}. */
508
546
  export interface ListSongsParams extends SongFilters, ListParams<(typeof SONG_FILTER_COLUMNS)[number]> {