@copilotkit/aimock 1.23.1 → 1.24.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.
Files changed (49) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +26 -0
  4. package/README.md +1 -1
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/config-loader.d.cts.map +1 -1
  7. package/dist/fal-audio.cjs +171 -18
  8. package/dist/fal-audio.cjs.map +1 -1
  9. package/dist/fal-audio.d.cts.map +1 -1
  10. package/dist/fal-audio.d.ts.map +1 -1
  11. package/dist/fal-audio.js +173 -20
  12. package/dist/fal-audio.js.map +1 -1
  13. package/dist/fal.cjs +412 -32
  14. package/dist/fal.cjs.map +1 -1
  15. package/dist/fal.d.cts +16 -1
  16. package/dist/fal.d.cts.map +1 -1
  17. package/dist/fal.d.ts +16 -1
  18. package/dist/fal.d.ts.map +1 -1
  19. package/dist/fal.js +410 -34
  20. package/dist/fal.js.map +1 -1
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/llmock.cjs +18 -1
  24. package/dist/llmock.cjs.map +1 -1
  25. package/dist/llmock.d.cts +13 -1
  26. package/dist/llmock.d.cts.map +1 -1
  27. package/dist/llmock.d.ts +13 -1
  28. package/dist/llmock.d.ts.map +1 -1
  29. package/dist/llmock.js +18 -1
  30. package/dist/llmock.js.map +1 -1
  31. package/dist/recorder.cjs +86 -55
  32. package/dist/recorder.cjs.map +1 -1
  33. package/dist/recorder.d.cts +12 -1
  34. package/dist/recorder.d.cts.map +1 -1
  35. package/dist/recorder.d.ts +12 -1
  36. package/dist/recorder.d.ts.map +1 -1
  37. package/dist/recorder.js +85 -56
  38. package/dist/recorder.js.map +1 -1
  39. package/dist/server.cjs +4 -1
  40. package/dist/server.cjs.map +1 -1
  41. package/dist/server.js +4 -1
  42. package/dist/server.js.map +1 -1
  43. package/dist/types.d.cts +41 -0
  44. package/dist/types.d.cts.map +1 -1
  45. package/dist/types.d.ts +41 -0
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/ws-gemini-live.d.ts +2 -2
  48. package/dist/ws-realtime.d.ts +2 -2
  49. package/package.json +1 -1
package/dist/fal.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
2
  const require_helpers = require('./helpers.cjs');
3
3
  const require_router = require('./router.cjs');
4
+ const require_url = require('./url.cjs');
4
5
  const require_recorder = require('./recorder.cjs');
5
6
  const require_fal_audio = require('./fal-audio.cjs');
6
7
  let node_crypto = require("node:crypto");
@@ -50,6 +51,109 @@ var FalQueueStateMap = class {
50
51
  }
51
52
  };
52
53
  const falQueueStates = new FalQueueStateMap();
