@miraiclip/assistant 0.1.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.
@@ -0,0 +1,358 @@
1
+ import { Project, Command, ProjectDocument } from '@miraiclip/core';
2
+
3
+ /**
4
+ * The vendor-neutral model contract. An adapter turns these neutral messages
5
+ * and tools into one vendor's API and back; nothing else in the package knows
6
+ * which vendor is on the other end.
7
+ */
8
+ /** A tool call the model made. `arguments` is already parsed JSON. */
9
+ interface ToolCall {
10
+ id: string;
11
+ name: string;
12
+ arguments: Record<string, unknown>;
13
+ }
14
+ type ChatMessage = {
15
+ role: "system";
16
+ content: string;
17
+ } | {
18
+ role: "user";
19
+ content: string;
20
+ } | {
21
+ role: "assistant";
22
+ content: string;
23
+ toolCalls?: ToolCall[];
24
+ } | {
25
+ role: "tool";
26
+ toolCallId: string;
27
+ name: string;
28
+ content: string;
29
+ };
30
+ /** A tool as the model sees it: name, description and a JSON Schema for its input. */
31
+ interface ToolSpec {
32
+ name: string;
33
+ description: string;
34
+ inputSchema: Record<string, unknown>;
35
+ }
36
+ interface ChatRequest {
37
+ messages: ChatMessage[];
38
+ tools?: ToolSpec[];
39
+ /** Model id, when the adapter serves several. */
40
+ model?: string;
41
+ maxOutputTokens?: number;
42
+ /** Vendor-specific request fields, passed through untouched (e.g. `{ reasoning_effort: "low" }`). */
43
+ params?: Record<string, unknown>;
44
+ }
45
+ interface ChatUsage {
46
+ inputTokens?: number;
47
+ outputTokens?: number;
48
+ }
49
+ interface ChatResponse {
50
+ content: string;
51
+ toolCalls: ToolCall[];
52
+ /** Why the model stopped. */
53
+ finish: "stop" | "tool_calls" | "length" | "content_filter" | "other";
54
+ model?: string;
55
+ usage?: ChatUsage;
56
+ }
57
+ interface ChatOptions {
58
+ signal?: AbortSignal;
59
+ /** Streaming text, as it arrives (adapters that can't stream call it once). */
60
+ onText?: (delta: string) => void;
61
+ }
62
+ interface ChatModelInfo {
63
+ /** Adapter id, e.g. "openai". Alphanumeric with - or _. */
64
+ id: string;
65
+ label: string;
66
+ /** Models this adapter serves; the first is the default. */
67
+ models?: {
68
+ id: string;
69
+ label?: string;
70
+ }[];
71
+ }
72
+ interface ChatModel extends ChatModelInfo {
73
+ complete(request: ChatRequest, options?: ChatOptions): Promise<ChatResponse>;
74
+ }
75
+ /** An error from a model vendor, carrying its HTTP status when there was one. */
76
+ declare class ChatModelError extends Error {
77
+ readonly provider: string;
78
+ readonly status?: number | undefined;
79
+ constructor(provider: string, message: string, status?: number | undefined);
80
+ }
81
+ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
82
+ /** Check an adapter's shape once, at registration. */
83
+ declare function defineChatModel<M extends ChatModel>(model: M): M;
84
+
85
+ /**
86
+ * OpenAI adapter, over the Chat Completions API with function calling.
87
+ *
88
+ * Chat Completions is also what most other servers speak (Azure OpenAI,
89
+ * OpenRouter, Groq, Together, vLLM, Ollama, LM Studio…), so `baseUrl` points
90
+ * this one adapter at any of them.
91
+ */
92
+
93
+ interface OpenAIChatModelOptions {
94
+ /** Model id, e.g. "gpt-5.4-mini". Required: model names change faster than this package. */
95
+ model: string;
96
+ /** Other models the app may pick per request (`ChatRequest.model`). */
97
+ models?: {
98
+ id: string;
99
+ label?: string;
100
+ }[];
101
+ /** API key. Omit for local servers that don't need one. Keep it server-side (see `createChatHandler`). */
102
+ apiKey?: string;
103
+ /** Default https://api.openai.com/v1. Any OpenAI-compatible base URL works. */
104
+ baseUrl?: string;
105
+ organization?: string;
106
+ project?: string;
107
+ headers?: Record<string, string>;
108
+ /** Default request fields merged into every call (e.g. `{ reasoning_effort: "low" }`). */
109
+ params?: Record<string, unknown>;
110
+ /** Stream responses (default true). */
111
+ stream?: boolean;
112
+ /** Adapter id / label (default "openai" / "OpenAI"), e.g. "ollama" when pointing elsewhere. */
113
+ id?: string;
114
+ label?: string;
115
+ fetch?: FetchLike;
116
+ }
117
+ type OpenAIMessage = {
118
+ role: "system" | "user";
119
+ content: string;
120
+ } | {
121
+ role: "assistant";
122
+ content: string | null;
123
+ tool_calls?: {
124
+ id: string;
125
+ type: "function";
126
+ function: {
127
+ name: string;
128
+ arguments: string;
129
+ };
130
+ }[];
131
+ } | {
132
+ role: "tool";
133
+ tool_call_id: string;
134
+ content: string;
135
+ };
136
+ /** Neutral messages → Chat Completions messages. */
137
+ declare function toOpenAIMessages(messages: readonly ChatMessage[]): OpenAIMessage[];
138
+ /** Parse a tool call's JSON arguments; unparseable JSON is reported to the model as a tool error. */
139
+ declare function parseToolArguments(raw: string): Record<string, unknown>;
140
+ declare function openAIChatModel(options: OpenAIChatModelOptions): ChatModel;
141
+
142
+ /**
143
+ * Keys stay on the server: run the real model adapter in your backend behind
144
+ * `createChatHandler`, and give the browser `remoteChatModel(endpoint)` — a
145
+ * proxy with the same `ChatModel` shape. The agent loop and the tools run in
146
+ * the browser, next to the project; only model calls cross the network.
147
+ *
148
+ * GET {base}/ → { id, label, models }
149
+ * POST {base}/chat { request: ChatRequest } →
150
+ * application/x-ndjson, one JSON object per line:
151
+ * { "type": "text", "delta": "…" } (as it streams)
152
+ * { "type": "done", "response": ChatResponse }
153
+ * { "type": "error", "error": "…", "status": 401 }
154
+ * errors before streaming → { error } with 400 / 404 / 413, or the vendor's status
155
+ *
156
+ * Fetch-API handler (`Request` → `Response`): mounts in Node 18+, edge
157
+ * runtimes, Next, Hono, Express (via an adapter).
158
+ */
159
+
160
+ interface ChatHandlerOptions {
161
+ /** Path prefix the handler is mounted at, e.g. "/api/assistant". */
162
+ basePath?: string;
163
+ /** Reject request bodies larger than this (default 2 MB). */
164
+ maxBodyBytes?: number;
165
+ /** Return a Response to refuse a request (auth, rate limits); undefined to allow. */
166
+ authorize?: (request: Request) => Promise<Response | undefined> | Response | undefined;
167
+ /** Adjust each request server-side (pin a model, cap tokens, add instructions). */
168
+ prepare?: (request: ChatRequest) => ChatRequest;
169
+ }
170
+ declare function createChatHandler(model: ChatModel, options?: ChatHandlerOptions): (request: Request) => Promise<Response | null>;
171
+ interface RemoteChatModelOptions {
172
+ fetch?: FetchLike;
173
+ headers?: Record<string, string>;
174
+ /** Skip the GET for model info (id/label/models default to "remote"). */
175
+ info?: {
176
+ id: string;
177
+ label: string;
178
+ models?: {
179
+ id: string;
180
+ label?: string;
181
+ }[];
182
+ };
183
+ }
184
+ /** The model a backend serves, as a browser-side `ChatModel`. */
185
+ declare function remoteChatModel(endpoint: string, options?: RemoteChatModelOptions): Promise<ChatModel>;
186
+
187
+ /** What a tool's `run` gets. `project` is the turn's working copy. */
188
+ interface ToolContext {
189
+ project: Project;
190
+ signal?: AbortSignal;
191
+ }
192
+ /** A tool's answer. `changes` are human-readable lines for the app's "what changed" list. */
193
+ type ToolOutput = {
194
+ ok: true;
195
+ result?: unknown;
196
+ changes?: string[];
197
+ } | {
198
+ ok: false;
199
+ error: unknown;
200
+ };
201
+ interface AssistantTool extends ToolSpec {
202
+ run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolOutput> | ToolOutput;
203
+ }
204
+ declare function defineTool(tool: AssistantTool): AssistantTool;
205
+ /**
206
+ * Adopt tools that come as definitions plus one runner — e.g.
207
+ * `@miraiclip/audio-sources`' `audioToolDefinitions` + `runAudioTool`.
208
+ * Accepts `{ name, description, input_schema }` or OpenAI function tools.
209
+ */
210
+ declare function toolsFromDefinitions(definitions: readonly unknown[], run: (name: string, input: Record<string, unknown>, ctx: ToolContext) => Promise<{
211
+ ok: boolean;
212
+ result?: unknown;
213
+ error?: unknown;
214
+ }>, options?: {
215
+ changes?: (name: string, input: Record<string, unknown>, result: unknown) => string[] | undefined;
216
+ }): AssistantTool[];
217
+ /** One line per command, for a "what changed" list (keyframe edits fold into one line per clip). */
218
+ declare function describeCommands(commands: readonly Command[]): string[];
219
+ interface EditorToolsOptions {
220
+ /** Transition kinds to offer (default: the built-ins). Add your registered custom kinds. */
221
+ transitionKinds?: string[];
222
+ /** The command catalog to offer in apply_commands (default: the built-ins). Pass `project.commandCatalog()` when you register custom commands. */
223
+ commandCatalog?: Record<string, unknown>;
224
+ }
225
+ /**
226
+ * The tools that edit a Miraiclip project: read state, look up a command's
227
+ * schema, apply command batches, and two high-level tools (transitions,
228
+ * animation presets) that use the same logic as the editor's panels.
229
+ */
230
+ declare function editorTools(options?: EditorToolsOptions): AssistantTool[];
231
+ /** apply_commands' input schema: `type` is an enum of the catalog's commands. */
232
+ declare function applyCommandsSchema(catalog: Record<string, unknown>): Record<string, unknown>;
233
+
234
+ /**
235
+ * A working copy of a project that records every command that sticks.
236
+ *
237
+ * The assistant edits the copy while the model thinks (async, many steps);
238
+ * the real project only changes when the turn commits, by replaying the
239
+ * recorded commands in one transaction — one undo step, and nothing half-done
240
+ * if the request fails. Commands inside a transaction that rolls back are
241
+ * dropped, exactly as the copy drops their effects.
242
+ *
243
+ * Replay must produce the same ids the model saw, so commands whose ids are
244
+ * optional get explicit ones before they run.
245
+ */
246
+ interface RecordingProject extends Project {
247
+ /** Commands that changed the copy, in order. */
248
+ readonly recorded: readonly Command[];
249
+ }
250
+ interface ForkOptions {
251
+ /** Build the copy (default: `createProject(doc)`). Register custom commands here. */
252
+ createFork?: (doc: ReturnType<Project["toJSON"]>) => Project;
253
+ /** Give commands explicit ids (default handles the built-ins with optional ids). */
254
+ ensureIds?: (command: Command) => Command;
255
+ }
256
+ /** Built-in commands that would otherwise get a random id inside the engine. */
257
+ declare function withExplicitIds(command: Command): Command;
258
+ declare function forkProject(project: Project, options?: ForkOptions): RecordingProject;
259
+ type CommitResult = {
260
+ ok: true;
261
+ applied: number;
262
+ } | {
263
+ ok: false;
264
+ error: string;
265
+ };
266
+ /** Replay a fork's commands onto the real project as one undoable step. */
267
+ declare function commitFork(project: Project, fork: RecordingProject, label?: string): CommitResult;
268
+
269
+ interface AssistantOptions {
270
+ model: ChatModel;
271
+ /** Default `editorTools()`. Add your own (audio, captions, stock media…). */
272
+ tools?: AssistantTool[];
273
+ /** Extra system instructions (brand voice, house rules). */
274
+ instructions?: string;
275
+ /** Model round-trips per request (default 12). */
276
+ maxSteps?: number;
277
+ /** Model id when the adapter serves several. */
278
+ modelId?: string;
279
+ maxOutputTokens?: number;
280
+ /** Vendor request fields, passed through. */
281
+ params?: Record<string, unknown>;
282
+ fork?: ForkOptions;
283
+ }
284
+ type AssistantEvent = {
285
+ type: "text";
286
+ delta: string;
287
+ } | {
288
+ type: "tool-start";
289
+ call: ToolCall;
290
+ } | {
291
+ type: "tool-end";
292
+ call: ToolCall;
293
+ ok: boolean;
294
+ changes?: string[];
295
+ error?: string;
296
+ } | {
297
+ type: "step";
298
+ index: number;
299
+ };
300
+ interface TurnOptions {
301
+ /** Earlier messages of this conversation (`turn.messages` from the last turn). */
302
+ history?: readonly ChatMessage[];
303
+ /** What the user is looking at, e.g. "Selected: clip t-1. Playhead: 4.2s." */
304
+ context?: string;
305
+ /** "auto" (default) commits when the request finishes; "review" waits for `turn.apply()`. */
306
+ apply?: "auto" | "review";
307
+ signal?: AbortSignal;
308
+ onEvent?: (event: AssistantEvent) => void;
309
+ /** History label for the undo step (default "Assistant: <request>"). */
310
+ label?: string;
311
+ }
312
+ interface AssistantTurn {
313
+ /** The model's final answer to the user. */
314
+ reply: string;
315
+ /** Human-readable list of what the turn changed (empty when nothing). */
316
+ changes: string[];
317
+ /** The commands the turn makes, in order (replayed onto the project on apply). */
318
+ commands: readonly Command[];
319
+ /**
320
+ * applied: committed as one undo step. review: waiting for apply()/discard().
321
+ * no-changes: nothing to apply. failed / canceled: the project wasn't touched.
322
+ */
323
+ status: "applied" | "review" | "no-changes" | "failed" | "canceled";
324
+ error?: string;
325
+ /** The project as the turn left it (for previews in review mode). */
326
+ preview: ProjectDocument;
327
+ /** The conversation so far, without the system prompt: pass back as `history`. */
328
+ messages: ChatMessage[];
329
+ usage: Required<ChatUsage>;
330
+ /** Commit a reviewed turn (no-op once applied). */
331
+ apply(): CommitResult;
332
+ discard(): void;
333
+ }
334
+ interface Assistant {
335
+ readonly tools: readonly AssistantTool[];
336
+ run(project: Project, request: string, options?: TurnOptions): Promise<AssistantTurn>;
337
+ }
338
+ 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, and any audio or media tools). 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.\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. 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). No markdown headings.";
339
+ declare function createAssistant(options: AssistantOptions): Assistant;
340
+
341
+ type ScriptStep = string | {
342
+ text?: string;
343
+ toolCalls?: (Omit<ToolCall, "id"> & {
344
+ id?: string;
345
+ })[];
346
+ } | ((request: ChatRequest, index: number) => ScriptStep);
347
+ /**
348
+ * A model that answers from a script: for tests, demos and offline
349
+ * development. Each `complete` call takes the next step; a string is a final
350
+ * text answer, an object can call tools. Requests are kept in `requests`.
351
+ */
352
+ declare function scriptedChatModel(steps: ScriptStep[], options?: {
353
+ id?: string;
354
+ }): ChatModel & {
355
+ requests: ChatRequest[];
356
+ };
357
+
358
+ export { type Assistant, type AssistantEvent, type AssistantOptions, type AssistantTool, type AssistantTurn, 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 RecordingProject, type RemoteChatModelOptions, type ScriptStep, type ToolCall, type ToolContext, type ToolOutput, type ToolSpec, type TurnOptions, applyCommandsSchema, commitFork, createAssistant, createChatHandler, defineChatModel, defineTool, describeCommands, editorTools, forkProject, openAIChatModel, parseToolArguments, remoteChatModel, scriptedChatModel, toOpenAIMessages, toolsFromDefinitions, withExplicitIds };