@rslsp1/fa-app-tools 2.0.78 → 2.0.81

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.
@@ -3,64 +3,68 @@ import JSZip from "jszip";
3
3
  import { uploadFile } from "@huggingface/hub";
4
4
 
5
5
  // src/lib/hfEventTypes.ts
6
- var CURRENT_EVENT_VERSION = { major: 1, minor: 0 };
7
-
8
- // src/lib/hfStateService.ts
9
6
  var HF_BASE = "https://huggingface.co";
10
7
  var HF_REPO = "RolandSch/fa-app-state";
11
- var HF_TOKEN_KEY = "hf-token";
12
- function getHFToken() {
13
- try {
14
- return localStorage.getItem(HF_TOKEN_KEY);
15
- } catch {
16
- return null;
17
- }
18
- }
19
- function setHFToken(token) {
20
- try {
21
- localStorage.setItem(HF_TOKEN_KEY, token);
22
- } catch {
23
- }
24
- }
8
+ var CURRENT_EVENT_VERSION = { major: 1, minor: 0 };
9
+
10
+ // src/lib/hfEventLog.ts
25
11
  function treeUrl(namespace, subdir = "") {
26
12
  const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
27
13
  return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
28
14
  }
15
+ var DROSSEL_VERSUCHE = 4;
16
+ var DROSSEL_BASIS_MS = 400;
17
+ var schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
18
+ async function mitGeduld(anfrage) {
19
+ let letzte = null;
20
+ for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
21
+ const res = await anfrage();
22
+ if (res.status !== 429 && res.status < 500) return res;
23
+ letzte = res;
24
+ if (versuch === DROSSEL_VERSUCHE - 1) break;
25
+ const angesagt = Number(res.headers.get("retry-after"));
26
+ const warten = Number.isFinite(angesagt) && angesagt > 0 ? angesagt * 1e3 : DROSSEL_BASIS_MS * 2 ** versuch;
27
+ await schlafen(warten);
28
+ }
29
+ return letzte;
30
+ }
29
31
  async function hfListDir(namespace, subdir, token) {
30
- const res = await fetch(treeUrl(namespace, subdir), {
32
+ const res = await mitGeduld(() => fetch(treeUrl(namespace, subdir), {
31
33
  headers: { Authorization: `Bearer ${token}` }
32
- });
34
+ }));
33
35
  if (res.status === 404) return [];
34
36
  if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
35
37
  return res.json();
36
38
  }
37
39
  async function hfDownloadJsonByPath(repoPath, token) {
38
- const res = await fetch(
40
+ const res = await mitGeduld(() => fetch(
39
41
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
40
42
  { headers: { Authorization: `Bearer ${token}` } }
41
- );
43
+ ));
42
44
  if (!res.ok) throw new Error(`HF download failed: ${res.status}`);
43
45
  return res.json();
44
46
  }
45
47
  async function hfDownloadBinaryByPath(repoPath, token) {
46
- const res = await fetch(
48
+ const res = await mitGeduld(() => fetch(
47
49
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
48
50
  { headers: { Authorization: `Bearer ${token}` } }
49
- );
51
+ ));
50
52
  if (!res.ok) throw new Error(`HF download binary failed: ${res.status}`);
51
53
  return new Uint8Array(await res.arrayBuffer());
52
54
  }
53
- async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
54
- const bytes = new TextEncoder().encode(content);
55
+ function toBase64(text) {
56
+ const bytes = new TextEncoder().encode(text);
55
57
  let binary = "";
56
58
  bytes.forEach((b) => binary += String.fromCharCode(b));
57
- const b64 = btoa(binary);
59
+ return btoa(binary);
60
+ }
61
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
58
62
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
59
63
  method: "POST",
60
64
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
61
65
  body: [
62
66
  JSON.stringify({ key: "header", value: { summary, description: "" } }),
63
- JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: b64 } })
67
+ JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
64
68
  ].join("\n")
65
69
  });
