@mevdragon/vidfarm-devcli 0.20.1 → 0.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/app.js CHANGED
@@ -24,6 +24,7 @@ import { writeForkManifest } from "./services/fork-manifest.js";
24
24
  import { renderHelpPage } from "./help-page.js";
25
25
  import { renderHomepage } from "./homepage.js";
26
26
  import { renderLandingPage } from "./landing-page.js";
27
+ import { createTemplateJobContext } from "./context.js";
27
28
  import { renderReskinIndex } from "./reskin/index-page.js";
28
29
  import { renderReskinSettings } from "./reskin/settings-page.js";
29
30
  import { renderReskinLibrary } from "./reskin/library-page.js";
@@ -76,7 +77,7 @@ import { clipRecords, makeUserPreset } from "./services/clip-records.js";
76
77
  import { matchScenesToClips, searchUserClips, vectorIndex } from "./services/clip-search.js";
77
78
  import { ATTACHMENTS_FOLDER_SCOPE, buildDirectoryPath, DIRECTORY_ROOTS, DIRECTORY_ROOT_KEYS, folderItem, immediateChildFolders, normalizeFolderPath, parseDirectoryPath } from "./services/file-directory.js";
78
79
  import { applyHuntSpecDefaults, BUILTIN_PRESETS, buildClipEmbeddingText, buildEffectiveGuidance, ClipModelClient, cosineSimilarity, detectLocalAgent, effectiveHuntDurationSec, ACTIVE_TAXONOMY_VERSION, estimateScanCostFromDuration, extractAudioClip, extractClip, extractThumbnail, extractWaveformThumbnail, hasFfmpeg, LocalAgentClipClient, normalizeCropFocus, normalizeHuntSpec, parseClipHuntPrompt, pickBestVideoMedia, probeVideo, resolveDurationBand, resolveTargetClipCount, scanVideo } from "./services/clip-curation/index.js";
79
- import { analyzeCastMembers, annotateScenesFromSource, buildBlankCompositionHtml, buildSmartDecomposedHtml, DEFAULT_EDITOR_HARNESS, HyperframesService, smartDecomposeVideo, stampCompositionIdentity } from "./services/hyperframes.js";
80
+ import { analyzeCastMembers, annotateScenesFromSource, buildBlankCompositionHtml, buildSmartDecomposedData, buildSmartDecomposedHtml, DEFAULT_EDITOR_HARNESS, DEFAULT_VIRAL_DNA, HyperframesService, smartDecomposeVideo, stampCompositionIdentity } from "./services/hyperframes.js";
80
81
  import { buildPendingSceneAnnotationsState } from "./services/scene-annotations.js";
81
82
  import { createPrimitiveJobContext } from "./primitive-context.js";
82
83
  import { buildPendingCastState, castMemberFromRaw, extractCastStill, isolateCastMember } from "./services/cast.js";
@@ -7805,7 +7806,7 @@ async function publishCompositionToRenderer(input) {
7805
7806
  fps: 30,
7806
7807
  chunk_size: 240,
7807
7808
  max_parallel_chunks: 8,
7808
- output_key: `renders/vidfarm/${slugId}/${Date.now().toString(36)}/output.mp4`,
7809
+ output_key: "output.mp4",
7809
7810
  project_files: projectFiles,
7810
7811
  composition_data: compositionData ?? undefined
7811
7812
  });
@@ -7894,6 +7895,48 @@ async function runPrimitiveJobInProcess(jobId) {
7894
7895
  throw error;
7895
7896
  }
7896
7897
  }