54
+ function extractExtension(url, fallback) {
55
+ const segment = url.split("?")[0].split("#")[0].split("/").pop() ?? "";
56
+ const fileName = segment.length > 0 ? segment : "";
57
+ const dotIdx = fileName.lastIndexOf(".");
58
+ return {
59
+ fileName,
60
+ ext: dotIdx >= 0 ? fileName.slice(dotIdx + 1).toLowerCase() : fallback
61
+ };
62
+ }
63
+ function imageItemToFalImage(item, index) {
64
+ const url = item.url ?? `https://mock.fal.media/files/generated_image_${index}.png`;
65
+ const { ext } = extractExtension(url, "png");
66
+ return {
67
+ url,
68
+ width: 1024,
69
+ height: 1024,
70
+ content_type: ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext}`
71
+ };
72
+ }
73
+ /**
74
+ * Translate an `ImageResponse` fixture into fal's image envelope shape:
75
+ * `{ images: [...], timings, seed, has_nsfw_concepts, prompt }`.
76
+ * Used by `LLMock.onFalImage` to keep callers from re-deriving the wire shape.
77
+ */
78
+ function imageResponseToFalJson(response) {
79
+ const images = (response.images ?? (response.image ? [response.image] : [])).map((item, i) => imageItemToFalImage(item, i));
80
+ return {
81
+ images,
82
+ timings: { inference: 0 },
83
+ seed: 0,
84
+ has_nsfw_concepts: images.map(() => false),
85
+ prompt: ""
86
+ };
87
+ }
88
+ /**
89
+ * Translate a `VideoResponse` fixture into fal's video envelope shape:
90
+ * `{ video: { url, content_type, file_name, file_size }, seed }`.
91
+ */
92
+ function videoResponseToFalJson(response) {
93
+ const url = response.video.url ?? "https://mock.fal.media/files/generated_video.mp4";
94
+ const { fileName, ext } = extractExtension(url, "mp4");
95
+ return {
96
+ video: {
97
+ url,
98
+ content_type: `video/${ext}`,
99
+ file_name: fileName || "generated_video.mp4",
100
+ file_size: 0
101
+ },
102
+ seed: 0
103
+ };
104
+ }
105
+ function resolveProgression(config) {
106
+ const pollsBeforeInProgress = config?.pollsBeforeInProgress ?? 0;
107
+ const explicitCompleted = config?.pollsBeforeCompleted;
108
+ let pollsBeforeCompleted;
109
+ if (explicitCompleted != null) pollsBeforeCompleted = Math.max(pollsBeforeInProgress, explicitCompleted);
110
+ else if (config?.pollsBeforeInProgress != null) pollsBeforeCompleted = pollsBeforeInProgress + 1;
111
+ else pollsBeforeCompleted = 0;
112
+ return {
113
+ pollsBeforeInProgress,
114
+ pollsBeforeCompleted
115
+ };
116
+ }
117
+ /**
118
+ * Mutates a job in place to advance its state on a status/result poll.
119
+ * IN_QUEUE → IN_PROGRESS → COMPLETED based on poll-count thresholds. No-op
120
+ * once COMPLETED or CANCELLED.
121
+ */
122
+ function advanceJob(job) {
123
+ if (job.status === "COMPLETED" || job.status === "CANCELLED") return;
124
+ job.pollCount += 1;
125
+ if (job.status === "IN_QUEUE" && job.pollCount >= job.pollsBeforeInProgress) {
126
+ job.status = "IN_PROGRESS";
127
+ job.logs.push({
128
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
129
+ level: "INFO",
130
+ message: "Job started processing."
131
+ });
132
+ } else if (job.pollCount >= job.pollsBeforeCompleted) {
133
+ job.status = "COMPLETED";
134
+ job.completedAt = Date.now();
135
+ job.logs.push({
136
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
137
+ level: "INFO",
138
+ message: "Job completed."
139
+ });
140
+ }
141
+ }
142
+ function queuePosition(job) {
143
+ if (job.status !== "IN_QUEUE") return 0;
144
+ return Math.max(0, job.pollsBeforeInProgress - job.pollCount);
145
+ }
146
+ function statusResponseBody(job) {
147
+ const body = {
148
+ status: job.status,
149
+ request_id: job.requestId,
150
+ response_url: `https://${FAL_HOSTS.queue}/${job.modelId}/requests/${job.requestId}`,
151
+ logs: job.logs
152
+ };
153
+ if (job.status === "IN_QUEUE" || job.status === "IN_PROGRESS") body.queue_position = queuePosition(job);
154
+ if (job.status === "COMPLETED" && job.completedAt != null) body.metrics = { inference_time: (job.completedAt - job.submittedAt) / 1e3 };
155
+ return body;
156
+ }
53
157
  const FAL_HOSTS = {
54
158
  queue: "queue.fal.run",
55
159
  sync: "fal.run",
@@ -166,11 +270,8 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
166
270
  respondNotFound(req, res, pathname, journal, route.requestId);
167
271
  return "handled";
168
272
  }
169
- writeJson(req, res, 200, {
170
- status: job.status,
171
- request_id: job.requestId,
172
- response_url: `https://${FAL_HOSTS.queue}/${job.modelId}/requests/${job.requestId}`
173
- }, pathname, journal);
273
+ advanceJob(job);
274
+ writeJson(req, res, 200, statusResponseBody(job), pathname, journal);
174
275
  return "handled";
175
276
  }
176
277
  case "queue-result": {
@@ -179,11 +280,17 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
179
280
  respondNotFound(req, res, pathname, journal, route.requestId);
180
281
  return "handled";
181
282
  }
283
+ advanceJob(job);
284
+ if (job.status !== "COMPLETED") {
285
+ writeJson(req, res, 202, statusResponseBody(job), pathname, journal);
286
+ return "handled";
287
+ }
182
288
  writeJson(req, res, 200, job.result, pathname, journal);
