@mengine/medeo-tool 1.2.1-alpha.8 → 1.3.1-alpha.6

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 CHANGED
@@ -38,7 +38,7 @@ const commit = await medeo.handle({
38
38
  plan_id: run.plan_id,
39
39
  });
40
40
 
41
- // Generation facts are recalled by the host and passed into the sandbox.
41
+ // Media facts are recalled by the host and passed into the sandbox.
42
42
  // This example is text-to-image: there is no invented input-media entity.
43
43
  const entityRun = await medeo.handle({
44
44
  op: 'run-edit-script',
@@ -48,12 +48,10 @@ const entityRun = await medeo.handle({
48
48
  if (typeof inputs.output_asset_id !== 'string' || typeof inputs.track_entity_id !== 'string') {
49
49
  throw new Error('missing recalled generation facts');
50
50
  }
51
- const asset = entities.importAsset({ asset_id: inputs.output_asset_id });
52
- const output = entities.create({
53
- entity_kind: 'image',
54
- payload: { extent: { kind: 'unbounded', start: 0 }, sampling: 'constant', coordinateSpace: 'ms' },
51
+ const { contentEntityId: output } = entities.ensureMedia({
52
+ assetId: inputs.output_asset_id,
53
+ kind: 'image',
55
54
  });
56
- relations.link({ relation_kind: 'physical-asset', endpoint_0_entity_id: output, endpoint_1_entity_id: asset });
57
55
  edit.insertClip({
58
56
  trackEntityId: inputs.track_entity_id,
59
57
  contentEntityId: output,
@@ -70,15 +68,64 @@ await medeo.close();
70
68
  The sandbox has no network, storage, clock, or generation access. Materialize
71
69
  or recall speech/media side effects in the host first and pass stable facts
72
70
  through `inputs`. There is no automatic Asset→Entity projection: the model
73
- selects and imports assets, creates only known typed Entities, and links them
74
- explicitly. Assets and media Entities are intentionally not one-to-one.
71
+ selects when to bring a recalled asset into the editor through `entities.ensureMedia`.
72
+ The resolver gets or creates one typed Asset by external `(system, key)` within
73
+ the document. Image/Video/Audio/Voice are logical Asset variants: the typed row
74
+ directly owns `external`, `storageKey` and its media fields, and its Asset ID is
75
+ also its content Entity ID. Imports create no separate physical-only Asset
76
+ row and no `physical-asset` relation. Raw typed resource creation also reuses
77
+ an existing identity; prefer `ensureMedia` with the recalled media kind.
78
+ `findByAssetId` includes Voice (`memota-speech`) variants. There is no separate
79
+ `importAsset` API.
80
+ Generation facts currently carry bare asset IDs. If a returned fact matches
81
+ both media and speech namespaces, synchronization fails explicitly without
82
+ writing ambiguous lineage edges.
83
+ Conflicting facts or duplicate candidates fail explicitly; no automatic
84
+ merge or deletion occurs. This is the MEngine editor profile, not a global DSL
85
+ cardinality rule. Separate Asset+media graphs are invalid: every media variant
86
+ must directly own its external identity, with no read fallback or migration.
87
+ Inline caption Assets without external identity are not deduplicated.
88
+
89
+ Before model context/snapshot/editing, the host ensures one Timeline and the four
90
+ fixed Tracks (`video_clip`, `speech`, `caption`, `bgm`). Existing IDs, settings and
91
+ relations are retained; repeated calls add no rows or revisions. First initialization
92
+ uses revision CAS and may write. An empty legacy draft is initialized through the
93
+ version-guarded migration path to preserve configured track identities/visibility;
94
+ non-empty legacy content still requires explicit `migrate-legacy`. Missing Tracks
95
+ are filled in a subsequent CAS when necessary. A lost initialization response is
96
+ reconciled by a fresh snapshot, never by assuming success.
97
+
98
+ Each placement still creates an independent Clip and SequenceMarker. Reusing a
99
+ media variant does not share per-placement trim, speed, volume or placement state.
75
100
 
76
101
  The production tool exposes native Clip/Marker editing plus visual placement,
77
102
  voiceover, caption, and BGM helpers through its generated sandbox interface.
78
103
  They preserve structural and anchor relations in the same plan. Timeline targets
79
- are Entity IDs, never raw asset IDs or URLs. Asset import, media creation, factual `generated` relations, and Clip
80
- insertion belong in the **same entity plan**. `generated` retains output at
81
- endpoint 0 and input at endpoint 1, while lookup can use either endpoint.
104
+ are Entity IDs, never raw asset IDs or URLs. Asset import, media creation, and
105
+ Clip insertion belong in the **same entity plan**.
106
+
107
+ Generation lineage is program-synced, not model-authored. After a confirmed
108
+ entity commit the tool resolves the plan's diff against host-supplied generation
109
+ facts and commits missing `generated` Relations between fact-matched media
110
+ Entities already present in the document (endpoint 0 output, endpoint 1 input;
111
+ lookup can use either endpoint). The diff covers newly exposed media Asset
112
+ identities; placement-only edits and
113
+ pairs already fact-resolvable before the plan stay untouched. Models
114
+ do not pass generation history through inputs — the host queries it with
115
+ `loadGenerationFacts(docId, assetIds)`, returning every known generation record
116
+ involving the given asset ids in either role. Each record must carry an explicit
117
+ `inputAssetIds` array: an explicit empty array declares text-only generation
118
+ with no lineage edge, while a missing or non-array field is a malformed record
119
+ that fails the whole query instead of being silently read as text-only.
120
+ One-sided facts are skipped without creating entities or blocking the commit —
121
+ lineage sync never backfills a missing source or output Entity; entity creation
122
+ stays a model decision inside the edit plan. Repeated commits are idempotent,
123
+ and deletions and revision conflicts are respected: a CAS-conflict retry reads
124
+ the fresh graph and recomputes missing edges without recreating deleted endpoints.
125
+ Asset identities are immutable, so each synchronization queries its facts once.
126
+ An empty result array means no known lineage; a rejection means the lineage
127
+ query failed and is reported as `generation_sync: {status:'failed'}` plus a
128
+ `generation_sync_failed` warning — never as synced state.
82
129
 
83
130
  Entities own their facts: SequenceMarker owns source/target ranges, duration,
84
131
  and time remapping; Clip owns volume; Track owns role/visibility. Cross-entity
@@ -1,3 +1,5 @@
1
+ import { MediaAssetFact } from "@mengine/medeo-client";
2
+
1
3
  //#region src/entity/entity-contract.d.ts
2
4
  type JsonPrimitive = string | number | boolean | null;
3
5
  type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
@@ -5,7 +7,9 @@ interface JsonObject {
5
7
  [key: string]: JsonValue;
6
8
  }
7
9
  type KnownEntityKind = 'axvideo' | 'timeline' | 'track' | 'clip' | 'asset' | 'video' | 'audio' | 'voice' | 'image' | 'sequence-marker' | 'viewport' | 'audio-script' | 'phonetic-script' | 'caption';
8
- type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'physical-asset' | 'generated' | 'phonetic-script-provenance' | 'caption-provenance' | 'caption-alignment' | 'clip-anchor' | 'audio-script-render';
10
+ /** Asset identity, either an old physical-only row or a directly composed media variant. */
11
+ type ResourceEntityKind = 'image' | 'video' | 'audio' | 'voice';
12
+ type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'physical-asset' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'audio-script-source' | 'audio-script-marker';
9
13
  type AuthorableRelationKind = Exclude<KnownRelationKind, 'generated'>;
10
14
  interface BoundedNativeSequencePayload extends JsonObject {
11
15
  /** Factual coordinates from recalled media metadata; never invent an end/duration. */
@@ -39,6 +43,30 @@ type ScriptTextSegment = JsonObject & {
39
43
  text: string;
40
44
  language?: string;
41
45
  };
46
+ type MediaAssetPayload = JsonObject & {
47
+ external: {
48
+ system: 'memota' | 'memota-speech';
49
+ key: string;
50
+ };
51
+ storageKey?: string;
52
+ };
53
+ type CaptionTextSelection = JsonObject & {
54
+ segmentId: string; /** Half-open Unicode code-point range within the selected source segment. */
55
+ textRange?: {
56
+ start: number;
57
+ end: number;
58
+ };
59
+ };
60
+ /** Read result only: base text is assembled from the real AudioScript row. */
61
+ interface ComposedScriptContent {
62
+ audio_script_entity_id: string;
63
+ text: string;
64
+ segments: ScriptTextSegment[];
65
+ }
66
+ interface ComposedPhoneticContent extends ComposedScriptContent {
67
+ phonemeScript?: string;
68
+ prosody?: JsonObject;
69
+ }
42
70
  interface EntityPayloadByKind {
43
71
  axvideo: BoundedDerivedSequencePayload;
44
72
  timeline: JsonObject;
@@ -47,12 +75,12 @@ interface EntityPayloadByKind {
47
75
  role?: string;
48
76
  };
49
77
  clip: JsonObject;
50
- /** Asset-owned metadata. Peer media associations belong in physical-asset Relations. */
78
+ /** Physical resource fields; never a copy of Caption content. */
51
79
  asset: JsonObject;
52
- video: BoundedNativeSequencePayload;
53
- audio: BoundedNativeSequencePayload;
54
- voice: BoundedNativeSequencePayload;
55
- image: UnboundedConstantSequencePayload;
80
+ video: BoundedNativeSequencePayload & MediaAssetPayload;
81
+ audio: BoundedNativeSequencePayload & MediaAssetPayload;
82
+ voice: BoundedNativeSequencePayload & MediaAssetPayload;
83
+ image: UnboundedConstantSequencePayload & MediaAssetPayload;
56
84
  'sequence-marker': JsonObject & {
57
85
  sourceRange: {
58
86
  start: number;
@@ -70,21 +98,36 @@ interface EntityPayloadByKind {
70
98
  };
71
99
  timeRemapping?: JsonValue;
72
100
  anchorOffset?: number;
73
- durationPolicy?: 'timeline';
101
+ durationPolicy?: 'timeline'; /** Directly assigned AudioScript annotation times; annotation Markers only. */
102
+ segmentRanges?: {
103
+ segmentId: string;
104
+ startMs: number;
105
+ endMs: number;
106
+ }[];
74
107
  };
75
108
  viewport: JsonObject;
76
109
  'audio-script': JsonObject & {
77
110
  segments: ScriptTextSegment[];
78
111
  };
79
112
  'phonetic-script': JsonObject & {
80
- segments: ScriptTextSegment[];
113
+ baseEntityIds: string[];
114
+ phonemeScript?: string;
115
+ prosody?: JsonObject;
116
+ };
117
+ caption: BoundedNativeSequencePayload & {
118
+ baseEntityIds: string[];
119
+ selections: CaptionTextSelection[];
120
+ style?: JsonObject;
81
121
  };
82
- caption: BoundedNativeSequencePayload;
83
122
  }
123
+ /** Stored own fields; a variant may obtain required content fields from its declared bases. */
124
+ type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] | (JsonObject & Partial<EntityPayloadByKind[K]> & {
125
+ baseEntityIds: string[];
126
+ });
84
127
  interface SandboxEntity<K extends KnownEntityKind = KnownEntityKind> {
85
128
  entity_id: string;
86
129
  entity_kind: K;
87
- payload: EntityPayloadByKind[K];
130
+ payload: StoredEntityPayload<K>;
88
131
  }
89
132
  interface SandboxRelation {
90
133
  relation_id: string;
@@ -102,8 +145,9 @@ interface EntityStoreSnapshot {
102
145
  type CreateEntityInput = { [K in KnownEntityKind]: {
103
146
  entity_id?: string;
104
147
  entity_kind: K;
105
- payload: EntityPayloadByKind[K];
148
+ payload: StoredEntityPayload<K>;
106
149
  } }[KnownEntityKind];
150
+ /** Patch supplied fields on the assembled entity; omitted fields remain unchanged. */
107
151
  interface UpdateEntityInput {
108
152
  entity_id: string;
109
153
  payload: JsonObject;
@@ -111,12 +155,7 @@ interface UpdateEntityInput {
111
155
  interface DeleteEntityInput {
112
156
  entity_id: string;
113
157
  }
114
- interface ImportAssetInput {
115
- asset_id: string;
116
- entity_id?: string;
117
- payload?: JsonObject;
118
- }
119
- type EmptyRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline';
158
+ type EmptyRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'audio-script-marker';
120
159
  interface LinkRelationBase {
121
160
  relation_id?: string;
122
161
  endpoint_0_entity_id: string;
@@ -131,11 +170,6 @@ type LinkRelationInput = (LinkRelationBase & {
131
170
  }) | (LinkRelationBase & {
132
171
  relation_kind: 'physical-asset';
133
172
  metadata?: JsonObject;
134
- }) | (LinkRelationBase & {
135
- relation_kind: 'phonetic-script-provenance' | 'caption-provenance';
136
- metadata: JsonObject & {
137
- segmentAlignment: JsonValue;
138
- };
139
173
  }) | (LinkRelationBase & {
140
174
  relation_kind: 'caption-alignment';
141
175
  metadata: JsonObject & {
@@ -154,10 +188,17 @@ interface LinkClipAnchorRelationInput {
154
188
  host_clip_entity_id: string;
155
189
  trace?: JsonObject;
156
190
  }
157
- interface LinkAudioScriptRenderRelationInput {
191
+ interface LinkPhoneticScriptRenderRelationInput {
158
192
  relation_id?: string;
159
193
  output_entity_id: string;
194
+ phonetic_script_entity_id: string;
195
+ trace?: JsonObject;
196
+ }
197
+ /** `audio-script-source(script, source)`; the script was transcribed from the source media. */
198
+ interface LinkAudioScriptSourceRelationInput {
199
+ relation_id?: string;
160
200
  script_entity_id: string;
201
+ source_entity_id: string;
161
202
  trace?: JsonObject;
162
203
  }
163
204
  interface UnlinkRelationInput {
@@ -188,33 +229,44 @@ interface EntityPlanState {
188
229
  deleted_relation_ids: readonly string[];
189
230
  }
190
231
  interface EntityFacade {
232
+ /** Read complete assembled fields; returned objects are snapshots. Use update to persist edits. */
191
233
  list(): SandboxEntity[];
192
234
  get(entityId: string): SandboxEntity | null;
193
- /** Return every explicitly imported Asset entity for a Memota asset id. */
194
- findByAssetId(assetId: string): SandboxEntity<'asset'>[];
235
+ /** Find document resources by external Memota asset id, including directly composed media variants. */
236
+ findByAssetId(assetId: string): SandboxEntity<ResourceEntityKind>[];
237
+ /** Assemble selected Caption text; missing composition is an error. */
238
+ readCaptionContent(entityId: string): ComposedScriptContent;
239
+ /** Assemble base text and pronunciation fields before generating Voice. */
240
+ readPhoneticScriptContent(entityId: string): ComposedPhoneticContent;
195
241
  create(input: CreateEntityInput): string;
196
- /** Replace one Entity's owned payload without changing its identity or kind. */
242
+ /** Patch assembled fields, routing inherited fields to their declaring entity. */
197
243
  update(input: UpdateEntityInput): void;
244
+ /** Explicitly declare own fields, overriding unambiguous bases without modifying them. Ordinary edits use update. */
245
+ declareFields(input: UpdateEntityInput): void;
198
246
  /** Delete an Entity only after all of its incident Relations have been explicitly unlinked. */
199
247
  delete(input: DeleteEntityInput): void;
200
- /** Import one physical asset without implying a one-to-one media Entity mapping. */
201
- importAsset(input: ImportAssetInput): string;
248
+ /** Get or create one typed Asset by factual external id and return its single content identity. Never creates a Clip. */
249
+ ensureMedia(fact: MediaAssetFact): {
250
+ contentEntityId: string;
251
+ };
202
252
  }
203
253
  interface RelationFacade {
204
254
  list(): SandboxRelation[];
205
255
  /** Incident lookup is endpoint-agnostic; persisted endpoint positions stay unchanged. */
206
256
  of(entityId: string, relationKind?: KnownRelationKind): SandboxRelation[];
207
- /** For physical-asset use sequence media as endpoint 0 and Asset as endpoint 1. */
257
+ /** Link existing entities through ordinary associations; variant bases are stored directly on the variant. */
208
258
  link(input: LinkRelationInput): string;
209
259
  /** Author ordered generated(output,input); generic link() deliberately rejects this kind. */
210
260
  linkGenerated(input: LinkGeneratedRelationInput): string;
211
261
  /** Author ordered clip-anchor(child,host) without positional endpoint ambiguity. */
212
262
  linkClipAnchor(input: LinkClipAnchorRelationInput): string;
213
- /** Author ordered audio-script-render(output,script) without positional endpoint ambiguity. */
214
- linkAudioScriptRender(input: LinkAudioScriptRenderRelationInput): string;
263
+ /** Author ordered phonetic-script-render(output,script) without positional endpoint ambiguity. */
264
+ linkPhoneticScriptRender(input: LinkPhoneticScriptRenderRelationInput): string;
265
+ /** Author ordered audio-script-source(script,source) without positional endpoint ambiguity. */
266
+ linkAudioScriptSource(input: LinkAudioScriptSourceRelationInput): string;
215
267
  /** Remove a Relation by identity; endpoint replacement is an explicit unlink plus link. */
216
268
  unlink(input: UnlinkRelationInput): void;
217
269
  }
218
270
  //#endregion
219
- export { SandboxEntity as _, EntityFacade as a, UpdateEntityInput as b, ImportAssetInput as c, JsonValue as d, KnownEntityKind as f, RelationFacade as g, LinkRelationInput as h, EntityCommand as i, JsonObject as l, LinkGeneratedRelationInput as m, CreateEntityInput as n, EntityPlanState as o, KnownRelationKind as p, DeleteEntityInput as r, EntityStoreSnapshot as s, AuthorableRelationKind as t, JsonPrimitive as u, SandboxRelation as v, UnlinkRelationInput as y };
220
- //# sourceMappingURL=entity-contract-DycLxdQ5.d.mts.map
271
+ export { SandboxEntity as _, EntityFacade as a, UpdateEntityInput as b, JsonObject as c, KnownEntityKind as d, KnownRelationKind as f, ResourceEntityKind as g, RelationFacade as h, EntityCommand as i, JsonPrimitive as l, LinkRelationInput as m, CreateEntityInput as n, EntityPlanState as o, LinkGeneratedRelationInput as p, DeleteEntityInput as r, EntityStoreSnapshot as s, AuthorableRelationKind as t, JsonValue as u, SandboxRelation as v, UnlinkRelationInput as y };
272
+ //# sourceMappingURL=entity-contract-hKZbJSRP.d.mts.map