agnes-ai-cli 0.1.1 → 0.1.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/README.md CHANGED
@@ -29,9 +29,12 @@ agnes video text2video --prompt "a cinematic drone shot of waves at dusk"
29
29
  agnes video img2video --image ./frame.png --prompt "animate subtle rain and drifting fog"
30
30
  agnes video multivideo --image ./frame-a.png --image ./frame-b.png --prompt "blend these references into one motion concept"
31
31
  agnes video keyframes --image ./frame-a.png --image ./frame-b.png --prompt "morph between the two scenes"
32
- agnes video poll <task-id>
32
+ agnes video poll <video-or-task-id>
33
33
  ```
34
34
 
35
+ Local media inputs are uploaded to a temporary public URL automatically. The default bridge tries x0.at first, then falls back to tmpfiles, Uguu, and Litterbox.
36
+ Video creation is asynchronous. Create commands print both `taskId` and `videoId` when Agnes returns them; use `videoId` with `agnes video poll`. Passing an older `taskId` still works through the legacy polling endpoint.
37
+
35
38
  ## JS API
36
39
 
37
40
  ```js
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ Use this group when Agnes requires a public image URL and your source asset is a
72
72
  .command("url")
73
73
  .description("Return a public URL for a local file or pass through an existing URL")
74
74
  .argument("<file-or-url>", "Local file path or http(s) URL")
75
- .option("--ttl <ttl>", "Litterbox TTL (1h, 12h, 24h, 72h)", "1h")
75
+ .option("--ttl <ttl>", "Temporary upload TTL (1h, 12h, 24h, 72h)", "1h")
76
76
  .option("--json", "Output JSON")
