-
Notifications
You must be signed in to change notification settings - Fork 2
Polish up some robots commands #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
9c91134
feat: add --file config support and --wait polling to robots commands…
dylanjha 121bdb1
refactor: conditionally display job creation messages only in non-JSO…
dylanjha 442a12a
feat: fix validation order to check file mutex before duration parame…
dylanjha 06bfb77
feat: add job completion validation for robots commands with error ha…
dylanjha File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { describe, expect, test } from 'bun:test'; | ||
| import type { AnyRobotsJob } from './_shared.ts'; | ||
| import { assertJobCompleted } from './_shared.ts'; | ||
|
|
||
| const baseJob = { | ||
| id: 'rjob_xyz', | ||
| workflow: 'summarize', | ||
| created_at: 0, | ||
| updated_at: 0, | ||
| units_consumed: 0, | ||
| parameters: { asset_id: 'asset_x' }, | ||
| } as unknown as AnyRobotsJob; | ||
|
|
||
| describe('assertJobCompleted', () => { | ||
| test('returns silently on status=completed', () => { | ||
| expect(() => | ||
| assertJobCompleted({ ...baseJob, status: 'completed' } as AnyRobotsJob), | ||
| ).not.toThrow(); | ||
| }); | ||
|
|
||
| test('throws on status=errored', () => { | ||
| expect(() => | ||
| assertJobCompleted({ ...baseJob, status: 'errored' } as AnyRobotsJob), | ||
| ).toThrow(/errored/i); | ||
| }); | ||
|
|
||
| test('throws on status=cancelled', () => { | ||
| expect(() => | ||
| assertJobCompleted({ ...baseJob, status: 'cancelled' } as AnyRobotsJob), | ||
| ).toThrow(/cancelled/i); | ||
| }); | ||
|
|
||
| test('includes job.errors details when present', () => { | ||
| const job = { | ||
| ...baseJob, | ||
| status: 'errored', | ||
| errors: [ | ||
| { type: 'processing_error', message: 'asset not ready' }, | ||
| { type: 'timeout', message: 'took too long' }, | ||
| ], | ||
| } as unknown as AnyRobotsJob; | ||
| expect(() => assertJobCompleted(job)).toThrow( | ||
| /processing_error: asset not ready.*timeout: took too long/, | ||
| ); | ||
| }); | ||
|
|
||
| test('includes job id in the error message', () => { | ||
| expect(() => | ||
| assertJobCompleted({ | ||
| ...baseJob, | ||
| id: 'rjob_abc123', | ||
| status: 'errored', | ||
| } as AnyRobotsJob), | ||
| ).toThrow(/rjob_abc123/); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import { readFile } from 'node:fs/promises'; | ||
| import type Mux from '@mux/mux-node'; | ||
| import type { | ||
| AskQuestionsJob, | ||
| FindKeyMomentsJob, | ||
| GenerateChaptersJob, | ||
| ModerateJob, | ||
| SummarizeJob, | ||
| TranslateCaptionsJob, | ||
| } from '@mux/mux-node/resources/robots-preview/jobs'; | ||
|
|
||
| export type AnyRobotsJob = | ||
| | AskQuestionsJob | ||
| | FindKeyMomentsJob | ||
| | GenerateChaptersJob | ||
| | ModerateJob | ||
| | SummarizeJob | ||
| | TranslateCaptionsJob; | ||
|
|
||
| export type RobotsWorkflow = AnyRobotsJob['workflow']; | ||
|
|
||
| export const FILE_MUTEX_MSG = | ||
| '--file cannot be combined with other parameter flags. Use one or the other.'; | ||
|
|
||
| const TERMINAL_STATUSES = new Set(['completed', 'cancelled', 'errored']); | ||
|
|
||
| export function isTerminalStatus(status: string): boolean { | ||
| return TERMINAL_STATUSES.has(status); | ||
| } | ||
|
|
||
| export async function loadJobParameters<T extends { asset_id?: string }>( | ||
| filePath: string, | ||
| assetIdFromPositional: string, | ||
| ): Promise<T> { | ||
| let content: string; | ||
| try { | ||
| content = await readFile(filePath, 'utf-8'); | ||
| } catch (err) { | ||
| if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | ||
| throw new Error(`Config file not found: ${filePath}`); | ||
| } | ||
| throw err; | ||
| } | ||
|
|
||
| let parsed: T; | ||
| try { | ||
| parsed = JSON.parse(content) as T; | ||
| } catch (err) { | ||
| throw new Error( | ||
| `Invalid JSON in config file ${filePath}: ${(err as Error).message}`, | ||
| ); | ||
| } | ||
|
|
||
| if (parsed.asset_id && parsed.asset_id !== assetIdFromPositional) { | ||
| throw new Error( | ||
| `asset_id in config file (${parsed.asset_id}) does not match positional argument (${assetIdFromPositional}).`, | ||
| ); | ||
| } | ||
| parsed.asset_id = assetIdFromPositional; | ||
| return parsed; | ||
| } | ||
|
|
||
| export function retrieveRobotsJob( | ||
| mux: Mux, | ||
| workflow: RobotsWorkflow, | ||
| jobId: string, | ||
| ): Promise<AnyRobotsJob> { | ||
| switch (workflow) { | ||
| case 'ask-questions': | ||
| return mux.robotsPreview.jobs.askQuestions.retrieve(jobId); | ||
| case 'find-key-moments': | ||
| return mux.robotsPreview.jobs.findKeyMoments.retrieve(jobId); | ||
| case 'generate-chapters': | ||
| return mux.robotsPreview.jobs.generateChapters.retrieve(jobId); | ||
| case 'moderate': | ||
| return mux.robotsPreview.jobs.moderate.retrieve(jobId); | ||
| case 'summarize': | ||
| return mux.robotsPreview.jobs.summarize.retrieve(jobId); | ||
| case 'translate-captions': | ||
| return mux.robotsPreview.jobs.translateCaptions.retrieve(jobId); | ||
| } | ||
| } | ||
|
|
||
| export function assertJobCompleted(job: AnyRobotsJob): void { | ||
| if (job.status === 'completed') return; | ||
| const details = job.errors?.length | ||
| ? `: ${job.errors.map((e) => `${e.type}: ${e.message}`).join('; ')}` | ||
| : ''; | ||
| throw new Error(`Job ${job.id} ended with status "${job.status}"${details}`); | ||
| } | ||
|
|
||
| export async function pollForRobotsJob( | ||
| mux: Mux, | ||
| workflow: RobotsWorkflow, | ||
| jobId: string, | ||
| jsonOutput: boolean, | ||
| ): Promise<AnyRobotsJob> { | ||
| const POLL_INTERVAL_MS = 3000; | ||
| const MAX_POLL_TIME_MS = 15 * 60 * 1000; | ||
| const start = Date.now(); | ||
|
|
||
| if (!jsonOutput) { | ||
| process.stderr.write('Waiting for job to complete'); | ||
| } | ||
|
|
||
| while (Date.now() - start < MAX_POLL_TIME_MS) { | ||
| const job = await retrieveRobotsJob(mux, workflow, jobId); | ||
| if (isTerminalStatus(job.status)) { | ||
| if (!jsonOutput) { | ||
| process.stderr.write(` ${job.status}!\n`); | ||
| } | ||
| return job; | ||
| } | ||
| if (!jsonOutput) { | ||
| process.stderr.write('.'); | ||
| } | ||
| await sleep(POLL_INTERVAL_MS); | ||
| } | ||
|
|
||
| throw new Error(`Timed out waiting for job ${jobId} to complete`); | ||
| } | ||
|
|
||
| function sleep(ms: number): Promise<void> { | ||
| return new Promise((resolve) => setTimeout(resolve, ms)); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.