@pixerate/editor 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -1
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent/index.cjs +175 -11
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +18 -4
- package/dist/agent/index.d.ts +18 -4
- package/dist/agent/index.js +168 -4
- package/dist/agent/index.js.map +1 -1
- package/dist/{arrangeShortcuts-B8O3f8-P.d.cts → arrange-BICutYPk.d.cts} +243 -29
- package/dist/{arrangeShortcuts-sLlD2rSX.d.ts → arrange-BICutYPk.d.ts} +243 -29
- package/dist/builtins-CdPcLwwF.d.cts +176 -0
- package/dist/builtins-Chj6zv_O.d.ts +176 -0
- package/dist/canvas/index.cjs +8588 -1857
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +1847 -9
- package/dist/canvas/index.d.ts +1847 -9
- package/dist/canvas/index.js +8577 -1846
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-3CC7OPUQ.cjs +831 -0
- package/dist/chunk-3CC7OPUQ.cjs.map +1 -0
- package/dist/chunk-4BVP64JJ.cjs +1406 -0
- package/dist/chunk-4BVP64JJ.cjs.map +1 -0
- package/dist/chunk-BVP3TSLH.cjs +69 -0
- package/dist/chunk-BVP3TSLH.cjs.map +1 -0
- package/dist/{chunk-OLZJXL4R.cjs → chunk-CS6NZWWA.cjs} +59 -25
- package/dist/chunk-CS6NZWWA.cjs.map +1 -0
- package/dist/{chunk-MCSJFWAO.cjs → chunk-EWCOFSCQ.cjs} +685 -100
- package/dist/chunk-EWCOFSCQ.cjs.map +1 -0
- package/dist/chunk-HDYMLS3X.cjs +409 -0
- package/dist/chunk-HDYMLS3X.cjs.map +1 -0
- package/dist/chunk-HRMEDT2L.js +1406 -0
- package/dist/chunk-HRMEDT2L.js.map +1 -0
- package/dist/{chunk-D27YFC46.cjs → chunk-IFHKKVXH.cjs} +382 -63
- package/dist/chunk-IFHKKVXH.cjs.map +1 -0
- package/dist/{chunk-PGNZKFQS.js → chunk-JOIREKL7.js} +137 -58
- package/dist/chunk-JOIREKL7.js.map +1 -0
- package/dist/chunk-KRGXUQ2Y.js +1696 -0
- package/dist/chunk-KRGXUQ2Y.js.map +1 -0
- package/dist/{chunk-3LV6RRPH.js → chunk-MHEPLBFB.js} +2 -2
- package/dist/chunk-MR2764X2.js +831 -0
- package/dist/chunk-MR2764X2.js.map +1 -0
- package/dist/{chunk-XNORY7M6.cjs → chunk-NFVBT3UZ.cjs} +166 -87
- package/dist/chunk-NFVBT3UZ.cjs.map +1 -0
- package/dist/chunk-NQF32I6Z.cjs +6478 -0
- package/dist/chunk-NQF32I6Z.cjs.map +1 -0
- package/dist/chunk-O5RYH2PG.js +69 -0
- package/dist/chunk-O5RYH2PG.js.map +1 -0
- package/dist/{chunk-ITBSKI26.js → chunk-Q2OHWD2A.js} +1064 -360
- package/dist/chunk-Q2OHWD2A.js.map +1 -0
- package/dist/{chunk-FODNLX5N.js → chunk-QJSOZFCZ.js} +38 -4
- package/dist/chunk-QJSOZFCZ.js.map +1 -0
- package/dist/{chunk-SML64VJG.cjs → chunk-RRREAOVM.cjs} +4 -4
- package/dist/{chunk-SML64VJG.cjs.map → chunk-RRREAOVM.cjs.map} +1 -1
- package/dist/{chunk-5S3Z4PPF.js → chunk-SWE3NPOU.js} +347 -28
- package/dist/chunk-SWE3NPOU.js.map +1 -0
- package/dist/chunk-TXXNBDQX.cjs +1696 -0
- package/dist/chunk-TXXNBDQX.cjs.map +1 -0
- package/dist/chunk-UBCFIABU.js +409 -0
- package/dist/chunk-UBCFIABU.js.map +1 -0
- package/dist/chunk-X7HXL2SB.js +6478 -0
- package/dist/chunk-X7HXL2SB.js.map +1 -0
- package/dist/{chunk-SBCZ6RN5.cjs → chunk-YOMYSFJU.cjs} +1163 -459
- package/dist/chunk-YOMYSFJU.cjs.map +1 -0
- package/dist/{chunk-3CN2WD3X.js → chunk-ZRNHGAQT.js} +661 -76
- package/dist/chunk-ZRNHGAQT.js.map +1 -0
- package/dist/commands/index.cjs +425 -4
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +39 -77
- package/dist/commands/index.d.ts +39 -77
- package/dist/commands/index.js +424 -3
- package/dist/commands/index.js.map +1 -1
- package/dist/compare/index.cjs +3 -3
- package/dist/compare/index.d.cts +1 -1
- package/dist/compare/index.d.ts +1 -1
- package/dist/compare/index.js +2 -2
- package/dist/controller-CxXGOEmy.d.cts +2038 -0
- package/dist/controller-CxXGOEmy.d.ts +2038 -0
- package/dist/{documents-BQjzJNe2.d.ts → documents-CHT1U5Vs.d.ts} +10 -0
- package/dist/{documents-KRTPhvst.d.cts → documents-Gp7wb0Me.d.cts} +10 -0
- package/dist/export/index.cjs +36 -0
- package/dist/export/index.cjs.map +1 -0
- package/dist/export/index.d.cts +243 -0
- package/dist/export/index.d.ts +243 -0
- package/dist/export/index.js +36 -0
- package/dist/export/index.js.map +1 -0
- package/dist/extensions/index.cjs +101 -6
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +18 -5
- package/dist/extensions/index.d.ts +18 -5
- package/dist/extensions/index.js +101 -6
- package/dist/generation/index.cjs +1013 -69
- package/dist/generation/index.cjs.map +1 -1
- package/dist/generation/index.d.cts +474 -13
- package/dist/generation/index.d.ts +474 -13
- package/dist/generation/index.js +976 -32
- package/dist/generation/index.js.map +1 -1
- package/dist/image-editor/index.cjs +3385 -1275
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +693 -128
- package/dist/image-editor/index.d.ts +693 -128
- package/dist/image-editor/index.js +3325 -1215
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-fJrlkJCd.d.ts → index-B273Zllk.d.ts} +500 -7
- package/dist/{index-BhJBSm2d.d.cts → index-jcJwmBYt.d.cts} +500 -7
- package/dist/index.cjs +1603 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -27
- package/dist/index.d.ts +154 -27
- package/dist/index.js +1584 -26
- package/dist/index.js.map +1 -1
- package/dist/mention-text-CDnCFd3c.d.cts +53 -0
- package/dist/mention-text-CDnCFd3c.d.ts +53 -0
- package/dist/model-C1zApQM8.d.cts +219 -0
- package/dist/model-C1zApQM8.d.ts +219 -0
- package/dist/persistence/index.d.cts +3 -120
- package/dist/persistence/index.d.ts +3 -120
- package/dist/registry-CED5C0lA.d.cts +204 -0
- package/dist/registry-CED5C0lA.d.ts +204 -0
- package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.cts} +12 -1
- package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.ts} +12 -1
- package/dist/{registry-D32LpIc4.d.cts → registry-D0GADKc7.d.cts} +1 -1
- package/dist/{registry-evKTP4ls.d.ts → registry-DhVUZioj.d.ts} +1 -1
- package/dist/serializers/index.cjs +12 -4
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +41 -13
- package/dist/serializers/index.d.ts +41 -13
- package/dist/serializers/index.js +12 -4
- package/dist/spreadsheet/index.cjs +157 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +534 -11
- package/dist/spreadsheet/index.d.ts +534 -11
- package/dist/spreadsheet/index.js +157 -3
- package/dist/storage-BpFfspHm.d.cts +120 -0
- package/dist/storage-BpFfspHm.d.ts +120 -0
- package/dist/tokens/index.cjs +115 -0
- package/dist/tokens/index.cjs.map +1 -0
- package/dist/tokens/index.d.cts +212 -0
- package/dist/tokens/index.d.ts +212 -0
- package/dist/tokens/index.js +115 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/{track-changes-BK5sVf4_.d.cts → track-changes-D_VAPELi.d.cts} +95 -11
- package/dist/{track-changes-CDjCrwKe.d.ts → track-changes-Db_fGEvx.d.ts} +95 -11
- package/package.json +15 -1
- package/dist/chunk-3CN2WD3X.js.map +0 -1
- package/dist/chunk-5S3Z4PPF.js.map +0 -1
- package/dist/chunk-D27YFC46.cjs.map +0 -1
- package/dist/chunk-FODNLX5N.js.map +0 -1
- package/dist/chunk-ITBSKI26.js.map +0 -1
- package/dist/chunk-MA2F5KHF.cjs +0 -3223
- package/dist/chunk-MA2F5KHF.cjs.map +0 -1
- package/dist/chunk-MCSJFWAO.cjs.map +0 -1
- package/dist/chunk-OLZJXL4R.cjs.map +0 -1
- package/dist/chunk-PGNZKFQS.js.map +0 -1
- package/dist/chunk-SBCZ6RN5.cjs.map +0 -1
- package/dist/chunk-U5KWZU4X.js +0 -3223
- package/dist/chunk-U5KWZU4X.js.map +0 -1
- package/dist/chunk-XNORY7M6.cjs.map +0 -1
- package/dist/controller-BhLLAoi8.d.cts +0 -1000
- package/dist/controller-BhLLAoi8.d.ts +0 -1000
- package/dist/types-DX4Mh4SL.d.cts +0 -217
- package/dist/types-DX4Mh4SL.d.ts +0 -217
- /package/dist/{chunk-3LV6RRPH.js.map → chunk-MHEPLBFB.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/generation/index.cjs","../../src/generation/capabilities.ts","../../src/generation/plan.ts","../../src/generation/schema.ts","../../src/generation/tools.ts","../../src/generation/provenance.ts","../../src/generation/batch.ts","../../src/generation/progress.ts","../../src/generation/controls.ts"],"names":["list"],"mappings":"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;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACF,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AAEI,EAAA;AACH,IAAA;AACA,IAAA;AACC,sBAAA;AAGD,IAAA;AACD,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACC,sBAAA;AAGG,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AAEJ,EAAA;AACC,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACX,EAAA;AACI,EAAA;AACT;AAGgB;AACT,EAAA;AACF,EAAA;AACH,IAAA;AACA,IAAA;AACI,IAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACC,EAAA;AACE,EAAA;AACR;AAGgB;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACH,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,EAAA;AACF;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AR+lCS;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/generation/index.cjs","sourcesContent":[null,"/**\n * Declarative descriptions of generative models (what a model accepts) and of\n * the requests sent to them. Everything here is plain data so capabilities can\n * come from a server, be merged with local presentation overlays, and be\n * turned into JSON Schema for agents.\n */\n\nexport type GenerationMediaKind = 'image' | 'video' | 'audio' | 'text';\n\n/** What a reference input is used for. */\nexport type ReferenceRole =\n\t| 'start_frame'\n\t| 'end_frame'\n\t| 'style_ref'\n\t| 'character_ref'\n\t| 'background_ref'\n\t| 'mask'\n\t| 'image_ref'\n\t| 'video_ref'\n\t| 'audio_ref';\n\n/** Every reference role, in a stable display order. */\nexport const REFERENCE_ROLES: readonly ReferenceRole[] = [\n\t'start_frame',\n\t'end_frame',\n\t'style_ref',\n\t'character_ref',\n\t'background_ref',\n\t'mask',\n\t'image_ref',\n\t'video_ref',\n\t'audio_ref'\n];\n\n/** Human-readable default labels for reference roles. */\nexport const REFERENCE_ROLE_LABELS: Readonly<Record<ReferenceRole, string>> = {\n\tstart_frame: 'Start frame',\n\tend_frame: 'End frame',\n\tstyle_ref: 'Style reference',\n\tcharacter_ref: 'Character reference',\n\tbackground_ref: 'Background reference',\n\tmask: 'Mask',\n\timage_ref: 'Image reference',\n\tvideo_ref: 'Video reference',\n\taudio_ref: 'Audio reference'\n};\n\n/** A choice from a fixed list. `options` order matters (low → high where it applies). */\nexport interface EnumCapability<T extends string | number = string> {\n\toptions: readonly T[];\n\tdefault?: T;\n\t/** Display labels by option value. */\n\tlabels?: Readonly<Record<string, string>>;\n}\n\nexport interface TextFieldCapability {\n\tmaxLength?: number;\n}\n\nexport interface PromptCapability extends TextFieldCapability {\n\t/**\n\t * Count the resolved prompt (templates expanded, variables substituted)\n\t * against `maxLength` rather than the raw template text. Default `true`.\n\t * Pass the resolved text to the planner as `resolvedPrompt`.\n\t */\n\tcountResolved?: boolean;\n\t/** The prompt must be non-empty. */\n\trequired?: boolean;\n}\n\nexport interface CountCapability {\n\t/** Largest number of outputs per request. */\n\tmax: number;\n\t/** Predefined choices; when given, requests snap to one of them. */\n\toptions?: readonly number[];\n\tdefault?: number;\n}\n\nexport interface DurationCapability {\n\t/** Seconds. */\n\tmin?: number;\n\tmax?: number;\n\t/** Predefined choices; when given, requests snap to one of them. */\n\toptions?: readonly number[];\n\tdefault?: number;\n\tstep?: number;\n}\n\nexport interface SeedCapability {\n\tmin?: number;\n\tmax?: number;\n}\n\nexport interface ReferenceRoleCapability {\n\t/** Most references of this role. */\n\tmax: number;\n\t/** Fewest references of this role (e.g. an image-to-video model needs a `start_frame`). */\n\tmin?: number;\n\tlabel?: string;\n\tdescription?: string;\n}\n\nexport interface ReferenceCapability {\n\t/** Supported roles and their per-role limits. Roles not listed are unsupported. */\n\troles: Partial<Readonly<Record<ReferenceRole, ReferenceRoleCapability>>>;\n\t/** Most references across all roles. */\n\tmaxTotal?: number;\n}\n\nexport interface NumberParamSpec {\n\ttype: 'number' | 'integer';\n\tmin?: number;\n\tmax?: number;\n\tstep?: number;\n\tdefault?: number;\n\tlabel?: string;\n\tdescription?: string;\n}\n\nexport interface EnumParamSpec {\n\ttype: 'enum';\n\toptions: readonly (string | number)[];\n\tdefault?: string | number;\n\tlabel?: string;\n\tdescription?: string;\n}\n\nexport interface BooleanParamSpec {\n\ttype: 'boolean';\n\tdefault?: boolean;\n\tlabel?: string;\n\tdescription?: string;\n}\n\n/** A model-specific parameter (`guidance`, `motion_strength`, `style`, …). */\nexport type ExtraParamSpec = NumberParamSpec | EnumParamSpec | BooleanParamSpec;\n\n/** Rough timing used for progress estimates (`expectedDurationFor`). */\nexport interface TimingCapability {\n\t/** Fixed cost of one request in ms. */\n\tbaseMs: number;\n\t/** Extra ms per output beyond the first. */\n\tperItemMs?: number;\n\t/** Extra ms per second of requested duration (video/audio). */\n\tperSecondMs?: number;\n\t/** Extra ms per megapixel of the requested resolution. */\n\tperMegapixelMs?: number;\n}\n\n/** Presentation-only fields; overlays may set these (see `mergeCapabilities`). */\nexport interface CapabilityPresentation {\n\tname?: string;\n\tdescription?: string;\n\tbadges?: readonly string[];\n\ticon?: string;\n\tgroup?: string;\n}\n\n/** Everything a generative model accepts. Absent sections mean \"unsupported\". */\nexport interface ModelCapabilities extends CapabilityPresentation {\n\tid: string;\n\tmedia: GenerationMediaKind;\n\tprompt?: PromptCapability;\n\t/** Negative prompt support (`true` or limits). */\n\tnegativePrompt?: boolean | TextFieldCapability;\n\t/** System prompt support (`true` or limits). */\n\tsystemPrompt?: boolean | TextFieldCapability;\n\t/** E.g. `['1:1', '16:9', '9:16']`. */\n\taspectRatios?: EnumCapability<string>;\n\t/** E.g. `['512x512', '1024x1024']`, `['720p', '1080p']` or `['1k', '2k', '4k']`, low → high. */\n\tresolutions?: EnumCapability<string>;\n\t/** Ordered low → high, e.g. `['draft', 'standard', 'high']`. */\n\tquality?: EnumCapability<string>;\n\tcount?: CountCapability;\n\tduration?: DurationCapability;\n\tseed?: boolean | SeedCapability;\n\treferences?: ReferenceCapability;\n\t/** Accepts a source to edit (`mode: 'edit'`). */\n\tediting?: boolean;\n\t/** Accepts a source plus a `mask` reference (`mode: 'inpaint'`). */\n\tinpainting?: boolean;\n\tparams?: Readonly<Record<string, ExtraParamSpec>>;\n\ttiming?: TimingCapability;\n}\n\nexport interface GenerationReference {\n\t/** Asset id (opaque to this library). */\n\tid: string;\n\trole: ReferenceRole;\n\turl?: string;\n}\n\nexport type GenerationMode = 'generate' | 'edit' | 'inpaint';\n\nexport type GenerationParamValue = string | number | boolean;\n\n/** A request for one model. Every field is optional; the planner fills and fixes. */\nexport interface GenerationRequest {\n\tmodel?: string;\n\tprompt?: string;\n\tnegativePrompt?: string;\n\tsystemPrompt?: string;\n\taspectRatio?: string;\n\tresolution?: string;\n\tquality?: string;\n\tcount?: number;\n\t/** Seconds. */\n\tduration?: number;\n\tseed?: number;\n\tmode?: GenerationMode;\n\treferences?: GenerationReference[];\n\tparams?: Record<string, GenerationParamValue>;\n}\n\n/** Fields of {@link GenerationRequest} the planner knows about, in planning order. */\nexport const GENERATION_REQUEST_FIELDS = [\n\t'model',\n\t'prompt',\n\t'negativePrompt',\n\t'systemPrompt',\n\t'aspectRatio',\n\t'resolution',\n\t'quality',\n\t'count',\n\t'duration',\n\t'seed',\n\t'mode',\n\t'references',\n\t'params'\n] as const;\n\n/** Text field limits normalized from `boolean | TextFieldCapability`. */\nexport function textFieldCapability(value: boolean | TextFieldCapability | undefined): TextFieldCapability | null {\n\tif (!value) return null;\n\treturn value === true ? {} : value;\n}\n\n/** Seed limits normalized from `boolean | SeedCapability`. */\nexport function seedCapability(value: boolean | SeedCapability | undefined): SeedCapability | null {\n\tif (!value) return null;\n\treturn value === true ? {} : value;\n}\n\n/** Supported reference roles of a model in {@link REFERENCE_ROLES} order. */\nexport function supportedReferenceRoles(caps: ModelCapabilities): ReferenceRole[] {\n\tconst roles = caps.references?.roles ?? {};\n\treturn REFERENCE_ROLES.filter((role) => (roles[role]?.max ?? 0) > 0);\n}\n\n/** Supported generation modes of a model. */\nexport function supportedModes(caps: ModelCapabilities): GenerationMode[] {\n\tconst modes: GenerationMode[] = ['generate'];\n\tif (caps.editing) modes.push('edit');\n\tif (caps.inpainting) modes.push('inpaint');\n\treturn modes;\n}\n\nconst PRESENTATION_KEYS = ['name', 'description', 'badges', 'icon', 'group'] as const;\n\n/** A partial model entry used to decorate (or add) models locally. */\nexport type CapabilityOverlay = Partial<ModelCapabilities> & { id: string };\n\n/**\n * Merges server-reported capabilities with a local overlay. For models the\n * server knows, every capability value comes from `remote`; the overlay only\n * contributes presentation fields (`name`, `description`, `badges`, `icon`,\n * `group`), filling in or replacing the remote ones. Overlay entries for\n * models the server does not report are appended when they are complete\n * (have a `media` kind). Remote order is kept; overlay-only models follow in\n * overlay order.\n */\nexport function mergeCapabilities(\n\tremote: readonly ModelCapabilities[],\n\toverlay: readonly CapabilityOverlay[] = []\n): ModelCapabilities[] {\n\tconst overlayById = new Map(overlay.map((entry) => [entry.id, entry]));\n\tconst remoteIds = new Set(remote.map((model) => model.id));\n\tconst merged = remote.map((model) => {\n\t\tconst extra = overlayById.get(model.id);\n\t\tif (!extra) return { ...model };\n\t\tconst next: ModelCapabilities = { ...model };\n\t\tfor (const key of PRESENTATION_KEYS) {\n\t\t\tif (extra[key] !== undefined) (next as any)[key] = extra[key];\n\t\t}\n\t\treturn next;\n\t});\n\tfor (const entry of overlay) {\n\t\tif (remoteIds.has(entry.id) || !entry.media) continue;\n\t\tremoteIds.add(entry.id);\n\t\tmerged.push({ ...(entry as ModelCapabilities) });\n\t}\n\treturn merged;\n}\n","import {\n\tGENERATION_REQUEST_FIELDS,\n\tREFERENCE_ROLES,\n\tseedCapability,\n\tsupportedModes,\n\ttextFieldCapability,\n\ttype EnumCapability,\n\ttype ExtraParamSpec,\n\ttype GenerationParamValue,\n\ttype GenerationReference,\n\ttype GenerationRequest,\n\ttype ModelCapabilities,\n\ttype ReferenceRole,\n\ttype ReferenceRoleCapability\n} from './capabilities.js';\n\n/**\n * How the planner treats values the model does not support:\n *\n * - `error`: nothing is changed; every mismatch is an error.\n * - `nearest`: snap to the closest supported value (ties go to the higher\n * value), clamp ranges, truncate lists, drop unsupported fields and\n * unsupported references.\n * - `downgrade`: like `nearest`, but ties go to the lower value.\n * - `upgrade`: like `nearest`, but never drops references the model cannot\n * use (that would lose intent), so those are errors.\n *\n * \"Lower\" means a narrower aspect ratio, fewer pixels, an earlier quality\n * option, a smaller number.\n */\nexport type PlanStrategy = 'error' | 'upgrade' | 'downgrade' | 'nearest';\n\nexport type PlanAdjustmentKind = 'snapped' | 'clamped' | 'truncated' | 'dropped' | 'defaulted';\n\nexport interface PlanAdjustment {\n\t/** Request path, e.g. `aspectRatio`, `params.guidance`, `references.style_ref`. */\n\tfield: string;\n\tfrom: unknown;\n\tto: unknown;\n\treason: string;\n\tkind: PlanAdjustmentKind;\n}\n\nexport interface PlanError {\n\tfield: string;\n\tvalue: unknown;\n\tmessage: string;\n}\n\nexport interface GenerationPlan {\n\t/** `true` when there are no errors and `request` can be sent. */\n\tok: boolean;\n\t/** The adjusted request (always a copy; the input is never mutated). */\n\trequest: GenerationRequest;\n\tadjustments: PlanAdjustment[];\n\terrors: PlanError[];\n\t/** Model the plan was made for. */\n\tmodel: string;\n\tstrategy: PlanStrategy;\n}\n\nexport interface PlanOptions {\n\t/** Default `nearest`. */\n\tstrategy?: PlanStrategy;\n\t/**\n\t * The prompt with templates and variables resolved. Measured against\n\t * `prompt.maxLength` when the model counts resolved text (the default).\n\t */\n\tresolvedPrompt?: string;\n\t/** Fill missing fields that have a capability `default` (reported as `defaulted`). */\n\tfillDefaults?: boolean;\n\t/** Override whether references with unsupported roles may be dropped (default: `nearest` and `downgrade` only). */\n\tdropUnsupportedReferences?: boolean;\n}\n\nconst EPSILON = 1e-9;\n\n/** Numeric rank of an aspect ratio such as `16:9` (log of width / height). */\nexport function aspectRatioRank(value: string): number | undefined {\n\tconst match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*[:x/×]\\s*(\\d+(?:\\.\\d+)?)\\s*$/i.exec(String(value));\n\tif (!match) return undefined;\n\tconst w = Number(match[1]);\n\tconst h = Number(match[2]);\n\tif (!(w > 0) || !(h > 0)) return undefined;\n\treturn Math.log(w / h);\n}\n\n/**\n * Numeric rank of a resolution: `1024x768` (pixels), `720p` (16:9 pixels at\n * that height) or `2k` (square of that many kibi-pixels). Log scale.\n */\nexport function resolutionRank(value: string): number | undefined {\n\tconst text = String(value).trim().toLowerCase();\n\tlet pixels: number | undefined;\n\tlet match = /^(\\d+(?:\\.\\d+)?)\\s*[x×*]\\s*(\\d+(?:\\.\\d+)?)$/.exec(text);\n\tif (match) pixels = Number(match[1]) * Number(match[2]);\n\telse if ((match = /^(\\d+(?:\\.\\d+)?)p$/.exec(text))) pixels = (Number(match[1]) ** 2 * 16) / 9;\n\telse if ((match = /^(\\d+(?:\\.\\d+)?)k$/.exec(text))) pixels = (Number(match[1]) * 1024) ** 2;\n\treturn pixels && pixels > 0 ? Math.log(pixels) : undefined;\n}\n\n/** Megapixels of a resolution string, when it can be parsed. */\nexport function resolutionMegapixels(value: string): number | undefined {\n\tconst rank = resolutionRank(value);\n\treturn rank === undefined ? undefined : Math.exp(rank) / 1_000_000;\n}\n\n/** Common quality names, low → high. Used to compare values missing from a model's options. */\nexport const QUALITY_LADDER: Readonly<Record<string, number>> = {\n\tdraft: 0,\n\tpreview: 0,\n\tlow: 1,\n\tfast: 1,\n\tturbo: 1,\n\tstandard: 2,\n\tnormal: 2,\n\tmedium: 2,\n\tbalanced: 2,\n\thigh: 3,\n\thd: 3,\n\tquality: 3,\n\tultra: 4,\n\tmax: 5,\n\tbest: 5\n};\n\nexport function qualityRank(value: string): number | undefined {\n\treturn QUALITY_LADDER[String(value).trim().toLowerCase()];\n}\n\nconst numberRank = (value: unknown) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);\n\n/**\n * Picks the option closest to `value` by `rank`. Ties go to the lower or\n * higher rank. Returns `undefined` when `value` or no option can be ranked.\n */\nexport function snapToOption<T>(\n\tvalue: unknown,\n\toptions: readonly T[],\n\trank: (value: any) => number | undefined,\n\ttie: 'lower' | 'higher'\n): T | undefined {\n\tconst target = rank(value);\n\tif (target === undefined) return undefined;\n\tlet best: { option: T; distance: number; rank: number } | undefined;\n\tfor (const option of options) {\n\t\tconst r = rank(option);\n\t\tif (r === undefined) continue;\n\t\tconst distance = Math.abs(r - target);\n\t\tif (\n\t\t\t!best ||\n\t\t\tdistance < best.distance - EPSILON ||\n\t\t\t(Math.abs(distance - best.distance) <= EPSILON && (tie === 'lower' ? r < best.rank : r > best.rank))\n\t\t) {\n\t\t\tbest = { option, distance, rank: r };\n\t\t}\n\t}\n\treturn best?.option;\n}\n\n/** Truncates to `max` UTF-16 units without splitting a surrogate pair. */\nfunction truncateText(text: string, max: number): string {\n\tif (text.length <= max) return text;\n\tlet end = max;\n\tconst code = text.charCodeAt(end - 1);\n\tif (end > 0 && code >= 0xd800 && code <= 0xdbff) end -= 1;\n\treturn text.slice(0, end);\n}\n\nfunction cloneRequest(request: GenerationRequest): GenerationRequest {\n\tconst copy: GenerationRequest = { ...request };\n\tif (request.references) copy.references = request.references.map((ref) => ({ ...ref }));\n\tif (request.params) copy.params = { ...request.params };\n\treturn copy;\n}\n\nconst describe = (value: unknown) => (typeof value === 'string' ? `\"${value}\"` : JSON.stringify(value));\nconst list = (options: readonly unknown[]) => options.map(describe).join(', ');\n\nclass Planner {\n\treadonly adjustments: PlanAdjustment[] = [];\n\treadonly errors: PlanError[] = [];\n\treadonly tie: 'lower' | 'higher';\n\treadonly strict: boolean;\n\n\tconstructor(\n\t\treadonly caps: ModelCapabilities,\n\t\treadonly request: GenerationRequest,\n\t\treadonly options: PlanOptions,\n\t\treadonly strategy: PlanStrategy\n\t) {\n\t\tthis.tie = strategy === 'downgrade' ? 'lower' : 'higher';\n\t\tthis.strict = strategy === 'error';\n\t}\n\n\tadjust(field: string, from: unknown, to: unknown, kind: PlanAdjustmentKind, reason: string) {\n\t\tthis.adjustments.push({ field, from, to, kind, reason });\n\t}\n\n\terror(field: string, value: unknown, message: string) {\n\t\tthis.errors.push({ field, value, message });\n\t}\n\n\t/** Removes an unsupported field (or reports it under `error`). */\n\tunsupported(field: keyof GenerationRequest, message: string) {\n\t\tconst value = this.request[field];\n\t\tif (this.strict) {\n\t\t\tthis.error(field, value, message);\n\t\t\treturn;\n\t\t}\n\t\tdelete this.request[field];\n\t\tthis.adjust(field, value, undefined, 'dropped', message);\n\t}\n\n\tdefaulted(field: string, value: unknown, set: () => void) {\n\t\tif (!this.options.fillDefaults || value === undefined) return;\n\t\tset();\n\t\tthis.adjust(field, undefined, value, 'defaulted', 'Filled with the model default.');\n\t}\n\n\tplanText(field: 'prompt' | 'negativePrompt' | 'systemPrompt', maxLength: number | undefined, measured?: string) {\n\t\tconst value = this.request[field];\n\t\tif (value === undefined || maxLength === undefined) return;\n\t\tif (typeof value !== 'string') {\n\t\t\tthis.error(field, value, `Expected text.`);\n\t\t\treturn;\n\t\t}\n\t\tconst text = measured ?? value;\n\t\tif (text.length <= maxLength) return;\n\t\tif (this.strict || measured !== undefined) {\n\t\t\tthis.error(\n\t\t\t\tfield,\n\t\t\t\ttext.length,\n\t\t\t\t`${measured !== undefined ? 'Resolved text' : 'Text'} is ${text.length} characters; the model accepts at most ${maxLength}.`\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst next = truncateText(value, maxLength);\n\t\tthis.request[field] = next;\n\t\tthis.adjust(field, value.length, next.length, 'truncated', `Truncated to the model's ${maxLength}-character limit.`);\n\t}\n\n\tplanEnum(\n\t\tfield: 'aspectRatio' | 'resolution' | 'quality',\n\t\tcap: EnumCapability<string> | undefined,\n\t\trank: (value: string) => number | undefined,\n\t\tlabel: string\n\t) {\n\t\tconst value = this.request[field];\n\t\tif (value === undefined) {\n\t\t\tif (cap) this.defaulted(field, cap.default, () => (this.request[field] = cap.default));\n\t\t\treturn;\n\t\t}\n\t\tif (!cap || cap.options.length === 0) {\n\t\t\tthis.unsupported(field, `The model does not take a ${label}.`);\n\t\t\treturn;\n\t\t}\n\t\tif (cap.options.includes(value)) return;\n\t\tif (this.strict) {\n\t\t\tthis.error(field, value, `${describe(value)} is not a supported ${label}; supported: ${list(cap.options)}.`);\n\t\t\treturn;\n\t\t}\n\t\tconst snapped = snapToOption(value, cap.options, rank, this.tie);\n\t\tconst next = snapped ?? cap.default ?? cap.options[0];\n\t\tthis.request[field] = next;\n\t\tthis.adjust(\n\t\t\tfield,\n\t\t\tvalue,\n\t\t\tnext,\n\t\t\t'snapped',\n\t\t\tsnapped !== undefined\n\t\t\t\t? `${describe(value)} is not supported; using the nearest ${label}.`\n\t\t\t\t: `${describe(value)} is not supported; using ${cap.default !== undefined ? 'the default' : 'the first option'}.`\n\t\t);\n\t}\n\n\t/** Clamps/rounds/snaps a number. Returns the next value or `undefined` after an error. */\n\tplanNumber(\n\t\tfield: string,\n\t\tvalue: unknown,\n\t\tspec: { min?: number; max?: number; step?: number; integer?: boolean; options?: readonly number[] },\n\t\tlabel: string\n\t): { value: number; changed: boolean } | undefined {\n\t\tif (typeof value !== 'number' || !Number.isFinite(value)) {\n\t\t\tthis.error(field, value, `Expected a number for ${label}.`);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst options = spec.options?.filter(\n\t\t\t(o) => (spec.min === undefined || o >= spec.min) && (spec.max === undefined || o <= spec.max)\n\t\t);\n\t\tif (options && options.length > 0) {\n\t\t\tif (options.includes(value)) return { value, changed: false };\n\t\t\tif (this.strict) {\n\t\t\t\tthis.error(field, value, `${value} is not a supported ${label}; supported: ${list(options)}.`);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst next = snapToOption(value, options, numberRank, this.tie)!;\n\t\t\tthis.adjust(field, value, next, 'snapped', `${value} is not supported; using the nearest ${label}.`);\n\t\t\treturn { value: next, changed: true };\n\t\t}\n\t\tlet next = value;\n\t\tif (spec.step !== undefined && spec.step > 0) {\n\t\t\tconst base = spec.min ?? 0;\n\t\t\tconst steps = (next - base) / spec.step;\n\t\t\tconst rounded = Math.abs(steps - Math.trunc(steps) - 0.5) < EPSILON\n\t\t\t\t? this.tie === 'lower' ? Math.floor(steps) : Math.ceil(steps)\n\t\t\t\t: Math.round(steps);\n\t\t\tnext = Number((base + rounded * spec.step).toPrecision(12));\n\t\t}\n\t\tif (spec.integer && !Number.isInteger(next)) {\n\t\t\tconst frac = next - Math.floor(next);\n\t\t\tnext = Math.abs(frac - 0.5) < EPSILON ? (this.tie === 'lower' ? Math.floor(next) : Math.ceil(next)) : Math.round(next);\n\t\t}\n\t\tlet kind: PlanAdjustmentKind = 'snapped';\n\t\tif (spec.min !== undefined && next < spec.min) {\n\t\t\tnext = spec.min;\n\t\t\tkind = 'clamped';\n\t\t}\n\t\tif (spec.max !== undefined && next > spec.max) {\n\t\t\tnext = spec.max;\n\t\t\tkind = 'clamped';\n\t\t}\n\t\tif (next === value) return { value, changed: false };\n\t\tconst range = `${spec.min ?? '-∞'}–${spec.max ?? '∞'}`;\n\t\tif (this.strict) {\n\t\t\tthis.error(\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tkind === 'clamped' ? `${label} must be within ${range}.` : `${value} is not a valid ${label} (try ${next}).`\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.adjust(\n\t\t\tfield,\n\t\t\tvalue,\n\t\t\tnext,\n\t\t\tkind,\n\t\t\tkind === 'clamped' ? `Clamped to the supported range ${range}.` : `Rounded to a supported ${label}.`\n\t\t);\n\t\treturn { value: next, changed: true };\n\t}\n\n\tplanCount() {\n\t\tconst value = this.request.count;\n\t\tconst cap = this.caps.count;\n\t\tif (value === undefined) {\n\t\t\tif (cap) this.defaulted('count', cap.default, () => (this.request.count = cap.default));\n\t\t\treturn;\n\t\t}\n\t\tif (!cap) {\n\t\t\tif (value === 1) return;\n\t\t\tthis.unsupported('count', 'The model produces one output per request.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = this.planNumber('count', value, { min: 1, max: cap.max, integer: true, options: cap.options }, 'output count');\n\t\tif (result) this.request.count = result.value;\n\t}\n\n\tplanDuration() {\n\t\tconst value = this.request.duration;\n\t\tconst cap = this.caps.duration;\n\t\tif (value === undefined) {\n\t\t\tif (cap) this.defaulted('duration', cap.default, () => (this.request.duration = cap.default));\n\t\t\treturn;\n\t\t}\n\t\tif (!cap) {\n\t\t\tthis.unsupported('duration', 'The model does not take a duration.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = this.planNumber('duration', value, cap, 'duration');\n\t\tif (result) this.request.duration = result.value;\n\t}\n\n\tplanSeed() {\n\t\tconst value = this.request.seed;\n\t\tif (value === undefined) return;\n\t\tconst cap = seedCapability(this.caps.seed);\n\t\tif (!cap) {\n\t\t\tthis.unsupported('seed', 'The model does not take a seed.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = this.planNumber('seed', value, { ...cap, integer: true }, 'seed');\n\t\tif (result) this.request.seed = result.value;\n\t}\n\n\tplanMode() {\n\t\tconst mode = this.request.mode;\n\t\tif (mode === undefined) return;\n\t\tconst modes = supportedModes(this.caps);\n\t\tif (!modes.includes(mode)) {\n\t\t\tthis.error('mode', mode, `The model does not support ${describe(mode)}; supported: ${list(modes)}.`);\n\t\t}\n\t}\n\n\troleCapability(role: ReferenceRole): ReferenceRoleCapability | undefined {\n\t\tconst cap = this.caps.references?.roles[role];\n\t\tif (cap && cap.max > 0) return cap;\n\t\tif (role === 'mask' && this.caps.inpainting) return { max: 1 };\n\t\treturn undefined;\n\t}\n\n\tplanReferences() {\n\t\tconst refs = this.request.references;\n\t\tconst allowDrop =\n\t\t\t!this.strict &&\n\t\t\t(this.options.dropUnsupportedReferences ?? (this.strategy === 'nearest' || this.strategy === 'downgrade'));\n\t\tconst byRole = new Map<ReferenceRole, GenerationReference[]>();\n\t\tconst kept: GenerationReference[] = [];\n\t\tconst unsupported = new Map<string, number>();\n\t\tfor (const [index, ref] of (refs ?? []).entries()) {\n\t\t\tif (!ref || typeof ref.id !== 'string' || ref.id === '' || !REFERENCE_ROLES.includes(ref.role)) {\n\t\t\t\tthis.error(`references.${index}`, ref, 'Each reference needs an `id` and a known `role`.');\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!this.roleCapability(ref.role)) {\n\t\t\t\tunsupported.set(ref.role, (unsupported.get(ref.role) ?? 0) + 1);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst group = byRole.get(ref.role) ?? [];\n\t\t\tif (group.some((other) => other.id === ref.id)) {\n\t\t\t\tthis.adjust(`references.${ref.role}`, ref.id, undefined, 'dropped', `Duplicate reference \"${ref.id}\" removed.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tgroup.push(ref);\n\t\t\tbyRole.set(ref.role, group);\n\t\t\tkept.push(ref);\n\t\t}\n\n\t\tfor (const [role, count] of unsupported) {\n\t\t\tconst message =\n\t\t\t\tthis.caps.media === 'text' || !this.caps.references\n\t\t\t\t\t? `The ${this.caps.media} model \"${this.caps.id}\" does not accept reference inputs (${role}).`\n\t\t\t\t\t: `The model does not accept ${role} references.`;\n\t\t\tif (allowDrop) this.adjust(`references.${role}`, count, 0, 'dropped', message);\n\t\t\telse this.error(`references.${role}`, count, message);\n\t\t}\n\n\t\t// Per-role limits.\n\t\tconst removed = new Set<GenerationReference>();\n\t\tfor (const role of REFERENCE_ROLES) {\n\t\t\tconst cap = this.roleCapability(role);\n\t\t\tconst group = byRole.get(role) ?? [];\n\t\t\tif (!cap) continue;\n\t\t\tif (group.length > cap.max) {\n\t\t\t\tif (this.strict) {\n\t\t\t\t\tthis.error(`references.${role}`, group.length, `At most ${cap.max} ${role} reference(s) are supported.`);\n\t\t\t\t} else {\n\t\t\t\t\tgroup.slice(cap.max).forEach((ref) => removed.add(ref));\n\t\t\t\t\tthis.adjust(\n\t\t\t\t\t\t`references.${role}`,\n\t\t\t\t\t\tgroup.length,\n\t\t\t\t\t\tcap.max,\n\t\t\t\t\t\t'truncated',\n\t\t\t\t\t\t`Kept the first ${cap.max} ${role} reference(s); the model accepts no more.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cap.min !== undefined && group.length < cap.min) {\n\t\t\t\tthis.error(`references.${role}`, group.length, `At least ${cap.min} ${role} reference(s) are required.`);\n\t\t\t}\n\t\t}\n\t\tlet next = kept.filter((ref) => !removed.has(ref));\n\n\t\t// Total limit.\n\t\tconst maxTotal = this.caps.references?.maxTotal;\n\t\tif (maxTotal !== undefined && next.length > maxTotal) {\n\t\t\tif (this.strict) {\n\t\t\t\tthis.error('references', next.length, `At most ${maxTotal} references are supported in total.`);\n\t\t\t} else {\n\t\t\t\tthis.adjust(\n\t\t\t\t\t'references',\n\t\t\t\t\tnext.length,\n\t\t\t\t\tmaxTotal,\n\t\t\t\t\t'truncated',\n\t\t\t\t\t`Kept the first ${maxTotal} references; the model accepts no more in total.`\n\t\t\t\t);\n\t\t\t\tnext = next.slice(0, maxTotal);\n\t\t\t}\n\t\t}\n\n\t\tif (this.request.mode === 'inpaint' && !next.some((ref) => ref.role === 'mask')) {\n\t\t\tthis.error('references.mask', 0, 'Inpainting needs a `mask` reference.');\n\t\t}\n\n\t\tif (refs === undefined) return;\n\t\tif (!this.strict) {\n\t\t\tif (next.length > 0) this.request.references = next;\n\t\t\telse delete this.request.references;\n\t\t}\n\t}\n\n\tplanParam(name: string, spec: ExtraParamSpec | undefined, value: GenerationParamValue): GenerationParamValue | undefined {\n\t\tconst field = `params.${name}`;\n\t\tif (!spec) {\n\t\t\tif (this.strict) this.error(field, value, `Unknown parameter \"${name}\".`);\n\t\t\telse this.adjust(field, value, undefined, 'dropped', `The model has no \"${name}\" parameter.`);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst label = spec.label ?? name;\n\t\tif (spec.type === 'boolean') {\n\t\t\tif (typeof value !== 'boolean') this.error(field, value, `Expected true or false for ${label}.`);\n\t\t\treturn value;\n\t\t}\n\t\tif (spec.type === 'enum') {\n\t\t\tif (spec.options.includes(value as string | number)) return value;\n\t\t\tif (this.strict) {\n\t\t\t\tthis.error(field, value, `${describe(value)} is not a supported ${label}; supported: ${list(spec.options)}.`);\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\tconst numeric = spec.options.every((o) => typeof o === 'number');\n\t\t\tconst snapped = numeric ? snapToOption(value, spec.options, numberRank, this.tie) : undefined;\n\t\t\tconst next = snapped ?? spec.default ?? spec.options[0];\n\t\t\tthis.adjust(\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tnext,\n\t\t\t\t'snapped',\n\t\t\t\tsnapped !== undefined ? `Using the nearest supported ${label}.` : `${describe(value)} is not supported; using ${spec.default !== undefined ? 'the default' : 'the first option'}.`\n\t\t\t);\n\t\t\treturn next;\n\t\t}\n\t\tconst result = this.planNumber(field, value, { ...spec, integer: spec.type === 'integer' }, label);\n\t\treturn result ? result.value : value;\n\t}\n\n\tplanParams() {\n\t\tconst params = this.request.params;\n\t\tconst specs = this.caps.params ?? {};\n\t\tconst next: Record<string, GenerationParamValue> = {};\n\t\tfor (const [name, value] of Object.entries(params ?? {})) {\n\t\t\tif (value === undefined) continue;\n\t\t\tconst planned = this.planParam(name, specs[name], value);\n\t\t\tif (planned !== undefined) next[name] = planned;\n\t\t\telse if (this.strict) next[name] = value;\n\t\t}\n\t\tif (this.options.fillDefaults) {\n\t\t\tfor (const [name, spec] of Object.entries(specs)) {\n\t\t\t\tif (next[name] === undefined && spec.default !== undefined) {\n\t\t\t\t\tnext[name] = spec.default;\n\t\t\t\t\tthis.adjust(`params.${name}`, undefined, spec.default, 'defaulted', 'Filled with the model default.');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (Object.keys(next).length > 0) this.request.params = next;\n\t\telse delete this.request.params;\n\t}\n\n\trun(): void {\n\t\tconst { caps, request } = this;\n\t\tfor (const key of Object.keys(request)) {\n\t\t\tif ((GENERATION_REQUEST_FIELDS as readonly string[]).includes(key)) continue;\n\t\t\tconst value = (request as any)[key];\n\t\t\tif (this.strict) this.error(key, value, `Unknown request field \"${key}\".`);\n\t\t\telse {\n\t\t\t\tdelete (request as any)[key];\n\t\t\t\tthis.adjust(key, value, undefined, 'dropped', `Unknown request field \"${key}\".`);\n\t\t\t}\n\t\t}\n\n\t\tif (request.model !== undefined && request.model !== caps.id) {\n\t\t\tthis.error('model', request.model, `The request targets \"${request.model}\" but the plan is for \"${caps.id}\".`);\n\t\t}\n\t\trequest.model = caps.id;\n\n\t\t// Text fields.\n\t\tconst promptCap = caps.prompt ?? {};\n\t\tif (promptCap.required && !(request.prompt ?? '').trim()) {\n\t\t\tthis.error('prompt', request.prompt, 'A prompt is required.');\n\t\t}\n\t\tconst countResolved = promptCap.countResolved ?? true;\n\t\tconst measured =\n\t\t\tcountResolved && this.options.resolvedPrompt !== undefined && this.options.resolvedPrompt !== request.prompt\n\t\t\t\t? this.options.resolvedPrompt\n\t\t\t\t: undefined;\n\t\tthis.planText('prompt', promptCap.maxLength, measured);\n\t\tfor (const field of ['negativePrompt', 'systemPrompt'] as const) {\n\t\t\tif (request[field] === undefined) continue;\n\t\t\tconst cap = textFieldCapability(caps[field]);\n\t\t\tif (!cap) {\n\t\t\t\tthis.unsupported(field, `The model does not take a ${field === 'negativePrompt' ? 'negative' : 'system'} prompt.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.planText(field, cap.maxLength);\n\t\t}\n\n\t\tthis.planEnum('aspectRatio', caps.aspectRatios, aspectRatioRank, 'aspect ratio');\n\t\tthis.planEnum('resolution', caps.resolutions, resolutionRank, 'resolution');\n\t\tthis.planEnum('quality', caps.quality, qualityRank, 'quality');\n\t\tthis.planCount();\n\t\tthis.planDuration();\n\t\tthis.planSeed();\n\t\tthis.planMode();\n\t\tthis.planReferences();\n\t\tthis.planParams();\n\t}\n}\n\n/**\n * Plans `request` against a model's capabilities before sending it: snaps\n * enum values (aspect ratio, resolution, quality, count/duration choices) to\n * the nearest supported option, clamps ranges, truncates text and reference\n * lists over their limits, drops unsupported fields, and reports every change\n * in `adjustments` and every unfixable problem in `errors`.\n *\n * Pure and deterministic: the input is not mutated and the same input always\n * gives the same plan.\n */\nexport function planGenerationRequest(\n\trequest: GenerationRequest,\n\tcaps: ModelCapabilities,\n\toptions: PlanOptions = {}\n): GenerationPlan {\n\tconst strategy = options.strategy ?? 'nearest';\n\tconst planner = new Planner(caps, cloneRequest(request ?? {}), options, strategy);\n\tplanner.run();\n\treturn {\n\t\tok: planner.errors.length === 0,\n\t\trequest: planner.request,\n\t\tadjustments: planner.adjustments,\n\t\terrors: planner.errors,\n\t\tmodel: caps.id,\n\t\tstrategy\n\t};\n}\n","import type { JsonSchema } from '../agent/schema.js';\nimport {\n\tREFERENCE_ROLE_LABELS,\n\tseedCapability,\n\tsupportedModes,\n\tsupportedReferenceRoles,\n\ttextFieldCapability,\n\ttype EnumCapability,\n\ttype ExtraParamSpec,\n\ttype ModelCapabilities\n} from './capabilities.js';\n\nfunction enumSchema(cap: EnumCapability<string>, description: string): JsonSchema {\n\treturn {\n\t\ttype: 'string',\n\t\tenum: [...cap.options],\n\t\t...(cap.default !== undefined ? { default: cap.default } : {}),\n\t\tdescription\n\t};\n}\n\nfunction paramSchema(name: string, spec: ExtraParamSpec): JsonSchema {\n\tconst description = spec.description ?? spec.label ?? name;\n\tif (spec.type === 'boolean') {\n\t\treturn { type: 'boolean', description, ...(spec.default !== undefined ? { default: spec.default } : {}) };\n\t}\n\tif (spec.type === 'enum') {\n\t\tconst numeric = spec.options.every((o) => typeof o === 'number');\n\t\treturn {\n\t\t\ttype: numeric ? 'number' : 'string',\n\t\t\tenum: [...spec.options],\n\t\t\tdescription,\n\t\t\t...(spec.default !== undefined ? { default: spec.default } : {})\n\t\t};\n\t}\n\treturn {\n\t\ttype: spec.type,\n\t\tdescription: spec.step !== undefined ? `${description} (step ${spec.step})` : description,\n\t\t...(spec.min !== undefined ? { minimum: spec.min } : {}),\n\t\t...(spec.max !== undefined ? { maximum: spec.max } : {}),\n\t\t...(spec.default !== undefined ? { default: spec.default } : {})\n\t};\n}\n\n/**\n * JSON Schema for a request to one model: enums for choices, `minimum` /\n * `maximum` for ranges, `maxItems` for reference lists and `description`s that\n * spell out limits the schema cannot express (per-role reference counts,\n * resolved-prompt length). Unsupported fields are absent and\n * `additionalProperties` is `false`, so `validateJsonSchema` rejects them.\n */\nexport function capabilitiesToJsonSchema(caps: ModelCapabilities): JsonSchema & { type: 'object' } {\n\tconst properties: Record<string, JsonSchema> = {\n\t\tmodel: { type: 'string', const: caps.id, description: 'Model id.' }\n\t};\n\tconst required: string[] = [];\n\n\tconst prompt = caps.prompt ?? {};\n\tconst countResolved = prompt.countResolved ?? true;\n\tproperties.prompt = {\n\t\ttype: 'string',\n\t\tdescription:\n\t\t\tprompt.maxLength !== undefined\n\t\t\t\t? `Prompt text. At most ${prompt.maxLength} characters${countResolved ? ' after templates and variables are resolved' : ''}.`\n\t\t\t\t: 'Prompt text.',\n\t\t...(prompt.maxLength !== undefined && !countResolved ? { maxLength: prompt.maxLength } : {}),\n\t\t...(prompt.required ? { minLength: 1 } : {})\n\t};\n\tif (prompt.required) required.push('prompt');\n\n\tconst negative = textFieldCapability(caps.negativePrompt);\n\tif (negative) {\n\t\tproperties.negativePrompt = {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'What to avoid in the output.',\n\t\t\t...(negative.maxLength !== undefined ? { maxLength: negative.maxLength } : {})\n\t\t};\n\t}\n\tconst system = textFieldCapability(caps.systemPrompt);\n\tif (system) {\n\t\tproperties.systemPrompt = {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'System instructions.',\n\t\t\t...(system.maxLength !== undefined ? { maxLength: system.maxLength } : {})\n\t\t};\n\t}\n\n\tif (caps.aspectRatios?.options.length) properties.aspectRatio = enumSchema(caps.aspectRatios, 'Aspect ratio (width:height).');\n\tif (caps.resolutions?.options.length) properties.resolution = enumSchema(caps.resolutions, 'Output resolution, low to high.');\n\tif (caps.quality?.options.length) properties.quality = enumSchema(caps.quality, 'Quality level, low to high.');\n\n\tif (caps.count) {\n\t\tconst options = caps.count.options?.filter((o) => o >= 1 && o <= caps.count!.max);\n\t\tproperties.count = options?.length\n\t\t\t? { type: 'integer', enum: options, description: 'Number of outputs.' }\n\t\t\t: { type: 'integer', minimum: 1, maximum: caps.count.max, description: 'Number of outputs.' };\n\t\tif (caps.count.default !== undefined) properties.count.default = caps.count.default;\n\t}\n\n\tif (caps.duration) {\n\t\tconst d = caps.duration;\n\t\tconst options = d.options?.filter((o) => (d.min === undefined || o >= d.min) && (d.max === undefined || o <= d.max));\n\t\tproperties.duration = options?.length\n\t\t\t? { type: 'number', enum: options, description: 'Duration in seconds.' }\n\t\t\t: {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\tdescription: d.step !== undefined ? `Duration in seconds (step ${d.step}).` : 'Duration in seconds.',\n\t\t\t\t\t...(d.min !== undefined ? { minimum: d.min } : {}),\n\t\t\t\t\t...(d.max !== undefined ? { maximum: d.max } : {})\n\t\t\t\t};\n\t\tif (d.default !== undefined) properties.duration.default = d.default;\n\t}\n\n\tconst seed = seedCapability(caps.seed);\n\tif (seed) {\n\t\tproperties.seed = {\n\t\t\ttype: 'integer',\n\t\t\tdescription: 'Random seed for reproducible output.',\n\t\t\t...(seed.min !== undefined ? { minimum: seed.min } : {}),\n\t\t\t...(seed.max !== undefined ? { maximum: seed.max } : {})\n\t\t};\n\t}\n\n\tconst modes = supportedModes(caps);\n\tif (modes.length > 1) {\n\t\tproperties.mode = { type: 'string', enum: modes, default: 'generate', description: 'Generate, edit a source, or inpaint with a mask.' };\n\t}\n\n\tconst roles = supportedReferenceRoles(caps);\n\tif (caps.inpainting && !roles.includes('mask')) roles.push('mask');\n\tif (roles.length > 0) {\n\t\tconst limits = roles.map((role) => {\n\t\t\tconst cap = caps.references?.roles[role] ?? { max: 1 };\n\t\t\tconst min = cap.min ? `${cap.min}–` : 'up to ';\n\t\t\treturn `${role} (${cap.label ?? REFERENCE_ROLE_LABELS[role]}): ${min}${cap.max}`;\n\t\t});\n\t\tconst perRoleTotal = roles.reduce((sum, role) => sum + (caps.references?.roles[role]?.max ?? 1), 0);\n\t\tconst maxItems = Math.min(caps.references?.maxTotal ?? Infinity, perRoleTotal);\n\t\tproperties.references = {\n\t\t\ttype: 'array',\n\t\t\tdescription: `Reference inputs by role. Limits: ${limits.join('; ')}${\n\t\t\t\tcaps.references?.maxTotal !== undefined ? `; at most ${caps.references.maxTotal} in total` : ''\n\t\t\t}.`,\n\t\t\tmaxItems,\n\t\t\titems: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: 'string', minLength: 1, description: 'Asset id.' },\n\t\t\t\t\trole: { type: 'string', enum: roles },\n\t\t\t\t\turl: { type: 'string' }\n\t\t\t\t},\n\t\t\t\trequired: ['id', 'role'],\n\t\t\t\tadditionalProperties: false\n\t\t\t}\n\t\t};\n\t\tif (roles.some((role) => (caps.references?.roles[role]?.min ?? 0) > 0)) required.push('references');\n\t}\n\n\tconst params = Object.entries(caps.params ?? {});\n\tif (params.length > 0) {\n\t\tproperties.params = {\n\t\t\ttype: 'object',\n\t\t\tdescription: 'Model-specific parameters.',\n\t\t\tproperties: Object.fromEntries(params.map(([name, spec]) => [name, paramSchema(name, spec)])),\n\t\t\tadditionalProperties: false\n\t\t};\n\t}\n\n\treturn {\n\t\ttype: 'object',\n\t\ttitle: caps.name ?? caps.id,\n\t\tdescription: `${caps.description ? `${caps.description} ` : ''}(${caps.media} model \"${caps.id}\")`,\n\t\tproperties,\n\t\t...(required.length ? { required } : {}),\n\t\tadditionalProperties: false\n\t};\n}\n","import type { AgentTool, AgentToolProvider, ToolCallContext } from '../agent/registry.js';\nimport type { JsonSchema } from '../agent/schema.js';\nimport { REFERENCE_ROLES, type GenerationRequest, type ModelCapabilities } from './capabilities.js';\nimport { planGenerationRequest, type GenerationPlan, type PlanStrategy } from './plan.js';\nimport { capabilitiesToJsonSchema } from './schema.js';\n\nexport interface GenerateContext {\n\tmodel: ModelCapabilities;\n\tplan: GenerationPlan;\n\tidempotencyKey: string;\n\tsignal?: AbortSignal;\n}\n\nexport interface EstimateContext {\n\tmodel: ModelCapabilities;\n\tplan: GenerationPlan;\n\tsignal?: AbortSignal;\n}\n\nexport interface GenerationToolProviderOptions {\n\t/** Provider id (default `generation`). */\n\tid?: string;\n\tnamespace?: string;\n\t/** Models, or a function returning the current models (re-read on every list/call). */\n\tmodels: readonly ModelCapabilities[] | (() => readonly ModelCapabilities[]);\n\t/** Sends a planned request. Its resolved value is the tool result (e.g. `{ jobId }`). */\n\tgenerate: (request: GenerationRequest, context: GenerateContext) => unknown | Promise<unknown>;\n\t/** Optional cost/time estimate; adds the `estimate_generation` tool. */\n\testimate?: (request: GenerationRequest, context: EstimateContext) => unknown | Promise<unknown>;\n\t/** Strategy used when the caller does not pass one (default `nearest`). */\n\tdefaultStrategy?: PlanStrategy;\n\t/** Completed idempotency keys remembered (default 500, oldest evicted first). */\n\tmaxIdempotencyKeys?: number;\n\t/** Lets the host signal that the model list changed. */\n\tsubscribe?: AgentToolProvider['subscribe'];\n}\n\nexport interface GenerationToolProvider extends AgentToolProvider {\n\t/** Forgets remembered idempotency keys (all, or one). */\n\tclearIdempotencyKeys(key?: string): void;\n}\n\nconst STRATEGIES: PlanStrategy[] = ['nearest', 'upgrade', 'downgrade', 'error'];\n\nconst requestSchema: JsonSchema = {\n\ttype: 'object',\n\tdescription:\n\t\t'Generation request. Call list_models for the fields each model supports; unsupported values are adjusted per `strategy`.',\n\tproperties: {\n\t\tprompt: { type: 'string' },\n\t\tnegativePrompt: { type: 'string' },\n\t\tsystemPrompt: { type: 'string' },\n\t\taspectRatio: { type: 'string', description: 'E.g. \"16:9\".' },\n\t\tresolution: { type: 'string' },\n\t\tquality: { type: 'string' },\n\t\tcount: { type: 'integer', minimum: 1 },\n\t\tduration: { type: 'number', minimum: 0, description: 'Seconds.' },\n\t\tseed: { type: 'integer' },\n\t\tmode: { type: 'string', enum: ['generate', 'edit', 'inpaint'] },\n\t\treferences: {\n\t\t\ttype: 'array',\n\t\t\titems: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: 'string', minLength: 1 },\n\t\t\t\t\trole: { type: 'string', enum: [...REFERENCE_ROLES] },\n\t\t\t\t\turl: { type: 'string' }\n\t\t\t\t},\n\t\t\t\trequired: ['id', 'role'],\n\t\t\t\tadditionalProperties: false\n\t\t\t}\n\t\t},\n\t\tparams: { type: 'object', additionalProperties: { type: ['string', 'number', 'boolean'] } }\n\t},\n\tadditionalProperties: false\n};\n\nconst strategySchema: JsonSchema = {\n\ttype: 'string',\n\tenum: STRATEGIES,\n\tdescription:\n\t\t'How to handle unsupported values: nearest (snap/clamp, ties up), downgrade (ties down), upgrade (ties up, never drops references), error (change nothing).'\n};\n\n/** Stable JSON (sorted object keys) used to compare retried arguments. */\nfunction stableStringify(value: unknown): string {\n\tif (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;\n\tif (value && typeof value === 'object') {\n\t\tconst entries = Object.keys(value as object)\n\t\t\t.filter((key) => (value as any)[key] !== undefined)\n\t\t\t.sort()\n\t\t\t.map((key) => `${JSON.stringify(key)}:${stableStringify((value as any)[key])}`);\n\t\treturn `{${entries.join(',')}}`;\n\t}\n\treturn JSON.stringify(value) ?? 'null';\n}\n\nfunction planFailure(plan: GenerationPlan) {\n\treturn {\n\t\tisError: true,\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\ttext: `The request cannot be sent to \"${plan.model}\":\\n${plan.errors.map((e) => `- ${e.field}: ${e.message}`).join('\\n')}`\n\t\t\t}\n\t\t],\n\t\tstructuredContent: { error: { code: 'plan_failed', message: 'Request planning failed.' }, plan }\n\t};\n}\n\n/**\n * Exposes generation models to a `ToolRegistry`:\n *\n * - `list_models` (read-only): ids, presentation fields and per-model request schemas.\n * - `plan_generation` (read-only): the adjusted request, adjustments and errors.\n * - `estimate_generation` (read-only, only with `estimate`): your estimate for the planned request.\n * - `generate` (destructive): plans, then calls `generate` with the adjusted\n * request. Requires an `idempotencyKey`: a retry with the same key and\n * arguments returns the first call's result (or joins it while in flight)\n * instead of generating again; the same key with different arguments is\n * rejected. Failed calls forget their key so they can be retried.\n */\nexport function createGenerationToolProvider(options: GenerationToolProviderOptions): GenerationToolProvider {\n\tconst readModels = () => (typeof options.models === 'function' ? options.models() : options.models);\n\tconst defaultStrategy = options.defaultStrategy ?? 'nearest';\n\tconst maxKeys = Math.max(1, options.maxIdempotencyKeys ?? 500);\n\tconst calls = new Map<string, { signature: string; result: Promise<unknown> }>();\n\n\tconst findModel = (id: unknown) => {\n\t\tconst model = readModels().find((m) => m.id === id);\n\t\tif (!model) throw new Error(`Unknown model \"${String(id)}\". Call list_models for the available ids.`);\n\t\treturn model;\n\t};\n\n\tconst plan = (args: Record<string, any>) => {\n\t\tconst model = findModel(args.model);\n\t\tconst request: GenerationRequest = { ...(args.request ?? {}), model: model.id };\n\t\treturn {\n\t\t\tmodel,\n\t\t\tplan: planGenerationRequest(request, model, {\n\t\t\t\tstrategy: args.strategy ?? defaultStrategy,\n\t\t\t\tresolvedPrompt: args.resolvedPrompt\n\t\t\t})\n\t\t};\n\t};\n\n\tconst planInput = (): JsonSchema & { type: 'object' } => {\n\t\tconst ids = readModels().map((m) => m.id);\n\t\treturn {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tmodel: { type: 'string', ...(ids.length ? { enum: ids } : {}), description: 'Model id from list_models.' },\n\t\t\t\trequest: requestSchema,\n\t\t\t\tstrategy: strategySchema,\n\t\t\t\tresolvedPrompt: {\n\t\t\t\t\ttype: 'string',\n\t\t\t\t\tdescription: 'The prompt with templates and variables resolved, when `request.prompt` is a template.'\n\t\t\t\t}\n\t\t\t},\n\t\t\trequired: ['model', 'request'],\n\t\t\tadditionalProperties: false\n\t\t};\n\t};\n\n\tconst tools = (): AgentTool[] => {\n\t\tconst list: AgentTool[] = [\n\t\t\t{\n\t\t\t\tname: 'list_models',\n\t\t\t\ttitle: 'List generation models',\n\t\t\t\tdescription:\n\t\t\t\t\t'Lists the available generation models with their media kind, description and the JSON Schema of the request each accepts.',\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tmedia: { type: 'string', enum: ['image', 'video', 'audio', 'text'], description: 'Only models of this kind.' }\n\t\t\t\t\t},\n\t\t\t\t\tadditionalProperties: false\n\t\t\t\t},\n\t\t\t\tannotations: {\n\t\t\t\t\ttitle: 'List generation models',\n\t\t\t\t\treadOnlyHint: true,\n\t\t\t\t\tdestructiveHint: false,\n\t\t\t\t\tidempotentHint: true,\n\t\t\t\t\topenWorldHint: false\n\t\t\t\t},\n\t\t\t\thandler: (args) => ({\n\t\t\t\t\tmodels: readModels()\n\t\t\t\t\t\t.filter((m) => !args.media || m.media === args.media)\n\t\t\t\t\t\t.map((m) => ({\n\t\t\t\t\t\t\tid: m.id,\n\t\t\t\t\t\t\tmedia: m.media,\n\t\t\t\t\t\t\t...(m.name ? { name: m.name } : {}),\n\t\t\t\t\t\t\t...(m.description ? { description: m.description } : {}),\n\t\t\t\t\t\t\t...(m.badges?.length ? { badges: [...m.badges] } : {}),\n\t\t\t\t\t\t\trequestSchema: capabilitiesToJsonSchema(m)\n\t\t\t\t\t\t}))\n\t\t\t\t})\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'plan_generation',\n\t\t\t\ttitle: 'Plan a generation request',\n\t\t\t\tdescription:\n\t\t\t\t\t'Checks a request against a model without generating: returns the adjusted request, every adjustment (field, from, to, reason) and errors.',\n\t\t\t\tinputSchema: planInput(),\n\t\t\t\tannotations: {\n\t\t\t\t\ttitle: 'Plan a generation request',\n\t\t\t\t\treadOnlyHint: true,\n\t\t\t\t\tdestructiveHint: false,\n\t\t\t\t\tidempotentHint: true,\n\t\t\t\t\topenWorldHint: false\n\t\t\t\t},\n\t\t\t\thandler: (args) => ({ plan: plan(args).plan })\n\t\t\t}\n\t\t];\n\n\t\tif (options.estimate) {\n\t\t\tconst estimate = options.estimate;\n\t\t\tlist.push({\n\t\t\t\tname: 'estimate_generation',\n\t\t\t\ttitle: 'Estimate a generation',\n\t\t\t\tdescription: 'Estimates cost and/or time of a request after planning it. Does not generate anything.',\n\t\t\t\tinputSchema: planInput(),\n\t\t\t\tannotations: {\n\t\t\t\t\ttitle: 'Estimate a generation',\n\t\t\t\t\treadOnlyHint: true,\n\t\t\t\t\tdestructiveHint: false,\n\t\t\t\t\tidempotentHint: true,\n\t\t\t\t\topenWorldHint: true\n\t\t\t\t},\n\t\t\t\thandler: async (args, context: ToolCallContext) => {\n\t\t\t\t\tconst planned = plan(args);\n\t\t\t\t\tif (!planned.plan.ok) return planFailure(planned.plan);\n\t\t\t\t\tconst value = await estimate(planned.plan.request, { ...planned, signal: context.signal });\n\t\t\t\t\treturn { plan: planned.plan, estimate: value };\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tconst generateInput = planInput();\n\t\tlist.push({\n\t\t\tname: 'generate',\n\t\t\ttitle: 'Generate media',\n\t\t\tdescription:\n\t\t\t\t'Plans the request and starts a generation. Pass a fresh `idempotencyKey` per intended generation and reuse it when retrying, so a retry never generates twice.',\n\t\t\tinputSchema: {\n\t\t\t\t...generateInput,\n\t\t\t\tproperties: {\n\t\t\t\t\t...generateInput.properties,\n\t\t\t\t\tidempotencyKey: {\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tminLength: 1,\n\t\t\t\t\t\tmaxLength: 200,\n\t\t\t\t\t\tdescription: 'Unique per intended generation; reuse it to retry safely.'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\trequired: ['model', 'request', 'idempotencyKey']\n\t\t\t},\n\t\t\tannotations: {\n\t\t\t\ttitle: 'Generate media',\n\t\t\t\treadOnlyHint: false,\n\t\t\t\tdestructiveHint: true,\n\t\t\t\tidempotentHint: true,\n\t\t\t\topenWorldHint: true\n\t\t\t},\n\t\t\thandler: async (args, context: ToolCallContext) => {\n\t\t\t\tconst key = String(args.idempotencyKey);\n\t\t\t\tconst { idempotencyKey: _key, ...rest } = args;\n\t\t\t\tconst signature = stableStringify(rest);\n\t\t\t\tconst existing = calls.get(key);\n\t\t\t\tif (existing) {\n\t\t\t\t\tif (existing.signature !== signature) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Idempotency key \"${key}\" was already used with different arguments. Use a new key for a new generation.`\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn { ...(await existing.result as object), deduplicated: true };\n\t\t\t\t}\n\t\t\t\tconst planned = plan(args);\n\t\t\t\tif (!planned.plan.ok) return planFailure(planned.plan);\n\t\t\t\tconst result = Promise.resolve()\n\t\t\t\t\t.then(() =>\n\t\t\t\t\t\toptions.generate(planned.plan.request, {\n\t\t\t\t\t\t\t...planned,\n\t\t\t\t\t\t\tidempotencyKey: key,\n\t\t\t\t\t\t\tsignal: context.signal\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\t.then((value) => ({\n\t\t\t\t\t\tresult: value,\n\t\t\t\t\t\tplan: planned.plan,\n\t\t\t\t\t\tidempotencyKey: key\n\t\t\t\t\t}));\n\t\t\t\tcalls.set(key, { signature, result });\n\t\t\t\twhile (calls.size > maxKeys) calls.delete(calls.keys().next().value as string);\n\t\t\t\ttry {\n\t\t\t\t\treturn await result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (calls.get(key)?.result === result) calls.delete(key);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn list;\n\t};\n\n\treturn {\n\t\tid: options.id ?? 'generation',\n\t\t...(options.namespace ? { namespace: options.namespace } : {}),\n\t\ttools,\n\t\t...(options.subscribe ? { subscribe: options.subscribe } : {}),\n\t\tclearIdempotencyKeys(key?: string) {\n\t\t\tif (key === undefined) calls.clear();\n\t\t\telse calls.delete(key);\n\t\t}\n\t};\n}\n","import { getLatestVersion, getTemplateBody, resolvePrompt } from '../grammar/engine.js';\nimport type { SourceMapSegment } from '../grammar/sourcemap.js';\nimport { tokenizePrompt } from '../grammar/tokenizer.js';\nimport type { PromptValues, Template } from '../grammar/tokens.js';\nimport type { GenerationRequest, ModelCapabilities, ReferenceRole } from './capabilities.js';\nimport type { GenerationPlan, PlanAdjustment } from './plan.js';\n\nexport const PROVENANCE_FORMAT = 'pixerate.generation-provenance';\nexport const PROVENANCE_VERSION = 1;\n\n/** The exact template body a generation used. */\nexport interface PinnedTemplate {\n\tname: string;\n\ttemplateId?: string;\n\t/** Version id or version label the body came from (absent for unversioned templates). */\n\tversionId?: string;\n\tversion?: string | number;\n\tbody: string;\n}\n\n/** Everything needed to explain and reproduce one generation. */\nexport interface GenerationProvenance {\n\tformat: typeof PROVENANCE_FORMAT;\n\tversion: typeof PROVENANCE_VERSION;\n\t/** Raw prompt as typed, with `{{templates}}` and `{variables}`. */\n\trawPrompt: string;\n\t/** Every template the prompt reached (recursively), pinned to the body used. */\n\ttemplates: PinnedTemplate[];\n\t/** Version bindings in effect (`{ templateName: versionId }`) for every pinned versioned template. */\n\tbindings: Record<string, string>;\n\t/** Variable values, or `null` when variables were left unresolved. */\n\tvalues: PromptValues | null;\n\tresolvedPrompt: string;\n\tsourceMap: SourceMapSegment[];\n\tmodelId: string;\n\t/** The planned (adjusted) request that was sent. */\n\trequest: GenerationRequest;\n\tadjustments: PlanAdjustment[];\n\treferences: { id: string; role: ReferenceRole }[];\n\t/** ISO timestamps. */\n\tcreatedAt: string;\n\tstartedAt?: string;\n\tcompletedAt?: string;\n\t/** Ids of the outputs this generation produced. */\n\toutputIds?: string[];\n\tmeta?: Record<string, unknown>;\n}\n\nexport interface CreateProvenanceOptions {\n\trawPrompt: string;\n\ttemplates?: Template[];\n\t/** Template version bindings (`{ templateName: versionIdOrVersion }`). */\n\tbindings?: Record<string, string>;\n\tvalues?: PromptValues;\n\tmodel: string | ModelCapabilities;\n\t/** The plan the request came from (its request and adjustments are recorded) … */\n\tplan?: GenerationPlan;\n\t/** … or the request that was sent. */\n\trequest?: GenerationRequest;\n\tadjustments?: PlanAdjustment[];\n\t/** Creation time (default `Date.now()`). */\n\tcreatedAt?: Date | number | string;\n\tstartedAt?: Date | number | string;\n\tcompletedAt?: Date | number | string;\n\toutputIds?: string[];\n\tmeta?: Record<string, unknown>;\n\tmaxDepth?: number;\n}\n\nexport class ProvenanceParseError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'ProvenanceParseError';\n\t}\n}\n\nconst toIso = (value: Date | number | string) => new Date(value).toISOString();\nconst clone = <T>(value: T): T => (value === undefined ? value : JSON.parse(JSON.stringify(value)));\n\n/** Collects the templates a prompt reaches, with the body each one resolves to. */\nfunction pinTemplates(\n\trawPrompt: string,\n\ttemplates: Template[],\n\tbindings: Record<string, string>,\n\tmaxDepth: number\n): PinnedTemplate[] {\n\tconst byName = new Map(templates.map((t) => [t.name, t]));\n\tconst pinned = new Map<string, PinnedTemplate>();\n\tconst visit = (text: string, depth: number) => {\n\t\tif (depth > maxDepth) return;\n\t\tfor (const token of tokenizePrompt(text)) {\n\t\t\tif (token.type !== 'template' || pinned.has(token.value)) continue;\n\t\t\tconst template = byName.get(token.value);\n\t\t\tif (!template) continue;\n\t\t\t// Mirror `getTemplateBody` so the pinned body is the one `resolvePrompt` used.\n\t\t\tconst body = getTemplateBody(template, bindings);\n\t\t\tif (body === undefined) continue;\n\t\t\tconst bound = bindings[template.name];\n\t\t\tconst version =\n\t\t\t\tbound && template.versions\n\t\t\t\t\t? template.versions.find((v) => v.id === bound || v.version === bound)\n\t\t\t\t\t: getLatestVersion(template);\n\t\t\tconst isVersioned = !!template.versions?.length && !!version;\n\t\t\tpinned.set(template.name, {\n\t\t\t\tname: template.name,\n\t\t\t\t...(template.id !== undefined ? { templateId: template.id } : {}),\n\t\t\t\t...(isVersioned ? { versionId: version!.id, version: version!.version } : {}),\n\t\t\t\tbody\n\t\t\t});\n\t\t\tvisit(body, depth + 1);\n\t\t}\n\t};\n\tvisit(rawPrompt, 0);\n\treturn [...pinned.values()];\n}\n\n/** Templates that reproduce the pinned bodies exactly (one single-version template each). */\nexport function pinnedTemplatesToTemplates(pinned: readonly PinnedTemplate[]): Template[] {\n\treturn pinned.map((p) =>\n\t\tp.versionId !== undefined\n\t\t\t? {\n\t\t\t\t\t...(p.templateId !== undefined ? { id: p.templateId } : {}),\n\t\t\t\t\tname: p.name,\n\t\t\t\t\tlatestVersion: p.version,\n\t\t\t\t\tversions: [{ id: p.versionId, version: p.version ?? p.versionId, body: p.body }]\n\t\t\t\t}\n\t\t\t: { ...(p.templateId !== undefined ? { id: p.templateId } : {}), name: p.name, body: p.body }\n\t);\n}\n\n/**\n * Records what produced an output: the raw prompt, the exact template bodies\n * and versions it reached, variable values, the resolved prompt and its\n * source map, the model, the planned settings and adjustments, and the\n * references by role.\n */\nexport function createProvenance(options: CreateProvenanceOptions): GenerationProvenance {\n\tconst templates = options.templates ?? [];\n\tconst maxDepth = options.maxDepth ?? 10;\n\tconst resolved = resolvePrompt(options.rawPrompt, templates, {\n\t\tbindings: options.bindings,\n\t\tvalues: options.values,\n\t\tmaxDepth\n\t});\n\tconst pinned = pinTemplates(options.rawPrompt, templates, options.bindings ?? {}, maxDepth);\n\tconst bindings: Record<string, string> = {};\n\tfor (const p of pinned) if (p.versionId !== undefined) bindings[p.name] = p.versionId;\n\n\tconst modelId = typeof options.model === 'string' ? options.model : options.model.id;\n\tconst request: GenerationRequest = clone(options.plan?.request ?? options.request ?? {});\n\trequest.model = modelId;\n\tif (request.prompt === undefined) request.prompt = resolved.text;\n\tconst adjustments = clone(options.adjustments ?? options.plan?.adjustments ?? []);\n\n\treturn {\n\t\tformat: PROVENANCE_FORMAT,\n\t\tversion: PROVENANCE_VERSION,\n\t\trawPrompt: options.rawPrompt,\n\t\ttemplates: pinned,\n\t\tbindings,\n\t\tvalues: options.values ? clone(options.values) : null,\n\t\tresolvedPrompt: resolved.text,\n\t\tsourceMap: resolved.map.map((segment) => {\n\t\t\tconst { locked: _locked, ...rest } = segment as SourceMapSegment & { locked?: boolean };\n\t\t\treturn rest;\n\t\t}),\n\t\tmodelId,\n\t\trequest,\n\t\tadjustments,\n\t\treferences: (request.references ?? []).map(({ id, role }) => ({ id, role })),\n\t\tcreatedAt: toIso(options.createdAt ?? Date.now()),\n\t\t...(options.startedAt !== undefined ? { startedAt: toIso(options.startedAt) } : {}),\n\t\t...(options.completedAt !== undefined ? { completedAt: toIso(options.completedAt) } : {}),\n\t\t...(options.outputIds ? { outputIds: [...options.outputIds] } : {}),\n\t\t...(options.meta ? { meta: clone(options.meta) } : {})\n\t};\n}\n\n/** Versioned JSON for storage next to an output. */\nexport function serializeProvenance(provenance: GenerationProvenance): string {\n\treturn JSON.stringify(provenance);\n}\n\nconst isRecord = (value: unknown): value is Record<string, any> =>\n\t!!value && typeof value === 'object' && !Array.isArray(value);\n\n/** Parses and checks serialized provenance. Throws {@link ProvenanceParseError}. */\nexport function parseProvenance(input: string | unknown): GenerationProvenance {\n\tlet data: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tdata = JSON.parse(input);\n\t\t} catch {\n\t\t\tthrow new ProvenanceParseError('Provenance is not valid JSON.');\n\t\t}\n\t}\n\tif (!isRecord(data) || data.format !== PROVENANCE_FORMAT) {\n\t\tthrow new ProvenanceParseError('Not a generation provenance record.');\n\t}\n\tif (typeof data.version !== 'number' || data.version > PROVENANCE_VERSION) {\n\t\tthrow new ProvenanceParseError(`Unsupported provenance version ${JSON.stringify(data.version)}.`);\n\t}\n\tconst checks: [string, boolean][] = [\n\t\t['rawPrompt', typeof data.rawPrompt === 'string'],\n\t\t['resolvedPrompt', typeof data.resolvedPrompt === 'string'],\n\t\t['modelId', typeof data.modelId === 'string'],\n\t\t['createdAt', typeof data.createdAt === 'string'],\n\t\t['request', isRecord(data.request)],\n\t\t['templates', Array.isArray(data.templates) && data.templates.every((t: any) => isRecord(t) && typeof t.name === 'string' && typeof t.body === 'string')],\n\t\t['sourceMap', Array.isArray(data.sourceMap)],\n\t\t['adjustments', Array.isArray(data.adjustments)],\n\t\t['references', Array.isArray(data.references)]\n\t];\n\tconst bad = checks.find(([, ok]) => !ok);\n\tif (bad) throw new ProvenanceParseError(`Invalid provenance field \"${bad[0]}\".`);\n\treturn {\n\t\t...(data as GenerationProvenance),\n\t\tbindings: isRecord(data.bindings) ? data.bindings : {},\n\t\tvalues: isRecord(data.values) ? data.values : null\n\t};\n}\n\n/**\n * A request that reproduces the recorded generation exactly: the planned\n * settings, model, seed and references that were sent, with the prompt as\n * resolved at the time (not re-resolved against today's templates).\n */\nexport function recreateRequest(provenance: GenerationProvenance): GenerationRequest {\n\tconst request = clone(provenance.request);\n\trequest.model = provenance.modelId;\n\tif (request.prompt === undefined) request.prompt = provenance.resolvedPrompt;\n\treturn request;\n}\n\n/**\n * Re-resolves the raw prompt against the pinned template bodies and values,\n * e.g. to check a record or to open it in an editor with its source map.\n */\nexport function replayProvenancePrompt(provenance: GenerationProvenance): {\n\ttext: string;\n\tmap: SourceMapSegment[];\n\tmatches: boolean;\n} {\n\tconst result = resolvePrompt(provenance.rawPrompt, pinnedTemplatesToTemplates(provenance.templates), {\n\t\tbindings: provenance.bindings,\n\t\t...(provenance.values ? { values: provenance.values } : {})\n\t});\n\treturn { text: result.text, map: result.map, matches: result.text === provenance.resolvedPrompt };\n}\n","export interface BatchResolverOptions<K, V> {\n\t/** Loads many keys at once. Keys missing from the map resolve to `undefined`. */\n\tfetch: (keys: K[]) => Promise<Map<K, V>>;\n\t/** Largest batch passed to `fetch` (default 50). */\n\tmaxBatch?: number;\n\t/** How long to collect keys before fetching, in ms (default 0: the same tick). */\n\tdelayMs?: number;\n\t/** Called once per failed batch. The batch's keys are recorded as resolved-empty. */\n\tonError?: (error: unknown, keys: K[]) => void;\n}\n\nexport interface BatchResolver<K, V> {\n\t/** Resolves one key, batched with other loads in the same window. Never rejects. */\n\tload(key: K): Promise<V | undefined>;\n\tloadMany(keys: readonly K[]): Promise<(V | undefined)[]>;\n\t/** The settled value for a key, if it has been resolved. */\n\tpeek(key: K): V | undefined;\n\t/** Whether a key has settled (found, missing or failed). */\n\thas(key: K): boolean;\n\t/** Whether a key's last fetch failed. */\n\tfailed(key: K): boolean;\n\t/** Seeds a value without fetching. */\n\tprime(key: K, value: V): void;\n\t/** Forgets one key (or all) so the next `load` fetches again. */\n\tclear(key?: K): void;\n\t/** Fetches anything still waiting now, without waiting for `delayMs`. */\n\tflush(): void;\n}\n\ninterface Pending<V> {\n\tpromise: Promise<V | undefined>;\n\tresolve: (value: V | undefined) => void;\n}\n\n/**\n * Coalesces lookups (e.g. asset ids → thumbnails, template ids → bodies) into\n * batched `fetch` calls: keys requested within `delayMs` share a batch of at\n * most `maxBatch`, duplicate keys are fetched once, and results are cached.\n * A failed batch resolves its keys to `undefined` and records them as\n * resolved-empty (`failed(key)` is `true`) so one bad response does not cause\n * a retry storm; `clear(key)` allows a retry.\n */\nexport function createBatchResolver<K, V>(options: BatchResolverOptions<K, V>): BatchResolver<K, V> {\n\tconst maxBatch = Math.max(1, Math.floor(options.maxBatch ?? 50));\n\tconst delayMs = Math.max(0, options.delayMs ?? 0);\n\tconst settled = new Map<K, V | undefined>();\n\tconst failures = new Set<K>();\n\tconst inFlight = new Map<K, Pending<V>>();\n\tlet queue: K[] = [];\n\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\n\tconst run = (keys: K[]) => {\n\t\tconst pendings = keys.map((key) => inFlight.get(key));\n\t\tconst finish = (values: Map<K, V> | null, error?: unknown) => {\n\t\t\tif (values === null) options.onError?.(error, keys);\n\t\t\tkeys.forEach((key, index) => {\n\t\t\t\tconst pending = pendings[index];\n\t\t\t\tconst value = values?.get(key);\n\t\t\t\t// Only cache if nobody cleared the key while the batch was in flight.\n\t\t\t\tif (pending && inFlight.get(key) === pending) {\n\t\t\t\t\tinFlight.delete(key);\n\t\t\t\t\tsettled.set(key, value);\n\t\t\t\t\tif (values === null) failures.add(key);\n\t\t\t\t}\n\t\t\t\tpending?.resolve(value);\n\t\t\t});\n\t\t};\n\t\tlet promise: Promise<Map<K, V>>;\n\t\ttry {\n\t\t\tpromise = Promise.resolve(options.fetch(keys));\n\t\t} catch (error) {\n\t\t\tpromise = Promise.reject(error);\n\t\t}\n\t\tpromise.then(\n\t\t\t(values) => finish(values instanceof Map ? values : new Map()),\n\t\t\t(error) => finish(null, error ?? new Error('Batch fetch failed.'))\n\t\t);\n\t};\n\n\tconst flush = () => {\n\t\tif (timer !== null) clearTimeout(timer);\n\t\ttimer = null;\n\t\tconst keys = queue;\n\t\tqueue = [];\n\t\tfor (let i = 0; i < keys.length; i += maxBatch) run(keys.slice(i, i + maxBatch));\n\t};\n\n\tconst load = (key: K): Promise<V | undefined> => {\n\t\tif (settled.has(key)) return Promise.resolve(settled.get(key));\n\t\tconst existing = inFlight.get(key);\n\t\tif (existing) return existing.promise;\n\t\tlet resolve!: (value: V | undefined) => void;\n\t\tconst promise = new Promise<V | undefined>((r) => (resolve = r));\n\t\tinFlight.set(key, { promise, resolve });\n\t\tqueue.push(key);\n\t\tif (queue.length >= maxBatch) flush();\n\t\telse if (timer === null) timer = setTimeout(flush, delayMs);\n\t\treturn promise;\n\t};\n\n\treturn {\n\t\tload,\n\t\tloadMany: (keys) => Promise.all(keys.map(load)),\n\t\tpeek: (key) => settled.get(key),\n\t\thas: (key) => settled.has(key),\n\t\tfailed: (key) => failures.has(key),\n\t\tprime(key, value) {\n\t\t\tsettled.set(key, value);\n\t\t\tfailures.delete(key);\n\t\t},\n\t\tclear(key) {\n\t\t\tif (key === undefined) {\n\t\t\t\tsettled.clear();\n\t\t\t\tfailures.clear();\n\t\t\t\t// Loads already fetching still resolve, but their results are not cached.\n\t\t\t\tfor (const k of [...inFlight.keys()]) if (!queue.includes(k)) inFlight.delete(k);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled.delete(key);\n\t\t\tfailures.delete(key);\n\t\t\tif (!queue.includes(key)) inFlight.delete(key);\n\t\t},\n\t\tflush\n\t};\n}\n","import type { GenerationRequest, ModelCapabilities } from './capabilities.js';\nimport { resolutionMegapixels } from './plan.js';\n\nexport interface JobProgressInput {\n\t/** Start time (ms since epoch, `Date` or ISO string). */\n\tstartedAt: number | Date | string;\n\t/** Current time (default `Date.now()`). */\n\tnow?: number | Date | string;\n\t/** Expected total duration in ms. Without it progress eases towards 95%. */\n\texpectedMs?: number;\n\t/** The job finished. */\n\tdone?: boolean;\n}\n\nexport interface JobProgressEstimate {\n\t/** 0–100. Never above 95 until `done`. */\n\tpercent: number;\n\t/** E.g. `~2m left`, `~40s left`, `Almost done…`, `Starting…`, `Done`. */\n\tlabel: string;\n\telapsedMs: number;\n\t/** Estimated ms left (`undefined` without `expectedMs`). */\n\tremainingMs?: number;\n\t/** Running longer than expected. */\n\toverdue: boolean;\n}\n\n/** Highest percentage shown before a job reports completion. */\nexport const MAX_PENDING_PERCENT = 95;\n/** Remaining time under which the label switches to \"Almost done…\". */\nconst ALMOST_DONE_MS = 5_000;\n/** Time constant for jobs without an expected duration. */\nconst UNKNOWN_DURATION_TAU_MS = 30_000;\n\nconst toMs = (value: number | Date | string) => (typeof value === 'number' ? value : new Date(value).getTime());\n\n/** `~45s left`, `~2m left`, `~1h 5m left`. Minutes and seconds round up. */\nexport function formatRemaining(ms: number): string {\n\tconst seconds = Math.ceil(ms / 1000);\n\tif (seconds < 60) return `~${Math.max(1, seconds)}s left`;\n\tconst minutes = Math.ceil(ms / 60_000);\n\tif (minutes < 60) return `~${minutes}m left`;\n\tconst hours = Math.floor(minutes / 60);\n\tconst rest = minutes % 60;\n\treturn rest ? `~${hours}h ${rest}m left` : `~${hours}h left`;\n}\n\n/**\n * Progress for a job that reports no progress of its own: linear in elapsed\n * time over `expectedMs`, held at 95% until `done` (so the bar never sits at\n * 100% while still waiting), with a \"~2m left\" style label that turns into\n * \"Almost done…\" near or past the expected end. Pure: pass `now` to test.\n */\nexport function estimateJobProgress(input: JobProgressInput): JobProgressEstimate {\n\tconst started = toMs(input.startedAt);\n\tconst now = toMs(input.now ?? Date.now());\n\tconst elapsedMs = Math.max(0, now - started);\n\tif (input.done) return { percent: 100, label: 'Done', elapsedMs, remainingMs: 0, overdue: false };\n\tif (!Number.isFinite(started) || now <= started) {\n\t\treturn { percent: 0, label: 'Starting…', elapsedMs: 0, ...(input.expectedMs ? { remainingMs: input.expectedMs } : {}), overdue: false };\n\t}\n\tconst expected = input.expectedMs;\n\tif (!expected || !(expected > 0)) {\n\t\tconst percent = MAX_PENDING_PERCENT * (1 - Math.exp(-elapsedMs / UNKNOWN_DURATION_TAU_MS));\n\t\treturn { percent: Math.floor(percent), label: 'Working…', elapsedMs, overdue: false };\n\t}\n\tconst remainingMs = Math.max(0, expected - elapsedMs);\n\tconst percent = Math.min(MAX_PENDING_PERCENT, Math.floor((elapsedMs / expected) * 100));\n\treturn {\n\t\tpercent,\n\t\tlabel: remainingMs <= ALMOST_DONE_MS ? 'Almost done…' : formatRemaining(remainingMs),\n\t\telapsedMs,\n\t\tremainingMs,\n\t\toverdue: elapsedMs > expected\n\t};\n}\n\n/**\n * Expected duration of a request from the model's `timing`: `baseMs`, plus\n * `perItemMs` per output beyond the first, `perSecondMs` per second of\n * duration and `perMegapixelMs` per megapixel of resolution. `undefined` when\n * the model has no timing.\n */\nexport function expectedDurationFor(caps: ModelCapabilities, request: GenerationRequest = {}): number | undefined {\n\tconst timing = caps.timing;\n\tif (!timing) return undefined;\n\tconst count = request.count ?? caps.count?.default ?? 1;\n\tconst duration = request.duration ?? caps.duration?.default ?? 0;\n\tconst resolution = request.resolution ?? caps.resolutions?.default;\n\tconst megapixels = resolution ? (resolutionMegapixels(resolution) ?? 0) : 0;\n\treturn Math.round(\n\t\ttiming.baseMs +\n\t\t\t(timing.perItemMs ?? 0) * Math.max(0, count - 1) +\n\t\t\t(timing.perSecondMs ?? 0) * duration +\n\t\t\t(timing.perMegapixelMs ?? 0) * megapixels\n\t);\n}\n","import {\n\tREFERENCE_ROLE_LABELS,\n\tseedCapability,\n\tsupportedModes,\n\tsupportedReferenceRoles,\n\ttextFieldCapability,\n\ttype EnumCapability,\n\ttype GenerationParamValue,\n\ttype GenerationReference,\n\ttype GenerationRequest,\n\ttype ModelCapabilities,\n\ttype ReferenceRole\n} from './capabilities.js';\n\nexport interface GenerationControlOption {\n\tvalue: string | number;\n\tlabel: string;\n}\n\n/**\n * A UI control derived from capabilities. `key` is a request path:\n * `aspectRatio`, `count`, `params.guidance`, …\n */\nexport interface GenerationControl {\n\tkey: string;\n\tlabel: string;\n\t/** `select` for choices, `range` for bounded numbers, `number` for open numbers. */\n\tkind: 'select' | 'range' | 'number' | 'textarea' | 'checkbox';\n\tvalueType: 'string' | 'number' | 'boolean';\n\toptions?: GenerationControlOption[];\n\tmin?: number;\n\tmax?: number;\n\tstep?: number;\n\tmaxLength?: number;\n\tdefaultValue?: string | number | boolean;\n\tdescription?: string;\n}\n\nexport interface ReferenceSlot {\n\trole: ReferenceRole;\n\tlabel: string;\n\tdescription?: string;\n\tmin: number;\n\tmax: number;\n\treferences: GenerationReference[];\n\t/** More references of this role fit (per role and in total). */\n\tcanAdd: boolean;\n}\n\nfunction enumControl(key: string, label: string, cap: EnumCapability<string> | undefined): GenerationControl | null {\n\tif (!cap?.options.length) return null;\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\tkind: 'select',\n\t\tvalueType: 'string',\n\t\toptions: cap.options.map((value) => ({ value, label: cap.labels?.[value] ?? value })),\n\t\t...(cap.default !== undefined ? { defaultValue: cap.default } : {})\n\t};\n}\n\n/**\n * The settings controls a model supports, in display order. Excludes the\n * prompt (edited by the prompt editor) and references (see\n * {@link getReferenceSlots}).\n */\nexport function getGenerationControls(caps: ModelCapabilities): GenerationControl[] {\n\tconst controls: (GenerationControl | null)[] = [];\n\tconst modes = supportedModes(caps);\n\tif (modes.length > 1) {\n\t\tcontrols.push({\n\t\t\tkey: 'mode',\n\t\t\tlabel: 'Mode',\n\t\t\tkind: 'select',\n\t\t\tvalueType: 'string',\n\t\t\toptions: modes.map((m) => ({ value: m, label: m[0].toUpperCase() + m.slice(1) })),\n\t\t\tdefaultValue: 'generate'\n\t\t});\n\t}\n\tconst negative = textFieldCapability(caps.negativePrompt);\n\tif (negative) controls.push({ key: 'negativePrompt', label: 'Negative prompt', kind: 'textarea', valueType: 'string', maxLength: negative.maxLength });\n\tconst system = textFieldCapability(caps.systemPrompt);\n\tif (system) controls.push({ key: 'systemPrompt', label: 'System prompt', kind: 'textarea', valueType: 'string', maxLength: system.maxLength });\n\tcontrols.push(enumControl('aspectRatio', 'Aspect ratio', caps.aspectRatios));\n\tcontrols.push(enumControl('resolution', 'Resolution', caps.resolutions));\n\tcontrols.push(enumControl('quality', 'Quality', caps.quality));\n\n\tif (caps.count) {\n\t\tconst options = caps.count.options?.filter((o) => o >= 1 && o <= caps.count!.max);\n\t\tcontrols.push(\n\t\t\toptions?.length\n\t\t\t\t? { key: 'count', label: 'Outputs', kind: 'select', valueType: 'number', options: options.map((o) => ({ value: o, label: String(o) })), defaultValue: caps.count.default }\n\t\t\t\t: { key: 'count', label: 'Outputs', kind: 'range', valueType: 'number', min: 1, max: caps.count.max, step: 1, defaultValue: caps.count.default ?? 1 }\n\t\t);\n\t}\n\tif (caps.duration) {\n\t\tconst d = caps.duration;\n\t\tconst options = d.options?.filter((o) => (d.min === undefined || o >= d.min) && (d.max === undefined || o <= d.max));\n\t\tcontrols.push(\n\t\t\toptions?.length\n\t\t\t\t? { key: 'duration', label: 'Duration (s)', kind: 'select', valueType: 'number', options: options.map((o) => ({ value: o, label: `${o}s` })), defaultValue: d.default }\n\t\t\t\t: {\n\t\t\t\t\t\tkey: 'duration',\n\t\t\t\t\t\tlabel: 'Duration (s)',\n\t\t\t\t\t\tkind: d.min !== undefined && d.max !== undefined ? 'range' : 'number',\n\t\t\t\t\t\tvalueType: 'number',\n\t\t\t\t\t\tmin: d.min,\n\t\t\t\t\t\tmax: d.max,\n\t\t\t\t\t\tstep: d.step,\n\t\t\t\t\t\tdefaultValue: d.default\n\t\t\t\t\t}\n\t\t);\n\t}\n\tconst seed = seedCapability(caps.seed);\n\tif (seed) controls.push({ key: 'seed', label: 'Seed', kind: 'number', valueType: 'number', min: seed.min, max: seed.max, step: 1 });\n\n\tfor (const [name, spec] of Object.entries(caps.params ?? {})) {\n\t\tconst base = { key: `params.${name}`, label: spec.label ?? name, ...(spec.description ? { description: spec.description } : {}) };\n\t\tif (spec.type === 'boolean') {\n\t\t\tcontrols.push({ ...base, kind: 'checkbox', valueType: 'boolean', defaultValue: spec.default });\n\t\t} else if (spec.type === 'enum') {\n\t\t\tconst numeric = spec.options.every((o) => typeof o === 'number');\n\t\t\tcontrols.push({\n\t\t\t\t...base,\n\t\t\t\tkind: 'select',\n\t\t\t\tvalueType: numeric ? 'number' : 'string',\n\t\t\t\toptions: spec.options.map((value) => ({ value, label: String(value) })),\n\t\t\t\tdefaultValue: spec.default\n\t\t\t});\n\t\t} else {\n\t\t\tcontrols.push({\n\t\t\t\t...base,\n\t\t\t\tkind: spec.min !== undefined && spec.max !== undefined ? 'range' : 'number',\n\t\t\t\tvalueType: 'number',\n\t\t\t\tmin: spec.min,\n\t\t\t\tmax: spec.max,\n\t\t\t\tstep: spec.step ?? (spec.type === 'integer' ? 1 : undefined),\n\t\t\t\tdefaultValue: spec.default\n\t\t\t});\n\t\t}\n\t}\n\treturn controls.filter((c): c is GenerationControl => c !== null);\n}\n\n/** Reads a request value by control key (`params.x` reads a param). */\nexport function getRequestValue(request: GenerationRequest, key: string): unknown {\n\tif (key.startsWith('params.')) return request.params?.[key.slice(7)];\n\treturn (request as Record<string, unknown>)[key];\n}\n\n/** Returns a copy of `request` with `key` set (`undefined` or `''` removes it). */\nexport function setRequestValue(request: GenerationRequest, key: string, value: unknown): GenerationRequest {\n\tconst remove = value === undefined || value === '' || (typeof value === 'number' && Number.isNaN(value));\n\tif (key.startsWith('params.')) {\n\t\tconst name = key.slice(7);\n\t\tconst params = { ...(request.params ?? {}) };\n\t\tif (remove) delete params[name];\n\t\telse params[name] = value as GenerationParamValue;\n\t\tconst next: GenerationRequest = { ...request, params };\n\t\tif (Object.keys(params).length === 0) delete next.params;\n\t\treturn next;\n\t}\n\tconst next = { ...request } as Record<string, unknown>;\n\tif (remove) delete next[key];\n\telse next[key] = value;\n\treturn next as GenerationRequest;\n}\n\n/** Parses a DOM control's string value into the control's value type. */\nexport function parseControlValue(control: GenerationControl, raw: string | boolean): string | number | boolean | undefined {\n\tif (control.valueType === 'boolean') return Boolean(raw);\n\tif (raw === '' || raw === undefined) return undefined;\n\tif (control.valueType === 'number') {\n\t\tconst n = Number(raw);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn String(raw);\n}\n\n/** Reference slots for every role the model accepts, filled from `request`. */\nexport function getReferenceSlots(caps: ModelCapabilities, request: GenerationRequest = {}): ReferenceSlot[] {\n\tconst roles = supportedReferenceRoles(caps);\n\tif (caps.inpainting && !roles.includes('mask')) roles.push('mask');\n\tconst refs = request.references ?? [];\n\tconst total = refs.length;\n\tconst maxTotal = caps.references?.maxTotal ?? Infinity;\n\treturn roles.map((role) => {\n\t\tconst cap = caps.references?.roles[role] ?? { max: 1 };\n\t\tconst references = refs.filter((ref) => ref.role === role);\n\t\treturn {\n\t\t\trole,\n\t\t\tlabel: cap.label ?? REFERENCE_ROLE_LABELS[role],\n\t\t\t...(cap.description ? { description: cap.description } : {}),\n\t\t\tmin: cap.min ?? 0,\n\t\t\tmax: cap.max,\n\t\t\treferences,\n\t\t\tcanAdd: references.length < cap.max && total < maxTotal\n\t\t};\n\t});\n}\n\n/** Returns a copy of `request` with a reference appended. */\nexport function addReference(request: GenerationRequest, reference: GenerationReference): GenerationRequest {\n\treturn { ...request, references: [...(request.references ?? []), { ...reference }] };\n}\n\n/** Returns a copy of `request` without the reference `id` in `role`. */\nexport function removeReference(request: GenerationRequest, role: ReferenceRole, id: string): GenerationRequest {\n\tconst references = (request.references ?? []).filter((ref) => !(ref.role === role && ref.id === id));\n\tconst next: GenerationRequest = { ...request, references };\n\tif (references.length === 0) delete next.references;\n\treturn next;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/generation/index.cjs","../../src/generation/capabilities.ts","../../src/generation/plan.ts","../../src/generation/schema.ts","../../src/generation/hash.ts","../../src/generation/tools.ts","../../src/generation/provenance.ts","../../src/generation/batch.ts","../../src/generation/progress.ts","../../src/generation/controls.ts","../../src/generation/assets.ts","../../src/generation/record.ts","../../src/generation/store.ts"],"names":["findModel","list","clone"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACF,yDAA8B;AAC9B;AACA;ACcO,IAAM,0BAAA,EAA4B,0CAAA;AAGlC,SAAS,mBAAA,CAAoB,KAAA,EAAwC;AAC3E,EAAA,OAAO,OAAO,MAAA,IAAU,SAAA,GAAY,KAAA,CAAM,OAAA,GAAU,GAAA,GAAM,yBAAA,CAA0B,IAAA,CAAK,KAAK,CAAA;AAC/F;AAGO,IAAM,gBAAA,EAAmD;AAAA,EAC/D,aAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACD,CAAA;AAGO,IAAM,sBAAA,EAAwE;AAAA,EACpF,WAAA,EAAa,aAAA;AAAA,EACb,SAAA,EAAW,WAAA;AAAA,EACX,SAAA,EAAW,iBAAA;AAAA,EACX,aAAA,EAAe,qBAAA;AAAA,EACf,cAAA,EAAgB,sBAAA;AAAA,EAChB,IAAA,EAAM,MAAA;AAAA,EACN,SAAA,EAAW,iBAAA;AAAA,EACX,SAAA,EAAW,iBAAA;AAAA,EACX,SAAA,EAAW;AACZ,CAAA;AAQO,IAAM,uBAAA,EAAyE;AAAA,EACrF,SAAA,EAAW,WAAA;AAAA,EACX,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO,WAAA;AAAA,EACP,SAAA,EAAW,eAAA;AAAA,EACX,UAAA,EAAY,gBAAA;AAAA,EACZ,aAAA,EAAe,aAAA;AAAA,EACf,aAAA,EAAe,aAAA;AAAA,EACf,WAAA,EAAa,aAAA;AAAA,EACb,WAAA,EAAa,WAAA;AAAA,EACb,YAAA,EAAc,WAAA;AAAA,EACd,UAAA,EAAY,WAAA;AAAA,EACZ,WAAA,EAAa,WAAA;AAAA,EACb,eAAA,EAAiB,eAAA;AAAA,EACjB,gBAAA,EAAkB,gBAAA;AAAA,EAClB,WAAA,EAAa,WAAA;AAAA,EACb,WAAA,EAAa,WAAA;AAAA,EACb,WAAA,EAAa,WAAA;AAAA,EACb,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO;AACR,CAAA;AAEA,IAAM,cAAA,EAAgB,CAAC,IAAA,EAAA,GACrB,eAAA,CAAsC,QAAA,CAAS,IAAI,CAAA;AAG9C,SAAS,kBAAA,CAAmB,IAAA,EAAqB,IAAA,EAAkC;AACzF,EAAA,MAAM,SAAA,kBAAW,IAAA,2BAAM,UAAA,6BAAY,KAAA,qBAAM,IAAI,CAAA,6BAAG,OAAA;AAChD,EAAA,GAAA,CAAI,QAAA,EAAU,OAAO,QAAA;AACrB,EAAA,GAAA,CAAI,aAAA,CAAc,IAAI,CAAA,EAAG,OAAO,qBAAA,CAAsB,IAAI,CAAA;AAC1D,EAAA,MAAM,MAAA,EAAQ,MAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA,CAAE,IAAA,CAAK,CAAA;AACvD,EAAA,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,CAAY,EAAA,EAAI,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA;AACrD;AAOO,SAAS,sBAAA,CAAuB,IAAA,EAAc,IAAA,EAAyC;AAC7F,EAAA,MAAM,KAAA,EAAO,MAAA,CAAO,IAAI,CAAA,CAAE,IAAA,CAAK,CAAA;AAC/B,EAAA,GAAA,CAAI,KAAA,EAAO,CAAC,iBAAC,IAAA,qBAAK,UAAA,6BAAY,KAAA,qBAAM,IAAI,IAAA,EAAI,aAAA,CAAc,IAAI,CAAA,EAAG,OAAO,IAAA;AACxE,EAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,WAAA,CAAY,CAAA;AAC/B,EAAA,GAAA,iBAAI,IAAA,6BAAM,UAAA,6BAAY,KAAA,uBAAM,KAAK,GAAA,EAAG,OAAO,KAAA;AAC3C,EAAA,GAAA,CAAI,CAAC,KAAA,GAAQ,aAAA,CAAc,KAAK,CAAA,EAAG,OAAO,KAAA;AAC1C,EAAA,wBAAO,sBAAA,CAAuB,KAAK,CAAA,UAAA,CAAM,aAAA,CAAc,KAAK,EAAA,EAAI,MAAA,EAAQ,IAAA,GAAA;AACzE;AAQO,SAAS,gBAAA,CAAiB,SAAA,EAA+B,OAAA,EAAiD;AAChH,EAAA,GAAA,CAAI,CAAC,QAAA,GAAW,OAAA,CAAQ,OAAA,IAAW,CAAA,EAAG,OAAO,IAAA;AAC7C,EAAA,GAAA,CAAI,CAAC,SAAA,EAAW,OAAO,KAAA;AACvB,EAAA,MAAM,KAAA,EAAO,SAAA,CAAU,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,CAAE,IAAA,CAAK,CAAA,CAAE,WAAA,CAAY,CAAA;AACxD,EAAA,MAAM,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA;AAC9B,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,CAAC,KAAA,EAAA,GAAU;AAC9B,IAAA,MAAM,QAAA,EAAU,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,WAAA,CAAY,CAAA;AACzC,IAAA,GAAA,CAAI,QAAA,IAAY,IAAA,GAAO,QAAA,IAAY,MAAA,GAAS,QAAA,IAAY,IAAA,EAAM,OAAO,IAAA;AACrE,IAAA,GAAA,CAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,EAAG,OAAO,QAAA,IAAY,IAAA;AAC/C,IAAA,GAAA,CAAI,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,CAAA,CAAE,EAAA,IAAM,IAAA;AAC5D,IAAA,OAAO,KAAA;AAAA,EACR,CAAC,CAAA;AACF;AAoLO,IAAM,0BAAA,EAA4B;AAAA,EACxC,OAAA;AAAA,EACA,QAAA;AAAA,EACA,gBAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,YAAA;AAAA,EACA;AACD,CAAA;AAGO,SAAS,mBAAA,CAAoB,KAAA,EAA8E;AACjH,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,IAAA;AACnB,EAAA,OAAO,MAAA,IAAU,KAAA,EAAO,CAAC,EAAA,EAAI,KAAA;AAC9B;AAGO,SAAS,cAAA,CAAe,KAAA,EAAoE;AAClG,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,IAAA;AACnB,EAAA,OAAO,MAAA,IAAU,KAAA,EAAO,CAAC,EAAA,EAAI,KAAA;AAC9B;AAMO,SAAS,uBAAA,CAAwB,IAAA,EAA0C;AACjF,EAAA,MAAM,MAAA,mCAAQ,IAAA,uBAAK,UAAA,+BAAY,OAAA,UAAS,CAAC,GAAA;AACzC,EAAA,MAAM,QAAA,EAA2B,eAAA,CAAgB,MAAA,CAAO,CAAC,IAAA,EAAA,GAAA,kCAAU,KAAA,uBAAM,IAAI,CAAA,+BAAG,KAAA,UAAO,GAAA,EAAA,EAAK,CAAC,CAAA;AAC7F,EAAA,MAAM,OAAA,EAAS,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA;AAAA,IACjC,CAAC,IAAA,EAAA,GAAS,CAAC,aAAA,CAAc,IAAI,EAAA,GAAK,mBAAA,CAAoB,IAAI,EAAA,GAAA,kCAAM,KAAA,uBAAM,IAAI,CAAA,+BAAG,KAAA,UAAO,GAAA,EAAA,EAAK;AAAA,EAC1F,CAAA;AACA,EAAA,OAAO,CAAC,GAAG,OAAA,EAAS,GAAG,MAAM,CAAA;AAC9B;AAGO,SAAS,cAAA,CAAe,IAAA,EAA2C;AACzE,EAAA,MAAM,MAAA,EAA0B,CAAC,UAAU,CAAA;AAC3C,EAAA,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA;AACnC,EAAA,GAAA,CAAI,IAAA,CAAK,UAAA,EAAY,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA;AACzC,EAAA,OAAO,KAAA;AACR;AAEA,IAAM,kBAAA,EAAoB,CAAC,MAAA,EAAQ,aAAA,EAAe,QAAA,EAAU,MAAA,EAAQ,OAAO,CAAA;AAcpE,SAAS,iBAAA,CACf,MAAA,EACA,QAAA,EAAwC,CAAC,CAAA,EACnB;AACtB,EAAA,MAAM,YAAA,EAAc,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,KAAA,EAAA,GAAU,CAAC,KAAA,CAAM,EAAA,EAAI,KAAK,CAAC,CAAC,CAAA;AACrE,EAAA,MAAM,UAAA,EAAY,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAA,GAAU,KAAA,CAAM,EAAE,CAAC,CAAA;AACzD,EAAA,MAAM,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAA,GAAU;AACpC,IAAA,MAAM,MAAA,EAAQ,WAAA,CAAY,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACtC,IAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,EAAE,GAAG,MAAM,CAAA;AAC9B,IAAA,MAAM,KAAA,EAA0B,EAAE,GAAG,MAAM,CAAA;AAC3C,IAAA,IAAA,CAAA,MAAW,IAAA,GAAO,iBAAA,EAAmB;AACpC,MAAA,GAAA,CAAI,KAAA,CAAM,GAAG,EAAA,IAAM,KAAA,CAAA,EAAY,IAAA,CAAa,GAAG,EAAA,EAAI,KAAA,CAAM,GAAG,CAAA;AAAA,IAC7D;AACA,IAAA,OAAO,IAAA;AAAA,EACR,CAAC,CAAA;AACD,EAAA,IAAA,CAAA,MAAW,MAAA,GAAS,OAAA,EAAS;AAC5B,IAAA,GAAA,CAAI,SAAA,CAAU,GAAA,CAAI,KAAA,CAAM,EAAE,EAAA,GAAK,CAAC,KAAA,CAAM,KAAA,EAAO,QAAA;AAC7C,IAAA,SAAA,CAAU,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AACtB,IAAA,MAAA,CAAO,IAAA,CAAK,EAAE,GAAI,MAA4B,CAAC,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,MAAA;AACR;ADzPA;AACA;AElDA,IAAM,QAAA,EAAU,IAAA;AAGT,SAAS,eAAA,CAAgB,KAAA,EAAmC;AAClE,EAAA,MAAM,MAAA,EAAQ,qDAAA,CAAsD,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AACtF,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,KAAA,CAAA;AACnB,EAAA,MAAM,EAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AACzB,EAAA,MAAM,EAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AACzB,EAAA,GAAA,CAAI,CAAA,CAAE,EAAA,EAAI,CAAA,EAAA,GAAM,CAAA,CAAE,EAAA,EAAI,CAAA,CAAA,EAAI,OAAO,KAAA,CAAA;AACjC,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,CAAC,CAAA;AACtB;AAMO,SAAS,cAAA,CAAe,KAAA,EAAmC;AACjE,EAAA,MAAM,KAAA,EAAO,MAAA,CAAO,KAAK,CAAA,CAAE,IAAA,CAAK,CAAA,CAAE,WAAA,CAAY,CAAA;AAC9C,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,MAAA,EAAQ,6CAAA,CAA8C,IAAA,CAAK,IAAI,CAAA;AACnE,EAAA,GAAA,CAAI,KAAA,EAAO,OAAA,EAAS,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,EAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EAAA,KAAA,GAAA,CAC5C,MAAA,EAAQ,oBAAA,CAAqB,IAAA,CAAK,IAAI,CAAA,EAAI,OAAA,EAAU,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,EAAA,GAAK,EAAA,EAAI,GAAA,EAAM,CAAA;AAAA,EAAA,KAAA,GAAA,CAClF,MAAA,EAAQ,oBAAA,CAAqB,IAAA,CAAK,IAAI,CAAA,EAAI,OAAA,EAAA,CAAU,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,EAAA,EAAI,IAAA,EAAA,GAAS,CAAA;AAC1F,EAAA,OAAO,OAAA,GAAU,OAAA,EAAS,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,MAAM,EAAA,EAAI,KAAA,CAAA;AAClD;AAGO,SAAS,oBAAA,CAAqB,KAAA,EAAmC;AACvE,EAAA,MAAM,KAAA,EAAO,cAAA,CAAe,KAAK,CAAA;AACjC,EAAA,OAAO,KAAA,IAAS,KAAA,EAAA,EAAY,KAAA,EAAA,EAAY,IAAA,CAAK,GAAA,CAAI,IAAI,EAAA,EAAI,GAAA;AAC1D;AAGO,IAAM,eAAA,EAAmD;AAAA,EAC/D,KAAA,EAAO,CAAA;AAAA,EACP,OAAA,EAAS,CAAA;AAAA,EACT,GAAA,EAAK,CAAA;AAAA,EACL,IAAA,EAAM,CAAA;AAAA,EACN,KAAA,EAAO,CAAA;AAAA,EACP,QAAA,EAAU,CAAA;AAAA,EACV,MAAA,EAAQ,CAAA;AAAA,EACR,MAAA,EAAQ,CAAA;AAAA,EACR,QAAA,EAAU,CAAA;AAAA,EACV,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,CAAA;AAAA,EACJ,OAAA,EAAS,CAAA;AAAA,EACT,KAAA,EAAO,CAAA;AAAA,EACP,GAAA,EAAK,CAAA;AAAA,EACL,IAAA,EAAM;AACP,CAAA;AAEO,SAAS,WAAA,CAAY,KAAA,EAAmC;AAC9D,EAAA,OAAO,cAAA,CAAe,MAAA,CAAO,KAAK,CAAA,CAAE,IAAA,CAAK,CAAA,CAAE,WAAA,CAAY,CAAC,CAAA;AACzD;AAEA,IAAM,WAAA,EAAa,CAAC,KAAA,EAAA,GAAoB,OAAO,MAAA,IAAU,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,KAAK,EAAA,EAAI,MAAA,EAAQ,KAAA,CAAA;AAM/F,SAAS,YAAA,CACf,KAAA,EACA,OAAA,EACA,IAAA,EACA,GAAA,EACgB;AAChB,EAAA,MAAM,OAAA,EAAS,IAAA,CAAK,KAAK,CAAA;AACzB,EAAA,GAAA,CAAI,OAAA,IAAW,KAAA,CAAA,EAAW,OAAO,KAAA,CAAA;AACjC,EAAA,IAAI,IAAA;AACJ,EAAA,IAAA,CAAA,MAAW,OAAA,GAAU,OAAA,EAAS;AAC7B,IAAA,MAAM,EAAA,EAAI,IAAA,CAAK,MAAM,CAAA;AACrB,IAAA,GAAA,CAAI,EAAA,IAAM,KAAA,CAAA,EAAW,QAAA;AACrB,IAAA,MAAM,SAAA,EAAW,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,MAAM,CAAA;AACpC,IAAA,GAAA,CACC,CAAC,KAAA,GACD,SAAA,EAAW,IAAA,CAAK,SAAA,EAAW,QAAA,GAC1B,IAAA,CAAK,GAAA,CAAI,SAAA,EAAW,IAAA,CAAK,QAAQ,EAAA,GAAK,QAAA,GAAA,CAAY,IAAA,IAAQ,QAAA,EAAU,EAAA,EAAI,IAAA,CAAK,KAAA,EAAO,EAAA,EAAI,IAAA,CAAK,IAAA,CAAA,EAC7F;AACD,MAAA,KAAA,EAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,IAAA,EAAM,EAAE,CAAA;AAAA,IACpC;AAAA,EACD;AACA,EAAA,uBAAO,IAAA,+BAAM,QAAA;AACd;AAGA,SAAS,YAAA,CAAa,IAAA,EAAc,GAAA,EAAqB;AACxD,EAAA,GAAA,CAAI,IAAA,CAAK,OAAA,GAAU,GAAA,EAAK,OAAO,IAAA;AAC/B,EAAA,IAAI,IAAA,EAAM,GAAA;AACV,EAAA,MAAM,KAAA,EAAO,IAAA,CAAK,UAAA,CAAW,IAAA,EAAM,CAAC,CAAA;AACpC,EAAA,GAAA,CAAI,IAAA,EAAM,EAAA,GAAK,KAAA,GAAQ,MAAA,GAAU,KAAA,GAAQ,KAAA,EAAQ,IAAA,GAAO,CAAA;AACxD,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AACzB;AAEA,SAAS,YAAA,CAAa,OAAA,EAA+C;AACpE,EAAA,MAAM,KAAA,EAA0B,EAAE,GAAG,QAAQ,CAAA;AAC7C,EAAA,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,IAAA,CAAK,WAAA,EAAa,OAAA,CAAQ,UAAA,CAAW,GAAA,CAAI,CAAC,GAAA,EAAA,GAAA,CAAS,EAAE,GAAG,IAAI,CAAA,CAAE,CAAA;AACtF,EAAA,GAAA,CAAI,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,OAAA,EAAS,EAAE,GAAG,OAAA,CAAQ,OAAO,CAAA;AACtD,EAAA,OAAO,IAAA;AACR;AAEA,IAAM,SAAA,EAAW,CAAC,KAAA,EAAA,GAAoB,OAAO,MAAA,IAAU,SAAA,EAAW,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAA,EAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACrG,IAAM,KAAA,EAAO,CAAC,OAAA,EAAA,GAAgC,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAE7E,IAAM,QAAA,YAAN,MAAc;AAAA,EAMb,WAAA,CACU,IAAA,EACA,OAAA,EACA,OAAA,EACA,QAAA,EACR;AAJQ,IAAA,IAAA,CAAA,KAAA,EAAA,IAAA;AACA,IAAA,IAAA,CAAA,QAAA,EAAA,OAAA;AACA,IAAA,IAAA,CAAA,QAAA,EAAA,OAAA;AACA,IAAA,IAAA,CAAA,SAAA,EAAA,QAAA;AAET,IAAA,IAAA,CAAK,IAAA,EAAM,SAAA,IAAa,YAAA,EAAc,QAAA,EAAU,QAAA;AAChD,IAAA,IAAA,CAAK,OAAA,EAAS,SAAA,IAAa,OAAA;AAAA,EAC5B;AAAA,EAPU;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,iBATD,YAAA,EAAgC,CAAC,EAAA;AAAA,kBACjC,OAAA,EAAsB,CAAC,EAAA;AAAA,EACvB;AAAA,EACA;AAAA,EAYT,MAAA,CAAO,KAAA,EAAe,IAAA,EAAe,EAAA,EAAa,IAAA,EAA0B,MAAA,EAAgB;AAC3F,IAAA,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,EAAE,KAAA,EAAO,IAAA,EAAM,EAAA,EAAI,IAAA,EAAM,OAAO,CAAC,CAAA;AAAA,EACxD;AAAA,EAEA,KAAA,CAAM,KAAA,EAAe,KAAA,EAAgB,OAAA,EAAiB;AACrD,IAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,KAAA,EAAO,QAAQ,CAAC,CAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,WAAA,CAAY,KAAA,EAAgC,OAAA,EAAiB;AAC5D,IAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA;AAChC,IAAA,GAAA,CAAI,IAAA,CAAK,MAAA,EAAQ;AAChB,MAAA,IAAA,CAAK,KAAA,CAAM,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAChC,MAAA,MAAA;AAAA,IACD;AACA,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA;AACzB,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA,EAAO,KAAA,EAAO,KAAA,CAAA,EAAW,SAAA,EAAW,OAAO,CAAA;AAAA,EACxD;AAAA,EAEA,SAAA,CAAU,KAAA,EAAe,KAAA,EAAgB,GAAA,EAAiB;AACzD,IAAA,GAAA,CAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,aAAA,GAAgB,MAAA,IAAU,KAAA,CAAA,EAAW,MAAA;AACvD,IAAA,GAAA,CAAI,CAAA;AACJ,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA,EAAO,KAAA,CAAA,EAAW,KAAA,EAAO,WAAA,EAAa,gCAAgC,CAAA;AAAA,EACnF;AAAA,EAEA,QAAA,CAAS,KAAA,EAAqD,SAAA,EAA+B,QAAA,EAAmB;AAC/G,IAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA;AAChC,IAAA,GAAA,CAAI,MAAA,IAAU,KAAA,EAAA,GAAa,UAAA,IAAc,KAAA,CAAA,EAAW,MAAA;AACpD,IAAA,GAAA,CAAI,OAAO,MAAA,IAAU,QAAA,EAAU;AAC9B,MAAA,IAAA,CAAK,KAAA,CAAM,KAAA,EAAO,KAAA,EAAO,CAAA,cAAA,CAAgB,CAAA;AACzC,MAAA,MAAA;AAAA,IACD;AACA,IAAA,MAAM,KAAA,mBAAO,QAAA,UAAY,OAAA;AACzB,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,GAAU,SAAA,EAAW,MAAA;AAC9B,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,GAAU,SAAA,IAAa,KAAA,CAAA,EAAW;AAC1C,MAAA,IAAA,CAAK,KAAA;AAAA,QACJ,KAAA;AAAA,QACA,IAAA,CAAK,MAAA;AAAA,QACL,CAAA,EAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACK,IAAA;AACA,IAAA;AACN,EAAA;AAEA,EAAA;AAMC,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACK,IAAA;AACA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAGD,IAAA;AACD,EAAA;AAAA;AAGA,EAAA;AAMK,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AAGA,MAAA;AACD,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACA,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACJ,IAAA;AACI,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACI,IAAA;AACL,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACI,IAAA;AACL,EAAA;AAEA,EAAA;AACC,IAAA;AACI,IAAA;AACJ,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACI,IAAA;AACL,EAAA;AAEA,EAAA;AACC,IAAA;AACI,IAAA;AACJ,IAAA;AACK,IAAA;AACJ,MAAA;AACD,IAAA;AACD,EAAA;AAEA,EAAA;AACC,IAAA;AACI,IAAA;AACA,IAAA;AACJ,IAAA;AACD,EAAA;AAAA;AAGA,EAAA;AACC,IAAA;AACK,IAAA;AACL,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAAK,UAAA;AACc,UAAA;AAClB,UAAA;AACA,UAAA;AACA,UAAA;AAED,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACK,IAAA;AACN,EAAA;AAAA;AAGA,EAAA;AACC,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AAGA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACC,YAAA;AACA,YAAA;AAAA,UAAA;AAED,UAAA;AAAK,YAAA;AACkB,YAAA;AAClB,YAAA;AACJ,YAAA;AACA,YAAA;AACyD,UAAA;AAE1D,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAA0F,QAAA;AAE1F,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAEA,IAAA;AACC,MAAA;AAIA,MAAA;AAA6E,MAAA;AAE9E,IAAA;AAGA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACC,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AAAK,YAAA;AACc,YAAA;AACZ,YAAA;AACF,YAAA;AACJ,YAAA;AACiC,UAAA;AAEnC,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AAGJ,IAAA;AACI,IAAA;AACH,MAAA;AACC,QAAA;AACD,MAAA;AACC,QAAA;AAAK,UAAA;AACJ,UAAA;AACK,UAAA;AACL,UAAA;AACA,UAAA;AAED,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AAEI,IAAA;AACH,MAAA;AACD,IAAA;AAEI,IAAA;AACC,IAAA;AACJ,MAAA;AAA+C,MAAA;AAEhD,IAAA;AACD,EAAA;AAEA,EAAA;AACC,IAAA;AACK,IAAA;AACJ,MAAA;AAAwE,MAAA;AAExE,MAAA;AACD,IAAA;AACA,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACH,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AAAwC,MAAA;AAEzC,IAAA;AACI,IAAA;AACH,MAAA;AACC,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AACC,IAAA;AACN,EAAA;AAEY,EAAA;AACX,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AAAyE,MAAA;AAExE,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AAEI,IAAA;AACH,MAAA;AACD,IAAA;AACA,IAAA;AAGA,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AAIK,IAAA;AACL,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AAEK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACD;AAYgB;AAKT,EAAA;AACA,EAAA;AACN,EAAA;AACO,EAAA;AACF,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AF5CS;AACA;AGrpBA;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACI,IAAA;AACJ,IAAA;AACD,EAAA;AACD;AAES;AACF,EAAA;AACF,EAAA;AACH,IAAA;AACD,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACN,IAAA;AACA,IAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACL,EAAA;AACD;AASgB;AACT,EAAA;AACL,IAAA;AACD,EAAA;AACM,EAAA;AAEA,EAAA;AACA,EAAA;AACN,EAAA;AACC,IAAA;AACA,IAAA;AAII,IAAA;AACA,IAAA;AACL,EAAA;AACI,EAAA;AAEE,EAAA;AACF,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACD,EAAA;AAEI,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACH,IAAA;AACA,IAAA;AAGI,IAAA;AACL,EAAA;AAEI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AAGG,MAAA;AACA,MAAA;AACI,MAAA;AACA,MAAA;AACL,IAAA;AACE,IAAA;AACL,EAAA;AAEM,EAAA;AACF,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACA,MAAA;AACL,IAAA;AACD,EAAA;AAEM,EAAA;AACF,EAAA;AACH,IAAA;AACD,EAAA;AAEM,EAAA;AACF,EAAA;AACA,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AAGA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAAY,UAAA;AACkD,UAAA;AACzB,UAAA;AACd,UAAA;AAC8E,UAAA;AAErG,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AACL,EAAA;AAEM,EAAA;AACF,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAEO,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACJ,IAAA;AACD,EAAA;AACD;AH2nBS;AACA;AIzyBO;AACX,EAAA;AACA,EAAA;AACH,IAAA;AAIA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAEM;AAQU;AACX,EAAA;AACA,EAAA;AACJ,EAAA;AACC,IAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACN,EAAA;AAEK,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAEM;AAOU;AACT,EAAA;AACN,EAAA;AACM,IAAA;AACN,EAAA;AACO,EAAA;AACR;AJsxBS;AACA;AKxyBH;AAGG;AACF,EAAA;AACN,EAAA;AACC,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAEM;AACC,EAAA;AACN,EAAA;AAEA,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAAY,UAAA;AACwB,UAAA;AAC7B,YAAA;AACC,YAAA;AACS,YAAA;AACF,UAAA;AACd,UAAA;AACsB,UAAA;AACiE,UAAA;AAExF,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACA,EAAA;AACD;AAEM;AACC,EAAA;AACA,EAAA;AACN,EAAA;AAED;AAES;AACD,EAAA;AACN,IAAA;AACA,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AAA8D;AAC/D,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAcgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEAA,EAAAA;AACL,IAAA;AACK,IAAA;AACL,IAAA;AACD,EAAA;AAEM,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AAEM,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAAgB,UAAA;AACT,UAAA;AAEP,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AAEM,EAAA;AACL,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAEA,QAAA;AAAa,UAAA;AACN,UAAA;AACM,YAAA;AACkG,UAAA;AAC9G,UAAA;AAED,QAAA;AACA,QAAA;AAAa,UAAA;AACL,UAAA;AACO,UAAA;AACG,UAAA;AACD,UAAA;AAEjB,QAAA;AACA,QAAA;AAAoB,UAAA;AAGL,YAAA;AACN,YAAA;AACG,YAAA;AACwB,YAAA;AACqB,YAAA;AACF,YAAA;AACX,UAAA;AAE5C,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAEA,QAAA;AACA,QAAA;AAAa,UAAA;AACL,UAAA;AACO,UAAA;AACG,UAAA;AACD,UAAA;AAEjB,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AAEI,IAAA;AACH,MAAA;AACAC,MAAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAAa,UAAA;AACL,UAAA;AACO,UAAA;AACG,UAAA;AACD,UAAA;AAEjB,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACAA,IAAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACC,QAAA;AACA,QAAA;AAAY,UAAA;AACM,UAAA;AACD,YAAA;AACT,YAAA;AACK,YAAA;AACA,YAAA;AACE,UAAA;AAEf,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACC,YAAA;AAAU,cAAA;AACc,YAAA;AACxB,UAAA;AAED,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACuC,YAAA;AACnC,YAAA;AACa,YAAA;AACA,UAAA;AAElB,QAAA;AACkB,UAAA;AACT,UAAA;AACM,UAAA;AAEf,QAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,IAAA;AACD,IAAA;AACD,EAAA;AAEO,EAAA;AACF,IAAA;AACA,IAAA;AACJ,IAAA;AACI,IAAA;AACJ,IAAA;AACC,MAAA;AAAmC,MAAA;AAEpC,IAAA;AACD,EAAA;AACD;ALywBS;AACA;AMxkCI;AACA;AA6DA;AACZ,EAAA;AACC,IAAA;AACK,IAAA;AACN,EAAA;AACD;AAEM;AACA;AAGG;AAMF,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACJ,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAIA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAGgB;AACR,EAAA;AAAY,IAAA;AAGX,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AAEA,IAAA;AACJ,EAAA;AACD;AAQgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACK,EAAA;AACA,EAAA;AACN,EAAA;AAEM,EAAA;AACA,EAAA;AACN,EAAA;AACI,EAAA;AACE,EAAA;AAEC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACL,EAAA;AACD;AAGgB;AACR,EAAA;AACR;AAEM;AAIU;AACX,EAAA;AACA,EAAA;AACC,IAAA;AACH,MAAA;AACD,IAAA;AACC,MAAA;AACD,IAAA;AACD,EAAA;AACK,EAAA;AACJ,IAAA;AACD,EAAA;AACI,EAAA;AACH,IAAA;AACD,EAAA;AACM,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACM,EAAA;AACF,EAAA;AACG,EAAA;AACF,IAAA;AACJ,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAMgB;AAKT,EAAA;AACL,IAAA;AACI,IAAA;AACJ,EAAA;AACM,EAAA;AACR;ANs+BS;AACA;AOzqCO;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AAEE,EAAA;AACL,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AAEA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,wBAAA;AACA,MAAA;AACF,IAAA;AACI,IAAA;AACA,IAAA;AACH,MAAA;AACD,IAAA;AACC,MAAA;AACD,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AAEM,EAAA;AACD,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEM,EAAA;AACD,IAAA;AACJ,IAAA;AACI,IAAA;AACA,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AAAgC,IAAA;AAEpC,IAAA;AACD,EAAA;AAEO,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACK,IAAA;AACL,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AAEA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACD;APqqCS;AACA;AQvwCI;AAEP;AAEA;AAEA;AAGU;AACT,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAQgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACC,EAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACD,EAAA;AACJ,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAQgB;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AAID,EAAA;AACD;ARmvCS;AACA;AShyCA;AACH,EAAA;AACE,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACL,EAAA;AACD;AAOgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACF,EAAA;AACM,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AAEI,EAAA;AACH,IAAA;AACA,IAAA;AACC,sBAAA;AAGD,IAAA;AACD,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACC,sBAAA;AAGG,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AAEJ,EAAA;AACC,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACX,EAAA;AACI,EAAA;AACT;AAGgB;AACT,EAAA;AACF,EAAA;AACH,IAAA;AACA,IAAA;AACI,IAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACF,EAAA;AACC,EAAA;AACE,EAAA;AACR;AAGgB;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACH,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACA,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,EAAA;AACF;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACG,EAAA;AACR;AT0wCS;AACA;AU17CO;AACV,EAAA;AACD,EAAA;AACA,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAES;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAGgB;AACT,EAAA;AACF,EAAA;AACG,EAAA;AACN,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACA,EAAA;AACF;AAiCgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACC,IAAA;AACA,IAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACC,IAAA;AACJ,MAAA;AACA,MAAA;AACC,QAAA;AAAY,UAAA;AACX,UAAA;AACA,UAAA;AACA,UAAA;AACM,UAAA;AACI,UAAA;AACE,UAAA;AAEZ,QAAA;AACD,QAAA;AACD,MAAA;AACC,QAAA;AAAY,UAAA;AACX,UAAA;AACA,UAAA;AACA,UAAA;AACM,UAAA;AACI,UAAA;AAEV,QAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AACA,EAAA;AACK,EAAA;AAGC,EAAA;AACR;AAOgB;AAIT,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACC,IAAA;AACK,IAAA;AACL,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACJ,IAAA;AACF,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAsBgB;AAKT,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACC,IAAA;AACK,IAAA;AACL,IAAA;AACA,IAAA;AACC,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACI,MAAA;AAGJ,MAAA;AACA,IAAA;AACF,EAAA;AACI,EAAA;AACH,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,MAAA;AACF,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACO,EAAA;AACR;AV62CS;AACA;AW7lDI;AACA;AA+ET;AAEY;AACT,EAAA;AACF,EAAA;AACJ,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAEMC;AAEG;AACD,EAAA;AACN,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACC,EAAA;AACH;AAES;AACF,EAAA;AACN,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACA,EAAA;AACF;AAWgB;AACT,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACA,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AACC,EAAA;AACJ,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACD,IAAA;AACC,MAAA;AACA,MAAA;AACI,MAAA;AACJ,IAAA;AACF,EAAA;AACM,EAAA;AACF,IAAA;AACH,IAAA;AACI,IAAA;AACJ,EAAA;AACK,EAAA;AACC,EAAA;AACN,IAAA;AACA,IAAA;AACI,IAAA;AACA,I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,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAEM;AAWU;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AAEE,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AAEA,EAAA;AACD,IAAA;AACJ,IAAA;AACI,IAAA;AACA,IAAA;AACH,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACD,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AACL,IAAA;AACA,IAAA;AACD,EAAA;AAEM,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEM,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACJ,IAAA;AACA,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAA0C,MAAA;AAEzC,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACI,IAAA;AAEJ,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEO,EAAA;AACD,IAAA;AAEH,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACG,IAAA;AAEH,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AAEE,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACD,QAAA;AAEA,QAAA;AACD,MAAA;AACA,MAAA;AACA,IAAA;AACF,IAAA;AAEE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACF,IAAA;AAEE,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACF,IAAA;AAEE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACF,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACD;AZq7DS;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/generation/index.cjs","sourcesContent":[null,"/**\n * Declarative descriptions of generative models (what a model accepts) and of\n * the requests sent to them. Everything here is plain data so capabilities can\n * come from a server, be merged with local presentation overlays, and be\n * turned into JSON Schema for agents.\n */\n\nexport type GenerationMediaKind = 'image' | 'video' | 'audio' | 'text';\n\n/** The reference roles this library knows by name (labels, aliases, schema order). */\nexport type BuiltinReferenceRole =\n\t| 'start_frame'\n\t| 'end_frame'\n\t| 'style_ref'\n\t| 'character_ref'\n\t| 'background_ref'\n\t| 'mask'\n\t| 'image_ref'\n\t| 'video_ref'\n\t| 'audio_ref';\n\n/**\n * What a reference input (an asset) is used for. The built-in roles are\n * listed in {@link REFERENCE_ROLES}; models may declare any other role name\n * (e.g. `subject`, `pose`) in `references.roles`, which makes it valid for\n * that model. Role names match {@link REFERENCE_ROLE_NAME_REGEX}.\n */\nexport type ReferenceRole = BuiltinReferenceRole | (string & {});\n\n/** Valid role names: a letter, then letters, digits, `_` or `-`, not ending in `_` or `-`. */\nexport const REFERENCE_ROLE_NAME_REGEX = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/;\n\n/** True for a string usable as a role name. */\nexport function isReferenceRoleName(value: unknown): value is ReferenceRole {\n\treturn typeof value === 'string' && value.length <= 64 && REFERENCE_ROLE_NAME_REGEX.test(value);\n}\n\n/** Every built-in reference role, in a stable display order. */\nexport const REFERENCE_ROLES: readonly BuiltinReferenceRole[] = [\n\t'start_frame',\n\t'end_frame',\n\t'style_ref',\n\t'character_ref',\n\t'background_ref',\n\t'mask',\n\t'image_ref',\n\t'video_ref',\n\t'audio_ref'\n];\n\n/** Human-readable default labels for reference roles. */\nexport const REFERENCE_ROLE_LABELS: Readonly<Record<BuiltinReferenceRole, string>> = {\n\tstart_frame: 'Start frame',\n\tend_frame: 'End frame',\n\tstyle_ref: 'Style reference',\n\tcharacter_ref: 'Character reference',\n\tbackground_ref: 'Background reference',\n\tmask: 'Mask',\n\timage_ref: 'Image reference',\n\tvideo_ref: 'Video reference',\n\taudio_ref: 'Audio reference'\n};\n\n/**\n * Friendly role names accepted wherever a role is written by hand (asset\n * mentions such as `@fox:style`, agent calls). A model that declares the\n * alias itself (e.g. its own `style` role) keeps it; otherwise the alias maps\n * to the built-in role. Keys are lower case.\n */\nexport const REFERENCE_ROLE_ALIASES: Readonly<Record<string, BuiltinReferenceRole>> = {\n\treference: 'image_ref',\n\timage: 'image_ref',\n\tstyle: 'style_ref',\n\tcharacter: 'character_ref',\n\tbackground: 'background_ref',\n\t'start-frame': 'start_frame',\n\t'first-frame': 'start_frame',\n\tfirst_frame: 'start_frame',\n\t'end-frame': 'end_frame',\n\t'last-frame': 'end_frame',\n\tlast_frame: 'end_frame',\n\t'style-ref': 'style_ref',\n\t'character-ref': 'character_ref',\n\t'background-ref': 'background_ref',\n\t'image-ref': 'image_ref',\n\t'video-ref': 'video_ref',\n\t'audio-ref': 'audio_ref',\n\tvideo: 'video_ref',\n\taudio: 'audio_ref'\n};\n\nconst isBuiltinRole = (role: string): role is BuiltinReferenceRole =>\n\t(REFERENCE_ROLES as readonly string[]).includes(role);\n\n/** Display label of a role: the model's label, the built-in label, or the name made readable. */\nexport function referenceRoleLabel(role: ReferenceRole, caps?: ModelCapabilities): string {\n\tconst declared = caps?.references?.roles[role]?.label;\n\tif (declared) return declared;\n\tif (isBuiltinRole(role)) return REFERENCE_ROLE_LABELS[role];\n\tconst words = String(role).replace(/[_-]+/g, ' ').trim();\n\treturn words.charAt(0).toUpperCase() + words.slice(1);\n}\n\n/**\n * Canonical role for a hand-written name: the name itself when the model\n * declares it (or, without a model, when it is built in), else its alias\n * ({@link REFERENCE_ROLE_ALIASES}, case-insensitive), else the trimmed name.\n */\nexport function normalizeReferenceRole(role: string, caps?: ModelCapabilities): ReferenceRole {\n\tconst name = String(role).trim();\n\tif (caps ? !!caps.references?.roles[name] : isBuiltinRole(name)) return name;\n\tconst lower = name.toLowerCase();\n\tif (caps?.references?.roles[lower]) return lower;\n\tif (!caps && isBuiltinRole(lower)) return lower;\n\treturn REFERENCE_ROLE_ALIASES[lower] ?? (isBuiltinRole(lower) ? lower : name);\n}\n\n/**\n * Whether `mediaType` (a MIME type such as `image/png`, or a media kind such\n * as `image`) matches one of `accepts` (`image`, `image/*`, `image/png`).\n * An empty or missing `accepts` accepts everything; an unknown media type\n * only matches a missing `accepts`.\n */\nexport function mediaTypeMatches(mediaType: string | undefined, accepts: readonly string[] | undefined): boolean {\n\tif (!accepts || accepts.length === 0) return true;\n\tif (!mediaType) return false;\n\tconst type = mediaType.split(';')[0].trim().toLowerCase();\n\tconst kind = type.split('/')[0];\n\treturn accepts.some((entry) => {\n\t\tconst pattern = entry.trim().toLowerCase();\n\t\tif (pattern === '*' || pattern === '*/*' || pattern === type) return true;\n\t\tif (!pattern.includes('/')) return pattern === kind;\n\t\tif (pattern.endsWith('/*')) return pattern.slice(0, -2) === kind;\n\t\treturn false;\n\t});\n}\n\n/** A choice from a fixed list. `options` order matters (low → high where it applies). */\nexport interface EnumCapability<T extends string | number = string> {\n\toptions: readonly T[];\n\tdefault?: T;\n\t/** Display labels by option value. */\n\tlabels?: Readonly<Record<string, string>>;\n}\n\nexport interface TextFieldCapability {\n\tmaxLength?: number;\n}\n\nexport interface PromptCapability extends TextFieldCapability {\n\t/**\n\t * Count the resolved prompt (templates expanded, variables substituted)\n\t * against `maxLength` rather than the raw template text. Default `true`.\n\t * Pass the resolved text to the planner as `resolvedPrompt`.\n\t */\n\tcountResolved?: boolean;\n\t/** The prompt must be non-empty. */\n\trequired?: boolean;\n}\n\nexport interface CountCapability {\n\t/** Largest number of outputs per request. */\n\tmax: number;\n\t/** Predefined choices; when given, requests snap to one of them. */\n\toptions?: readonly number[];\n\tdefault?: number;\n}\n\nexport interface DurationCapability {\n\t/** Seconds. */\n\tmin?: number;\n\tmax?: number;\n\t/** Predefined choices; when given, requests snap to one of them. */\n\toptions?: readonly number[];\n\tdefault?: number;\n\tstep?: number;\n}\n\nexport interface SeedCapability {\n\tmin?: number;\n\tmax?: number;\n}\n\nexport interface ReferenceRoleCapability {\n\t/** Most references of this role. */\n\tmax: number;\n\t/** Fewest references of this role (e.g. an image-to-video model needs a `start_frame`). */\n\tmin?: number;\n\tlabel?: string;\n\tdescription?: string;\n\t/**\n\t * Media accepted for this role: kinds (`image`), wildcards (`image/*`) or\n\t * MIME types (`image/png`). Absent: anything. References whose\n\t * `mediaType` does not match are dropped or rejected by the planner.\n\t */\n\taccepts?: readonly string[];\n}\n\nexport interface ReferenceCapability {\n\t/** Supported roles and their per-role limits. Roles not listed are unsupported. */\n\troles: Partial<Readonly<Record<ReferenceRole, ReferenceRoleCapability>>>;\n\t/** Most references across all roles. */\n\tmaxTotal?: number;\n}\n\nexport interface NumberParamSpec {\n\ttype: 'number' | 'integer';\n\tmin?: number;\n\tmax?: number;\n\tstep?: number;\n\tdefault?: number;\n\tlabel?: string;\n\tdescription?: string;\n}\n\nexport interface EnumParamSpec {\n\ttype: 'enum';\n\toptions: readonly (string | number)[];\n\tdefault?: string | number;\n\tlabel?: string;\n\tdescription?: string;\n}\n\nexport interface BooleanParamSpec {\n\ttype: 'boolean';\n\tdefault?: boolean;\n\tlabel?: string;\n\tdescription?: string;\n}\n\n/** A model-specific parameter (`guidance`, `motion_strength`, `style`, …). */\nexport type ExtraParamSpec = NumberParamSpec | EnumParamSpec | BooleanParamSpec;\n\n/** Rough timing used for progress estimates (`expectedDurationFor`). */\nexport interface TimingCapability {\n\t/** Fixed cost of one request in ms. */\n\tbaseMs: number;\n\t/** Extra ms per output beyond the first. */\n\tperItemMs?: number;\n\t/** Extra ms per second of requested duration (video/audio). */\n\tperSecondMs?: number;\n\t/** Extra ms per megapixel of the requested resolution. */\n\tperMegapixelMs?: number;\n}\n\n/** Presentation-only fields; overlays may set these (see `mergeCapabilities`). */\nexport interface CapabilityPresentation {\n\tname?: string;\n\tdescription?: string;\n\tbadges?: readonly string[];\n\ticon?: string;\n\tgroup?: string;\n}\n\n/** Everything a generative model accepts. Absent sections mean \"unsupported\". */\nexport interface ModelCapabilities extends CapabilityPresentation {\n\tid: string;\n\tmedia: GenerationMediaKind;\n\tprompt?: PromptCapability;\n\t/** Negative prompt support (`true` or limits). */\n\tnegativePrompt?: boolean | TextFieldCapability;\n\t/** System prompt support (`true` or limits). */\n\tsystemPrompt?: boolean | TextFieldCapability;\n\t/** E.g. `['1:1', '16:9', '9:16']`. */\n\taspectRatios?: EnumCapability<string>;\n\t/** E.g. `['512x512', '1024x1024']`, `['720p', '1080p']` or `['1k', '2k', '4k']`, low → high. */\n\tresolutions?: EnumCapability<string>;\n\t/** Ordered low → high, e.g. `['draft', 'standard', 'high']`. */\n\tquality?: EnumCapability<string>;\n\tcount?: CountCapability;\n\tduration?: DurationCapability;\n\tseed?: boolean | SeedCapability;\n\treferences?: ReferenceCapability;\n\t/** Accepts a source to edit (`mode: 'edit'`). */\n\tediting?: boolean;\n\t/** Accepts a source plus a `mask` reference (`mode: 'inpaint'`). */\n\tinpainting?: boolean;\n\tparams?: Readonly<Record<string, ExtraParamSpec>>;\n\ttiming?: TimingCapability;\n}\n\nexport interface GenerationReference {\n\t/** Asset id (opaque to this library). */\n\tid: string;\n\trole: ReferenceRole;\n\turl?: string;\n\t/** MIME type or media kind, checked against the role's `accepts`. */\n\tmediaType?: string;\n\t/** Content hash of the asset when it was referenced (used by generation records). */\n\tcontentHash?: string;\n}\n\nexport type GenerationMode = 'generate' | 'edit' | 'inpaint';\n\nexport type GenerationParamValue = string | number | boolean;\n\n/** A request for one model. Every field is optional; the planner fills and fixes. */\nexport interface GenerationRequest {\n\tmodel?: string;\n\tprompt?: string;\n\tnegativePrompt?: string;\n\tsystemPrompt?: string;\n\taspectRatio?: string;\n\tresolution?: string;\n\tquality?: string;\n\tcount?: number;\n\t/** Seconds. */\n\tduration?: number;\n\tseed?: number;\n\tmode?: GenerationMode;\n\treferences?: GenerationReference[];\n\tparams?: Record<string, GenerationParamValue>;\n}\n\n/** Fields of {@link GenerationRequest} the planner knows about, in planning order. */\nexport const GENERATION_REQUEST_FIELDS = [\n\t'model',\n\t'prompt',\n\t'negativePrompt',\n\t'systemPrompt',\n\t'aspectRatio',\n\t'resolution',\n\t'quality',\n\t'count',\n\t'duration',\n\t'seed',\n\t'mode',\n\t'references',\n\t'params'\n] as const;\n\n/** Text field limits normalized from `boolean | TextFieldCapability`. */\nexport function textFieldCapability(value: boolean | TextFieldCapability | undefined): TextFieldCapability | null {\n\tif (!value) return null;\n\treturn value === true ? {} : value;\n}\n\n/** Seed limits normalized from `boolean | SeedCapability`. */\nexport function seedCapability(value: boolean | SeedCapability | undefined): SeedCapability | null {\n\tif (!value) return null;\n\treturn value === true ? {} : value;\n}\n\n/**\n * Supported reference roles of a model: built-in roles in\n * {@link REFERENCE_ROLES} order, then custom roles in declaration order.\n */\nexport function supportedReferenceRoles(caps: ModelCapabilities): ReferenceRole[] {\n\tconst roles = caps.references?.roles ?? {};\n\tconst builtin: ReferenceRole[] = REFERENCE_ROLES.filter((role) => (roles[role]?.max ?? 0) > 0);\n\tconst custom = Object.keys(roles).filter(\n\t\t(role) => !isBuiltinRole(role) && isReferenceRoleName(role) && (roles[role]?.max ?? 0) > 0\n\t);\n\treturn [...builtin, ...custom];\n}\n\n/** Supported generation modes of a model. */\nexport function supportedModes(caps: ModelCapabilities): GenerationMode[] {\n\tconst modes: GenerationMode[] = ['generate'];\n\tif (caps.editing) modes.push('edit');\n\tif (caps.inpainting) modes.push('inpaint');\n\treturn modes;\n}\n\nconst PRESENTATION_KEYS = ['name', 'description', 'badges', 'icon', 'group'] as const;\n\n/** A partial model entry used to decorate (or add) models locally. */\nexport type CapabilityOverlay = Partial<ModelCapabilities> & { id: string };\n\n/**\n * Merges server-reported capabilities with a local overlay. For models the\n * server knows, every capability value comes from `remote`; the overlay only\n * contributes presentation fields (`name`, `description`, `badges`, `icon`,\n * `group`), filling in or replacing the remote ones. Overlay entries for\n * models the server does not report are appended when they are complete\n * (have a `media` kind). Remote order is kept; overlay-only models follow in\n * overlay order.\n */\nexport function mergeCapabilities(\n\tremote: readonly ModelCapabilities[],\n\toverlay: readonly CapabilityOverlay[] = []\n): ModelCapabilities[] {\n\tconst overlayById = new Map(overlay.map((entry) => [entry.id, entry]));\n\tconst remoteIds = new Set(remote.map((model) => model.id));\n\tconst merged = remote.map((model) => {\n\t\tconst extra = overlayById.get(model.id);\n\t\tif (!extra) return { ...model };\n\t\tconst next: ModelCapabilities = { ...model };\n\t\tfor (const key of PRESENTATION_KEYS) {\n\t\t\tif (extra[key] !== undefined) (next as any)[key] = extra[key];\n\t\t}\n\t\treturn next;\n\t});\n\tfor (const entry of overlay) {\n\t\tif (remoteIds.has(entry.id) || !entry.media) continue;\n\t\tremoteIds.add(entry.id);\n\t\tmerged.push({ ...(entry as ModelCapabilities) });\n\t}\n\treturn merged;\n}\n","import {\n\tGENERATION_REQUEST_FIELDS,\n\tREFERENCE_ROLES,\n\tisReferenceRoleName,\n\tmediaTypeMatches,\n\tnormalizeReferenceRole,\n\tseedCapability,\n\tsupportedReferenceRoles,\n\tsupportedModes,\n\ttextFieldCapability,\n\ttype EnumCapability,\n\ttype ExtraParamSpec,\n\ttype GenerationParamValue,\n\ttype GenerationReference,\n\ttype GenerationRequest,\n\ttype ModelCapabilities,\n\ttype ReferenceRole,\n\ttype ReferenceRoleCapability\n} from './capabilities.js';\n\n/**\n * How the planner treats values the model does not support:\n *\n * - `error`: nothing is changed; every mismatch is an error.\n * - `nearest`: snap to the closest supported value (ties go to the higher\n * value), clamp ranges, truncate lists, drop unsupported fields and\n * unsupported references.\n * - `downgrade`: like `nearest`, but ties go to the lower value.\n * - `upgrade`: like `nearest`, but never drops references the model cannot\n * use (that would lose intent), so those are errors.\n *\n * \"Lower\" means a narrower aspect ratio, fewer pixels, an earlier quality\n * option, a smaller number.\n */\nexport type PlanStrategy = 'error' | 'upgrade' | 'downgrade' | 'nearest';\n\nexport type PlanAdjustmentKind = 'snapped' | 'clamped' | 'truncated' | 'dropped' | 'defaulted';\n\nexport interface PlanAdjustment {\n\t/** Request path, e.g. `aspectRatio`, `params.guidance`, `references.style_ref`. */\n\tfield: string;\n\tfrom: unknown;\n\tto: unknown;\n\treason: string;\n\tkind: PlanAdjustmentKind;\n}\n\nexport interface PlanError {\n\tfield: string;\n\tvalue: unknown;\n\tmessage: string;\n}\n\nexport interface GenerationPlan {\n\t/** `true` when there are no errors and `request` can be sent. */\n\tok: boolean;\n\t/** The adjusted request (always a copy; the input is never mutated). */\n\trequest: GenerationRequest;\n\tadjustments: PlanAdjustment[];\n\terrors: PlanError[];\n\t/** Model the plan was made for. */\n\tmodel: string;\n\tstrategy: PlanStrategy;\n}\n\nexport interface PlanOptions {\n\t/** Default `nearest`. */\n\tstrategy?: PlanStrategy;\n\t/**\n\t * The prompt with templates and variables resolved. Measured against\n\t * `prompt.maxLength` when the model counts resolved text (the default).\n\t */\n\tresolvedPrompt?: string;\n\t/** Fill missing fields that have a capability `default` (reported as `defaulted`). */\n\tfillDefaults?: boolean;\n\t/** Override whether references with unsupported roles may be dropped (default: `nearest` and `downgrade` only). */\n\tdropUnsupportedReferences?: boolean;\n\t/**\n\t * Assets mentioned in the prompt (e.g. from `extractMentions(text, { details: true, roles: true })`\n\t * joined with your asset library). They are appended to `request.references`\n\t * and validated per role like any other reference. A mention without a role\n\t * gets `defaultMentionRole`, or the first role of the model that accepts its\n\t * media type (reported as `defaulted`).\n\t */\n\tmentions?: readonly PlanAssetMention[];\n\t/** Role for mentions that have none (see `mentions`). */\n\tdefaultMentionRole?: ReferenceRole;\n}\n\n/** An asset mention handed to the planner (see {@link PlanOptions.mentions}). */\nexport interface PlanAssetMention {\n\t/** Asset id. */\n\tid: string;\n\trole?: ReferenceRole | null;\n\tmediaType?: string;\n\tcontentHash?: string;\n\turl?: string;\n\t/** The mention text, used in messages (default: the id). */\n\tlabel?: string;\n}\n\nconst EPSILON = 1e-9;\n\n/** Numeric rank of an aspect ratio such as `16:9` (log of width / height). */\nexport function aspectRatioRank(value: string): number | undefined {\n\tconst match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*[:x/×]\\s*(\\d+(?:\\.\\d+)?)\\s*$/i.exec(String(value));\n\tif (!match) return undefined;\n\tconst w = Number(match[1]);\n\tconst h = Number(match[2]);\n\tif (!(w > 0) || !(h > 0)) return undefined;\n\treturn Math.log(w / h);\n}\n\n/**\n * Numeric rank of a resolution: `1024x768` (pixels), `720p` (16:9 pixels at\n * that height) or `2k` (square of that many kibi-pixels). Log scale.\n */\nexport function resolutionRank(value: string): number | undefined {\n\tconst text = String(value).trim().toLowerCase();\n\tlet pixels: number | undefined;\n\tlet match = /^(\\d+(?:\\.\\d+)?)\\s*[x×*]\\s*(\\d+(?:\\.\\d+)?)$/.exec(text);\n\tif (match) pixels = Number(match[1]) * Number(match[2]);\n\telse if ((match = /^(\\d+(?:\\.\\d+)?)p$/.exec(text))) pixels = (Number(match[1]) ** 2 * 16) / 9;\n\telse if ((match = /^(\\d+(?:\\.\\d+)?)k$/.exec(text))) pixels = (Number(match[1]) * 1024) ** 2;\n\treturn pixels && pixels > 0 ? Math.log(pixels) : undefined;\n}\n\n/** Megapixels of a resolution string, when it can be parsed. */\nexport function resolutionMegapixels(value: string): number | undefined {\n\tconst rank = resolutionRank(value);\n\treturn rank === undefined ? undefined : Math.exp(rank) / 1_000_000;\n}\n\n/** Common quality names, low → high. Used to compare values missing from a model's options. */\nexport const QUALITY_LADDER: Readonly<Record<string, number>> = {\n\tdraft: 0,\n\tpreview: 0,\n\tlow: 1,\n\tfast: 1,\n\tturbo: 1,\n\tstandard: 2,\n\tnormal: 2,\n\tmedium: 2,\n\tbalanced: 2,\n\thigh: 3,\n\thd: 3,\n\tquality: 3,\n\tultra: 4,\n\tmax: 5,\n\tbest: 5\n};\n\nexport function qualityRank(value: string): number | undefined {\n\treturn QUALITY_LADDER[String(value).trim().toLowerCase()];\n}\n\nconst numberRank = (value: unknown) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);\n\n/**\n * Picks the option closest to `value` by `rank`. Ties go to the lower or\n * higher rank. Returns `undefined` when `value` or no option can be ranked.\n */\nexport function snapToOption<T>(\n\tvalue: unknown,\n\toptions: readonly T[],\n\trank: (value: any) => number | undefined,\n\ttie: 'lower' | 'higher'\n): T | undefined {\n\tconst target = rank(value);\n\tif (target === undefined) return undefined;\n\tlet best: { option: T; distance: number; rank: number } | undefined;\n\tfor (const option of options) {\n\t\tconst r = rank(option);\n\t\tif (r === undefined) continue;\n\t\tconst distance = Math.abs(r - target);\n\t\tif (\n\t\t\t!best ||\n\t\t\tdistance < best.distance - EPSILON ||\n\t\t\t(Math.abs(distance - best.distance) <= EPSILON && (tie === 'lower' ? r < best.rank : r > best.rank))\n\t\t) {\n\t\t\tbest = { option, distance, rank: r };\n\t\t}\n\t}\n\treturn best?.option;\n}\n\n/** Truncates to `max` UTF-16 units without splitting a surrogate pair. */\nfunction truncateText(text: string, max: number): string {\n\tif (text.length <= max) return text;\n\tlet end = max;\n\tconst code = text.charCodeAt(end - 1);\n\tif (end > 0 && code >= 0xd800 && code <= 0xdbff) end -= 1;\n\treturn text.slice(0, end);\n}\n\nfunction cloneRequest(request: GenerationRequest): GenerationRequest {\n\tconst copy: GenerationRequest = { ...request };\n\tif (request.references) copy.references = request.references.map((ref) => ({ ...ref }));\n\tif (request.params) copy.params = { ...request.params };\n\treturn copy;\n}\n\nconst describe = (value: unknown) => (typeof value === 'string' ? `\"${value}\"` : JSON.stringify(value));\nconst list = (options: readonly unknown[]) => options.map(describe).join(', ');\n\nclass Planner {\n\treadonly adjustments: PlanAdjustment[] = [];\n\treadonly errors: PlanError[] = [];\n\treadonly tie: 'lower' | 'higher';\n\treadonly strict: boolean;\n\n\tconstructor(\n\t\treadonly caps: ModelCapabilities,\n\t\treadonly request: GenerationRequest,\n\t\treadonly options: PlanOptions,\n\t\treadonly strategy: PlanStrategy\n\t) {\n\t\tthis.tie = strategy === 'downgrade' ? 'lower' : 'higher';\n\t\tthis.strict = strategy === 'error';\n\t}\n\n\tadjust(field: string, from: unknown, to: unknown, kind: PlanAdjustmentKind, reason: string) {\n\t\tthis.adjustments.push({ field, from, to, kind, reason });\n\t}\n\n\terror(field: string, value: unknown, message: string) {\n\t\tthis.errors.push({ field, value, message });\n\t}\n\n\t/** Removes an unsupported field (or reports it under `error`). */\n\tunsupported(field: keyof GenerationRequest, message: string) {\n\t\tconst value = this.request[field];\n\t\tif (this.strict) {\n\t\t\tthis.error(field, value, message);\n\t\t\treturn;\n\t\t}\n\t\tdelete this.request[field];\n\t\tthis.adjust(field, value, undefined, 'dropped', message);\n\t}\n\n\tdefaulted(field: string, value: unknown, set: () => void) {\n\t\tif (!this.options.fillDefaults || value === undefined) return;\n\t\tset();\n\t\tthis.adjust(field, undefined, value, 'defaulted', 'Filled with the model default.');\n\t}\n\n\tplanText(field: 'prompt' | 'negativePrompt' | 'systemPrompt', maxLength: number | undefined, measured?: string) {\n\t\tconst value = this.request[field];\n\t\tif (value === undefined || maxLength === undefined) return;\n\t\tif (typeof value !== 'string') {\n\t\t\tthis.error(field, value, `Expected text.`);\n\t\t\treturn;\n\t\t}\n\t\tconst text = measured ?? value;\n\t\tif (text.length <= maxLength) return;\n\t\tif (this.strict || measured !== undefined) {\n\t\t\tthis.error(\n\t\t\t\tfield,\n\t\t\t\ttext.length,\n\t\t\t\t`${measured !== undefined ? 'Resolved text' : 'Text'} is ${text.length} characters; the model accepts at most ${maxLength}.`\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst next = truncateText(value, maxLength);\n\t\tthis.request[field] = next;\n\t\tthis.adjust(field, value.length, next.length, 'truncated', `Truncated to the model's ${maxLength}-character limit.`);\n\t}\n\n\tplanEnum(\n\t\tfield: 'aspectRatio' | 'resolution' | 'quality',\n\t\tcap: EnumCapability<string> | undefined,\n\t\trank: (value: string) => number | undefined,\n\t\tlabel: string\n\t) {\n\t\tconst value = this.request[field];\n\t\tif (value === undefined) {\n\t\t\tif (cap) this.defaulted(field, cap.default, () => (this.request[field] = cap.default));\n\t\t\treturn;\n\t\t}\n\t\tif (!cap || cap.options.length === 0) {\n\t\t\tthis.unsupported(field, `The model does not take a ${label}.`);\n\t\t\treturn;\n\t\t}\n\t\tif (cap.options.includes(value)) return;\n\t\tif (this.strict) {\n\t\t\tthis.error(field, value, `${describe(value)} is not a supported ${label}; supported: ${list(cap.options)}.`);\n\t\t\treturn;\n\t\t}\n\t\tconst snapped = snapToOption(value, cap.options, rank, this.tie);\n\t\tconst next = snapped ?? cap.default ?? cap.options[0];\n\t\tthis.request[field] = next;\n\t\tthis.adjust(\n\t\t\tfield,\n\t\t\tvalue,\n\t\t\tnext,\n\t\t\t'snapped',\n\t\t\tsnapped !== undefined\n\t\t\t\t? `${describe(value)} is not supported; using the nearest ${label}.`\n\t\t\t\t: `${describe(value)} is not supported; using ${cap.default !== undefined ? 'the default' : 'the first option'}.`\n\t\t);\n\t}\n\n\t/** Clamps/rounds/snaps a number. Returns the next value or `undefined` after an error. */\n\tplanNumber(\n\t\tfield: string,\n\t\tvalue: unknown,\n\t\tspec: { min?: number; max?: number; step?: number; integer?: boolean; options?: readonly number[] },\n\t\tlabel: string\n\t): { value: number; changed: boolean } | undefined {\n\t\tif (typeof value !== 'number' || !Number.isFinite(value)) {\n\t\t\tthis.error(field, value, `Expected a number for ${label}.`);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst options = spec.options?.filter(\n\t\t\t(o) => (spec.min === undefined || o >= spec.min) && (spec.max === undefined || o <= spec.max)\n\t\t);\n\t\tif (options && options.length > 0) {\n\t\t\tif (options.includes(value)) return { value, changed: false };\n\t\t\tif (this.strict) {\n\t\t\t\tthis.error(field, value, `${value} is not a supported ${label}; supported: ${list(options)}.`);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst next = snapToOption(value, options, numberRank, this.tie)!;\n\t\t\tthis.adjust(field, value, next, 'snapped', `${value} is not supported; using the nearest ${label}.`);\n\t\t\treturn { value: next, changed: true };\n\t\t}\n\t\tlet next = value;\n\t\tif (spec.step !== undefined && spec.step > 0) {\n\t\t\tconst base = spec.min ?? 0;\n\t\t\tconst steps = (next - base) / spec.step;\n\t\t\tconst rounded = Math.abs(steps - Math.trunc(steps) - 0.5) < EPSILON\n\t\t\t\t? this.tie === 'lower' ? Math.floor(steps) : Math.ceil(steps)\n\t\t\t\t: Math.round(steps);\n\t\t\tnext = Number((base + rounded * spec.step).toPrecision(12));\n\t\t}\n\t\tif (spec.integer && !Number.isInteger(next)) {\n\t\t\tconst frac = next - Math.floor(next);\n\t\t\tnext = Math.abs(frac - 0.5) < EPSILON ? (this.tie === 'lower' ? Math.floor(next) : Math.ceil(next)) : Math.round(next);\n\t\t}\n\t\tlet kind: PlanAdjustmentKind = 'snapped';\n\t\tif (spec.min !== undefined && next < spec.min) {\n\t\t\tnext = spec.min;\n\t\t\tkind = 'clamped';\n\t\t}\n\t\tif (spec.max !== undefined && next > spec.max) {\n\t\t\tnext = spec.max;\n\t\t\tkind = 'clamped';\n\t\t}\n\t\tif (next === value) return { value, changed: false };\n\t\tconst range = `${spec.min ?? '-∞'}–${spec.max ?? '∞'}`;\n\t\tif (this.strict) {\n\t\t\tthis.error(\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tkind === 'clamped' ? `${label} must be within ${range}.` : `${value} is not a valid ${label} (try ${next}).`\n\t\t\t);\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.adjust(\n\t\t\tfield,\n\t\t\tvalue,\n\t\t\tnext,\n\t\t\tkind,\n\t\t\tkind === 'clamped' ? `Clamped to the supported range ${range}.` : `Rounded to a supported ${label}.`\n\t\t);\n\t\treturn { value: next, changed: true };\n\t}\n\n\tplanCount() {\n\t\tconst value = this.request.count;\n\t\tconst cap = this.caps.count;\n\t\tif (value === undefined) {\n\t\t\tif (cap) this.defaulted('count', cap.default, () => (this.request.count = cap.default));\n\t\t\treturn;\n\t\t}\n\t\tif (!cap) {\n\t\t\tif (value === 1) return;\n\t\t\tthis.unsupported('count', 'The model produces one output per request.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = this.planNumber('count', value, { min: 1, max: cap.max, integer: true, options: cap.options }, 'output count');\n\t\tif (result) this.request.count = result.value;\n\t}\n\n\tplanDuration() {\n\t\tconst value = this.request.duration;\n\t\tconst cap = this.caps.duration;\n\t\tif (value === undefined) {\n\t\t\tif (cap) this.defaulted('duration', cap.default, () => (this.request.duration = cap.default));\n\t\t\treturn;\n\t\t}\n\t\tif (!cap) {\n\t\t\tthis.unsupported('duration', 'The model does not take a duration.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = this.planNumber('duration', value, cap, 'duration');\n\t\tif (result) this.request.duration = result.value;\n\t}\n\n\tplanSeed() {\n\t\tconst value = this.request.seed;\n\t\tif (value === undefined) return;\n\t\tconst cap = seedCapability(this.caps.seed);\n\t\tif (!cap) {\n\t\t\tthis.unsupported('seed', 'The model does not take a seed.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = this.planNumber('seed', value, { ...cap, integer: true }, 'seed');\n\t\tif (result) this.request.seed = result.value;\n\t}\n\n\tplanMode() {\n\t\tconst mode = this.request.mode;\n\t\tif (mode === undefined) return;\n\t\tconst modes = supportedModes(this.caps);\n\t\tif (!modes.includes(mode)) {\n\t\t\tthis.error('mode', mode, `The model does not support ${describe(mode)}; supported: ${list(modes)}.`);\n\t\t}\n\t}\n\n\troleCapability(role: ReferenceRole): ReferenceRoleCapability | undefined {\n\t\tconst cap = this.caps.references?.roles[role];\n\t\tif (cap && cap.max > 0) return cap;\n\t\tif (role === 'mask' && this.caps.inpainting) return { max: 1 };\n\t\treturn undefined;\n\t}\n\n\t/** Turns `options.mentions` into references (appended after the request's own). */\n\taddMentions() {\n\t\tconst mentions = this.options.mentions;\n\t\tif (!mentions?.length) return;\n\t\tconst refs = [...(this.request.references ?? [])];\n\t\tfor (const mention of mentions) {\n\t\t\tif (!mention || typeof mention.id !== 'string' || mention.id === '') continue;\n\t\t\tlet role = mention.role ?? undefined;\n\t\t\tif (!role) {\n\t\t\t\trole = this.options.defaultMentionRole ?? this.inferRole(mention.mediaType);\n\t\t\t\tthis.adjust(\n\t\t\t\t\t`references.${role}`,\n\t\t\t\t\tundefined,\n\t\t\t\t\trole,\n\t\t\t\t\t'defaulted',\n\t\t\t\t\t`@${mention.label ?? mention.id} has no role; using ${role}.`\n\t\t\t\t);\n\t\t\t}\n\t\t\trefs.push({\n\t\t\t\tid: mention.id,\n\t\t\t\trole,\n\t\t\t\t...(mention.url !== undefined ? { url: mention.url } : {}),\n\t\t\t\t...(mention.mediaType !== undefined ? { mediaType: mention.mediaType } : {}),\n\t\t\t\t...(mention.contentHash !== undefined ? { contentHash: mention.contentHash } : {})\n\t\t\t});\n\t\t}\n\t\tthis.request.references = refs;\n\t}\n\n\t/** First role of the model (other than `mask`) whose `accepts` matches `mediaType`. */\n\tinferRole(mediaType: string | undefined): ReferenceRole {\n\t\tfor (const role of supportedReferenceRoles(this.caps)) {\n\t\t\tif (role === 'mask') continue;\n\t\t\tconst cap = this.roleCapability(role);\n\t\t\tif (cap && (mediaType === undefined || mediaTypeMatches(mediaType, cap.accepts))) return role;\n\t\t}\n\t\treturn 'image_ref';\n\t}\n\n\tplanReferences() {\n\t\tconst refs = this.request.references;\n\t\tconst allowDrop =\n\t\t\t!this.strict &&\n\t\t\t(this.options.dropUnsupportedReferences ?? (this.strategy === 'nearest' || this.strategy === 'downgrade'));\n\t\tconst byRole = new Map<ReferenceRole, GenerationReference[]>();\n\t\tconst kept: GenerationReference[] = [];\n\t\tconst unsupported = new Map<string, number>();\n\t\tfor (const [index, original] of (refs ?? []).entries()) {\n\t\t\tlet ref = original;\n\t\t\tif (!ref || typeof ref.id !== 'string' || ref.id === '' || !isReferenceRoleName(ref.role)) {\n\t\t\t\tthis.error(`references.${index}`, ref, 'Each reference needs an `id` and a valid `role`.');\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!this.roleCapability(ref.role)) {\n\t\t\t\tconst alias = normalizeReferenceRole(ref.role, this.caps);\n\t\t\t\tif (alias !== ref.role && this.roleCapability(alias)) {\n\t\t\t\t\tif (this.strict) {\n\t\t\t\t\t\tthis.error(`references.${ref.role}`, ref.role, `The model calls this role ${describe(alias)}.`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthis.adjust(\n\t\t\t\t\t\t`references.${ref.role}`,\n\t\t\t\t\t\tref.role,\n\t\t\t\t\t\talias,\n\t\t\t\t\t\t'snapped',\n\t\t\t\t\t\t`Using the model's ${alias} role for ${describe(ref.role)}.`\n\t\t\t\t\t);\n\t\t\t\t\tref = { ...ref, role: alias };\n\t\t\t\t} else {\n\t\t\t\t\tunsupported.set(ref.role, (unsupported.get(ref.role) ?? 0) + 1);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst cap = this.roleCapability(ref.role)!;\n\t\t\tif (ref.mediaType !== undefined && !mediaTypeMatches(ref.mediaType, cap.accepts)) {\n\t\t\t\tconst message = `${describe(ref.id)} is ${describe(ref.mediaType)}; ${ref.role} accepts ${list(cap.accepts ?? [])}.`;\n\t\t\t\tif (allowDrop) this.adjust(`references.${ref.role}`, ref.id, undefined, 'dropped', message);\n\t\t\t\telse this.error(`references.${ref.role}`, ref.id, message);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst group = byRole.get(ref.role) ?? [];\n\t\t\tif (group.some((other) => other.id === ref.id)) {\n\t\t\t\tthis.adjust(`references.${ref.role}`, ref.id, undefined, 'dropped', `Duplicate reference \"${ref.id}\" removed.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tgroup.push(ref);\n\t\t\tbyRole.set(ref.role, group);\n\t\t\tkept.push(ref);\n\t\t}\n\n\t\tfor (const [role, count] of unsupported) {\n\t\t\tconst message =\n\t\t\t\tthis.caps.media === 'text' || !this.caps.references\n\t\t\t\t\t? `The ${this.caps.media} model \"${this.caps.id}\" does not accept reference inputs (${role}).`\n\t\t\t\t\t: `The model does not accept ${role} references.`;\n\t\t\tif (allowDrop) this.adjust(`references.${role}`, count, 0, 'dropped', message);\n\t\t\telse this.error(`references.${role}`, count, message);\n\t\t}\n\n\t\t// Per-role limits (built-in roles first, then the model's own roles).\n\t\tconst removed = new Set<GenerationReference>();\n\t\tconst custom = supportedReferenceRoles(this.caps).filter(\n\t\t\t(role) => !(REFERENCE_ROLES as readonly string[]).includes(role)\n\t\t);\n\t\tfor (const role of [...REFERENCE_ROLES, ...custom]) {\n\t\t\tconst cap = this.roleCapability(role);\n\t\t\tconst group = byRole.get(role) ?? [];\n\t\t\tif (!cap) continue;\n\t\t\tif (group.length > cap.max) {\n\t\t\t\tif (this.strict) {\n\t\t\t\t\tthis.error(`references.${role}`, group.length, `At most ${cap.max} ${role} reference(s) are supported.`);\n\t\t\t\t} else {\n\t\t\t\t\tgroup.slice(cap.max).forEach((ref) => removed.add(ref));\n\t\t\t\t\tthis.adjust(\n\t\t\t\t\t\t`references.${role}`,\n\t\t\t\t\t\tgroup.length,\n\t\t\t\t\t\tcap.max,\n\t\t\t\t\t\t'truncated',\n\t\t\t\t\t\t`Kept the first ${cap.max} ${role} reference(s); the model accepts no more.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cap.min !== undefined && group.length < cap.min) {\n\t\t\t\tthis.error(`references.${role}`, group.length, `At least ${cap.min} ${role} reference(s) are required.`);\n\t\t\t}\n\t\t}\n\t\tlet next = kept.filter((ref) => !removed.has(ref));\n\n\t\t// Total limit.\n\t\tconst maxTotal = this.caps.references?.maxTotal;\n\t\tif (maxTotal !== undefined && next.length > maxTotal) {\n\t\t\tif (this.strict) {\n\t\t\t\tthis.error('references', next.length, `At most ${maxTotal} references are supported in total.`);\n\t\t\t} else {\n\t\t\t\tthis.adjust(\n\t\t\t\t\t'references',\n\t\t\t\t\tnext.length,\n\t\t\t\t\tmaxTotal,\n\t\t\t\t\t'truncated',\n\t\t\t\t\t`Kept the first ${maxTotal} references; the model accepts no more in total.`\n\t\t\t\t);\n\t\t\t\tnext = next.slice(0, maxTotal);\n\t\t\t}\n\t\t}\n\n\t\tif (this.request.mode === 'inpaint' && !next.some((ref) => ref.role === 'mask')) {\n\t\t\tthis.error('references.mask', 0, 'Inpainting needs a `mask` reference.');\n\t\t}\n\n\t\tif (refs === undefined) return;\n\t\tif (!this.strict) {\n\t\t\tif (next.length > 0) this.request.references = next;\n\t\t\telse delete this.request.references;\n\t\t}\n\t}\n\n\tplanParam(name: string, spec: ExtraParamSpec | undefined, value: GenerationParamValue): GenerationParamValue | undefined {\n\t\tconst field = `params.${name}`;\n\t\tif (!spec) {\n\t\t\tif (this.strict) this.error(field, value, `Unknown parameter \"${name}\".`);\n\t\t\telse this.adjust(field, value, undefined, 'dropped', `The model has no \"${name}\" parameter.`);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst label = spec.label ?? name;\n\t\tif (spec.type === 'boolean') {\n\t\t\tif (typeof value !== 'boolean') this.error(field, value, `Expected true or false for ${label}.`);\n\t\t\treturn value;\n\t\t}\n\t\tif (spec.type === 'enum') {\n\t\t\tif (spec.options.includes(value as string | number)) return value;\n\t\t\tif (this.strict) {\n\t\t\t\tthis.error(field, value, `${describe(value)} is not a supported ${label}; supported: ${list(spec.options)}.`);\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\tconst numeric = spec.options.every((o) => typeof o === 'number');\n\t\t\tconst snapped = numeric ? snapToOption(value, spec.options, numberRank, this.tie) : undefined;\n\t\t\tconst next = snapped ?? spec.default ?? spec.options[0];\n\t\t\tthis.adjust(\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tnext,\n\t\t\t\t'snapped',\n\t\t\t\tsnapped !== undefined ? `Using the nearest supported ${label}.` : `${describe(value)} is not supported; using ${spec.default !== undefined ? 'the default' : 'the first option'}.`\n\t\t\t);\n\t\t\treturn next;\n\t\t}\n\t\tconst result = this.planNumber(field, value, { ...spec, integer: spec.type === 'integer' }, label);\n\t\treturn result ? result.value : value;\n\t}\n\n\tplanParams() {\n\t\tconst params = this.request.params;\n\t\tconst specs = this.caps.params ?? {};\n\t\tconst next: Record<string, GenerationParamValue> = {};\n\t\tfor (const [name, value] of Object.entries(params ?? {})) {\n\t\t\tif (value === undefined) continue;\n\t\t\tconst planned = this.planParam(name, specs[name], value);\n\t\t\tif (planned !== undefined) next[name] = planned;\n\t\t\telse if (this.strict) next[name] = value;\n\t\t}\n\t\tif (this.options.fillDefaults) {\n\t\t\tfor (const [name, spec] of Object.entries(specs)) {\n\t\t\t\tif (next[name] === undefined && spec.default !== undefined) {\n\t\t\t\t\tnext[name] = spec.default;\n\t\t\t\t\tthis.adjust(`params.${name}`, undefined, spec.default, 'defaulted', 'Filled with the model default.');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (Object.keys(next).length > 0) this.request.params = next;\n\t\telse delete this.request.params;\n\t}\n\n\trun(): void {\n\t\tconst { caps, request } = this;\n\t\tfor (const key of Object.keys(request)) {\n\t\t\tif ((GENERATION_REQUEST_FIELDS as readonly string[]).includes(key)) continue;\n\t\t\tconst value = (request as any)[key];\n\t\t\tif (this.strict) this.error(key, value, `Unknown request field \"${key}\".`);\n\t\t\telse {\n\t\t\t\tdelete (request as any)[key];\n\t\t\t\tthis.adjust(key, value, undefined, 'dropped', `Unknown request field \"${key}\".`);\n\t\t\t}\n\t\t}\n\n\t\tif (request.model !== undefined && request.model !== caps.id) {\n\t\t\tthis.error('model', request.model, `The request targets \"${request.model}\" but the plan is for \"${caps.id}\".`);\n\t\t}\n\t\trequest.model = caps.id;\n\n\t\t// Text fields.\n\t\tconst promptCap = caps.prompt ?? {};\n\t\tif (promptCap.required && !(request.prompt ?? '').trim()) {\n\t\t\tthis.error('prompt', request.prompt, 'A prompt is required.');\n\t\t}\n\t\tconst countResolved = promptCap.countResolved ?? true;\n\t\tconst measured =\n\t\t\tcountResolved && this.options.resolvedPrompt !== undefined && this.options.resolvedPrompt !== request.prompt\n\t\t\t\t? this.options.resolvedPrompt\n\t\t\t\t: undefined;\n\t\tthis.planText('prompt', promptCap.maxLength, measured);\n\t\tfor (const field of ['negativePrompt', 'systemPrompt'] as const) {\n\t\t\tif (request[field] === undefined) continue;\n\t\t\tconst cap = textFieldCapability(caps[field]);\n\t\t\tif (!cap) {\n\t\t\t\tthis.unsupported(field, `The model does not take a ${field === 'negativePrompt' ? 'negative' : 'system'} prompt.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.planText(field, cap.maxLength);\n\t\t}\n\n\t\tthis.planEnum('aspectRatio', caps.aspectRatios, aspectRatioRank, 'aspect ratio');\n\t\tthis.planEnum('resolution', caps.resolutions, resolutionRank, 'resolution');\n\t\tthis.planEnum('quality', caps.quality, qualityRank, 'quality');\n\t\tthis.planCount();\n\t\tthis.planDuration();\n\t\tthis.planSeed();\n\t\tthis.planMode();\n\t\tthis.addMentions();\n\t\tthis.planReferences();\n\t\tthis.planParams();\n\t}\n}\n\n/**\n * Plans `request` against a model's capabilities before sending it: snaps\n * enum values (aspect ratio, resolution, quality, count/duration choices) to\n * the nearest supported option, clamps ranges, truncates text and reference\n * lists over their limits, drops unsupported fields, and reports every change\n * in `adjustments` and every unfixable problem in `errors`.\n *\n * Pure and deterministic: the input is not mutated and the same input always\n * gives the same plan.\n */\nexport function planGenerationRequest(\n\trequest: GenerationRequest,\n\tcaps: ModelCapabilities,\n\toptions: PlanOptions = {}\n): GenerationPlan {\n\tconst strategy = options.strategy ?? 'nearest';\n\tconst planner = new Planner(caps, cloneRequest(request ?? {}), options, strategy);\n\tplanner.run();\n\treturn {\n\t\tok: planner.errors.length === 0,\n\t\trequest: planner.request,\n\t\tadjustments: planner.adjustments,\n\t\terrors: planner.errors,\n\t\tmodel: caps.id,\n\t\tstrategy\n\t};\n}\n","import type { JsonSchema } from '../agent/schema.js';\nimport {\n\treferenceRoleLabel,\n\tseedCapability,\n\tsupportedModes,\n\tsupportedReferenceRoles,\n\ttextFieldCapability,\n\ttype EnumCapability,\n\ttype ExtraParamSpec,\n\ttype ModelCapabilities\n} from './capabilities.js';\n\nfunction enumSchema(cap: EnumCapability<string>, description: string): JsonSchema {\n\treturn {\n\t\ttype: 'string',\n\t\tenum: [...cap.options],\n\t\t...(cap.default !== undefined ? { default: cap.default } : {}),\n\t\tdescription\n\t};\n}\n\nfunction paramSchema(name: string, spec: ExtraParamSpec): JsonSchema {\n\tconst description = spec.description ?? spec.label ?? name;\n\tif (spec.type === 'boolean') {\n\t\treturn { type: 'boolean', description, ...(spec.default !== undefined ? { default: spec.default } : {}) };\n\t}\n\tif (spec.type === 'enum') {\n\t\tconst numeric = spec.options.every((o) => typeof o === 'number');\n\t\treturn {\n\t\t\ttype: numeric ? 'number' : 'string',\n\t\t\tenum: [...spec.options],\n\t\t\tdescription,\n\t\t\t...(spec.default !== undefined ? { default: spec.default } : {})\n\t\t};\n\t}\n\treturn {\n\t\ttype: spec.type,\n\t\tdescription: spec.step !== undefined ? `${description} (step ${spec.step})` : description,\n\t\t...(spec.min !== undefined ? { minimum: spec.min } : {}),\n\t\t...(spec.max !== undefined ? { maximum: spec.max } : {}),\n\t\t...(spec.default !== undefined ? { default: spec.default } : {})\n\t};\n}\n\n/**\n * JSON Schema for a request to one model: enums for choices, `minimum` /\n * `maximum` for ranges, `maxItems` for reference lists and `description`s that\n * spell out limits the schema cannot express (per-role reference counts,\n * resolved-prompt length). Unsupported fields are absent and\n * `additionalProperties` is `false`, so `validateJsonSchema` rejects them.\n */\nexport function capabilitiesToJsonSchema(caps: ModelCapabilities): JsonSchema & { type: 'object' } {\n\tconst properties: Record<string, JsonSchema> = {\n\t\tmodel: { type: 'string', const: caps.id, description: 'Model id.' }\n\t};\n\tconst required: string[] = [];\n\n\tconst prompt = caps.prompt ?? {};\n\tconst countResolved = prompt.countResolved ?? true;\n\tproperties.prompt = {\n\t\ttype: 'string',\n\t\tdescription:\n\t\t\tprompt.maxLength !== undefined\n\t\t\t\t? `Prompt text. At most ${prompt.maxLength} characters${countResolved ? ' after templates and variables are resolved' : ''}.`\n\t\t\t\t: 'Prompt text.',\n\t\t...(prompt.maxLength !== undefined && !countResolved ? { maxLength: prompt.maxLength } : {}),\n\t\t...(prompt.required ? { minLength: 1 } : {})\n\t};\n\tif (prompt.required) required.push('prompt');\n\n\tconst negative = textFieldCapability(caps.negativePrompt);\n\tif (negative) {\n\t\tproperties.negativePrompt = {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'What to avoid in the output.',\n\t\t\t...(negative.maxLength !== undefined ? { maxLength: negative.maxLength } : {})\n\t\t};\n\t}\n\tconst system = textFieldCapability(caps.systemPrompt);\n\tif (system) {\n\t\tproperties.systemPrompt = {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'System instructions.',\n\t\t\t...(system.maxLength !== undefined ? { maxLength: system.maxLength } : {})\n\t\t};\n\t}\n\n\tif (caps.aspectRatios?.options.length) properties.aspectRatio = enumSchema(caps.aspectRatios, 'Aspect ratio (width:height).');\n\tif (caps.resolutions?.options.length) properties.resolution = enumSchema(caps.resolutions, 'Output resolution, low to high.');\n\tif (caps.quality?.options.length) properties.quality = enumSchema(caps.quality, 'Quality level, low to high.');\n\n\tif (caps.count) {\n\t\tconst options = caps.count.options?.filter((o) => o >= 1 && o <= caps.count!.max);\n\t\tproperties.count = options?.length\n\t\t\t? { type: 'integer', enum: options, description: 'Number of outputs.' }\n\t\t\t: { type: 'integer', minimum: 1, maximum: caps.count.max, description: 'Number of outputs.' };\n\t\tif (caps.count.default !== undefined) properties.count.default = caps.count.default;\n\t}\n\n\tif (caps.duration) {\n\t\tconst d = caps.duration;\n\t\tconst options = d.options?.filter((o) => (d.min === undefined || o >= d.min) && (d.max === undefined || o <= d.max));\n\t\tproperties.duration = options?.length\n\t\t\t? { type: 'number', enum: options, description: 'Duration in seconds.' }\n\t\t\t: {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\tdescription: d.step !== undefined ? `Duration in seconds (step ${d.step}).` : 'Duration in seconds.',\n\t\t\t\t\t...(d.min !== undefined ? { minimum: d.min } : {}),\n\t\t\t\t\t...(d.max !== undefined ? { maximum: d.max } : {})\n\t\t\t\t};\n\t\tif (d.default !== undefined) properties.duration.default = d.default;\n\t}\n\n\tconst seed = seedCapability(caps.seed);\n\tif (seed) {\n\t\tproperties.seed = {\n\t\t\ttype: 'integer',\n\t\t\tdescription: 'Random seed for reproducible output.',\n\t\t\t...(seed.min !== undefined ? { minimum: seed.min } : {}),\n\t\t\t...(seed.max !== undefined ? { maximum: seed.max } : {})\n\t\t};\n\t}\n\n\tconst modes = supportedModes(caps);\n\tif (modes.length > 1) {\n\t\tproperties.mode = { type: 'string', enum: modes, default: 'generate', description: 'Generate, edit a source, or inpaint with a mask.' };\n\t}\n\n\tconst roles = supportedReferenceRoles(caps);\n\tif (caps.inpainting && !roles.includes('mask')) roles.push('mask');\n\tif (roles.length > 0) {\n\t\tconst limits = roles.map((role) => {\n\t\t\tconst cap = caps.references?.roles[role] ?? { max: 1 };\n\t\t\tconst min = cap.min ? `${cap.min}–` : 'up to ';\n\t\t\tconst accepts = 'accepts' in cap && cap.accepts?.length ? `, ${cap.accepts.join('/')} only` : '';\n\t\t\treturn `${role} (${referenceRoleLabel(role, caps)}): ${min}${cap.max}${accepts}`;\n\t\t});\n\t\tconst perRoleTotal = roles.reduce((sum, role) => sum + (caps.references?.roles[role]?.max ?? 1), 0);\n\t\tconst maxItems = Math.min(caps.references?.maxTotal ?? Infinity, perRoleTotal);\n\t\tproperties.references = {\n\t\t\ttype: 'array',\n\t\t\tdescription: `Reference inputs by role. Limits: ${limits.join('; ')}${\n\t\t\t\tcaps.references?.maxTotal !== undefined ? `; at most ${caps.references.maxTotal} in total` : ''\n\t\t\t}.`,\n\t\t\tmaxItems,\n\t\t\titems: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: 'string', minLength: 1, description: 'Asset id.' },\n\t\t\t\t\trole: { type: 'string', enum: roles },\n\t\t\t\t\turl: { type: 'string' },\n\t\t\t\t\tmediaType: { type: 'string', description: 'MIME type or media kind of the asset, e.g. \"image/png\".' },\n\t\t\t\t\tcontentHash: { type: 'string' }\n\t\t\t\t},\n\t\t\t\trequired: ['id', 'role'],\n\t\t\t\tadditionalProperties: false\n\t\t\t}\n\t\t};\n\t\tif (roles.some((role) => (caps.references?.roles[role]?.min ?? 0) > 0)) required.push('references');\n\t}\n\n\tconst params = Object.entries(caps.params ?? {});\n\tif (params.length > 0) {\n\t\tproperties.params = {\n\t\t\ttype: 'object',\n\t\t\tdescription: 'Model-specific parameters.',\n\t\t\tproperties: Object.fromEntries(params.map(([name, spec]) => [name, paramSchema(name, spec)])),\n\t\t\tadditionalProperties: false\n\t\t};\n\t}\n\n\treturn {\n\t\ttype: 'object',\n\t\ttitle: caps.name ?? caps.id,\n\t\tdescription: `${caps.description ? `${caps.description} ` : ''}(${caps.media} model \"${caps.id}\")`,\n\t\tproperties,\n\t\t...(required.length ? { required } : {}),\n\t\tadditionalProperties: false\n\t};\n}\n","import type { ModelCapabilities } from './capabilities.js';\n\n/**\n * Stable JSON: object keys sorted, `undefined` members left out, so equal\n * values always give the same string regardless of key order.\n */\nexport function stableStringify(value: unknown): string {\n\tif (Array.isArray(value)) return `[${value.map((item) => (item === undefined ? 'null' : stableStringify(item))).join(',')}]`;\n\tif (value && typeof value === 'object') {\n\t\tconst entries = Object.keys(value as object)\n\t\t\t.filter((key) => (value as any)[key] !== undefined)\n\t\t\t.sort()\n\t\t\t.map((key) => `${JSON.stringify(key)}:${stableStringify((value as any)[key])}`);\n\t\treturn `{${entries.join(',')}}`;\n\t}\n\treturn JSON.stringify(value) ?? 'null';\n}\n\nconst FNV_PRIME = 0x01000193;\n\n/**\n * A short, stable, synchronous content hash (two independent 32-bit FNV-1a\n * lanes over the UTF-16 code units, 16 hex digits, prefixed with `h1-`).\n * It detects changes; it is not cryptographic, so do not use it to\n * authenticate content.\n */\nexport function hashText(text: string): string {\n\tlet a = 0x811c9dc5;\n\tlet b = 0x2f0e1d5b;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tconst unit = text.charCodeAt(i);\n\t\ta = Math.imul(a ^ (unit & 0xff), FNV_PRIME);\n\t\ta = Math.imul(a ^ (unit >>> 8), FNV_PRIME);\n\t\tb = Math.imul(b ^ (unit >>> 8), FNV_PRIME);\n\t\tb = Math.imul(b ^ (unit & 0xff), FNV_PRIME);\n\t\tb ^= b >>> 15;\n\t}\n\t// Final avalanche so short inputs still spread over all digits.\n\ta ^= a >>> 16;\n\ta = Math.imul(a, 0x85ebca6b);\n\ta ^= a >>> 13;\n\tb ^= b >>> 16;\n\tb = Math.imul(b, 0xc2b2ae35);\n\tb ^= b >>> 13;\n\tconst hex = (n: number) => (n >>> 0).toString(16).padStart(8, '0');\n\treturn `h1-${hex(a)}${hex(b)}`;\n}\n\n/** {@link hashText} of the {@link stableStringify} form of `value`. */\nexport function hashValue(value: unknown): string {\n\treturn hashText(stableStringify(value));\n}\n\nconst PRESENTATION_ONLY = new Set(['name', 'description', 'badges', 'icon', 'group', 'timing']);\n\n/**\n * Hash of what a model accepts. Presentation fields (`name`, `description`,\n * `badges`, `icon`, `group`) and `timing` are left out, so renaming a model or\n * changing its progress estimate is not reported as a capability change.\n */\nexport function hashCapabilities(caps: ModelCapabilities): string {\n\tconst relevant: Record<string, unknown> = {};\n\tfor (const key of Object.keys(caps)) {\n\t\tif (!PRESENTATION_ONLY.has(key)) relevant[key] = (caps as any)[key];\n\t}\n\treturn hashValue(relevant);\n}\n","import type { AgentTool, AgentToolProvider, ToolCallContext } from '../agent/registry.js';\nimport type { JsonSchema } from '../agent/schema.js';\nimport {\n\tREFERENCE_ROLES,\n\tsupportedReferenceRoles,\n\ttype GenerationRequest,\n\ttype ModelCapabilities,\n\ttype ReferenceRole\n} from './capabilities.js';\nimport { planGenerationRequest, type GenerationPlan, type PlanStrategy } from './plan.js';\nimport { capabilitiesToJsonSchema } from './schema.js';\nimport { stableStringify } from './hash.js';\n\nexport interface GenerateContext {\n\tmodel: ModelCapabilities;\n\tplan: GenerationPlan;\n\tidempotencyKey: string;\n\tsignal?: AbortSignal;\n}\n\nexport interface EstimateContext {\n\tmodel: ModelCapabilities;\n\tplan: GenerationPlan;\n\tsignal?: AbortSignal;\n}\n\nexport interface GenerationToolProviderOptions {\n\t/** Provider id (default `generation`). */\n\tid?: string;\n\tnamespace?: string;\n\t/** Models, or a function returning the current models (re-read on every list/call). */\n\tmodels: readonly ModelCapabilities[] | (() => readonly ModelCapabilities[]);\n\t/** Sends a planned request. Its resolved value is the tool result (e.g. `{ jobId }`). */\n\tgenerate: (request: GenerationRequest, context: GenerateContext) => unknown | Promise<unknown>;\n\t/** Optional cost/time estimate; adds the `estimate_generation` tool. */\n\testimate?: (request: GenerationRequest, context: EstimateContext) => unknown | Promise<unknown>;\n\t/** Strategy used when the caller does not pass one (default `nearest`). */\n\tdefaultStrategy?: PlanStrategy;\n\t/** Completed idempotency keys remembered (default 500, oldest evicted first). */\n\tmaxIdempotencyKeys?: number;\n\t/** Lets the host signal that the model list changed. */\n\tsubscribe?: AgentToolProvider['subscribe'];\n}\n\nexport interface GenerationToolProvider extends AgentToolProvider {\n\t/** Forgets remembered idempotency keys (all, or one). */\n\tclearIdempotencyKeys(key?: string): void;\n}\n\nconst STRATEGIES: PlanStrategy[] = ['nearest', 'upgrade', 'downgrade', 'error'];\n\n/** Built-in roles plus every role a model declares, for the shared request schema. */\nfunction knownRoles(models: readonly ModelCapabilities[]): ReferenceRole[] {\n\tconst roles: ReferenceRole[] = [...REFERENCE_ROLES];\n\tfor (const model of models) {\n\t\tfor (const role of supportedReferenceRoles(model)) if (!roles.includes(role)) roles.push(role);\n\t}\n\treturn roles;\n}\n\nconst requestSchemaFor = (roles: readonly ReferenceRole[]): JsonSchema => ({\n\ttype: 'object',\n\tdescription:\n\t\t'Generation request. Call list_models for the fields each model supports; unsupported values are adjusted per `strategy`.',\n\tproperties: {\n\t\tprompt: { type: 'string' },\n\t\tnegativePrompt: { type: 'string' },\n\t\tsystemPrompt: { type: 'string' },\n\t\taspectRatio: { type: 'string', description: 'E.g. \"16:9\".' },\n\t\tresolution: { type: 'string' },\n\t\tquality: { type: 'string' },\n\t\tcount: { type: 'integer', minimum: 1 },\n\t\tduration: { type: 'number', minimum: 0, description: 'Seconds.' },\n\t\tseed: { type: 'integer' },\n\t\tmode: { type: 'string', enum: ['generate', 'edit', 'inpaint'] },\n\t\treferences: {\n\t\t\ttype: 'array',\n\t\t\titems: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: 'string', minLength: 1 },\n\t\t\t\t\trole: {\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tenum: [...roles],\n\t\t\t\t\t\tdescription: 'What the asset is used for. Each model lists the roles it accepts (list_models).'\n\t\t\t\t\t},\n\t\t\t\t\turl: { type: 'string' },\n\t\t\t\t\tmediaType: { type: 'string', description: 'MIME type or media kind, e.g. \"image/png\".' },\n\t\t\t\t\tcontentHash: { type: 'string' }\n\t\t\t\t},\n\t\t\t\trequired: ['id', 'role'],\n\t\t\t\tadditionalProperties: false\n\t\t\t}\n\t\t},\n\t\tparams: { type: 'object', additionalProperties: { type: ['string', 'number', 'boolean'] } }\n\t},\n\tadditionalProperties: false\n});\n\nconst strategySchema: JsonSchema = {\n\ttype: 'string',\n\tenum: STRATEGIES,\n\tdescription:\n\t\t'How to handle unsupported values: nearest (snap/clamp, ties up), downgrade (ties down), upgrade (ties up, never drops references), error (change nothing).'\n};\n\nfunction planFailure(plan: GenerationPlan) {\n\treturn {\n\t\tisError: true,\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\ttext: `The request cannot be sent to \"${plan.model}\":\\n${plan.errors.map((e) => `- ${e.field}: ${e.message}`).join('\\n')}`\n\t\t\t}\n\t\t],\n\t\tstructuredContent: { error: { code: 'plan_failed', message: 'Request planning failed.' }, plan }\n\t};\n}\n\n/**\n * Exposes generation models to a `ToolRegistry`:\n *\n * - `list_models` (read-only): ids, presentation fields and per-model request schemas.\n * - `plan_generation` (read-only): the adjusted request, adjustments and errors.\n * - `estimate_generation` (read-only, only with `estimate`): your estimate for the planned request.\n * - `generate` (destructive): plans, then calls `generate` with the adjusted\n * request. Requires an `idempotencyKey`: a retry with the same key and\n * arguments returns the first call's result (or joins it while in flight)\n * instead of generating again; the same key with different arguments is\n * rejected. Failed calls forget their key so they can be retried.\n */\nexport function createGenerationToolProvider(options: GenerationToolProviderOptions): GenerationToolProvider {\n\tconst readModels = () => (typeof options.models === 'function' ? options.models() : options.models);\n\tconst defaultStrategy = options.defaultStrategy ?? 'nearest';\n\tconst maxKeys = Math.max(1, options.maxIdempotencyKeys ?? 500);\n\tconst calls = new Map<string, { signature: string; result: Promise<unknown> }>();\n\n\tconst findModel = (id: unknown) => {\n\t\tconst model = readModels().find((m) => m.id === id);\n\t\tif (!model) throw new Error(`Unknown model \"${String(id)}\". Call list_models for the available ids.`);\n\t\treturn model;\n\t};\n\n\tconst plan = (args: Record<string, any>) => {\n\t\tconst model = findModel(args.model);\n\t\tconst request: GenerationRequest = { ...(args.request ?? {}), model: model.id };\n\t\treturn {\n\t\t\tmodel,\n\t\t\tplan: planGenerationRequest(request, model, {\n\t\t\t\tstrategy: args.strategy ?? defaultStrategy,\n\t\t\t\tresolvedPrompt: args.resolvedPrompt\n\t\t\t})\n\t\t};\n\t};\n\n\tconst planInput = (): JsonSchema & { type: 'object' } => {\n\t\tconst models = readModels();\n\t\tconst ids = models.map((m) => m.id);\n\t\treturn {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\tmodel: { type: 'string', ...(ids.length ? { enum: ids } : {}), description: 'Model id from list_models.' },\n\t\t\t\trequest: requestSchemaFor(knownRoles(models)),\n\t\t\t\tstrategy: strategySchema,\n\t\t\t\tresolvedPrompt: {\n\t\t\t\t\ttype: 'string',\n\t\t\t\t\tdescription: 'The prompt with templates and variables resolved, when `request.prompt` is a template.'\n\t\t\t\t}\n\t\t\t},\n\t\t\trequired: ['model', 'request'],\n\t\t\tadditionalProperties: false\n\t\t};\n\t};\n\n\tconst tools = (): AgentTool[] => {\n\t\tconst list: AgentTool[] = [\n\t\t\t{\n\t\t\t\tname: 'list_models',\n\t\t\t\ttitle: 'List generation models',\n\t\t\t\tdescription:\n\t\t\t\t\t'Lists the available generation models with their media kind, description and the JSON Schema of the request each accepts.',\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tmedia: { type: 'string', enum: ['image', 'video', 'audio', 'text'], description: 'Only models of this kind.' }\n\t\t\t\t\t},\n\t\t\t\t\tadditionalProperties: false\n\t\t\t\t},\n\t\t\t\tannotations: {\n\t\t\t\t\ttitle: 'List generation models',\n\t\t\t\t\treadOnlyHint: true,\n\t\t\t\t\tdestructiveHint: false,\n\t\t\t\t\tidempotentHint: true,\n\t\t\t\t\topenWorldHint: false\n\t\t\t\t},\n\t\t\t\thandler: (args) => ({\n\t\t\t\t\tmodels: readModels()\n\t\t\t\t\t\t.filter((m) => !args.media || m.media === args.media)\n\t\t\t\t\t\t.map((m) => ({\n\t\t\t\t\t\t\tid: m.id,\n\t\t\t\t\t\t\tmedia: m.media,\n\t\t\t\t\t\t\t...(m.name ? { name: m.name } : {}),\n\t\t\t\t\t\t\t...(m.description ? { description: m.description } : {}),\n\t\t\t\t\t\t\t...(m.badges?.length ? { badges: [...m.badges] } : {}),\n\t\t\t\t\t\t\trequestSchema: capabilitiesToJsonSchema(m)\n\t\t\t\t\t\t}))\n\t\t\t\t})\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'plan_generation',\n\t\t\t\ttitle: 'Plan a generation request',\n\t\t\t\tdescription:\n\t\t\t\t\t'Checks a request against a model without generating: returns the adjusted request, every adjustment (field, from, to, reason) and errors.',\n\t\t\t\tinputSchema: planInput(),\n\t\t\t\tannotations: {\n\t\t\t\t\ttitle: 'Plan a generation request',\n\t\t\t\t\treadOnlyHint: true,\n\t\t\t\t\tdestructiveHint: false,\n\t\t\t\t\tidempotentHint: true,\n\t\t\t\t\topenWorldHint: false\n\t\t\t\t},\n\t\t\t\thandler: (args) => ({ plan: plan(args).plan })\n\t\t\t}\n\t\t];\n\n\t\tif (options.estimate) {\n\t\t\tconst estimate = options.estimate;\n\t\t\tlist.push({\n\t\t\t\tname: 'estimate_generation',\n\t\t\t\ttitle: 'Estimate a generation',\n\t\t\t\tdescription: 'Estimates cost and/or time of a request after planning it. Does not generate anything.',\n\t\t\t\tinputSchema: planInput(),\n\t\t\t\tannotations: {\n\t\t\t\t\ttitle: 'Estimate a generation',\n\t\t\t\t\treadOnlyHint: true,\n\t\t\t\t\tdestructiveHint: false,\n\t\t\t\t\tidempotentHint: true,\n\t\t\t\t\topenWorldHint: true\n\t\t\t\t},\n\t\t\t\thandler: async (args, context: ToolCallContext) => {\n\t\t\t\t\tconst planned = plan(args);\n\t\t\t\t\tif (!planned.plan.ok) return planFailure(planned.plan);\n\t\t\t\t\tconst value = await estimate(planned.plan.request, { ...planned, signal: context.signal });\n\t\t\t\t\treturn { plan: planned.plan, estimate: value };\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tconst generateInput = planInput();\n\t\tlist.push({\n\t\t\tname: 'generate',\n\t\t\ttitle: 'Generate media',\n\t\t\tdescription:\n\t\t\t\t'Plans the request and starts a generation. Pass a fresh `idempotencyKey` per intended generation and reuse it when retrying, so a retry never generates twice.',\n\t\t\tinputSchema: {\n\t\t\t\t...generateInput,\n\t\t\t\tproperties: {\n\t\t\t\t\t...generateInput.properties,\n\t\t\t\t\tidempotencyKey: {\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tminLength: 1,\n\t\t\t\t\t\tmaxLength: 200,\n\t\t\t\t\t\tdescription: 'Unique per intended generation; reuse it to retry safely.'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\trequired: ['model', 'request', 'idempotencyKey']\n\t\t\t},\n\t\t\tannotations: {\n\t\t\t\ttitle: 'Generate media',\n\t\t\t\treadOnlyHint: false,\n\t\t\t\tdestructiveHint: true,\n\t\t\t\tidempotentHint: true,\n\t\t\t\topenWorldHint: true\n\t\t\t},\n\t\t\thandler: async (args, context: ToolCallContext) => {\n\t\t\t\tconst key = String(args.idempotencyKey);\n\t\t\t\tconst { idempotencyKey: _key, ...rest } = args;\n\t\t\t\tconst signature = stableStringify(rest);\n\t\t\t\tconst existing = calls.get(key);\n\t\t\t\tif (existing) {\n\t\t\t\t\tif (existing.signature !== signature) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Idempotency key \"${key}\" was already used with different arguments. Use a new key for a new generation.`\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn { ...(await existing.result as object), deduplicated: true };\n\t\t\t\t}\n\t\t\t\tconst planned = plan(args);\n\t\t\t\tif (!planned.plan.ok) return planFailure(planned.plan);\n\t\t\t\tconst result = Promise.resolve()\n\t\t\t\t\t.then(() =>\n\t\t\t\t\t\toptions.generate(planned.plan.request, {\n\t\t\t\t\t\t\t...planned,\n\t\t\t\t\t\t\tidempotencyKey: key,\n\t\t\t\t\t\t\tsignal: context.signal\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\t.then((value) => ({\n\t\t\t\t\t\tresult: value,\n\t\t\t\t\t\tplan: planned.plan,\n\t\t\t\t\t\tidempotencyKey: key\n\t\t\t\t\t}));\n\t\t\t\tcalls.set(key, { signature, result });\n\t\t\t\twhile (calls.size > maxKeys) calls.delete(calls.keys().next().value as string);\n\t\t\t\ttry {\n\t\t\t\t\treturn await result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (calls.get(key)?.result === result) calls.delete(key);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn list;\n\t};\n\n\treturn {\n\t\tid: options.id ?? 'generation',\n\t\t...(options.namespace ? { namespace: options.namespace } : {}),\n\t\ttools,\n\t\t...(options.subscribe ? { subscribe: options.subscribe } : {}),\n\t\tclearIdempotencyKeys(key?: string) {\n\t\t\tif (key === undefined) calls.clear();\n\t\t\telse calls.delete(key);\n\t\t}\n\t};\n}\n","import { getLatestVersion, getTemplateBody, resolvePrompt } from '../grammar/engine.js';\nimport type { SourceMapSegment } from '../grammar/sourcemap.js';\nimport { tokenizePrompt } from '../grammar/tokenizer.js';\nimport type { PromptValues, Template } from '../grammar/tokens.js';\nimport type { GenerationRequest, ModelCapabilities, ReferenceRole } from './capabilities.js';\nimport type { GenerationPlan, PlanAdjustment } from './plan.js';\n\nexport const PROVENANCE_FORMAT = 'pixerate.generation-provenance';\nexport const PROVENANCE_VERSION = 1;\n\n/** The exact template body a generation used. */\nexport interface PinnedTemplate {\n\tname: string;\n\ttemplateId?: string;\n\t/** Version id or version label the body came from (absent for unversioned templates). */\n\tversionId?: string;\n\tversion?: string | number;\n\tbody: string;\n}\n\n/** Everything needed to explain and reproduce one generation. */\nexport interface GenerationProvenance {\n\tformat: typeof PROVENANCE_FORMAT;\n\tversion: typeof PROVENANCE_VERSION;\n\t/** Raw prompt as typed, with `{{templates}}` and `{variables}`. */\n\trawPrompt: string;\n\t/** Every template the prompt reached (recursively), pinned to the body used. */\n\ttemplates: PinnedTemplate[];\n\t/** Version bindings in effect (`{ templateName: versionId }`) for every pinned versioned template. */\n\tbindings: Record<string, string>;\n\t/** Variable values, or `null` when variables were left unresolved. */\n\tvalues: PromptValues | null;\n\tresolvedPrompt: string;\n\tsourceMap: SourceMapSegment[];\n\tmodelId: string;\n\t/** The planned (adjusted) request that was sent. */\n\trequest: GenerationRequest;\n\tadjustments: PlanAdjustment[];\n\treferences: { id: string; role: ReferenceRole }[];\n\t/** ISO timestamps. */\n\tcreatedAt: string;\n\tstartedAt?: string;\n\tcompletedAt?: string;\n\t/** Ids of the outputs this generation produced. */\n\toutputIds?: string[];\n\tmeta?: Record<string, unknown>;\n}\n\nexport interface CreateProvenanceOptions {\n\trawPrompt: string;\n\ttemplates?: Template[];\n\t/** Template version bindings (`{ templateName: versionIdOrVersion }`). */\n\tbindings?: Record<string, string>;\n\tvalues?: PromptValues;\n\tmodel: string | ModelCapabilities;\n\t/** The plan the request came from (its request and adjustments are recorded) … */\n\tplan?: GenerationPlan;\n\t/** … or the request that was sent. */\n\trequest?: GenerationRequest;\n\tadjustments?: PlanAdjustment[];\n\t/** Creation time (default `Date.now()`). */\n\tcreatedAt?: Date | number | string;\n\tstartedAt?: Date | number | string;\n\tcompletedAt?: Date | number | string;\n\toutputIds?: string[];\n\tmeta?: Record<string, unknown>;\n\tmaxDepth?: number;\n}\n\nexport class ProvenanceParseError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'ProvenanceParseError';\n\t}\n}\n\nconst toIso = (value: Date | number | string) => new Date(value).toISOString();\nconst clone = <T>(value: T): T => (value === undefined ? value : JSON.parse(JSON.stringify(value)));\n\n/** Collects the templates a prompt reaches, with the body each one resolves to. */\nfunction pinTemplates(\n\trawPrompt: string,\n\ttemplates: Template[],\n\tbindings: Record<string, string>,\n\tmaxDepth: number\n): PinnedTemplate[] {\n\tconst byName = new Map(templates.map((t) => [t.name, t]));\n\tconst pinned = new Map<string, PinnedTemplate>();\n\tconst visit = (text: string, depth: number) => {\n\t\tif (depth > maxDepth) return;\n\t\tfor (const token of tokenizePrompt(text)) {\n\t\t\tif (token.type !== 'template' || pinned.has(token.value)) continue;\n\t\t\tconst template = byName.get(token.value);\n\t\t\tif (!template) continue;\n\t\t\t// Mirror `getTemplateBody` so the pinned body is the one `resolvePrompt` used.\n\t\t\tconst body = getTemplateBody(template, bindings);\n\t\t\tif (body === undefined) continue;\n\t\t\tconst bound = bindings[template.name];\n\t\t\tconst version =\n\t\t\t\tbound && template.versions\n\t\t\t\t\t? template.versions.find((v) => v.id === bound || v.version === bound)\n\t\t\t\t\t: getLatestVersion(template);\n\t\t\tconst isVersioned = !!template.versions?.length && !!version;\n\t\t\tpinned.set(template.name, {\n\t\t\t\tname: template.name,\n\t\t\t\t...(template.id !== undefined ? { templateId: template.id } : {}),\n\t\t\t\t...(isVersioned ? { versionId: version!.id, version: version!.version } : {}),\n\t\t\t\tbody\n\t\t\t});\n\t\t\tvisit(body, depth + 1);\n\t\t}\n\t};\n\tvisit(rawPrompt, 0);\n\treturn [...pinned.values()];\n}\n\n/** Templates that reproduce the pinned bodies exactly (one single-version template each). */\nexport function pinnedTemplatesToTemplates(pinned: readonly PinnedTemplate[]): Template[] {\n\treturn pinned.map((p) =>\n\t\tp.versionId !== undefined\n\t\t\t? {\n\t\t\t\t\t...(p.templateId !== undefined ? { id: p.templateId } : {}),\n\t\t\t\t\tname: p.name,\n\t\t\t\t\tlatestVersion: p.version,\n\t\t\t\t\tversions: [{ id: p.versionId, version: p.version ?? p.versionId, body: p.body }]\n\t\t\t\t}\n\t\t\t: { ...(p.templateId !== undefined ? { id: p.templateId } : {}), name: p.name, body: p.body }\n\t);\n}\n\n/**\n * Records what produced an output: the raw prompt, the exact template bodies\n * and versions it reached, variable values, the resolved prompt and its\n * source map, the model, the planned settings and adjustments, and the\n * references by role.\n */\nexport function createProvenance(options: CreateProvenanceOptions): GenerationProvenance {\n\tconst templates = options.templates ?? [];\n\tconst maxDepth = options.maxDepth ?? 10;\n\tconst resolved = resolvePrompt(options.rawPrompt, templates, {\n\t\tbindings: options.bindings,\n\t\tvalues: options.values,\n\t\tmaxDepth\n\t});\n\tconst pinned = pinTemplates(options.rawPrompt, templates, options.bindings ?? {}, maxDepth);\n\tconst bindings: Record<string, string> = {};\n\tfor (const p of pinned) if (p.versionId !== undefined) bindings[p.name] = p.versionId;\n\n\tconst modelId = typeof options.model === 'string' ? options.model : options.model.id;\n\tconst request: GenerationRequest = clone(options.plan?.request ?? options.request ?? {});\n\trequest.model = modelId;\n\tif (request.prompt === undefined) request.prompt = resolved.text;\n\tconst adjustments = clone(options.adjustments ?? options.plan?.adjustments ?? []);\n\n\treturn {\n\t\tformat: PROVENANCE_FORMAT,\n\t\tversion: PROVENANCE_VERSION,\n\t\trawPrompt: options.rawPrompt,\n\t\ttemplates: pinned,\n\t\tbindings,\n\t\tvalues: options.values ? clone(options.values) : null,\n\t\tresolvedPrompt: resolved.text,\n\t\tsourceMap: resolved.map.map((segment) => {\n\t\t\tconst { locked: _locked, ...rest } = segment as SourceMapSegment & { locked?: boolean };\n\t\t\treturn rest;\n\t\t}),\n\t\tmodelId,\n\t\trequest,\n\t\tadjustments,\n\t\treferences: (request.references ?? []).map(({ id, role }) => ({ id, role })),\n\t\tcreatedAt: toIso(options.createdAt ?? Date.now()),\n\t\t...(options.startedAt !== undefined ? { startedAt: toIso(options.startedAt) } : {}),\n\t\t...(options.completedAt !== undefined ? { completedAt: toIso(options.completedAt) } : {}),\n\t\t...(options.outputIds ? { outputIds: [...options.outputIds] } : {}),\n\t\t...(options.meta ? { meta: clone(options.meta) } : {})\n\t};\n}\n\n/** Versioned JSON for storage next to an output. */\nexport function serializeProvenance(provenance: GenerationProvenance): string {\n\treturn JSON.stringify(provenance);\n}\n\nconst isRecord = (value: unknown): value is Record<string, any> =>\n\t!!value && typeof value === 'object' && !Array.isArray(value);\n\n/** Parses and checks serialized provenance. Throws {@link ProvenanceParseError}. */\nexport function parseProvenance(input: string | unknown): GenerationProvenance {\n\tlet data: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tdata = JSON.parse(input);\n\t\t} catch {\n\t\t\tthrow new ProvenanceParseError('Provenance is not valid JSON.');\n\t\t}\n\t}\n\tif (!isRecord(data) || data.format !== PROVENANCE_FORMAT) {\n\t\tthrow new ProvenanceParseError('Not a generation provenance record.');\n\t}\n\tif (typeof data.version !== 'number' || data.version > PROVENANCE_VERSION) {\n\t\tthrow new ProvenanceParseError(`Unsupported provenance version ${JSON.stringify(data.version)}.`);\n\t}\n\tconst checks: [string, boolean][] = [\n\t\t['rawPrompt', typeof data.rawPrompt === 'string'],\n\t\t['resolvedPrompt', typeof data.resolvedPrompt === 'string'],\n\t\t['modelId', typeof data.modelId === 'string'],\n\t\t['createdAt', typeof data.createdAt === 'string'],\n\t\t['request', isRecord(data.request)],\n\t\t['templates', Array.isArray(data.templates) && data.templates.every((t: any) => isRecord(t) && typeof t.name === 'string' && typeof t.body === 'string')],\n\t\t['sourceMap', Array.isArray(data.sourceMap)],\n\t\t['adjustments', Array.isArray(data.adjustments)],\n\t\t['references', Array.isArray(data.references)]\n\t];\n\tconst bad = checks.find(([, ok]) => !ok);\n\tif (bad) throw new ProvenanceParseError(`Invalid provenance field \"${bad[0]}\".`);\n\treturn {\n\t\t...(data as GenerationProvenance),\n\t\tbindings: isRecord(data.bindings) ? data.bindings : {},\n\t\tvalues: isRecord(data.values) ? data.values : null\n\t};\n}\n\n/**\n * Re-resolves the raw prompt against the pinned template bodies and values,\n * e.g. to check a record or to open it in an editor with its source map.\n */\nexport function replayProvenancePrompt(provenance: GenerationProvenance): {\n\ttext: string;\n\tmap: SourceMapSegment[];\n\tmatches: boolean;\n} {\n\tconst result = resolvePrompt(provenance.rawPrompt, pinnedTemplatesToTemplates(provenance.templates), {\n\t\tbindings: provenance.bindings,\n\t\t...(provenance.values ? { values: provenance.values } : {})\n\t});\n\treturn { text: result.text, map: result.map, matches: result.text === provenance.resolvedPrompt };\n}\n","export interface BatchResolverOptions<K, V> {\n\t/** Loads many keys at once. Keys missing from the map resolve to `undefined`. */\n\tfetch: (keys: K[]) => Promise<Map<K, V>>;\n\t/** Largest batch passed to `fetch` (default 50). */\n\tmaxBatch?: number;\n\t/** How long to collect keys before fetching, in ms (default 0: the same tick). */\n\tdelayMs?: number;\n\t/** Called once per failed batch. The batch's keys are recorded as resolved-empty. */\n\tonError?: (error: unknown, keys: K[]) => void;\n}\n\nexport interface BatchResolver<K, V> {\n\t/** Resolves one key, batched with other loads in the same window. Never rejects. */\n\tload(key: K): Promise<V | undefined>;\n\tloadMany(keys: readonly K[]): Promise<(V | undefined)[]>;\n\t/** The settled value for a key, if it has been resolved. */\n\tpeek(key: K): V | undefined;\n\t/** Whether a key has settled (found, missing or failed). */\n\thas(key: K): boolean;\n\t/** Whether a key's last fetch failed. */\n\tfailed(key: K): boolean;\n\t/** Seeds a value without fetching. */\n\tprime(key: K, value: V): void;\n\t/** Forgets one key (or all) so the next `load` fetches again. */\n\tclear(key?: K): void;\n\t/** Fetches anything still waiting now, without waiting for `delayMs`. */\n\tflush(): void;\n}\n\ninterface Pending<V> {\n\tpromise: Promise<V | undefined>;\n\tresolve: (value: V | undefined) => void;\n}\n\n/**\n * Coalesces lookups (e.g. asset ids → thumbnails, template ids → bodies) into\n * batched `fetch` calls: keys requested within `delayMs` share a batch of at\n * most `maxBatch`, duplicate keys are fetched once, and results are cached.\n * A failed batch resolves its keys to `undefined` and records them as\n * resolved-empty (`failed(key)` is `true`) so one bad response does not cause\n * a retry storm; `clear(key)` allows a retry.\n */\nexport function createBatchResolver<K, V>(options: BatchResolverOptions<K, V>): BatchResolver<K, V> {\n\tconst maxBatch = Math.max(1, Math.floor(options.maxBatch ?? 50));\n\tconst delayMs = Math.max(0, options.delayMs ?? 0);\n\tconst settled = new Map<K, V | undefined>();\n\tconst failures = new Set<K>();\n\tconst inFlight = new Map<K, Pending<V>>();\n\tlet queue: K[] = [];\n\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\n\tconst run = (keys: K[]) => {\n\t\tconst pendings = keys.map((key) => inFlight.get(key));\n\t\tconst finish = (values: Map<K, V> | null, error?: unknown) => {\n\t\t\tif (values === null) options.onError?.(error, keys);\n\t\t\tkeys.forEach((key, index) => {\n\t\t\t\tconst pending = pendings[index];\n\t\t\t\tconst value = values?.get(key);\n\t\t\t\t// Only cache if nobody cleared the key while the batch was in flight.\n\t\t\t\tif (pending && inFlight.get(key) === pending) {\n\t\t\t\t\tinFlight.delete(key);\n\t\t\t\t\tsettled.set(key, value);\n\t\t\t\t\tif (values === null) failures.add(key);\n\t\t\t\t}\n\t\t\t\tpending?.resolve(value);\n\t\t\t});\n\t\t};\n\t\tlet promise: Promise<Map<K, V>>;\n\t\ttry {\n\t\t\tpromise = Promise.resolve(options.fetch(keys));\n\t\t} catch (error) {\n\t\t\tpromise = Promise.reject(error);\n\t\t}\n\t\tpromise.then(\n\t\t\t(values) => finish(values instanceof Map ? values : new Map()),\n\t\t\t(error) => finish(null, error ?? new Error('Batch fetch failed.'))\n\t\t);\n\t};\n\n\tconst flush = () => {\n\t\tif (timer !== null) clearTimeout(timer);\n\t\ttimer = null;\n\t\tconst keys = queue;\n\t\tqueue = [];\n\t\tfor (let i = 0; i < keys.length; i += maxBatch) run(keys.slice(i, i + maxBatch));\n\t};\n\n\tconst load = (key: K): Promise<V | undefined> => {\n\t\tif (settled.has(key)) return Promise.resolve(settled.get(key));\n\t\tconst existing = inFlight.get(key);\n\t\tif (existing) return existing.promise;\n\t\tlet resolve!: (value: V | undefined) => void;\n\t\tconst promise = new Promise<V | undefined>((r) => (resolve = r));\n\t\tinFlight.set(key, { promise, resolve });\n\t\tqueue.push(key);\n\t\tif (queue.length >= maxBatch) flush();\n\t\telse if (timer === null) timer = setTimeout(flush, delayMs);\n\t\treturn promise;\n\t};\n\n\treturn {\n\t\tload,\n\t\tloadMany: (keys) => Promise.all(keys.map(load)),\n\t\tpeek: (key) => settled.get(key),\n\t\thas: (key) => settled.has(key),\n\t\tfailed: (key) => failures.has(key),\n\t\tprime(key, value) {\n\t\t\tsettled.set(key, value);\n\t\t\tfailures.delete(key);\n\t\t},\n\t\tclear(key) {\n\t\t\tif (key === undefined) {\n\t\t\t\tsettled.clear();\n\t\t\t\tfailures.clear();\n\t\t\t\t// Loads already fetching still resolve, but their results are not cached.\n\t\t\t\tfor (const k of [...inFlight.keys()]) if (!queue.includes(k)) inFlight.delete(k);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled.delete(key);\n\t\t\tfailures.delete(key);\n\t\t\tif (!queue.includes(key)) inFlight.delete(key);\n\t\t},\n\t\tflush\n\t};\n}\n","import type { GenerationRequest, ModelCapabilities } from './capabilities.js';\nimport { resolutionMegapixels } from './plan.js';\n\nexport interface JobProgressInput {\n\t/** Start time (ms since epoch, `Date` or ISO string). */\n\tstartedAt: number | Date | string;\n\t/** Current time (default `Date.now()`). */\n\tnow?: number | Date | string;\n\t/** Expected total duration in ms. Without it progress eases towards 95%. */\n\texpectedMs?: number;\n\t/** The job finished. */\n\tdone?: boolean;\n}\n\nexport interface JobProgressEstimate {\n\t/** 0–100. Never above 95 until `done`. */\n\tpercent: number;\n\t/** E.g. `~2m left`, `~40s left`, `Almost done…`, `Starting…`, `Done`. */\n\tlabel: string;\n\telapsedMs: number;\n\t/** Estimated ms left (`undefined` without `expectedMs`). */\n\tremainingMs?: number;\n\t/** Running longer than expected. */\n\toverdue: boolean;\n}\n\n/** Highest percentage shown before a job reports completion. */\nexport const MAX_PENDING_PERCENT = 95;\n/** Remaining time under which the label switches to \"Almost done…\". */\nconst ALMOST_DONE_MS = 5_000;\n/** Time constant for jobs without an expected duration. */\nconst UNKNOWN_DURATION_TAU_MS = 30_000;\n\nconst toMs = (value: number | Date | string) => (typeof value === 'number' ? value : new Date(value).getTime());\n\n/** `~45s left`, `~2m left`, `~1h 5m left`. Minutes and seconds round up. */\nexport function formatRemaining(ms: number): string {\n\tconst seconds = Math.ceil(ms / 1000);\n\tif (seconds < 60) return `~${Math.max(1, seconds)}s left`;\n\tconst minutes = Math.ceil(ms / 60_000);\n\tif (minutes < 60) return `~${minutes}m left`;\n\tconst hours = Math.floor(minutes / 60);\n\tconst rest = minutes % 60;\n\treturn rest ? `~${hours}h ${rest}m left` : `~${hours}h left`;\n}\n\n/**\n * Progress for a job that reports no progress of its own: linear in elapsed\n * time over `expectedMs`, held at 95% until `done` (so the bar never sits at\n * 100% while still waiting), with a \"~2m left\" style label that turns into\n * \"Almost done…\" near or past the expected end. Pure: pass `now` to test.\n */\nexport function estimateJobProgress(input: JobProgressInput): JobProgressEstimate {\n\tconst started = toMs(input.startedAt);\n\tconst now = toMs(input.now ?? Date.now());\n\tconst elapsedMs = Math.max(0, now - started);\n\tif (input.done) return { percent: 100, label: 'Done', elapsedMs, remainingMs: 0, overdue: false };\n\tif (!Number.isFinite(started) || now <= started) {\n\t\treturn { percent: 0, label: 'Starting…', elapsedMs: 0, ...(input.expectedMs ? { remainingMs: input.expectedMs } : {}), overdue: false };\n\t}\n\tconst expected = input.expectedMs;\n\tif (!expected || !(expected > 0)) {\n\t\tconst percent = MAX_PENDING_PERCENT * (1 - Math.exp(-elapsedMs / UNKNOWN_DURATION_TAU_MS));\n\t\treturn { percent: Math.floor(percent), label: 'Working…', elapsedMs, overdue: false };\n\t}\n\tconst remainingMs = Math.max(0, expected - elapsedMs);\n\tconst percent = Math.min(MAX_PENDING_PERCENT, Math.floor((elapsedMs / expected) * 100));\n\treturn {\n\t\tpercent,\n\t\tlabel: remainingMs <= ALMOST_DONE_MS ? 'Almost done…' : formatRemaining(remainingMs),\n\t\telapsedMs,\n\t\tremainingMs,\n\t\toverdue: elapsedMs > expected\n\t};\n}\n\n/**\n * Expected duration of a request from the model's `timing`: `baseMs`, plus\n * `perItemMs` per output beyond the first, `perSecondMs` per second of\n * duration and `perMegapixelMs` per megapixel of resolution. `undefined` when\n * the model has no timing.\n */\nexport function expectedDurationFor(caps: ModelCapabilities, request: GenerationRequest = {}): number | undefined {\n\tconst timing = caps.timing;\n\tif (!timing) return undefined;\n\tconst count = request.count ?? caps.count?.default ?? 1;\n\tconst duration = request.duration ?? caps.duration?.default ?? 0;\n\tconst resolution = request.resolution ?? caps.resolutions?.default;\n\tconst megapixels = resolution ? (resolutionMegapixels(resolution) ?? 0) : 0;\n\treturn Math.round(\n\t\ttiming.baseMs +\n\t\t\t(timing.perItemMs ?? 0) * Math.max(0, count - 1) +\n\t\t\t(timing.perSecondMs ?? 0) * duration +\n\t\t\t(timing.perMegapixelMs ?? 0) * megapixels\n\t);\n}\n","import {\n\treferenceRoleLabel,\n\tseedCapability,\n\tsupportedModes,\n\tsupportedReferenceRoles,\n\ttextFieldCapability,\n\ttype EnumCapability,\n\ttype GenerationParamValue,\n\ttype GenerationReference,\n\ttype GenerationRequest,\n\ttype ModelCapabilities,\n\ttype ReferenceRole\n} from './capabilities.js';\n\nexport interface GenerationControlOption {\n\tvalue: string | number;\n\tlabel: string;\n}\n\n/**\n * A UI control derived from capabilities. `key` is a request path:\n * `aspectRatio`, `count`, `params.guidance`, …\n */\nexport interface GenerationControl {\n\tkey: string;\n\tlabel: string;\n\t/** `select` for choices, `range` for bounded numbers, `number` for open numbers. */\n\tkind: 'select' | 'range' | 'number' | 'textarea' | 'checkbox';\n\tvalueType: 'string' | 'number' | 'boolean';\n\toptions?: GenerationControlOption[];\n\tmin?: number;\n\tmax?: number;\n\tstep?: number;\n\tmaxLength?: number;\n\tdefaultValue?: string | number | boolean;\n\tdescription?: string;\n}\n\nexport interface ReferenceSlot {\n\trole: ReferenceRole;\n\tlabel: string;\n\tdescription?: string;\n\tmin: number;\n\tmax: number;\n\t/** Media accepted for this role (see `ReferenceRoleCapability.accepts`). */\n\taccepts?: string[];\n\treferences: GenerationReference[];\n\t/** More references of this role fit (per role and in total). */\n\tcanAdd: boolean;\n}\n\nfunction enumControl(key: string, label: string, cap: EnumCapability<string> | undefined): GenerationControl | null {\n\tif (!cap?.options.length) return null;\n\treturn {\n\t\tkey,\n\t\tlabel,\n\t\tkind: 'select',\n\t\tvalueType: 'string',\n\t\toptions: cap.options.map((value) => ({ value, label: cap.labels?.[value] ?? value })),\n\t\t...(cap.default !== undefined ? { defaultValue: cap.default } : {})\n\t};\n}\n\n/**\n * The settings controls a model supports, in display order. Excludes the\n * prompt (edited by the prompt editor) and references (see\n * {@link getReferenceSlots}).\n */\nexport function getGenerationControls(caps: ModelCapabilities): GenerationControl[] {\n\tconst controls: (GenerationControl | null)[] = [];\n\tconst modes = supportedModes(caps);\n\tif (modes.length > 1) {\n\t\tcontrols.push({\n\t\t\tkey: 'mode',\n\t\t\tlabel: 'Mode',\n\t\t\tkind: 'select',\n\t\t\tvalueType: 'string',\n\t\t\toptions: modes.map((m) => ({ value: m, label: m[0].toUpperCase() + m.slice(1) })),\n\t\t\tdefaultValue: 'generate'\n\t\t});\n\t}\n\tconst negative = textFieldCapability(caps.negativePrompt);\n\tif (negative) controls.push({ key: 'negativePrompt', label: 'Negative prompt', kind: 'textarea', valueType: 'string', maxLength: negative.maxLength });\n\tconst system = textFieldCapability(caps.systemPrompt);\n\tif (system) controls.push({ key: 'systemPrompt', label: 'System prompt', kind: 'textarea', valueType: 'string', maxLength: system.maxLength });\n\tcontrols.push(enumControl('aspectRatio', 'Aspect ratio', caps.aspectRatios));\n\tcontrols.push(enumControl('resolution', 'Resolution', caps.resolutions));\n\tcontrols.push(enumControl('quality', 'Quality', caps.quality));\n\n\tif (caps.count) {\n\t\tconst options = caps.count.options?.filter((o) => o >= 1 && o <= caps.count!.max);\n\t\tcontrols.push(\n\t\t\toptions?.length\n\t\t\t\t? { key: 'count', label: 'Outputs', kind: 'select', valueType: 'number', options: options.map((o) => ({ value: o, label: String(o) })), defaultValue: caps.count.default }\n\t\t\t\t: { key: 'count', label: 'Outputs', kind: 'range', valueType: 'number', min: 1, max: caps.count.max, step: 1, defaultValue: caps.count.default ?? 1 }\n\t\t);\n\t}\n\tif (caps.duration) {\n\t\tconst d = caps.duration;\n\t\tconst options = d.options?.filter((o) => (d.min === undefined || o >= d.min) && (d.max === undefined || o <= d.max));\n\t\tcontrols.push(\n\t\t\toptions?.length\n\t\t\t\t? { key: 'duration', label: 'Duration (s)', kind: 'select', valueType: 'number', options: options.map((o) => ({ value: o, label: `${o}s` })), defaultValue: d.default }\n\t\t\t\t: {\n\t\t\t\t\t\tkey: 'duration',\n\t\t\t\t\t\tlabel: 'Duration (s)',\n\t\t\t\t\t\tkind: d.min !== undefined && d.max !== undefined ? 'range' : 'number',\n\t\t\t\t\t\tvalueType: 'number',\n\t\t\t\t\t\tmin: d.min,\n\t\t\t\t\t\tmax: d.max,\n\t\t\t\t\t\tstep: d.step,\n\t\t\t\t\t\tdefaultValue: d.default\n\t\t\t\t\t}\n\t\t);\n\t}\n\tconst seed = seedCapability(caps.seed);\n\tif (seed) controls.push({ key: 'seed', label: 'Seed', kind: 'number', valueType: 'number', min: seed.min, max: seed.max, step: 1 });\n\n\tfor (const [name, spec] of Object.entries(caps.params ?? {})) {\n\t\tconst base = { key: `params.${name}`, label: spec.label ?? name, ...(spec.description ? { description: spec.description } : {}) };\n\t\tif (spec.type === 'boolean') {\n\t\t\tcontrols.push({ ...base, kind: 'checkbox', valueType: 'boolean', defaultValue: spec.default });\n\t\t} else if (spec.type === 'enum') {\n\t\t\tconst numeric = spec.options.every((o) => typeof o === 'number');\n\t\t\tcontrols.push({\n\t\t\t\t...base,\n\t\t\t\tkind: 'select',\n\t\t\t\tvalueType: numeric ? 'number' : 'string',\n\t\t\t\toptions: spec.options.map((value) => ({ value, label: String(value) })),\n\t\t\t\tdefaultValue: spec.default\n\t\t\t});\n\t\t} else {\n\t\t\tcontrols.push({\n\t\t\t\t...base,\n\t\t\t\tkind: spec.min !== undefined && spec.max !== undefined ? 'range' : 'number',\n\t\t\t\tvalueType: 'number',\n\t\t\t\tmin: spec.min,\n\t\t\t\tmax: spec.max,\n\t\t\t\tstep: spec.step ?? (spec.type === 'integer' ? 1 : undefined),\n\t\t\t\tdefaultValue: spec.default\n\t\t\t});\n\t\t}\n\t}\n\treturn controls.filter((c): c is GenerationControl => c !== null);\n}\n\n/** Reads a request value by control key (`params.x` reads a param). */\nexport function getRequestValue(request: GenerationRequest, key: string): unknown {\n\tif (key.startsWith('params.')) return request.params?.[key.slice(7)];\n\treturn (request as Record<string, unknown>)[key];\n}\n\n/** Returns a copy of `request` with `key` set (`undefined` or `''` removes it). */\nexport function setRequestValue(request: GenerationRequest, key: string, value: unknown): GenerationRequest {\n\tconst remove = value === undefined || value === '' || (typeof value === 'number' && Number.isNaN(value));\n\tif (key.startsWith('params.')) {\n\t\tconst name = key.slice(7);\n\t\tconst params = { ...(request.params ?? {}) };\n\t\tif (remove) delete params[name];\n\t\telse params[name] = value as GenerationParamValue;\n\t\tconst next: GenerationRequest = { ...request, params };\n\t\tif (Object.keys(params).length === 0) delete next.params;\n\t\treturn next;\n\t}\n\tconst next = { ...request } as Record<string, unknown>;\n\tif (remove) delete next[key];\n\telse next[key] = value;\n\treturn next as GenerationRequest;\n}\n\n/** Parses a DOM control's string value into the control's value type. */\nexport function parseControlValue(control: GenerationControl, raw: string | boolean): string | number | boolean | undefined {\n\tif (control.valueType === 'boolean') return Boolean(raw);\n\tif (raw === '' || raw === undefined) return undefined;\n\tif (control.valueType === 'number') {\n\t\tconst n = Number(raw);\n\t\treturn Number.isFinite(n) ? n : undefined;\n\t}\n\treturn String(raw);\n}\n\n/** Reference slots for every role the model accepts, filled from `request`. */\nexport function getReferenceSlots(caps: ModelCapabilities, request: GenerationRequest = {}): ReferenceSlot[] {\n\tconst roles = supportedReferenceRoles(caps);\n\tif (caps.inpainting && !roles.includes('mask')) roles.push('mask');\n\tconst refs = request.references ?? [];\n\tconst total = refs.length;\n\tconst maxTotal = caps.references?.maxTotal ?? Infinity;\n\treturn roles.map((role) => {\n\t\tconst cap = caps.references?.roles[role] ?? { max: 1 };\n\t\tconst references = refs.filter((ref) => ref.role === role);\n\t\treturn {\n\t\t\trole,\n\t\t\tlabel: referenceRoleLabel(role, caps),\n\t\t\t...(cap.description ? { description: cap.description } : {}),\n\t\t\t...('accepts' in cap && cap.accepts?.length ? { accepts: [...cap.accepts] } : {}),\n\t\t\tmin: cap.min ?? 0,\n\t\t\tmax: cap.max,\n\t\t\treferences,\n\t\t\tcanAdd: references.length < cap.max && total < maxTotal\n\t\t};\n\t});\n}\n\n/** Returns a copy of `request` with a reference appended. */\nexport function addReference(request: GenerationRequest, reference: GenerationReference): GenerationRequest {\n\treturn { ...request, references: [...(request.references ?? []), { ...reference }] };\n}\n\n/** Returns a copy of `request` without the reference `id` in `role`. */\nexport function removeReference(request: GenerationRequest, role: ReferenceRole, id: string): GenerationRequest {\n\tconst references = (request.references ?? []).filter((ref) => !(ref.role === role && ref.id === id));\n\tconst next: GenerationRequest = { ...request, references };\n\tif (references.length === 0) delete next.references;\n\treturn next;\n}\n","import {\n\tREFERENCE_ROLES,\n\tmediaTypeMatches,\n\tnormalizeReferenceRole,\n\treferenceRoleLabel,\n\tsupportedReferenceRoles,\n\ttype ModelCapabilities,\n\ttype ReferenceRole,\n\ttype ReferenceRoleCapability\n} from './capabilities.js';\nimport type { PlanAssetMention } from './plan.js';\n\n/** A role a model accepts for asset mentions / references, for pickers. */\nexport interface AssetRoleOption {\n\trole: ReferenceRole;\n\tlabel: string;\n\tdescription?: string;\n\tmin: number;\n\tmax: number;\n\taccepts?: string[];\n}\n\n/** What the host knows about an asset (for planning, records and drift checks). */\nexport interface AssetInfo {\n\tid: string;\n\tlabel?: string;\n\t/** MIME type or media kind (`image/png`, `image`). */\n\tmediaType?: string;\n\t/** Content hash; a change is reported as `asset-changed` drift. */\n\tcontentHash?: string;\n\turl?: string;\n}\n\n/** Assets by id: a lookup function, a map, or a list. */\nexport type AssetLookup =\n\t| ((id: string) => AssetInfo | null | undefined)\n\t| ReadonlyMap<string, AssetInfo>\n\t| readonly AssetInfo[];\n\n/** Normalizes an {@link AssetLookup} to a function. */\nexport function toAssetResolver(lookup: AssetLookup | undefined): ((id: string) => AssetInfo | undefined) | undefined {\n\tif (!lookup) return undefined;\n\tif (typeof lookup === 'function') return (id) => lookup(id) ?? undefined;\n\tif (lookup instanceof Map) return (id) => lookup.get(id);\n\tconst byId = new Map((lookup as readonly AssetInfo[]).map((asset) => [asset.id, asset]));\n\treturn (id) => byId.get(id);\n}\n\nfunction roleCap(caps: ModelCapabilities, role: ReferenceRole): ReferenceRoleCapability | undefined {\n\tconst cap = caps.references?.roles[role];\n\tif (cap && cap.max > 0) return cap;\n\tif (role === 'mask' && caps.inpainting) return { max: 1 };\n\treturn undefined;\n}\n\n/** The roles a model accepts, with labels, limits and accepted media (inpainting adds `mask`). */\nexport function getAssetRoleOptions(caps: ModelCapabilities): AssetRoleOption[] {\n\tconst roles = supportedReferenceRoles(caps);\n\tif (caps.inpainting && !roles.includes('mask')) roles.push('mask');\n\treturn roles.map((role) => {\n\t\tconst cap = roleCap(caps, role) ?? { max: 1 };\n\t\treturn {\n\t\t\trole,\n\t\t\tlabel: referenceRoleLabel(role, caps),\n\t\t\t...(cap.description ? { description: cap.description } : {}),\n\t\t\tmin: cap.min ?? 0,\n\t\t\tmax: cap.max,\n\t\t\t...(cap.accepts?.length ? { accepts: [...cap.accepts] } : {})\n\t\t};\n\t});\n}\n\nexport type AssetMentionIssueCode = 'missing-role' | 'unsupported-role' | 'renamed-role' | 'media-mismatch' | 'too-many';\n\nexport interface AssetMentionIssue {\n\t/** Index of the mention in the input. */\n\tindex: number;\n\tid: string;\n\trole?: ReferenceRole;\n\tcode: AssetMentionIssueCode;\n\t/** `error` blocks generation under the `error` strategy; `warning` is fixed by planning. */\n\tseverity: 'error' | 'warning';\n\tmessage: string;\n\t/** The role the planner will use instead (`renamed-role`). */\n\tsuggestion?: ReferenceRole;\n}\n\nexport interface AssetMentionValidation {\n\tok: boolean;\n\tissues: AssetMentionIssue[];\n\t/** Mentions per (canonical) role. */\n\tcounts: Record<string, number>;\n\t/** Roles below their `min`. */\n\tmissing: { role: ReferenceRole; min: number; count: number }[];\n}\n\n/**\n * Checks asset mentions against what a model accepts, per mention, for UI\n * feedback (badges, popovers). Mirrors the planner: aliases (`style` →\n * `style_ref`) are accepted with a `renamed-role` warning, unsupported roles,\n * media types the role does not accept and mentions beyond a role's `max`\n * are reported, and roles below their `min` are listed in `missing`.\n */\nexport function validateAssetMentions(\n\tmentions: readonly PlanAssetMention[],\n\tcaps: ModelCapabilities,\n\toptions: { assets?: AssetLookup } = {}\n): AssetMentionValidation {\n\tconst resolve = toAssetResolver(options.assets);\n\tconst issues: AssetMentionIssue[] = [];\n\tconst counts: Record<string, number> = {};\n\tmentions.forEach((mention, index) => {\n\t\tconst id = mention.id;\n\t\tconst label = `@${mention.label ?? id}`;\n\t\tif (!mention.role) {\n\t\t\tissues.push({ index, id, code: 'missing-role', severity: 'warning', message: `${label} has no role.` });\n\t\t\treturn;\n\t\t}\n\t\tlet role: ReferenceRole = mention.role;\n\t\tif (!roleCap(caps, role)) {\n\t\t\tconst alias = normalizeReferenceRole(role, caps);\n\t\t\tif (alias !== role && roleCap(caps, alias)) {\n\t\t\t\tissues.push({\n\t\t\t\t\tindex,\n\t\t\t\t\tid,\n\t\t\t\t\trole,\n\t\t\t\t\tcode: 'renamed-role',\n\t\t\t\t\tseverity: 'warning',\n\t\t\t\t\tsuggestion: alias,\n\t\t\t\t\tmessage: `${label}: this model calls \"${role}\" ${referenceRoleLabel(alias, caps)} (${alias}).`\n\t\t\t\t});\n\t\t\t\trole = alias;\n\t\t\t} else {\n\t\t\t\tissues.push({\n\t\t\t\t\tindex,\n\t\t\t\t\tid,\n\t\t\t\t\trole,\n\t\t\t\t\tcode: 'unsupported-role',\n\t\t\t\t\tseverity: 'error',\n\t\t\t\t\tmessage: `${label}: ${caps.name ?? caps.id} does not accept ${referenceRoleLabel(role, caps).toLowerCase()} assets.`\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tconst cap = roleCap(caps, role)!;\n\t\tconst mediaType = mention.mediaType ?? resolve?.(id)?.mediaType;\n\t\tif (mediaType !== undefined && !mediaTypeMatches(mediaType, cap.accepts)) {\n\t\t\tissues.push({\n\t\t\t\tindex,\n\t\t\t\tid,\n\t\t\t\trole,\n\t\t\t\tcode: 'media-mismatch',\n\t\t\t\tseverity: 'error',\n\t\t\t\tmessage: `${label} is ${mediaType}; ${referenceRoleLabel(role, caps)} accepts ${(cap.accepts ?? []).join(', ')}.`\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tcounts[role] = (counts[role] ?? 0) + 1;\n\t\tif (counts[role] > cap.max) {\n\t\t\tissues.push({\n\t\t\t\tindex,\n\t\t\t\tid,\n\t\t\t\trole,\n\t\t\t\tcode: 'too-many',\n\t\t\t\tseverity: 'warning',\n\t\t\t\tmessage: `${label}: at most ${cap.max} ${referenceRoleLabel(role, caps).toLowerCase()} asset(s); extra ones are left out.`\n\t\t\t});\n\t\t}\n\t});\n\tconst missing = getAssetRoleOptions(caps)\n\t\t.filter((option) => (counts[option.role] ?? 0) < option.min)\n\t\t.map((option) => ({ role: option.role, min: option.min, count: counts[option.role] ?? 0 }));\n\treturn { ok: !issues.some((issue) => issue.severity === 'error') && missing.length === 0, issues, counts, missing };\n}\n\n/**\n * Joins mentions (`{ handle, role }`, e.g. from `extractMentions(text, { details: true, roles: true })`)\n * with your assets: each handle is looked up by id, then by label\n * (case-insensitive). Mentions that are not assets are left out.\n */\nexport function resolveAssetMentions(\n\tmentions: readonly { handle: string; role?: string | null }[],\n\tassets: readonly AssetInfo[]\n): PlanAssetMention[] {\n\tconst byId = new Map(assets.map((asset) => [asset.id, asset]));\n\tconst byLabel = new Map(assets.filter((a) => a.label).map((asset) => [asset.label!.toLowerCase(), asset]));\n\tconst result: PlanAssetMention[] = [];\n\tfor (const mention of mentions) {\n\t\tconst asset = byId.get(mention.handle) ?? byLabel.get(mention.handle.toLowerCase());\n\t\tif (!asset) continue;\n\t\tresult.push({\n\t\t\tid: asset.id,\n\t\t\tlabel: mention.handle,\n\t\t\t...(mention.role ? { role: mention.role } : {}),\n\t\t\t...(asset.mediaType !== undefined ? { mediaType: asset.mediaType } : {}),\n\t\t\t...(asset.contentHash !== undefined ? { contentHash: asset.contentHash } : {}),\n\t\t\t...(asset.url !== undefined ? { url: asset.url } : {})\n\t\t});\n\t}\n\treturn result;\n}\n\n/** Built-in roles as picker options, for when no model is selected. */\nexport function defaultAssetRoleOptions(): AssetRoleOption[] {\n\treturn REFERENCE_ROLES.map((role) => ({ role, label: referenceRoleLabel(role), min: 0, max: 1 }));\n}\n\n/** An asset mention prepared for display as a chip. */\nexport interface AssetMentionChip {\n\t/** Position among the chips. */\n\tindex: number;\n\t/** Offset of the `@` in the text, when known (for `setMentionRoleInText`). */\n\tstart?: number;\n\thandle: string;\n\tasset: AssetInfo;\n\trole?: ReferenceRole;\n\t/** Display label of the role (the model's, else built-in). */\n\troleLabel?: string;\n\t/** Problems with this mention for the model (`validateAssetMentions`). */\n\tissues: AssetMentionIssue[];\n}\n\n/**\n * Chips for the asset mentions among `mentions` (e.g. `findTextMentions(text)`\n * from `@pixerate/editor/serializers`): each mention whose handle is an asset\n * id or label, with its role label and the problems the model has with it.\n */\nexport function describeAssetMentions(\n\tmentions: readonly { handle: string; role?: string | null; start?: number }[],\n\tassets: readonly AssetInfo[],\n\tcaps?: ModelCapabilities\n): AssetMentionChip[] {\n\tconst byId = new Map(assets.map((asset) => [asset.id, asset]));\n\tconst byLabel = new Map(assets.filter((a) => a.label).map((asset) => [asset.label!.toLowerCase(), asset]));\n\tconst chips: AssetMentionChip[] = [];\n\tfor (const mention of mentions) {\n\t\tconst asset = byId.get(mention.handle) ?? byLabel.get(mention.handle.toLowerCase());\n\t\tif (!asset) continue;\n\t\tconst role = mention.role || undefined;\n\t\tchips.push({\n\t\t\tindex: chips.length,\n\t\t\t...(mention.start !== undefined ? { start: mention.start } : {}),\n\t\t\thandle: mention.handle,\n\t\t\tasset,\n\t\t\t...(role\n\t\t\t\t? { role, roleLabel: referenceRoleLabel(caps ? normalizeReferenceRole(role, caps) : role, caps) }\n\t\t\t\t: {}),\n\t\t\tissues: []\n\t\t});\n\t}\n\tif (caps) {\n\t\tconst validation = validateAssetMentions(\n\t\t\tchips.map((chip) => ({\n\t\t\t\tid: chip.asset.id,\n\t\t\t\tlabel: chip.handle,\n\t\t\t\t...(chip.role ? { role: chip.role } : {}),\n\t\t\t\t...(chip.asset.mediaType ? { mediaType: chip.asset.mediaType } : {})\n\t\t\t})),\n\t\t\tcaps\n\t\t);\n\t\tfor (const issue of validation.issues) chips[issue.index].issues.push(issue);\n\t}\n\treturn chips;\n}\n","import { getLatestVersion, getTemplateBody, resolvePrompt } from '../grammar/engine.js';\nimport type { SourceMapSegment } from '../grammar/sourcemap.js';\nimport type { PromptValues, Template } from '../grammar/tokens.js';\nimport { toAssetResolver, type AssetLookup } from './assets.js';\nimport {\n\tGENERATION_REQUEST_FIELDS,\n\tseedCapability,\n\ttype GenerationReference,\n\ttype GenerationRequest,\n\ttype ModelCapabilities,\n\ttype ReferenceRole\n} from './capabilities.js';\nimport { hashCapabilities, hashText, stableStringify } from './hash.js';\nimport { planGenerationRequest, type GenerationPlan, type PlanAssetMention, type PlanStrategy } from './plan.js';\nimport {\n\tcreateProvenance,\n\tparseProvenance,\n\tpinnedTemplatesToTemplates,\n\tProvenanceParseError,\n\ttype CreateProvenanceOptions,\n\ttype GenerationProvenance,\n\ttype PinnedTemplate\n} from './provenance.js';\n\nexport const GENERATION_RECORD_FORMAT = 'pixerate.generation-record';\nexport const GENERATION_RECORD_VERSION = 1;\n\n/** How a record came about. */\nexport type GenerationRecordKind = 'generate' | 'recreate' | 'variation';\n\n/** A template the generation used, with a hash of the exact body. */\nexport interface RecordedTemplate {\n\tname: string;\n\ttemplateId?: string;\n\tversionId?: string;\n\tversion?: string | number;\n\t/** {@link hashText} of the body that was used. */\n\thash: string;\n}\n\n/** An asset the generation used, by role, with its content hash at the time. */\nexport interface RecordedAsset {\n\tid: string;\n\trole: ReferenceRole;\n\tcontentHash?: string;\n\tmediaType?: string;\n\turl?: string;\n}\n\n/**\n * Everything needed to reproduce one generation. The prompt, templates,\n * values, resolved prompt, source map, planned request and adjustments live\n * in `provenance` (see `createProvenance`); the record adds hashes for\n * change detection, the user's original settings, assets and lineage.\n */\nexport interface GenerationRecord {\n\tformat: typeof GENERATION_RECORD_FORMAT;\n\tversion: typeof GENERATION_RECORD_VERSION;\n\tid: string;\n\t/** The record this one was recreated or varied from. */\n\tparentId?: string;\n\tkind: GenerationRecordKind;\n\tprovenance: GenerationProvenance;\n\t/** One entry per template in `provenance.templates`, same order. */\n\ttemplates: RecordedTemplate[];\n\t/** {@link hashText} of `provenance.resolvedPrompt`. */\n\tpromptHash: string;\n\t/** {@link hashCapabilities} of the model when the record was made (absent when only an id was known). */\n\tcapabilityHash?: string;\n\t/** The request as the user set it, before planning (snapping, clamping, dropping). */\n\tsettings: GenerationRequest;\n\t/** Strategy the request was planned with. */\n\tstrategy: PlanStrategy;\n\t/** Seed used: the one the provider reported, else the planned one. */\n\tseed?: number;\n\tassets: RecordedAsset[];\n\t/** Asset mentions that were planned as references (kept apart from `settings`). */\n\tmentions?: PlanAssetMention[];\n\t/** ISO timestamp (same as `provenance.createdAt`). */\n\tcreatedAt: string;\n\tupdatedAt?: string;\n}\n\nexport interface CreateGenerationRecordOptions\n\textends Omit<CreateProvenanceOptions, 'model' | 'plan' | 'request' | 'adjustments'> {\n\t/** The model (needed for the capability hash and for planning when `plan` is absent). */\n\tmodel: ModelCapabilities | string;\n\t/** The user's settings before planning. */\n\trequest?: GenerationRequest;\n\t/** The plan that was sent. Without it the request is planned here (needs capabilities). */\n\tplan?: GenerationPlan;\n\t/** Strategy for planning here, and recorded for recreating (default: the plan's, else `nearest`). */\n\tstrategy?: PlanStrategy;\n\t/** Asset mentions to plan as references (see `PlanOptions.mentions`). */\n\tmentions?: readonly PlanAssetMention[];\n\t/** Fills in content hashes and media types of referenced assets. */\n\tassets?: AssetLookup;\n\t/** Seed the provider actually used, when it differs from the planned one (e.g. random). */\n\tseed?: number;\n\tid?: string;\n\tparentId?: string;\n\tkind?: GenerationRecordKind;\n}\n\nlet idCounter = 0;\n/** A unique record id (`crypto.randomUUID` when available). */\nexport function createGenerationRecordId(): string {\n\tconst uuid = (globalThis as any).crypto?.randomUUID?.();\n\tif (typeof uuid === 'string') return `gen_${uuid}`;\n\tidCounter = (idCounter + 1) % 1_679_616;\n\tconst random = Math.floor(Math.random() * 2 ** 48).toString(36).padStart(10, '0');\n\treturn `gen_${Date.now().toString(36)}${idCounter.toString(36).padStart(4, '0')}${random}`;\n}\n\nconst clone = <T>(value: T): T => (value === undefined ? value : JSON.parse(JSON.stringify(value)));\n\nfunction recordTemplates(pinned: readonly PinnedTemplate[]): RecordedTemplate[] {\n\treturn pinned.map((p) => ({\n\t\tname: p.name,\n\t\t...(p.templateId !== undefined ? { templateId: p.templateId } : {}),\n\t\t...(p.versionId !== undefined ? { versionId: p.versionId, version: p.version } : {}),\n\t\thash: hashText(p.body)\n\t}));\n}\n\nfunction recordAssets(references: readonly GenerationReference[] | undefined, lookup?: AssetLookup): RecordedAsset[] {\n\tconst resolve = toAssetResolver(lookup);\n\treturn (references ?? []).map((ref) => {\n\t\tconst info = resolve?.(ref.id);\n\t\tconst contentHash = ref.contentHash ?? info?.contentHash;\n\t\tconst mediaType = ref.mediaType ?? info?.mediaType;\n\t\tconst url = ref.url ?? info?.url;\n\t\treturn {\n\t\t\tid: ref.id,\n\t\t\trole: ref.role,\n\t\t\t...(contentHash !== undefined ? { contentHash } : {}),\n\t\t\t...(mediaType !== undefined ? { mediaType } : {}),\n\t\t\t...(url !== undefined ? { url } : {})\n\t\t};\n\t});\n}\n\n/**\n * Records a generation so it can be listed, explained and recreated. Builds\n * the provenance (pinned template bodies, values, resolved prompt and source\n * map, planned request, adjustments) and adds a hash per template body, the\n * capability hash of the model, the user's original settings, the seed, the\n * referenced assets with their roles and content hashes, and the parent\n * record. Plans the request when no `plan` is given and `model` is a\n * capability object.\n */\nexport function createGenerationRecord(options: CreateGenerationRecordOptions): GenerationRecord {\n\tconst {\n\t\tmodel,\n\t\trequest: settingsInput,\n\t\tplan: planInput,\n\t\tstrategy: strategyInput,\n\t\tmentions,\n\t\tassets,\n\t\tseed: seedInput,\n\t\tid,\n\t\tparentId,\n\t\tkind,\n\t\t...provenanceOptions\n\t} = options;\n\tconst caps = typeof model === 'string' ? undefined : model;\n\tconst settings: GenerationRequest = clone(settingsInput ?? planInput?.request ?? {});\n\tlet plan = planInput;\n\tif (!plan && caps) {\n\t\tconst resolved = resolvePrompt(options.rawPrompt, options.templates ?? [], {\n\t\t\tbindings: options.bindings,\n\t\t\tvalues: options.values,\n\t\t\tmaxDepth: options.maxDepth\n\t\t});\n\t\tplan = planGenerationRequest(settings, caps, {\n\t\t\tstrategy: strategyInput ?? 'nearest',\n\t\t\tresolvedPrompt: resolved.text,\n\t\t\t...(mentions ? { mentions } : {})\n\t\t});\n\t}\n\tconst provenance = createProvenance({\n\t\t...provenanceOptions,\n\t\tmodel,\n\t\t...(plan ? { plan } : { request: settings })\n\t});\n\tconst seed = seedInput ?? provenance.request.seed;\n\treturn {\n\t\tformat: GENERATION_RECORD_FORMAT,\n\t\tversion: GENERATION_RECORD_VERSION,\n\t\tid: id ?? createGenerationRecordId(),\n\t\t...(parentId !== undefined ? { parentId } : {}),\n\t\tkind: kind ?? (parentId !== undefined ? 'recreate' : 'generate'),\n\t\tprovenance,\n\t\ttemplates: recordTemplates(provenance.templates),\n\t\tpromptHash: hashText(provenance.resolvedPrompt),\n\t\t...(caps ? { capabilityHash: hashCapabilities(caps) } : {}),\n\t\tsettings,\n\t\tstrategy: strategyInput ?? plan?.strategy ?? 'nearest',\n\t\t...(seed !== undefined ? { seed } : {}),\n\t\tassets: recordAssets(provenance.request.references, assets),\n\t\t...(mentions?.length ? { mentions: clone([...mentions]) } : {}),\n\t\tcreatedAt: provenance.createdAt\n\t};\n}\n\n/** Returns a copy of `record` with outputs and completion time added (e.g. when the job finishes). */\nexport function completeGenerationRecord(\n\trecord: GenerationRecord,\n\tupdate: { outputIds?: string[]; completedAt?: Date | number | string; seed?: number; meta?: Record<string, unknown> }\n): GenerationRecord {\n\tconst next = clone(record);\n\tconst now = new Date(update.completedAt ?? Date.now()).toISOString();\n\tif (update.outputIds) next.provenance.outputIds = [...update.outputIds];\n\tnext.provenance.completedAt = now;\n\tif (update.meta) next.provenance.meta = { ...(next.provenance.meta ?? {}), ...clone(update.meta) };\n\tif (update.seed !== undefined) next.seed = update.seed;\n\tnext.updatedAt = now;\n\treturn next;\n}\n\n/** Versioned JSON for storage. */\nexport function serializeGenerationRecord(record: GenerationRecord): string {\n\treturn JSON.stringify(record);\n}\n\nconst isObject = (value: unknown): value is Record<string, any> =>\n\t!!value && typeof value === 'object' && !Array.isArray(value);\n\n/** Parses and checks a serialized record. Throws `ProvenanceParseError`. */\nexport function parseGenerationRecord(input: string | unknown): GenerationRecord {\n\tlet data: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tdata = JSON.parse(input);\n\t\t} catch {\n\t\t\tthrow new ProvenanceParseError('Generation record is not valid JSON.');\n\t\t}\n\t}\n\tif (!isObject(data) || data.format !== GENERATION_RECORD_FORMAT) {\n\t\tthrow new ProvenanceParseError('Not a generation record.');\n\t}\n\tif (typeof data.version !== 'number' || data.version > GENERATION_RECORD_VERSION) {\n\t\tthrow new ProvenanceParseError(`Unsupported generation record version ${JSON.stringify(data.version)}.`);\n\t}\n\tconst checks: [string, boolean][] = [\n\t\t['id', typeof data.id === 'string' && data.id !== ''],\n\t\t['templates', Array.isArray(data.templates) && data.templates.every((t: any) => isObject(t) && typeof t.name === 'string' && typeof t.hash === 'string')],\n\t\t['assets', Array.isArray(data.assets) && data.assets.every((a: any) => isObject(a) && typeof a.id === 'string' && typeof a.role === 'string')],\n\t\t['settings', isObject(data.settings)],\n\t\t['promptHash', typeof data.promptHash === 'string'],\n\t\t['createdAt', typeof data.createdAt === 'string']\n\t];\n\tconst bad = checks.find(([, ok]) => !ok);\n\tif (bad) throw new ProvenanceParseError(`Invalid generation record field \"${bad[0]}\".`);\n\tconst provenance = parseProvenance(data.provenance);\n\treturn {\n\t\t...(data as GenerationRecord),\n\t\tkind: data.kind === 'recreate' || data.kind === 'variation' ? data.kind : 'generate',\n\t\tstrategy: ['nearest', 'upgrade', 'downgrade', 'error'].includes(data.strategy) ? data.strategy : 'nearest',\n\t\tprovenance\n\t};\n}\n\n/** True for a {@link GenerationRecord} (as opposed to bare provenance). */\nexport function isGenerationRecord(value: unknown): value is GenerationRecord {\n\treturn isObject(value) && value.format === GENERATION_RECORD_FORMAT;\n}\n\n// ------------------------------------------------------------------ recreate\n\n/**\n * `exact`: the same resolved prompt (pinned template bodies), the planned\n * settings that were sent, the same seed. `latest`: the raw prompt resolved\n * against today's templates (latest versions) and the user's original\n * settings planned against today's capabilities, with the same seed.\n */\nexport type RecreateMode = 'exact' | 'latest';\n\nexport type DriftType =\n\t| 'template-changed'\n\t| 'template-missing'\n\t| 'prompt-changed'\n\t| 'model-missing'\n\t| 'model-changed'\n\t| 'setting-unsupported'\n\t| 'setting-changed'\n\t| 'asset-missing'\n\t| 'asset-changed';\n\nexport interface DriftItem {\n\ttype: DriftType;\n\t/** `error` items make `ok` false (the request cannot reproduce the record). */\n\tseverity: 'info' | 'warning' | 'error';\n\tmessage: string;\n\t/** Template name (template drift). */\n\ttemplate?: string;\n\t/** Request path, e.g. `resolution`, `params.guidance`, `references` (setting drift). */\n\tfield?: string;\n\t/** Asset id and role (asset drift). */\n\tassetId?: string;\n\trole?: ReferenceRole;\n\tmodelId?: string;\n\t/** Recorded and current value, version or hash. */\n\tfrom?: unknown;\n\tto?: unknown;\n\t/** Whether the change is in the returned request (`latest` mode), or only reported (`exact`). */\n\tapplied?: boolean;\n}\n\nexport interface RecreateOptions {\n\t/** Current templates (to detect edits, and to resolve in `latest` mode). */\n\ttemplates?: readonly Template[];\n\t/** Current model capabilities: one model, or a list to find the record's model in. */\n\tcapabilities?: ModelCapabilities | readonly ModelCapabilities[];\n\t/** Default `exact`. */\n\tmode?: RecreateMode;\n\t/** Current assets, to report missing or changed assets. */\n\tassets?: AssetLookup;\n\t/** Planning strategy (default: the record's). */\n\tstrategy?: PlanStrategy;\n}\n\nexport interface RecreateResult {\n\tmode: RecreateMode;\n\t/** No `error` drift and (when capabilities were given) the plan has no errors. */\n\tok: boolean;\n\t/** The request to send (re-planned when capabilities were given). */\n\trequest: GenerationRequest;\n\t/** The plan against current capabilities (absent without capabilities or when the model is gone). */\n\tplan?: GenerationPlan;\n\trawPrompt: string;\n\tresolvedPrompt: string;\n\tsourceMap: SourceMapSegment[];\n\tvalues: PromptValues | null;\n\t/** Templates and bindings the prompt was resolved with. */\n\ttemplates: Template[];\n\tbindings: Record<string, string>;\n\tdrift: DriftItem[];\n\t/** The record this request comes from. */\n\tparentId: string;\n\tkind: Exclude<GenerationRecordKind, 'generate'>;\n\t/** Options for {@link createGenerationRecord} once the new generation has run. */\n\trecordOptions: CreateGenerationRecordOptions;\n}\n\n/** Changes for {@link variationRequest}. */\nexport interface VariationOverrides {\n\t/** New seed, `null` for none, or absent for a random one different from the record's. */\n\tseed?: number | null;\n\t/** Values merged over the recorded ones. */\n\tvalues?: PromptValues;\n\t/** Settings merged over the recreated request (`params` are merged too). */\n\tsettings?: Partial<GenerationRequest>;\n\t/** A different raw prompt. */\n\trawPrompt?: string;\n\t/** Random source in [0, 1) for the seed (default `Math.random`). */\n\trandom?: () => number;\n}\n\nconst SKIPPED_SETTING_FIELDS = new Set(['model', 'prompt', 'references', 'params', 'seed']);\n\nfunction findModel(\n\tcapabilities: RecreateOptions['capabilities'],\n\tmodelId: string\n): { given: boolean; caps?: ModelCapabilities } {\n\tif (!capabilities) return { given: false };\n\tconst list = Array.isArray(capabilities) ? capabilities : [capabilities as ModelCapabilities];\n\treturn { given: true, caps: list.find((m) => m.id === modelId) };\n}\n\nconst sameValue = (a: unknown, b: unknown) => stableStringify(a) === stableStringify(b);\nconst show = (value: unknown) => (value === undefined ? 'unset' : typeof value === 'string' ? `\"${value}\"` : JSON.stringify(value));\nconst refKey = (ref: { id: string; role: string }) => `${ref.role}:${ref.id}`;\n\nfunction templateDrift(record: GenerationRecord, current: readonly Template[] | undefined, mode: RecreateMode): DriftItem[] {\n\tif (!current) return [];\n\tconst byName = new Map(current.map((t) => [t.name, t]));\n\tconst drift: DriftItem[] = [];\n\tfor (const recorded of record.templates) {\n\t\tconst template = byName.get(recorded.name);\n\t\tconst fromVersion = recorded.version ?? recorded.versionId;\n\t\tif (!template) {\n\t\t\tdrift.push({\n\t\t\t\ttype: 'template-missing',\n\t\t\t\tseverity: 'warning',\n\t\t\t\ttemplate: recorded.name,\n\t\t\t\tfrom: fromVersion,\n\t\t\t\tapplied: false,\n\t\t\t\tmessage: `Template \"${recorded.name}\" no longer exists; the recorded body is used.`\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst body = getTemplateBody(template, {});\n\t\tconst hash = body === undefined ? undefined : hashText(body);\n\t\tif (hash === recorded.hash) continue;\n\t\tconst latest = getLatestVersion(template);\n\t\tconst toVersion = template.versions?.length ? latest?.version ?? latest?.id : undefined;\n\t\tconst versions =\n\t\t\tfromVersion !== undefined || toVersion !== undefined\n\t\t\t\t? ` (${fromVersion ?? 'unversioned'} → ${toVersion ?? 'unversioned'})`\n\t\t\t\t: '';\n\t\tdrift.push({\n\t\t\ttype: 'template-changed',\n\t\t\tseverity: mode === 'latest' ? 'info' : 'warning',\n\t\t\ttemplate: recorded.name,\n\t\t\tfrom: { version: fromVersion, hash: recorded.hash },\n\t\t\tto: { version: toVersion, hash },\n\t\t\tapplied: mode === 'latest',\n\t\t\tmessage:\n\t\t\t\tmode === 'latest'\n\t\t\t\t\t? `Template \"${recorded.name}\" changed since this generation${versions}; using the current version.`\n\t\t\t\t\t: `Template \"${recorded.name}\" changed since this generation${versions}; recreating with the recorded version.`\n\t\t});\n\t}\n\treturn drift;\n}\n\nfunction randomSeed(caps: ModelCapabilities | undefined, previous: number | undefined, random: () => number): number | undefined {\n\tconst cap = caps ? seedCapability(caps.seed) : {};\n\tif (!cap) return undefined;\n\tconst min = Math.ceil(cap.min ?? 0);\n\tconst max = Math.floor(cap.max ?? 2 ** 31 - 1);\n\tif (max < min) return min;\n\tlet seed = previous;\n\tfor (let attempt = 0; attempt < 8 && (seed === undefined || seed === previous); attempt++) {\n\t\tseed = min + Math.floor(random() * (max - min + 1));\n\t}\n\tif (seed === previous && max > min) seed = previous === max ? min : previous! + 1;\n\treturn seed;\n}\n\nfunction rebuild(\n\trecord: GenerationRecord,\n\toptions: RecreateOptions,\n\tkind: 'recreate' | 'variation',\n\toverrides?: VariationOverrides\n): RecreateResult {\n\tconst mode = options.mode ?? 'exact';\n\tconst provenance = record.provenance;\n\tconst drift: DriftItem[] = templateDrift(record, options.templates, mode);\n\tconst rawPrompt = overrides?.rawPrompt ?? provenance.rawPrompt;\n\tconst values: PromptValues | null =\n\t\toverrides?.values !== undefined ? { ...(provenance.values ?? {}), ...overrides.values } : provenance.values ? clone(provenance.values) : null;\n\n\t// Templates and bindings to resolve with.\n\tconst pinned = pinnedTemplatesToTemplates(provenance.templates);\n\tlet templates: Template[];\n\tlet bindings: Record<string, string>;\n\tif (mode === 'latest' && options.templates) {\n\t\tconst currentNames = new Set(options.templates.map((t) => t.name));\n\t\tconst fallback = pinned.filter((t) => !currentNames.has(t.name));\n\t\ttemplates = [...options.templates.map((t) => ({ ...t })), ...fallback];\n\t\tbindings = {};\n\t\tfor (const t of fallback) {\n\t\t\tif (provenance.bindings[t.name] !== undefined) bindings[t.name] = provenance.bindings[t.name];\n\t\t}\n\t} else {\n\t\ttemplates = pinned;\n\t\tbindings = { ...provenance.bindings };\n\t}\n\tconst resolved = resolvePrompt(rawPrompt, templates, { bindings, ...(values ? { values } : {}) });\n\tconst sourceMap = resolved.map.map((segment) => {\n\t\tconst { locked: _locked, ...rest } = segment as SourceMapSegment & { locked?: boolean };\n\t\treturn rest;\n\t});\n\tif (resolved.text !== provenance.resolvedPrompt && !overrides) {\n\t\tdrift.push({\n\t\t\ttype: 'prompt-changed',\n\t\t\tseverity: 'info',\n\t\t\tfrom: provenance.resolvedPrompt,\n\t\t\tto: resolved.text,\n\t\t\tapplied: true,\n\t\t\tmessage: 'The resolved prompt differs from the recorded one.'\n\t\t});\n\t}\n\n\t// The request to start from.\n\tconst base: GenerationRequest = clone(mode === 'exact' ? provenance.request : record.settings);\n\tconst recordedPlanned = provenance.request;\n\tif (base.prompt === undefined || base.prompt === provenance.resolvedPrompt) base.prompt = resolved.text;\n\telse if (base.prompt === provenance.rawPrompt) base.prompt = rawPrompt;\n\tif (mode === 'latest' && record.mentions?.length) {\n\t\t// Mentions were planned as references; give them the role they were planned with.\n\t\tconst plannedRole = new Map((recordedPlanned.references ?? []).map((ref) => [ref.id, ref.role]));\n\t\tbase.references = [\n\t\t\t...(base.references ?? []),\n\t\t\t...record.mentions.map((m) => ({\n\t\t\t\tid: m.id,\n\t\t\t\trole: m.role || plannedRole.get(m.id) || 'image_ref',\n\t\t\t\t...(m.url !== undefined ? { url: m.url } : {}),\n\t\t\t\t...(m.mediaType !== undefined ? { mediaType: m.mediaType } : {}),\n\t\t\t\t...(m.contentHash !== undefined ? { contentHash: m.contentHash } : {})\n\t\t\t}))\n\t\t];\n\t} else if (mode === 'latest' && base.references === undefined && recordedPlanned.references) {\n\t\tbase.references = clone(recordedPlanned.references);\n\t}\n\tif (record.seed !== undefined) base.seed = record.seed;\n\tbase.model = provenance.modelId;\n\n\tconst { given, caps } = findModel(options.capabilities, provenance.modelId);\n\tif (overrides) {\n\t\tif (overrides.settings) {\n\t\t\tconst { params, ...rest } = overrides.settings;\n\t\t\tObject.assign(base, clone(rest));\n\t\t\tif (params) base.params = { ...(base.params ?? {}), ...clone(params) };\n\t\t}\n\t\tif (overrides.seed === null) delete base.seed;\n\t\telse base.seed = overrides.seed ?? randomSeed(caps, record.seed, overrides.random ?? Math.random);\n\t\tif (base.seed === undefined) delete base.seed;\n\t}\n\n\t// Assets.\n\tconst resolveAsset = toAssetResolver(options.assets);\n\tif (resolveAsset && base.references) {\n\t\tconst recordedById = new Map(record.assets.map((a) => [refKey(a), a]));\n\t\tconst kept: GenerationReference[] = [];\n\t\tfor (const ref of base.references) {\n\t\t\tconst info = resolveAsset(ref.id);\n\t\t\tif (!info) {\n\t\t\t\tdrift.push({\n\t\t\t\t\ttype: 'asset-missing',\n\t\t\t\t\tseverity: 'error',\n\t\t\t\t\tassetId: ref.id,\n\t\t\t\t\trole: ref.role,\n\t\t\t\t\tmessage: `Asset \"${ref.id}\" (${ref.role}) is no longer available.`\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst recordedHash = recordedById.get(refKey(ref))?.contentHash ?? ref.contentHash;\n\t\t\tif (recordedHash !== undefined && info.contentHash !== undefined && recordedHash !== info.contentHash) {\n\t\t\t\tdrift.push({\n\t\t\t\t\ttype: 'asset-changed',\n\t\t\t\t\tseverity: 'warning',\n\t\t\t\t\tassetId: ref.id,\n\t\t\t\t\trole: ref.role,\n\t\t\t\t\tfrom: recordedHash,\n\t\t\t\t\tto: info.contentHash,\n\t\t\t\t\tapplied: true,\n\t\t\t\t\tmessage: `Asset \"${ref.id}\" (${ref.role}) has changed since this generation.`\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst next: GenerationReference = { ...ref };\n\t\t\tif (info.contentHash !== undefined) next.contentHash = info.contentHash;\n\t\t\tif (next.mediaType === undefined && info.mediaType !== undefined) next.mediaType = info.mediaType;\n\t\t\tkept.push(next);\n\t\t}\n\t\tif (kept.length) base.references = kept;\n\t\telse delete base.references;\n\t}\n\n\t// Model and settings.\n\tlet plan: GenerationPlan | undefined;\n\tlet request = base;\n\tif (given && !caps) {\n\t\tdrift.push({\n\t\t\ttype: 'model-missing',\n\t\t\tseverity: 'error',\n\t\t\tmodelId: provenance.modelId,\n\t\t\tmessage: `Model \"${provenance.modelId}\" is no longer available.`\n\t\t});\n\t} else if (caps) {\n\t\tconst capabilityHash = hashCapabilities(caps);\n\t\tif (record.capabilityHash !== undefined && record.capabilityHash !== capabilityHash) {\n\t\t\tdrift.push({\n\t\t\t\ttype: 'model-changed',\n\t\t\t\tseverity: 'warning',\n\t\t\t\tmodelId: caps.id,\n\t\t\t\tfrom: record.capabilityHash,\n\t\t\t\tto: capabilityHash,\n\t\t\t\tmessage: `The capabilities of \"${caps.name ?? caps.id}\" changed since this generation.`\n\t\t\t});\n\t\t}\n\t\tplan = planGenerationRequest(base, caps, {\n\t\t\tstrategy: options.strategy ?? record.strategy,\n\t\t\tresolvedPrompt: resolved.text\n\t\t});\n\t\trequest = plan.request;\n\t\tconst severity = mode === 'exact' && !overrides ? 'warning' : 'info';\n\t\tconst type: DriftType = mode === 'exact' && !overrides ? 'setting-unsupported' : 'setting-changed';\n\t\tconst describeChange = (field: string, from: unknown, to: unknown) => {\n\t\t\tconst adjustment = plan!.adjustments.find((a) => a.field === field || a.field.startsWith(`${field}.`));\n\t\t\tdrift.push({\n\t\t\t\ttype,\n\t\t\t\tseverity,\n\t\t\t\tfield,\n\t\t\t\tfrom,\n\t\t\t\tto,\n\t\t\t\tapplied: true,\n\t\t\t\tmessage:\n\t\t\t\t\ttype === 'setting-unsupported'\n\t\t\t\t\t\t? `${field} ${show(from)} is no longer supported; using ${show(to)}.${adjustment ? ` ${adjustment.reason}` : ''}`\n\t\t\t\t\t\t: `${field} was ${show(from)}, now ${show(to)}.${adjustment ? ` ${adjustment.reason}` : ''}`\n\t\t\t});\n\t\t};\n\t\tif (!overrides) {\n\t\t\tfor (const field of GENERATION_REQUEST_FIELDS) {\n\t\t\t\tif (SKIPPED_SETTING_FIELDS.has(field)) continue;\n\t\t\t\tconst from = (recordedPlanned as any)[field];\n\t\t\t\tconst to = (request as any)[field];\n\t\t\t\tif (!sameValue(from, to)) describeChange(field, from, to);\n\t\t\t}\n\t\t\tconst paramNames = new Set([...Object.keys(recordedPlanned.params ?? {}), ...Object.keys(request.params ?? {})]);\n\t\t\tfor (const name of [...paramNames].sort()) {\n\t\t\t\tconst from = recordedPlanned.params?.[name];\n\t\t\t\tconst to = request.params?.[name];\n\t\t\t\tif (!sameValue(from, to)) describeChange(`params.${name}`, from, to);\n\t\t\t}\n\t\t\tif (!sameValue(record.seed ?? recordedPlanned.seed, request.seed)) describeChange('seed', record.seed ?? recordedPlanned.seed, request.seed);\n\t\t\tconst missing = new Set(drift.filter((d) => d.type === 'asset-missing').map((d) => `${d.role}:${d.assetId}`));\n\t\t\tconst fromRefs = (recordedPlanned.references ?? []).map(refKey).filter((key) => !missing.has(key));\n\t\t\tconst toRefs = (request.references ?? []).map(refKey);\n\t\t\tif (!sameValue(fromRefs, toRefs)) describeChange('references', fromRefs, toRefs);\n\t\t}\n\t\tfor (const error of plan.errors) {\n\t\t\tdrift.push({\n\t\t\t\ttype: 'setting-unsupported',\n\t\t\t\tseverity: 'error',\n\t\t\t\tfield: error.field,\n\t\t\t\tfrom: error.value,\n\t\t\t\tmessage: error.message\n\t\t\t});\n\t\t}\n\t}\n\n\tconst ok = !drift.some((d) => d.severity === 'error') && (plan ? plan.ok : true);\n\tconst recordOptions: CreateGenerationRecordOptions = {\n\t\trawPrompt,\n\t\ttemplates,\n\t\tbindings,\n\t\t...(values ? { values } : {}),\n\t\tmodel: caps ?? provenance.modelId,\n\t\trequest,\n\t\t...(plan ? { plan } : {}),\n\t\tstrategy: options.strategy ?? record.strategy,\n\t\tparentId: record.id,\n\t\tkind\n\t};\n\treturn {\n\t\tmode,\n\t\tok,\n\t\trequest,\n\t\t...(plan ? { plan } : {}),\n\t\trawPrompt,\n\t\tresolvedPrompt: resolved.text,\n\t\tsourceMap,\n\t\tvalues,\n\t\ttemplates,\n\t\tbindings,\n\t\tdrift,\n\t\tparentId: record.id,\n\t\tkind,\n\t\trecordOptions\n\t};\n}\n\n/**\n * Rebuilds the request of a recorded generation.\n *\n * - With a {@link GenerationRecord}: returns a {@link RecreateResult} with the\n * re-planned request and a drift report: templates edited or deleted since,\n * model capabilities changed or the model gone, settings no longer\n * supported (or, in `latest` mode, planned differently), assets missing or\n * changed. `mode: 'exact'` (default) reproduces the recorded prompt and\n * settings; `mode: 'latest'` uses current templates and re-plans the\n * original settings. Template edits are always reported; only `latest`\n * applies them.\n * - With bare {@link GenerationProvenance} (the original API): returns the\n * planned request that was sent, with the prompt as resolved at the time.\n */\nexport function recreateRequest(provenance: GenerationProvenance): GenerationRequest;\nexport function recreateRequest(record: GenerationRecord, options?: RecreateOptions): RecreateResult;\nexport function recreateRequest(\n\tinput: GenerationProvenance | GenerationRecord,\n\toptions?: RecreateOptions\n): GenerationRequest | RecreateResult {\n\tif (isGenerationRecord(input)) return rebuild(input, options ?? {}, 'recreate');\n\tconst provenance = input as GenerationProvenance;\n\tconst request = clone(provenance.request);\n\trequest.model = provenance.modelId;\n\tif (request.prompt === undefined) request.prompt = provenance.resolvedPrompt;\n\treturn request;\n}\n\n/**\n * A request for a variation of a recorded generation: the same prompt,\n * settings and assets (as {@link recreateRequest} would rebuild them, `exact`\n * mode by default) with a new seed (random within the model's range unless\n * given) and any overrides. Setting drift is not reported for the fields you\n * change; template, model and asset drift are.\n */\nexport function variationRequest(\n\trecord: GenerationRecord,\n\toverrides: VariationOverrides = {},\n\toptions: RecreateOptions = {}\n): RecreateResult {\n\treturn rebuild(record, options, 'variation', overrides);\n}\n\n/** Short display labels for drift types (history panels, badges). */\nexport const DRIFT_TYPE_LABELS: Readonly<Record<DriftType, string>> = {\n\t'template-changed': 'Template changed',\n\t'template-missing': 'Template deleted',\n\t'prompt-changed': 'Prompt changed',\n\t'model-missing': 'Model unavailable',\n\t'model-changed': 'Model changed',\n\t'setting-unsupported': 'Setting unsupported',\n\t'setting-changed': 'Setting changed',\n\t'asset-missing': 'Asset missing',\n\t'asset-changed': 'Asset changed'\n};\n\n/** Drift items that block an exact reproduction (`severity: 'error'`). */\nexport function blockingDrift(drift: readonly DriftItem[]): DriftItem[] {\n\treturn drift.filter((d) => d.severity === 'error');\n}\n","// Type-only: the store works with any `StorageAdapter` from\n// `@pixerate/editor/persistence` (memory, localStorage, IndexedDB, OPFS)\n// without bundling the persistence module into this entry.\nimport type { StorageAdapter } from '../persistence/storage.js';\nimport type { ReferenceRole } from './capabilities.js';\nimport {\n\tparseGenerationRecord,\n\tserializeGenerationRecord,\n\ttype GenerationRecord,\n\ttype GenerationRecordKind\n} from './record.js';\n\n/** What the store indexes per record (kept in one index entry, so lists need no record reads). */\nexport interface GenerationRecordSummary {\n\tid: string;\n\tmodelId: string;\n\tkind: GenerationRecordKind;\n\tparentId?: string;\n\tcreatedAt: string;\n\t/** Template names and ids used. */\n\ttemplates: string[];\n\ttemplateIds: string[];\n\t/** Asset ids and roles used. */\n\tassets: string[];\n\troles: ReferenceRole[];\n\t/** First 160 characters of the resolved prompt, for lists. */\n\tpreview: string;\n}\n\nexport interface GenerationRecordFilter {\n\t/** Model id(s). */\n\tmodel?: string | readonly string[];\n\t/** Template name or id. */\n\ttemplate?: string;\n\t/** Asset id. */\n\tasset?: string;\n\trole?: ReferenceRole;\n\tkind?: GenerationRecordKind;\n\tparentId?: string;\n\t/** Created at or after / before (exclusive). */\n\tsince?: Date | number | string;\n\tuntil?: Date | number | string;\n\t/** Case-insensitive text in the prompt preview. */\n\ttext?: string;\n\toffset?: number;\n\tlimit?: number;\n}\n\nexport interface PruneOptions {\n\t/** Keep at most this many records (newest first). */\n\tmaxRecords?: number;\n\t/** Remove records created before this time. */\n\tolderThan?: Date | number | string;\n\t/** Records for which this returns `true` are never removed (they still count toward `maxRecords`). */\n\tkeep?: (summary: GenerationRecordSummary) => boolean;\n}\n\nexport interface GenerationRecordStoreOptions {\n\tstorage: Pick<StorageAdapter, 'get' | 'set' | 'remove'>;\n\t/** Key prefix (default `pixerate.generation-records`). */\n\tnamespace?: string;\n\t/** Prune to this many records after every `put`. */\n\tmaxRecords?: number;\n}\n\nexport interface GenerationRecordStore {\n\t/** Adds or replaces a record. */\n\tput(record: GenerationRecord): Promise<void>;\n\tget(id: string): Promise<GenerationRecord | null>;\n\t/** Records matching `filter`, newest first. Unreadable records are skipped. */\n\tlist(filter?: GenerationRecordFilter): Promise<GenerationRecord[]>;\n\t/** Index entries matching `filter`, newest first (no record reads). */\n\tsummaries(filter?: GenerationRecordFilter): Promise<GenerationRecordSummary[]>;\n\tremove(id: string): Promise<boolean>;\n\t/** Removes records by count and age; returns the removed ids. */\n\tprune(options: PruneOptions): Promise<string[]>;\n\tclear(): Promise<void>;\n\t/** Called after every change. Returns an unsubscribe function. */\n\tsubscribe(listener: () => void): () => void;\n}\n\nconst INDEX_VERSION = 1;\nconst time = (value: Date | number | string) => new Date(value).getTime();\n\n/** The index entry for a record. */\nexport function summarizeGenerationRecord(record: GenerationRecord): GenerationRecordSummary {\n\tconst unique = <T>(values: T[]) => [...new Set(values)];\n\treturn {\n\t\tid: record.id,\n\t\tmodelId: record.provenance.modelId,\n\t\tkind: record.kind,\n\t\t...(record.parentId !== undefined ? { parentId: record.parentId } : {}),\n\t\tcreatedAt: record.createdAt,\n\t\ttemplates: unique(record.templates.map((t) => t.name)),\n\t\ttemplateIds: unique(record.templates.flatMap((t) => (t.templateId !== undefined ? [t.templateId] : []))),\n\t\tassets: unique(record.assets.map((a) => a.id)),\n\t\troles: unique(record.assets.map((a) => a.role)),\n\t\tpreview: record.provenance.resolvedPrompt.slice(0, 160)\n\t};\n}\n\n/** True when `summary` matches every condition of `filter` (ignores `offset` / `limit`). */\nexport function matchesRecordFilter(summary: GenerationRecordSummary, filter: GenerationRecordFilter = {}): boolean {\n\tif (filter.model !== undefined) {\n\t\tconst models = typeof filter.model === 'string' ? [filter.model] : filter.model;\n\t\tif (!models.includes(summary.modelId)) return false;\n\t}\n\tif (filter.template !== undefined && !summary.templates.includes(filter.template) && !summary.templateIds.includes(filter.template)) {\n\t\treturn false;\n\t}\n\tif (filter.asset !== undefined && !summary.assets.includes(filter.asset)) return false;\n\tif (filter.role !== undefined && !summary.roles.includes(filter.role)) return false;\n\tif (filter.kind !== undefined && summary.kind !== filter.kind) return false;\n\tif (filter.parentId !== undefined && summary.parentId !== filter.parentId) return false;\n\tconst created = time(summary.createdAt);\n\tif (filter.since !== undefined && created < time(filter.since)) return false;\n\tif (filter.until !== undefined && created >= time(filter.until)) return false;\n\tif (filter.text !== undefined && !summary.preview.toLowerCase().includes(filter.text.toLowerCase())) return false;\n\treturn true;\n}\n\nconst newestFirst = (a: GenerationRecordSummary, b: GenerationRecordSummary) =>\n\ttime(b.createdAt) - time(a.createdAt) || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0);\n\n/**\n * Persists generation records on a key/value `StorageAdapter`\n * (`createMemoryStorage`, `createLocalStorageAdapter`, `createIndexedDbAdapter`,\n * `createOpfsAdapter`). Each record is stored under its own key and a single\n * index entry holds the summaries used for listing and filtering. Operations\n * run one at a time, so concurrent `put`s do not lose index entries. The index\n * is cached after the first read: use one store per storage namespace.\n */\nexport function createGenerationRecordStore(options: GenerationRecordStoreOptions): GenerationRecordStore {\n\tconst storage = options.storage;\n\tconst ns = options.namespace ?? 'pixerate.generation-records';\n\tconst indexKey = `${ns}:index`;\n\tconst recordKey = (id: string) => `${ns}:record:${id}`;\n\tconst listeners = new Set<() => void>();\n\tlet index: GenerationRecordSummary[] | null = null;\n\tlet queue: Promise<unknown> = Promise.resolve();\n\n\tconst run = <T>(task: () => Promise<T>): Promise<T> => {\n\t\tconst result = queue.then(task, task);\n\t\tqueue = result.catch(() => undefined);\n\t\treturn result;\n\t};\n\tconst notify = () => listeners.forEach((listener) => listener());\n\n\tconst loadIndex = async (): Promise<GenerationRecordSummary[]> => {\n\t\tif (index) return index;\n\t\tconst raw = await storage.get(indexKey);\n\t\tlet items: GenerationRecordSummary[] = [];\n\t\tif (raw) {\n\t\t\ttry {\n\t\t\t\tconst data = JSON.parse(raw);\n\t\t\t\tif (data && Array.isArray(data.items)) {\n\t\t\t\t\titems = data.items.filter((item: any) => item && typeof item.id === 'string' && typeof item.createdAt === 'string');\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\titems = [];\n\t\t\t}\n\t\t}\n\t\tindex = items;\n\t\treturn index;\n\t};\n\tconst saveIndex = async (items: GenerationRecordSummary[]) => {\n\t\tawait storage.set(indexKey, JSON.stringify({ version: INDEX_VERSION, items }));\n\t\tindex = items;\n\t};\n\n\tconst select = (items: GenerationRecordSummary[], filter: GenerationRecordFilter = {}) => {\n\t\tconst matched = items.filter((s) => matchesRecordFilter(s, filter)).sort(newestFirst);\n\t\tconst offset = Math.max(0, filter.offset ?? 0);\n\t\treturn matched.slice(offset, filter.limit !== undefined ? offset + Math.max(0, filter.limit) : undefined);\n\t};\n\n\tconst pruneLocked = async (prune: PruneOptions): Promise<string[]> => {\n\t\tconst items = [...(await loadIndex())].sort(newestFirst);\n\t\tconst removed: GenerationRecordSummary[] = [];\n\t\tconst kept: GenerationRecordSummary[] = [];\n\t\tlet candidates = 0;\n\t\tconst cutoff = prune.olderThan !== undefined ? time(prune.olderThan) : undefined;\n\t\tfor (const item of items) {\n\t\t\tif (prune.keep?.(item)) {\n\t\t\t\tkept.push(item);\n\t\t\t\tcandidates++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst tooOld = cutoff !== undefined && time(item.createdAt) < cutoff;\n\t\t\tconst overLimit = prune.maxRecords !== undefined && candidates >= Math.max(0, prune.maxRecords);\n\t\t\tif (tooOld || overLimit) removed.push(item);\n\t\t\telse {\n\t\t\t\tkept.push(item);\n\t\t\t\tcandidates++;\n\t\t\t}\n\t\t}\n\t\tif (removed.length === 0) return [];\n\t\t// Index first: a crash between the two steps leaves orphaned record keys, never dangling index entries.\n\t\tawait saveIndex(kept);\n\t\tfor (const item of removed) await storage.remove(recordKey(item.id));\n\t\treturn removed.map((item) => item.id);\n\t};\n\n\treturn {\n\t\tput: (record) =>\n\t\t\trun(async () => {\n\t\t\t\tconst serialized = serializeGenerationRecord(record);\n\t\t\t\tawait storage.set(recordKey(record.id), serialized);\n\t\t\t\tconst items = (await loadIndex()).filter((item) => item.id !== record.id);\n\t\t\t\titems.push(summarizeGenerationRecord(record));\n\t\t\t\tawait saveIndex(items);\n\t\t\t\tif (options.maxRecords !== undefined) await pruneLocked({ maxRecords: options.maxRecords });\n\t\t\t\tnotify();\n\t\t\t}),\n\t\tget: (id) =>\n\t\t\trun(async () => {\n\t\t\t\tconst raw = await storage.get(recordKey(id));\n\t\t\t\tif (!raw) return null;\n\t\t\t\ttry {\n\t\t\t\t\treturn parseGenerationRecord(raw);\n\t\t\t\t} catch {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}),\n\t\tsummaries: (filter) => run(async () => select(await loadIndex(), filter)),\n\t\tlist: (filter) =>\n\t\t\trun(async () => {\n\t\t\t\tconst records: GenerationRecord[] = [];\n\t\t\t\tfor (const summary of select(await loadIndex(), filter)) {\n\t\t\t\t\tconst raw = await storage.get(recordKey(summary.id));\n\t\t\t\t\tif (!raw) continue;\n\t\t\t\t\ttry {\n\t\t\t\t\t\trecords.push(parseGenerationRecord(raw));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Skip unreadable records rather than failing the whole list.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn records;\n\t\t\t}),\n\t\tremove: (id) =>\n\t\t\trun(async () => {\n\t\t\t\tconst items = await loadIndex();\n\t\t\t\tconst next = items.filter((item) => item.id !== id);\n\t\t\t\tif (next.length === items.length) return false;\n\t\t\t\tawait saveIndex(next);\n\t\t\t\tawait storage.remove(recordKey(id));\n\t\t\t\tnotify();\n\t\t\t\treturn true;\n\t\t\t}),\n\t\tprune: (prune) =>\n\t\t\trun(async () => {\n\t\t\t\tconst removed = await pruneLocked(prune);\n\t\t\t\tif (removed.length) notify();\n\t\t\t\treturn removed;\n\t\t\t}),\n\t\tclear: () =>\n\t\t\trun(async () => {\n\t\t\t\tconst items = await loadIndex();\n\t\t\t\tawait saveIndex([]);\n\t\t\t\tfor (const item of items) await storage.remove(recordKey(item.id));\n\t\t\t\tnotify();\n\t\t\t}),\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t}\n\t};\n}\n"]}
|