77
77
  .action(async (input, options) => {
78
78
  const result = await client.media.toPublicUrl(input, { ttl: ttlSchema.parse(options.ttl) });
@@ -224,9 +224,9 @@ Examples:
224
224
  agnes video text2video --prompt "A cinematic beach scene at sunset"
225
225
  agnes video img2video --image ./frame.png --prompt "Add gentle wind and a soft push-in"
226
226
  agnes video keyframes --image ./a.png --image ./b.png --prompt "Transition between the frames"
227
- agnes video poll task_123
227
+ agnes video poll video_123
228
228
 
229
- Use this group for Agnes video task creation and polling. Video creation is asynchronous; poll returns the final result.
229
+ Use this group for Agnes video task creation and polling. Video creation is asynchronous; poll uses the recommended video_id endpoint and keeps task_id inputs compatible with the legacy endpoint.
230
230
  `);
231
231
  buildVideoGenerateCommand(video, "text2video", "Generate a video from text", async (client, options) => client.video.generate({
232
232
  mode: "text2video",
@@ -277,19 +277,19 @@ Use this group for Agnes video task creation and polling. Video creation is asyn
277
277
  video
278
278
  .command("poll")
279
279
  .description("Poll an Agnes video task until it finishes")
280
- .argument("<task-id>", "Task id returned by Agnes video creation")
281
- .option("--interval <seconds>", "Polling interval seconds", parseInteger, 3)
280
+ .argument("<video-or-task-id>", "Video id returned by Agnes video creation; task ids are accepted for legacy compatibility")
281
+ .option("--interval <seconds>", "Polling interval seconds", parseInteger, 5)
282
282
  .option("--timeout <seconds>", "Polling timeout seconds", parseInteger, 600)
283
283
  .option("--json", "Output JSON")
284
284
  .addHelpText("after", `
285
285
  Example:
286
- agnes video poll task_123 --interval 3 --timeout 600
286
+ agnes video poll video_123 --interval 5 --timeout 600
287
287
 
288
288
  Request shape:
289
- Polls Agnes /videos/{task_id} until the task completes, fails, or times out. Use this after any asynchronous video creation command.
289
+ Polls Agnes /agnesapi?video_id={video_id} until the task completes, fails, or times out. If you pass a task_ id, poll uses the legacy /videos/{task_id} endpoint for compatibility. Use this after any asynchronous video creation command.
290
290
  `)
291
- .action(async (taskId, options) => {
292
- const result = await client.video.poll(taskId, {
291
+ .action(async (id, options) => {
292
+ const result = await client.video.poll(id, {
293
293
  intervalSeconds: options.interval,
294
294
  timeoutSeconds: options.timeout,
295
295
  });
@@ -344,6 +344,7 @@ function buildVideoGenerateCommand(video, name, description, fn, imageMode = fal
344
344
  const task = result;
345
345
  printLines([
346
346
  `taskId: ${task.taskId}`,
347
+ task.videoId ? `videoId: ${task.videoId}` : undefined,
347
348
  `status: ${task.status}`,
348
349
  ]);
349
350
  });
package/dist/config.d.ts CHANGED
@@ -40,12 +40,13 @@ export interface AuthCheckResult {
40
40
  export interface PublicUrlResult {
41
41
  ok: true;
42
42
  url: string;
43
- source: "passthrough" | "litterbox" | "provider";
43
+ source: "passthrough" | "litterbox" | "temporary" | "provider";
44
44
  }
45
45
  export type AgnesStatus = "queued" | "in_progress" | "completed" | "failed" | "timed_out";
46
46
  export interface NormalizedVideoTask {
47
47
  ok: true;
48
48
  taskId: string;
49
+ videoId?: string;
49
50
  status: AgnesStatus;
50
51
  rawStatus?: string;
51
52
  model: string;
@@ -54,6 +55,7 @@ export interface NormalizedVideoTask {
54
55
  export interface NormalizedVideoResult {
55
56
  ok: true;
56
57
  taskId: string;
58
+ videoId?: string;
57
59
  status: AgnesStatus;
58
60
  rawStatus?: string;
59
61
  model: string;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { AgnesClientConfig } from "./config.js";
2
2
  export { checkAuth } from "./auth/check.js";
3
3
  export { toPublicUrl } from "./media/toPublicUrl.js";
4
4
  export { LitterboxMediaUrlProvider } from "./media/litterbox.js";
5
+ export { TemporaryMediaUrlProvider, TmpfilesMediaUrlProvider, UguuMediaUrlProvider, X0MediaUrlProvider } from "./media/temporary-upload.js";
5
6
  export type { AgnesClientConfig, AuthCheckResult, PublicUrlResult, ImageGenerationResult, NormalizedVideoTask, NormalizedVideoResult, Ttl, } from "./config.js";
6
7
  export type { ImageGenerateOptions } from "./image/normalizeImageRequest.js";
7
8
  export type { TextCompleteOptions, TextCompletionResult } from "./text/complete.js";
@@ -28,6 +29,6 @@ export declare function createAgnesClient(config?: AgnesClientConfig): {
28
29
  };
29
30
  video: {
30
31
  generate: (options: import("./video/normalizeVideoRequest.js").VideoGenerateOptions) => Promise<import("./config.js").NormalizedVideoTask>;
31
- poll: (taskId: string, options?: import("./video/pollVideo.js").PollVideoOptions) => Promise<import("./config.js").NormalizedVideoResult>;
32
+ poll: (id: string, options?: import("./video/pollVideo.js").PollVideoOptions) => Promise<import("./config.js").NormalizedVideoResult>;
32
33
  };
33
34
  };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { pollVideo } from "./video/pollVideo.js";
7
7
  export { checkAuth } from "./auth/check.js";
8
8
  export { toPublicUrl } from "./media/toPublicUrl.js";
9
9
  export { LitterboxMediaUrlProvider } from "./media/litterbox.js";
10
+ export { TemporaryMediaUrlProvider, TmpfilesMediaUrlProvider, UguuMediaUrlProvider, X0MediaUrlProvider } from "./media/temporary-upload.js";
10
11
  export function createAgnesClient(config = {}) {
11
12
  return {
12
13
  auth: {
@@ -23,7 +24,7 @@ export function createAgnesClient(config = {}) {
23
24
  },
24
25
  video: {
25
26
  generate: (options) => generateVideo(options, config),
26
- poll: (taskId, options) => pollVideo(taskId, options, config),
27
+ poll: (id, options) => pollVideo(id, options, config),
27
28
  },
28
29
  };
29
30
  }
@@ -1,9 +1,15 @@
1
1
  import type { FetchLike, Ttl } from "../config.js";
2
+ type LitterboxMediaUrlProviderOptions = {
3
+ maxAttempts?: number;
4
+ retryDelayMs?: number;
5
+ };
2
6
  export declare class LitterboxMediaUrlProvider {
3
7
  private readonly fetchImpl;
4
- constructor(fetchImpl?: FetchLike);
8
+ private readonly options;
9
+ constructor(fetchImpl?: FetchLike, options?: LitterboxMediaUrlProviderOptions);
5
10
  upload(localPath: string, options?: {
6
11
  ttl?: Ttl;
7
12
  }): Promise<string>;
8
13
  private postFormWithRetry;
9
14
  }
15
+ export {};
@@ -14,8 +14,10 @@ const MIME_TYPES = {
14
14
  };
15
15
  export class LitterboxMediaUrlProvider {
16
16
  fetchImpl;
17
- constructor(fetchImpl = fetch) {
17
+ options;
18
+ constructor(fetchImpl = fetch, options = {}) {
18
19
  this.fetchImpl = fetchImpl;
20
+ this.options = options;
19
21
  }
20
22
  async upload(localPath, options = {}) {
21
23
  const ttl = options.ttl ?? "1h";
@@ -40,7 +42,9 @@ export class LitterboxMediaUrlProvider {
40
42
  }
41
43
  async postFormWithRetry(form) {
42
44
  let lastError;
43
- for (let attempt = 0; attempt <= 2; attempt += 1) {
45
+ const maxAttempts = this.options.maxAttempts ?? 3;
46
+ const retryDelayMs = this.options.retryDelayMs ?? 500;
47
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
44
48
  try {
45
49
  const response = await this.fetchImpl(LITTERBOX_ENDPOINT, {
46
50
  method: "POST",
@@ -51,12 +55,12 @@ export class LitterboxMediaUrlProvider {
51
55
  }
52
56
  catch (error) {
53
57
  lastError = error;
54
- if (attempt === 2) {
58
+ if (attempt === maxAttempts) {
55
59
  throw new AgnesCliError("UPLOAD_FAILED", "Litterbox upload failed before a response was received.", {
56
60
  cause: error instanceof Error ? error.message : String(error),
57
61
  });
58
62
  }
59
- await sleep(500);
63
+ await sleep(retryDelayMs);
60
64
  }
61
65
  }
62
66
  throw new AgnesCliError("UPLOAD_FAILED", "Litterbox upload failed before a response was received.", {
@@ -0,0 +1,25 @@
1
+ import type { FetchLike, MediaUrlProvider, Ttl } from "../config.js";
2
+ export declare class TemporaryMediaUrlProvider implements MediaUrlProvider {
3
+ private readonly providers;
4
+ constructor(fetchImpl?: FetchLike);
5
+ upload(localPath: string, options?: {
6
+ ttl?: Ttl;
7
+ }): Promise<string>;
8
+ }
9
+ export declare class X0MediaUrlProvider implements MediaUrlProvider {
10
+ private readonly fetchImpl;
11
+ constructor(fetchImpl?: FetchLike);
12
+ upload(localPath: string): Promise<string>;
13
+ }
14
+ export declare class UguuMediaUrlProvider implements MediaUrlProvider {
15
+ private readonly fetchImpl;
16
+ constructor(fetchImpl?: FetchLike);
17
+ upload(localPath: string): Promise<string>;
18
+ }
19
+ export declare class TmpfilesMediaUrlProvider implements MediaUrlProvider {
20
+ private readonly fetchImpl;
21
+ constructor(fetchImpl?: FetchLike);
22
+ upload(localPath: string, options?: {
23
+ ttl?: Ttl;
24
+ }): Promise<string>;
25
+ }
@@ -0,0 +1,176 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { basename, extname } from "node:path";
3
+ import { AgnesCliError } from "../errors.js";
4
+ import { LitterboxMediaUrlProvider } from "./litterbox.js";
5
+ const X0_ENDPOINT = "https://x0.at/";
6
+ const UGUU_ENDPOINT = "https://uguu.se/upload";
7
+ const TMPFILES_ENDPOINT = "https://tmpfiles.org/api/v1/upload";
8
+ const MIME_TYPES = {
9
+ ".jpg": "image/jpeg",
10
+ ".jpeg": "image/jpeg",
11
+ ".png": "image/png",
12
+ ".webp": "image/webp",
13
+ ".gif": "image/gif",
14
+ ".mp4": "video/mp4",
15
+ ".mov": "video/quicktime",
16
+ ".webm": "video/webm",
17
+ };
18
+ const TTL_SECONDS = {
19
+ "1h": "3600",
20
+ "12h": "43200",
21
+ "24h": "86400",
22
+ "72h": "259200",
23
+ };
24
+ export class TemporaryMediaUrlProvider {
25
+ providers;
26
+ constructor(fetchImpl = fetch) {
27
+ this.providers = [
28
+ { name: "x0.at", provider: new X0MediaUrlProvider(fetchImpl) },
29
+ { name: "tmpfiles", provider: new TmpfilesMediaUrlProvider(fetchImpl) },
30
+ { name: "Uguu", provider: new UguuMediaUrlProvider(fetchImpl) },
31
+ { name: "Litterbox", provider: new LitterboxMediaUrlProvider(fetchImpl, { maxAttempts: 1 }) },
32
+ ];
33
+ }
34
+ async upload(localPath, options = {}) {
35
+ const failures = [];
36
+ for (const { name, provider } of this.providers) {
37
+ try {
38
+ return await provider.upload(localPath, options);
39
+ }
40
+ catch (error) {
41
+ failures.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
42
+ }
43
+ }
44
+ throw new AgnesCliError("UPLOAD_FAILED", "Temporary upload failed after trying x0.at, tmpfiles, Uguu, and Litterbox.", {
45
+ failures,
46
+ });
47
+ }
48
+ }
49
+ export class X0MediaUrlProvider {
50
+ fetchImpl;
51
+ constructor(fetchImpl = fetch) {
52
+ this.fetchImpl = fetchImpl;
53
+ }
54
+ async upload(localPath) {
55
+ const form = await createFileForm(localPath, "file");
56
+ const { response, text } = await postForm(this.fetchImpl, X0_ENDPOINT, form);
57
+ if (!response.ok) {
58
+ throw new AgnesCliError("UPLOAD_FAILED", `x0.at upload failed with HTTP ${response.status}.`, {
59
+ status: response.status,
60
+ body: text,
61
+ });
62
+ }
63
+ const url = extractPlainTextUrl(text);
64
+ if (!url) {
65
+ throw new AgnesCliError("UPLOAD_FAILED", `x0.at did not return a public URL: ${text}`);
66
+ }
67
+ return url;
68
+ }
69
+ }
70
+ export class UguuMediaUrlProvider {
71
+ fetchImpl;
72
+ constructor(fetchImpl = fetch) {
73
+ this.fetchImpl = fetchImpl;
74
+ }
75
+ async upload(localPath) {
76
+ const form = await createFileForm(localPath, "files[]");
77
+ const { response, text } = await postForm(this.fetchImpl, UGUU_ENDPOINT, form);
78
+ if (!response.ok) {
79
+ throw new AgnesCliError("UPLOAD_FAILED", `Uguu upload failed with HTTP ${response.status}.`, {
80
+ status: response.status,
81
+ body: text,
82
+ });
83
+ }
84
+ const raw = parseUploadJson(text, "Uguu");
85
+ const url = extractUguuUrl(raw);
86
+ if (!url) {
87
+ throw new AgnesCliError("UPLOAD_FAILED", `Uguu did not return a public URL: ${text}`);
88
+ }
89
+ return url;
90
+ }
91
+ }
92
+ export class TmpfilesMediaUrlProvider {
93
+ fetchImpl;
94
+ constructor(fetchImpl = fetch) {
95
+ this.fetchImpl = fetchImpl;
96
+ }
97
+ async upload(localPath, options = {}) {
98
+ const form = await createFileForm(localPath, "file");
99
+ form.set("expire", TTL_SECONDS[options.ttl ?? "1h"]);
100
+ const { response, text } = await postForm(this.fetchImpl, TMPFILES_ENDPOINT, form);
101
+ if (!response.ok) {
102
+ throw new AgnesCliError("UPLOAD_FAILED", `tmpfiles upload failed with HTTP ${response.status}.`, {
103
+ status: response.status,
104
+ body: text,
105
+ });
106
+ }
107
+ const raw = parseUploadJson(text, "tmpfiles");
108
+ const url = extractTmpfilesUrl(raw);
109
+ if (!url) {
110
+ throw new AgnesCliError("UPLOAD_FAILED", `tmpfiles did not return a public URL: ${text}`);
111
+ }
112
+ return toTmpfilesDirectUrl(url);
113
+ }
114
+ }
115
+ async function createFileForm(localPath, fieldName) {
116
+ const file = await fs.readFile(localPath);
117
+ const form = new FormData();
118
+ const mimeType = MIME_TYPES[extname(localPath).toLowerCase()] ?? "application/octet-stream";
119
+ form.set(fieldName, new Blob([file], { type: mimeType }), basename(localPath));
120
+ return form;
121
+ }
122
+ async function postForm(fetchImpl, endpoint, form) {
123
+ try {
124
+ const response = await fetchImpl(endpoint, {
125
+ method: "POST",
126
+ body: form,
127
+ });
128
+ return { response, text: (await response.text()).trim() };
129
+ }
130
+ catch (error) {
131
+ throw new AgnesCliError("UPLOAD_FAILED", `${new URL(endpoint).hostname} upload failed before a response was received.`, {
132
+ cause: error instanceof Error ? error.message : String(error),
133
+ });
134
+ }
135
+ }
136
+ function parseUploadJson(text, providerName) {
137
+ try {
138
+ return JSON.parse(text);
139
+ }
140
+ catch {
141
+ throw new AgnesCliError("UPLOAD_FAILED", `${providerName} did not return JSON: ${text}`);
142
+ }
143
+ }
144
+ function extractPlainTextUrl(text) {
145
+ const url = text.match(/https?:\/\/[^\s"'<>]+/)?.[0];
146
+ return url && /^https?:\/\//.test(url) ? url : undefined;
147
+ }
148
+ function extractUguuUrl(raw) {
149
+ if (!raw || typeof raw !== "object")
150
+ return undefined;
151
+ const files = raw.files;
152
+ if (!Array.isArray(files))
153
+ return undefined;
154
+ const first = files[0];
155
+ if (!first || typeof first !== "object")
156
+ return undefined;
157
+ const url = first.url;
158
+ return typeof url === "string" && /^https?:\/\//.test(url) ? url : undefined;
159
+ }
160
+ function extractTmpfilesUrl(raw) {
161
+ if (!raw || typeof raw !== "object")
162
+ return undefined;
163
+ const data = raw.data;
164
+ if (!data || typeof data !== "object")
165
+ return undefined;
166
+ const url = data.url;
167
+ return typeof url === "string" && /^https?:\/\//.test(url) ? url : undefined;
168
+ }
169
+ function toTmpfilesDirectUrl(url) {
170
+ const parsed = new URL(url);
171
+ if (parsed.hostname !== "tmpfiles.org" || parsed.pathname.startsWith("/dl/")) {
172
+ return url;
173
+ }
174
+ parsed.pathname = `/dl${parsed.pathname}`;
175
+ return parsed.toString();
176
+ }
@@ -2,7 +2,7 @@ import { access, mkdtemp, rm, writeFile } from "node:fs/promises";
2
2
  import { constants } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { extname, join } from "node:path";
5
- import { LitterboxMediaUrlProvider } from "./litterbox.js";
5
+ import { TemporaryMediaUrlProvider } from "./temporary-upload.js";
6
6
  import { resolveConfig } from "../config.js";
7
7
  import { AgnesCliError } from "../errors.js";
8
8
  const MIME_EXTENSIONS = {
@@ -20,11 +20,11 @@ export async function toPublicUrl(input, options = {}, config = {}) {
20
20
  }
21
21
  const resolved = resolveConfig(config);
22
22
  const provider = resolved.mediaProvider ??
23
- new LitterboxMediaUrlProvider(resolved.fetchImpl);
23
+ new TemporaryMediaUrlProvider(resolved.fetchImpl);
24
24
  const materialized = await materializeUploadInput(input, resolved.fetchImpl);
25
25
  try {
26
26
  const url = await provider.upload(materialized.path, { ttl: options.ttl ?? resolved.defaultMediaTtl });
27
- return { ok: true, url, source: resolved.mediaProvider ? "provider" : "litterbox" };
27
+ return { ok: true, url, source: resolved.mediaProvider ? "provider" : "temporary" };
28
28
  }
29
29
  finally {
30
30
  if (materialized.cleanupDir) {
@@ -3,4 +3,5 @@ import { type VideoGenerateOptions } from "./normalizeVideoRequest.js";
3
3
  export declare function generateVideo(options: VideoGenerateOptions, config?: AgnesClientConfig): Promise<NormalizedVideoTask>;
4
4
  export declare function normalizeVideoTask(raw: unknown): NormalizedVideoTask;
5
5
  export declare function extractTaskId(record: Record<string, unknown>): string;
6
+ export declare function extractVideoId(record: Record<string, unknown>): string | undefined;
6
7
  export declare function normalizeStatus(rawStatus: string): "queued" | "in_progress" | "completed" | "failed";
@@ -42,10 +42,12 @@ async function resolveMediaInputs(inputs, ttl, config) {
42
42
  export function normalizeVideoTask(raw) {
43
43
  const record = ensureRecord(raw);
44
44
  const taskId = extractTaskId(record);
45
+ const videoId = extractVideoId(record);
45
46
  const rawStatus = typeof record.status === "string" ? record.status : "queued";
46
47
  return {
47
48
  ok: true,
48
49
  taskId,
50
+ videoId,
49
51
  status: normalizeStatus(rawStatus),
50
52
  rawStatus,
51
53
  model: typeof record.model === "string" ? record.model : "agnes-video-v2.0",
@@ -69,6 +71,13 @@ export function extractTaskId(record) {
69
71
  }
70
72
  return taskId;
71
73
  }
74
+ export function extractVideoId(record) {
75
+ if (typeof record.video_id === "string")
76
+ return record.video_id;
77
+ if (typeof record.id === "string" && record.id.startsWith("video_"))
78
+ return record.id;
79
+ return undefined;
80
+ }
72
81
  export function normalizeStatus(rawStatus) {
73
82
  if (rawStatus === "queued" || rawStatus === "in_progress" || rawStatus === "completed" || rawStatus === "failed") {
74
83
  return rawStatus;
@@ -3,5 +3,5 @@ export interface PollVideoOptions {
3
3
  intervalSeconds?: number;
4
4
  timeoutSeconds?: number;
5
5
  }
6
- export declare function pollVideo(taskId: string, options?: PollVideoOptions, config?: AgnesClientConfig): Promise<NormalizedVideoResult>;
6
+ export declare function pollVideo(id: string, options?: PollVideoOptions, config?: AgnesClientConfig): Promise<NormalizedVideoResult>;
7
7
  export declare function normalizeVideoResult(raw: unknown): NormalizedVideoResult;
@@ -1,20 +1,20 @@
1
1
  import { resolveConfig } from "../config.js";
2
2
  import { AgnesCliError } from "../errors.js";
3
3
  import { requestJson } from "../http/requestJson.js";
4
- import { extractTaskId, normalizeStatus } from "./generateVideo.js";
5
- export async function pollVideo(taskId, options = {}, config = {}) {
4
+ import { extractTaskId, extractVideoId, normalizeStatus } from "./generateVideo.js";
5
+ export async function pollVideo(id, options = {}, config = {}) {
6
6
  const resolved = resolveConfig(config);
7
7
  if (!resolved.apiKey) {
8
8
  throw new AgnesCliError("AUTH_MISSING", "AGNES_API_KEY is required for Agnes requests.");
9
9
  }
10
- const intervalMs = (options.intervalSeconds ?? 3) * 1000;
10
+ const intervalMs = (options.intervalSeconds ?? 5) * 1000;
11
11
  const timeoutMs = (options.timeoutSeconds ?? 600) * 1000;
12
12
  const started = Date.now();
13
13
  while (true) {
14
14
  if (Date.now() - started > timeoutMs) {
15
- throw new AgnesCliError("POLL_TIMEOUT", "Agnes video polling timed out.", { taskId, status: "timed_out" });
15
+ throw new AgnesCliError("POLL_TIMEOUT", "Agnes video polling timed out.", { id, status: "timed_out" });
16
16
  }
17
- const { response, raw } = await requestJson(resolved.fetchImpl, `${resolved.baseUrl}/videos/${taskId}`, {
17
+ const { response, raw } = await requestJson(resolved.fetchImpl, buildVideoPollUrl(resolved.baseUrl, id), {
18
18
  headers: {
19
19
  Authorization: `Bearer ${resolved.apiKey}`,
20
20
  },
@@ -22,13 +22,13 @@ export async function pollVideo(taskId, options = {}, config = {}) {
22
22
  networkMessage: "Agnes video poll request failed before a response was received.",
23
23
  });
24
24
  if (response.status === 404) {
25
- throw new AgnesCliError("TASK_NOT_FOUND", "Agnes video task was not found.", { taskId, status: "failed", raw });
25
+ throw new AgnesCliError("TASK_NOT_FOUND", "Agnes video task was not found.", { id, status: "failed", raw });
26
26
  }
27
27
  if (response.status === 503) {
28
- throw new AgnesCliError("SERVICE_BUSY", "Agnes video service is busy.", { taskId, status: "queued", raw });
28
+ throw new AgnesCliError("SERVICE_BUSY", "Agnes video service is busy.", { id, status: "queued", raw });
29
29
  }
30
30
  if (!response.ok) {
31
- throw new AgnesCliError("AGNES_REQUEST_FAILED", `Agnes video poll failed with HTTP ${response.status}.`, { taskId, status: "failed", raw });
31
+ throw new AgnesCliError("AGNES_REQUEST_FAILED", `Agnes video poll failed with HTTP ${response.status}.`, { id, status: "failed", raw });
32
32
  }
33
33
  const result = normalizeVideoResult(raw);
34
34
  if (result.status === "completed")
@@ -45,6 +45,7 @@ export function normalizeVideoResult(raw) {
45
45
  }
46
46
  const record = raw;
47
47
  const taskId = extractTaskId(record);
48
+ const videoId = extractVideoId(record);
48
49
  const rawStatus = typeof record.status === "string" ? record.status : "queued";
49
50
  const status = normalizeStatus(rawStatus);
50
51
  const videoUrl = extractVideoUrl(record);
@@ -54,6 +55,7 @@ export function normalizeVideoResult(raw) {
54
55
  return {
55
56
  ok: true,
56
57
  taskId,
58
+ videoId,
57
59
  status: status,
58
60
  rawStatus,
59
61
  model: typeof record.model === "string" ? record.model : "agnes-video-v2.0",
@@ -63,6 +65,14 @@ export function normalizeVideoResult(raw) {
63
65
  raw,
64
66
  };
65
67
  }
68
+ function buildVideoPollUrl(baseUrl, id) {
69
+ if (id.startsWith("task_")) {
70
+ return `${baseUrl.replace(/\/$/, "")}/videos/${encodeURIComponent(id)}`;
71
+ }
72
+ const url = new URL("/agnesapi", new URL(baseUrl));
73
+ url.searchParams.set("video_id", id);
74
+ return url.toString();
75
+ }
66
76
  function extractVideoUrl(record) {
67
77
  const candidates = [
68
78
  record.video_url,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agnes-ai-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI and JS API for Agnes text, image, and video workflows.",
5
5
  "type": "module",
6
6
  "bin": {