diff --git a/apps/web/e2e/media-card.spec.ts b/apps/web/e2e/media-card.spec.ts index 8938856..3b46bbe 100644 --- a/apps/web/e2e/media-card.spec.ts +++ b/apps/web/e2e/media-card.spec.ts @@ -206,6 +206,6 @@ test("renders a media job card while it runs and when it is done", async ({ page botId, messageId: expect.stringMatching(/^media-message-\d+$/), operation: "transcribe", - input: { artifactId: voice.id, title: "voice-note.wav" }, + input: { artifact_id: voice.id, title: "voice-note.wav" }, }); }); diff --git a/apps/web/src/components/media/MediaCard.tsx b/apps/web/src/components/media/MediaCard.tsx index da2626e..14b6dff 100644 --- a/apps/web/src/components/media/MediaCard.tsx +++ b/apps/web/src/components/media/MediaCard.tsx @@ -378,7 +378,7 @@ export function MediaCard({ : // The new card is titled with the file it transcribes. { operation: "transcribe", - input: { artifactId: action.artifactId, title: action.name }, + input: { artifact_id: action.artifactId, title: action.name }, }), }); }; diff --git a/packages/adapters/src/media/image-operations.postgres.test.ts b/packages/adapters/src/media/image-operations.postgres.test.ts index 2809d4d..23d7326 100644 --- a/packages/adapters/src/media/image-operations.postgres.test.ts +++ b/packages/adapters/src/media/image-operations.postgres.test.ts @@ -120,7 +120,7 @@ describePostgres("ComfyUI operations in the media runtime (PostgreSQL + ComfyUI const edited = await runtime.start({ ...scope, operation: "edit_image", - input: { artifactId: source, resize: { aspect: "vertical", fit: "blur" }, format: "jpeg" }, + input: { artifact_id: source, resize: { aspect: "vertical", fit: "blur" }, format: "jpeg" }, }); await runtime.runJob(edited.jobId); const edit = await runtime.get(edited.jobId); @@ -145,14 +145,14 @@ describePostgres("ComfyUI operations in the media runtime (PostgreSQL + ComfyUI runtime.start({ ...scope, operation: "generate_video", - input: { prompt: "animate it", sourceArtifactId: foreign }, + input: { prompt: "animate it", source_artifact_id: foreign }, }), ).rejects.toThrow(MediaJobRejectedError); await expect( runtime.start({ ...scope, operation: "edit_image", - input: { artifactId: foreign, rotate: 90 }, + input: { artifact_id: foreign, rotate: 90 }, }), ).rejects.toThrow(MediaJobRejectedError); }); diff --git a/packages/adapters/src/media/image-operations.test.ts b/packages/adapters/src/media/image-operations.test.ts index f414c08..84da555 100644 --- a/packages/adapters/src/media/image-operations.test.ts +++ b/packages/adapters/src/media/image-operations.test.ts @@ -165,7 +165,7 @@ describe("image operations", () => { const { outputs } = await run( operation, "generate_image", - { prompt: "night", sourceArtifactId: "photo-1" }, + { prompt: "night", source_artifact_id: "photo-1" }, { "photo-1": photo }, ); const [upload] = emulator.uploads.values(); @@ -173,7 +173,7 @@ describe("image operations", () => { expect(outputs[0]).toMatchObject({ width: 1280, height: 832, parentArtifactId: "photo-1" }); const clip = await fixture("clip.mp4", 8, 8); await expect( - run(operation, "generate_image", { prompt: "x", sourceArtifactId: "c" }, { c: clip }), + run(operation, "generate_image", { prompt: "x", source_artifact_id: "c" }, { c: clip }), ).rejects.toThrow("The source must be an image"); }); @@ -203,7 +203,7 @@ describe("image operations", () => { operation, "edit_image", { - artifactId: "photo-1", + artifact_id: "photo-1", crop: { left: 0, top: 0, width: 300, height: 300 }, rotate: 90, resize: { aspect: "vertical", fit: "blur" }, @@ -226,12 +226,17 @@ describe("image operations", () => { const titled = await run( operation, "edit_image", - { artifactId: "photo-1", title: "Titelbild: Sommer", rotate: 180 }, + { artifact_id: "photo-1", title: "Titelbild: Sommer", rotate: 180 }, { "photo-1": photo }, ); expect(titled.outputs[0]?.name).toBe("Titelbild Sommer.jpg"); await expect( - run(operation, "edit_image", { artifactId: "photo-1", prompt: "snow" }, { "photo-1": photo }), + run( + operation, + "edit_image", + { artifact_id: "photo-1", prompt: "snow" }, + { "photo-1": photo }, + ), ).rejects.toThrow("ComfyUI is not configured"); }); @@ -239,7 +244,7 @@ describe("image operations", () => { const { operation, emulator } = setup(); const photo = await fixture("photo.png", 300, 200); const input = { - artifactId: "photo-1", + artifact_id: "photo-1", crop: { left: 0, top: 0, width: 250, height: 200 }, prompt: "snow", }; @@ -268,7 +273,7 @@ describe("helpers", () => { expect(fileStem("夜の海", "video")).toBe("video"); expect( editImageTitle({ - artifactId: "a", + artifact_id: "a", crop: { left: 0, top: 0, width: 1, height: 1 }, rotate: 90, resize: { aspect: "vertical", fit: "crop" }, @@ -277,15 +282,15 @@ describe("helpers", () => { ).toBe("1×1 · 90° · 9:16 · PNG"); expect( editImageTitle({ - artifactId: "a", + artifact_id: "a", flip: "vertical", extend: { top: 4, right: 0, bottom: 0, left: 0 }, }), ).toBe("↕"); expect( - editImageTitle({ artifactId: "a", title: "Titelbild", prompt: "snow", rotate: 90 }), + editImageTitle({ artifact_id: "a", title: "Titelbild", prompt: "snow", rotate: 90 }), ).toBe("Titelbild"); - expect(editImageTitle({ artifactId: "a", resize: { width: 800, fit: "crop" } })).toBe("800×…"); - expect(editImageTitle({ artifactId: "a", prompt: "make it snow" })).toBe("make it snow"); + expect(editImageTitle({ artifact_id: "a", resize: { width: 800, fit: "crop" } })).toBe("800×…"); + expect(editImageTitle({ artifact_id: "a", prompt: "make it snow" })).toBe("make it snow"); }); }); diff --git a/packages/adapters/src/media/image-operations.ts b/packages/adapters/src/media/image-operations.ts index f19ab09..17f481e 100644 --- a/packages/adapters/src/media/image-operations.ts +++ b/packages/adapters/src/media/image-operations.ts @@ -162,8 +162,8 @@ async function generateImage( ): Promise { const input = GenerateImageInputSchema.parse(context.input); const provider = await requireProvider(providers); - const source = input.sourceArtifactId - ? await fetchImage(context, input.sourceArtifactId) + const source = input.source_artifact_id + ? await fetchImage(context, input.source_artifact_id) : undefined; const aspect = input.aspect ? presetAspect(input.aspect) @@ -177,7 +177,7 @@ async function generateImage( const seed = input.seed ?? randomInt(2 ** 48 - 1); const images = await provider.generateImage({ prompt: input.prompt, - negativePrompt: input.negativePrompt, + negativePrompt: input.negative_prompt, ...size, count: input.count, seed, @@ -200,8 +200,8 @@ async function generateVideo( ): Promise { const input = GenerateVideoInputSchema.parse(context.input); const provider = await requireProvider(providers); - const source = input.sourceArtifactId - ? await fetchImage(context, input.sourceArtifactId) + const source = input.source_artifact_id + ? await fetchImage(context, input.source_artifact_id) : undefined; const aspect = input.aspect ? presetAspect(input.aspect) @@ -212,9 +212,9 @@ async function generateVideo( const seed = input.seed ?? randomInt(2 ** 48 - 1); const clips = await provider.generateVideo({ prompt: input.prompt, - negativePrompt: input.negativePrompt, + negativePrompt: input.negative_prompt, ...size, - durationSeconds: input.durationSeconds, + durationSeconds: input.duration_seconds, seed, source: source && { bytes: await coverImage(source.path, size.width, size.height), @@ -224,7 +224,7 @@ async function generateVideo( onProgress: reportProgress(context), }); await addGeneratedOutputs(context, clips, fileStem(input.prompt, "video"), source?.artifactId); - return { summary: `Generated a ${input.durationSeconds} s video with seed ${seed}.` }; + return { summary: `Generated a ${input.duration_seconds} s video with seed ${seed}.` }; } function defaultFormat(mimeType: string): ImageFormat { @@ -238,7 +238,7 @@ async function editImage( providers: ProviderSource, ): Promise { const input = EditImageInputSchema.parse(context.input); - const source = await fetchImage(context, input.artifactId); + const source = await fetchImage(context, input.artifact_id); let png: Uint8Array = await editImageGeometry(source.path, input); if (input.prompt) { const provider = await requireProvider(providers); @@ -289,8 +289,8 @@ export function createImageOperations(deps: ImageOperationsDeps): MediaOperation }; const comfyUiConfigured = async () => Boolean((await deps.loadSettings()).comfyui); const status = async (signal?: AbortSignal) => (await providers())?.gpuStatus(signal); - const sourceIds = (input: { sourceArtifactId?: string }) => - input.sourceArtifactId ? [input.sourceArtifactId] : []; + const sourceIds = (input: { source_artifact_id?: string }) => + input.source_artifact_id ? [input.source_artifact_id] : []; const image: MediaOperationRegistration = { operation: "generate_image", inputSchema: GenerateImageInputSchema, @@ -315,7 +315,7 @@ export function createImageOperations(deps: ImageOperationsDeps): MediaOperation title: editImageTitle, available: sharpAvailable, handler: { operation: "edit_image", run: (context) => editImage(context, providers) }, - artifactIds: (input) => [input.artifactId], + artifactIds: (input) => [input.artifact_id], }; return [image, video, edit] as MediaOperationRegistration[]; } diff --git a/packages/adapters/src/media/transcription.postgres.test.ts b/packages/adapters/src/media/transcription.postgres.test.ts index fc31366..dd11472 100644 --- a/packages/adapters/src/media/transcription.postgres.test.ts +++ b/packages/adapters/src/media/transcription.postgres.test.ts @@ -130,7 +130,7 @@ describePostgres("transcribe through the media runtime (PostgreSQL)", () => { it("turns an audio artifact into subtitle files on the media card", async () => { const ctx = await setup(); expect((await ctx.runtime.capabilities(ctx.id)).transcribe).toBe(true); - const started = await ctx.start({ artifactId: ctx.source.id }); + const started = await ctx.start({ artifact_id: ctx.source.id }); expect(await ctx.card(started.jobId)).toMatchObject({ operation: "transcribe", status: "queued", @@ -163,7 +163,9 @@ describePostgres("transcribe through the media runtime (PostgreSQL)", () => { it("refuses an artifact of another user before a job exists", async () => { const owner = await setup(); const other = await setup(); - await expect(other.start({ artifactId: owner.source.id })).rejects.toThrow("Unknown artifact."); + await expect(other.start({ artifact_id: owner.source.id })).rejects.toThrow( + "Unknown artifact.", + ); expect(await prisma.mediaJob.count({ where: { userId: other.id } })).toBe(0); }); }); diff --git a/packages/adapters/src/media/transcription.test.ts b/packages/adapters/src/media/transcription.test.ts index 1de999b..4b5f0ae 100644 --- a/packages/adapters/src/media/transcription.test.ts +++ b/packages/adapters/src/media/transcription.test.ts @@ -198,18 +198,18 @@ describe("transcribe operation", () => { }), ).toBe(false); const { registration } = operation(); - const input = registration.inputSchema.parse({ artifactId: "audio-1", language: "de" }); + const input = registration.inputSchema.parse({ artifact_id: "audio-1", language: "de" }); expect(registration.artifactIds?.(input)).toEqual(["audio-1"]); expect(registration.title(input)).toBe("Transcript"); expect(registration.title({ ...input, title: "voice-note.wav" })).toBe("voice-note.wav"); expect( - registration.inputSchema.safeParse({ artifactId: "audio-1", language: "German!" }).success, + registration.inputSchema.safeParse({ artifact_id: "audio-1", language: "German!" }).success, ).toBe(false); }); it("turns an audio artifact into SRT and VTT files next to it", async () => { const { registration, server } = operation(); - const job = await jobContext(audioSource, { artifactId: "audio-1", language: "de" }); + const job = await jobContext(audioSource, { artifact_id: "audio-1", language: "de" }); const result = await registration.handler.run(job.context); expect(server.requests).toEqual([ expect.objectContaining({ @@ -234,7 +234,7 @@ describe("transcribe operation", () => { it("writes a text file when the server returns no timings", async () => { const { registration } = operation(TRANSCRIPTION, { flavor: "text" }); - const job = await jobContext(audioSource, { artifactId: "audio-1" }); + const job = await jobContext(audioSource, { artifact_id: "audio-1" }); const result = await registration.handler.run(job.context); expect(job.outputs.map((output) => [output.name, output.mimeType])).toEqual([ ["Interview take 2.txt", "text/plain"], @@ -252,7 +252,7 @@ describe("transcribe operation", () => { }; const { registration } = operation(); await expect( - registration.handler.run((await jobContext(video, { artifactId: "video-1" })).context), + registration.handler.run((await jobContext(video, { artifact_id: "video-1" })).context), ).rejects.toThrow("Transcribing video needs ffmpeg on the server."); const image = { ...audioSource, @@ -261,12 +261,12 @@ describe("transcribe operation", () => { kind: "image" as const, }; await expect( - registration.handler.run((await jobContext(image, { artifactId: "image-1" })).context), + registration.handler.run((await jobContext(image, { artifact_id: "image-1" })).context), ).rejects.toThrow("Only audio and video can be transcribed."); const long = operation(TRANSCRIPTION, { maxBytes: 4 }); await expect( long.registration.handler.run( - (await jobContext(audioSource, { artifactId: "audio-1" })).context, + (await jobContext(audioSource, { artifact_id: "audio-1" })).context, ), ).rejects.toThrow("too long to transcribe without ffmpeg"); }); @@ -294,7 +294,7 @@ describe("transcribe operation", () => { mimeType: "video/mp4", kind: "video" as const, }; - const job = await jobContext(video, { artifactId: "video-1" }); + const job = await jobContext(video, { artifact_id: "video-1" }); await registration.handler.run(job.context); expect(extracted).toEqual([ { startMs: 0, endMs: 62_000, mimeType: "video/mp4" }, @@ -312,7 +312,7 @@ describe("transcribe operation", () => { it("waits for a transcription server that is down instead of failing", async () => { const { registration, server } = operation(); server.failNextStatus = 503; - const job = await jobContext(audioSource, { artifactId: "audio-1" }); + const job = await jobContext(audioSource, { artifact_id: "audio-1" }); await expect(registration.handler.run(job.context)).rejects.toBeInstanceOf( MediaJobDeferredError, ); @@ -358,7 +358,7 @@ describe.skipIf(!ffmpeg)("transcribe with ffmpeg", () => { it("transcribes a video's audio as 16 kHz mono WAV", async () => { const { registration, server } = ffmpegOperation(); const job = await fakeMediaJobContext( - { artifactId: "video-1" }, + { artifact_id: "video-1" }, { "video-1": { path: clips.landscape, name: "landscape.mp4", mimeType: "video/mp4" } }, ); dirs.push(job.tempDir); @@ -374,7 +374,7 @@ describe.skipIf(!ffmpeg)("transcribe with ffmpeg", () => { it("uploads a long recording in ranges below the server's limit", async () => { const { registration, server } = ffmpegOperation(1024); const job = await fakeMediaJobContext( - { artifactId: "audio-1" }, + { artifact_id: "audio-1" }, { "audio-1": { path: longTone, name: "long-tone.m4a", mimeType: "audio/mp4" } }, ); dirs.push(job.tempDir); @@ -387,7 +387,7 @@ describe.skipIf(!ffmpeg)("transcribe with ffmpeg", () => { it("refuses a video without sound", async () => { const { registration, server } = ffmpegOperation(); const job = await fakeMediaJobContext( - { artifactId: "video-2" }, + { artifact_id: "video-2" }, { "video-2": { path: clips.silentPortrait, name: "silent.mp4", mimeType: "video/mp4" } }, ); dirs.push(job.tempDir); diff --git a/packages/adapters/src/media/transcription.ts b/packages/adapters/src/media/transcription.ts index af28f57..da5996d 100644 --- a/packages/adapters/src/media/transcription.ts +++ b/packages/adapters/src/media/transcription.ts @@ -148,7 +148,7 @@ export function createTranscriptionOperations( inputSchema: TranscribeInputSchema, title: (input) => input.title ?? "Transcript", available: async () => Boolean((await deps.loadSettings()).transcription), - artifactIds: (input) => [input.artifactId], + artifactIds: (input) => [input.artifact_id], handler: { operation: "transcribe", async run(context) { @@ -156,7 +156,7 @@ export function createTranscriptionOperations( const settings = (await deps.loadSettings()).transcription; if (!settings) throw new MediaJobRejectedError("Transcription is not set up."); const provider = createProvider(settings); - const source = await context.fetchArtifact(input.artifactId); + const source = await context.fetchArtifact(input.artifact_id); if (source.kind !== "audio" && source.kind !== "video") { throw new MediaJobRejectedError("Only audio and video can be transcribed."); } diff --git a/packages/contracts/src/media-generation.test.ts b/packages/contracts/src/media-generation.test.ts index 32f9b78..f382f3d 100644 --- a/packages/contracts/src/media-generation.test.ts +++ b/packages/contracts/src/media-generation.test.ts @@ -13,7 +13,7 @@ describe("media generation inputs", () => { }); expect(GenerateVideoInputSchema.parse({ prompt: "waves" })).toEqual({ prompt: "waves", - durationSeconds: 5, + duration_seconds: 5, }); }); @@ -23,20 +23,21 @@ describe("media generation inputs", () => { expect( GenerateImageInputSchema.safeParse({ prompt: "x", width: 512, height: 4096 }).success, ).toBe(false); - expect(GenerateVideoInputSchema.safeParse({ prompt: "x", durationSeconds: 11 }).success).toBe( + expect(GenerateVideoInputSchema.safeParse({ prompt: "x", duration_seconds: 11 }).success).toBe( false, ); }); it("needs at least one edit and valid edit values", () => { - expect(EditImageInputSchema.safeParse({ artifactId: "a" }).success).toBe(false); - expect(EditImageInputSchema.safeParse({ artifactId: "a", rotate: 45 }).success).toBe(false); - expect(EditImageInputSchema.safeParse({ artifactId: "a", resize: {} }).success).toBe(false); + expect(EditImageInputSchema.safeParse({ artifact_id: "a" }).success).toBe(false); + expect(EditImageInputSchema.safeParse({ artifact_id: "a", rotate: 45 }).success).toBe(false); + expect(EditImageInputSchema.safeParse({ artifact_id: "a", resize: {} }).success).toBe(false); expect( - EditImageInputSchema.safeParse({ artifactId: "a", extend: { top: 4, color: "red" } }).success, + EditImageInputSchema.safeParse({ artifact_id: "a", extend: { top: 4, color: "red" } }) + .success, ).toBe(false); expect( - EditImageInputSchema.parse({ artifactId: "a", resize: { aspect: "vertical" }, rotate: 90 }), - ).toEqual({ artifactId: "a", resize: { aspect: "vertical", fit: "crop" }, rotate: 90 }); + EditImageInputSchema.parse({ artifact_id: "a", resize: { aspect: "vertical" }, rotate: 90 }), + ).toEqual({ artifact_id: "a", resize: { aspect: "vertical", fit: "crop" }, rotate: 90 }); }); }); diff --git a/packages/contracts/src/media-generation.ts b/packages/contracts/src/media-generation.ts index cbec033..b834196 100644 --- a/packages/contracts/src/media-generation.ts +++ b/packages/contracts/src/media-generation.ts @@ -24,13 +24,13 @@ const Pixels = z.number().int().min(0).max(16_384); export const GenerateImageInputSchema = z .object({ prompt: Prompt.describe("What the image shows."), - negativePrompt: NegativePrompt, + negative_prompt: NegativePrompt, aspect: aspect("Default square, or the source image's aspect ratio.").optional(), width: Side.optional().describe("Exact width in pixels, with height; overrides aspect."), height: Side.optional(), count: z.number().int().min(1).max(MEDIA_IMAGE_MAX_COUNT).default(1), seed: Seed, - sourceArtifactId: Id.optional().describe("Image artifact to start from (image-to-image)."), + source_artifact_id: Id.optional().describe("Image artifact to start from (image-to-image)."), strength: Strength.describe("With a source: how far to move away from it (default 0.6)."), }) .refine((input) => (input.width === undefined) === (input.height === undefined), { @@ -41,11 +41,11 @@ export type GenerateImageInput = z.infer; export const GenerateVideoInputSchema = z.object({ prompt: Prompt.describe("What happens in the clip: subject, motion, camera."), - negativePrompt: NegativePrompt, + negative_prompt: NegativePrompt, aspect: aspect("Default landscape, or the start frame's aspect ratio.").optional(), - durationSeconds: z.number().min(1).max(MEDIA_VIDEO_MAX_SECONDS).default(5), + duration_seconds: z.number().min(1).max(MEDIA_VIDEO_MAX_SECONDS).default(5), seed: Seed, - sourceArtifactId: Id.optional().describe("Image artifact to use as the first frame."), + source_artifact_id: Id.optional().describe("Image artifact to use as the first frame."), }); export type GenerateVideoInput = z.infer; @@ -55,7 +55,7 @@ export type ImageFormat = z.infer; /** Edits apply in this order: crop, rotate, flip, resize, extend, repaint, format. */ export const EditImageInputSchema = z .object({ - artifactId: Id.describe("Image artifact to edit."), + artifact_id: Id.describe("Image artifact to edit."), title: MediaTitleSchema.optional().describe( "A few words in the user's language for the card and file name.", ), diff --git a/packages/contracts/src/media-transcription.ts b/packages/contracts/src/media-transcription.ts index 970c05a..4c51589 100644 --- a/packages/contracts/src/media-transcription.ts +++ b/packages/contracts/src/media-transcription.ts @@ -2,7 +2,7 @@ import * as z from "zod"; import { Id } from "./ids.js"; export const TranscribeInputSchema = z.object({ - artifactId: Id.describe("Audio or video artifact to transcribe."), + artifact_id: Id.describe("Audio or video artifact to transcribe."), language: z .string() .trim() diff --git a/packages/testkit/src/media.postgres.test.ts b/packages/testkit/src/media.postgres.test.ts index 5c48c98..ef06a03 100644 --- a/packages/testkit/src/media.postgres.test.ts +++ b/packages/testkit/src/media.postgres.test.ts @@ -5,7 +5,7 @@ import path from "node:path"; import type { MediaOperationRegistration } from "@rakazo/adapters"; import { ComposioEmulator } from "@rakazo/adapters"; import type { MediaJobBlock, MediaSettingsView, MessageBlock } from "@rakazo/contracts"; -import { MediaOutputSchema } from "@rakazo/contracts"; +import { TranscribeInputSchema } from "@rakazo/contracts"; import { createThreadMessage } from "@rakazo/db"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { createApp } from "../../../apps/api/src/app.ts"; @@ -26,21 +26,21 @@ const PNG_BASE64 = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ); // testkit has no zod of its own; the contracts' schemas carry it. -const SourceInputSchema = MediaOutputSchema.pick({ artifactId: true }); +const SourceInputSchema = TranscribeInputSchema.pick({ artifact_id: true }); /** Fake card operations: an export that copies its source, and a transcription that waits. */ function fakeOperations(): MediaOperationRegistration[] { - const exportVideo: MediaOperationRegistration<{ artifactId: string }> = { + const exportVideo: MediaOperationRegistration<{ artifact_id: string }> = { operation: "export_video", inputSchema: SourceInputSchema, title: () => "Export", available: async () => true, - artifactIds: (input) => [input.artifactId], + artifactIds: (input) => [input.artifact_id], handler: { operation: "export_video", run: async (context) => { - const { artifactId } = context.input as { artifactId: string }; - const source = await context.fetchArtifact(artifactId); + const { artifact_id } = context.input as { artifact_id: string }; + const source = await context.fetchArtifact(artifact_id); const out = path.join(context.tempDir, "export.png"); await writeFile(out, Buffer.from(PNG_BASE64, "base64")); await context.addOutput({ @@ -54,12 +54,12 @@ function fakeOperations(): MediaOperationRegistration[] { }, }, }; - const transcribe: MediaOperationRegistration<{ artifactId: string }> = { + const transcribe: MediaOperationRegistration<{ artifact_id: string }> = { operation: "transcribe", inputSchema: SourceInputSchema, title: () => "Transcript", available: async () => true, - artifactIds: (input) => [input.artifactId], + artifactIds: (input) => [input.artifact_id], handler: { operation: "transcribe", run: async (context) => { @@ -260,7 +260,7 @@ describeWithDatabase("media RPCs (PostgreSQL)", () => { botId: alice.bot.id, messageId: alice.card.id, operation: "export_video", - input: { artifactId: alice.artifact.id }, + input: { artifact_id: alice.artifact.id }, }, ); const job = await waitForStatus(started.jobId, "done"); @@ -305,19 +305,19 @@ describeWithDatabase("media RPCs (PostgreSQL)", () => { botId: alice.bot.id, messageId: alice.card.id, operation: "export_video", - input: { artifactId: mallory.artifact.id }, + input: { artifact_id: mallory.artifact.id }, }, { botId: mallory.bot.id, messageId: alice.card.id, operation: "export_video", - input: { artifactId: mallory.artifact.id }, + input: { artifact_id: mallory.artifact.id }, }, { botId: mallory.bot.id, messageId: mallory.card.id, operation: "export_video", - input: { artifactId: alice.artifact.id }, + input: { artifact_id: alice.artifact.id }, }, { botId: mallory.bot.id, @@ -335,7 +335,7 @@ describeWithDatabase("media RPCs (PostgreSQL)", () => { botId: alice.bot.id, messageId: alice.card.id, operation: "transcribe", - input: { artifactId: alice.artifact.id }, + input: { artifact_id: alice.artifact.id }, }); await waitForStatus(slow.jobId, "running"); const cancelled = await rpc<{ status: string }>(app, alice.cookie, "media/jobs/cancel", {