@miraiclip/assistant 0.1.2 → 0.3.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/dist/index.d.ts CHANGED
@@ -10,6 +10,18 @@ interface ToolCall {
10
10
  id: string;
11
11
  name: string;
12
12
  arguments: Record<string, unknown>;
13
+ /**
14
+ * Opaque data the adapter that produced the call needs back when the
15
+ * conversation is replayed (Gemini thought signatures, Anthropic thinking
16
+ * blocks). Keep it with the call; other adapters ignore it.
17
+ */
18
+ meta?: Record<string, unknown>;
19
+ }
20
+ /** An image the model looks at (a rendered preview a tool returns): base64 bytes and their type. */
21
+ interface ChatImage {
22
+ mimeType: string;
23
+ /** Base64, no `data:` prefix. */
24
+ data: string;
13
25
  }
14
26
  type ChatMessage = {
15
27
  role: "system";
@@ -21,11 +33,14 @@ type ChatMessage = {
21
33
  role: "assistant";
22
34
  content: string;
23
35
  toolCalls?: ToolCall[];
24
- } | {
36
+ }
37
+ /** `images`: pictures that come with the result (e.g. rendered frames), for models that can see. */
38
+ | {
25
39
  role: "tool";
26
40
  toolCallId: string;
27
41
  name: string;
28
42
  content: string;
43
+ images?: ChatImage[];
29
44
  };
30
45
  /** A tool as the model sees it: name, description and a JSON Schema for its input. */
31
46
  interface ToolSpec {
@@ -59,18 +74,28 @@ interface ChatOptions {
59
74
  /** Streaming text, as it arrives (adapters that can't stream call it once). */
60
75
  onText?: (delta: string) => void;
61
76
  }
77
+ interface ModelEntry {
78
+ id: string;
79
+ label?: string;
80
+ }
62
81
  interface ChatModelInfo {
63
82
  /** Adapter id, e.g. "openai". Alphanumeric with - or _. */
64
83
  id: string;
65
84
  label: string;
66
85
  /** Models this adapter serves; the first is the default. */
67
- models?: {
86
+ models?: ModelEntry[];
87
+ /** For a model that routes to several providers (`routedChatModel`): which ones. */
88
+ providers?: {
68
89
  id: string;
69
- label?: string;
90
+ label: string;
70
91
  }[];
71
92
  }
72
93
  interface ChatModel extends ChatModelInfo {
73
94
  complete(request: ChatRequest, options?: ChatOptions): Promise<ChatResponse>;
95
+ /** Every model the account behind this adapter can use, live from the vendor (for model pickers). */
96
+ listModels?(options?: {
97
+ signal?: AbortSignal;
98
+ }): Promise<ModelEntry[]>;
74
99
  }
75
100
  /** An error from a model vendor, carrying its HTTP status when there was one. */
76
101
  declare class ChatModelError extends Error {
@@ -82,6 +107,16 @@ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
82
107
  /** Check an adapter's shape once, at registration. */
83
108
  declare function defineChatModel<M extends ChatModel>(model: M): M;
84
109
 
110
+ /**
111
+ * Plumbing the vendor adapters share: POST with retries on rate limits and
112
+ * server errors, server-sent-event reading, and tool-argument parsing.
113
+ */
114
+
115
+ /** Parse a tool call's JSON arguments; unparseable JSON is reported to the model as a tool error. */
116
+ declare function parseToolArguments(raw: string): Record<string, unknown>;
117
+ /** How long the server asked us to wait (Retry-After header, or "try again in 1.2s / 350ms"), else exponential backoff. */
118
+ declare function retryDelayMs(res: Pick<Response, "headers">, message: string, attempt: number): number;
119
+
85
120
  /**
86
121
  * OpenAI adapter, over the Chat Completions API with function calling.
87
122
  *
@@ -99,7 +134,7 @@ interface OpenAIChatModelOptions {
99
134
  label?: string;
100
135
  }[];
101
136
  /** API key. Omit for local servers that don't need one. Keep it server-side (see `createChatHandler`). */
102
- apiKey?: string;
137
+ apiKey?: string | undefined;
103
138
  /** Default https://api.openai.com/v1. Any OpenAI-compatible base URL works. */
104
139
  baseUrl?: string;
105
140
  organization?: string;
@@ -115,14 +150,33 @@ interface OpenAIChatModelOptions {
115
150
  * backing off 1 s, 2 s, 4 s… (capped at 30 s). Default 4; 0 disables.
116
151
  */
117
152
  maxRetries?: number;
153
+ /**
154
+ * The request field for the output cap: "max_completion_tokens" (default;
155
+ * OpenAI) or "max_tokens" (what most other compatible servers read).
156
+ */
157
+ maxTokensField?: "max_completion_tokens" | "max_tokens";
158
+ /** Ask for token usage at the end of a stream (default true; turn off for servers that reject `stream_options`). */
159
+ streamUsage?: boolean;
118
160
  /** Adapter id / label (default "openai" / "OpenAI"), e.g. "ollama" when pointing elsewhere. */
119
161
  id?: string;
120
162
  label?: string;
121
163
  fetch?: FetchLike;
122
164
  }
165
+ type OpenAIPart = {
166
+ type: "text";
167
+ text: string;
168
+ } | {
169
+ type: "image_url";
170
+ image_url: {
171
+ url: string;
172
+ };
173
+ };
123
174
  type OpenAIMessage = {
124
175
  role: "system" | "user";
125
176
  content: string;
177
+ } | {
178
+ role: "user";
179
+ content: OpenAIPart[];
126
180
  } | {
127
181
  role: "assistant";
128
182
  content: string | null;
@@ -141,56 +195,364 @@ type OpenAIMessage = {
141
195
  };
142
196
  /** Neutral messages → Chat Completions messages. */
143
197
  declare function toOpenAIMessages(messages: readonly ChatMessage[]): OpenAIMessage[];
144
- /** Parse a tool call's JSON arguments; unparseable JSON is reported to the model as a tool error. */
145
- declare function parseToolArguments(raw: string): Record<string, unknown>;
146
- /** How long the server asked us to wait (Retry-After header, or "try again in 1.2s / 350ms"), else exponential backoff. */
147
- declare function retryDelayMs(res: Pick<Response, "headers">, message: string, attempt: number): number;
148
198
  declare function openAIChatModel(options: OpenAIChatModelOptions): ChatModel;
149
199
 
200
+ /**
201
+ * Anthropic adapter, over the Messages API with tool use.
202
+ *
203
+ * System messages become the top-level `system` prompt; tool results are
204
+ * grouped into one user turn, as the API expects. When extended thinking is
205
+ * on (`params: { thinking: … }`), the thinking blocks of a tool-using turn
206
+ * ride on the first tool call's `meta` and are sent back on the next request,
207
+ * which the API requires.
208
+ */
209
+
210
+ interface AnthropicChatModelOptions {
211
+ /** Model id, e.g. "claude-sonnet-5-5". Required: model names change faster than this package. */
212
+ model: string;
213
+ /** Other models the app may pick per request (`ChatRequest.model`). */
214
+ models?: {
215
+ id: string;
216
+ label?: string;
217
+ }[];
218
+ /** API key. Keep it server-side (see `createChatHandler`) unless it's the user's own key in their own browser (`browser`). */
219
+ apiKey?: string | undefined;
220
+ /** Default https://api.anthropic.com (requests go to `{baseUrl}/v1/messages`). */
221
+ baseUrl?: string;
222
+ /** `anthropic-version` header (default "2023-06-01"). */
223
+ version?: string;
224
+ /** `anthropic-beta` flags, joined with commas. */
225
+ betas?: string[];
226
+ headers?: Record<string, string>;
227
+ /** Default request fields merged into every call (e.g. `{ thinking: { type: "enabled", budget_tokens: 4000 } }`). */
228
+ params?: Record<string, unknown>;
229
+ /** The API requires an output cap; used when a request sets none (default 8192). */
230
+ maxOutputTokens?: number;
231
+ /** Stream responses (default true). */
232
+ stream?: boolean;
233
+ /** Retries on 429, 5xx and 529 (overloaded), as the server asks. Default 4; 0 disables. */
234
+ maxRetries?: number;
235
+ /**
236
+ * Calling from a browser with the user's own key: sends the header the API
237
+ * needs to allow cross-origin requests. Never ship your own key this way.
238
+ */
239
+ browser?: boolean;
240
+ /** Adapter id / label (default "anthropic" / "Anthropic"). */
241
+ id?: string;
242
+ label?: string;
243
+ fetch?: FetchLike;
244
+ }
245
+ type ImageBlock = {
246
+ type: "image";
247
+ source: {
248
+ type: "base64";
249
+ media_type: string;
250
+ data: string;
251
+ };
252
+ };
253
+ type Block = {
254
+ type: "text";
255
+ text: string;
256
+ } | {
257
+ type: "tool_use";
258
+ id: string;
259
+ name: string;
260
+ input: Record<string, unknown>;
261
+ } | {
262
+ type: "tool_result";
263
+ tool_use_id: string;
264
+ content: string | ({
265
+ type: "text";
266
+ text: string;
267
+ } | ImageBlock)[];
268
+ } | {
269
+ type: "thinking";
270
+ thinking: string;
271
+ signature: string;
272
+ } | {
273
+ type: "redacted_thinking";
274
+ data: string;
275
+ };
276
+ type AnthropicMessage = {
277
+ role: "user" | "assistant";
278
+ content: Block[];
279
+ };
280
+ /** Neutral messages → `{ system, messages }` for the Messages API. */
281
+ declare function toAnthropicMessages(messages: readonly ChatMessage[]): {
282
+ system: string;
283
+ messages: AnthropicMessage[];
284
+ };
285
+ declare function anthropicChatModel(options: AnthropicChatModelOptions): ChatModel;
286
+
287
+ /**
288
+ * Google Gemini adapter, over the Gemini API's `generateContent` with
289
+ * function calling.
290
+ *
291
+ * Tools are sent as full JSON Schema (`parametersJsonSchema`), so the same
292
+ * schemas the other adapters use work unchanged. Gemini's thought signatures
293
+ * (required when thinking models call tools) ride on each tool call's `meta`
294
+ * and are sent back exactly as received.
295
+ */
296
+
297
+ interface GeminiChatModelOptions {
298
+ /** Model id, e.g. "gemini-3.8-flash". Required: model names change faster than this package. */
299
+ model: string;
300
+ /** Other models the app may pick per request (`ChatRequest.model`). */
301
+ models?: {
302
+ id: string;
303
+ label?: string;
304
+ }[];
305
+ /** Gemini API key. Keep it server-side (see `createChatHandler`) unless it's the user's own key in their own browser. */
306
+ apiKey?: string | undefined;
307
+ /** Default https://generativelanguage.googleapis.com/v1beta. */
308
+ baseUrl?: string;
309
+ headers?: Record<string, string>;
310
+ /** Default request fields merged into every call; `generationConfig` is merged key by key (e.g. `{ generationConfig: { temperature: 0.2 } }`). */
311
+ params?: Record<string, unknown>;
312
+ /** Stream responses (default true). */
313
+ stream?: boolean;
314
+ /** Retries on 429 and 5xx, as the server asks. Default 4; 0 disables. */
315
+ maxRetries?: number;
316
+ /** Adapter id / label (default "gemini" / "Google Gemini"). */
317
+ id?: string;
318
+ label?: string;
319
+ fetch?: FetchLike;
320
+ }
321
+ type Part = {
322
+ text?: string;
323
+ thought?: boolean;
324
+ thoughtSignature?: string;
325
+ functionCall?: {
326
+ id?: string;
327
+ name: string;
328
+ args?: Record<string, unknown>;
329
+ };
330
+ functionResponse?: {
331
+ id?: string;
332
+ name: string;
333
+ response: Record<string, unknown>;
334
+ };
335
+ inlineData?: {
336
+ mimeType: string;
337
+ data: string;
338
+ };
339
+ };
340
+ type GeminiContent = {
341
+ role: "user" | "model";
342
+ parts: Part[];
343
+ };
344
+ /** Neutral messages → `{ systemInstruction, contents }` for `generateContent`. */
345
+ declare function toGeminiContents(messages: readonly ChatMessage[]): {
346
+ system: string;
347
+ contents: GeminiContent[];
348
+ };
349
+ declare function geminiChatModel(options: GeminiChatModelOptions): ChatModel;
350
+
351
+ type ProviderApi = "openai" | "anthropic" | "gemini";
352
+ interface AiProviderInfo {
353
+ /** Id used in configs, env names and "provider/model" references. */
354
+ id: string;
355
+ label: string;
356
+ /** Which wire format it speaks. */
357
+ api: ProviderApi;
358
+ /** API root; `openai-compatible` has none (you give it). */
359
+ baseUrl?: string;
360
+ /** Whether calls need an API key (local servers don't). */
361
+ needsKey: boolean;
362
+ /** Runs on the user's machine (Ollama, LM Studio): reachable from that machine only. */
363
+ local?: boolean;
364
+ /** Where a user creates a key. */
365
+ keyUrl?: string;
366
+ /** Env var prefix: `{PREFIX}_API_KEY`, `{PREFIX}_MODEL`, `{PREFIX}_BASE_URL`, `{PREFIX}_MODELS`. */
367
+ env: string;
368
+ /** Used when no model is named. Providers without one need a model picked (env or UI). */
369
+ defaultModel?: string;
370
+ /** A few good picks to show before the live list loads. */
371
+ models?: ModelEntry[];
372
+ /** Accepts calls straight from a browser (CORS) with the user's own key. */
373
+ browser?: boolean;
374
+ /** OpenAI-format servers: the output-cap field and whether `stream_options` is accepted. */
375
+ maxTokensField?: "max_completion_tokens" | "max_tokens";
376
+ streamUsage?: boolean;
377
+ }
378
+ /**
379
+ * The built-in provider catalog. Model ids are suggestions as of release; the
380
+ * live list comes from each provider (`ChatModel.listModels`).
381
+ */
382
+ declare const AI_PROVIDERS: readonly AiProviderInfo[];
383
+ /** A provider from the catalog, or undefined. */
384
+ declare function aiProvider(id: string): AiProviderInfo | undefined;
385
+ interface CreateChatModelOptions {
386
+ /** A catalog id (`AI_PROVIDERS`): "openai", "anthropic", "gemini", "openrouter", "ollama", "openai-compatible", … */
387
+ provider: string;
388
+ apiKey?: string | undefined;
389
+ /** Model id (default: the provider's `defaultModel`; required for providers without one). */
390
+ model?: string | undefined;
391
+ /** Other models to offer (default: the catalog's suggestions). */
392
+ models?: ModelEntry[];
393
+ /** Override the API root (required for "openai-compatible"). */
394
+ baseUrl?: string | undefined;
395
+ headers?: Record<string, string>;
396
+ /** Default request fields for every call (vendor-specific). */
397
+ params?: Record<string, unknown>;
398
+ stream?: boolean;
399
+ maxRetries?: number;
400
+ /** Calling from the browser with the user's own key (sets what the vendor needs for CORS). */
401
+ browser?: boolean;
402
+ /** Adapter id / label (default: the provider's). */
403
+ id?: string;
404
+ label?: string;
405
+ fetch?: FetchLike;
406
+ }
407
+ /** One `ChatModel` for any provider in the catalog. */
408
+ declare function createChatModel(options: CreateChatModelOptions): ChatModel;
409
+ interface ChatModelsFromEnvOptions {
410
+ /** Only these providers (default: all). */
411
+ providers?: string[];
412
+ /** Told about providers that have a key but couldn't be set up (e.g. no model). */
413
+ onSkip?: (provider: string, reason: string) => void;
414
+ fetch?: FetchLike;
415
+ }
416
+ /**
417
+ * A model for every provider configured in `env` (`process.env`, Vite's
418
+ * `loadEnv`, a Worker's bindings):
419
+ *
420
+ * {PREFIX}_API_KEY enables a hosted provider (GEMINI also reads GOOGLE_API_KEY)
421
+ * {PREFIX}_MODEL its default model (required where the catalog has none)
422
+ * {PREFIX}_MODELS more models to offer, comma-separated
423
+ * {PREFIX}_BASE_URL another API root (a proxy, a gateway, a regional endpoint)
424
+ *
425
+ * Local servers (OLLAMA, LMSTUDIO) are enabled by their _MODEL or _BASE_URL.
426
+ * OPENAI_BASE_URL alone keeps its old meaning: an OpenAI-compatible server
427
+ * (label OPENAI_LABEL), key optional. Order follows the catalog, so the
428
+ * first entry makes a sensible default.
429
+ */
430
+ declare function chatModelsFromEnv(env: Record<string, string | undefined>, options?: ChatModelsFromEnvOptions): ChatModel[];
431
+ /** "anthropic/claude-sonnet-5-5" → { provider: "anthropic", model: "claude-sonnet-5-5" } when the prefix is a known id. */
432
+ declare function splitModelRef(ref: string, providerIds: Iterable<string>): {
433
+ provider?: string;
434
+ model: string;
435
+ };
436
+ interface RoutedChatModelOptions {
437
+ id?: string;
438
+ label?: string;
439
+ /** Adapter id used when a request names no provider (default: the first). */
440
+ default?: string;
441
+ }
442
+ /**
443
+ * Several providers as one `ChatModel`. Its models are "provider/model"
444
+ * (`anthropic/claude-sonnet-5-5`, `openrouter/meta-llama/…`); a request's
445
+ * `model` picks the adapter and the model. A bare model id goes to the
446
+ * adapter that lists it, else to the default adapter; no model → the default
447
+ * adapter's default model.
448
+ */
449
+ declare function routedChatModel(models: readonly ChatModel[], options?: RoutedChatModelOptions): ChatModel;
450
+
150
451
  /**
151
452
  * Keys stay on the server: run the real model adapter in your backend behind
152
453
  * `createChatHandler`, and give the browser `remoteChatModel(endpoint)` — a
153
454
  * proxy with the same `ChatModel` shape. The agent loop and the tools run in
154
455
  * the browser, next to the project; only model calls cross the network.
155
456
  *
156
- * GET {base}/ → { id, label, models }
457
+ * GET {base}/ → { id, label, models, providers?, userKeys? }
458
+ * GET {base}/models → { models } (live from the vendor when it can list them)
157
459
  * POST {base}/chat { request: ChatRequest } →
158
460
  * application/x-ndjson, one JSON object per line:
159
461
  * { "type": "text", "delta": "…" } (as it streams)
160
462
  * { "type": "done", "response": ChatResponse }
161
463
  * { "type": "error", "error": "…", "status": 401 }
162
- * errors before streaming → { error } with 400 / 404 / 413, or the vendor's status
464
+ * errors before streaming → { error } with 400 / 401 / 403 / 404 / 413, or the vendor's status
465
+ *
466
+ * Bring your own key (`userKeys`): a request may carry the user's own
467
+ * provider and key in headers (`x-ai-provider`, `x-ai-key`, optionally
468
+ * `x-ai-base-url`). The handler builds a model for that one request and
469
+ * forgets the key — it's never stored or logged. Use it for plans where
470
+ * customers pay their AI vendor directly.
163
471
  *
164
472
  * Fetch-API handler (`Request` → `Response`): mounts in Node 18+, edge
165
473
  * runtimes, Next, Hono, Express (via an adapter).
166
474
  */
167
475
 
476
+ /** Header names a request uses to bring the user's own key. */
477
+ declare const USER_KEY_HEADERS: {
478
+ readonly provider: "x-ai-provider";
479
+ readonly key: "x-ai-key";
480
+ readonly baseUrl: "x-ai-base-url";
481
+ };
482
+ /** The user's own provider settings, as the browser sends them. */
483
+ interface UserCredentials {
484
+ provider: string;
485
+ apiKey?: string;
486
+ /** Only honored when the server allows it (`userKeys.allowBaseUrl`). */
487
+ baseUrl?: string;
488
+ }
489
+ interface UserKeyOptions {
490
+ /** Providers users may bring a key for (default: every hosted provider in the catalog). */
491
+ providers?: string[];
492
+ /**
493
+ * Let users send their own API root (OpenAI-compatible servers, gateways).
494
+ * Off by default: your server would call whatever URL a user sends.
495
+ */
496
+ allowBaseUrl?: boolean;
497
+ /** Refuse requests without the user's own key, even when the server has a model. */
498
+ required?: boolean;
499
+ /** Build the model for a user's key (default `createChatModel`): add headers, pin params, wrap with limits. */
500
+ create?: (options: CreateChatModelOptions, request: Request) => ChatModel;
501
+ fetch?: FetchLike;
502
+ }
168
503
  interface ChatHandlerOptions {
169
504
  /** Path prefix the handler is mounted at, e.g. "/api/assistant". */
170
505
  basePath?: string;
171
506
  /** Reject request bodies larger than this (default 2 MB). */
172
507
  maxBodyBytes?: number;
173
- /** Return a Response to refuse a request (auth, rate limits); undefined to allow. */
508
+ /** Return a Response to refuse a request (auth, rate limits, plan checks); undefined to allow. */
174
509
  authorize?: (request: Request) => Promise<Response | undefined> | Response | undefined;
175
510
  /** Adjust each request server-side (pin a model, cap tokens, add instructions). */
176
511
  prepare?: (request: ChatRequest) => ChatRequest;
512
+ /** Accept the user's own provider key per request (see the module note). Off by default. */
513
+ userKeys?: boolean | UserKeyOptions;
514
+ }
515
+ /** What `GET {base}/` tells the browser about bring-your-own-key. */
516
+ interface UserKeyInfo {
517
+ providers: Pick<AiProviderInfo, "id" | "label" | "needsKey" | "keyUrl" | "defaultModel" | "models">[];
518
+ allowBaseUrl: boolean;
519
+ required: boolean;
520
+ }
521
+ /**
522
+ * @param model The server's own model (its keys, from env or config), or
523
+ * `null` when every user brings their own key (`userKeys` must be on).
524
+ */
525
+ declare function createChatHandler(model: ChatModel | null, options?: ChatHandlerOptions): (request: Request) => Promise<Response | null>;
526
+ /** What the server said about itself (`GET {base}/`). */
527
+ interface RemoteModelInfo {
528
+ id: string;
529
+ label: string;
530
+ models?: ModelEntry[];
531
+ providers?: {
532
+ id: string;
533
+ label: string;
534
+ }[];
535
+ userKeys?: UserKeyInfo;
536
+ /** The server has no model of its own (or requires user keys): calls need the user's key. */
537
+ needsKey?: boolean;
177
538
  }
178
- declare function createChatHandler(model: ChatModel, options?: ChatHandlerOptions): (request: Request) => Promise<Response | null>;
179
539
  interface RemoteChatModelOptions {
180
540
  fetch?: FetchLike;
181
541
  headers?: Record<string, string>;
182
542
  /** Skip the GET for model info (id/label/models default to "remote"). */
183
- info?: {
184
- id: string;
185
- label: string;
186
- models?: {
187
- id: string;
188
- label?: string;
189
- }[];
190
- };
543
+ info?: RemoteModelInfo;
544
+ /**
545
+ * The user's own provider key, read on every call (so a settings change
546
+ * applies at once). Return nothing to use the server's model.
547
+ */
548
+ credentials?: () => UserCredentials | null | undefined;
191
549
  }
192
- /** The model a backend serves, as a browser-side `ChatModel`. */
193
- declare function remoteChatModel(endpoint: string, options?: RemoteChatModelOptions): Promise<ChatModel>;
550
+ /** Headers carrying the user's own key, for any request to a `createChatHandler` server. */
551
+ declare function userKeyHeaders(credentials: UserCredentials | null | undefined): Record<string, string>;
552
+ /** The model a backend serves, as a browser-side `ChatModel` (plus what the server said about itself in `info`). */
553
+ declare function remoteChatModel(endpoint: string, options?: RemoteChatModelOptions): Promise<ChatModel & {
554
+ info: RemoteModelInfo;
555
+ }>;
194
556
 
195
557
  interface RateLimitOptions {
196
558
  /** Tokens per rolling minute across every caller of this model (input + output). */
@@ -224,16 +586,32 @@ interface UnknownFields {
224
586
  /** Unknown payload fields for a command, judged against the best-matching schema branch. */
225
587
  declare function unknownFields(schema: unknown, payload: unknown): UnknownFields | null;
226
588
 
589
+ /**
590
+ * Checks an html clip's template the way the renderer will read it: as XML
591
+ * (it rasterizes through an SVG `foreignObject`). Markup that isn't
592
+ * well-formed renders nothing, silently, so the assistant rejects it up front
593
+ * with an error the model can fix.
594
+ */
595
+ /** Why the markup won't parse as XML, or undefined when it's well-formed. */
596
+ declare function markupProblem(markup: string): string | undefined;
597
+ /** `asset:<id>` references in a template. */
598
+ declare function assetRefs(markup: string): string[];
599
+
227
600
  /** What a tool's `run` gets. `project` is the turn's working copy. */
228
601
  interface ToolContext {
229
602
  project: Project;
230
603
  signal?: AbortSignal;
231
604
  }
232
- /** A tool's answer. `changes` are human-readable lines for the app's "what changed" list. */
605
+ /**
606
+ * A tool's answer. `changes` are human-readable lines for the app's "what
607
+ * changed" list. `images` go to the model with the result, so it can look at
608
+ * what it made (rendered frames); models that can't see get the text only.
609
+ */
233
610
  type ToolOutput = {
234
611
  ok: true;
235
612
  result?: unknown;
236
613
  changes?: string[];
614
+ images?: ChatImage[];
237
615
  } | {
238
616
  ok: false;
239
617
  error: unknown;
@@ -245,7 +623,8 @@ declare function defineTool(tool: AssistantTool): AssistantTool;
245
623
  /**
246
624
  * Adopt tools that come as definitions plus one runner — e.g.
247
625
  * `@miraiclip/audio-sources`' `audioToolDefinitions` + `runAudioTool`.
248
- * Accepts `{ name, description, input_schema }` or OpenAI function tools.
626
+ * Accepts `{ name, description, input_schema }`, OpenAI function tools or
627
+ * Gemini function declarations.
249
628
  */
250
629
  declare function toolsFromDefinitions(definitions: readonly unknown[], run: (name: string, input: Record<string, unknown>, ctx: ToolContext) => Promise<{
251
630
  ok: boolean;
@@ -263,6 +642,13 @@ interface EditorToolsOptions {
263
642
  transitionKinds?: string[];
264
643
  /** The command catalog to offer in apply_commands (default: the built-ins). Pass `project.commandCatalog()` when you register custom commands. */
265
644
  commandCatalog?: Record<string, unknown>;
645
+ /**
646
+ * Runs the continuous-shot check over the document (e.g. `checkShot` from `@miraiclip/server-export`, or the app's
647
+ * in-browser equivalent). The `check_sequence` tool is offered only when this is given, because the check needs a browser.
648
+ */
649
+ checkSequence?: (doc: ProjectDocument, options: {
650
+ clipId?: string;
651
+ }) => Promise<unknown>;
266
652
  }
267
653
  /**
268
654
  * The tools that edit a Miraiclip project: read state, look up a command's
@@ -315,12 +701,75 @@ type CommitResult = {
315
701
  /** Replay a fork's commands onto the real project as one undoable step. */
316
702
  declare function commitFork(project: Project, fork: RecordingProject, label?: string): CommitResult;
317
703
 
704
+ /**
705
+ * Skills: playbooks for bigger jobs (a launch video, captions, a reformat),
706
+ * loaded by the model only when a request needs one.
707
+ *
708
+ * The system prompt lists each skill's name and description. The model calls
709
+ * `load_skill` to read one, and `read_skill_file` for the reference files it
710
+ * points to, so the prompt stays small however many skills there are. An app
711
+ * can also start a request with a skill already loaded (`TurnOptions.skills`),
712
+ * e.g. from a button.
713
+ *
714
+ * Skills are plain Markdown with a front matter (`name`, `description`), the
715
+ * same shape as Claude Code skills. `MIRAICLIP_SKILLS` are the Miraiclip
716
+ * plugin's skills, in their editor versions.
717
+ */
718
+ interface AssistantSkill {
719
+ /** Lowercase letters, digits and dashes, e.g. "captions". */
720
+ name: string;
721
+ /** When to use it, in one or two sentences. The model picks skills by this. */
722
+ description: string;
723
+ /** The playbook (Markdown, without the front matter). */
724
+ instructions: string;
725
+ /** Reference files the playbook points to, by relative path ("reference/craft.md"). */
726
+ files?: Readonly<Record<string, string>>;
727
+ }
728
+ declare function defineSkill(skill: AssistantSkill): AssistantSkill;
729
+ /** Split `---\nkey: value\n---\nbody` into its fields and body. Values may be quoted. */
730
+ declare function parseFrontMatter(markdown: string): {
731
+ data: Record<string, string>;
732
+ body: string;
733
+ };
734
+ /** One skill from its Markdown (front matter `name` and `description`). */
735
+ declare function parseSkill(markdown: string, options?: {
736
+ name?: string;
737
+ description?: string;
738
+ files?: Record<string, string>;
739
+ }): AssistantSkill;
740
+ interface SkillsFromFilesOptions {
741
+ /**
742
+ * Which file in a skill folder is the playbook, in order of preference.
743
+ * Default `["SKILL.md"]`. The Miraiclip plugin keeps the Claude Code
744
+ * version in SKILL.md and the in-editor version in EDITOR.md, so an editor
745
+ * passes `["EDITOR.md"]` (folders without one are skipped). The other
746
+ * entry file's front matter fills in a missing name or description.
747
+ */
748
+ entry?: string[];
749
+ }
750
+ /**
751
+ * Skills from a folder of skill folders, given as `{ path: text }` — e.g.
752
+ * Vite's `import.meta.glob("./skills/**\/*.md", { query: "?raw", import: "default", eager: true })`.
753
+ * Every other `.md` in a skill's folder becomes one of its files.
754
+ */
755
+ declare function skillsFromFiles(files: Record<string, string>, options?: SkillsFromFilesOptions): AssistantSkill[];
756
+ /** The skill list for the system prompt. */
757
+ declare function describeSkills(skills: readonly AssistantSkill[]): string;
758
+ /** `load_skill` and `read_skill_file` for these skills. */
759
+ declare function skillTools(skills: readonly AssistantSkill[]): AssistantTool[];
760
+
318
761
  interface AssistantOptions {
319
762
  model: ChatModel;
320
763
  /** Default `editorTools()`. Add your own (audio, captions, stock media…). */
321
764
  tools?: AssistantTool[];
322
765
  /** Extra system instructions (brand voice, house rules). */
323
766
  instructions?: string;
767
+ /**
768
+ * Playbooks the model can load when a request needs one (e.g.
769
+ * `MIRAICLIP_SKILLS`). Adds `load_skill` and `read_skill_file` to the tools
770
+ * and lists each skill's name and description in the system prompt.
771
+ */
772
+ skills?: readonly AssistantSkill[];
324
773
  /** Model round-trips per request (default 12). */
325
774
  maxSteps?: number;
326
775
  /** Model id when the adapter serves several. */
@@ -342,6 +791,8 @@ type AssistantEvent = {
342
791
  ok: boolean;
343
792
  changes?: string[];
344
793
  error?: string;
794
+ /** Images the tool returned (previews, storyboards) — the model sees them too; apps may show them. */
795
+ images?: ChatImage[];
345
796
  } | {
346
797
  type: "step";
347
798
  index: number;
@@ -357,6 +808,13 @@ interface TurnOptions {
357
808
  onEvent?: (event: AssistantEvent) => void;
358
809
  /** History label for the undo step (default "Assistant: <request>"). */
359
810
  label?: string;
811
+ /**
812
+ * Skills to load up front for this request, by name (e.g. the user picked
813
+ * "Captions" in the app). Their playbooks go into the system prompt, so the
814
+ * model starts on them without a `load_skill` call. Unknown names are
815
+ * ignored.
816
+ */
817
+ skills?: readonly string[];
360
818
  }
361
819
  interface AssistantTurn {
362
820
  /** The model's final answer to the user. */
@@ -387,6 +845,9 @@ interface Assistant {
387
845
  declare const DEFAULT_INSTRUCTIONS = "You are the editing assistant inside a video editor. You change the user's project by calling tools; every change you make lands as ONE undo step when you finish, so act confidently.\n\nHow to work:\n- The current project is summarized below. Clip, track and asset ids there are what tools expect. Call get_state to re-read it after big changes.\n- Prefer the high-level tools when they fit (add_transition, animate_clip, add_effect, set_effects_enabled, set_background, trim_clip, set_keyframes, close_gaps, and any audio or media tools). Call tools directly; apply_commands takes only editing commands like \"clip/add\", never tool names. Use apply_commands for everything else, batching related commands; look up a command's payload with get_command_schema the first time you use it, and use only the fields it lists.\n- Fading a picture (text, image, logo, video) in or out is animate_clip (in / out: fade). fadeInUs / fadeOutUs are audio fades. \"Fade the video in at the start / out at the end\" means the first clip's entrance and the last clip's exit.\n- Constant values are set directly with clip/set-property: rotation (\"rotate 45\u00B0\", \"tilt\"), volume (\"lower to 30%\", \"louder\"), position, size, opacity. Only values that CHANGE over a time range (\"duck the music between 10 and 15 s\", \"fade to silence between 18 and 20 s\", \"spin one full turn\", \"pan across\") are keyframes: set_keyframes, with times in timeline seconds. Remove an animation with keyframe/clear.\n- \"Cuts\" are the ones listed in the project summary; use their times (e.g. one sound per cut).\n- \"Add N more seconds of footage\" adds a new clip (clip/add with trimStartUs for the source point) after the last one; it does not stretch an existing clip.\n- Caption looks (box behind the text, outline, shadow, colors, word preset) are the caption clip's style (clip/set-property { style }).\n- \"The whole video\" means from 0 to the composition length (trim_clip startSeconds / endSeconds).\n- A solid color background behind everything is set_background (add, recolor, remove). Other html clips are recolored through the params their template uses (shown in the clip details). A new track goes on top unless track/add gets index 0 (the bottom).\n- \"Replace X with Y\" removes X and adds Y in the same turn, whichever tool adds Y (audio tools take replaceClipId, which does both).\n- Solo / mute / lock / hide a track with track/set-property (solo, muted, locked, hidden). \"Solo X\" is solo: true on X, not muting the others.\n- Relative requests (\"bigger\", \"louder\", \"a bit brighter\", \"twice as big\") start from the current values in the clip details below: make a visible change in the asked direction (about 25% for \"bigger\"/\"smaller\", \u00D72 for \"twice\").\n- When something is selected, \"this\", \"it\", \"the clip\" and any request that names no target mean ONLY the selection, even with words like \"at the end\" (the end of that clip). Edit nothing else. \"Here\" / \"now\" / \"this point\" mean the playhead. \"Clips\" without a kind mean the video clips on the main video track.\n- \"Again\" / \"also\" / \"another\" mean keep the original and add a copy (clip/duplicate, or clip/add with the same asset); never move the original. \"Instead\" means move or replace.\n- Removing a clip leaves a gap; when asked to close it, also run close_gaps (or trim_clip with ripple).\n- Commands use integer microseconds (1 second = 1000000). High-level tools take seconds.\n- Positions (transform x, y) are fractions of the frame: 0.5, 0.5 is the center.\n- Only reference media that exists in the project or that a tool returned. Never invent file URLs.\n- If a tool fails, read the error, fix the input and retry. A failed apply_commands batch applied NONE of its commands: resend all of them, corrected. If something can't be done, say why.\n- If the request is ambiguous in a way that matters, ask one short question instead of guessing.\n\nWhen you're done, reply in one to three short sentences: what you changed, in plain words (times in seconds, names over ids). Describe only edits your tools reported as applied; if a tool says something changed nothing or failed, fix it or say so. No markdown headings.";
388
846
  declare function createAssistant(options: AssistantOptions): Assistant;
389
847
 
848
+ /** The Miraiclip plugin's skills, in their in-editor versions: pass to `createAssistant({ skills })`. */
849
+ declare const MIRAICLIP_SKILLS: readonly AssistantSkill[];
850
+
390
851
  type ScriptStep = string | {
391
852
  text?: string;
392
853
  toolCalls?: (Omit<ToolCall, "id"> & {
@@ -404,4 +865,4 @@ declare function scriptedChatModel(steps: ScriptStep[], options?: {
404
865
  requests: ChatRequest[];
405
866
  };
406
867
 
407
- export { type Assistant, type AssistantEvent, type AssistantOptions, type AssistantTool, type AssistantTurn, BACKGROUND_TEMPLATE, type ChatHandlerOptions, type ChatMessage, type ChatModel, ChatModelError, type ChatModelInfo, type ChatOptions, type ChatRequest, type ChatResponse, type ChatUsage, type CommitResult, DEFAULT_INSTRUCTIONS, type EditorToolsOptions, type FetchLike, type ForkOptions, type OpenAIChatModelOptions, type RateLimitOptions, type RecordingProject, type RemoteChatModelOptions, type ScriptStep, type ToolCall, type ToolContext, type ToolOutput, type ToolSpec, type TurnOptions, type UnknownFields, applyChecked, applyCommandsSchema, commitFork, createAssistant, createChatHandler, defineChatModel, defineTool, describeCommands, describeForAssistant, editorTools, forkProject, openAIChatModel, parseToolArguments, rateLimitedChatModel, remoteChatModel, retryDelayMs, scriptedChatModel, toOpenAIMessages, toolsFromDefinitions, unknownFields, withExplicitIds };
868
+ export { AI_PROVIDERS, type AiProviderInfo, type AnthropicChatModelOptions, type Assistant, type AssistantEvent, type AssistantOptions, type AssistantSkill, type AssistantTool, type AssistantTurn, BACKGROUND_TEMPLATE, type ChatHandlerOptions, type ChatImage, type ChatMessage, type ChatModel, ChatModelError, type ChatModelInfo, type ChatModelsFromEnvOptions, type ChatOptions, type ChatRequest, type ChatResponse, type ChatUsage, type CommitResult, type CreateChatModelOptions, DEFAULT_INSTRUCTIONS, type EditorToolsOptions, type FetchLike, type ForkOptions, type GeminiChatModelOptions, MIRAICLIP_SKILLS, type ModelEntry, type OpenAIChatModelOptions, type ProviderApi, type RateLimitOptions, type RecordingProject, type RemoteChatModelOptions, type RemoteModelInfo, type RoutedChatModelOptions, type ScriptStep, type SkillsFromFilesOptions, type ToolCall, type ToolContext, type ToolOutput, type ToolSpec, type TurnOptions, USER_KEY_HEADERS, type UnknownFields, type UserCredentials, type UserKeyInfo, type UserKeyOptions, aiProvider, anthropicChatModel, applyChecked, applyCommandsSchema, assetRefs, chatModelsFromEnv, commitFork, createAssistant, createChatHandler, createChatModel, defineChatModel, defineSkill, defineTool, describeCommands, describeForAssistant, describeSkills, editorTools, forkProject, geminiChatModel, markupProblem, openAIChatModel, parseFrontMatter, parseSkill, parseToolArguments, rateLimitedChatModel, remoteChatModel, retryDelayMs, routedChatModel, scriptedChatModel, skillTools, skillsFromFiles, splitModelRef, toAnthropicMessages, toGeminiContents, toOpenAIMessages, toolsFromDefinitions, unknownFields, userKeyHeaders, withExplicitIds };