7898
+ async function runTemplateJobInProcess(jobId) {
7899
+ const job = await jobs.getJobAsync(jobId);
7900
+ if (!job)
7901
+ throw new Error(`Job not found: ${jobId}`);
7902
+ const failContract = async (message) => {
7903
+ await jobs.failJob(job.id, { message, type: "local_runner_contract_error" });
7904
+ throw new Error(message);
7905
+ };
7906
+ const customer = await serverlessRecords.getCustomerById(job.customerId);
7907
+ if (!customer)
7908
+ return failContract(`Customer not found: ${job.customerId}`);
7909
+ const template = await templateRegistry.getExecutable(job.templateId);
7910
+ if (!template)
7911
+ return failContract(`Template not found: ${job.templateId}`);
7912
+ const workflow = template.jobs[job.workflowName];
7913
+ if (!workflow)
7914
+ return failContract(`Template workflow not found: ${job.workflowName}`);
7915
+ const workerId = createId("local_worker");
7916
+ try {
7917
+ await jobs.markRunning(job.id);
7918
+ const ctx = createTemplateJobContext({
7919
+ customer,
7920
+ job,
7921
+ template,
7922
+ workerId,
7923
+ storage,
7924
+ jobs,
7925
+ billing,
7926
+ providers,
7927
+ hyperframes
7928
+ });
7929
+ const result = await workflow(ctx, job.payload);
7930
+ await jobs.succeedJob(job.id, result.output ?? {}, result.progress ?? 1);
7931
+ }
7932
+ catch (error) {
7933
+ await jobs.failJob(job.id, {
7934
+ message: error instanceof Error ? error.message : "Local template job failed",
7935
+ type: "local_render_failed"
7936
+ });
7937
+ throw error;
7938
+ }
7939
+ }
7897
7940
  function mapJobStatusToPublishPhase(status) {
7898
7941
  const normalized = String(status ?? "").toLowerCase();
7899
7942
  if (normalized === "completed" || normalized === "succeeded")
@@ -7977,6 +8020,7 @@ function adaptJobToPublishStatus(job, fallbackTitle) {
7977
8020
  cost,
7978
8021
  outputS3Uri: readNonEmptyString(asRecord(result.output)?.s3_uri) ?? readNonEmptyString(renderBlock.output_s3_uri),
7979
8022
  outputUrl,
8023
+ expectedOutputPublicUrl: readNonEmptyString(record.expected_output_public_url) ?? expectedHyperframesRenderOutputPublicUrl(job),
7980
8024
  title: readNonEmptyString(asRecord(record.payload)?.title) ?? fallbackTitle,
7981
8025
  fatalErrorEncountered: Boolean(publishErrorMessage),
7982
8026
  errors: publishErrorMessage ? [{ error: publishErrorMessage }] : []
@@ -8807,6 +8851,7 @@ app.post(`${INSPIRATIONS_PREFIX}/:inspirationId/decompose`, async (c) => {
8807
8851
  result: smart
8808
8852
  });
8809
8853
  await storage.putText(storage.compositionForkWorkingKey(canonicalFork.id, "composition.html"), nextHtml, "text/html; charset=utf-8");
8854
+ await storage.putJson(storage.compositionForkWorkingKey(canonicalFork.id, "composition.json"), buildSmartDecomposedData({ sourceUrl, result: smart }));
8810
8855
  // Persist placement surfaces on the canonical fork too, before the
8811
8856
  // snapshot, so they travel with v1 and seed every downstream fork.
8812
8857
  try {
@@ -9296,6 +9341,7 @@ app.post(`${COMPOSITIONS_PREFIX}/:forkId/auto-decompose`, async (c) => {
9296
9341
  }
9297
9342
  const nextHtml = buildSmartDecomposedHtml({ compositionId, sourceUrl, title, result: smart });
9298
9343
  await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), nextHtml, "text/html; charset=utf-8");
9344
+ await storage.putJson(storage.compositionForkWorkingKey(fork.id, "composition.json"), buildSmartDecomposedData({ sourceUrl, result: smart }));
9299
9345
  // Persist the raw smart-decompose result so the async /remove-video-captions-poll
9300
9346
  // handler can rebuild the composition HTML pointing at the cleaned
9301
9347
  // (ghostcut-mirrored) video URL — with the ORIGINAL captions, positions,
@@ -9685,10 +9731,7 @@ async function rebuildDecomposedCompositionAgainstCleanVideo(input) {
9685
9731
  title: persisted.title ?? fork.title ?? fork.id,
9686
9732
  result: {
9687
9733
  summary: persisted.result.summary ?? "",
9688
- viralDna: persisted.result.viralDna ?? {
9689
- trend_tagline: "", hook: "", retention: "", payoff: "",
9690
- preserve: [], avoid: [], promotions: [], keywords: []
9691
- },
9734
+ viralDna: persisted.result.viralDna ?? DEFAULT_VIRAL_DNA,
9692
9735
  transcript: persisted.result.transcript ?? null,
9693
9736
  durationSeconds: Number(persisted.result.durationSeconds ?? 0),
9694
9737
  width: Number(persisted.result.width ?? 720),
@@ -9704,6 +9747,21 @@ async function rebuildDecomposedCompositionAgainstCleanVideo(input) {
9704
9747
  }
9705
9748
  });
9706
9749
  await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), rebuilt, "text/html; charset=utf-8");
