From ef5be7e17c50ee0a39d22da3083096ce567a8dc5 Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Mon, 27 Apr 2026 23:16:05 +0800 Subject: [PATCH] fix(cli): clarify asyncTaskId vs generationId in gen status/download + better error message (#14230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔖 chore(release): release version v2.1.53 [skip ci] * fix(cli): improve gen status/download error message for wrong asyncTaskId * docs(cli-skill): clarify asyncTaskId vs generationId in gen status/download * fix(builtin-skills): clarify asyncTaskId vs generationId in gen status/download * fix(cli): distinguish asyncTaskId not found vs generationId not found in error message * Update package.json --------- Co-authored-by: lobehubbot --- .agents/skills/cli/references/generate.md | 95 ++++++++++------ apps/cli/src/commands/generate/index.ts | 102 +++++++++++++++--- .../src/lobehub/references/generate.ts | 30 +++++- 3 files changed, 176 insertions(+), 51 deletions(-) diff --git a/.agents/skills/cli/references/generate.md b/.agents/skills/cli/references/generate.md index 8e15b7df54..d2ec708f31 100644 --- a/.agents/skills/cli/references/generate.md +++ b/.agents/skills/cli/references/generate.md @@ -8,16 +8,20 @@ Generate text, images, videos, speech, and transcriptions. ``` lh generate (alias: gen) -├── text # Text generation -├── image # Image generation -├── video # Video generation -├── tts # Text-to-speech -├── asr # Audio-to-text (speech recognition) -├── download # Wait & download generation result -├── status # Check async task status -└── list # List generation topics +├── text # Text generation +├── image # Image generation +├── video # Video generation +├── tts # Text-to-speech +├── asr # Audio-to-text (speech recognition) +├── download # Wait & download generation result +├── status # Check async task status +└── list # List generation topics ``` +> ⚠️ **Important**: `status` and `download` require an `asyncTaskId` (UUID format, e.g. +> `7ad0eb13-e9a5-4403-8070-1f7fe95b2f95`), **not** the generation ID (`gen_xxx`). +> The asyncTaskId is printed after "→ Task" in the `video` / `image` command output. + --- ## `lh generate text ` / `lh gen text ` @@ -54,7 +58,7 @@ cat README.md | lh gen text "summarize this" --pipe ## `lh generate image ` / `lh gen image ` -Generate images from text prompt. This is an async operation — the command submits the task and returns a generation ID + task ID for tracking. +Generate images from text prompt. This is an async operation — the command submits the task and returns a generation ID + async task ID for tracking. **Source**: `apps/cli/src/commands/generate/image.ts` @@ -80,17 +84,22 @@ lh gen image "A cute cat" --model dall-e-3 --provider openai --json ✓ Image generation started Batch ID: gb_xxx 1 image(s) queued - Generation gen_xxx → Task + Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + This is the asyncTaskId — use this for status/download -Use "lh generate status " to check progress. +Use "lh generate status " to check progress. ``` **Typical workflow**: ```bash -# Generate image, then wait & download +# 1. Submit generation — note down BOTH IDs from the output lh gen image "A cute cat" -lh gen download -o cat.png +# Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 + +# 2. Wait & download using generationId + asyncTaskId (the UUID) +lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o cat.png ``` --- @@ -102,7 +111,7 @@ Generate video from text prompt. This is an async operation. **Source**: `apps/cli/src/commands/generate/video.ts` ```bash -lh gen video "A cat playing piano" -m < model > -p < provider > [options] +lh gen video "A cat playing piano" -m -p [options] ``` | Option | Description | Required | @@ -122,9 +131,26 @@ lh gen video "A cat playing piano" -m < model > -p < provider > [options] ``` ✓ Video generation started Batch ID: gb_xxx - Generation gen_xxx → Task + Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + This is the asyncTaskId — use this for status/download -Use "lh generate status " to check progress. +Use "lh generate status " to check progress. +``` + +**Typical workflow**: + +```bash +# 1. Find available video models for a provider +lh model list volcengine --json | grep -i seedance + +# 2. Submit generation — note down BOTH IDs from the output +lh gen video "A cat on a runway" -m doubao-seedance-2-0-260128 -p volcengine \ + --aspect-ratio 9:16 --duration 5 --resolution 1080p +# Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 + +# 3. Wait & download using generationId + asyncTaskId (the UUID) +lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o result.mp4 --timeout 600 ``` --- @@ -153,15 +179,18 @@ lh gen asr recording.wav [options] --- -## `lh generate download ` +## `lh generate download ` Wait for an async generation task to complete and download the result file. **Source**: `apps/cli/src/commands/generate/index.ts` +> ⚠️ `` is the UUID printed after "→ Task" in the video/image output. +> Do **not** pass the generation ID (`gen_xxx`) here — that will cause a server error. + ```bash -lh gen download [-o output.png] -lh gen download gen_xxx task_xxx -o ~/Desktop/result.mp4 --timeout 600 +lh gen download [-o output.png] +lh gen download gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx -o ~/Desktop/result.mp4 --timeout 600 ``` | Option | Description | Default | @@ -175,30 +204,21 @@ lh gen download gen_xxx task_xxx -o ~/Desktop/result.mp4 --timeout 600 1. Polls `generation.getGenerationStatus` at the specified interval 2. Shows live progress: `⋯ Status: processing... (42s)` 3. On success: downloads asset URL to local file -4. On error: displays error message and exits +4. On error / wrong ID: displays a clear message pointing to the correct ID format 5. On timeout: suggests using `lh gen status` to check later -**Typical workflow**: - -```bash -# One-shot: generate and download -lh gen image "A sunset" -# Copy the generation ID and task ID from output -lh gen download gen_xxx taskId_xxx -o sunset.png - -# Video (longer timeout) -lh gen video "A cat running" -m model -p provider -lh gen download gen_xxx taskId_xxx -o cat.mp4 --timeout 600 -``` - --- -## `lh generate status ` +## `lh generate status ` Check the status of an async generation task. +> ⚠️ `` is the UUID printed after "→ Task" in the video/image output. +> Do **not** pass the generation ID (`gen_xxx`) here — that will cause a server error. + ```bash -lh gen status [--json] +lh gen status [--json] +lh gen status gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx ``` | Option | Description | @@ -235,12 +255,17 @@ Image and video generation use an async task pattern: - Triggers async background task (image via `createAsyncCaller`, video via `initModelRuntimeFromDB`) - Returns `{ data: { batch, generations }, success }` with `asyncTaskId` in each generation 3. **Poll status** → `generation.getGenerationStatus` + - Input: `{ generationId, asyncTaskId }` — both are required, and `asyncTaskId` must be the + UUID from the `async_tasks` table, not `gen_xxx` - Returns `{ status, error, generation }` (generation includes asset URLs on success) + - Before querying, calls `checkTimeoutTasks` which marks tasks as `error` if they have been + `pending` or `processing` for more than ~5 minutes (`ASYNC_TASK_TIMEOUT = 298s`) **Server routes**: - `src/server/routers/lambda/image/index.ts` — image creation (uses `authedProcedure` + `serverDatabase`) - `src/server/routers/lambda/video/index.ts` — video creation (uses `authedProcedure` + `serverDatabase`) - `src/server/routers/lambda/generation.ts` — status checking +- `packages/database/src/models/asyncTask.ts` — `AsyncTaskModel` including `checkTimeoutTasks` **Note**: Image/video routes do NOT use the `keyVaults` middleware — they read API keys from the database via `initModelRuntimeFromDB` or `createAsyncCaller`. diff --git a/apps/cli/src/commands/generate/index.ts b/apps/cli/src/commands/generate/index.ts index 449e0473a1..b97c39c054 100644 --- a/apps/cli/src/commands/generate/index.ts +++ b/apps/cli/src/commands/generate/index.ts @@ -9,6 +9,61 @@ import { registerTextCommand } from './text'; import { registerTtsCommand } from './tts'; import { registerVideoCommand } from './video'; +/** + * Parse a tRPC/server error and return a user-friendly message for gen status/download. + * + * getGenerationStatus throws NOT_FOUND in two distinct cases: + * 1. "Async task not found" → asyncTaskId is wrong (user passed gen_xxx instead of UUID) + * 2. "Generation not found" → generationId is wrong + * + * INTERNAL_SERVER_ERROR with a message mentioning "async_tasks" also indicates a bad asyncTaskId + * (e.g. the server SQL query fails when a non-UUID is passed). + */ +function parseGenStatusError( + err: any, + generationId: string, + asyncTaskId: string, + command: 'status' | 'download', +): string | null { + const code = err?.data?.code || err?.shape?.data?.code; + const message: string = err?.message || err?.shape?.message || ''; + + const isAsyncTaskNotFound = + (code === 'NOT_FOUND' && message.includes('Async task not found')) || + (code === 'INTERNAL_SERVER_ERROR' && message.includes('async_tasks')); + + const isGenerationNotFound = code === 'NOT_FOUND' && message.includes('Generation not found'); + + if (isAsyncTaskNotFound) { + return ( + `${pc.red('✗')} Async task not found: ${pc.bold(asyncTaskId)}\n` + + `\n` + + ` The second argument must be the ${pc.bold('asyncTaskId')} — the UUID printed after\n` + + ` "→ Task" in the video/image output, not the generation ID (gen_xxx).\n` + + `\n` + + ` Example output from "lh gen video":\n` + + ` Generation ${pc.bold('gen_abc123')} → Task ${pc.dim('7ad0eb13-e9a5-4403-8070-1f7fe95b2f95')}\n` + + `\n` + + ` Correct usage:\n` + + ` ${pc.cyan(`lh gen ${command} gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95`)}` + ); + } + + if (isGenerationNotFound) { + return ( + `${pc.red('✗')} Generation not found: ${pc.bold(generationId)}\n` + + `\n` + + ` The first argument must be the ${pc.bold('generationId')} (gen_xxx) from the\n` + + ` video/image output.\n` + + `\n` + + ` Correct usage:\n` + + ` ${pc.cyan(`lh gen ${command} `)}` + ); + } + + return null; +} + export function registerGenerateCommand(program: Command) { const generate = program .command('generate') @@ -23,15 +78,26 @@ export function registerGenerateCommand(program: Command) { // ── status ────────────────────────────────────────── generate - .command('status ') + .command('status ') .description('Check generation task status') .option('--json', 'Output raw JSON') - .action(async (generationId: string, taskId: string, options: { json?: boolean }) => { + .action(async (generationId: string, asyncTaskId: string, options: { json?: boolean }) => { const client = await getTrpcClient(); - const result = await client.generation.getGenerationStatus.query({ - asyncTaskId: taskId, - generationId, - }); + + let result: any; + try { + result = await client.generation.getGenerationStatus.query({ + asyncTaskId, + generationId, + }); + } catch (err: any) { + const msg = parseGenStatusError(err, generationId, asyncTaskId, 'status'); + if (msg) { + console.error(msg); + process.exit(1); + } + throw err; + } if (options.json) { console.log(JSON.stringify(result, null, 2)); @@ -53,7 +119,7 @@ export function registerGenerateCommand(program: Command) { // ── download ────────────────────────────────────────── generate - .command('download ') + .command('download ') .description('Wait for generation to complete and download the result') .option('-o, --output ', 'Output file path (default: auto-detect from asset)') .option('--interval ', 'Polling interval in seconds', '5') @@ -61,7 +127,7 @@ export function registerGenerateCommand(program: Command) { .action( async ( generationId: string, - taskId: string, + asyncTaskId: string, options: { interval?: string; output?: string; timeout?: string }, ) => { const client = await getTrpcClient(); @@ -73,10 +139,20 @@ export function registerGenerateCommand(program: Command) { // Poll for completion while (true) { - const result = (await client.generation.getGenerationStatus.query({ - asyncTaskId: taskId, - generationId, - })) as any; + let result: any; + try { + result = await client.generation.getGenerationStatus.query({ + asyncTaskId, + generationId, + }); + } catch (err: any) { + const msg = parseGenStatusError(err, generationId, asyncTaskId, 'download'); + if (msg) { + console.error(`\n${msg}`); + process.exit(1); + } + throw err; + } if (result.status === 'success' && result.generation) { const gen = result.generation; @@ -125,7 +201,7 @@ export function registerGenerateCommand(program: Command) { console.log( `${pc.red('✗')} Timed out after ${options.timeout}s. Task still ${result.status}.`, ); - console.log(pc.dim(`Run "lh gen status ${generationId} ${taskId}" to check later.`)); + console.log(pc.dim(`Run "lh gen status ${generationId} ${asyncTaskId}" to check later.`)); process.exit(1); } diff --git a/packages/builtin-skills/src/lobehub/references/generate.ts b/packages/builtin-skills/src/lobehub/references/generate.ts index 85c4da514e..37c9100e34 100644 --- a/packages/builtin-skills/src/lobehub/references/generate.ts +++ b/packages/builtin-skills/src/lobehub/references/generate.ts @@ -6,11 +6,11 @@ Generate text, images, videos, and audio. Alias: \`lh generate\`. - \`lh gen text [-m ] [-p ] [--stream] [--temperature ]\` - Generate text - \`lh gen image [-m ] [-n ] [--width ] [--height ]\` - Generate image -- \`lh gen video [-m ] [--aspect-ratio ] [--duration ]\` - Generate video +- \`lh gen video -m -p [--aspect-ratio ] [--duration ] [--resolution ]\` - Generate video - \`lh gen tts [-o ] [--voice ] [--speed ]\` - Text-to-speech - \`lh gen asr [--model ] [--language ]\` - Speech-to-text -- \`lh gen status \` - Check generation task status -- \`lh gen download [-o ]\` - Wait and download result +- \`lh gen status \` - Check generation task status +- \`lh gen download [-o ]\` - Wait and download result - \`lh gen list\` - List generation topics ## Tips @@ -18,6 +18,30 @@ Generate text, images, videos, and audio. Alias: \`lh generate\`. - Image/video generation is async; use \`status\` or \`download\` to get results - \`--stream\` for text generation outputs tokens as they arrive - \`--pipe\` for text generation outputs only the raw text (no formatting) + +## ⚠️ asyncTaskId vs generationId + +\`gen status\` and \`gen download\` require TWO different IDs: + +- \`\` — prefixed with \`gen_\`, e.g. \`gen_abc123\` +- \`\` — a UUID printed after \`→ Task\` in the \`gen image\` / \`gen video\` output, + e.g. \`7ad0eb13-e9a5-4403-8070-1f7fe95b2f95\` + +Passing \`gen_xxx\` as \`\` will cause a server error. Always use the UUID. + +Example output from \`lh gen video\`: +\`\`\` +✓ Video generation started + Batch ID: gb_xxx + Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ← this is asyncTaskId +\`\`\` + +Correct usage: +\`\`\`bash +lh gen status gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 +lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o result.mp4 +\`\`\` `; export default content;