183
289
  return "handled";
184
290
  }
185
- case "queue-cancel":
186
- if (!falQueueStates.get(stateKey(route.requestId))) {
291
+ case "queue-cancel": {
292
+ const job = falQueueStates.get(stateKey(route.requestId));
293
+ if (!job) {
187
294
  journal.add({
188
295
  method: req.method ?? "PUT",
189
296
  path: pathname,
@@ -198,19 +305,56 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
198
305
  res.end(JSON.stringify({ status: "NOT_FOUND" }));
199
306
  return "handled";
200
307
  }
308
+ if (job.status === "COMPLETED") {
309
+ journal.add({
310
+ method: req.method ?? "PUT",
311
+ path: pathname,
312
+ headers: require_helpers.flattenHeaders(req.headers),
313
+ body: null,
314
+ response: {
315
+ status: 400,
316
+ fixture: null
317
+ }
318
+ });
319
+ res.writeHead(400, { "Content-Type": "application/json" });
320
+ res.end(JSON.stringify({ status: "ALREADY_COMPLETED" }));
321
+ return "handled";
322
+ }
323
+ if (job.status === "CANCELLED") {
324
+ journal.add({
325
+ method: req.method ?? "PUT",
326
+ path: pathname,
327
+ headers: require_helpers.flattenHeaders(req.headers),
328
+ body: null,
329
+ response: {
330
+ status: 200,
331
+ fixture: null
332
+ }
333
+ });
334
+ res.writeHead(200, { "Content-Type": "application/json" });
335
+ res.end(JSON.stringify({ status: "CANCELLED" }));
336
+ return "handled";
337
+ }
338
+ job.status = "CANCELLED";
339
+ job.logs.push({
340
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
341
+ level: "INFO",
342
+ message: "Job cancelled."
343
+ });
201
344
  journal.add({
202
345
  method: req.method ?? "PUT",
203
346
  path: pathname,
204
347
  headers: require_helpers.flattenHeaders(req.headers),
205
348
  body: null,
206
349
  response: {
207
- status: 400,
350
+ status: 200,
208
351
  fixture: null
209
352
  }
210
353
  });
211
- res.writeHead(400, { "Content-Type": "application/json" });
212
- res.end(JSON.stringify({ status: "ALREADY_COMPLETED" }));
354
+ res.writeHead(200, { "Content-Type": "application/json" });
355
+ res.end(JSON.stringify({ status: "CANCELLED" }));
213
356
  return "handled";
357
+ }
214
358
  case "storage": {
215
359
  let filename = "upload.bin";
216
360
  try {
@@ -228,7 +372,29 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
228
372
  case "queue-submit":
229
373
  case "sync-run": {
230
374
  const modelId = route.modelId;
231
- const parsedBody = parseBody(body);
375
+ let parsedBody;
376
+ try {
377
+ parsedBody = parseBody(body);
378
+ } catch (err) {
379
+ const detail = err instanceof Error ? err.message : "Invalid JSON body";
380
+ journal.add({
381
+ method: req.method ?? "POST",
382
+ path: pathname,
383
+ headers: require_helpers.flattenHeaders(req.headers),
384
+ body: null,
385
+ response: {
386
+ status: 400,
387
+ fixture: null
388
+ }
389
+ });
390
+ res.writeHead(400, { "Content-Type": "application/json" });
391
+ res.end(JSON.stringify({ error: {
392
+ message: detail,
393
+ type: "invalid_request_error",
394
+ code: "invalid_json"
395
+ } }));
396
+ return "handled";
397
+ }
232
398
  const syntheticReq = {
233
399
  model: modelId,
234
400
  messages: [{
@@ -261,21 +427,37 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
261
427
  }
262
428
  if (defaults.record) {
263
429
  const effectiveDefaults = withFalUpstream(defaults, route.targetHost);
264
- const outcome = await require_recorder.proxyAndRecord(req, res, syntheticReq, "fal", stripFalPrefix(pathname), fixtures, effectiveDefaults, body);
265
- if (outcome === "handled_by_hook") return "handled";
266
- if (outcome !== "not_configured") {
267
- journal.add({
268
- method: req.method ?? "POST",
269
- path: pathname,
270
- headers: require_helpers.flattenHeaders(req.headers),
271
- body: syntheticReq,
272
- response: {
273
- status: res.statusCode ?? 200,
274
- fixture: null,
275
- source: "proxy"
276
- }
277
- });
278
- return "handled";
430
+ if (route.kind === "queue-submit") {
431
+ if (await proxyAndRecordFalQueueSubmit({
432
+ req,
433
+ res,
434
+ syntheticReq,
435
+ modelId,
436
+ pathname,
437
+ strippedPath: stripFalPrefix(pathname),
438
+ body,
439
+ fixtures,
440
+ defaults: effectiveDefaults,
441
+ stateKey,
442
+ journal
443
+ }) === "handled") return "handled";
444
+ } else {
445
+ const outcome = await require_recorder.proxyAndRecord(req, res, syntheticReq, "fal", stripFalPrefix(pathname), fixtures, effectiveDefaults, body);
446
+ if (outcome === "handled_by_hook") return "handled";
447
+ if (outcome !== "not_configured") {
448
+ journal.add({
449
+ method: req.method ?? "POST",
450
+ path: pathname,
451
+ headers: require_helpers.flattenHeaders(req.headers),
452
+ body: syntheticReq,
453
+ response: {
454
+ status: res.statusCode ?? 200,
455
+ fixture: null,
456
+ source: "proxy"
457
+ }
458
+ });
459
+ return "handled";
460
+ }
279
461
  }
280
462
  }
281
463
  journal.add({
@@ -352,19 +534,33 @@ async function handleFal(req, res, body, pathname, fixtures, defaults, journal)
352
534
  return "handled";
353
535
  }
354
536
  const requestId = node_crypto.default.randomUUID();
355
- falQueueStates.set(stateKey(requestId), {
537
+ const progression = resolveProgression(defaults.falQueue);
538
+ const now = Date.now();
539
+ const initialStatus = progression.pollsBeforeCompleted === 0 ? "COMPLETED" : "IN_QUEUE";
540
+ const job = {
356
541
  requestId,
357
542
  modelId,
358
- status: "COMPLETED",
543
+ status: initialStatus,
359
544
  result: payload,
360
- createdAt: Date.now()
361
- });
545
+ pollCount: 0,
546
+ pollsBeforeInProgress: progression.pollsBeforeInProgress,
547
+ pollsBeforeCompleted: progression.pollsBeforeCompleted,
548
+ submittedAt: now,
549
+ completedAt: initialStatus === "COMPLETED" ? now : null,
550
+ logs: [{
551
+ timestamp: new Date(now).toISOString(),
552
+ level: "INFO",
553
+ message: "Job enqueued."
554
+ }],
555
+ createdAt: now
556
+ };
557
+ falQueueStates.set(stateKey(requestId), job);
362
558
  const envelope = {
363
559
  request_id: requestId,
364
560
  response_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}`,
365
561
  status_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}/status`,
366
562
  cancel_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${requestId}/cancel`,
367
- queue_position: 0
563
+ queue_position: queuePosition(job)
368
564
  };
369
565
  journal.add({
370
566
  method: req.method ?? "POST",
@@ -386,8 +582,188 @@ function parseBody(raw) {
386
582
  if (!raw.trim()) return null;
387
583
  try {
388
584
  return JSON.parse(raw);
585
+ } catch (err) {
586
+ const detail = err instanceof Error ? err.message : "unknown";
587
+ throw new Error(`Malformed JSON: ${detail}`);
588
+ }
589
+ }
590
+ const DEFAULT_FAL_POLL_INTERVAL_MS = 1e3;
591
+ const DEFAULT_FAL_TIMEOUT_MS = 9e5;
592
+ const FAL_STRIP_FORWARD_HEADERS = new Set([
593
+ "connection",
594
+ "keep-alive",
595
+ "transfer-encoding",
596
+ "te",
597
+ "trailer",
598
+ "upgrade",
599
+ "proxy-authorization",
600
+ "proxy-authenticate",
601
+ "host",
602
+ "content-length",
603
+ "cookie",
604
+ "accept-encoding"
605
+ ]);
606
+ function buildFalForwardHeaders(req) {
607
+ const out = {};
608
+ for (const [name, val] of Object.entries(req.headers)) {
609
+ if (val === void 0) continue;
610
+ if (FAL_STRIP_FORWARD_HEADERS.has(name.toLowerCase())) continue;
611
+ out[name] = Array.isArray(val) ? val.join(", ") : val;
612
+ }
613
+ return out;
614
+ }
615
+ /**
616
+ * Walk a fal-shaped queue protocol upstream: POST submit, poll status until
617
+ * COMPLETED, GET final result body. Returns the parsed final body so the caller
618
+ * can persist it as the fixture and seed local queue state.
619
+ *
620
+ * Decoupled from the route layer so the legacy `/fal/queue/submit/{model}`
621
+ * audio path (`fal-audio.ts`) can reuse the same logic.
622
+ */
623
+ async function walkFalQueue(args) {
624
+ const { upstreamBase, submitPath, body, headers, pollIntervalMs = DEFAULT_FAL_POLL_INTERVAL_MS, timeoutMs = DEFAULT_FAL_TIMEOUT_MS, fallbackStatusPath, fallbackResultPath } = args;
625
+ const deadline = Date.now() + timeoutMs;
626
+ const submitUrl = require_url.resolveUpstreamUrl(upstreamBase, submitPath);
627
+ const submitRes = await fetch(submitUrl, {
628
+ method: "POST",
629
+ headers,
630
+ body
631
+ });
632
+ const submitText = await submitRes.text();
633
+ if (!submitRes.ok) throw new Error(`Submit ${submitRes.status}: ${submitText.slice(0, 200)}`);
634
+ const env = parseJsonOrThrow(submitText, "Submit");
635
+ const upstreamRequestId = String(env.request_id ?? "").trim();
636
+ if (!upstreamRequestId) throw new Error("Submit response missing request_id");
637
+ const envStatusUrl = env.status_url;
638
+ const envResponseUrl = env.response_url;
639
+ const statusUrl = typeof envStatusUrl === "string" && envStatusUrl ? new URL(envStatusUrl) : require_url.resolveUpstreamUrl(upstreamBase, fallbackStatusPath(upstreamRequestId));
640
+ const resultUrl = typeof envResponseUrl === "string" && envResponseUrl ? new URL(envResponseUrl) : require_url.resolveUpstreamUrl(upstreamBase, fallbackResultPath(upstreamRequestId));
641
+ while (true) {
642
+ if (Date.now() > deadline) throw new Error(`Queue walk timed out after ${timeoutMs}ms`);
643
+ const statusRes = await fetch(statusUrl, { headers });
644
+ const statusText = await statusRes.text();
645
+ if (!statusRes.ok) throw new Error(`Status ${statusRes.status}: ${statusText.slice(0, 200)}`);
646
+ const statusJson = parseJsonOrThrow(statusText, "Status");
647
+ const s = String(statusJson.status ?? "");
648
+ if (s === "COMPLETED") break;
649
+ if (s === "FAILED" || s === "ERROR" || s === "CANCELLED") throw new Error(`Upstream job terminated with status ${s}`);
650
+ const remaining = deadline - Date.now();
651
+ const sleep = Math.min(pollIntervalMs, Math.max(0, remaining));
652
+ if (sleep <= 0) throw new Error(`Queue walk timed out after ${timeoutMs}ms`);
653
+ await new Promise((r) => setTimeout(r, sleep));
654
+ }
655
+ const resultRes = await fetch(resultUrl, { headers });
656
+ const resultText = await resultRes.text();
657
+ if (!resultRes.ok) throw new Error(`Result ${resultRes.status}: ${resultText.slice(0, 200)}`);
658
+ return parseJsonOrThrow(resultText, "Result");
659
+ }
660
+ async function proxyAndRecordFalQueueSubmit(args) {
661
+ const { req, res, syntheticReq, modelId, pathname, strippedPath, body, fixtures, defaults, stateKey, journal } = args;
662
+ const record = defaults.record;
663
+ if (!record) return "no_upstream";
664
+ const upstreamBase = record.providers.fal;
665
+ if (!upstreamBase) {
666
+ defaults.logger.warn(`No upstream URL configured for provider "fal" — cannot proxy`);
667
+ return "no_upstream";
668
+ }
669
+ defaults.logger.warn(`NO FIXTURE MATCH — walking fal queue at ${upstreamBase}${strippedPath}`);
670
+ let finalBody;
671
+ try {
672
+ finalBody = await walkFalQueue({
673
+ upstreamBase,
674
+ submitPath: strippedPath,
675
+ body,
676
+ headers: buildFalForwardHeaders(req),
677
+ pollIntervalMs: record.fal?.pollIntervalMs,
678
+ timeoutMs: record.fal?.timeoutMs,
679
+ fallbackStatusPath: (id) => `${modelId}/requests/${id}/status`,
680
+ fallbackResultPath: (id) => `${modelId}/requests/${id}`
681
+ });
682
+ } catch (err) {
683
+ const msg = err instanceof Error ? err.message : "Unknown queue-walk error";
684
+ defaults.logger.error(`fal queue-walk proxy failed: ${msg}`);
685
+ journal.add({
686
+ method: req.method ?? "POST",
687
+ path: pathname,
688
+ headers: require_helpers.flattenHeaders(req.headers),
689
+ body: syntheticReq,
690
+ response: {
691
+ status: 502,
692
+ fixture: null,
693
+ source: "proxy"
694
+ }
695
+ });
696
+ res.writeHead(502, { "Content-Type": "application/json" });
697
+ res.end(JSON.stringify({ error: {
698
+ message: `Proxy to upstream failed: ${msg}`,
699
+ type: "proxy_error"
700
+ } }));
701
+ return "handled";
702
+ }
703
+ const fixture = {
704
+ match: require_recorder.buildFixtureMatch(defaults.requestTransform ? defaults.requestTransform(syntheticReq) : syntheticReq, record),
705
+ response: {
706
+ json: finalBody,
707
+ status: 200
708
+ }
709
+ };
710
+ require_recorder.persistFixture({
711
+ record,
712
+ providerKey: "fal",
713
+ testId: require_helpers.getTestId(req),
714
+ fixture,
715
+ fixtures,
716
+ logger: defaults.logger
717
+ });
718
+ const newRequestId = node_crypto.default.randomUUID();
719
+ const progression = resolveProgression(defaults.falQueue);
720
+ const now = Date.now();
721
+ const initialStatus = progression.pollsBeforeCompleted === 0 ? "COMPLETED" : "IN_QUEUE";
722
+ const job = {
723
+ requestId: newRequestId,
724
+ modelId,
725
+ status: initialStatus,
726
+ result: finalBody,
727
+ pollCount: 0,
728
+ pollsBeforeInProgress: progression.pollsBeforeInProgress,
729
+ pollsBeforeCompleted: progression.pollsBeforeCompleted,
730
+ submittedAt: now,
731
+ completedAt: initialStatus === "COMPLETED" ? now : null,
732
+ logs: [{
733
+ timestamp: new Date(now).toISOString(),
734
+ level: "INFO",
735
+ message: "Job enqueued."
736
+ }],
737
+ createdAt: now
738
+ };
739
+ falQueueStates.set(stateKey(newRequestId), job);
740
+ const envelope = {
741
+ request_id: newRequestId,
742
+ response_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${newRequestId}`,
743
+ status_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${newRequestId}/status`,
744
+ cancel_url: `https://${FAL_HOSTS.queue}/${modelId}/requests/${newRequestId}/cancel`,
745
+ queue_position: queuePosition(job)
746
+ };
747
+ journal.add({
748
+ method: req.method ?? "POST",
749
+ path: pathname,
750
+ headers: require_helpers.flattenHeaders(req.headers),
751
+ body: syntheticReq,
752
+ response: {
753
+ status: 200,
754
+ fixture: null,
755
+ source: "proxy"
756
+ }
757
+ });
758
+ res.writeHead(200, { "Content-Type": "application/json" });
759
+ res.end(JSON.stringify(envelope));
760
+ return "handled";
761
+ }
762
+ function parseJsonOrThrow(text, label) {
763
+ try {
764
+ return JSON.parse(text);
389
765
  } catch {
390
- return null;
766
+ throw new Error(`${label} returned non-JSON: ${text.slice(0, 200)}`);
391
767
  }
392
768
  }
393
769
  function withFalUpstream(defaults, targetHost) {
@@ -438,6 +814,10 @@ function respondNotFound(req, res, pathname, journal, requestId) {
438
814
 
439
815
  //#endregion
440
816
  exports.FalQueueStateMap = FalQueueStateMap;
817
+ exports.buildFalForwardHeaders = buildFalForwardHeaders;
441
818
  exports.falQueueStates = falQueueStates;
442
819
  exports.handleFal = handleFal;
820
+ exports.imageResponseToFalJson = imageResponseToFalJson;
821
+ exports.videoResponseToFalJson = videoResponseToFalJson;
822
+ exports.walkFalQueue = walkFalQueue;
443
823
  //# sourceMappingURL=fal.cjs.map