9750
+ await storage.putJson(storage.compositionForkWorkingKey(fork.id, "composition.json"), buildSmartDecomposedData({
9751
+ sourceUrl: cleanedSourceUrl,
9752
+ result: {
9753
+ summary: persisted.result.summary ?? "",
9754
+ viralDna: persisted.result.viralDna ?? DEFAULT_VIRAL_DNA,
9755
+ transcript: persisted.result.transcript ?? null,
9756
+ durationSeconds: Number(persisted.result.durationSeconds ?? 0),
9757
+ width: Number(persisted.result.width ?? 720),
9758
+ height: Number(persisted.result.height ?? 1280),
9759
+ scenes: persisted.result.scenes,
9760
+ captions: Array.isArray(persisted.result.captions) ? persisted.result.captions : [],
9761
+ placementSurfaces: [],
9762
+ editorHarness: persisted.result.editorHarness ?? DEFAULT_EDITOR_HARNESS
9763
+ }
9764
+ }));
9707
9765
  await snapshotForkVersion({
9708
9766
  fork,
9709
9767
  callerCustomerId,
@@ -10238,7 +10296,7 @@ app.post(`${COMPOSITIONS_PREFIX}/:forkId/scene-annotations-poll`, async (c) => {
10238
10296
  sourceUrl: state.sourceUrl,
10239
10297
  keys,
10240
10298
  scenes,
10241
- viralDna: viralDna ?? { trend_tagline: "", hook: "", retention: "", payoff: "", preserve: [], avoid: [], promotions: [], keywords: [] },
10299
+ viralDna: viralDna ?? DEFAULT_VIRAL_DNA,
10242
10300
  summary,
10243
10301
  transcript,
10244
10302
  userPrompt
@@ -16449,8 +16507,10 @@ async function serializeJob(job) {
16449
16507
  customerId: job.customerId,
16450
16508
  templateId: job.templateId
16451
16509
  });
16510
+ const expectedOutputPublicUrl = expectedHyperframesRenderOutputPublicUrl(job);
16452
16511
  return {
16453
16512
  job_id: job.id,
16513
+ customer_id: job.customerId,
16454
16514
  template_id: job.templateId,
16455
16515
  operation_name: job.operationName,
16456
16516
  workflow_name: job.workflowName,
@@ -16466,6 +16526,7 @@ async function serializeJob(job) {
16466
16526
  event_count: jobBilling.eventCount,
16467
16527
  consumed_usd: jobBilling.totalChargeUsd
16468
16528
  },
16529
+ expected_output_public_url: expectedOutputPublicUrl,
16469
16530
  parent_job_id: job.parentJobId,
16470
16531
  created_at: job.createdAt,
16471
16532
  updated_at: job.updatedAt,
@@ -16485,6 +16546,19 @@ function serializeArtifact(artifact) {
16485
16546
  created_at: artifact.createdAt
16486
16547
  };
16487
16548
  }
16549
+ function expectedHyperframesRenderOutputPublicUrl(job) {
16550
+ const record = asRecord(job);
16551
+ if (!record)
16552
+ return null;
16553
+ const templateId = readNonEmptyString(record.templateId) ?? readNonEmptyString(record.template_id);
16554
+ const customerId = readNonEmptyString(record.customerId) ?? readNonEmptyString(record.customer_id);
16555
+ const jobId = readNonEmptyString(record.id) ?? readNonEmptyString(record.job_id);
16556
+ if (templateId !== "primitive:hyperframes_render" || !customerId || !jobId)
16557
+ return null;
16558
+ const outputKey = readNonEmptyString(asRecord(record.payload)?.output_key) ?? "output.mp4";
16559
+ const storageKey = storage.primitiveJobKey(templateId, customerId, jobId, outputKey);
16560
+ return storage.getPublicUrl(storageKey);
16561
+ }
16488
16562
  async function buildJobDebugBundle(input) {
16489
16563
  const job = input.job;
16490
16564
  const startTime = input.startTime ?? offsetIso(job.createdAt, -60_000);
@@ -17020,7 +17094,7 @@ async function deriveProviderHint(input) {
17020
17094
  ];
17021
17095
  return useHintIfAvailable(configCandidates.find(isProviderType));
17022
17096
  }
17023
- async function listPrimitiveJobsForCustomer(input) {
17097
+ async function listJobsForCustomerByTemplateId(input) {
17024
17098
  const target = clampPageLimit(input.limit, 100) + 1;
17025
17099
  const collected = [];
17026
17100
  let cursor = input.cursor ?? null;
@@ -17037,7 +17111,8 @@ async function listPrimitiveJobsForCustomer(input) {
17037
17111
  break;
17038
17112
  }
17039
17113
  for (const job of batch) {
17040
- if (job.templateId.startsWith("primitive:") && (!input.primitiveId || job.templateId === input.primitiveId)) {
17114
+ if ((!input.templateId || job.templateId === input.templateId) &&
17115
+ (!input.templateIdPrefix || job.templateId.startsWith(input.templateIdPrefix))) {
17041
17116
  collected.push(job);
17042
17117
  if (collected.length >= target) {
17043
17118
  break;
@@ -17058,6 +17133,16 @@ async function listPrimitiveJobsForCustomer(input) {
17058
17133
  }
17059
17134
  return collected;
17060
17135
  }
17136
+ async function getJobForCustomerByTemplateId(input) {
17137
+ const job = await jobs.getJobAsync(input.jobId);
17138
+ if (!job || job.customerId !== input.customerId) {
17139
+ return null;
17140
+ }
17141
+ if (input.templateId && job.templateId !== input.templateId) {
17142
+ return null;
17143
+ }
17144
+ return job;
17145
+ }
17061
17146
  async function createPrimitiveJob(c, input) {
17062
17147
  const customer = requireCustomer(c);
17063
17148
  const primitive = primitiveRegistry.get(input.primitiveId);
@@ -17145,6 +17230,90 @@ async function createPrimitiveJob(c, input) {
17145
17230
  operation_name: input.operationName
17146
17231
  }, 202);
17147
17232
  }
17233
+ async function createTemplateJob(c, input) {
17234
+ const customer = requireCustomer(c);
17235
+ const template = await templateRegistry.getExecutable(input.templateId);
17236
+ if (!template) {
17237
+ return c.json({ error: "Template not found" }, 404);
17238
+ }
17239
+ const operation = template.operations[input.operationName];
17240
+ if (!operation) {
17241
+ return c.json({ error: "Template operation not found" }, 404);
17242
+ }
17243
+ let body;
17244
+ let payload;
17245
+ try {
17246
+ body = primitiveEnvelopeSchema.parse(await c.req.json());
17247
+ payload = operation.inputSchema.parse(body.payload);
17248
+ }
17249
+ catch (error) {
17250
+ if (error instanceof z.ZodError) {
17251
+ return c.json({
17252
+ error: "Invalid template request.",
17253
+ issues: error.issues
17254
+ }, 400);
17255
+ }
17256
+ if (isJsonBodyParseError(error)) {
17257
+ return c.json({
17258
+ error: "Invalid template request.",
17259
+ detail: "Request body must be valid JSON with top-level tracer and payload fields."
17260
+ }, 400);
17261
+ }
17262
+ throw error;
17263
+ }
17264
+ const providerPreflight = await preflightAiProviderKeys({
17265
+ customerId: customer.id,
17266
+ templateId: template.id,
17267
+ operationName: input.operationName,
17268
+ operationHint: operation.providerHint,
17269
+ payload
17270
+ });
17271
+ if (providerPreflight) {
17272
+ return c.json(providerPreflight, 412);
17273
+ }
17274
+ let job;
17275
+ try {
17276
+ await assertWalletCanQueueJobs(customer.id);
17277
+ job = await jobs.createRootJob({
17278
+ templateId: template.id,
17279
+ operationName: input.operationName,
17280
+ workflowName: operation.workflow,
17281
+ tracer: body.tracer,
17282
+ payload,
17283
+ webhookUrl: body.webhook_url,
17284
+ customer,
17285
+ providerHint: await deriveProviderHint({
17286
+ customerId: customer.id,
17287
+ templateId: template.id,
17288
+ operationName: input.operationName,
17289
+ operationHint: operation.providerHint,
17290
+ payload
17291
+ })
17292
+ });
17293
+ }
17294
+ catch (error) {
17295
+ if (error instanceof InsufficientWalletFundsError) {
17296
+ return insufficientFundsResponse(c, error);
17297
+ }
17298
+ if (error instanceof ActiveJobLimitExceededError) {
17299
+ return activeJobLimitExceededResponse(c, error);
17300
+ }
17301
+ throw error;
17302
+ }
17303
+ if (!config.VIDFARM_JOB_STATE_MACHINE_ARN) {
17304
+ void runTemplateJobInProcess(job.id).catch((error) => {
17305
+ console.error("local template job failed", job.id, error instanceof Error ? error.message : error);
17306
+ });
17307
+ }
17308
+ return c.json({
17309
+ job_id: job.id,
17310
+ tracer: job.tracer,
17311
+ status: job.status,
17312
+ template_id: template.id,
17313
+ template_type: "template",
17314
+ operation_name: input.operationName
17315
+ }, 202);
17316
+ }
17148
17317
  function resolvePrimitiveRouteTarget(primitiveParam) {
17149
17318
  const normalized = primitiveParam.trim();
17150
17319
  if (!normalized) {
@@ -17185,6 +17354,12 @@ app.post(`${PRIMITIVES_PREFIX}/:primitiveKind/operations/:operationName`, async
17185
17354
  operationName: c.req.param("operationName")
17186
17355
  });
17187
17356
  });
17357
+ app.post(`${TEMPLATES_PREFIX}/:templateId/operations/:operationName`, async (c) => {
17358
+ return createTemplateJob(c, {
17359
+ templateId: c.req.param("templateId"),
17360
+ operationName: c.req.param("operationName")
17361
+ });
17362
+ });
17188
17363
  app.get(`${PRIMITIVES_PREFIX}/jobs`, async (c) => {
17189
17364
  const customer = requireCustomer(c);
17190
17365
  const query = listJobsQuerySchema.parse({
@@ -17195,8 +17370,9 @@ app.get(`${PRIMITIVES_PREFIX}/jobs`, async (c) => {
17195
17370
  limit: c.req.query("limit")
17196
17371
  });
17197
17372
  const limit = clampPageLimit(query.limit, 100);
17198
- const listedJobs = await listPrimitiveJobsForCustomer({
17373
+ const listedJobs = await listJobsForCustomerByTemplateId({
17199
17374
  customerId: customer.id,
17375
+ templateIdPrefix: "primitive:",
17200
17376
  tracer: query.tracer,
17201
17377
  startTime: query.start_time,
17202
17378
  endTime: query.end_time,
@@ -17206,9 +17382,40 @@ app.get(`${PRIMITIVES_PREFIX}/jobs`, async (c) => {
17206
17382
  const page = paginateDescendingByCreatedAt(listedJobs, limit);
17207
17383
  return c.json({ primitive_jobs: await Promise.all(page.items.map(serializePrimitiveJob)), next_cursor: page.nextCursor });
17208
17384
  });
17385
+ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs`, async (c) => {
17386
+ const customer = requireCustomer(c);
17387
+ const template = await templateRegistry.getExecutable(c.req.param("templateId"));
17388
+ if (!template) {
17389
+ return c.json({ error: "Template not found" }, 404);
17390
+ }
17391
+ const query = listJobsQuerySchema.parse({
17392
+ tracer: c.req.query("tracer"),
17393
+ start_time: c.req.query("start_time"),
17394
+ end_time: c.req.query("end_time"),
17395
+ cursor: c.req.query("cursor"),
17396
+ limit: c.req.query("limit")
17397
+ });
17398
+ const limit = clampPageLimit(query.limit, 100);
17399
+ const listedJobs = await listJobsForCustomerByTemplateId({
17400
+ customerId: customer.id,
17401
+ templateId: template.id,
17402
+ tracer: query.tracer,
17403
+ startTime: query.start_time,
17404
+ endTime: query.end_time,
17405
+ limit,
17406
+ cursor: parseCreatedAtIdCursor(query.cursor)
17407
+ });
17408
+ const page = paginateDescendingByCreatedAt(listedJobs, limit);
17409
+ return c.json({
17410
+ template_id: template.id,
17411
+ template_type: "template",
17412
+ jobs: await Promise.all(page.items.map(serializeJob)),
17413
+ next_cursor: page.nextCursor
17414
+ });
17415
+ });
17209
17416
  app.get(`${PRIMITIVES_PREFIX}/jobs/:jobId`, async (c) => {
17210
17417
  const customer = requireCustomer(c);
17211
- const job = await getPrimitiveJobForCustomer({
17418
+ const job = await getJobForCustomerByTemplateId({
17212
17419
  jobId: c.req.param("jobId"),
17213
17420
  customerId: customer.id
17214
17421
  });
@@ -17217,9 +17424,25 @@ app.get(`${PRIMITIVES_PREFIX}/jobs/:jobId`, async (c) => {
17217
17424
  }
17218
17425
  return c.json(await serializePrimitiveJob(job));
17219
17426
  });
17427
+ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId`, async (c) => {
17428
+ const customer = requireCustomer(c);
17429
+ const template = await templateRegistry.getExecutable(c.req.param("templateId"));
17430
+ if (!template) {
17431
+ return c.json({ error: "Template not found" }, 404);
17432
+ }
17433
+ const job = await getJobForCustomerByTemplateId({
17434
+ jobId: c.req.param("jobId"),
17435
+ customerId: customer.id,
17436
+ templateId: template.id
17437
+ });
17438
+ if (!job) {
17439
+ return c.json({ error: "Template job not found" }, 404);
17440
+ }
17441
+ return c.json(await serializeJob(job));
17442
+ });
17220
17443
  app.get(`${PRIMITIVES_PREFIX}/jobs/:jobId/artifacts`, async (c) => {
17221
17444
  const customer = requireCustomer(c);
17222
- const job = await getPrimitiveJobForCustomer({
17445
+ const job = await getJobForCustomerByTemplateId({
17223
17446
  jobId: c.req.param("jobId"),
17224
17447
  customerId: customer.id
17225
17448
  });
@@ -17242,9 +17465,40 @@ app.get(`${PRIMITIVES_PREFIX}/jobs/:jobId/artifacts`, async (c) => {
17242
17465
  next_cursor: page.nextCursor
17243
17466
  });
17244
17467
  });
17468
+ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/artifacts`, async (c) => {
17469
+ const customer = requireCustomer(c);
17470
+ const template = await templateRegistry.getExecutable(c.req.param("templateId"));
17471
+ if (!template) {
17472
+ return c.json({ error: "Template not found" }, 404);
17473
+ }
17474
+ const job = await getJobForCustomerByTemplateId({
17475
+ jobId: c.req.param("jobId"),
17476
+ customerId: customer.id,
17477
+ templateId: template.id
17478
+ });
17479
+ if (!job) {
17480
+ return c.json({ error: "Template job not found" }, 404);
17481
+ }
17482
+ const limit = clampPageLimit(Number(c.req.query("limit") || "100"), 100);
17483
+ const page = await listArtifactsForJobPage({
17484
+ jobId: job.id,
17485
+ customerId: customer.id,
17486
+ templateId: job.templateId,
17487
+ limit,
17488
+ cursor: parseCreatedAtIdCursor(c.req.query("cursor"))
17489
+ });
17490
+ return c.json({
17491
+ job_id: job.id,
17492
+ tracer: job.tracer,
17493
+ template_id: job.templateId,
17494
+ template_type: "template",
17495
+ artifacts: page.items.map(serializeArtifact),
17496
+ next_cursor: page.nextCursor
17497
+ });
17498
+ });
17245
17499
  app.get(`${PRIMITIVES_PREFIX}/jobs/:jobId/logs`, async (c) => {
17246
17500
  const customer = requireCustomer(c);
17247
- const job = await getPrimitiveJobForCustomer({
17501
+ const job = await getJobForCustomerByTemplateId({
17248
17502
  jobId: c.req.param("jobId"),
17249
17503
  customerId: customer.id
17250
17504
  });
@@ -17268,9 +17522,40 @@ app.get(`${PRIMITIVES_PREFIX}/jobs/:jobId/logs`, async (c) => {
17268
17522
  const page = paginateDescendingByCreatedAt(logs, limit);
17269
17523
  return c.json({ job_id: job.id, tracer: job.tracer, logs: page.items, next_cursor: page.nextCursor });
17270
17524
  });
17525
+ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/logs`, async (c) => {
17526
+ const customer = requireCustomer(c);
17527
+ const template = await templateRegistry.getExecutable(c.req.param("templateId"));
17528
+ if (!template) {
17529
+ return c.json({ error: "Template not found" }, 404);
17530
+ }
17531
+ const job = await getJobForCustomerByTemplateId({
17532
+ jobId: c.req.param("jobId"),
17533
+ customerId: customer.id,
17534
+ templateId: template.id
17535
+ });
17536
+ if (!job) {
17537
+ return c.json({ error: "Template job not found" }, 404);
17538
+ }
17539
+ const query = listJobsQuerySchema.parse({
17540
+ start_time: c.req.query("start_time"),
17541
+ end_time: c.req.query("end_time"),
17542
+ cursor: c.req.query("cursor"),
17543
+ limit: c.req.query("limit")
17544
+ });
17545
+ const limit = clampPageLimit(query.limit, 100);
17546
+ const logs = await jobs.listLogs({
17547
+ jobId: job.id,
17548
+ startTime: query.start_time,
17549
+ endTime: query.end_time,
17550
+ limit: limit + 1,
17551
+ cursor: parseCreatedAtIdCursor(query.cursor)
17552
+ });
17553
+ const page = paginateDescendingByCreatedAt(logs, limit);
17554
+ return c.json({ job_id: job.id, tracer: job.tracer, logs: page.items, next_cursor: page.nextCursor });
17555
+ });
17271
17556
  app.post(`${PRIMITIVES_PREFIX}/jobs/:jobId/cancel`, async (c) => {
17272
17557
  const customer = requireCustomer(c);
17273
- const job = await getPrimitiveJobForCustomer({
17558
+ const job = await getJobForCustomerByTemplateId({
17274
17559
  jobId: c.req.param("jobId"),
17275
17560
  customerId: customer.id
17276
17561
  });
@@ -17280,6 +17565,23 @@ app.post(`${PRIMITIVES_PREFIX}/jobs/:jobId/cancel`, async (c) => {
17280
17565
  await jobs.cancelJobAsync(job.id);
17281
17566
  return c.json({ ok: true, job_id: job.id, status: "cancelled" });
17282
17567
  });
17568
+ app.post(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/cancel`, async (c) => {
17569
+ const customer = requireCustomer(c);
17570
+ const template = await templateRegistry.getExecutable(c.req.param("templateId"));
17571
+ if (!template) {
17572
+ return c.json({ error: "Template not found" }, 404);
17573
+ }
17574
+ const job = await getJobForCustomerByTemplateId({
17575
+ jobId: c.req.param("jobId"),
17576
+ customerId: customer.id,
17577
+ templateId: template.id
17578
+ });
17579
+ if (!job) {
17580
+ return c.json({ error: "Template job not found" }, 404);
17581
+ }
17582
+ await jobs.cancelJobAsync(job.id);
17583
+ return c.json({ ok: true, job_id: job.id, status: "cancelled" });
17584
+ });
17283
17585
  app.get(`${PRIMITIVES_PREFIX}/:primitiveKind/jobs`, async (c) => {
17284
17586
  const customer = requireCustomer(c);
17285
17587
  const primitive = resolvePrimitiveRouteTarget(c.req.param("primitiveKind"));
@@ -17294,9 +17596,9 @@ app.get(`${PRIMITIVES_PREFIX}/:primitiveKind/jobs`, async (c) => {
17294
17596
  limit: c.req.query("limit")
17295
17597
  });
17296
17598
  const limit = clampPageLimit(query.limit, 100);
17297
- const listedJobs = await listPrimitiveJobsForCustomer({
17599
+ const listedJobs = await listJobsForCustomerByTemplateId({
17298
17600
  customerId: customer.id,
17299
- primitiveId: primitive.id,
17601
+ templateId: primitive.id,
17300
17602
  tracer: query.tracer,
17301
17603
  startTime: query.start_time,
17302
17604
  endTime: query.end_time,
@@ -17317,10 +17619,10 @@ app.get(`${PRIMITIVES_PREFIX}/:primitiveKind/jobs/:jobId`, async (c) => {
17317
17619
  if (!primitive) {
17318
17620
  return c.json({ error: "Primitive not found" }, 404);
17319
17621
  }
17320
- const job = await getPrimitiveJobForCustomer({
17622
+ const job = await getJobForCustomerByTemplateId({
17321
17623
  jobId: c.req.param("jobId"),
17322
17624
  customerId: customer.id,
17323
- primitiveId: primitive.id
17625
+ templateId: primitive.id
17324
17626
  });
17325
17627
  if (!job) {
17326
17628
  return c.json({ error: "Primitive job not found" }, 404);
@@ -17333,10 +17635,10 @@ app.get(`${PRIMITIVES_PREFIX}/:primitiveKind/jobs/:jobId/artifacts`, async (c) =
17333
17635
  if (!primitive) {
17334
17636
  return c.json({ error: "Primitive not found" }, 404);
17335
17637
  }
17336
- const job = await getPrimitiveJobForCustomer({
17638
+ const job = await getJobForCustomerByTemplateId({
17337
17639
  jobId: c.req.param("jobId"),
17338
17640
  customerId: customer.id,
17339
- primitiveId: primitive.id
17641
+ templateId: primitive.id
17340
17642
  });
17341
17643
  if (!job) {
17342
17644
  return c.json({ error: "Primitive job not found" }, 404);
@@ -17364,10 +17666,10 @@ app.get(`${PRIMITIVES_PREFIX}/:primitiveKind/jobs/:jobId/logs`, async (c) => {
17364
17666
  if (!primitive) {
17365
17667
  return c.json({ error: "Primitive not found" }, 404);
17366
17668
  }
17367
- const job = await getPrimitiveJobForCustomer({
17669
+ const job = await getJobForCustomerByTemplateId({
17368
17670
  jobId: c.req.param("jobId"),
17369
17671
  customerId: customer.id,
17370
- primitiveId: primitive.id
17672
+ templateId: primitive.id
17371
17673
  });
17372
17674
  if (!job) {
17373
17675
  return c.json({ error: "Primitive job not found" }, 404);
@@ -17402,10 +17704,10 @@ app.post(`${PRIMITIVES_PREFIX}/:primitiveKind/jobs/:jobId/cancel`, async (c) =>
17402
17704
  if (!primitive) {
17403
17705
  return c.json({ error: "Primitive not found" }, 404);
17404
17706
  }
17405
- const job = await getPrimitiveJobForCustomer({
17707
+ const job = await getJobForCustomerByTemplateId({
17406
17708
  jobId: c.req.param("jobId"),
17407
17709
  customerId: customer.id,
17408
- primitiveId: primitive.id
17710
+ templateId: primitive.id
17409
17711
  });
17410
17712
  if (!job) {
17411
17713
  return c.json({ error: "Primitive job not found" }, 404);