66
70
  if (!res.ok) {
@@ -71,15 +75,10 @@ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${r
71
75
  async function hfBatchArchive(moves, token, summary) {
72
76
  const lines = [
73
77
  JSON.stringify({ key: "header", value: { summary, description: "" } }),
74
- ...moves.flatMap(({ from, to, content }) => {
75
- const bytes = new TextEncoder().encode(content);
76
- let binary = "";
77
- bytes.forEach((b) => binary += String.fromCharCode(b));
78
- return [
79
- JSON.stringify({ key: "file", value: { path: to, encoding: "base64", content: btoa(binary) } }),
80
- JSON.stringify({ key: "deletedFile", value: { path: from } })
81
- ];
82
- })
78
+ ...moves.flatMap(({ from, to, content }) => [
79
+ JSON.stringify({ key: "file", value: { path: to, encoding: "base64", content: toBase64(content) } }),
80
+ JSON.stringify({ key: "deletedFile", value: { path: from } })
81
+ ])
83
82
  ];
84
83
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
85
84
  method: "POST",
@@ -98,40 +97,42 @@ function tsFromEventPath(repoPath) {
98
97
  const ts = Date.parse(iso);
99
98
  return isNaN(ts) ? 0 : ts;
100
99
  }
101
- async function loadHFState(namespace, token) {
102
- const files = await hfListDir(namespace, "", token);
103
- const stateFiles = files.filter((f) => f.type === "file" && /^state-[\dT\-]+Z\.zip$/.test(f.path.split("/").pop() || "")).sort((a, b) => b.path.localeCompare(a.path));
104
- if (!stateFiles.length) return null;
105
- const zipBytes = await hfDownloadBinaryByPath(stateFiles[0].path, token);
106
- const zip = await JSZip.loadAsync(zipBytes);
107
- const [metadataStr, tagsStr, metaStr, openTasksStr] = await Promise.all([
108
- zip.file("metadata.json")?.async("string"),
109
- zip.file("tags.json")?.async("string"),
110
- zip.file("state_meta.json")?.async("string"),
111
- zip.file("open_tasks.json")?.async("string")
112
- // optional — ältere Snapshots haben es nicht
113
- ]);
114
- if (!metadataStr || !tagsStr || !metaStr) throw new Error("state.zip: fehlende Pflicht-Dateien");
115
- return {
116
- metadata: JSON.parse(metadataStr),
117
- tags: JSON.parse(tagsStr),
118
- meta: JSON.parse(metaStr),
119
- openTasks: openTasksStr ? JSON.parse(openTasksStr) : {}
100
+ var EVENTDOWNLOAD_PARALLELITAET = 3;
101
+ async function mitBegrenzterParallelitaet(count, limit, task) {
102
+ let cursor = 0;
103
+ let ersterFehler;
104
+ let fehlerAufgetreten = false;
105
+ const worker = async () => {
106
+ while (cursor < count) {
107
+ const index = cursor++;
108
+ try {
109
+ await task(index);
110
+ } catch (error) {
111
+ if (!fehlerAufgetreten) {
112
+ fehlerAufgetreten = true;
113
+ ersterFehler = error;
114
+ }
115
+ }
116
+ }
120
117
  };
118
+ await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
119
+ if (fehlerAufgetreten) throw ersterFehler;
121
120
  }
122
121
  async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
123
122
  const files = await hfListDir(namespace, "events", token);
124
123
  const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
125
124
  if (!pending.length) return [];
126
- const raw = await Promise.all(
127
- pending.map(async (f) => {
128
- const result = await hfDownloadJsonByPath(f.path, token);
129
- knownPaths?.add(f.path);
130
- return result;
131
- })
132
- );
125
+ const raw = new Array(pending.length);
126
+ await mitBegrenzterParallelitaet(pending.length, EVENTDOWNLOAD_PARALLELITAET, async (index) => {
127
+ raw[index] = await hfDownloadJsonByPath(pending[index].path, token);
128
+ });
129
+ for (const f of pending) knownPaths?.add(f.path);
133
130
  return raw.flatMap((e) => Array.isArray(e) ? e : [e]);
134
131
  }
132
+ async function listEventPaths(namespace, token) {
133
+ const files = await hfListDir(namespace, "events", token);
134
+ return files.filter((f) => f.type === "file").map((f) => f.path).sort((a, b) => a.localeCompare(b));
135
+ }
135
136
  var SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
136
137
  function getSessionClientId() {
137
138
  return SESSION_CLIENT_ID;
@@ -139,8 +140,12 @@ function getSessionClientId() {
139
140
  function tsToFilename(ts) {
140
141
  return new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-");
141
142
  }
142
- async function writeHFEvent(namespace, token, type, payload, prevTs) {
143
- const ts = Date.now();
143
+ var writeSeq = 0;
144
+ var lastOrderedTs = 0;
145
+ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
146
+ const wallTs = Date.now();
147
+ const ts = options?.ordered ? Math.max(wallTs, lastOrderedTs) : wallTs;
148
+ if (options?.ordered) lastOrderedTs = ts;
144
149
  const uuid = crypto.randomUUID().slice(0, 8);
145
150
  const event = {
146
151
  v: CURRENT_EVENT_VERSION,
@@ -150,11 +155,49 @@ async function writeHFEvent(namespace, token, type, payload, prevTs) {
150
155
  clientId: SESSION_CLIENT_ID,
151
156
  payload
152
157
  };
153
- const filename = `${tsToFilename(ts)}_${uuid}.json`;
158
+ const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
159
+ const filename = `${tsToFilename(ts)}_${suffix}.json`;
154
160
  const repoPath = `${namespace}events/${filename}`;
155
161
  await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
156
162
  return event;
157
163
  }
164
+
165
+ // src/lib/hfStateService.ts
166
+ var HF_TOKEN_KEY = "hf-token";
167
+ function getHFToken() {
168
+ try {
169
+ return localStorage.getItem(HF_TOKEN_KEY);
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+ function setHFToken(token) {
175
+ try {
176
+ localStorage.setItem(HF_TOKEN_KEY, token);
177
+ } catch {
178
+ }
179
+ }
180
+ async function loadHFState(namespace, token) {
181
+ const files = await hfListDir(namespace, "", token);
182
+ const stateFiles = files.filter((f) => f.type === "file" && /^state-[\dT\-]+Z\.zip$/.test(f.path.split("/").pop() || "")).sort((a, b) => b.path.localeCompare(a.path));
183
+ if (!stateFiles.length) return null;
184
+ const zipBytes = await hfDownloadBinaryByPath(stateFiles[0].path, token);
185
+ const zip = await JSZip.loadAsync(zipBytes);
186
+ const [metadataStr, tagsStr, metaStr, openTasksStr] = await Promise.all([
187
+ zip.file("metadata.json")?.async("string"),
188
+ zip.file("tags.json")?.async("string"),
189
+ zip.file("state_meta.json")?.async("string"),
190
+ zip.file("open_tasks.json")?.async("string")
191
+ // optional — ältere Snapshots haben es nicht
192
+ ]);
193
+ if (!metadataStr || !tagsStr || !metaStr) throw new Error("state.zip: fehlende Pflicht-Dateien");
194
+ return {
195
+ metadata: JSON.parse(metadataStr),
196
+ tags: JSON.parse(tagsStr),
197
+ meta: JSON.parse(metaStr),
198
+ openTasks: openTasksStr ? JSON.parse(openTasksStr) : {}
199
+ };
200
+ }
158
201
  async function hfBootstrapFromLegacy(namespace, token, onProgress) {
159
202
  const log = (msg) => {
160
203
  if (onProgress) onProgress(msg);
@@ -323,19 +366,20 @@ async function hfLoadImageAsBase64(id, token, namespace, filename, on404, mimeTy
323
366
  }
324
367
 
325
368
  export {
326
- HF_TOKEN_KEY,
327
- getHFToken,
328
- setHFToken,
329
369
  hfListDir,
330
370
  hfDownloadJsonByPath,
331
371
  hfDownloadBinaryByPath,
332
372
  hfUploadSmallFile,
333
373
  hfBatchArchive,
334
374
  tsFromEventPath,
335
- loadHFState,
336
375
  loadPendingEvents,
376
+ listEventPaths,
337
377
  getSessionClientId,
338
378
  writeHFEvent,
379
+ HF_TOKEN_KEY,
380
+ getHFToken,
381
+ setHFToken,
382
+ loadHFState,
339
383
  hfBootstrapFromLegacy,
340
384
  hfListProjects,
341
385
  hfDownloadProject,
@@ -0,0 +1,166 @@
1
+ /** Was der Port zurückgeben kann. Kein stilles `undefined`. */
2
+ type TextGenerationResult = {
3
+ kind: 'ok';
4
+ text: string;
5
+ model_id: string;
6
+ provider: string;
7
+ } | {
8
+ kind: 'failed';
9
+ /** Fehlerklassen aus `prompt-derivation.md` §6. */
10
+ class: 'safety_blocked' | 'finish_reason' | 'empty_content' | 'provider_error';
11
+ message: string;
12
+ model_id: string;
13
+ provider: string;
14
+ /** Provider-Angaben **unverändert**, nicht zusammengefasst. */
15
+ provider_detail?: unknown;
16
+ };
17
+ /** Ein Bild als Modelleingabe — Daten und der Typ, unter dem sie zu lesen sind. */
18
+ interface TextGenerationImage {
19
+ /** Base64, ohne `data:`-Praefix. */
20
+ data: string;
21
+ /** Der tatsaechliche Typ, etwa `image/png`. Nie geraten. */
22
+ mime_type: string;
23
+ }
24
+ interface TextGenerationRequest {
25
+ systemInstruction: string;
26
+ prompt: string;
27
+ /** `'json'` schaltet jede für Prosa gedachte Nachbereinigung ab (D-56). */
28
+ responseFormat: 'text' | 'json';
29
+ /**
30
+ * Optionale Bilder als Eingabe (D-53). Seit Stufe 6.5 mit Konsument: die
31
+ * Szenenrekonstruktion.
32
+ *
33
+ * Bis dahin waren es nackte Base64-Zeichenketten. Der Serveradapter musste
34
+ * daraus einen MIME-Typ raten und schrieb `image/jpeg` an jedes Bild — ein
35
+ * PNG wurde damit falsch deklariert. Der Typ gehört zum Bild, nicht zur
36
+ * Vermutung des Adapters.
37
+ */
38
+ images?: readonly TextGenerationImage[];
39
+ /** Voreinstellung bleibt die des Hosts; der Renderer hebt sie an (D-56). */
40
+ thinkingLevel?: 'low' | 'medium' | 'high';
41
+ }
42
+ /**
43
+ * Textgenerierung mit optionalen Bildern — die einzige Fähigkeit, die der
44
+ * Prompt-Lauf voraussetzt (D-53).
45
+ */
46
+ interface TextGenerationPort {
47
+ generate(request: TextGenerationRequest): Promise<TextGenerationResult>;
48
+ }
49
+ /**
50
+ * Eine deterministische Antwort zur Renderer-Eingabe.
51
+ *
52
+ * Sie nennt **jeden** Knoten (D-21) und richtet sich nach den Fähigkeiten des
53
+ * Zielmodells — dadurch läuft der echte Parser- und Schemapfad, nur ohne
54
+ * Provider. Eine Simulation, die die Prüfungen umginge, wiese nichts nach.
55
+ *
56
+ * Sie liegt **hier**, in der Testfläche der Bibliothek, und nicht im
57
+ * Serveradapter. Der Unterschied ist nicht kosmetisch: Ein Produktivadapter,
58
+ * der bei fehlender Konfiguration selbst eine gültige Antwort erfindet, macht
59
+ * aus einem Konfigurationsfehler einen gespeicherten Lauf. Genau das war der
60
+ * Befund des Umsetzungsreviews.
61
+ */
62
+ declare function simulatedAnswerFor(request: TextGenerationRequest): string | null;
63
+ /** Kennzeichnung eines simulierten Laufs — im Bestand unverwechselbar. */
64
+ declare const SIMULATION_PROVIDER = "simulation";
65
+ /**
66
+ * Ein deterministischer Port ohne Provider.
67
+ *
68
+ * Für Tests **und** für eine ausdrücklich angeforderte Simulation in der
69
+ * Entwicklung. Er ist nie ein Rückfall: Wer ihn benutzt, hat ihn gewählt, und
70
+ * der gespeicherte Lauf trägt `provider: 'simulation'`.
71
+ */
72
+ declare function createSimulationTextGenerationPort(): TextGenerationPort;
73
+ /**
74
+ * Ein Port mit frei wählbarer Antwort — für Tests, die einen bestimmten
75
+ * Ausgang brauchen. Protokolliert, was er bekommen hat.
76
+ */
77
+ declare function createFakeTextGenerationPort(antwort: TextGenerationResult | ((request: TextGenerationRequest) => TextGenerationResult)): TextGenerationPort & {
78
+ calls: TextGenerationRequest[];
79
+ };
80
+
81
+ type FeatureSliceId = 'library' | 'tags' | 'prompt.builder' | 'prompt.negative' | 'labs.compare' | 'labs.remix' | 'labs.loop' | 'labs.blend' | 'labs.frameExtractor';
82
+ type WorkspaceTabId = `react.${string}` | `flow.${string}`;
83
+ interface PersistenceDescriptor<I extends FeatureSliceId = FeatureSliceId> {
84
+ sliceId: I;
85
+ tabId: WorkspaceTabId;
86
+ schemaVersion: number;
87
+ }
88
+ interface PersistedSlice<T> {
89
+ workspaceId: string;
90
+ tabId: WorkspaceTabId;
91
+ schemaVersion: number;
92
+ revision: number;
93
+ data: T;
94
+ updatedAt: number;
95
+ resetAt?: number;
96
+ }
97
+ type LoadResult<T> = {
98
+ kind: 'loaded';
99
+ slice: PersistedSlice<T>;
100
+ } | {
101
+ kind: 'notFound';
102
+ } | {
103
+ kind: 'invalidVersion';
104
+ found: number;
105
+ supported: number;
106
+ } | {
107
+ kind: 'transportError';
108
+ message: string;
109
+ retryable: boolean;
110
+ };
111
+ type WriteResult<T> = {
112
+ kind: 'saved';
113
+ slice: PersistedSlice<T>;
114
+ } | {
115
+ kind: 'conflict';
116
+ expectedRevision: number;
117
+ current: PersistedSlice<T>;
118
+ } | {
119
+ kind: 'notFound';
120
+ } | {
121
+ kind: 'invalidVersion';
122
+ found: number;
123
+ supported: number;
124
+ } | {
125
+ kind: 'transportError';
126
+ message: string;
127
+ retryable: boolean;
128
+ };
129
+ interface WorkspacePersistence {
130
+ load<T>(workspaceId: string, descriptor: PersistenceDescriptor, signal: AbortSignal): Promise<LoadResult<T>>;
131
+ save<T>(slice: PersistedSlice<T>, expectedRevision: number, signal: AbortSignal): Promise<WriteResult<T>>;
132
+ reset<T>(sliceWithDefault: PersistedSlice<T>, expectedRevision: number, signal: AbortSignal): Promise<WriteResult<T>>;
133
+ }
134
+ interface SliceContract<T> {
135
+ id: FeatureSliceId;
136
+ schemaVersion: number;
137
+ createDefault(): T;
138
+ serialize(value: T): unknown;
139
+ validate(value: unknown): T;
140
+ migrate(fromVersion: number, value: unknown): T;
141
+ }
142
+ interface RequestContext {
143
+ requestId: string;
144
+ signal: AbortSignal;
145
+ }
146
+ type QueryResult<T> = {
147
+ kind: 'ok';
148
+ value: T;
149
+ } | {
150
+ kind: 'error';
151
+ message: string;
152
+ retryable: boolean;
153
+ };
154
+ type CommandResult<T> = {
155
+ kind: 'ok';
156
+ value: T;
157
+ } | {
158
+ kind: 'unsupported';
159
+ capability: string;
160
+ } | {
161
+ kind: 'error';
162
+ message: string;
163
+ retryable: boolean;
164
+ };
165
+
166
+ export { type CommandResult as C, type FeatureSliceId as F, type LoadResult as L, type PersistedSlice as P, type QueryResult as Q, type RequestContext as R, SIMULATION_PROVIDER as S, type TextGenerationPort as T, type WorkspacePersistence as W, type PersistenceDescriptor as a, type SliceContract as b, type TextGenerationRequest as c, type TextGenerationResult as d, type WorkspaceTabId as e, type WriteResult as f, createFakeTextGenerationPort as g, createSimulationTextGenerationPort as h, simulatedAnswerFor as s };
@@ -0,0 +1,166 @@
1
+ /** Was der Port zurückgeben kann. Kein stilles `undefined`. */
2
+ type TextGenerationResult = {
3
+ kind: 'ok';
4
+ text: string;
5
+ model_id: string;
6
+ provider: string;
7
+ } | {
8
+ kind: 'failed';
9
+ /** Fehlerklassen aus `prompt-derivation.md` §6. */
10
+ class: 'safety_blocked' | 'finish_reason' | 'empty_content' | 'provider_error';
11
+ message: string;
12
+ model_id: string;
13
+ provider: string;
14
+ /** Provider-Angaben **unverändert**, nicht zusammengefasst. */
15
+ provider_detail?: unknown;
16
+ };
17
+ /** Ein Bild als Modelleingabe — Daten und der Typ, unter dem sie zu lesen sind. */
18
+ interface TextGenerationImage {
19
+ /** Base64, ohne `data:`-Praefix. */
20
+ data: string;
21
+ /** Der tatsaechliche Typ, etwa `image/png`. Nie geraten. */
22
+ mime_type: string;
23
+ }
24
+ interface TextGenerationRequest {
25
+ systemInstruction: string;
26
+ prompt: string;
27
+ /** `'json'` schaltet jede für Prosa gedachte Nachbereinigung ab (D-56). */
28
+ responseFormat: 'text' | 'json';
29
+ /**
30
+ * Optionale Bilder als Eingabe (D-53). Seit Stufe 6.5 mit Konsument: die
31
+ * Szenenrekonstruktion.
32
+ *
33
+ * Bis dahin waren es nackte Base64-Zeichenketten. Der Serveradapter musste
34
+ * daraus einen MIME-Typ raten und schrieb `image/jpeg` an jedes Bild — ein
35
+ * PNG wurde damit falsch deklariert. Der Typ gehört zum Bild, nicht zur
36
+ * Vermutung des Adapters.
37
+ */
38
+ images?: readonly TextGenerationImage[];
39
+ /** Voreinstellung bleibt die des Hosts; der Renderer hebt sie an (D-56). */
40
+ thinkingLevel?: 'low' | 'medium' | 'high';
41
+ }
42
+ /**
43
+ * Textgenerierung mit optionalen Bildern — die einzige Fähigkeit, die der
44
+ * Prompt-Lauf voraussetzt (D-53).
45
+ */
46
+ interface TextGenerationPort {
47
+ generate(request: TextGenerationRequest): Promise<TextGenerationResult>;
48
+ }
49
+ /**
50
+ * Eine deterministische Antwort zur Renderer-Eingabe.
51
+ *
52
+ * Sie nennt **jeden** Knoten (D-21) und richtet sich nach den Fähigkeiten des
53
+ * Zielmodells — dadurch läuft der echte Parser- und Schemapfad, nur ohne
54
+ * Provider. Eine Simulation, die die Prüfungen umginge, wiese nichts nach.
55
+ *
56
+ * Sie liegt **hier**, in der Testfläche der Bibliothek, und nicht im
57
+ * Serveradapter. Der Unterschied ist nicht kosmetisch: Ein Produktivadapter,
58
+ * der bei fehlender Konfiguration selbst eine gültige Antwort erfindet, macht
59
+ * aus einem Konfigurationsfehler einen gespeicherten Lauf. Genau das war der
60
+ * Befund des Umsetzungsreviews.
61
+ */
62
+ declare function simulatedAnswerFor(request: TextGenerationRequest): string | null;
63
+ /** Kennzeichnung eines simulierten Laufs — im Bestand unverwechselbar. */
64
+ declare const SIMULATION_PROVIDER = "simulation";
65
+ /**
66
+ * Ein deterministischer Port ohne Provider.
67
+ *
68
+ * Für Tests **und** für eine ausdrücklich angeforderte Simulation in der
69
+ * Entwicklung. Er ist nie ein Rückfall: Wer ihn benutzt, hat ihn gewählt, und
70
+ * der gespeicherte Lauf trägt `provider: 'simulation'`.
71
+ */
72
+ declare function createSimulationTextGenerationPort(): TextGenerationPort;
73
+ /**
74
+ * Ein Port mit frei wählbarer Antwort — für Tests, die einen bestimmten
75
+ * Ausgang brauchen. Protokolliert, was er bekommen hat.
76
+ */
77
+ declare function createFakeTextGenerationPort(antwort: TextGenerationResult | ((request: TextGenerationRequest) => TextGenerationResult)): TextGenerationPort & {
78
+ calls: TextGenerationRequest[];
79
+ };
80
+
81
+ type FeatureSliceId = 'library' | 'tags' | 'prompt.builder' | 'prompt.negative' | 'labs.compare' | 'labs.remix' | 'labs.loop' | 'labs.blend' | 'labs.frameExtractor';
82
+ type WorkspaceTabId = `react.${string}` | `flow.${string}`;
83
+ interface PersistenceDescriptor<I extends FeatureSliceId = FeatureSliceId> {
84
+ sliceId: I;
85
+ tabId: WorkspaceTabId;
86
+ schemaVersion: number;
87
+ }
88
+ interface PersistedSlice<T> {
89
+ workspaceId: string;
90
+ tabId: WorkspaceTabId;
91
+ schemaVersion: number;
92
+ revision: number;
93
+ data: T;
94
+ updatedAt: number;
95
+ resetAt?: number;
96
+ }
97
+ type LoadResult<T> = {
98
+ kind: 'loaded';
99
+ slice: PersistedSlice<T>;
100
+ } | {
101
+ kind: 'notFound';
102
+ } | {
103
+ kind: 'invalidVersion';
104
+ found: number;
105
+ supported: number;
106
+ } | {
107
+ kind: 'transportError';
108
+ message: string;
109
+ retryable: boolean;
110
+ };
111
+ type WriteResult<T> = {
112
+ kind: 'saved';
113
+ slice: PersistedSlice<T>;
114
+ } | {
115
+ kind: 'conflict';
116
+ expectedRevision: number;
117
+ current: PersistedSlice<T>;
118
+ } | {
119
+ kind: 'notFound';
120
+ } | {
121
+ kind: 'invalidVersion';
122
+ found: number;
123
+ supported: number;
124
+ } | {
125
+ kind: 'transportError';
126
+ message: string;
127
+ retryable: boolean;
128
+ };
129
+ interface WorkspacePersistence {
130
+ load<T>(workspaceId: string, descriptor: PersistenceDescriptor, signal: AbortSignal): Promise<LoadResult<T>>;
131
+ save<T>(slice: PersistedSlice<T>, expectedRevision: number, signal: AbortSignal): Promise<WriteResult<T>>;
132
+ reset<T>(sliceWithDefault: PersistedSlice<T>, expectedRevision: number, signal: AbortSignal): Promise<WriteResult<T>>;
133
+ }
134
+ interface SliceContract<T> {
135
+ id: FeatureSliceId;
136
+ schemaVersion: number;
137
+ createDefault(): T;
138
+ serialize(value: T): unknown;
139
+ validate(value: unknown): T;
140
+ migrate(fromVersion: number, value: unknown): T;
141
+ }
142
+ interface RequestContext {
143
+ requestId: string;
144
+ signal: AbortSignal;
145
+ }
146
+ type QueryResult<T> = {
147
+ kind: 'ok';
148
+ value: T;
149
+ } | {
150
+ kind: 'error';
151
+ message: string;
152
+ retryable: boolean;
153
+ };
154
+ type CommandResult<T> = {
155
+ kind: 'ok';
156
+ value: T;
157
+ } | {
158
+ kind: 'unsupported';
159
+ capability: string;
160
+ } | {
161
+ kind: 'error';
162
+ message: string;
163
+ retryable: boolean;
164
+ };
165
+
166
+ export { type CommandResult as C, type FeatureSliceId as F, type LoadResult as L, type PersistedSlice as P, type QueryResult as Q, type RequestContext as R, SIMULATION_PROVIDER as S, type TextGenerationPort as T, type WorkspacePersistence as W, type PersistenceDescriptor as a, type SliceContract as b, type TextGenerationRequest as c, type TextGenerationResult as d, type WorkspaceTabId as e, type WriteResult as f, createFakeTextGenerationPort as g, createSimulationTextGenerationPort as h, simulatedAnswerFor as s };
@@ -0,0 +1,73 @@
1
+ import { T as TextGenerationPort } from './contracts-DdYOMBl9.mjs';
2
+
3
+ declare function buildGenerationPrompt(hierarchyText: string, mode?: 'literal' | 'creative'): string;
4
+ declare function buildFallbackPrompt(hierarchyText: string): string;
5
+ declare function cleanAiResponse(text: string): string;
6
+ declare function buildImageGenerationOptions(prompt: string, aspectRatio: string, model: string, referenceMediaId?: string): Record<string, any>;
7
+ interface SelectedTag {
8
+ label: string;
9
+ value: string;
10
+ category: string;
11
+ }
12
+ declare function buildPromptTabPayload(selectedTags: SelectedTag[], instructions: string, rules: string, treeText?: string): string;
13
+ declare function parsePromptResponse(raw: string): {
14
+ prompt: string;
15
+ feedback: string | null;
16
+ };
17
+ interface FlowSdkImage {
18
+ base64: string;
19
+ mimeType: string;
20
+ }
21
+ interface FlowTextGenerationOptions {
22
+ images?: FlowSdkImage[];
23
+ systemInstruction?: string;
24
+ responseFormat?: 'text' | 'json';
25
+ thinkingLevel?: 'low' | 'medium' | 'high';
26
+ }
27
+ interface FlowSdk {
28
+ generate: {
29
+ image: (options: Record<string, any>) => Promise<{
30
+ base64: string;
31
+ mediaId: string;
32
+ }>;
33
+ text: (prompt: string, options?: {
34
+ systemInstruction?: string;
35
+ thinkingLevel?: string;
36
+ images?: FlowSdkImage[];
37
+ }) => Promise<{
38
+ text: string;
39
+ }>;
40
+ };
41
+ download: (args: {
42
+ base64: string;
43
+ mimeType: string;
44
+ filename: string;
45
+ }) => Promise<void>;
46
+ media: {
47
+ selectMultiple: (args: {
48
+ filter: string;
49
+ }) => Promise<any[]>;
50
+ };
51
+ }
52
+ declare function createFlowServices(Flow: FlowSdk): {
53
+ generateImage: (options: Record<string, any>) => Promise<{
54
+ base64: string;
55
+ mediaId: string;
56
+ }>;
57
+ generateText: (text: string, options?: FlowTextGenerationOptions) => Promise<string>;
58
+ };
59
+ declare function interpretSdkError(err: any): Error;
60
+
61
+ type FlowGenerateText = (prompt: string, options?: FlowTextGenerationOptions) => Promise<string>;
62
+ interface CreateFlowTextGenerationPortOptions {
63
+ provider?: string;
64
+ modelId?: string;
65
+ }
66
+ /**
67
+ * Adapts the existing Flow SDK service to the host-neutral Szenen contract.
68
+ * It deliberately does not parse or validate the response: that happens once
69
+ * in `runPromptDerivation`, identically for every host.
70
+ */
71
+ declare function createFlowTextGenerationPort(generateText: FlowGenerateText, options?: CreateFlowTextGenerationPortOptions): TextGenerationPort;
72
+
73
+ export { type CreateFlowTextGenerationPortOptions as C, type FlowGenerateText as F, type SelectedTag as S, type FlowSdk as a, type FlowTextGenerationOptions as b, buildFallbackPrompt as c, buildGenerationPrompt as d, buildImageGenerationOptions as e, buildPromptTabPayload as f, cleanAiResponse as g, createFlowServices as h, createFlowTextGenerationPort as i, interpretSdkError as j, parsePromptResponse as p };