@tangle-network/agent-app 0.11.1 → 0.12.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/TimelineEditor-OXPJZDP2.js +12 -0
- package/dist/TimelineEditor-OXPJZDP2.js.map +1 -0
- package/dist/apply-Cp8c3K9D.d.ts +249 -0
- package/dist/chunk-3WAJWYKD.js +1730 -0
- package/dist/chunk-3WAJWYKD.js.map +1 -0
- package/dist/chunk-CF5DZELC.js +111 -0
- package/dist/chunk-CF5DZELC.js.map +1 -0
- package/dist/{chunk-4YTWB5MG.js → chunk-ETX4O4BB.js} +98 -1
- package/dist/chunk-ETX4O4BB.js.map +1 -0
- package/dist/chunk-IHR6K3GF.js +2367 -0
- package/dist/chunk-IHR6K3GF.js.map +1 -0
- package/dist/{chunk-OLCVUGGI.js → chunk-IJZJWKUK.js} +1 -61
- package/dist/chunk-IJZJWKUK.js.map +1 -0
- package/dist/chunk-ZYBWGSAZ.js +130 -0
- package/dist/chunk-ZYBWGSAZ.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +128 -6
- package/dist/mcp-CIupfjxV.d.ts +112 -0
- package/dist/runtime/index.d.ts +108 -1
- package/dist/runtime/index.js +7 -1
- package/dist/sequences/drizzle.d.ts +1244 -0
- package/dist/sequences/drizzle.js +368 -0
- package/dist/sequences/drizzle.js.map +1 -0
- package/dist/sequences/index.d.ts +327 -0
- package/dist/sequences/index.js +114 -0
- package/dist/sequences/index.js.map +1 -0
- package/dist/sequences-react/index.d.ts +752 -0
- package/dist/sequences-react/index.js +241 -0
- package/dist/sequences-react/index.js.map +1 -0
- package/dist/store-gckrNq-g.d.ts +242 -0
- package/dist/tools/index.d.ts +24 -108
- package/dist/tools/index.js +12 -6
- package/package.json +37 -2
- package/dist/chunk-4YTWB5MG.js.map +0 -1
- package/dist/chunk-OLCVUGGI.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sequences/operations.ts","../src/sequences/validate.ts","../src/sequences/apply.ts","../src/sequences/exports.ts","../src/sequences/captions.ts","../src/sequences/mcp-tools.ts","../src/sequences/mcp-handler.ts","../src/sequences/mcp-entry.ts"],"sourcesContent":["/**\n * The sequence operation union — the ONE mutation vocabulary shared by every\n * writer of a timeline: the MCP tool dispatcher (agent edits), the React\n * editor's command stack (human edits, undo/redo), and batch agent plans.\n * Positions are integer frames; the seconds→frames conversion happens at the\n * tool-argument edge (./mcp), never here.\n *\n * Validation lives in ./validate (`validateSequenceOperations`) and runs\n * against a `SequenceTimeline` BEFORE any store write; application lives in\n * ./apply (`applySequenceOperation`) which maps one validated operation to\n * `SequenceStore` calls. Keeping the union closed and frame-typed is what lets\n * undo work: every operation has a computable inverse given the pre-state.\n */\n\nimport type { SequenceExportFormat, SequenceTrackKind } from './model'\n\nexport interface PlaceClipOperation {\n type: 'place_clip'\n /** Omitted → first unlocked track matching the media kind. */\n trackId?: string\n label: string\n startFrame: number\n durationFrames: number\n sourceInFrame?: number\n /** Explicit source out-point; null/omitted → natural end of the source.\n * Carried so the durable inverse of a delete can restore a split clip's\n * exact playable window. */\n sourceOutFrame?: number | null\n /** Create the clip disabled; omitted → enabled. Carried so the durable\n * inverse of deleting a disabled clip does not resurrect it visible. */\n disabled?: boolean\n media?: { url: string; kind: 'video' | 'image' | 'audio' }\n generationId?: string\n assetId?: string\n metadata?: Record<string, unknown>\n}\n\nexport interface AddCaptionOperation {\n type: 'add_caption'\n text: string\n /** BCP-47 tag; omitted → sequence default language. */\n language?: string\n /** Omitted → placed near the playhead via `chooseCaptionPlacement`. */\n startFrame?: number\n durationFrames?: number\n /** Target caption track; omitted → first unlocked caption track, creating\n * a per-language track when `language` names one that has none. */\n trackId?: string\n}\n\nexport interface MoveClipOperation {\n type: 'move_clip'\n clipId: string\n startFrame: number\n trackId?: string\n}\n\nexport interface TrimClipOperation {\n type: 'trim_clip'\n clipId: string\n startFrame: number\n durationFrames: number\n /** New source in-point when trimming the head; omitted → unchanged. */\n sourceInFrame?: number\n /** New source out-point; null releases it to the source's natural end;\n * omitted → unchanged. Required when extending a clip whose stored window\n * (e.g. a split head's cut point) is too short for the new duration, and\n * by the durable inverse of `split_clip` to restore the original window. */\n sourceOutFrame?: number | null\n}\n\nexport interface SplitClipOperation {\n type: 'split_clip'\n clipId: string\n /** Sequence-frame to cut at; must fall strictly inside the clip. */\n atFrame: number\n}\n\nexport interface SetClipTextOperation {\n type: 'set_clip_text'\n clipId: string\n text: string\n language?: string\n}\n\nexport interface SetClipDisabledOperation {\n type: 'set_clip_disabled'\n clipId: string\n disabled: boolean\n}\n\nexport interface DeleteClipOperation {\n type: 'delete_clip'\n clipId: string\n}\n\nexport interface CreateTrackOperation {\n type: 'create_track'\n kind: SequenceTrackKind\n name: string\n}\n\nexport interface ExtendSequenceOperation {\n type: 'extend_sequence'\n durationFrames: number\n}\n\nexport interface QueueExportOperation {\n type: 'queue_export'\n format: SequenceExportFormat\n metadata?: Record<string, unknown>\n}\n\nexport type SequenceOperation =\n | PlaceClipOperation\n | AddCaptionOperation\n | MoveClipOperation\n | TrimClipOperation\n | SplitClipOperation\n | SetClipTextOperation\n | SetClipDisabledOperation\n | DeleteClipOperation\n | CreateTrackOperation\n | ExtendSequenceOperation\n | QueueExportOperation\n\n/** A batch of operations with the agent's stated intent — the decision-log\n * unit for agent edits. */\nexport interface SequencePlan {\n summary: string\n operations: SequenceOperation[]\n}\n\nexport type SequenceOperationType = SequenceOperation['type']\n\nexport const SEQUENCE_OPERATION_TYPES: readonly SequenceOperationType[] = [\n 'place_clip',\n 'add_caption',\n 'move_clip',\n 'trim_clip',\n 'split_clip',\n 'set_clip_text',\n 'set_clip_disabled',\n 'delete_clip',\n 'create_track',\n 'extend_sequence',\n 'queue_export',\n] as const\n","/**\n * Pre-write validation for sequence operations. Every rule runs against a\n * `SequenceTimeline` snapshot BEFORE any `SequenceStore` write, so a rejected\n * plan leaves no partial state. Batch errors carry the shape\n * `operation N (type): reason` — precise enough for an LLM planner to repair\n * the offending operation and resubmit.\n *\n * The resolution helpers (`resolvePlaceClipTrack`, `resolveCaptionTarget`,\n * `resolveCaptionPlacement`) are shared with ./apply so validation and\n * application cannot disagree about which track or bounds an operation lands\n * on.\n *\n * Validation is static: a batch is checked against the timeline as given, so\n * an operation may not reference entities created by an earlier operation in\n * the same batch. Dispatchers that chain operations must refresh the timeline\n * between applications and validate per-operation.\n */\n\nimport { assertClipFitsSequence, chooseCaptionPlacement, trackIntervals } from './model'\nimport type {\n SequenceClip,\n SequenceExportFormat,\n SequenceMediaKind,\n SequenceTimeline,\n SequenceTrack,\n SequenceTrackKind,\n TimelineClipBounds,\n} from './model'\nimport type {\n AddCaptionOperation,\n CreateTrackOperation,\n DeleteClipOperation,\n ExtendSequenceOperation,\n MoveClipOperation,\n PlaceClipOperation,\n QueueExportOperation,\n SequenceOperation,\n SetClipDisabledOperation,\n SetClipTextOperation,\n SplitClipOperation,\n TrimClipOperation,\n} from './operations'\nimport { SEQUENCE_OPERATION_TYPES } from './operations'\n\n/** Editor/agent context an operation is resolved against. `playheadFrame` is\n * the implicit position for omitted caption placement; never persisted. */\nexport interface SequenceOperationContext {\n playheadFrame: number\n}\n\n/** Runtime membership sets typed as exhaustive Records so adding a model\n * variant fails compilation here instead of silently passing junk through. */\nconst TRACK_KINDS: Record<SequenceTrackKind, true> = {\n video: true,\n audio: true,\n caption: true,\n reference: true,\n agent: true,\n}\n\nconst EXPORT_FORMATS: Record<SequenceExportFormat, true> = {\n mp4: true,\n otio: true,\n xml: true,\n edl: true,\n vtt: true,\n srt: true,\n contact_sheet: true,\n}\n\nconst MEDIA_KINDS: Record<SequenceMediaKind, true> = {\n video: true,\n image: true,\n audio: true,\n}\n\n/** Loose BCP-47 shape — enough to keep junk out of track names and clip rows\n * without shipping a full registry. */\nconst LANGUAGE_TAG = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/\n\nexport function validateSequenceOperations(\n timeline: SequenceTimeline,\n operations: SequenceOperation[],\n ctx: SequenceOperationContext,\n): void {\n assertPlayheadFrame(ctx.playheadFrame)\n operations.forEach((operation, index) => {\n try {\n validateSequenceOperation(timeline, operation, ctx)\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(`operation ${index + 1} (${operation.type}): ${reason}`)\n }\n })\n}\n\nexport function validateSequenceOperation(\n timeline: SequenceTimeline,\n operation: SequenceOperation,\n ctx: SequenceOperationContext,\n): void {\n switch (operation.type) {\n case 'place_clip':\n return validatePlaceClip(timeline, operation)\n case 'add_caption':\n return validateAddCaption(timeline, operation, ctx)\n case 'move_clip':\n return validateMoveClip(timeline, operation)\n case 'trim_clip':\n return validateTrimClip(timeline, operation)\n case 'split_clip':\n return validateSplitClip(timeline, operation)\n case 'set_clip_text':\n return validateSetClipText(timeline, operation)\n case 'set_clip_disabled':\n return validateSetClipDisabled(timeline, operation)\n case 'delete_clip':\n return validateDeleteClip(timeline, operation)\n case 'create_track':\n return validateCreateTrack(operation)\n case 'extend_sequence':\n return validateExtendSequence(timeline, operation)\n case 'queue_export':\n return validateQueueExport(operation)\n default: {\n // The union is closed at compile time; operations parsed from LLM JSON\n // can still arrive with junk types at runtime.\n const unknown = operation as { type?: unknown }\n throw new Error(`unsupported operation type ${JSON.stringify(unknown.type)}`)\n }\n }\n}\n\nexport function validatePlaceClip(timeline: SequenceTimeline, operation: PlaceClipOperation): void {\n if (operation.label.trim().length === 0) throw new Error('label must be non-empty')\n if (operation.media) {\n if (!(operation.media.kind in MEDIA_KINDS)) {\n throw new Error(`unsupported media kind ${JSON.stringify(operation.media.kind)}`)\n }\n assertSequenceMediaUrl(operation.media.url)\n }\n if (operation.sourceInFrame !== undefined) assertSourceInFrame(operation.sourceInFrame)\n if (operation.sourceOutFrame !== undefined) {\n assertSourceWindow(operation.sourceInFrame ?? 0, operation.sourceOutFrame, operation.durationFrames)\n }\n assertOperationBounds(timeline, { startFrame: operation.startFrame, durationFrames: operation.durationFrames })\n resolvePlaceClipTrack(timeline, operation)\n}\n\nexport function validateAddCaption(\n timeline: SequenceTimeline,\n operation: AddCaptionOperation,\n ctx: SequenceOperationContext,\n): void {\n if (operation.text.trim().length === 0) throw new Error('text must be non-empty')\n if (operation.language !== undefined) assertLanguageTag(operation.language)\n const target = resolveCaptionTarget(timeline, operation)\n const placement = resolveCaptionPlacement(\n timeline,\n operation,\n ctx,\n target.kind === 'existing' ? target.track.id : null,\n )\n // The auto path lands inside a free in-bounds gap (or throws) inside\n // chooseCaptionPlacement; only explicit placement can land out of bounds.\n if (operation.startFrame !== undefined || operation.durationFrames !== undefined) {\n assertOperationBounds(timeline, placement)\n }\n}\n\nexport function validateMoveClip(timeline: SequenceTimeline, operation: MoveClipOperation): void {\n const { clip, track } = requireMutableClip(timeline, operation.clipId)\n assertOperationBounds(timeline, { startFrame: operation.startFrame, durationFrames: clip.durationFrames })\n if (operation.trackId !== undefined) {\n const destination = requireTrack(timeline, operation.trackId)\n assertUnlocked(destination)\n if (destination.kind !== track.kind) {\n throw new Error(`moves a ${track.kind} clip to a ${destination.kind} track (${destination.id})`)\n }\n }\n}\n\nexport function validateTrimClip(timeline: SequenceTimeline, operation: TrimClipOperation): void {\n const { clip } = requireMutableClip(timeline, operation.clipId)\n if (operation.sourceInFrame !== undefined) assertSourceInFrame(operation.sourceInFrame)\n assertOperationBounds(timeline, { startFrame: operation.startFrame, durationFrames: operation.durationFrames })\n // Source-window invariant: the trimmed clip may never claim more source\n // frames than its (possibly updated) in/out window holds — otherwise a split\n // head ends up with duration > (out − in) and exports contradict the stored\n // out-point.\n const sourceInFrame = operation.sourceInFrame ?? clip.sourceInFrame\n const sourceOutFrame = operation.sourceOutFrame === undefined ? clip.sourceOutFrame : operation.sourceOutFrame\n assertSourceWindow(sourceInFrame, sourceOutFrame, operation.durationFrames)\n}\n\nexport function validateSplitClip(timeline: SequenceTimeline, operation: SplitClipOperation): void {\n const { clip } = requireMutableClip(timeline, operation.clipId)\n if (!Number.isInteger(operation.atFrame)) throw new Error('atFrame must be an integer')\n if (clip.durationFrames < 2) {\n throw new Error(`clip ${clip.id} is ${clip.durationFrames} frame(s) long; splitting needs at least 2 frames`)\n }\n const endFrame = clip.startFrame + clip.durationFrames\n if (operation.atFrame <= clip.startFrame || operation.atFrame >= endFrame) {\n throw new Error(\n `atFrame ${operation.atFrame} must fall strictly inside clip ${clip.id} (valid range ${clip.startFrame + 1}..${endFrame - 1})`,\n )\n }\n}\n\nexport function validateSetClipText(timeline: SequenceTimeline, operation: SetClipTextOperation): void {\n const { track } = requireMutableClip(timeline, operation.clipId)\n if (track.kind !== 'caption') {\n throw new Error(`targets a clip on a ${track.kind} track; text edits apply only to caption clips`)\n }\n if (operation.text.trim().length === 0) {\n throw new Error('text must be non-empty; use delete_clip to remove a caption')\n }\n if (operation.language !== undefined) assertLanguageTag(operation.language)\n}\n\nexport function validateSetClipDisabled(timeline: SequenceTimeline, operation: SetClipDisabledOperation): void {\n requireMutableClip(timeline, operation.clipId)\n}\n\nexport function validateDeleteClip(timeline: SequenceTimeline, operation: DeleteClipOperation): void {\n requireMutableClip(timeline, operation.clipId)\n}\n\nexport function validateCreateTrack(operation: CreateTrackOperation): void {\n if (!(operation.kind in TRACK_KINDS)) throw new Error(`unsupported track kind ${JSON.stringify(operation.kind)}`)\n if (operation.name.trim().length === 0) throw new Error('name must be non-empty')\n}\n\nexport function validateExtendSequence(timeline: SequenceTimeline, operation: ExtendSequenceOperation): void {\n if (!Number.isInteger(operation.durationFrames) || operation.durationFrames <= 0) {\n throw new Error('durationFrames must be a positive integer')\n }\n const lastEnd = lastClipEndFrame(timeline)\n if (operation.durationFrames < lastEnd) {\n throw new Error(`durationFrames ${operation.durationFrames} is below the last clip end (frame ${lastEnd})`)\n }\n}\n\nexport function validateQueueExport(operation: QueueExportOperation): void {\n if (!(operation.format in EXPORT_FORMATS)) {\n throw new Error(`unsupported export format ${JSON.stringify(operation.format)}`)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire parsing — the editor-persistence edge\n// ---------------------------------------------------------------------------\n\n/**\n * Shape-gate untrusted JSON (a product's `onApplyOperations` route body) into\n * `SequenceOperation[]` BEFORE `validateSequenceOperations` sees it. The\n * validator assumes well-typed fields (`label.trim()` on a number is a raw\n * TypeError → 500); this parser turns junk into a thrown Error naming the\n * operation index and field so the route can answer 400 with an actionable\n * reason. Unknown fields are dropped — only vocabulary fields reach the\n * validator and store.\n */\nexport function parseSequenceOperations(input: unknown): SequenceOperation[] {\n if (!Array.isArray(input)) throw new Error('operations must be an array of sequence operations')\n if (input.length === 0) throw new Error('operations must contain at least one operation')\n return input.map((raw, index) => {\n try {\n return parseSequenceOperation(raw)\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(`operations[${index}]: ${reason}`)\n }\n })\n}\n\nfunction parseSequenceOperation(raw: unknown): SequenceOperation {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error('each operation must be an object with a type field')\n }\n const record = raw as Record<string, unknown>\n const type = record.type\n if (typeof type !== 'string' || !(SEQUENCE_OPERATION_TYPES as readonly string[]).includes(type)) {\n throw new Error(`type must be one of: ${SEQUENCE_OPERATION_TYPES.join(', ')} (got ${JSON.stringify(type)})`)\n }\n switch (type as SequenceOperation['type']) {\n case 'place_clip':\n return {\n type: 'place_clip',\n label: readString(record, 'label'),\n startFrame: readInt(record, 'startFrame'),\n durationFrames: readInt(record, 'durationFrames'),\n ...readOptional(record, 'trackId', readString),\n ...readOptional(record, 'sourceInFrame', readInt),\n ...readOptionalNullable(record, 'sourceOutFrame', readInt),\n ...readOptional(record, 'disabled', readBool),\n ...readOptional(record, 'media', readMedia),\n ...readOptional(record, 'generationId', readString),\n ...readOptional(record, 'assetId', readString),\n ...readOptional(record, 'metadata', readRecord),\n }\n case 'add_caption':\n return {\n type: 'add_caption',\n text: readString(record, 'text'),\n ...readOptional(record, 'language', readString),\n ...readOptional(record, 'startFrame', readInt),\n ...readOptional(record, 'durationFrames', readInt),\n ...readOptional(record, 'trackId', readString),\n }\n case 'move_clip':\n return {\n type: 'move_clip',\n clipId: readString(record, 'clipId'),\n startFrame: readInt(record, 'startFrame'),\n ...readOptional(record, 'trackId', readString),\n }\n case 'trim_clip':\n return {\n type: 'trim_clip',\n clipId: readString(record, 'clipId'),\n startFrame: readInt(record, 'startFrame'),\n durationFrames: readInt(record, 'durationFrames'),\n ...readOptional(record, 'sourceInFrame', readInt),\n ...readOptionalNullable(record, 'sourceOutFrame', readInt),\n }\n case 'split_clip':\n return {\n type: 'split_clip',\n clipId: readString(record, 'clipId'),\n atFrame: readInt(record, 'atFrame'),\n }\n case 'set_clip_text':\n return {\n type: 'set_clip_text',\n clipId: readString(record, 'clipId'),\n text: readString(record, 'text'),\n ...readOptional(record, 'language', readString),\n }\n case 'set_clip_disabled':\n return {\n type: 'set_clip_disabled',\n clipId: readString(record, 'clipId'),\n disabled: readBool(record, 'disabled'),\n }\n case 'delete_clip':\n return { type: 'delete_clip', clipId: readString(record, 'clipId') }\n case 'create_track': {\n const kind = readString(record, 'kind')\n if (!(kind in TRACK_KINDS)) throw new Error(`kind must be one of: ${Object.keys(TRACK_KINDS).join(', ')}`)\n return { type: 'create_track', kind: kind as SequenceTrackKind, name: readString(record, 'name') }\n }\n case 'extend_sequence':\n return { type: 'extend_sequence', durationFrames: readInt(record, 'durationFrames') }\n case 'queue_export': {\n const format = readString(record, 'format')\n if (!(format in EXPORT_FORMATS)) throw new Error(`format must be one of: ${Object.keys(EXPORT_FORMATS).join(', ')}`)\n return { type: 'queue_export', format: format as SequenceExportFormat, ...readOptional(record, 'metadata', readRecord) }\n }\n }\n}\n\nfunction readString(record: Record<string, unknown>, name: string): string {\n const value = record[name]\n if (typeof value !== 'string') throw new Error(`${name} must be a string (got ${describeJsonValue(value)})`)\n return value\n}\n\nfunction readInt(record: Record<string, unknown>, name: string): number {\n const value = record[name]\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n throw new Error(`${name} must be an integer frame count (got ${describeJsonValue(value)})`)\n }\n return value\n}\n\nfunction readBool(record: Record<string, unknown>, name: string): boolean {\n const value = record[name]\n if (typeof value !== 'boolean') throw new Error(`${name} must be true or false (got ${describeJsonValue(value)})`)\n return value\n}\n\nfunction readRecord(record: Record<string, unknown>, name: string): Record<string, unknown> {\n const value = record[name]\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${name} must be an object (got ${describeJsonValue(value)})`)\n }\n return value as Record<string, unknown>\n}\n\nfunction readMedia(record: Record<string, unknown>, name: string): { url: string; kind: SequenceMediaKind } {\n const media = readRecord(record, name)\n const url = readString(media, 'url')\n const kind = readString(media, 'kind')\n if (!(kind in MEDIA_KINDS)) throw new Error(`${name}.kind must be one of: ${Object.keys(MEDIA_KINDS).join(', ')}`)\n return { url, kind: kind as SequenceMediaKind }\n}\n\n/** Spread helper: absent/undefined fields stay absent so optional-property\n * semantics survive the parse (exactOptionalPropertyTypes-safe). */\nfunction readOptional<T>(\n record: Record<string, unknown>,\n name: string,\n reader: (record: Record<string, unknown>, name: string) => T,\n): Record<string, T> {\n if (record[name] === undefined) return {}\n return { [name]: reader(record, name) }\n}\n\n/** Like `readOptional` but the field's vocabulary includes literal null. */\nfunction readOptionalNullable<T>(\n record: Record<string, unknown>,\n name: string,\n reader: (record: Record<string, unknown>, name: string) => T,\n): Record<string, T | null> {\n if (record[name] === undefined) return {}\n if (record[name] === null) return { [name]: null }\n return { [name]: reader(record, name) }\n}\n\nfunction describeJsonValue(value: unknown): string {\n if (value === undefined) return 'missing'\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'an array'\n return typeof value === 'object' ? 'an object' : `${typeof value} ${JSON.stringify(value)}`\n}\n\n// ---------------------------------------------------------------------------\n// Resolution helpers — shared with ./apply\n// ---------------------------------------------------------------------------\n\nexport type CaptionTargetResolution =\n | { kind: 'existing'; track: SequenceTrack }\n | { kind: 'create'; language: string; name: string }\n\n/** Naming convention for auto-created per-language caption tracks. The name\n * doubles as the recognition rule because `SequenceStore.createTrack` cannot\n * persist track metadata — matching by `metadata.language` alone would never\n * find tracks this module created. */\nexport function captionTrackNameForLanguage(language: string): string {\n return `Captions (${language})`\n}\n\n/** Target track for a `place_clip`. Video and image media land on video\n * tracks, audio media on audio tracks; a `reference` track is valid only when\n * targeted explicitly. Media-less clips (placeholders, agent markers) need an\n * explicit trackId because no kind can be inferred. */\nexport function resolvePlaceClipTrack(timeline: SequenceTimeline, operation: PlaceClipOperation): SequenceTrack {\n const media = operation.media\n if (operation.trackId !== undefined) {\n const track = requireTrack(timeline, operation.trackId)\n assertUnlocked(track)\n if (track.kind === 'caption') {\n throw new Error(`cannot target caption track ${track.id}; use add_caption for caption content`)\n }\n if (media) {\n const primary: SequenceTrackKind = media.kind === 'audio' ? 'audio' : 'video'\n if (track.kind !== primary && track.kind !== 'reference') {\n throw new Error(`media kind ${media.kind} requires a ${primary} or reference track; track ${track.id} is ${track.kind}`)\n }\n }\n return track\n }\n if (!media) throw new Error('requires trackId when media is omitted — the target track kind cannot be inferred')\n const wanted: SequenceTrackKind = media.kind === 'audio' ? 'audio' : 'video'\n const track = tracksBySortOrder(timeline).find((candidate) => candidate.kind === wanted && !candidate.locked)\n if (!track) throw new Error(`requires an unlocked ${wanted} track and the sequence has none`)\n return track\n}\n\n/** Target caption track for an `add_caption`. With `language` set and no\n * matching caption track, resolution returns a `create` instruction the apply\n * layer turns into a real track — a locked matching track is an error, never\n * a silent duplicate. */\nexport function resolveCaptionTarget(timeline: SequenceTimeline, operation: AddCaptionOperation): CaptionTargetResolution {\n if (operation.trackId !== undefined) {\n const track = requireTrack(timeline, operation.trackId)\n if (track.kind !== 'caption') {\n throw new Error(`targets ${track.kind} track ${track.id}; captions require a caption track`)\n }\n assertUnlocked(track)\n return { kind: 'existing', track }\n }\n const captionTracks = tracksBySortOrder(timeline).filter((track) => track.kind === 'caption')\n if (operation.language !== undefined) {\n const language = operation.language\n const matching = captionTracks.filter(\n (track) => track.metadata.language === language || track.name === captionTrackNameForLanguage(language),\n )\n const unlocked = matching.find((track) => !track.locked)\n if (unlocked) return { kind: 'existing', track: unlocked }\n if (matching.length > 0) throw new Error(`caption track for language \"${language}\" is locked`)\n return { kind: 'create', language, name: captionTrackNameForLanguage(language) }\n }\n const track = captionTracks.find((candidate) => !candidate.locked)\n if (!track) {\n throw new Error('requires an unlocked caption track and the sequence has none; pass language to auto-create one or create_track first')\n }\n return { kind: 'existing', track }\n}\n\n/** Caption bounds. Fully omitted placement slides past occupied intervals via\n * `chooseCaptionPlacement`; a partially explicit placement fills the missing\n * half deterministically (startFrame ← playhead, durationFrames ← fps*3) and\n * must pass the caller's bounds check — no collision slide. */\nexport function resolveCaptionPlacement(\n timeline: SequenceTimeline,\n operation: AddCaptionOperation,\n ctx: SequenceOperationContext,\n targetTrackId: string | null,\n): TimelineClipBounds {\n assertPlayheadFrame(ctx.playheadFrame)\n const fps = timeline.sequence.fps\n if (operation.startFrame === undefined && operation.durationFrames === undefined) {\n return chooseCaptionPlacement({\n playheadFrame: ctx.playheadFrame,\n fps,\n sequenceDurationFrames: timeline.sequence.durationFrames,\n occupiedIntervals: targetTrackId === null ? [] : trackIntervals(timeline, targetTrackId),\n })\n }\n return {\n startFrame: operation.startFrame ?? ctx.playheadFrame,\n durationFrames: operation.durationFrames ?? fps * 3,\n }\n}\n\n/** Last occupied frame across all clips — the floor for `extend_sequence`. */\nexport function lastClipEndFrame(timeline: SequenceTimeline): number {\n return timeline.clips.reduce((max, clip) => Math.max(max, clip.startFrame + clip.durationFrames), 0)\n}\n\n/** Media references must be provider URLs or app-served paths. Local sandbox\n * artifacts (file:, data:, /tmp/, /home/) are rejected because they are\n * unreachable from the product and signal an agent substituting local ffmpeg\n * output for real provider generation. */\nexport function assertSequenceMediaUrl(url: string): void {\n const trimmed = url.trim()\n if (/^https?:\\/\\//i.test(trimmed)) return\n if (trimmed.startsWith('/api/')) return\n const shown = trimmed.length > 96 ? `${trimmed.slice(0, 96)}…` : trimmed\n const lower = trimmed.toLowerCase()\n if (lower.startsWith('file:') || lower.startsWith('data:') || lower.startsWith('/tmp/') || lower.startsWith('/home/')) {\n throw new Error(`media url must reference a provider URL or rooted /api/ path, not a local sandbox file (${shown})`)\n }\n throw new Error(`media url must be http(s) or a rooted /api/ path (${shown})`)\n}\n\n// ---------------------------------------------------------------------------\n// Internal guards\n// ---------------------------------------------------------------------------\n\nfunction requireClip(timeline: SequenceTimeline, clipId: string): SequenceClip {\n const clip = timeline.clips.find((candidate) => candidate.id === clipId)\n if (!clip) throw new Error(`references unknown clip ${clipId}`)\n return clip\n}\n\nfunction requireTrack(timeline: SequenceTimeline, trackId: string): SequenceTrack {\n const track = timeline.tracks.find((candidate) => candidate.id === trackId)\n if (!track) throw new Error(`references unknown track ${trackId}`)\n return track\n}\n\n/** Clip mutations (move, trim, split, text, disable, delete) are writes to the\n * clip's track, so a locked track rejects them all. */\nfunction requireMutableClip(timeline: SequenceTimeline, clipId: string): { clip: SequenceClip; track: SequenceTrack } {\n const clip = requireClip(timeline, clipId)\n const track = requireTrack(timeline, clip.trackId)\n if (track.locked) throw new Error(`clip ${clip.id} sits on locked track \"${track.name}\" (${track.id})`)\n return { clip, track }\n}\n\nfunction assertUnlocked(track: SequenceTrack): void {\n if (track.locked) throw new Error(`targets locked track \"${track.name}\" (${track.id})`)\n}\n\nfunction assertOperationBounds(timeline: SequenceTimeline, bounds: TimelineClipBounds): void {\n assertClipFitsSequence({\n startFrame: bounds.startFrame,\n durationFrames: bounds.durationFrames,\n sequenceDurationFrames: timeline.sequence.durationFrames,\n // The label carries the numbers so the thrown message is actionable\n // without access to the original arguments.\n label: `clip [start=${bounds.startFrame} duration=${bounds.durationFrames}] in a ${timeline.sequence.durationFrames}-frame sequence:`,\n })\n}\n\nfunction assertSourceInFrame(sourceInFrame: number): void {\n if (!Number.isInteger(sourceInFrame) || sourceInFrame < 0) {\n throw new Error('sourceInFrame must be a non-negative integer')\n }\n}\n\n/** `null` out-point = natural end of the source: nothing to check. An explicit\n * out-point must leave at least `durationFrames` of playable source. */\nfunction assertSourceWindow(sourceInFrame: number, sourceOutFrame: number | null, durationFrames: number): void {\n if (sourceOutFrame === null) return\n if (!Number.isInteger(sourceOutFrame) || sourceOutFrame < 1) {\n throw new Error('sourceOutFrame must be a positive integer or null')\n }\n if (sourceOutFrame <= sourceInFrame) {\n throw new Error(`sourceOutFrame ${sourceOutFrame} must be greater than sourceInFrame ${sourceInFrame}`)\n }\n if (sourceInFrame + durationFrames > sourceOutFrame) {\n throw new Error(\n `needs ${durationFrames} source frames but the source window [${sourceInFrame}, ${sourceOutFrame}) holds ${sourceOutFrame - sourceInFrame} — shorten durationFrames, lower sourceInFrame, or pass sourceOutFrame (null releases it to the source's natural end)`,\n )\n }\n}\n\nfunction assertLanguageTag(language: string): void {\n if (!LANGUAGE_TAG.test(language)) {\n throw new Error(`language must be a BCP-47-style tag (got ${JSON.stringify(language)})`)\n }\n}\n\nfunction assertPlayheadFrame(playheadFrame: number): void {\n if (!Number.isInteger(playheadFrame) || playheadFrame < 0) {\n throw new Error('playheadFrame must be a non-negative integer')\n }\n}\n\nfunction tracksBySortOrder(timeline: SequenceTimeline): SequenceTrack[] {\n return [...timeline.tracks].sort((a, b) => a.sortOrder - b.sortOrder)\n}\n","/**\n * Maps one validated `SequenceOperation` to `SequenceStore` calls and reports\n * what changed. Each call re-validates its single operation against the\n * provided timeline — a dispatcher that skips batch validation still cannot\n * reach the store with an invalid write. The timeline must be the CURRENT\n * state: dispatchers applying a batch refresh it between operations so later\n * operations can reference entities earlier ones created.\n *\n * Split assumes source frames advance 1:1 with sequence frames (the model\n * addresses source in/out points at sequence fps); time-remapped clips are\n * outside this operation vocabulary.\n */\n\nimport type { SequenceClip, SequenceExportRecord, SequenceMeta, SequenceTimeline, SequenceTrack } from './model'\nimport type {\n AddCaptionOperation,\n PlaceClipOperation,\n SequenceOperation,\n SplitClipOperation,\n} from './operations'\nimport type { SequenceClipPatch, SequenceStore } from './store'\nimport {\n resolveCaptionPlacement,\n resolveCaptionTarget,\n resolvePlaceClipTrack,\n validateSequenceOperation,\n validateSequenceOperations,\n} from './validate'\nimport type { SequenceOperationContext } from './validate'\n\n/**\n * The entity an operation changed, for the MCP layer to serialize back to the\n * agent. Conventions for multi-entity operations:\n * - `split_clip` returns the newly created second half (the first half is the\n * original clip id with a shortened duration).\n * - `delete_clip` returns the pre-delete clip snapshot.\n */\nexport type SequenceApplyResult =\n | { kind: 'clip'; clip: SequenceClip }\n | { kind: 'track'; track: SequenceTrack }\n | { kind: 'export'; record: SequenceExportRecord }\n | { kind: 'sequence'; sequence: SequenceMeta }\n\n/**\n * The batch path every dispatcher (the MCP tools, a product's editor\n * persistence route) funnels through: fetch the timeline, validate the WHOLE\n * batch against pre-state, then apply in order with a timeline refresh between\n * operations — later operations must see earlier writes (the static-validation\n * boundary in ./validate).\n *\n * Atomicity contract: validation throws (before the first store write) leave\n * the sequence untouched. Store-layer throws after at least one successful\n * write leave a prefix-committed state — operations 1..N-1 are persisted,\n * operations N..end are not. The store is non-transactional (SQLite D1);\n * callers that receive a partial-commit throw must treat the result as partial\n * success, not a full rollback. Decision-log rows are the caller's job: the\n * MCP layer records `agent_edit`, an editor route records `human_edit`.\n */\nexport async function applySequenceOperations(\n store: SequenceStore,\n operations: SequenceOperation[],\n ctx: SequenceOperationContext,\n): Promise<SequenceApplyResult[]> {\n if (operations.length === 0) throw new Error('operations must contain at least one operation')\n let timeline = await store.getTimeline()\n validateSequenceOperations(timeline, operations, ctx)\n const results: SequenceApplyResult[] = []\n for (let index = 0; index < operations.length; index += 1) {\n if (index > 0) timeline = await store.getTimeline()\n results.push(await applySequenceOperation(store, timeline, operations[index] as SequenceOperation, ctx))\n }\n return results\n}\n\nexport async function applySequenceOperation(\n store: SequenceStore,\n timeline: SequenceTimeline,\n op: SequenceOperation,\n ctx: SequenceOperationContext,\n): Promise<SequenceApplyResult> {\n validateSequenceOperation(timeline, op, ctx)\n switch (op.type) {\n case 'place_clip':\n return applyPlaceClip(store, timeline, op)\n case 'add_caption':\n return applyAddCaption(store, timeline, op, ctx)\n case 'move_clip': {\n const patch: SequenceClipPatch = { startFrame: op.startFrame }\n if (op.trackId !== undefined) patch.trackId = op.trackId\n return { kind: 'clip', clip: await store.updateClip(op.clipId, patch) }\n }\n case 'trim_clip': {\n const patch: SequenceClipPatch = { startFrame: op.startFrame, durationFrames: op.durationFrames }\n if (op.sourceInFrame !== undefined) patch.sourceInFrame = op.sourceInFrame\n if (op.sourceOutFrame !== undefined) patch.sourceOutFrame = op.sourceOutFrame\n return { kind: 'clip', clip: await store.updateClip(op.clipId, patch) }\n }\n case 'split_clip':\n return applySplitClip(store, timeline, op)\n case 'set_clip_text': {\n const patch: SequenceClipPatch = { text: op.text, label: clipLabelFromText(op.text) }\n if (op.language !== undefined) patch.language = op.language\n return { kind: 'clip', clip: await store.updateClip(op.clipId, patch) }\n }\n case 'set_clip_disabled':\n return { kind: 'clip', clip: await store.updateClip(op.clipId, { disabled: op.disabled }) }\n case 'delete_clip': {\n const snapshot = requireTimelineClip(timeline, op.clipId)\n await store.deleteClip(op.clipId)\n return { kind: 'clip', clip: snapshot }\n }\n case 'create_track':\n return { kind: 'track', track: await store.createTrack({ kind: op.kind, name: op.name }) }\n case 'extend_sequence':\n return { kind: 'sequence', sequence: await store.updateSequenceDuration(op.durationFrames) }\n case 'queue_export':\n return { kind: 'export', record: await store.createExport(op.format, op.metadata) }\n }\n}\n\nasync function applyPlaceClip(\n store: SequenceStore,\n timeline: SequenceTimeline,\n op: PlaceClipOperation,\n): Promise<SequenceApplyResult> {\n const track = resolvePlaceClipTrack(timeline, op)\n // NewSequenceClip carries no first-class media field; the provider URL\n // reference rides in metadata.media for the store to resolve. Stores that\n // resolve media through generationId/assetId can ignore it.\n const metadata = op.media\n ? { ...(op.metadata ?? {}), media: { url: op.media.url, kind: op.media.kind } }\n : op.metadata\n const clip = await store.createClip({\n trackId: track.id,\n label: op.label,\n startFrame: op.startFrame,\n durationFrames: op.durationFrames,\n sourceInFrame: op.sourceInFrame ?? 0,\n ...(op.sourceOutFrame !== undefined ? { sourceOutFrame: op.sourceOutFrame } : {}),\n ...(op.generationId !== undefined ? { generationId: op.generationId } : {}),\n ...(op.assetId !== undefined ? { assetId: op.assetId } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n })\n // NewSequenceClip has no disabled field; flip it post-create so the durable\n // inverse of deleting a disabled clip restores it hidden.\n const final = op.disabled === true ? await store.updateClip(clip.id, { disabled: true }) : clip\n return { kind: 'clip', clip: final }\n}\n\nasync function applyAddCaption(\n store: SequenceStore,\n timeline: SequenceTimeline,\n op: AddCaptionOperation,\n ctx: SequenceOperationContext,\n): Promise<SequenceApplyResult> {\n const target = resolveCaptionTarget(timeline, op)\n const placement = resolveCaptionPlacement(timeline, op, ctx, target.kind === 'existing' ? target.track.id : null)\n const track = target.kind === 'existing'\n ? target.track\n : await store.createTrack({ kind: 'caption', name: target.name })\n const clip = await store.createClip({\n trackId: track.id,\n label: clipLabelFromText(op.text),\n startFrame: placement.startFrame,\n durationFrames: placement.durationFrames,\n sourceInFrame: 0,\n text: op.text,\n ...(op.language !== undefined ? { language: op.language } : {}),\n })\n return { kind: 'clip', clip }\n}\n\nasync function applySplitClip(\n store: SequenceStore,\n timeline: SequenceTimeline,\n op: SplitClipOperation,\n): Promise<SequenceApplyResult> {\n // Snapshot before any write: stores may hand back live row objects, so\n // reading `original` after updateClip would see the shortened first half.\n const original = { ...requireTimelineClip(timeline, op.clipId) }\n const offset = op.atFrame - original.startFrame\n // The store is non-transactional, so the tail is created BEFORE the head is\n // shortened: a failure between the writes leaves the cut content visible\n // twice (recoverable) instead of silently dropped from the timeline.\n const second = await store.createClip({\n trackId: original.trackId,\n label: original.label,\n startFrame: op.atFrame,\n durationFrames: original.durationFrames - offset,\n sourceInFrame: original.sourceInFrame + offset,\n sourceOutFrame: original.sourceOutFrame,\n ...(original.text !== undefined ? { text: original.text } : {}),\n ...(original.language !== undefined ? { language: original.language } : {}),\n ...(original.generationId !== undefined ? { generationId: original.generationId } : {}),\n ...(original.assetId !== undefined ? { assetId: original.assetId } : {}),\n metadata: original.metadata,\n })\n // First half keeps the clip id; its out point becomes explicit at the cut so\n // the playable source range stays exact.\n await store.updateClip(original.id, {\n durationFrames: offset,\n sourceOutFrame: original.sourceInFrame + offset,\n })\n // NewSequenceClip has no disabled field; a disabled original must not yield\n // an enabled second half.\n const secondFinal = original.disabled ? await store.updateClip(second.id, { disabled: true }) : second\n return { kind: 'clip', clip: secondFinal }\n}\n\nfunction requireTimelineClip(timeline: SequenceTimeline, clipId: string): SequenceClip {\n const clip = timeline.clips.find((candidate) => candidate.id === clipId)\n // Unreachable after validateSequenceOperation; kept loud for callers that\n // hand-roll dispatch.\n if (!clip) throw new Error(`references unknown clip ${clipId}`)\n return clip\n}\n\n/** Clip labels mirror caption text, truncated so list UIs stay readable. */\nfunction clipLabelFromText(text: string): string {\n return text.length > 120 ? text.slice(0, 120) : text\n}\n","/**\n * Pure interchange-format builders over `SequenceTimeline` — SRT, WebVTT,\n * CMX3600 EDL, OpenTimelineIO JSON, and the contact-sheet manifest. mp4\n * rendering needs ffmpeg and stays product-side; everything here is\n * deterministic frame math producing strings or JSON-serializable documents.\n *\n * Builders throw instead of emitting empty documents: an empty subtitle file\n * or zero-event EDL downloads \"successfully\" and then fails silently inside\n * the user's player or NLE — the worst failure mode for an agent-driven\n * editor. The one exception is OTIO, which meaningfully round-trips sequence\n * settings (fps, dimensions, track structure) even with zero clips.\n *\n * Disabled clips are excluded from every format: an export reflects what\n * renders, and a cue or event for an invisible clip is a lie.\n */\n\nimport {\n formatTimecode,\n framesToSeconds,\n secondsToFrames,\n type SequenceClip,\n type SequenceMediaKind,\n type SequenceTimeline,\n type SequenceTrack,\n type SequenceTrackKind,\n} from './model'\n\n// ---------------------------------------------------------------------------\n// Captions: SRT / WebVTT\n// ---------------------------------------------------------------------------\n\nexport interface CaptionExportOptions {\n /** BCP-47 tag; matched case-insensitively against `clip.language`. Clips\n * with no language never match a language-scoped export. */\n language?: string\n}\n\ninterface CaptionCue {\n startFrame: number\n endFrame: number\n lines: string[]\n}\n\n/** Numbered SubRip cues from caption-track clips, in timeline order. Throws\n * when no cue survives filtering — see module doc on empty documents. */\nexport function buildSrt(timeline: SequenceTimeline, opts: CaptionExportOptions = {}): string {\n const fps = timeline.sequence.fps\n const cues = collectCaptionCues(timeline, opts.language)\n const blocks = cues.map((cue, index) => [\n String(index + 1),\n `${frameToSubtitleTime(cue.startFrame, fps, ',')} --> ${frameToSubtitleTime(cue.endFrame, fps, ',')}`,\n ...cue.lines,\n ].join('\\n'))\n return `${blocks.join('\\n\\n')}\\n`\n}\n\n/** WebVTT with numbered cue identifiers; same filtering and frame math as\n * `buildSrt`, dot millisecond separator per the VTT grammar. */\nexport function buildVtt(timeline: SequenceTimeline, opts: CaptionExportOptions = {}): string {\n const fps = timeline.sequence.fps\n const cues = collectCaptionCues(timeline, opts.language)\n const blocks = cues.map((cue, index) => [\n String(index + 1),\n `${frameToSubtitleTime(cue.startFrame, fps, '.')} --> ${frameToSubtitleTime(cue.endFrame, fps, '.')}`,\n ...cue.lines,\n ].join('\\n'))\n return `WEBVTT\\n\\n${blocks.join('\\n\\n')}\\n`\n}\n\nfunction collectCaptionCues(timeline: SequenceTimeline, language?: string): CaptionCue[] {\n const captionTracks = timeline.tracks.filter((track) => track.kind === 'caption')\n const sortOrderByTrackId = new Map(captionTracks.map((track) => [track.id, track.sortOrder]))\n const wanted = language?.toLowerCase()\n\n const cues = timeline.clips\n .filter((clip) =>\n sortOrderByTrackId.has(clip.trackId)\n && !clip.disabled\n && typeof clip.text === 'string'\n && clip.text.trim().length > 0\n && (wanted === undefined || clip.language?.toLowerCase() === wanted))\n .map((clip) => ({\n startFrame: clip.startFrame,\n endFrame: clip.startFrame + clip.durationFrames,\n lines: captionLines(clip.text as string),\n }))\n .sort((a, b) => a.startFrame - b.startFrame || a.endFrame - b.endFrame)\n\n if (cues.length === 0) {\n const scope = language === undefined ? '' : ` in language '${language}'`\n throw new Error(\n `sequence '${timeline.sequence.title}' has no caption clips with text${scope} — an empty subtitle file would fail silently; add captions${language === undefined ? '' : ' in that language'} first`,\n )\n }\n return cues\n}\n\n/** A blank line inside a cue body terminates the cue early in both SRT and\n * VTT parsers, silently truncating the caption — so internal empty lines are\n * dropped and line edges trimmed. */\nfunction captionLines(text: string): string[] {\n return text\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n}\n\n/** `HH:MM:SS<sep>mmm` from a frame position. Milliseconds derive from total\n * frame time in one rounding step so the carry into seconds is exact at any\n * fps (per-component rounding can emit `,1000`). */\nfunction frameToSubtitleTime(frame: number, fps: number, separator: ',' | '.'): string {\n const totalMs = Math.round(framesToSeconds(frame, fps) * 1000)\n const ms = totalMs % 1000\n const totalSeconds = Math.floor(totalMs / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}${separator}${String(ms).padStart(3, '0')}`\n}\n\n// ---------------------------------------------------------------------------\n// CMX3600 EDL\n// ---------------------------------------------------------------------------\n\n/** CMX3600-style EDL: one event per enabled video/audio clip in record-start\n * order. Source in/out come from `sourceInFrame` + `durationFrames`; record\n * in/out from `startFrame`. Timecodes are non-drop `HH:MM:SS:FF` at the\n * sequence fps. Throws when the timeline has no video/audio clips. */\nexport function buildEdl(timeline: SequenceTimeline): string {\n const fps = timeline.sequence.fps\n const events = clipsOnTracks(timeline, ['video', 'audio'])\n\n if (events.length === 0) {\n throw new Error(\n `sequence '${timeline.sequence.title}' has no enabled video or audio clips — an empty EDL would fail silently in the NLE`,\n )\n }\n\n const lines = [`TITLE: ${timeline.sequence.title}`, 'FCM: NON-DROP FRAME', '']\n events.forEach(({ track, clip }, index) => {\n const channel = track.kind === 'video' ? 'V' : 'A'\n const sourceIn = frameToEdlTimecode(clip.sourceInFrame, fps)\n const sourceOut = frameToEdlTimecode(clip.sourceInFrame + clip.durationFrames, fps)\n const recordIn = frameToEdlTimecode(clip.startFrame, fps)\n const recordOut = frameToEdlTimecode(clip.startFrame + clip.durationFrames, fps)\n lines.push(`${String(index + 1).padStart(3, '0')} AX ${channel} C ${sourceIn} ${sourceOut} ${recordIn} ${recordOut}`)\n lines.push(`* FROM CLIP NAME: ${clip.label}`)\n if (clip.media) lines.push(`* SOURCE FILE: ${clip.media.url}`)\n lines.push('')\n })\n return lines.join('\\n')\n}\n\n/** Non-drop `HH:MM:SS:FF`. The frame field widens past two digits only when\n * the fps demands it (fps > 100), so standard rates stay CMX-conformant. */\nfunction frameToEdlTimecode(frame: number, fps: number): string {\n if (!Number.isInteger(frame) || frame < 0) throw new Error('frames must be a non-negative integer')\n const frameWidth = Math.max(2, String(fps - 1).length)\n const totalSeconds = Math.floor(frame / fps)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}:${String(frame % fps).padStart(frameWidth, '0')}`\n}\n\n// ---------------------------------------------------------------------------\n// OpenTimelineIO\n// ---------------------------------------------------------------------------\n\nexport interface OtioRationalTime {\n OTIO_SCHEMA: 'RationalTime.1'\n rate: number\n value: number\n}\n\nexport interface OtioTimeRange {\n OTIO_SCHEMA: 'TimeRange.1'\n start_time: OtioRationalTime\n duration: OtioRationalTime\n}\n\nexport interface OtioExternalReference {\n OTIO_SCHEMA: 'ExternalReference.1'\n target_url: string\n /** Natural extent of the source media when known; null when unknown. */\n available_range: OtioTimeRange | null\n}\n\nexport interface OtioMissingReference {\n OTIO_SCHEMA: 'MissingReference.1'\n}\n\nexport interface OtioGap {\n OTIO_SCHEMA: 'Gap.1'\n name: string\n source_range: OtioTimeRange\n}\n\nexport interface OtioClip {\n OTIO_SCHEMA: 'Clip.2'\n name: string\n source_range: OtioTimeRange\n media_reference: OtioExternalReference | OtioMissingReference\n metadata: Record<string, unknown>\n}\n\nexport interface OtioTrack {\n OTIO_SCHEMA: 'Track.1'\n name: string\n kind: 'Video' | 'Audio'\n metadata: Record<string, unknown>\n children: Array<OtioClip | OtioGap>\n}\n\nexport interface OtioStack {\n OTIO_SCHEMA: 'Stack.1'\n name: string\n children: OtioTrack[]\n}\n\nexport interface OtioTimeline {\n OTIO_SCHEMA: 'Timeline.1'\n name: string\n global_start_time: OtioRationalTime\n metadata: Record<string, unknown>\n tracks: OtioStack\n}\n\n/**\n * OpenTimelineIO `Timeline.1` document. Serialize with `JSON.stringify` at the\n * file-write edge.\n *\n * OTIO track children are SEQUENTIAL — position comes from accumulated child\n * durations, so timeline gaps become explicit `Gap.1` children and two enabled\n * clips overlapping on one track are unrepresentable (throws). Caption and\n * reference tracks export as `Video` tracks (OTIO has no caption kind) with\n * the original kind preserved in `metadata.sequenceTrackKind`; agent tracks\n * carry decision markers, never media, and are excluded.\n */\nexport function buildOtio(timeline: SequenceTimeline): OtioTimeline {\n const fps = timeline.sequence.fps\n const tracks = [...timeline.tracks]\n .sort((a, b) => a.sortOrder - b.sortOrder)\n .filter((track) => track.kind !== 'agent')\n\n return {\n OTIO_SCHEMA: 'Timeline.1',\n name: timeline.sequence.title,\n global_start_time: rationalTime(0, fps),\n metadata: {\n ...timeline.sequence.metadata,\n sequenceId: timeline.sequence.id,\n fps,\n width: timeline.sequence.width,\n height: timeline.sequence.height,\n aspectRatio: timeline.sequence.aspectRatio,\n durationFrames: timeline.sequence.durationFrames,\n },\n tracks: {\n OTIO_SCHEMA: 'Stack.1',\n name: 'tracks',\n children: tracks.map((track) => otioTrack(timeline, track, fps)),\n },\n }\n}\n\nfunction otioTrack(timeline: SequenceTimeline, track: SequenceTrack, fps: number): OtioTrack {\n const clips = timeline.clips\n .filter((clip) => clip.trackId === track.id && !clip.disabled)\n .sort((a, b) => a.startFrame - b.startFrame)\n\n const children: Array<OtioClip | OtioGap> = []\n let cursorFrame = 0\n for (const clip of clips) {\n if (clip.startFrame < cursorFrame) {\n throw new Error(\n `clip '${clip.label}' (${clip.id}) overlaps the previous clip on track '${track.name}' — OTIO tracks are sequential; move or trim the clip before exporting`,\n )\n }\n if (clip.startFrame > cursorFrame) {\n children.push({\n OTIO_SCHEMA: 'Gap.1',\n name: '',\n source_range: timeRange(0, clip.startFrame - cursorFrame, fps),\n })\n }\n children.push(otioClip(clip, fps))\n cursorFrame = clip.startFrame + clip.durationFrames\n }\n\n return {\n OTIO_SCHEMA: 'Track.1',\n name: track.name,\n kind: otioTrackKind(track.kind),\n metadata: { ...track.metadata, sequenceTrackKind: track.kind },\n children,\n }\n}\n\nfunction otioClip(clip: SequenceClip, fps: number): OtioClip {\n const metadata: Record<string, unknown> = { ...clip.metadata }\n if (clip.text !== undefined) metadata.text = clip.text\n if (clip.language !== undefined) metadata.language = clip.language\n if (clip.generationId !== undefined) metadata.generationId = clip.generationId\n if (clip.assetId !== undefined) metadata.assetId = clip.assetId\n\n return {\n OTIO_SCHEMA: 'Clip.2',\n name: clip.label,\n source_range: timeRange(clip.sourceInFrame, clip.durationFrames, fps),\n media_reference: clip.media === undefined\n ? { OTIO_SCHEMA: 'MissingReference.1' }\n : {\n OTIO_SCHEMA: 'ExternalReference.1',\n target_url: clip.media.url,\n available_range: clip.media.durationSeconds === undefined\n ? null\n : timeRange(0, secondsToFrames(clip.media.durationSeconds, fps), fps),\n },\n metadata,\n }\n}\n\nfunction otioTrackKind(kind: SequenceTrackKind): 'Video' | 'Audio' {\n if (kind === 'agent') throw new Error('agent tracks are excluded from OTIO export')\n return kind === 'audio' ? 'Audio' : 'Video'\n}\n\nfunction rationalTime(value: number, rate: number): OtioRationalTime {\n return { OTIO_SCHEMA: 'RationalTime.1', rate, value }\n}\n\nfunction timeRange(startValue: number, durationValue: number, rate: number): OtioTimeRange {\n return {\n OTIO_SCHEMA: 'TimeRange.1',\n start_time: rationalTime(startValue, rate),\n duration: rationalTime(durationValue, rate),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Contact-sheet manifest\n// ---------------------------------------------------------------------------\n\nexport interface ContactSheetEntry {\n clipId: string\n trackId: string\n label: string\n /** Timeline frame the sample represents — the clip midpoint. */\n frame: number\n /** `m:ss.ff` timecode of `frame`, for human-readable sheet labels. */\n timecode: string\n /** Source-media frame to extract: `sourceInFrame` + midpoint offset for\n * video, always 0 for stills (a seek into an image yields nothing). */\n sourceFrame: number\n sourceSeconds: number\n url: string\n mediaKind: SequenceMediaKind\n}\n\nexport interface ContactSheetManifest {\n sequenceId: string\n title: string\n fps: number\n width: number\n height: number\n entries: ContactSheetEntry[]\n}\n\n/**\n * One sample frame per enabled video-track clip with resolved, completed\n * media — the product side renders the actual sheet (needs ffmpeg/canvas).\n * Clips whose media is still rendering upstream (`providerStatus` queued/\n * processing/failed) have no extractable frame and are excluded; audio media\n * on a video track likewise. Throws when nothing is sampleable — see module\n * doc on empty documents.\n */\nexport function buildContactSheetManifest(timeline: SequenceTimeline): ContactSheetManifest {\n const fps = timeline.sequence.fps\n const entries = clipsOnTracks(timeline, ['video'])\n .flatMap(({ clip }) => {\n const media = clip.media\n if (media === undefined) return []\n if (media.providerStatus !== undefined && media.providerStatus !== 'completed') return []\n if (media.kind === 'audio') return []\n const midpointOffset = Math.floor(clip.durationFrames / 2)\n const frame = clip.startFrame + midpointOffset\n const sourceFrame = media.kind === 'image' ? 0 : clip.sourceInFrame + midpointOffset\n return [{\n clipId: clip.id,\n trackId: clip.trackId,\n label: clip.label,\n frame,\n timecode: formatTimecode(frame, fps),\n sourceFrame,\n sourceSeconds: framesToSeconds(sourceFrame, fps),\n url: media.url,\n mediaKind: media.kind,\n }]\n })\n\n if (entries.length === 0) {\n throw new Error(\n `sequence '${timeline.sequence.title}' has no sampleable video clips (enabled, media resolved and completed) — a contact sheet would be empty`,\n )\n }\n\n return {\n sequenceId: timeline.sequence.id,\n title: timeline.sequence.title,\n fps,\n width: timeline.sequence.width,\n height: timeline.sequence.height,\n entries,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared\n// ---------------------------------------------------------------------------\n\n/** Enabled clips on tracks of the given kinds, ordered by record start with\n * track sort order then clip id as deterministic tie-breakers. */\nfunction clipsOnTracks(\n timeline: SequenceTimeline,\n kinds: readonly SequenceTrackKind[],\n): Array<{ track: SequenceTrack; clip: SequenceClip }> {\n const trackById = new Map(\n timeline.tracks.filter((track) => kinds.includes(track.kind)).map((track) => [track.id, track]),\n )\n return timeline.clips\n .filter((clip) => !clip.disabled && trackById.has(clip.trackId))\n .map((clip) => ({ track: trackById.get(clip.trackId) as SequenceTrack, clip }))\n .sort((a, b) =>\n a.clip.startFrame - b.clip.startFrame\n || a.track.sortOrder - b.track.sortOrder\n || a.clip.id.localeCompare(b.clip.id))\n}\n\nfunction pad2(value: number): string {\n return String(value).padStart(2, '0')\n}\n","/**\n * Caption planning — pure, server-safe helpers that turn transcripts into\n * frame-typed caption chunks and answer \"are captions complete?\" per language.\n * No store access, no React, no provider coupling: products feed transcript\n * segments in (from any transcription provider) and get chunk bounds out,\n * ready to become `add_caption` operations.\n *\n * All outputs are integer frames at the sequence fps. Seconds appear only on\n * the transcript-segment inputs, because that is what transcription providers\n * emit; the conversion happens exactly once, here.\n */\n\nimport {\n MIN_SEQUENCE_CLIP_FRAMES,\n secondsToFrames,\n type SequenceTimeline,\n type TimelineInterval,\n} from './model'\n\n/** Server twin of `TranscriptionSegment` (../sequences-react/contracts) —\n * structurally identical so react-side transcription output feeds\n * `buildCaptionChunks` without mapping. Keep the shapes in lockstep. */\nexport interface TranscriptSegment {\n text: string\n startSeconds: number\n endSeconds: number\n}\n\n/** One caption clip's worth of text with its timeline bounds. */\nexport interface CaptionChunk {\n text: string\n startFrame: number\n durationFrames: number\n}\n\nexport interface BuildCaptionChunksOptions {\n /** Upper bound on words per caption; segments split on word boundaries. */\n maxWordsPerChunk?: number\n /** Readability floor — chunks shorter than this are extended, never\n * overlapped: the following chunk's start is pushed forward instead. */\n minDurationSeconds?: number\n fps: number\n}\n\nconst DEFAULT_MAX_WORDS_PER_CHUNK = 8\nconst DEFAULT_MIN_DURATION_SECONDS = 0.8\n\n/**\n * Split transcript segments into caption chunks. Within each segment, time is\n * apportioned to chunks by word count (a constant words-per-second estimate).\n * Guarantees, across the WHOLE output regardless of segment boundaries:\n *\n * - starts are strictly increasing and chunks never overlap\n * - every chunk lasts at least the min-duration clamp (and at least\n * `MIN_SEQUENCE_CLIP_FRAMES`)\n * - a chunk may extend past its segment's end by at most the clamp — the cost\n * of the readability floor on short tails\n *\n * Whitespace-only segments produce no chunks. Segments may arrive unsorted\n * (providers emit per-channel batches); they are ordered by start before\n * chunking so the no-overlap guarantee holds.\n */\nexport function buildCaptionChunks(\n segments: TranscriptSegment[],\n opts: BuildCaptionChunksOptions,\n): CaptionChunk[] {\n const maxWordsPerChunk = opts.maxWordsPerChunk ?? DEFAULT_MAX_WORDS_PER_CHUNK\n const minDurationSeconds = opts.minDurationSeconds ?? DEFAULT_MIN_DURATION_SECONDS\n if (!Number.isInteger(maxWordsPerChunk) || maxWordsPerChunk < 1) {\n throw new Error('maxWordsPerChunk must be a positive integer')\n }\n if (!Number.isFinite(minDurationSeconds) || minDurationSeconds < 0) {\n throw new Error('minDurationSeconds must be a non-negative finite number')\n }\n segments.forEach((segment, index) => {\n if (!Number.isFinite(segment.startSeconds) || segment.startSeconds < 0) {\n throw new Error(`segment ${index} startSeconds must be a non-negative finite number`)\n }\n if (!Number.isFinite(segment.endSeconds) || segment.endSeconds < segment.startSeconds) {\n throw new Error(`segment ${index} endSeconds must be a finite number >= startSeconds`)\n }\n })\n const minDurationFrames = Math.max(secondsToFrames(minDurationSeconds, opts.fps), MIN_SEQUENCE_CLIP_FRAMES)\n\n const ordered = [...segments].sort((a, b) => a.startSeconds - b.startSeconds)\n const chunks: CaptionChunk[] = []\n // Earliest frame the next chunk may start at — the no-overlap invariant.\n let cursorFrame = 0\n\n for (const segment of ordered) {\n const words = segment.text.trim().split(/\\s+/).filter((word) => word.length > 0)\n if (words.length === 0) continue\n const segmentDurationSeconds = segment.endSeconds - segment.startSeconds\n const chunkCount = Math.ceil(words.length / maxWordsPerChunk)\n\n for (let chunkIndex = 0; chunkIndex < chunkCount; chunkIndex += 1) {\n const wordStart = chunkIndex * maxWordsPerChunk\n const wordEnd = Math.min(wordStart + maxWordsPerChunk, words.length)\n const naturalStartFrame = secondsToFrames(\n segment.startSeconds + (wordStart / words.length) * segmentDurationSeconds,\n opts.fps,\n )\n const naturalEndFrame = secondsToFrames(\n segment.startSeconds + (wordEnd / words.length) * segmentDurationSeconds,\n opts.fps,\n )\n const startFrame = Math.max(naturalStartFrame, cursorFrame)\n const durationFrames = Math.max(naturalEndFrame - startFrame, minDurationFrames)\n chunks.push({\n text: words.slice(wordStart, wordEnd).join(' '),\n startFrame,\n durationFrames,\n })\n cursorFrame = startFrame + durationFrames\n }\n }\n\n return chunks\n}\n\n/** Liberal BCP-47 shape — primary language subtag plus optional 2-8 char\n * subtags. Deliberately permissive about subtag semantics (no registry\n * lookup); strict about structure so garbage tags fail before fan-out. */\nconst LANGUAGE_TAG_SHAPE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/\n\n/**\n * Normalize a BCP-47 tag to conventional casing: primary subtag lowercase,\n * 4-letter script subtags Title Case, 2-letter region subtags UPPER, all other\n * subtags lowercase. Throws on empty or structurally invalid tags.\n */\nexport function normalizeLanguageTag(tag: string): string {\n const trimmed = tag.trim()\n if (trimmed.length === 0) throw new Error('language tag must be a non-empty string')\n const normalized = trimmed\n .split('-')\n .map((subtag, index) => {\n const lower = subtag.toLowerCase()\n if (index === 0) return lower\n if (subtag.length === 4 && /^[a-z]+$/.test(lower)) return lower.charAt(0).toUpperCase() + lower.slice(1)\n if (subtag.length === 2 && /^[a-z]+$/.test(lower)) return lower.toUpperCase()\n return lower\n })\n .join('-')\n if (!LANGUAGE_TAG_SHAPE.test(normalized)) {\n throw new Error(`invalid BCP-47 language tag '${tag}' — expected a shape like 'en', 'pt-BR', or 'zh-Hans'`)\n }\n return normalized\n}\n\nexport interface LanguageFanoutOptions {\n languages: string[]\n /** Excluded from the plan (exact normalized match only — 'en' does not\n * exclude 'en-US'; a regional variant of the source is still a valid\n * fan-out target). */\n sourceLanguage?: string\n}\n\n/**\n * Plan which caption languages to generate: normalized, deduped (first\n * occurrence wins), source excluded. Throws on an empty request or any\n * invalid tag — a malformed fan-out request must fail before any generation\n * is queued. Returns [] when every requested language IS the source; the\n * caller reports \"nothing to fan out\" rather than erroring.\n */\nexport function planLanguageFanout(opts: LanguageFanoutOptions): string[] {\n if (opts.languages.length === 0) {\n throw new Error('languages must contain at least one BCP-47 tag')\n }\n const source = opts.sourceLanguage === undefined ? null : normalizeLanguageTag(opts.sourceLanguage)\n const seen = new Set<string>()\n const planned: string[] = []\n for (const raw of opts.languages) {\n const tag = normalizeLanguageTag(raw)\n if (tag === source || seen.has(tag)) continue\n seen.add(tag)\n planned.push(tag)\n }\n return planned\n}\n\n/** Coverage for one caption language across the sequence. `language` is the\n * clip's stored tag verbatim (no normalization — coverage reports what is\n * actually on the timeline); null groups caption clips with no tag. */\nexport interface CaptionCoverageEntry {\n language: string | null\n coveredFrames: number\n totalFrames: number\n /** Uncovered intervals, ascending; endFrame exclusive. */\n gaps: TimelineInterval[]\n}\n\n/**\n * Per-language caption coverage over [0, durationFrames). A frame counts as\n * covered when an enabled, non-empty-text clip on a caption-kind track spans\n * it. Overlapping clips merge (no double counting). Returns one entry per\n * distinct language, null first then lexicographic; [] when the timeline has\n * no caption clips at all.\n */\nexport function captionCoverage(timeline: SequenceTimeline): CaptionCoverageEntry[] {\n const totalFrames = timeline.sequence.durationFrames\n if (!Number.isInteger(totalFrames) || totalFrames < 1) {\n throw new Error('sequence durationFrames must be a positive integer')\n }\n const captionTrackIds = new Set(\n timeline.tracks.filter((track) => track.kind === 'caption').map((track) => track.id),\n )\n\n const intervalsByLanguage = new Map<string | null, TimelineInterval[]>()\n for (const clip of timeline.clips) {\n if (!captionTrackIds.has(clip.trackId) || clip.disabled) continue\n if (typeof clip.text !== 'string' || clip.text.length === 0) continue\n const startFrame = Math.max(0, clip.startFrame)\n const endFrame = Math.min(totalFrames, clip.startFrame + clip.durationFrames)\n if (endFrame <= startFrame) continue\n const language = clip.language ?? null\n const intervals = intervalsByLanguage.get(language)\n if (intervals) intervals.push({ startFrame, endFrame })\n else intervalsByLanguage.set(language, [{ startFrame, endFrame }])\n }\n\n const entries: CaptionCoverageEntry[] = []\n for (const [language, intervals] of intervalsByLanguage) {\n const merged = mergeIntervals(intervals)\n const coveredFrames = merged.reduce((sum, interval) => sum + (interval.endFrame - interval.startFrame), 0)\n entries.push({ language, coveredFrames, totalFrames, gaps: complementIntervals(merged, totalFrames) })\n }\n entries.sort((a, b) => {\n if (a.language === null) return b.language === null ? 0 : -1\n if (b.language === null) return 1\n return a.language < b.language ? -1 : a.language > b.language ? 1 : 0\n })\n return entries\n}\n\n/** Merge overlapping/adjacent intervals. Input need not be sorted; output is\n * ascending and disjoint. */\nfunction mergeIntervals(intervals: TimelineInterval[]): TimelineInterval[] {\n const sorted = [...intervals].sort((a, b) => a.startFrame - b.startFrame)\n const merged: TimelineInterval[] = []\n for (const interval of sorted) {\n const last = merged[merged.length - 1]\n if (last && interval.startFrame <= last.endFrame) {\n last.endFrame = Math.max(last.endFrame, interval.endFrame)\n } else {\n merged.push({ startFrame: interval.startFrame, endFrame: interval.endFrame })\n }\n }\n return merged\n}\n\n/** Complement of disjoint sorted intervals within [0, totalFrames). */\nfunction complementIntervals(merged: TimelineInterval[], totalFrames: number): TimelineInterval[] {\n const gaps: TimelineInterval[] = []\n let cursor = 0\n for (const interval of merged) {\n if (interval.startFrame > cursor) gaps.push({ startFrame: cursor, endFrame: interval.startFrame })\n cursor = Math.max(cursor, interval.endFrame)\n }\n if (cursor < totalFrames) gaps.push({ startFrame: cursor, endFrame: totalFrames })\n return gaps\n}\n","/**\n * The sequences MCP tool registry — what the in-sandbox agent sees over the\n * live agent→timeline channel. Each entry carries an LLM-facing description,\n * a JSON Schema for the arguments, and the typed dispatch that converts\n * seconds→frames exactly once, validates the resulting operations, applies\n * them through the store, and records ONE decision row per mutating call.\n *\n * Tool arguments speak SECONDS (the unit an LLM reasons in); everything past\n * this edge is integer frames at the sequence fps. Results come back in\n * snake_case with both seconds and `m:ss.ff` timecodes so the model can quote\n * positions back to the user without doing frame math.\n *\n * Every mutation funnels through the ./validate + ./apply kernel: the WHOLE\n * operation batch validates against pre-state before the first write. Errors\n * thrown anywhere in a tool run (argument shape, validation, store) carry the\n * precise reason — the handler surfaces them verbatim as `isError` tool\n * results the model can act on.\n */\n\nimport {\n MIN_SEQUENCE_CLIP_FRAMES,\n formatSeconds,\n formatTimecode,\n framesToSeconds,\n secondsToFrames,\n snapshotFrame,\n} from './model'\nimport type {\n SequenceClip,\n SequenceDecision,\n SequenceExportFormat,\n SequenceExportRecord,\n SequenceMediaKind,\n SequenceMeta,\n SequenceTimeline,\n SequenceTrack,\n SequenceTrackKind,\n} from './model'\nimport type { SequenceOperation } from './operations'\nimport type { SequenceStore } from './store'\nimport type { SequenceOperationContext } from './validate'\nimport { applySequenceOperations } from './apply'\nimport type { SequenceApplyResult } from './apply'\n\n/** Everything one tool invocation needs. Constructed per request by the\n * handler — the store is already scoped + authorized by the product. The\n * playhead is server-set (never a tool argument) so auto-placed captions\n * anchor to what the user is actually looking at. */\nexport interface SequenceMcpToolEnv {\n store: SequenceStore\n playheadFrame: number\n}\n\nexport interface SequenceMcpToolDefinition {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n run(args: Record<string, unknown>, env: SequenceMcpToolEnv): Promise<unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Enumerations (value-level twins of the model's type unions)\n// ---------------------------------------------------------------------------\n\nexport const SEQUENCE_EXPORT_FORMATS = ['mp4', 'otio', 'xml', 'edl', 'vtt', 'srt', 'contact_sheet'] as const satisfies readonly SequenceExportFormat[]\n\nexport const SEQUENCE_TRACK_KINDS = ['video', 'audio', 'caption', 'reference', 'agent'] as const satisfies readonly SequenceTrackKind[]\n\nexport const SEQUENCE_MEDIA_KINDS = ['video', 'image', 'audio'] as const satisfies readonly SequenceMediaKind[]\n\n/** Largest accepted `add_captions` batch — bounds one decision row / one\n * validation pass to a size the store can absorb in a single request. */\nexport const MAX_CAPTION_BATCH = 500\n\nconst MAX_INSTRUCTION_ARG_CHARS = 400\n\n// ---------------------------------------------------------------------------\n// Argument readers — fail loud with the argument name and expected unit\n// ---------------------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction requireString(args: Record<string, unknown>, name: string): string {\n const value = args[name]\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`${name} is required and must be a non-empty string`)\n }\n return value\n}\n\nfunction optionalString(args: Record<string, unknown>, name: string): string | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`${name} must be a non-empty string when provided`)\n }\n return value\n}\n\nfunction requireSeconds(args: Record<string, unknown>, name: string): number {\n const value = args[name]\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw new Error(`${name} is required and must be a non-negative number of seconds`)\n }\n return value\n}\n\nfunction optionalSeconds(args: Record<string, unknown>, name: string): number | undefined {\n if (args[name] === undefined || args[name] === null) return undefined\n return requireSeconds(args, name)\n}\n\nfunction requireBoolean(args: Record<string, unknown>, name: string): boolean {\n const value = args[name]\n if (typeof value !== 'boolean') throw new Error(`${name} is required and must be true or false`)\n return value\n}\n\nfunction optionalPositiveInteger(args: Record<string, unknown>, name: string, max: number): number | undefined {\n const value = args[name]\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 1 || value > max) {\n throw new Error(`${name} must be an integer between 1 and ${max}`)\n }\n return value\n}\n\nfunction requireEnum<T extends string>(args: Record<string, unknown>, name: string, values: readonly T[]): T {\n const value = args[name]\n if (typeof value !== 'string' || !(values as readonly string[]).includes(value)) {\n throw new Error(`${name} must be one of: ${values.join(', ')}`)\n }\n return value as T\n}\n\nfunction optionalEnum<T extends string>(args: Record<string, unknown>, name: string, values: readonly T[]): T | undefined {\n if (args[name] === undefined || args[name] === null) return undefined\n return requireEnum(args, name, values)\n}\n\n/** Seconds→frames for a duration argument: rejects values that round below the\n * minimum so the model learns the frame floor instead of hitting a deeper\n * validation error. */\nfunction durationArgToFrames(seconds: number, name: string, fps: number): number {\n const frames = secondsToFrames(seconds, fps)\n if (frames < MIN_SEQUENCE_CLIP_FRAMES) {\n throw new Error(`${name} (${formatSeconds(seconds)}) is shorter than ${MIN_SEQUENCE_CLIP_FRAMES} frame at ${fps} fps — minimum is ${formatSeconds(MIN_SEQUENCE_CLIP_FRAMES / fps)}`)\n }\n return frames\n}\n\n/** A (start, duration) seconds pair becomes frames by rounding the START and\n * END instants and deriving the duration — rounding start and duration\n * independently lets round(s·fps) + round(d·fps) drift a frame from\n * round((s+d)·fps), so back-to-back transcription cues would overlap or gap.\n * Rejects spans that collapse below the one-frame floor. */\nfunction boundsArgsToFrames(\n startSeconds: number,\n durationSeconds: number,\n durationName: string,\n fps: number,\n): { startFrame: number; durationFrames: number } {\n const startFrame = secondsToFrames(startSeconds, fps)\n const durationFrames = secondsToFrames(startSeconds + durationSeconds, fps) - startFrame\n if (durationFrames < MIN_SEQUENCE_CLIP_FRAMES) {\n throw new Error(`${durationName} (${formatSeconds(durationSeconds)}) spans fewer than ${MIN_SEQUENCE_CLIP_FRAMES} frame at ${fps} fps — minimum is ${formatSeconds(MIN_SEQUENCE_CLIP_FRAMES / fps)}`)\n }\n return { startFrame, durationFrames }\n}\n\n/** The frame DISPLAYED at instant t is floor(t·fps): a frame holds the screen\n * for [f/fps, (f+1)/fps), so rounding up would report the NEXT clip for the\n * half-frame before every cut. The epsilon absorbs float error in fractional\n * timestamps (1/3 s at 30 fps must be frame 10, not 9). */\nfunction displayedFrameAt(seconds: number, fps: number): number {\n return Math.floor(seconds * fps + 1e-6)\n}\n\n// ---------------------------------------------------------------------------\n// Wire views — snake_case, seconds + timecodes alongside raw frames\n// ---------------------------------------------------------------------------\n\nfunction clipView(clip: SequenceClip, fps: number): Record<string, unknown> {\n const endFrame = clip.startFrame + clip.durationFrames\n return {\n id: clip.id,\n track_id: clip.trackId,\n label: clip.label,\n start_seconds: framesToSeconds(clip.startFrame, fps),\n duration_seconds: framesToSeconds(clip.durationFrames, fps),\n end_seconds: framesToSeconds(endFrame, fps),\n start_timecode: formatTimecode(clip.startFrame, fps),\n end_timecode: formatTimecode(endFrame, fps),\n start_frame: clip.startFrame,\n duration_frames: clip.durationFrames,\n source_in_frame: clip.sourceInFrame,\n source_out_frame: clip.sourceOutFrame,\n disabled: clip.disabled,\n ...(clip.text !== undefined ? { text: clip.text } : {}),\n ...(clip.language !== undefined ? { language: clip.language } : {}),\n ...(clip.generationId !== undefined ? { generation_id: clip.generationId } : {}),\n ...(clip.assetId !== undefined ? { asset_id: clip.assetId } : {}),\n ...(clip.media\n ? {\n media: {\n url: clip.media.url,\n kind: clip.media.kind,\n ...(clip.media.durationSeconds !== undefined ? { duration_seconds: clip.media.durationSeconds } : {}),\n ...(clip.media.providerStatus !== undefined ? { provider_status: clip.media.providerStatus } : {}),\n },\n }\n : {}),\n }\n}\n\nfunction trackView(track: SequenceTrack): Record<string, unknown> {\n return {\n id: track.id,\n kind: track.kind,\n name: track.name,\n sort_order: track.sortOrder,\n locked: track.locked,\n muted: track.muted,\n }\n}\n\nfunction sequenceView(sequence: SequenceMeta): Record<string, unknown> {\n return {\n id: sequence.id,\n title: sequence.title,\n fps: sequence.fps,\n width: sequence.width,\n height: sequence.height,\n aspect_ratio: sequence.aspectRatio,\n status: sequence.status,\n duration_frames: sequence.durationFrames,\n duration_seconds: framesToSeconds(sequence.durationFrames, sequence.fps),\n duration_timecode: formatTimecode(sequence.durationFrames, sequence.fps),\n }\n}\n\nfunction exportView(record: SequenceExportRecord): Record<string, unknown> {\n return {\n id: record.id,\n format: record.format,\n status: record.status,\n result_url: record.resultUrl,\n created_at: record.createdAt.toISOString(),\n }\n}\n\nfunction decisionView(decision: SequenceDecision): Record<string, unknown> {\n return {\n id: decision.id,\n clip_id: decision.clipId,\n kind: decision.kind,\n instruction: decision.instruction,\n reasoning_summary: decision.reasoningSummary,\n accepted: decision.accepted,\n created_at: decision.createdAt.toISOString(),\n }\n}\n\nfunction timelineView(timeline: SequenceTimeline, playheadFrame: number): Record<string, unknown> {\n const fps = timeline.sequence.fps\n return {\n sequence: sequenceView(timeline.sequence),\n playhead: {\n frame: playheadFrame,\n seconds: framesToSeconds(playheadFrame, fps),\n timecode: formatTimecode(playheadFrame, fps),\n },\n tracks: [...timeline.tracks].sort((a, b) => a.sortOrder - b.sortOrder).map(trackView),\n clips: [...timeline.clips].sort((a, b) => a.startFrame - b.startFrame).map((clip) => clipView(clip, fps)),\n }\n}\n\nfunction applyResultView(result: SequenceApplyResult, fps: number): Record<string, unknown> {\n switch (result.kind) {\n case 'clip':\n return { kind: 'clip', clip: clipView(result.clip, fps) }\n case 'track':\n return { kind: 'track', track: trackView(result.track) }\n case 'export':\n return { kind: 'export', export: exportView(result.record) }\n case 'sequence':\n return { kind: 'sequence', sequence: sequenceView(result.sequence) }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mutation pipeline — validate the whole batch, apply in order, ONE decision\n// ---------------------------------------------------------------------------\n\nfunction instructionSummary(toolName: string, args: Record<string, unknown>): string {\n const body = JSON.stringify(args)\n const clipped = body.length > MAX_INSTRUCTION_ARG_CHARS ? `${body.slice(0, MAX_INSTRUCTION_ARG_CHARS - 3)}...` : body\n return `${toolName} ${clipped}`\n}\n\ninterface MutationBuild {\n operations: SequenceOperation[]\n /** Decision-row clip attribution. Omit for non-clip-targeted edits; never\n * set for `delete_clip` (the row must not reference a removed clip). */\n clipId?: string\n}\n\n/**\n * The single mutation path every editing tool funnels through: fetch the\n * timeline (the fps source for the one seconds→frames conversion), build the\n * frame-typed operations, run them through the shared validate+apply batch\n * kernel (`applySequenceOperations`), then record exactly one decision row.\n * Any throw before the first apply leaves the sequence untouched.\n */\nasync function runMutation(\n toolName: string,\n args: Record<string, unknown>,\n env: SequenceMcpToolEnv,\n build: (timeline: SequenceTimeline) => MutationBuild,\n): Promise<Record<string, unknown>> {\n const timeline = await env.store.getTimeline()\n const fps = timeline.sequence.fps\n const { operations, clipId } = build(timeline)\n const context: SequenceOperationContext = { playheadFrame: env.playheadFrame }\n\n const results = await applySequenceOperations(env.store, operations, context)\n\n const decision = await env.store.recordDecision({\n clipId: clipId ?? null,\n kind: 'agent_edit',\n instruction: instructionSummary(toolName, args),\n metadata: { tool: toolName, operation_count: operations.length },\n })\n\n return {\n changed: results.map((result) => applyResultView(result, fps)),\n decision_id: decision.id,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Schema fragments\n// ---------------------------------------------------------------------------\n\nfunction secondsSchema(description: string): Record<string, unknown> {\n return { type: 'number', minimum: 0, description }\n}\n\nfunction objectSchema(\n properties: Record<string, unknown>,\n required: string[],\n): Record<string, unknown> {\n return { type: 'object', properties, required, additionalProperties: false }\n}\n\nconst CLIP_ID_SCHEMA = { type: 'string', description: 'Clip id from get_timeline_state or get_clip' }\n\n// ---------------------------------------------------------------------------\n// The registry\n// ---------------------------------------------------------------------------\n\nexport const SEQUENCE_MCP_TOOLS: readonly SequenceMcpToolDefinition[] = [\n {\n name: 'get_timeline_state',\n description:\n 'Read the full timeline: sequence settings (fps, duration), playhead, all tracks, and all clips with positions in seconds and m:ss.ff timecodes plus media URLs and provider status. Call this before editing to get real clip and track ids.',\n inputSchema: objectSchema({}, []),\n run: async (_args, env) => {\n const timeline = await env.store.getTimeline()\n return timelineView(timeline, env.playheadFrame)\n },\n },\n {\n name: 'get_frame_at_time',\n description:\n 'What is on screen and audible at one moment. seconds is sequence time (e.g. 12.5). Returns the active clips, visible caption text, and a one-line human-readable summary.',\n inputSchema: objectSchema({ seconds: secondsSchema('Sequence time in seconds') }, ['seconds']),\n run: async (args, env) => {\n const timeline = await env.store.getTimeline()\n const fps = timeline.sequence.fps\n const frame = displayedFrameAt(requireSeconds(args, 'seconds'), fps)\n const snapshot = snapshotFrame(timeline, frame)\n const activeParts = snapshot.active.map(({ track, clip }) => {\n const range = `${formatTimecode(clip.startFrame, fps)}-${formatTimecode(clip.startFrame + clip.durationFrames, fps)}`\n return `${track.kind} \"${clip.label}\" (${range})`\n })\n const captionParts = snapshot.captions.map((caption) => `\"${caption.text}\"`)\n const summary =\n `At ${formatTimecode(frame, fps)} (${formatSeconds(snapshot.seconds)}): ` +\n (activeParts.length > 0 ? activeParts.join('; ') : 'nothing active') +\n (captionParts.length > 0 ? `. Captions: ${captionParts.join(', ')}` : '')\n return {\n frame: snapshot.frame,\n seconds: snapshot.seconds,\n timecode: formatTimecode(frame, fps),\n summary,\n active: snapshot.active.map(({ track, clip }) => ({ track: trackView(track), clip: clipView(clip, fps) })),\n captions: snapshot.captions.map((caption) => ({\n text: caption.text,\n ...(caption.language !== undefined ? { language: caption.language } : {}),\n clip_id: caption.clipId,\n })),\n }\n },\n },\n {\n name: 'get_clip',\n description: 'Read one clip by id, including its position (seconds + timecode), source in/out points, text, and media/provider status.',\n inputSchema: objectSchema({ clip_id: CLIP_ID_SCHEMA }, ['clip_id']),\n run: async (args, env) => {\n const clip = await env.store.getClip(requireString(args, 'clip_id'))\n const timeline = await env.store.getTimeline()\n return clipView(clip, timeline.sequence.fps)\n },\n },\n {\n name: 'place_clip',\n description:\n 'Place a new clip on the timeline. Requires label, start_seconds, duration_seconds. Bind playable media with media_url + media_kind (must come together), or reference product media via generation_id / asset_id. track_id targets a specific track; omit it to use the first unlocked track matching the media kind.',\n inputSchema: objectSchema(\n {\n label: { type: 'string', description: 'Short human-readable clip name' },\n start_seconds: secondsSchema('Where the clip starts on the timeline, in seconds'),\n duration_seconds: secondsSchema('Clip length in seconds (at least one frame)'),\n media_url: { type: 'string', description: 'Playable media URL; requires media_kind' },\n media_kind: { type: 'string', enum: [...SEQUENCE_MEDIA_KINDS], description: 'Kind of the media behind media_url' },\n generation_id: { type: 'string', description: 'Product generation row backing this clip' },\n asset_id: { type: 'string', description: 'Product asset row backing this clip' },\n track_id: { type: 'string', description: 'Target track id; omit for automatic track choice' },\n },\n ['label', 'start_seconds', 'duration_seconds'],\n ),\n run: (args, env) =>\n runMutation('place_clip', args, env, (timeline) => {\n const fps = timeline.sequence.fps\n const mediaUrl = optionalString(args, 'media_url')\n const mediaKind = optionalEnum(args, 'media_kind', SEQUENCE_MEDIA_KINDS)\n if ((mediaUrl === undefined) !== (mediaKind === undefined)) {\n throw new Error('media_url and media_kind must be provided together')\n }\n const generationId = optionalString(args, 'generation_id')\n const assetId = optionalString(args, 'asset_id')\n const trackId = optionalString(args, 'track_id')\n const bounds = boundsArgsToFrames(\n requireSeconds(args, 'start_seconds'),\n requireSeconds(args, 'duration_seconds'),\n 'duration_seconds',\n fps,\n )\n return {\n operations: [\n {\n type: 'place_clip',\n label: requireString(args, 'label'),\n startFrame: bounds.startFrame,\n durationFrames: bounds.durationFrames,\n ...(trackId !== undefined ? { trackId } : {}),\n ...(mediaUrl !== undefined && mediaKind !== undefined ? { media: { url: mediaUrl, kind: mediaKind } } : {}),\n ...(generationId !== undefined ? { generationId } : {}),\n ...(assetId !== undefined ? { assetId } : {}),\n },\n ],\n }\n }),\n },\n {\n name: 'add_caption',\n description:\n 'Add one caption clip. Omit start_seconds and duration_seconds to auto-place roughly 3 seconds of caption near the playhead without overlapping existing captions. language is a BCP-47 tag like \"en\" or \"es\".',\n inputSchema: objectSchema(\n {\n text: { type: 'string', description: 'Caption text' },\n language: { type: 'string', description: 'BCP-47 language tag; omit for the sequence default' },\n start_seconds: secondsSchema('Caption start in seconds; omit to auto-place near the playhead'),\n duration_seconds: secondsSchema('Caption length in seconds; omit for the ~3s default'),\n },\n ['text'],\n ),\n run: (args, env) =>\n runMutation('add_caption', args, env, (timeline) => {\n const fps = timeline.sequence.fps\n const language = optionalString(args, 'language')\n const startSeconds = optionalSeconds(args, 'start_seconds')\n const durationSeconds = optionalSeconds(args, 'duration_seconds')\n const bounds = startSeconds !== undefined && durationSeconds !== undefined\n ? boundsArgsToFrames(startSeconds, durationSeconds, 'duration_seconds', fps)\n : {\n ...(startSeconds !== undefined ? { startFrame: secondsToFrames(startSeconds, fps) } : {}),\n ...(durationSeconds !== undefined\n ? { durationFrames: durationArgToFrames(durationSeconds, 'duration_seconds', fps) }\n : {}),\n }\n return {\n operations: [\n {\n type: 'add_caption',\n text: requireString(args, 'text'),\n ...(language !== undefined ? { language } : {}),\n ...bounds,\n },\n ],\n }\n }),\n },\n {\n name: 'add_captions',\n description:\n `Add many caption clips in one call — use this for transcription output instead of repeated add_caption. Each item needs text, start_seconds, duration_seconds. One shared language applies to every caption. Max ${MAX_CAPTION_BATCH} per call.`,\n inputSchema: objectSchema(\n {\n captions: {\n type: 'array',\n minItems: 1,\n maxItems: MAX_CAPTION_BATCH,\n description: 'Caption entries in timeline order',\n items: objectSchema(\n {\n text: { type: 'string', description: 'Caption text' },\n start_seconds: secondsSchema('Caption start in seconds'),\n duration_seconds: secondsSchema('Caption length in seconds'),\n },\n ['text', 'start_seconds', 'duration_seconds'],\n ),\n },\n language: { type: 'string', description: 'BCP-47 language tag applied to every caption in the batch' },\n },\n ['captions'],\n ),\n run: (args, env) =>\n runMutation('add_captions', args, env, (timeline) => {\n const fps = timeline.sequence.fps\n const language = optionalString(args, 'language')\n const raw = args.captions\n if (!Array.isArray(raw) || raw.length === 0) {\n throw new Error('captions is required and must be a non-empty array of {text, start_seconds, duration_seconds}')\n }\n if (raw.length > MAX_CAPTION_BATCH) {\n throw new Error(`captions has ${raw.length} entries — max ${MAX_CAPTION_BATCH} per call; split the batch`)\n }\n const operations = raw.map((entry, index): SequenceOperation => {\n if (!isRecord(entry)) throw new Error(`captions[${index}] must be an object with text, start_seconds, duration_seconds`)\n try {\n const bounds = boundsArgsToFrames(\n requireSeconds(entry, 'start_seconds'),\n requireSeconds(entry, 'duration_seconds'),\n 'duration_seconds',\n fps,\n )\n return {\n type: 'add_caption',\n text: requireString(entry, 'text'),\n startFrame: bounds.startFrame,\n durationFrames: bounds.durationFrames,\n ...(language !== undefined ? { language } : {}),\n }\n } catch (err) {\n throw new Error(`captions[${index}]: ${err instanceof Error ? err.message : String(err)}`)\n }\n })\n return { operations }\n }),\n },\n {\n name: 'move_clip',\n description: 'Move a clip so it starts at start_seconds, optionally onto another track via track_id. Duration and source in/out points are unchanged.',\n inputSchema: objectSchema(\n {\n clip_id: CLIP_ID_SCHEMA,\n start_seconds: secondsSchema('New clip start on the timeline, in seconds'),\n track_id: { type: 'string', description: 'Destination track id; omit to stay on the current track' },\n },\n ['clip_id', 'start_seconds'],\n ),\n run: (args, env) => {\n const clipId = requireString(args, 'clip_id')\n return runMutation('move_clip', args, env, (timeline) => {\n const trackId = optionalString(args, 'track_id')\n return {\n clipId,\n operations: [\n {\n type: 'move_clip',\n clipId,\n startFrame: secondsToFrames(requireSeconds(args, 'start_seconds'), timeline.sequence.fps),\n ...(trackId !== undefined ? { trackId } : {}),\n },\n ],\n }\n })\n },\n },\n {\n name: 'trim_clip',\n description:\n 'Set a clip to start_seconds + duration_seconds. source_in_seconds re-anchors where playback begins inside the source media (use it when trimming the head so the visible content stays aligned). A clip with an explicit source out-point (e.g. a split half) cannot grow past it — pass source_out_seconds to move the out-point when extending.',\n inputSchema: objectSchema(\n {\n clip_id: CLIP_ID_SCHEMA,\n start_seconds: secondsSchema('Clip start on the timeline, in seconds'),\n duration_seconds: secondsSchema('New clip length in seconds (at least one frame)'),\n source_in_seconds: secondsSchema('Offset into the source media where playback begins, in seconds'),\n source_out_seconds: secondsSchema('Offset into the source media where playback ends, in seconds; only needed to extend past a stored out-point'),\n },\n ['clip_id', 'start_seconds', 'duration_seconds'],\n ),\n run: (args, env) => {\n const clipId = requireString(args, 'clip_id')\n return runMutation('trim_clip', args, env, (timeline) => {\n const fps = timeline.sequence.fps\n const sourceInSeconds = optionalSeconds(args, 'source_in_seconds')\n const sourceOutSeconds = optionalSeconds(args, 'source_out_seconds')\n const bounds = boundsArgsToFrames(\n requireSeconds(args, 'start_seconds'),\n requireSeconds(args, 'duration_seconds'),\n 'duration_seconds',\n fps,\n )\n return {\n clipId,\n operations: [\n {\n type: 'trim_clip',\n clipId,\n startFrame: bounds.startFrame,\n durationFrames: bounds.durationFrames,\n ...(sourceInSeconds !== undefined ? { sourceInFrame: secondsToFrames(sourceInSeconds, fps) } : {}),\n ...(sourceOutSeconds !== undefined ? { sourceOutFrame: secondsToFrames(sourceOutSeconds, fps) } : {}),\n },\n ],\n }\n })\n },\n },\n {\n name: 'split_clip',\n description: 'Cut a clip into two at at_seconds (sequence time, strictly inside the clip). The original clip id keeps the left half; the returned clip is the new right half with its source in-point re-anchored at the cut.',\n inputSchema: objectSchema(\n {\n clip_id: CLIP_ID_SCHEMA,\n at_seconds: secondsSchema('Sequence time of the cut, in seconds; must fall strictly inside the clip'),\n },\n ['clip_id', 'at_seconds'],\n ),\n run: (args, env) => {\n const clipId = requireString(args, 'clip_id')\n return runMutation('split_clip', args, env, (timeline) => ({\n clipId,\n operations: [\n {\n type: 'split_clip',\n clipId,\n atFrame: secondsToFrames(requireSeconds(args, 'at_seconds'), timeline.sequence.fps),\n },\n ],\n }))\n },\n },\n {\n name: 'set_clip_text',\n description: 'Replace a caption clip\\'s text, optionally changing its BCP-47 language tag.',\n inputSchema: objectSchema(\n {\n clip_id: CLIP_ID_SCHEMA,\n text: { type: 'string', description: 'New caption text' },\n language: { type: 'string', description: 'BCP-47 language tag; omit to keep the current one' },\n },\n ['clip_id', 'text'],\n ),\n run: (args, env) => {\n const clipId = requireString(args, 'clip_id')\n return runMutation('set_clip_text', args, env, () => {\n const language = optionalString(args, 'language')\n return {\n clipId,\n operations: [\n {\n type: 'set_clip_text',\n clipId,\n text: requireString(args, 'text'),\n ...(language !== undefined ? { language } : {}),\n },\n ],\n }\n })\n },\n },\n {\n name: 'delete_clip',\n description: 'Remove a clip from the timeline permanently. Prefer set_clip_disabled to audition a cut without losing the clip.',\n inputSchema: objectSchema({ clip_id: CLIP_ID_SCHEMA }, ['clip_id']),\n run: (args, env) => {\n const clipId = requireString(args, 'clip_id')\n return runMutation('delete_clip', args, env, () => ({\n operations: [{ type: 'delete_clip', clipId }],\n }))\n },\n },\n {\n name: 'set_clip_disabled',\n description: 'Disable (true) or re-enable (false) a clip without deleting it. Disabled clips do not render or sound.',\n inputSchema: objectSchema(\n {\n clip_id: CLIP_ID_SCHEMA,\n disabled: { type: 'boolean', description: 'true hides the clip; false restores it' },\n },\n ['clip_id', 'disabled'],\n ),\n run: (args, env) => {\n const clipId = requireString(args, 'clip_id')\n return runMutation('set_clip_disabled', args, env, () => ({\n clipId,\n operations: [{ type: 'set_clip_disabled', clipId, disabled: requireBoolean(args, 'disabled') }],\n }))\n },\n },\n {\n name: 'create_track',\n description: `Add a track to the sequence. kind is one of: ${SEQUENCE_TRACK_KINDS.join(', ')}. New tracks sort below existing ones.`,\n inputSchema: objectSchema(\n {\n kind: { type: 'string', enum: [...SEQUENCE_TRACK_KINDS], description: 'Track kind' },\n name: { type: 'string', description: 'Track display name' },\n },\n ['kind', 'name'],\n ),\n run: (args, env) =>\n runMutation('create_track', args, env, () => ({\n operations: [\n {\n type: 'create_track',\n kind: requireEnum(args, 'kind', SEQUENCE_TRACK_KINDS),\n name: requireString(args, 'name'),\n },\n ],\n })),\n },\n {\n name: 'extend_sequence',\n description: 'Set the sequence\\'s total duration in seconds. Growing always works; shrinking is rejected if any clip would fall past the new end.',\n inputSchema: objectSchema(\n { duration_seconds: secondsSchema('New total sequence duration in seconds') },\n ['duration_seconds'],\n ),\n run: (args, env) =>\n runMutation('extend_sequence', args, env, (timeline) => ({\n operations: [\n {\n type: 'extend_sequence',\n durationFrames: durationArgToFrames(requireSeconds(args, 'duration_seconds'), 'duration_seconds', timeline.sequence.fps),\n },\n ],\n })),\n },\n {\n name: 'queue_export',\n description: `Queue an export of the sequence. format is one of: ${SEQUENCE_EXPORT_FORMATS.join(', ')}. Returns the queued export record; rendering happens asynchronously.`,\n inputSchema: objectSchema(\n { format: { type: 'string', enum: [...SEQUENCE_EXPORT_FORMATS], description: 'Export format' } },\n ['format'],\n ),\n run: (args, env) =>\n runMutation('queue_export', args, env, () => ({\n operations: [{ type: 'queue_export', format: requireEnum(args, 'format', SEQUENCE_EXPORT_FORMATS) }],\n })),\n },\n {\n name: 'list_decisions',\n description: 'Read the sequence\\'s edit-decision log (human edits, agent edits, exports, notes), newest first. limit caps the number of rows.',\n inputSchema: objectSchema(\n { limit: { type: 'integer', minimum: 1, maximum: 1000, description: 'Max rows to return' } },\n [],\n ),\n run: async (args, env) => {\n const limit = optionalPositiveInteger(args, 'limit', 1000)\n const decisions = await env.store.listDecisions(limit)\n return { decisions: decisions.map(decisionView) }\n },\n },\n]\n\nexport function findSequenceMcpTool(name: string): SequenceMcpToolDefinition | undefined {\n return SEQUENCE_MCP_TOOLS.find((tool) => tool.name === name)\n}\n","/**\n * Streamable-HTTP MCP server for one sequence — the live agent→timeline\n * channel. JSON-RPC 2.0 over POST, stateless per request (Workers-compatible:\n * no session table, no SSE — a tools-only server answers every request with a\n * single `application/json` body, which the streamable-HTTP transport\n * explicitly permits).\n *\n * Trust boundary: the PRODUCT authenticates the request (capability token,\n * workspace RBAC) BEFORE constructing the scoped {@link SequenceStore} and\n * calling this handler — the handler trusts its store completely. Tool\n * execution failures (argument shape, validation, store throws) become\n * `isError` tool results carrying the thrown message verbatim so the model can\n * read WHY and retry; only protocol-level misuse becomes a JSON-RPC error.\n */\n\nimport type { SequenceStore } from './store'\nimport { SEQUENCE_MCP_TOOLS, findSequenceMcpTool } from './mcp-tools'\n\n/** Newest first. The handler echoes the client's requested version when\n * supported, else answers with the newest it speaks (per MCP negotiation the\n * client then disconnects if it cannot use it). */\nexport const SEQUENCES_MCP_PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'] as const\n\nconst LATEST_PROTOCOL_VERSION = SEQUENCES_MCP_PROTOCOL_VERSIONS[0]\n\nexport interface SequencesMcpServerInfo {\n name: string\n version: string\n}\n\nexport interface CreateSequencesMcpHandlerOptions {\n /** Already scoped + authorized for one (workspace, sequence, actor). */\n store: SequenceStore\n /** Editor playhead at request time, in frames; anchors auto-placed captions.\n * Default 0 (sequence start) for headless callers. */\n playheadFrame?: number\n serverInfo?: SequencesMcpServerInfo\n}\n\ntype JsonRpcId = string | number | null\n\ninterface ToolCallContent {\n content: Array<{ type: 'text'; text: string }>\n isError?: true\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction rpcResult(id: JsonRpcId, result: unknown): Response {\n return Response.json({ jsonrpc: '2.0', id, result })\n}\n\nfunction rpcError(id: JsonRpcId, code: number, message: string, status = 200): Response {\n return Response.json({ jsonrpc: '2.0', id, error: { code, message } }, { status })\n}\n\nexport function createSequencesMcpHandler(\n opts: CreateSequencesMcpHandlerOptions,\n): (request: Request) => Promise<Response> {\n const playheadFrame = opts.playheadFrame ?? 0\n if (!Number.isInteger(playheadFrame) || playheadFrame < 0) {\n throw new Error('playheadFrame must be a non-negative integer (frames at the sequence fps)')\n }\n const serverInfo = opts.serverInfo ?? { name: 'sequences', version: '1.0.0' }\n\n return async (request: Request): Promise<Response> => {\n if (request.method !== 'POST') {\n // Tools-only server: no GET/SSE stream to open, no DELETE session to end.\n return new Response('sequences MCP accepts JSON-RPC 2.0 over POST only', {\n status: 405,\n headers: { Allow: 'POST' },\n })\n }\n\n let body: unknown\n try {\n body = await request.json()\n } catch {\n return rpcError(null, -32700, 'Parse error: request body is not valid JSON', 400)\n }\n if (Array.isArray(body)) {\n return rpcError(null, -32600, 'Invalid request: JSON-RPC batching is not supported', 400)\n }\n if (!isRecord(body) || body.jsonrpc !== '2.0' || typeof body.method !== 'string') {\n return rpcError(null, -32600, 'Invalid request: expected a JSON-RPC 2.0 object with jsonrpc \"2.0\" and a string method', 400)\n }\n\n const method = body.method\n const params = isRecord(body.params) ? body.params : {}\n\n // A request without an `id` member is a notification (e.g.\n // notifications/initialized) — acknowledge with 202 and no JSON-RPC body.\n if (!('id' in body) || body.id === undefined) {\n return new Response(null, { status: 202 })\n }\n const id = body.id as JsonRpcId\n\n switch (method) {\n case 'initialize': {\n const requested = typeof params.protocolVersion === 'string' ? params.protocolVersion : undefined\n const protocolVersion =\n requested !== undefined && (SEQUENCES_MCP_PROTOCOL_VERSIONS as readonly string[]).includes(requested)\n ? requested\n : LATEST_PROTOCOL_VERSION\n return rpcResult(id, {\n protocolVersion,\n capabilities: { tools: { listChanged: false } },\n serverInfo,\n })\n }\n\n case 'ping':\n return rpcResult(id, {})\n\n case 'tools/list':\n return rpcResult(id, {\n tools: SEQUENCE_MCP_TOOLS.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n })),\n })\n\n case 'tools/call': {\n const name = params.name\n if (typeof name !== 'string' || name.length === 0) {\n return rpcError(id, -32602, 'tools/call requires params.name (string)')\n }\n const tool = findSequenceMcpTool(name)\n if (!tool) {\n return rpcError(\n id,\n -32602,\n `Unknown tool: ${name}. Available tools: ${SEQUENCE_MCP_TOOLS.map((t) => t.name).join(', ')}`,\n )\n }\n if (params.arguments !== undefined && !isRecord(params.arguments)) {\n return rpcError(id, -32602, 'tools/call params.arguments must be an object when provided')\n }\n const args = isRecord(params.arguments) ? params.arguments : {}\n try {\n const result = await tool.run(args, { store: opts.store, playheadFrame })\n const payload: ToolCallContent = { content: [{ type: 'text', text: JSON.stringify(result) }] }\n return rpcResult(id, payload)\n } catch (err) {\n // The model reads this text to correct its call — keep the thrown\n // reason verbatim, prefixed with the tool so multi-call turns stay\n // attributable.\n const message = err instanceof Error ? err.message : String(err)\n const payload: ToolCallContent = {\n content: [{ type: 'text', text: `${name} failed: ${message}` }],\n isError: true,\n }\n return rpcResult(id, payload)\n }\n }\n\n default:\n return rpcError(id, -32601, `Method not found: ${method}`)\n }\n }\n}\n","/**\n * Profile entry for the sequences MCP server — what a product spreads into its\n * sandbox `AgentProfile.mcp` map so the in-sandbox agent gets the live\n * timeline channel. Same shape and conventions as the app-tool bridges in\n * ../tools/mcp: transport 'http', capability token in the Authorization\n * header (server-set, never a tool argument), identity headers when the\n * product recovers the user via `authenticateToolRequest`.\n */\n\nimport { DEFAULT_HEADER_NAMES } from '../tools/auth'\nimport type { ToolHeaderNames } from '../tools/auth'\nimport { buildHttpMcpServer } from '../tools/mcp'\nimport type { AppToolMcpServer } from '../tools/mcp'\nimport type { AppToolContext } from '../tools/types'\n\nexport const DEFAULT_SEQUENCES_MCP_DESCRIPTION =\n 'Live timeline editor for the current video sequence: read timeline state, place/move/trim/split clips, add captions, manage tracks, and queue exports. All times are seconds.'\n\nexport interface BuildSequencesMcpServerEntryOptions {\n /** App base URL the sandbox reaches back to (trailing slash tolerated). */\n baseUrl: string\n /** Product route serving `createSequencesMcpHandler` for ONE sequence —\n * the sequence id is part of the path, never a tool argument. */\n path: string\n /** Capability token the product minted for this (user, sequence) scope.\n * With no token there is no entry to build — omit the server instead. */\n token: string\n description?: string\n /** Identity headers for products whose route recovers the user via\n * `authenticateToolRequest`. Omit when the bearer token is self-contained. */\n ctx?: AppToolContext\n headerNames?: ToolHeaderNames\n}\n\n/** Build the `AgentProfileMcpServer`-shaped entry for the sequences channel. */\nexport function buildSequencesMcpServerEntry(opts: BuildSequencesMcpServerEntryOptions): AppToolMcpServer {\n if (opts.token.trim().length === 0) {\n throw new Error('buildSequencesMcpServerEntry requires a capability token — omit the sequences MCP server when none is available')\n }\n if (!opts.path.startsWith('/')) {\n throw new Error(`buildSequencesMcpServerEntry path must start with \"/\" (got \"${opts.path}\")`)\n }\n const description = opts.description ?? DEFAULT_SEQUENCES_MCP_DESCRIPTION\n\n if (opts.ctx) {\n return buildHttpMcpServer({\n path: opts.path,\n baseUrl: opts.baseUrl,\n token: opts.token,\n ctx: opts.ctx,\n description,\n headerNames: opts.headerNames ?? DEFAULT_HEADER_NAMES,\n })\n }\n\n return {\n transport: 'http',\n url: `${opts.baseUrl.replace(/\\/+$/, '')}${opts.path}`,\n headers: {\n Authorization: `Bearer ${opts.token}`,\n 'Content-Type': 'application/json',\n },\n enabled: true,\n metadata: { description },\n }\n}\n"],"mappings":"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","names":["track","isRecord"]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
authenticateToolRequest
|
|
3
|
+
} from "./chunk-IJZJWKUK.js";
|
|
4
|
+
import {
|
|
5
|
+
dispatchAppTool,
|
|
6
|
+
outcomeStatus
|
|
7
|
+
} from "./chunk-QAQBR6KQ.js";
|
|
8
|
+
|
|
9
|
+
// src/tools/capability.ts
|
|
10
|
+
async function createCapabilityToken(userId, opts) {
|
|
11
|
+
const secret = opts.secret?.trim();
|
|
12
|
+
if (!secret) return void 0;
|
|
13
|
+
const prefix = opts.prefix ?? "cap_";
|
|
14
|
+
return `${prefix}${await sign(userId, secret)}`;
|
|
15
|
+
}
|
|
16
|
+
async function verifyCapabilityToken(userId, token, opts) {
|
|
17
|
+
const secret = opts.secret?.trim();
|
|
18
|
+
const prefix = opts.prefix ?? "cap_";
|
|
19
|
+
if (!secret || !token.startsWith(prefix)) return false;
|
|
20
|
+
const expected = `${prefix}${await sign(userId, secret)}`;
|
|
21
|
+
return timingSafeEqual(token, expected);
|
|
22
|
+
}
|
|
23
|
+
async function createExpiringCapabilityToken(subject, opts) {
|
|
24
|
+
const secret = opts.secret?.trim();
|
|
25
|
+
if (!secret) return void 0;
|
|
26
|
+
if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error("expiresInMs must be a positive number");
|
|
27
|
+
const prefix = opts.prefix ?? "cap_";
|
|
28
|
+
const now = opts.now ?? Date.now;
|
|
29
|
+
const payload = base64urlText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }));
|
|
30
|
+
return `${prefix}${payload}.${await signText(payload, secret)}`;
|
|
31
|
+
}
|
|
32
|
+
async function verifyExpiringCapabilityToken(subject, token, opts) {
|
|
33
|
+
const secret = opts.secret?.trim();
|
|
34
|
+
const prefix = opts.prefix ?? "cap_";
|
|
35
|
+
if (!secret || !token.startsWith(prefix)) return false;
|
|
36
|
+
const body = token.slice(prefix.length);
|
|
37
|
+
const dot = body.lastIndexOf(".");
|
|
38
|
+
if (dot <= 0 || dot === body.length - 1) return false;
|
|
39
|
+
const payload = body.slice(0, dot);
|
|
40
|
+
const sig = body.slice(dot + 1);
|
|
41
|
+
if (!timingSafeEqual(sig, await signText(payload, secret))) return false;
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(textFromBase64url(payload));
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (parsed.sub !== subject) return false;
|
|
49
|
+
if (typeof parsed.exp !== "number") return false;
|
|
50
|
+
const now = opts.now ?? Date.now;
|
|
51
|
+
return parsed.exp > now();
|
|
52
|
+
}
|
|
53
|
+
async function sign(userId, secret) {
|
|
54
|
+
return signText(`user:${userId}`, secret);
|
|
55
|
+
}
|
|
56
|
+
async function signText(message, secret) {
|
|
57
|
+
const enc = new TextEncoder();
|
|
58
|
+
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
59
|
+
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
|
|
60
|
+
return base64url(new Uint8Array(sig));
|
|
61
|
+
}
|
|
62
|
+
function base64urlText(text) {
|
|
63
|
+
return base64url(new TextEncoder().encode(text));
|
|
64
|
+
}
|
|
65
|
+
function textFromBase64url(value) {
|
|
66
|
+
const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
67
|
+
const bin = atob(b64);
|
|
68
|
+
const bytes = new Uint8Array(bin.length);
|
|
69
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
70
|
+
return new TextDecoder().decode(bytes);
|
|
71
|
+
}
|
|
72
|
+
function base64url(bytes) {
|
|
73
|
+
let s = "";
|
|
74
|
+
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
75
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
76
|
+
}
|
|
77
|
+
function timingSafeEqual(a, b) {
|
|
78
|
+
if (a.length !== b.length) return false;
|
|
79
|
+
let diff = 0;
|
|
80
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
81
|
+
return diff === 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/tools/http.ts
|
|
85
|
+
async function handleAppToolRequest(request, opts) {
|
|
86
|
+
if (request.method !== "POST") return Response.json({ error: "Method not allowed" }, { status: 405 });
|
|
87
|
+
const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames });
|
|
88
|
+
if (!auth.ok) return auth.response;
|
|
89
|
+
let body;
|
|
90
|
+
try {
|
|
91
|
+
body = await request.json();
|
|
92
|
+
} catch {
|
|
93
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
|
94
|
+
}
|
|
95
|
+
const args = body.args ?? body.arguments ?? body;
|
|
96
|
+
const outcome = await dispatchAppTool(opts.tool, args, auth.ctx, opts);
|
|
97
|
+
if (!outcome.ok) {
|
|
98
|
+
return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) });
|
|
99
|
+
}
|
|
100
|
+
const payload = outcome.result;
|
|
101
|
+
return Response.json({ ok: true, ...payload, ...opts.message ? { message: opts.message(outcome.result) } : {} });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
createCapabilityToken,
|
|
106
|
+
verifyCapabilityToken,
|
|
107
|
+
createExpiringCapabilityToken,
|
|
108
|
+
verifyExpiringCapabilityToken,
|
|
109
|
+
handleAppToolRequest
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=chunk-CF5DZELC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/capability.ts","../src/tools/http.ts"],"sourcesContent":["/**\n * Per-user capability token — the sandbox→app auth primitive behind the\n * `verifyToken` seam in {@link authenticateToolRequest}.\n *\n * An app-agent runs inside the sandbox and reaches the host app back over HTTP\n * (the app tools, the integration-invoke bridge). The route must act AS the\n * connecting user without trusting any model-supplied identity, so the turn\n * mints a short HMAC token bound to the user id and bakes it into the per-turn\n * MCP server header; the route verifies it to recover the user.\n *\n * `HMAC-SHA256(secret, \"user:<userId>\")`, base64url, with an app-chosen prefix.\n * The token encodes no scopes — the hub's policy engine authorizes per action.\n * Fail-closed: with no secret, no token is minted (the caller MUST omit the MCP\n * server rather than fake an authorized call). WebCrypto only — runs on\n * Workers, Node, and the browser with no Node `crypto` dependency.\n */\n\nexport interface CapabilityTokenOptions {\n /** Shared HMAC secret. When absent, mint returns undefined / verify returns false. */\n secret?: string\n /** Token prefix (namespaces the credential; lets verify reject foreign tokens\n * cheaply). Default `cap_`. */\n prefix?: string\n}\n\n/** Mint a capability token for `userId`, or `undefined` when no secret is\n * configured (fail-closed — the caller omits the MCP server rather than fake it). */\nexport async function createCapabilityToken(userId: string, opts: CapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n const prefix = opts.prefix ?? 'cap_'\n return `${prefix}${await sign(userId, secret)}`\n}\n\n/** Verify a capability token against `userId`. Returns false (never throws) for\n * an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */\nexport async function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const expected = `${prefix}${await sign(userId, secret)}`\n return timingSafeEqual(token, expected)\n}\n\nexport interface ExpiringCapabilityTokenOptions extends CapabilityTokenOptions {\n /** Token lifetime. Expired tokens verify false regardless of signature. */\n expiresInMs: number\n /** Clock injection for tests; defaults to Date.now. */\n now?: () => number\n}\n\n/**\n * Mint an EXPIRING capability token: `<prefix><base64url(payload)>.<sig>` where\n * the payload carries `{ sub, exp, n }` (subject, epoch-ms expiry, random\n * nonce) and the signature is HMAC-SHA256 over the encoded payload. Use this\n * for user-initiated scoped channels (e.g. a per-sequence MCP endpoint) where\n * a captured token must not stay valid past its window; the bare\n * {@link createCapabilityToken} remains for turn-scoped tool bridges whose\n * mint+verify happen inside one request cycle. Fail-closed like the bare\n * variant: no secret → no token.\n */\nexport async function createExpiringCapabilityToken(subject: string, opts: ExpiringCapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error('expiresInMs must be a positive number')\n const prefix = opts.prefix ?? 'cap_'\n const now = opts.now ?? Date.now\n const payload = base64urlText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }))\n return `${prefix}${payload}.${await signText(payload, secret)}`\n}\n\n/** Verify an expiring token against `subject`: prefix, payload integrity,\n * subject match, and expiry all checked; returns false (never throws) on any\n * failure including a malformed payload. */\nexport async function verifyExpiringCapabilityToken(subject: string, token: string, opts: CapabilityTokenOptions & { now?: () => number }): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const body = token.slice(prefix.length)\n const dot = body.lastIndexOf('.')\n if (dot <= 0 || dot === body.length - 1) return false\n const payload = body.slice(0, dot)\n const sig = body.slice(dot + 1)\n if (!timingSafeEqual(sig, await signText(payload, secret))) return false\n let parsed: { sub?: unknown; exp?: unknown }\n try {\n parsed = JSON.parse(textFromBase64url(payload)) as { sub?: unknown; exp?: unknown }\n } catch {\n return false\n }\n if (parsed.sub !== subject) return false\n if (typeof parsed.exp !== 'number') return false\n const now = opts.now ?? Date.now\n return parsed.exp > now()\n}\n\nasync function sign(userId: string, secret: string): Promise<string> {\n return signText(`user:${userId}`, secret)\n}\n\nasync function signText(message: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const sig = await crypto.subtle.sign('HMAC', key, enc.encode(message))\n return base64url(new Uint8Array(sig))\n}\n\nfunction base64urlText(text: string): string {\n return base64url(new TextEncoder().encode(text))\n}\n\nfunction textFromBase64url(value: string): string {\n const b64 = value.replace(/-/g, '+').replace(/_/g, '/')\n const bin = atob(b64)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n return new TextDecoder().decode(bytes)\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let s = ''\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/** Length-independent-leak-free compare for two same-charset strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport type { AppToolName } from './openai'\n\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves. */\n tool: AppToolName\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: { args?: Record<string, unknown>; arguments?: Record<string, unknown> } & Record<string, unknown>\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n const outcome = await dispatchAppTool(opts.tool, args, auth.ctx, opts)\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n"],"mappings":"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","names":[]}
|