agnes-ai-cli 0.1.0

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/config.js ADDED
@@ -0,0 +1,24 @@
1
+ import { AgnesCliError } from "./errors.js";
2
+ export const DEFAULT_BASE_URL = "https://apihub.agnes-ai.com/v1";
3
+ export const DEFAULT_MEDIA_TTL = "1h";
4
+ export const DEFAULT_VIDEO_DIMENSIONS = {
5
+ width: 1152,
6
+ height: 768,
7
+ };
8
+ export const DEFAULT_VIDEO_TEMPORAL = {
9
+ numFrames: 121,
10
+ frameRate: 24,
11
+ };
12
+ export function resolveConfig(config = {}) {
13
+ if (!config.fetchImpl && typeof fetch !== "function") {
14
+ throw new AgnesCliError("FETCH_UNAVAILABLE", "Global fetch is unavailable in this Node runtime.");
15
+ }
16
+ return {
17
+ apiKey: config.apiKey ?? config.env?.AGNES_API_KEY ?? process.env.AGNES_API_KEY,
18
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
19
+ fetchImpl: config.fetchImpl ?? fetch,
20
+ defaultMediaTtl: config.defaultMediaTtl ?? DEFAULT_MEDIA_TTL,
21
+ mediaProvider: config.mediaProvider,
22
+ env: config.env ?? process.env,
23
+ };
24
+ }
@@ -0,0 +1,7 @@
1
+ export declare class AgnesCliError extends Error {
2
+ code: string;
3
+ details?: unknown;
4
+ exitCode: number;
5
+ constructor(code: string, message: string, details?: unknown, exitCode?: number);
6
+ }
7
+ export declare function isAgnesCliError(error: unknown): error is AgnesCliError;
package/dist/errors.js ADDED
@@ -0,0 +1,15 @@
1
+ export class AgnesCliError extends Error {
2
+ code;
3
+ details;
4
+ exitCode;
5
+ constructor(code, message, details, exitCode = 1) {
6
+ super(message);
7
+ this.name = "AgnesCliError";
8
+ this.code = code;
9
+ this.details = details;
10
+ this.exitCode = exitCode;
11
+ }
12
+ }
13
+ export function isAgnesCliError(error) {
14
+ return error instanceof AgnesCliError;
15
+ }
@@ -0,0 +1,8 @@
1
+ export declare function requestJson(fetchImpl: typeof fetch, url: string, init: RequestInit, options?: {
2
+ retries?: number;
3
+ retryDelayMs?: number;
4
+ networkMessage?: string;
5
+ }): Promise<{
6
+ response: Response;
7
+ raw: unknown;
8
+ }>;
@@ -0,0 +1,26 @@
1
+ import { AgnesCliError } from "../errors.js";
2
+ export async function requestJson(fetchImpl, url, init, options = {}) {
3
+ const retries = options.retries ?? 2;
4
+ const retryDelayMs = options.retryDelayMs ?? 500;
5
+ let lastError;
6
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
7
+ try {
8
+ const response = await fetchImpl(url, init);
9
+ const raw = await response.json();
10
+ return { response, raw };
11
+ }
12
+ catch (error) {
13
+ lastError = error;
14
+ if (attempt === retries) {
15
+ throw new AgnesCliError("NETWORK_ERROR", options.networkMessage ?? "Network request failed.", { cause: error instanceof Error ? error.message : String(error) });
16
+ }
17
+ await sleep(retryDelayMs);
18
+ }
19
+ }
20
+ throw new AgnesCliError("NETWORK_ERROR", options.networkMessage ?? "Network request failed.", {
21
+ cause: lastError instanceof Error ? lastError.message : String(lastError),
22
+ });
23
+ }
24
+ function sleep(ms) {
25
+ return new Promise((resolve) => setTimeout(resolve, ms));
26
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgnesClientConfig, ImageGenerationResult } from "../config.js";
2
+ import { type ImageGenerateOptions } from "./normalizeImageRequest.js";
3
+ export declare function generateImage(options: ImageGenerateOptions, config?: AgnesClientConfig): Promise<ImageGenerationResult>;
@@ -0,0 +1,62 @@
1
+ import { resolveConfig } from "../config.js";
2
+ import { AgnesCliError } from "../errors.js";
3
+ import { requestJson } from "../http/requestJson.js";
4
+ import { toPublicUrl } from "../media/toPublicUrl.js";
5
+ import { collectImageInputs, imageGenerateSchema, normalizeImageRequest } from "./normalizeImageRequest.js";
6
+ export async function generateImage(options, config = {}) {
7
+ const parsed = imageGenerateSchema.parse(options);
8
+ const resolved = resolveConfig(config);
9
+ if (!resolved.apiKey) {
10
+ throw new AgnesCliError("AUTH_MISSING", "AGNES_API_KEY is required for Agnes requests.");
11
+ }
12
+ const { inputs, ttl } = collectImageInputs(parsed);
13
+ const resolvedImages = await resolveMediaInputs(inputs, ttl, config);
14
+ const payload = normalizeImageRequest(parsed, resolvedImages);
15
+ const { response, raw } = await requestJson(resolved.fetchImpl, `${resolved.baseUrl}/images/generations`, {
16
+ method: "POST",
17
+ headers: {
18
+ Authorization: `Bearer ${resolved.apiKey}`,
19
+ "Content-Type": "application/json",
20
+ },
21
+ body: JSON.stringify(payload),
22
+ }, {
23
+ networkMessage: "Agnes image request failed before a response was received.",
24
+ });
25
+ if (!response.ok) {
26
+ throw new AgnesCliError("AGNES_REQUEST_FAILED", `Agnes image request failed with HTTP ${response.status}.`, raw);
27
+ }
28
+ const url = extractImageUrl(raw);
29
+ if (!url) {
30
+ throw new AgnesCliError("IMAGE_URL_MISSING", "Agnes image response did not include a URL.", raw);
31
+ }
32
+ return {
33
+ ok: true,
34
+ model: String(payload.model),
35
+ url,
36
+ raw,
37
+ };
38
+ }
39
+ async function resolveMediaInputs(inputs, ttl, config) {
40
+ const cache = new Map();
41
+ const resolved = [];
42
+ for (const input of inputs) {
43
+ if (!cache.has(input)) {
44
+ const value = await toPublicUrl(input, ttl ? { ttl: ttl } : {}, config);
45
+ cache.set(input, value.url);
46
+ }
47
+ resolved.push(cache.get(input));
48
+ }
49
+ return resolved;
50
+ }
51
+ function extractImageUrl(raw) {
52
+ if (!raw || typeof raw !== "object")
53
+ return undefined;
54
+ const record = raw;
55
+ const data = Array.isArray(record.data) ? record.data[0] : undefined;
56
+ if (data && typeof data === "object" && typeof data.url === "string") {
57
+ return data.url;
58
+ }
59
+ if (typeof record.url === "string")
60
+ return record.url;
61
+ return undefined;
62
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ import type { Ttl } from "../config.js";
3
+ export declare const imageGenerateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ mode: z.ZodLiteral<"text2img">;
5
+ model: z.ZodOptional<z.ZodEnum<{
6
+ "agnes-image-2.1-flash": "agnes-image-2.1-flash";
7
+ "agnes-image-2.0-flash": "agnes-image-2.0-flash";
8
+ }>>;
9
+ prompt: z.ZodString;
10
+ size: z.ZodOptional<z.ZodString>;
11
+ responseFormat: z.ZodOptional<z.ZodLiteral<"url">>;
12
+ seed: z.ZodOptional<z.ZodNumber>;
13
+ }, z.core.$strip>, z.ZodObject<{
14
+ mode: z.ZodLiteral<"img2img">;
15
+ model: z.ZodOptional<z.ZodEnum<{
16
+ "agnes-image-2.1-flash": "agnes-image-2.1-flash";
17
+ "agnes-image-2.0-flash": "agnes-image-2.0-flash";
18
+ }>>;
19
+ image: z.ZodString;
20
+ prompt: z.ZodString;
21
+ size: z.ZodOptional<z.ZodString>;
22
+ responseFormat: z.ZodOptional<z.ZodLiteral<"url">>;
23
+ seed: z.ZodOptional<z.ZodNumber>;
24
+ ttl: z.ZodOptional<z.ZodEnum<{
25
+ "1h": "1h";
26
+ "12h": "12h";
27
+ "24h": "24h";
28
+ "72h": "72h";
29
+ }>>;
30
+ }, z.core.$strip>, z.ZodObject<{
31
+ mode: z.ZodLiteral<"compose">;
32
+ model: z.ZodOptional<z.ZodEnum<{
33
+ "agnes-image-2.1-flash": "agnes-image-2.1-flash";
34
+ "agnes-image-2.0-flash": "agnes-image-2.0-flash";
35
+ }>>;
36
+ images: z.ZodArray<z.ZodString>;
37
+ prompt: z.ZodString;
38
+ size: z.ZodOptional<z.ZodString>;
39
+ responseFormat: z.ZodOptional<z.ZodLiteral<"url">>;
40
+ seed: z.ZodOptional<z.ZodNumber>;
41
+ ttl: z.ZodOptional<z.ZodEnum<{
42
+ "1h": "1h";
43
+ "12h": "12h";
44
+ "24h": "24h";
45
+ "72h": "72h";
46
+ }>>;
47
+ }, z.core.$strip>], "mode">;
48
+ export type ImageGenerateOptions = z.infer<typeof imageGenerateSchema>;
49
+ export declare function normalizeImageRequest(options: ImageGenerateOptions, resolvedImages: string[]): Record<string, unknown>;
50
+ export declare function collectImageInputs(options: ImageGenerateOptions): {
51
+ inputs: string[];
52
+ ttl?: Ttl;
53
+ };
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ import { AgnesCliError } from "../errors.js";
3
+ export const imageGenerateSchema = z.discriminatedUnion("mode", [
4
+ z.object({
5
+ mode: z.literal("text2img"),
6
+ model: z.enum(["agnes-image-2.1-flash", "agnes-image-2.0-flash"]).optional(),
7
+ prompt: z.string().min(1),
8
+ size: z.string().optional(),
9
+ responseFormat: z.literal("url").optional(),
10
+ seed: z.number().int().optional(),
11
+ }),
12
+ z.object({
13
+ mode: z.literal("img2img"),
14
+ model: z.enum(["agnes-image-2.1-flash", "agnes-image-2.0-flash"]).optional(),
15
+ image: z.string().min(1),
16
+ prompt: z.string().min(1),
17
+ size: z.string().optional(),
18
+ responseFormat: z.literal("url").optional(),
19
+ seed: z.number().int().optional(),
20
+ ttl: z.enum(["1h", "12h", "24h", "72h"]).optional(),
21
+ }),
22
+ z.object({
23
+ mode: z.literal("compose"),
24
+ model: z.enum(["agnes-image-2.1-flash", "agnes-image-2.0-flash"]).optional(),
25
+ images: z.array(z.string().min(1)).min(2),
26
+ prompt: z.string().min(1),
27
+ size: z.string().optional(),
28
+ responseFormat: z.literal("url").optional(),
29
+ seed: z.number().int().optional(),
30
+ ttl: z.enum(["1h", "12h", "24h", "72h"]).optional(),
31
+ }),
32
+ ]);
33
+ export function normalizeImageRequest(options, resolvedImages) {
34
+ const model = options.model ?? "agnes-image-2.1-flash";
35
+ const payload = {
36
+ model,
37
+ prompt: options.prompt,
38
+ };
39
+ if (options.size)
40
+ payload.size = options.size;
41
+ if (options.seed !== undefined)
42
+ payload.seed = options.seed;
43
+ if (options.mode === "text2img") {
44
+ return payload;
45
+ }
46
+ const imageValue = options.mode === "img2img" ? resolvedImages[0] : resolvedImages;
47
+ payload.extra_body = {
48
+ image: imageValue,
49
+ ...(options.responseFormat ? { response_format: options.responseFormat } : {}),
50
+ };
51
+ if (model === "agnes-image-2.0-flash") {
52
+ payload.tags = ["img2img"];
53
+ }
54
+ return payload;
55
+ }
56
+ export function collectImageInputs(options) {
57
+ switch (options.mode) {
58
+ case "text2img":
59
+ return { inputs: [] };
60
+ case "img2img":
61
+ return { inputs: [options.image], ttl: options.ttl };
62
+ case "compose":
63
+ return { inputs: options.images, ttl: options.ttl };
64
+ default:
65
+ throw new AgnesCliError("INVALID_IMAGE_MODE", "Unsupported image generation mode.");
66
+ }
67
+ }
@@ -0,0 +1,33 @@
1
+ import type { AgnesClientConfig } from "./config.js";
2
+ export { checkAuth } from "./auth/check.js";
3
+ export { toPublicUrl } from "./media/toPublicUrl.js";
4
+ export { LitterboxMediaUrlProvider } from "./media/litterbox.js";
5
+ export type { AgnesClientConfig, AuthCheckResult, PublicUrlResult, ImageGenerationResult, NormalizedVideoTask, NormalizedVideoResult, Ttl, } from "./config.js";
6
+ export type { ImageGenerateOptions } from "./image/normalizeImageRequest.js";
7
+ export type { TextCompleteOptions, TextCompletionResult } from "./text/complete.js";
8
+ export type { VideoGenerateOptions } from "./video/normalizeVideoRequest.js";
9
+ export type { PollVideoOptions } from "./video/pollVideo.js";
10
+ export declare function createAgnesClient(config?: AgnesClientConfig): {
11
+ auth: {
12
+ check: () => {
13
+ readonly ok: boolean;
14
+ readonly configured: boolean;
15
+ readonly source: "env" | "missing";
16
+ };
17
+ };
18
+ media: {
19
+ toPublicUrl: (input: string, options?: {
20
+ ttl?: import("./config.js").Ttl;
21
+ }) => Promise<import("./config.js").PublicUrlResult>;
22
+ };
23
+ text: {
24
+ complete: (options: import("./text/complete.js").TextCompleteOptions) => Promise<import("./text/complete.js").TextCompletionResult>;
25
+ };
26
+ image: {
27
+ generate: (options: import("./image/normalizeImageRequest.js").ImageGenerateOptions) => Promise<import("./config.js").ImageGenerationResult>;
28
+ };
29
+ video: {
30
+ 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
+ };
33
+ };
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ import { checkAuth } from "./auth/check.js";
2
+ import { toPublicUrl } from "./media/toPublicUrl.js";
3
+ import { generateImage } from "./image/generateImage.js";
4
+ import { completeText } from "./text/complete.js";
5
+ import { generateVideo } from "./video/generateVideo.js";
6
+ import { pollVideo } from "./video/pollVideo.js";
7
+ export { checkAuth } from "./auth/check.js";
8
+ export { toPublicUrl } from "./media/toPublicUrl.js";
9
+ export { LitterboxMediaUrlProvider } from "./media/litterbox.js";
10
+ export function createAgnesClient(config = {}) {
11
+ return {
12
+ auth: {
13
+ check: () => checkAuth({ env: config.env }),
14
+ },
15
+ media: {
16
+ toPublicUrl: (input, options) => toPublicUrl(input, options, config),
17
+ },
18
+ text: {
19
+ complete: (options) => completeText(options, config),
20
+ },
21
+ image: {
22
+ generate: (options) => generateImage(options, config),
23
+ },
24
+ video: {
25
+ generate: (options) => generateVideo(options, config),
26
+ poll: (taskId, options) => pollVideo(taskId, options, config),
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { FetchLike, Ttl } from "../config.js";
2
+ export declare class LitterboxMediaUrlProvider {
3
+ private readonly fetchImpl;
4
+ constructor(fetchImpl?: FetchLike);
5
+ upload(localPath: string, options?: {
6
+ ttl?: Ttl;
7
+ }): Promise<string>;
8
+ private postFormWithRetry;
9
+ }
@@ -0,0 +1,69 @@
1
+ import { basename, extname } from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import { AgnesCliError } from "../errors.js";
4
+ const LITTERBOX_ENDPOINT = "https://litterbox.catbox.moe/resources/internals/api.php";
5
+ const MIME_TYPES = {
6
+ ".jpg": "image/jpeg",
7
+ ".jpeg": "image/jpeg",
8
+ ".png": "image/png",
9
+ ".webp": "image/webp",
10
+ ".gif": "image/gif",
11
+ ".mp4": "video/mp4",
12
+ ".mov": "video/quicktime",
13
+ ".webm": "video/webm",
14
+ };
15
+ export class LitterboxMediaUrlProvider {
16
+ fetchImpl;
17
+ constructor(fetchImpl = fetch) {
18
+ this.fetchImpl = fetchImpl;
19
+ }
20
+ async upload(localPath, options = {}) {
21
+ const ttl = options.ttl ?? "1h";
22
+ const file = await fs.readFile(localPath);
23
+ const form = new FormData();
24
+ const mimeType = MIME_TYPES[extname(localPath).toLowerCase()] ?? "application/octet-stream";
25
+ const blob = new Blob([file], { type: mimeType });
26
+ form.set("reqtype", "fileupload");
27
+ form.set("time", ttl);
28
+ form.set("fileToUpload", blob, basename(localPath));
29
+ const { response, text } = await this.postFormWithRetry(form);
30
+ if (!response.ok) {
31
+ throw new AgnesCliError("UPLOAD_FAILED", `Litterbox upload failed with HTTP ${response.status}.`, {
32
+ status: response.status,
33
+ body: text,
34
+ });
35
+ }
36
+ if (!/^https?:\/\//.test(text)) {
37
+ throw new AgnesCliError("UPLOAD_FAILED", `Litterbox did not return a public URL: ${text}`);
38
+ }
39
+ return text;
40
+ }
41
+ async postFormWithRetry(form) {
42
+ let lastError;
43
+ for (let attempt = 0; attempt <= 2; attempt += 1) {
44
+ try {
45
+ const response = await this.fetchImpl(LITTERBOX_ENDPOINT, {
46
+ method: "POST",
47
+ body: form,
48
+ });
49
+ const text = (await response.text()).trim();
50
+ return { response, text };
51
+ }
52
+ catch (error) {
53
+ lastError = error;
54
+ if (attempt === 2) {
55
+ throw new AgnesCliError("UPLOAD_FAILED", "Litterbox upload failed before a response was received.", {
56
+ cause: error instanceof Error ? error.message : String(error),
57
+ });
58
+ }
59
+ await sleep(500);
60
+ }
61
+ }
62
+ throw new AgnesCliError("UPLOAD_FAILED", "Litterbox upload failed before a response was received.", {
63
+ cause: lastError instanceof Error ? lastError.message : String(lastError),
64
+ });
65
+ }
66
+ }
67
+ function sleep(ms) {
68
+ return new Promise((resolve) => setTimeout(resolve, ms));
69
+ }
@@ -0,0 +1,4 @@
1
+ import type { AgnesClientConfig, PublicUrlResult, Ttl } from "../config.js";
2
+ export declare function toPublicUrl(input: string, options?: {
3
+ ttl?: Ttl;
4
+ }, config?: AgnesClientConfig): Promise<PublicUrlResult>;
@@ -0,0 +1,21 @@
1
+ import { access } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { LitterboxMediaUrlProvider } from "./litterbox.js";
4
+ import { resolveConfig } from "../config.js";
5
+ import { AgnesCliError } from "../errors.js";
6
+ export async function toPublicUrl(input, options = {}, config = {}) {
7
+ if (/^https?:\/\//.test(input)) {
8
+ return { ok: true, url: input, source: "passthrough" };
9
+ }
10
+ try {
11
+ await access(input, constants.R_OK);
12
+ }
13
+ catch {
14
+ throw new AgnesCliError("INPUT_NOT_FOUND", `Input must be an existing file path or an http(s) URL: ${input}`);
15
+ }
16
+ const resolved = resolveConfig(config);
17
+ const provider = resolved.mediaProvider ??
18
+ new LitterboxMediaUrlProvider(resolved.fetchImpl);
19
+ const url = await provider.upload(input, { ttl: options.ttl ?? resolved.defaultMediaTtl });
20
+ return { ok: true, url, source: resolved.mediaProvider ? "provider" : "litterbox" };
21
+ }
@@ -0,0 +1,2 @@
1
+ export declare function printJson(value: unknown): void;
2
+ export declare function printLines(lines: Array<string | undefined>): void;
package/dist/output.js ADDED
@@ -0,0 +1,9 @@
1
+ export function printJson(value) {
2
+ console.log(JSON.stringify(value, null, 2));
3
+ }
4
+ export function printLines(lines) {
5
+ for (const line of lines) {
6
+ if (line)
7
+ console.log(line);
8
+ }
9
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { AgnesClientConfig } from "../config.js";
3
+ declare const textCompleteSchema: z.ZodObject<{
4
+ model: z.ZodDefault<z.ZodString>;
5
+ prompt: z.ZodString;
6
+ stream: z.ZodOptional<z.ZodBoolean>;
7
+ }, z.core.$strip>;
8
+ export type TextCompleteOptions = z.infer<typeof textCompleteSchema>;
9
+ export interface TextCompletionResult {
10
+ ok: true;
11
+ model: string;
12
+ text: string;
13
+ raw: unknown;
14
+ }
15
+ export declare function completeText(options: TextCompleteOptions, config?: AgnesClientConfig): Promise<TextCompletionResult>;
16
+ export {};
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import { resolveConfig } from "../config.js";
3
+ import { AgnesCliError } from "../errors.js";
4
+ import { requestJson } from "../http/requestJson.js";
5
+ const textCompleteSchema = z.object({
6
+ model: z.string().default("agnes-2.0-flash"),
7
+ prompt: z.string().min(1),
8
+ stream: z.boolean().optional(),
9
+ });
10
+ export async function completeText(options, config = {}) {
11
+ const parsed = textCompleteSchema.parse(options);
12
+ const resolved = resolveConfig(config);
13
+ if (!resolved.apiKey) {
14
+ throw new AgnesCliError("AUTH_MISSING", "AGNES_API_KEY is required for Agnes requests.");
15
+ }
16
+ const payload = {
17
+ model: parsed.model,
18
+ messages: [{ role: "user", content: parsed.prompt }],
19
+ ...(parsed.stream ? { stream: true } : {}),
20
+ };
21
+ const { response, raw } = await requestJson(resolved.fetchImpl, `${resolved.baseUrl}/chat/completions`, {
22
+ method: "POST",
23
+ headers: {
24
+ Authorization: `Bearer ${resolved.apiKey}`,
25
+ "Content-Type": "application/json",
26
+ },
27
+ body: JSON.stringify(payload),
28
+ }, {
29
+ networkMessage: "Agnes text request failed before a response was received.",
30
+ });
31
+ if (!response.ok) {
32
+ throw new AgnesCliError("AGNES_REQUEST_FAILED", `Agnes text request failed with HTTP ${response.status}.`, raw);
33
+ }
34
+ const text = extractText(raw);
35
+ if (!text) {
36
+ throw new AgnesCliError("TEXT_MISSING", "Agnes text response did not include assistant content.", raw);
37
+ }
38
+ return {
39
+ ok: true,
40
+ model: parsed.model,
41
+ text,
42
+ raw,
43
+ };
44
+ }
45
+ function extractText(raw) {
46
+ if (!raw || typeof raw !== "object")
47
+ return undefined;
48
+ const record = raw;
49
+ const choices = Array.isArray(record.choices) ? record.choices : [];
50
+ const first = choices[0];
51
+ if (!first || typeof first !== "object")
52
+ return undefined;
53
+ const message = first.message;
54
+ if (!message || typeof message !== "object")
55
+ return undefined;
56
+ const content = message.content;
57
+ return typeof content === "string" ? content : undefined;
58
+ }
@@ -0,0 +1,6 @@
1
+ import type { AgnesClientConfig, NormalizedVideoTask } from "../config.js";
2
+ import { type VideoGenerateOptions } from "./normalizeVideoRequest.js";
3
+ export declare function generateVideo(options: VideoGenerateOptions, config?: AgnesClientConfig): Promise<NormalizedVideoTask>;
4
+ export declare function normalizeVideoTask(raw: unknown): NormalizedVideoTask;
5
+ export declare function extractTaskId(record: Record<string, unknown>): string;
6
+ export declare function normalizeStatus(rawStatus: string): "queued" | "in_progress" | "completed" | "failed";
@@ -0,0 +1,77 @@
1
+ import { resolveConfig } from "../config.js";
2
+ import { AgnesCliError } from "../errors.js";
3
+ import { requestJson } from "../http/requestJson.js";
4
+ import { toPublicUrl } from "../media/toPublicUrl.js";
5
+ import { collectVideoInputs, normalizeVideoRequest, videoGenerateSchema } from "./normalizeVideoRequest.js";
6
+ export async function generateVideo(options, config = {}) {
7
+ const parsed = videoGenerateSchema.parse(options);
8
+ const resolved = resolveConfig(config);
9
+ if (!resolved.apiKey) {
10
+ throw new AgnesCliError("AUTH_MISSING", "AGNES_API_KEY is required for Agnes requests.");
11
+ }
12
+ const { inputs, ttl } = collectVideoInputs(parsed);
13
+ const resolvedImages = await resolveMediaInputs(inputs, ttl, config);
14
+ const payload = normalizeVideoRequest(parsed, resolvedImages);
15
+ const { response, raw } = await requestJson(resolved.fetchImpl, `${resolved.baseUrl}/videos`, {
16
+ method: "POST",
17
+ headers: {
18
+ Authorization: `Bearer ${resolved.apiKey}`,
19
+ "Content-Type": "application/json",
20
+ },
21
+ body: JSON.stringify(payload),
22
+ }, {
23
+ networkMessage: "Agnes video request failed before a response was received.",
24
+ });
25
+ if (!response.ok) {
26
+ throw new AgnesCliError("AGNES_REQUEST_FAILED", `Agnes video request failed with HTTP ${response.status}.`, raw);
27
+ }
28
+ return normalizeVideoTask(raw);
29
+ }
30
+ async function resolveMediaInputs(inputs, ttl, config) {
31
+ const cache = new Map();
32
+ const resolved = [];
33
+ for (const input of inputs) {
34
+ if (!cache.has(input)) {
35
+ const value = await toPublicUrl(input, ttl ? { ttl: ttl } : {}, config);
36
+ cache.set(input, value.url);
37
+ }
38
+ resolved.push(cache.get(input));
39
+ }
40
+ return resolved;
41
+ }
42
+ export function normalizeVideoTask(raw) {
43
+ const record = ensureRecord(raw);
44
+ const taskId = extractTaskId(record);
45
+ const rawStatus = typeof record.status === "string" ? record.status : "queued";
46
+ return {
47
+ ok: true,
48
+ taskId,
49
+ status: normalizeStatus(rawStatus),
50
+ rawStatus,
51
+ model: typeof record.model === "string" ? record.model : "agnes-video-v2.0",
52
+ raw,
53
+ };
54
+ }
55
+ function ensureRecord(raw) {
56
+ if (!raw || typeof raw !== "object") {
57
+ throw new AgnesCliError("INVALID_RESPONSE", "Agnes video response was not an object.", raw);
58
+ }
59
+ return raw;
60
+ }
61
+ export function extractTaskId(record) {
62
+ const taskId = typeof record.task_id === "string"
63
+ ? record.task_id
64
+ : typeof record.id === "string"
65
+ ? record.id
66
+ : undefined;
67
+ if (!taskId) {
68
+ throw new AgnesCliError("TASK_ID_MISSING", "Agnes video response did not include task_id or id.", record);
69
+ }
70
+ return taskId;
71
+ }
72
+ export function normalizeStatus(rawStatus) {
73
+ if (rawStatus === "queued" || rawStatus === "in_progress" || rawStatus === "completed" || rawStatus === "failed") {
74
+ return rawStatus;
75
+ }
76
+ return "queued";
77
+ }