@ai-agent-forge/plugin-image-generation 0.85.18 → 0.85.19

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/agent-forge.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.image-generation",
4
- "pluginVersion": "0.85.17",
4
+ "pluginVersion": "0.85.18",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.85.14",
7
7
  "capabilityRefs": [
@@ -1 +1 @@
1
- {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AA2S1F;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,OAAO,CAAC,EAAE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,GAAG,2BAA2B,CAyJ9B","sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { undiciFetchV1, wrapFetchWithEnvProxyV1 } from \"./env-proxy-fetch.ts\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tdetectImageFormatV1,\n\tIMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageFormatV1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/** UTC `yyyymmdd-HHmmss` stamp shared by every file of one invocation. */\nfunction utcStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(\n\t\tdate.getUTCHours(),\n\t)}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;\n}\n\ninterface ImageContentPartV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\ninterface ImageToolSuccessV1 {\n\treadonly content: readonly ({ readonly type: \"text\"; readonly text: string } | ImageContentPartV1)[];\n\treadonly details: Record<string, never>;\n}\n\ninterface ProducedImageV1 {\n\treadonly bytes: Uint8Array;\n\treadonly format: ImageFormatV1;\n}\n\nfunction successResult(\n\tresult: ImageGenResultV1,\n\timages: readonly ProducedImageV1[],\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tfiles: result.files,\n\t\t\t\t\t\tmodel: result.model,\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t1,\n\t\t\t\t),\n\t\t\t},\n\t\t\t...images.map(\n\t\t\t\t(image): ImageContentPartV1 => ({\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tdata: Buffer.from(image.bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: image.format.mimeType,\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tdetails: {},\n\t};\n}\n\n/** Write one image; the exclusive-create retry makes concurrent invocations (parallel tool groups) safe. */\nasync function writeImage(\n\tworkspace: string,\n\tstamp: string,\n\tstartSequence: number,\n\tproduced: ProducedImageV1,\n): Promise<{ readonly relativePath: string; readonly sequence: number }> {\n\tconst directory = join(workspace, GENERATED_IMAGES_DIRECTORY);\n\tawait mkdir(directory, { recursive: true });\n\t// flag \"wx\" fails atomically when the name is taken (same-second stamp or\n\t// a concurrent tool group) instead of racing existsSync-then-write, which\n\t// silently overwrote one image with another under parallel execution.\n\tlet sequence = startSequence;\n\tfor (;;) {\n\t\tconst name = `img-${stamp}-${sequence}.${produced.format.extension}`;\n\t\ttry {\n\t\t\tawait writeFile(join(directory, name), produced.bytes, { flag: \"wx\" });\n\t\t\treturn { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n\t\t\t\tsequence += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/** Persist every produced image file for a settled response. */\nasync function persistImages(\n\tworkspace: string,\n\tproduced: readonly ProducedImageV1[],\n): Promise<{ readonly files: string[]; readonly images: ProducedImageV1[] }> {\n\tconst stamp = utcStampV1(new Date());\n\tconst files: string[] = [];\n\tconst images: ProducedImageV1[] = [];\n\tlet sequence = 1;\n\tfor (const image of produced) {\n\t\tconst written = await writeImage(workspace, stamp, sequence, image);\n\t\tfiles.push(written.relativePath);\n\t\timages.push(image);\n\t\tsequence = written.sequence + 1;\n\t}\n\treturn { files, images };\n}\n\n/** Inline b64 items pass through; url items are downloaded with the same fetch injection. */\nasync function collectImages(\n\tresponse: ImagesClientResponseV1,\n\tfetchImpl: typeof fetch,\n\tsignal: AbortSignal | undefined,\n): Promise<ProducedImageV1[] | ImageFailureV1> {\n\tconst images: ProducedImageV1[] = [];\n\tfor (const item of response.data) {\n\t\tif (item.b64 !== undefined) {\n\t\t\tconst bytes = Buffer.from(item.b64, \"base64\");\n\t\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t\t\tcontinue;\n\t\t}\n\t\tif (item.url === undefined) continue;\n\t\t// The download carries its own 120s budget: a slow proxy route must\n\t\t// not hang the tool on undici's default 300s body timeout.\n\t\tconst downloadSignal =\n\t\t\tsignal === undefined\n\t\t\t\t? AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)\n\t\t\t\t: AbortSignal.any([signal, AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)]);\n\t\tconst download = await fetchImpl(item.url, { signal: downloadSignal });\n\t\tif (!download.ok)\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from the provider url failed with status ${download.status} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`,\n\t\t\t};\n\t\tconst bytes = Buffer.from(await download.arrayBuffer());\n\t\tif (bytes.byteLength > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\treturn { code: \"response_too_large\", message: \"downloaded image exceeds the 20 MiB response limit\" };\n\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t}\n\treturn images;\n}\n\nfunction usageOf(response: ImagesClientResponseV1): { readonly usage?: Record<string, unknown> } {\n\treturn response.usage === undefined ? {} : { usage: response.usage };\n}\n\n/** Resolve provider config/credentials/workspace; a miss returns the failed result directly. */\nasync function resolveInvocation(\n\tapi: CapabilityAPI,\n\tproviderId: string,\n\tfetchImpl: typeof fetch,\n\tmodel: string,\n): Promise<\n\t| { readonly status: \"ok\"; readonly client: ReturnType<typeof createImagesClientV1>; readonly workspace: string }\n\t| { readonly status: \"failed\"; readonly result: ImageGenResultV1 }\n> {\n\tconst workspace = api.host.workspaceDirectory;\n\tif (typeof workspace !== \"string\" || workspace === \"\")\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tresult: failed(\n\t\t\t\tmodel,\n\t\t\t\t\"workspace_unavailable\",\n\t\t\t\t\"Image generation requires the host workspaceDirectory service to write generated-images/\",\n\t\t\t),\n\t\t};\n\tconst resolution = api.credentials === undefined ? undefined : await api.credentials.resolve({ providerId });\n\tif (resolution === undefined) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\tif (\n\t\ttypeof resolution.apiKey !== \"string\" ||\n\t\tresolution.apiKey === \"\" ||\n\t\ttypeof resolution.baseUrl !== \"string\" ||\n\t\tresolution.baseUrl === \"\"\n\t) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\treturn {\n\t\tstatus: \"ok\",\n\t\tworkspace,\n\t\tclient: createImagesClientV1({ baseUrl: resolution.baseUrl, apiKey: resolution.apiKey, fetchImpl }),\n\t};\n}\n\nfunction credentialsFailure(model: string, providerId: string): ImageGenResultV1 {\n\treturn failed(\n\t\tmodel,\n\t\t\"credentials_unavailable\",\n\t\t`Image generation credentials are unavailable for provider ${providerId}: the host must resolve apiKey and baseUrl for this provider (auth.json/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl ?? undiciFetchV1);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"image_generate\",\n\t\t\tlabel: \"Image generate\",\n\t\t\tdescription:\n\t\t\t\t\"Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves image files (png/jpg/webp/gif as produced) under generated-images/ and returns their workspace-relative paths\",\n\t\t\tparameters: IMAGE_GENERATE_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst size = boundedString(input.size, 32);\n\t\t\t\tconst quality = boundedString(input.quality, 32);\n\t\t\t\tif (input.n !== undefined && countFrom(input.n) === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"n must be an integer between 1 and 4\");\n\t\t\t\tconst n = countFrom(input.n) ?? 1;\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\t// Size-check before reading: users can drop their own files\n\t\t\t\t\t// into generated-images/, and the 20 MiB download cap must\n\t\t\t\t\t// not be bypassed by a renamed local file.\n\t\t\t\t\tconst sourceStats = await stat(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (sourceStats === undefined || !sourceStats.isFile())\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (sourceStats.size > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"response_too_large\",\n\t\t\t\t\t\t\t`image ${imagePath} is ${sourceStats.size} bytes, above the 20 MiB limit`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (source === undefined)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst response = await resolved.client.editImage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\timage: source,\n\t\t\t\t\t\t\t// The edits endpoint accepts png/jpeg/webp inputs; the\n\t\t\t\t\t\t\t// multipart blob must carry the source's real format\n\t\t\t\t\t\t\t// (a prior url-form download may be JPEG).\n\t\t\t\t\t\t\tmimeType: detectImageFormatV1(source).mimeType,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image edit request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n\nfunction failureFrom(\n\terror: unknown,\n\tsignal: AbortSignal | undefined,\n\tmodel: string,\n\tfallbackMessage: string,\n): ImageGenResultV1 {\n\tif (signal?.aborted) return cancelled(model);\n\tlet message = error instanceof Error ? error.message : fallbackMessage;\n\t// Append (never replace) a proxy hint for opaque network failures: the\n\t// single most common cause on machines that route out through a VPN.\n\tif (/fetch failed|terminated|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|UND_ERR/u.test(message))\n\t\tmessage = `${message} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`;\n\treturn failed(model, \"image_request_failed\", boundedImageMessageV1(message));\n}\n"]}
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAiU1F;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,OAAO,CAAC,EAAE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,GAAG,2BAA2B,CAyJ9B","sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { undiciFetchV1, wrapFetchWithEnvProxyV1 } from \"./env-proxy-fetch.ts\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tdetectImageFormatV1,\n\tIMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1,\n\tIMAGE_GENERATION_INLINE_LIMIT_BYTES_V1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageFormatV1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/**\n * Local-time `yyyymmdd-HHmmss` stamp shared by every file of one invocation.\n * Local (not UTC): users find today's images by the date in the filename, and\n * on UTC+8 machines a UTC stamp labels evening images as \"tomorrow\". Sorting\n * stays monotonic wherever local time has no DST jumps (e.g. China).\n */\nfunction localStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(\n\t\tdate.getHours(),\n\t)}${pad(date.getMinutes())}${pad(date.getSeconds())}`;\n}\n\ninterface ImageContentPartV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\ninterface ImageToolSuccessV1 {\n\treadonly content: readonly ({ readonly type: \"text\"; readonly text: string } | ImageContentPartV1)[];\n\treadonly details: Record<string, never>;\n}\n\ninterface ProducedImageV1 {\n\treadonly bytes: Uint8Array;\n\treadonly format: ImageFormatV1;\n}\n\nfunction successResult(\n\tresult: ImageGenResultV1,\n\timages: readonly ProducedImageV1[],\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\t// Inlining is bounded: content parts ride every later turn of the\n\t// conversation, so oversized images stay file-only and the model is told\n\t// to read them from disk when it actually needs to look.\n\tconst inlined = images.filter((image) => image.bytes.byteLength <= IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1);\n\tconst omittedNotes: string[] = [];\n\tfor (let index = 0; index < images.length; index += 1) {\n\t\tconst image = images[index];\n\t\tif (image.bytes.byteLength <= IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1) continue;\n\t\tomittedNotes.push(\n\t\t\t`${result.files[index] ?? \"generated-images/?\"} is too large to inline (${(\n\t\t\t\timage.bytes.byteLength /\n\t\t\t\t(1024 * 1024)\n\t\t\t).toFixed(1)} MiB); read the file to view it`,\n\t\t);\n\t}\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tfiles: result.files,\n\t\t\t\t\t\tmodel: result.model,\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t\t...(omittedNotes.length === 0 ? {} : { notInlined: omittedNotes }),\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t1,\n\t\t\t\t),\n\t\t\t},\n\t\t\t...inlined.map(\n\t\t\t\t(image): ImageContentPartV1 => ({\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tdata: Buffer.from(image.bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: image.format.mimeType,\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tdetails: {},\n\t};\n}\n\n/** Write one image; the exclusive-create retry makes concurrent invocations (parallel tool groups) safe. */\nasync function writeImage(\n\tworkspace: string,\n\tstamp: string,\n\tstartSequence: number,\n\tproduced: ProducedImageV1,\n): Promise<{ readonly relativePath: string; readonly sequence: number }> {\n\tconst directory = join(workspace, GENERATED_IMAGES_DIRECTORY);\n\tawait mkdir(directory, { recursive: true });\n\t// flag \"wx\" fails atomically when the name is taken (same-second stamp or\n\t// a concurrent tool group) instead of racing existsSync-then-write, which\n\t// silently overwrote one image with another under parallel execution.\n\tlet sequence = startSequence;\n\tfor (;;) {\n\t\tconst name = `img-${stamp}-${sequence}.${produced.format.extension}`;\n\t\ttry {\n\t\t\tawait writeFile(join(directory, name), produced.bytes, { flag: \"wx\" });\n\t\t\treturn { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n\t\t\t\tsequence += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/** Persist every produced image file for a settled response. */\nasync function persistImages(\n\tworkspace: string,\n\tproduced: readonly ProducedImageV1[],\n): Promise<{ readonly files: string[]; readonly images: ProducedImageV1[] }> {\n\tconst stamp = localStampV1(new Date());\n\tconst files: string[] = [];\n\tconst images: ProducedImageV1[] = [];\n\tlet sequence = 1;\n\tfor (const image of produced) {\n\t\tconst written = await writeImage(workspace, stamp, sequence, image);\n\t\tfiles.push(written.relativePath);\n\t\timages.push(image);\n\t\tsequence = written.sequence + 1;\n\t}\n\treturn { files, images };\n}\n\n/** Inline b64 items pass through; url items are downloaded with the same fetch injection. */\nasync function collectImages(\n\tresponse: ImagesClientResponseV1,\n\tfetchImpl: typeof fetch,\n\tsignal: AbortSignal | undefined,\n): Promise<ProducedImageV1[] | ImageFailureV1> {\n\tconst images: ProducedImageV1[] = [];\n\tfor (const item of response.data) {\n\t\tif (item.b64 !== undefined) {\n\t\t\tconst bytes = Buffer.from(item.b64, \"base64\");\n\t\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t\t\tcontinue;\n\t\t}\n\t\tif (item.url === undefined) continue;\n\t\t// The download carries its own 120s budget: a slow proxy route must\n\t\t// not hang the tool on undici's default 300s body timeout.\n\t\tconst downloadSignal =\n\t\t\tsignal === undefined\n\t\t\t\t? AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)\n\t\t\t\t: AbortSignal.any([signal, AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)]);\n\t\tconst download = await fetchImpl(item.url, { signal: downloadSignal });\n\t\tif (!download.ok)\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from the provider url failed with status ${download.status} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`,\n\t\t\t};\n\t\tconst bytes = Buffer.from(await download.arrayBuffer());\n\t\tif (bytes.byteLength > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\treturn { code: \"response_too_large\", message: \"downloaded image exceeds the 20 MiB response limit\" };\n\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t}\n\treturn images;\n}\n\nfunction usageOf(response: ImagesClientResponseV1): { readonly usage?: Record<string, unknown> } {\n\treturn response.usage === undefined ? {} : { usage: response.usage };\n}\n\n/** Resolve provider config/credentials/workspace; a miss returns the failed result directly. */\nasync function resolveInvocation(\n\tapi: CapabilityAPI,\n\tproviderId: string,\n\tfetchImpl: typeof fetch,\n\tmodel: string,\n): Promise<\n\t| { readonly status: \"ok\"; readonly client: ReturnType<typeof createImagesClientV1>; readonly workspace: string }\n\t| { readonly status: \"failed\"; readonly result: ImageGenResultV1 }\n> {\n\tconst workspace = api.host.workspaceDirectory;\n\tif (typeof workspace !== \"string\" || workspace === \"\")\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tresult: failed(\n\t\t\t\tmodel,\n\t\t\t\t\"workspace_unavailable\",\n\t\t\t\t\"Image generation requires the host workspaceDirectory service to write generated-images/\",\n\t\t\t),\n\t\t};\n\tconst resolution = api.credentials === undefined ? undefined : await api.credentials.resolve({ providerId });\n\tif (resolution === undefined) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\tif (\n\t\ttypeof resolution.apiKey !== \"string\" ||\n\t\tresolution.apiKey === \"\" ||\n\t\ttypeof resolution.baseUrl !== \"string\" ||\n\t\tresolution.baseUrl === \"\"\n\t) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\treturn {\n\t\tstatus: \"ok\",\n\t\tworkspace,\n\t\tclient: createImagesClientV1({ baseUrl: resolution.baseUrl, apiKey: resolution.apiKey, fetchImpl }),\n\t};\n}\n\nfunction credentialsFailure(model: string, providerId: string): ImageGenResultV1 {\n\treturn failed(\n\t\tmodel,\n\t\t\"credentials_unavailable\",\n\t\t`Image generation credentials are unavailable for provider ${providerId}: the host must resolve apiKey and baseUrl for this provider (auth.json/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl ?? undiciFetchV1);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"image_generate\",\n\t\t\tlabel: \"Image generate\",\n\t\t\tdescription:\n\t\t\t\t\"Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves image files (png/jpg/webp/gif as produced) under generated-images/ and returns their workspace-relative paths\",\n\t\t\tparameters: IMAGE_GENERATE_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst size = boundedString(input.size, 32);\n\t\t\t\tconst quality = boundedString(input.quality, 32);\n\t\t\t\tif (input.n !== undefined && countFrom(input.n) === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"n must be an integer between 1 and 4\");\n\t\t\t\tconst n = countFrom(input.n) ?? 1;\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\t// Size-check before reading: users can drop their own files\n\t\t\t\t\t// into generated-images/, and the 20 MiB download cap must\n\t\t\t\t\t// not be bypassed by a renamed local file.\n\t\t\t\t\tconst sourceStats = await stat(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (sourceStats === undefined || !sourceStats.isFile())\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (sourceStats.size > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"response_too_large\",\n\t\t\t\t\t\t\t`image ${imagePath} is ${sourceStats.size} bytes, above the 20 MiB limit`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (source === undefined)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst response = await resolved.client.editImage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\timage: source,\n\t\t\t\t\t\t\t// The edits endpoint accepts png/jpeg/webp inputs; the\n\t\t\t\t\t\t\t// multipart blob must carry the source's real format\n\t\t\t\t\t\t\t// (a prior url-form download may be JPEG).\n\t\t\t\t\t\t\tmimeType: detectImageFormatV1(source).mimeType,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image edit request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n\nfunction failureFrom(\n\terror: unknown,\n\tsignal: AbortSignal | undefined,\n\tmodel: string,\n\tfallbackMessage: string,\n): ImageGenResultV1 {\n\tif (signal?.aborted) return cancelled(model);\n\tlet message = error instanceof Error ? error.message : fallbackMessage;\n\t// Append (never replace) a proxy hint for opaque network failures: the\n\t// single most common cause on machines that route out through a VPN.\n\tif (/fetch failed|terminated|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|UND_ERR/u.test(message))\n\t\tmessage = `${message} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`;\n\treturn failed(model, \"image_request_failed\", boundedImageMessageV1(message));\n}\n"]}
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { Type } from "typebox";
4
4
  import { undiciFetchV1, wrapFetchWithEnvProxyV1 } from "./env-proxy-fetch.js";
5
5
  import { createImagesClientV1 } from "./images-client.js";
6
- import { boundedImageMessageV1, detectImageFormatV1, IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1, IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1, } from "./types.js";
6
+ import { boundedImageMessageV1, detectImageFormatV1, IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1, IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1, IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1, } from "./types.js";
7
7
  const IMAGE_GENERATE_PARAMETERS = Type.Object({
8
8
  prompt: Type.String({ minLength: 1, maxLength: 4000 }),
9
9
  size: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),
@@ -83,14 +83,31 @@ function validImagePath(imagePath) {
83
83
  return false;
84
84
  return !imagePath.split("/").includes("..");
85
85
  }
86
- /** UTC `yyyymmdd-HHmmss` stamp shared by every file of one invocation. */
87
- function utcStampV1(date) {
86
+ /**
87
+ * Local-time `yyyymmdd-HHmmss` stamp shared by every file of one invocation.
88
+ * Local (not UTC): users find today's images by the date in the filename, and
89
+ * on UTC+8 machines a UTC stamp labels evening images as "tomorrow". Sorting
90
+ * stays monotonic wherever local time has no DST jumps (e.g. China).
91
+ */
92
+ function localStampV1(date) {
88
93
  const pad = (value) => String(value).padStart(2, "0");
89
- return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
94
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
90
95
  }
91
96
  function successResult(result, images) {
92
97
  if (result.status !== "succeeded")
93
98
  return result;
99
+ // Inlining is bounded: content parts ride every later turn of the
100
+ // conversation, so oversized images stay file-only and the model is told
101
+ // to read them from disk when it actually needs to look.
102
+ const inlined = images.filter((image) => image.bytes.byteLength <= IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1);
103
+ const omittedNotes = [];
104
+ for (let index = 0; index < images.length; index += 1) {
105
+ const image = images[index];
106
+ if (image.bytes.byteLength <= IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1)
107
+ continue;
108
+ omittedNotes.push(`${result.files[index] ?? "generated-images/?"} is too large to inline (${(image.bytes.byteLength /
109
+ (1024 * 1024)).toFixed(1)} MiB); read the file to view it`);
110
+ }
94
111
  return {
95
112
  content: [
96
113
  {
@@ -99,9 +116,10 @@ function successResult(result, images) {
99
116
  files: result.files,
100
117
  model: result.model,
101
118
  ...(result.usage === undefined ? {} : { usage: result.usage }),
119
+ ...(omittedNotes.length === 0 ? {} : { notInlined: omittedNotes }),
102
120
  }, null, 1),
103
121
  },
104
- ...images.map((image) => ({
122
+ ...inlined.map((image) => ({
105
123
  type: "image",
106
124
  data: Buffer.from(image.bytes).toString("base64"),
107
125
  mimeType: image.format.mimeType,
@@ -135,7 +153,7 @@ async function writeImage(workspace, stamp, startSequence, produced) {
135
153
  }
136
154
  /** Persist every produced image file for a settled response. */
137
155
  async function persistImages(workspace, produced) {
138
- const stamp = utcStampV1(new Date());
156
+ const stamp = localStampV1(new Date());
139
157
  const files = [];
140
158
  const images = [];
141
159
  let sequence = 1;
@@ -1 +1 @@
1
- {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { undiciFetchV1, wrapFetchWithEnvProxyV1 } from \"./env-proxy-fetch.ts\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tdetectImageFormatV1,\n\tIMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageFormatV1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/** UTC `yyyymmdd-HHmmss` stamp shared by every file of one invocation. */\nfunction utcStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(\n\t\tdate.getUTCHours(),\n\t)}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;\n}\n\ninterface ImageContentPartV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\ninterface ImageToolSuccessV1 {\n\treadonly content: readonly ({ readonly type: \"text\"; readonly text: string } | ImageContentPartV1)[];\n\treadonly details: Record<string, never>;\n}\n\ninterface ProducedImageV1 {\n\treadonly bytes: Uint8Array;\n\treadonly format: ImageFormatV1;\n}\n\nfunction successResult(\n\tresult: ImageGenResultV1,\n\timages: readonly ProducedImageV1[],\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tfiles: result.files,\n\t\t\t\t\t\tmodel: result.model,\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t1,\n\t\t\t\t),\n\t\t\t},\n\t\t\t...images.map(\n\t\t\t\t(image): ImageContentPartV1 => ({\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tdata: Buffer.from(image.bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: image.format.mimeType,\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tdetails: {},\n\t};\n}\n\n/** Write one image; the exclusive-create retry makes concurrent invocations (parallel tool groups) safe. */\nasync function writeImage(\n\tworkspace: string,\n\tstamp: string,\n\tstartSequence: number,\n\tproduced: ProducedImageV1,\n): Promise<{ readonly relativePath: string; readonly sequence: number }> {\n\tconst directory = join(workspace, GENERATED_IMAGES_DIRECTORY);\n\tawait mkdir(directory, { recursive: true });\n\t// flag \"wx\" fails atomically when the name is taken (same-second stamp or\n\t// a concurrent tool group) instead of racing existsSync-then-write, which\n\t// silently overwrote one image with another under parallel execution.\n\tlet sequence = startSequence;\n\tfor (;;) {\n\t\tconst name = `img-${stamp}-${sequence}.${produced.format.extension}`;\n\t\ttry {\n\t\t\tawait writeFile(join(directory, name), produced.bytes, { flag: \"wx\" });\n\t\t\treturn { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n\t\t\t\tsequence += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/** Persist every produced image file for a settled response. */\nasync function persistImages(\n\tworkspace: string,\n\tproduced: readonly ProducedImageV1[],\n): Promise<{ readonly files: string[]; readonly images: ProducedImageV1[] }> {\n\tconst stamp = utcStampV1(new Date());\n\tconst files: string[] = [];\n\tconst images: ProducedImageV1[] = [];\n\tlet sequence = 1;\n\tfor (const image of produced) {\n\t\tconst written = await writeImage(workspace, stamp, sequence, image);\n\t\tfiles.push(written.relativePath);\n\t\timages.push(image);\n\t\tsequence = written.sequence + 1;\n\t}\n\treturn { files, images };\n}\n\n/** Inline b64 items pass through; url items are downloaded with the same fetch injection. */\nasync function collectImages(\n\tresponse: ImagesClientResponseV1,\n\tfetchImpl: typeof fetch,\n\tsignal: AbortSignal | undefined,\n): Promise<ProducedImageV1[] | ImageFailureV1> {\n\tconst images: ProducedImageV1[] = [];\n\tfor (const item of response.data) {\n\t\tif (item.b64 !== undefined) {\n\t\t\tconst bytes = Buffer.from(item.b64, \"base64\");\n\t\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t\t\tcontinue;\n\t\t}\n\t\tif (item.url === undefined) continue;\n\t\t// The download carries its own 120s budget: a slow proxy route must\n\t\t// not hang the tool on undici's default 300s body timeout.\n\t\tconst downloadSignal =\n\t\t\tsignal === undefined\n\t\t\t\t? AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)\n\t\t\t\t: AbortSignal.any([signal, AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)]);\n\t\tconst download = await fetchImpl(item.url, { signal: downloadSignal });\n\t\tif (!download.ok)\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from the provider url failed with status ${download.status} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`,\n\t\t\t};\n\t\tconst bytes = Buffer.from(await download.arrayBuffer());\n\t\tif (bytes.byteLength > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\treturn { code: \"response_too_large\", message: \"downloaded image exceeds the 20 MiB response limit\" };\n\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t}\n\treturn images;\n}\n\nfunction usageOf(response: ImagesClientResponseV1): { readonly usage?: Record<string, unknown> } {\n\treturn response.usage === undefined ? {} : { usage: response.usage };\n}\n\n/** Resolve provider config/credentials/workspace; a miss returns the failed result directly. */\nasync function resolveInvocation(\n\tapi: CapabilityAPI,\n\tproviderId: string,\n\tfetchImpl: typeof fetch,\n\tmodel: string,\n): Promise<\n\t| { readonly status: \"ok\"; readonly client: ReturnType<typeof createImagesClientV1>; readonly workspace: string }\n\t| { readonly status: \"failed\"; readonly result: ImageGenResultV1 }\n> {\n\tconst workspace = api.host.workspaceDirectory;\n\tif (typeof workspace !== \"string\" || workspace === \"\")\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tresult: failed(\n\t\t\t\tmodel,\n\t\t\t\t\"workspace_unavailable\",\n\t\t\t\t\"Image generation requires the host workspaceDirectory service to write generated-images/\",\n\t\t\t),\n\t\t};\n\tconst resolution = api.credentials === undefined ? undefined : await api.credentials.resolve({ providerId });\n\tif (resolution === undefined) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\tif (\n\t\ttypeof resolution.apiKey !== \"string\" ||\n\t\tresolution.apiKey === \"\" ||\n\t\ttypeof resolution.baseUrl !== \"string\" ||\n\t\tresolution.baseUrl === \"\"\n\t) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\treturn {\n\t\tstatus: \"ok\",\n\t\tworkspace,\n\t\tclient: createImagesClientV1({ baseUrl: resolution.baseUrl, apiKey: resolution.apiKey, fetchImpl }),\n\t};\n}\n\nfunction credentialsFailure(model: string, providerId: string): ImageGenResultV1 {\n\treturn failed(\n\t\tmodel,\n\t\t\"credentials_unavailable\",\n\t\t`Image generation credentials are unavailable for provider ${providerId}: the host must resolve apiKey and baseUrl for this provider (auth.json/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl ?? undiciFetchV1);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"image_generate\",\n\t\t\tlabel: \"Image generate\",\n\t\t\tdescription:\n\t\t\t\t\"Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves image files (png/jpg/webp/gif as produced) under generated-images/ and returns their workspace-relative paths\",\n\t\t\tparameters: IMAGE_GENERATE_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst size = boundedString(input.size, 32);\n\t\t\t\tconst quality = boundedString(input.quality, 32);\n\t\t\t\tif (input.n !== undefined && countFrom(input.n) === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"n must be an integer between 1 and 4\");\n\t\t\t\tconst n = countFrom(input.n) ?? 1;\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\t// Size-check before reading: users can drop their own files\n\t\t\t\t\t// into generated-images/, and the 20 MiB download cap must\n\t\t\t\t\t// not be bypassed by a renamed local file.\n\t\t\t\t\tconst sourceStats = await stat(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (sourceStats === undefined || !sourceStats.isFile())\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (sourceStats.size > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"response_too_large\",\n\t\t\t\t\t\t\t`image ${imagePath} is ${sourceStats.size} bytes, above the 20 MiB limit`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (source === undefined)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst response = await resolved.client.editImage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\timage: source,\n\t\t\t\t\t\t\t// The edits endpoint accepts png/jpeg/webp inputs; the\n\t\t\t\t\t\t\t// multipart blob must carry the source's real format\n\t\t\t\t\t\t\t// (a prior url-form download may be JPEG).\n\t\t\t\t\t\t\tmimeType: detectImageFormatV1(source).mimeType,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image edit request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n\nfunction failureFrom(\n\terror: unknown,\n\tsignal: AbortSignal | undefined,\n\tmodel: string,\n\tfallbackMessage: string,\n): ImageGenResultV1 {\n\tif (signal?.aborted) return cancelled(model);\n\tlet message = error instanceof Error ? error.message : fallbackMessage;\n\t// Append (never replace) a proxy hint for opaque network failures: the\n\t// single most common cause on machines that route out through a VPN.\n\tif (/fetch failed|terminated|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|UND_ERR/u.test(message))\n\t\tmessage = `${message} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`;\n\treturn failed(model, \"image_request_failed\", boundedImageMessageV1(message));\n}\n"]}
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EACN,qBAAqB,EACrB,mBAAmB,EACnB,uCAAuC,EACvC,sCAAsC,EACtC,sCAAsC,GAItC,MAAM,YAAY,CAAC;AAEpB,MAAM,yBAAyB,GAAG,IAAI,CAAC,MAAM,CAC5C;IACC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACtD,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IACjE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1D,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IACrE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;CACnE,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,qBAAqB,GAAG,IAAI,CAAC,MAAM,CACxC;IACC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC;IACnG,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACtD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IACrE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;CACnE,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,qBAAqB,GAAG,iCAAiC,CAAC;AAEhE,MAAM,0BAA0B,GAAG,kBAAkB,CAAC;AAOtD,SAAS,WAAW,CAAC,KAAc,EAAE,MAAe,EAA2B;IAC9E,yEAAyE;IACzE,iEAAiE;IACjE,yEAAyE;IACzE,oDAAoD;IACpD,MAAM,KAAK,GAAG,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IACpG,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC7D,OAAO,KAAgC,CAAC;AAAA,CACxC;AAED,SAAS,UAAU,CAAC,KAAc,EAA2B;IAC5D,OAAO,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACxD;AAED,SAAS,cAAc,CAAC,OAA6C,EAA2B;IAC/F,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAyB,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;IACxF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAAA,CAChC;AAED,SAAS,MAAM,CAAC,KAAa,EAAE,IAAY,EAAE,OAAe,EAAoB;IAC/E,OAAO;QACN,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,EAAE;QACT,KAAK;QACL,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,EAAE;KACxD,CAAC;AAAA,CACF;AAED,SAAS,SAAS,CAAC,KAAa,EAAoB;IACnD,OAAO;QACN,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,WAAW;QACnB,KAAK,EAAE,EAAE;QACT,KAAK;QACL,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,gCAAgC,EAAE;KACvE,CAAC;AAAA,CACF;AAED,8EAA8E;AAC9E,SAAS,UAAU,CAAC,KAAc,EAAE,OAAe,EAAsB;IACxE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAClF;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,SAAiB,EAAsB;IAC7E,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAClG;AAED,SAAS,UAAU,CAAC,KAAc,EAAsB;IACvD,OAAO,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AAAA,CAClC;AAED,SAAS,SAAS,CAAC,KAAc,EAAsB;IACtD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAChH;AAED,gFAAgF;AAChF,SAAS,cAAc,CAAC,SAAiB,EAAW;IACnD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACzD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,0BAA0B,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1E,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAAA,CAC5C;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,IAAU,EAAU;IACzC,MAAM,GAAG,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,GAAG,CACnF,IAAI,CAAC,QAAQ,EAAE,CACf,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC;AAAA,CACtD;AAkBD,SAAS,aAAa,CACrB,MAAwB,EACxB,MAAkC,EACM;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC;IACjD,kEAAkE;IAClE,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,sCAAsC,CAAC,CAAC;IAC3G,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,sCAAsC;YAAE,SAAS;QAC/E,YAAY,CAAC,IAAI,CAChB,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,oBAAoB,4BAA4B,CACzE,KAAK,CAAC,KAAK,CAAC,UAAU;YACtB,CAAC,IAAI,GAAG,IAAI,CAAC,CACb,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAC7C,CAAC;IACH,CAAC;IACD,OAAO;QACN,OAAO,EAAE;YACR;gBACC,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CACnB;oBACC,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;oBAC9D,GAAG,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;iBAClE,EACD,IAAI,EACJ,CAAC,CACD;aACD;YACD,GAAG,OAAO,CAAC,GAAG,CACb,CAAC,KAAK,EAAsB,EAAE,CAAC,CAAC;gBAC/B,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBACjD,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;aAC/B,CAAC,CACF;SACD;QACD,OAAO,EAAE,EAAE;KACX,CAAC;AAAA,CACF;AAED,4GAA4G;AAC5G,KAAK,UAAU,UAAU,CACxB,SAAiB,EACjB,KAAa,EACb,aAAqB,EACrB,QAAyB,EAC+C;IACxE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,0BAA0B,CAAC,CAAC;IAC9D,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,0EAA0E;IAC1E,0EAA0E;IAC1E,sEAAsE;IACtE,IAAI,QAAQ,GAAG,aAAa,CAAC;IAC7B,SAAS,CAAC;QACT,MAAM,IAAI,GAAG,OAAO,KAAK,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACrE,IAAI,CAAC;YACJ,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,OAAO,EAAE,YAAY,EAAE,GAAG,0BAA0B,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC;QAC5E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxD,QAAQ,IAAI,CAAC,CAAC;gBACd,SAAS;YACV,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;AAAA,CACD;AAED,gEAAgE;AAChE,KAAK,UAAU,aAAa,CAC3B,SAAiB,EACjB,QAAoC,EACwC;IAC5E,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QACpE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,QAAQ,GAAG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAAA,CACzB;AAED,6FAA6F;AAC7F,KAAK,UAAU,aAAa,CAC3B,QAAgC,EAChC,SAAuB,EACvB,MAA+B,EACe;IAC9C,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3D,SAAS;QACV,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,SAAS;QACrC,oEAAoE;QACpE,2DAA2D;QAC3D,MAAM,cAAc,GACnB,MAAM,KAAK,SAAS;YACnB,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,uCAAuC,CAAC;YAC9D,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,CAAC,CAAC;QAC5F,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,QAAQ,CAAC,EAAE;YACf,OAAO;gBACN,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,2DAA2D,QAAQ,CAAC,MAAM,gIAA8H;aACjN,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,UAAU,GAAG,sCAAsC;YAC5D,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC;QACtG,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,SAAS,OAAO,CAAC,QAAgC,EAAgD;IAChG,OAAO,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;AAAA,CACrE;AAED,gGAAgG;AAChG,KAAK,UAAU,iBAAiB,CAC/B,GAAkB,EAClB,UAAkB,EAClB,SAAuB,EACvB,KAAa,EAIZ;IACD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC;IAC9C,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,EAAE;QACpD,OAAO;YACN,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,MAAM,CACb,KAAK,EACL,uBAAuB,EACvB,0FAA0F,CAC1F;SACD,CAAC;IACH,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7G,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;IAC5E,CAAC;IACD,IACC,OAAO,UAAU,CAAC,MAAM,KAAK,QAAQ;QACrC,UAAU,CAAC,MAAM,KAAK,EAAE;QACxB,OAAO,UAAU,CAAC,OAAO,KAAK,QAAQ;QACtC,UAAU,CAAC,OAAO,KAAK,EAAE,EACxB,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO;QACN,MAAM,EAAE,IAAI;QACZ,SAAS;QACT,MAAM,EAAE,oBAAoB,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;KACnG,CAAC;AAAA,CACF;AAED,SAAS,kBAAkB,CAAC,KAAa,EAAE,UAAkB,EAAoB;IAChF,OAAO,MAAM,CACZ,KAAK,EACL,yBAAyB,EACzB,6DAA6D,UAAU,sFAAsF,CAC7J,CAAC;AAAA,CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,wCAAwC,CAAC,OAExD,EAA+B;IAC/B,uEAAuE;IACvE,sEAAsE;IACtE,yEAAyE;IACzE,0EAAwE;IACxE,yDAAyD;IACzD,MAAM,SAAS,GAAiB,uBAAuB,CAAC,OAAO,EAAE,SAAS,IAAI,aAAa,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QACf,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU;YAAE,OAAO;QACzC,GAAG,CAAC,YAAY,CAAC;YAChB,IAAI,EAAE,gBAAgB;YACtB,KAAK,EAAE,gBAAgB;YACvB,WAAW,EACV,oNAAoN;YACrN,UAAU,EAAE,yBAAyB;YACrC,OAAO,EAAE,KAAK,EAAE,KAAc,EAAE,MAAgB,EAAE,KAAe,EAAE,EAAE,CAAC;gBACrE,IAAI,KAA8B,CAAC;gBACnC,IAAI,CAAC;oBACJ,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBACpC,CAAC;gBAAC,MAAM,CAAC;oBACR,OAAO,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,0CAA0C,CAAC,CAAC;gBAChF,CAAC;gBACD,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACxC,IAAI,MAAM,KAAK,SAAS;oBACvB,OAAO,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,8DAA8D,CAAC,CAAC;gBACpG,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC3C,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBACjD,IAAI,KAAK,CAAC,CAAC,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS;oBAC5D,OAAO,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,sCAAsC,CAAC,CAAC;gBAC5E,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAClC,MAAM,aAAa,GAAG,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;gBACjD,MAAM,gBAAgB,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;gBAC3C,MAAM,YAAY,GAAG,cAAc,CAAC,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC;gBACvE,IAAI,YAAY,EAAE,OAAO;oBAAE,OAAO,SAAS,CAAC,EAAE,CAAC,CAAC;gBAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,+BAA+B,CAAC,CAAC;gBAC7E,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC;gBACpE,IAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;oBAChD,OAAO,MAAM,CACZ,EAAE,EACF,gBAAgB,EAChB,4KAA4K,CAC5K,CAAC;gBACH,IAAI,CAAC;oBACJ,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;oBAC1E,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ;wBAAE,OAAO,QAAQ,CAAC,MAAM,CAAC;oBACzD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,cAAc,CACpD;wBACC,KAAK;wBACL,MAAM;wBACN,CAAC;wBACD,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;wBACvC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;qBAC7C,EACD,YAAY,CACZ,CAAC;oBACF,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;oBACzE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;wBAAE,OAAO,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;oBACvF,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;oBACrE,OAAO,aAAa,CACnB,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,EACxF,SAAS,CAAC,MAAM,CAChB,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,OAAO,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,iCAAiC,CAAC,CAAC;gBACnF,CAAC;YAAA,CACD;SACD,CAAC,CAAC;QACH,GAAG,CAAC,YAAY,CAAC;YAChB,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,YAAY;YACnB,WAAW,EACV,uIAAuI;YACxI,UAAU,EAAE,qBAAqB;YACjC,OAAO,EAAE,KAAK,EAAE,KAAc,EAAE,MAAgB,EAAE,KAAe,EAAE,EAAE,CAAC;gBACrE,IAAI,KAA8B,CAAC;gBACnC,IAAI,CAAC;oBACJ,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBACpC,CAAC;gBAAC,MAAM,CAAC;oBACR,OAAO,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,0CAA0C,CAAC,CAAC;gBAChF,CAAC;gBACD,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACxC,IAAI,MAAM,KAAK,SAAS;oBACvB,OAAO,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,8DAA8D,CAAC,CAAC;gBACpG,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;gBACpF,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;oBACxD,OAAO,MAAM,CACZ,EAAE,EACF,cAAc,EACd,2GAA2G,CAC3G,CAAC;gBACH,MAAM,aAAa,GAAG,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;gBACjD,MAAM,gBAAgB,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;gBAC3C,MAAM,YAAY,GAAG,cAAc,CAAC,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC;gBACvE,IAAI,YAAY,EAAE,OAAO;oBAAE,OAAO,SAAS,CAAC,EAAE,CAAC,CAAC;gBAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,+BAA+B,CAAC,CAAC;gBAC7E,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC;gBACpE,IAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;oBAChD,OAAO,MAAM,CACZ,EAAE,EACF,gBAAgB,EAChB,4KAA4K,CAC5K,CAAC;gBACH,IAAI,CAAC;oBACJ,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;oBAC1E,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ;wBAAE,OAAO,QAAQ,CAAC,MAAM,CAAC;oBACzD,4DAA4D;oBAC5D,2DAA2D;oBAC3D,2CAA2C;oBAC3C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAC3F,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;wBACrD,OAAO,MAAM,CACZ,KAAK,EACL,iBAAiB,EACjB,SAAS,SAAS,gEAAgE,CAClF,CAAC;oBACH,IAAI,WAAW,CAAC,IAAI,GAAG,sCAAsC;wBAC5D,OAAO,MAAM,CACZ,KAAK,EACL,oBAAoB,EACpB,SAAS,SAAS,OAAO,WAAW,CAAC,IAAI,gCAAgC,CACzE,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAC1F,IAAI,MAAM,KAAK,SAAS;wBACvB,OAAO,MAAM,CACZ,KAAK,EACL,iBAAiB,EACjB,SAAS,SAAS,gEAAgE,CAClF,CAAC;oBACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,CAC/C;wBACC,KAAK,EAAE,MAAM;wBACb,uDAAuD;wBACvD,qDAAqD;wBACrD,2CAA2C;wBAC3C,QAAQ,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,QAAQ;wBAC9C,MAAM;wBACN,KAAK;qBACL,EACD,YAAY,CACZ,CAAC;oBACF,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;oBACzE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;wBAAE,OAAO,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;oBACvF,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;oBACrE,OAAO,aAAa,CACnB,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,EACxF,SAAS,CAAC,MAAM,CAChB,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,OAAO,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAC;gBAC7E,CAAC;YAAA,CACD;SACD,CAAC,CAAC;IAAA,CACH,CAAC;AAAA,CACF;AAED,SAAS,WAAW,CACnB,KAAc,EACd,MAA+B,EAC/B,KAAa,EACb,eAAuB,EACJ;IACnB,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;IACvE,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,6EAA6E,CAAC,IAAI,CAAC,OAAO,CAAC;QAC9F,OAAO,GAAG,GAAG,OAAO,gIAA8H,CAAC;IACpJ,OAAO,MAAM,CAAC,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CAC7E","sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { undiciFetchV1, wrapFetchWithEnvProxyV1 } from \"./env-proxy-fetch.ts\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tdetectImageFormatV1,\n\tIMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1,\n\tIMAGE_GENERATION_INLINE_LIMIT_BYTES_V1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageFormatV1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/**\n * Local-time `yyyymmdd-HHmmss` stamp shared by every file of one invocation.\n * Local (not UTC): users find today's images by the date in the filename, and\n * on UTC+8 machines a UTC stamp labels evening images as \"tomorrow\". Sorting\n * stays monotonic wherever local time has no DST jumps (e.g. China).\n */\nfunction localStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(\n\t\tdate.getHours(),\n\t)}${pad(date.getMinutes())}${pad(date.getSeconds())}`;\n}\n\ninterface ImageContentPartV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\ninterface ImageToolSuccessV1 {\n\treadonly content: readonly ({ readonly type: \"text\"; readonly text: string } | ImageContentPartV1)[];\n\treadonly details: Record<string, never>;\n}\n\ninterface ProducedImageV1 {\n\treadonly bytes: Uint8Array;\n\treadonly format: ImageFormatV1;\n}\n\nfunction successResult(\n\tresult: ImageGenResultV1,\n\timages: readonly ProducedImageV1[],\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\t// Inlining is bounded: content parts ride every later turn of the\n\t// conversation, so oversized images stay file-only and the model is told\n\t// to read them from disk when it actually needs to look.\n\tconst inlined = images.filter((image) => image.bytes.byteLength <= IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1);\n\tconst omittedNotes: string[] = [];\n\tfor (let index = 0; index < images.length; index += 1) {\n\t\tconst image = images[index];\n\t\tif (image.bytes.byteLength <= IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1) continue;\n\t\tomittedNotes.push(\n\t\t\t`${result.files[index] ?? \"generated-images/?\"} is too large to inline (${(\n\t\t\t\timage.bytes.byteLength /\n\t\t\t\t(1024 * 1024)\n\t\t\t).toFixed(1)} MiB); read the file to view it`,\n\t\t);\n\t}\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tfiles: result.files,\n\t\t\t\t\t\tmodel: result.model,\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t\t...(omittedNotes.length === 0 ? {} : { notInlined: omittedNotes }),\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t1,\n\t\t\t\t),\n\t\t\t},\n\t\t\t...inlined.map(\n\t\t\t\t(image): ImageContentPartV1 => ({\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tdata: Buffer.from(image.bytes).toString(\"base64\"),\n\t\t\t\t\tmimeType: image.format.mimeType,\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tdetails: {},\n\t};\n}\n\n/** Write one image; the exclusive-create retry makes concurrent invocations (parallel tool groups) safe. */\nasync function writeImage(\n\tworkspace: string,\n\tstamp: string,\n\tstartSequence: number,\n\tproduced: ProducedImageV1,\n): Promise<{ readonly relativePath: string; readonly sequence: number }> {\n\tconst directory = join(workspace, GENERATED_IMAGES_DIRECTORY);\n\tawait mkdir(directory, { recursive: true });\n\t// flag \"wx\" fails atomically when the name is taken (same-second stamp or\n\t// a concurrent tool group) instead of racing existsSync-then-write, which\n\t// silently overwrote one image with another under parallel execution.\n\tlet sequence = startSequence;\n\tfor (;;) {\n\t\tconst name = `img-${stamp}-${sequence}.${produced.format.extension}`;\n\t\ttry {\n\t\t\tawait writeFile(join(directory, name), produced.bytes, { flag: \"wx\" });\n\t\t\treturn { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"EEXIST\") {\n\t\t\t\tsequence += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/** Persist every produced image file for a settled response. */\nasync function persistImages(\n\tworkspace: string,\n\tproduced: readonly ProducedImageV1[],\n): Promise<{ readonly files: string[]; readonly images: ProducedImageV1[] }> {\n\tconst stamp = localStampV1(new Date());\n\tconst files: string[] = [];\n\tconst images: ProducedImageV1[] = [];\n\tlet sequence = 1;\n\tfor (const image of produced) {\n\t\tconst written = await writeImage(workspace, stamp, sequence, image);\n\t\tfiles.push(written.relativePath);\n\t\timages.push(image);\n\t\tsequence = written.sequence + 1;\n\t}\n\treturn { files, images };\n}\n\n/** Inline b64 items pass through; url items are downloaded with the same fetch injection. */\nasync function collectImages(\n\tresponse: ImagesClientResponseV1,\n\tfetchImpl: typeof fetch,\n\tsignal: AbortSignal | undefined,\n): Promise<ProducedImageV1[] | ImageFailureV1> {\n\tconst images: ProducedImageV1[] = [];\n\tfor (const item of response.data) {\n\t\tif (item.b64 !== undefined) {\n\t\t\tconst bytes = Buffer.from(item.b64, \"base64\");\n\t\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t\t\tcontinue;\n\t\t}\n\t\tif (item.url === undefined) continue;\n\t\t// The download carries its own 120s budget: a slow proxy route must\n\t\t// not hang the tool on undici's default 300s body timeout.\n\t\tconst downloadSignal =\n\t\t\tsignal === undefined\n\t\t\t\t? AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)\n\t\t\t\t: AbortSignal.any([signal, AbortSignal.timeout(IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1)]);\n\t\tconst download = await fetchImpl(item.url, { signal: downloadSignal });\n\t\tif (!download.ok)\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from the provider url failed with status ${download.status} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`,\n\t\t\t};\n\t\tconst bytes = Buffer.from(await download.arrayBuffer());\n\t\tif (bytes.byteLength > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\treturn { code: \"response_too_large\", message: \"downloaded image exceeds the 20 MiB response limit\" };\n\t\timages.push({ bytes, format: detectImageFormatV1(bytes) });\n\t}\n\treturn images;\n}\n\nfunction usageOf(response: ImagesClientResponseV1): { readonly usage?: Record<string, unknown> } {\n\treturn response.usage === undefined ? {} : { usage: response.usage };\n}\n\n/** Resolve provider config/credentials/workspace; a miss returns the failed result directly. */\nasync function resolveInvocation(\n\tapi: CapabilityAPI,\n\tproviderId: string,\n\tfetchImpl: typeof fetch,\n\tmodel: string,\n): Promise<\n\t| { readonly status: \"ok\"; readonly client: ReturnType<typeof createImagesClientV1>; readonly workspace: string }\n\t| { readonly status: \"failed\"; readonly result: ImageGenResultV1 }\n> {\n\tconst workspace = api.host.workspaceDirectory;\n\tif (typeof workspace !== \"string\" || workspace === \"\")\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tresult: failed(\n\t\t\t\tmodel,\n\t\t\t\t\"workspace_unavailable\",\n\t\t\t\t\"Image generation requires the host workspaceDirectory service to write generated-images/\",\n\t\t\t),\n\t\t};\n\tconst resolution = api.credentials === undefined ? undefined : await api.credentials.resolve({ providerId });\n\tif (resolution === undefined) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\tif (\n\t\ttypeof resolution.apiKey !== \"string\" ||\n\t\tresolution.apiKey === \"\" ||\n\t\ttypeof resolution.baseUrl !== \"string\" ||\n\t\tresolution.baseUrl === \"\"\n\t) {\n\t\treturn { status: \"failed\", result: credentialsFailure(model, providerId) };\n\t}\n\treturn {\n\t\tstatus: \"ok\",\n\t\tworkspace,\n\t\tclient: createImagesClientV1({ baseUrl: resolution.baseUrl, apiKey: resolution.apiKey, fetchImpl }),\n\t};\n}\n\nfunction credentialsFailure(model: string, providerId: string): ImageGenResultV1 {\n\treturn failed(\n\t\tmodel,\n\t\t\"credentials_unavailable\",\n\t\t`Image generation credentials are unavailable for provider ${providerId}: the host must resolve apiKey and baseUrl for this provider (auth.json/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl ?? undiciFetchV1);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"image_generate\",\n\t\t\tlabel: \"Image generate\",\n\t\t\tdescription:\n\t\t\t\t\"Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves image files (png/jpg/webp/gif as produced) under generated-images/ and returns their workspace-relative paths\",\n\t\t\tparameters: IMAGE_GENERATE_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst size = boundedString(input.size, 32);\n\t\t\t\tconst quality = boundedString(input.quality, 32);\n\t\t\t\tif (input.n !== undefined && countFrom(input.n) === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"n must be an integer between 1 and 4\");\n\t\t\t\tconst n = countFrom(input.n) ?? 1;\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\t// Size-check before reading: users can drop their own files\n\t\t\t\t\t// into generated-images/, and the 20 MiB download cap must\n\t\t\t\t\t// not be bypassed by a renamed local file.\n\t\t\t\t\tconst sourceStats = await stat(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (sourceStats === undefined || !sourceStats.isFile())\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (sourceStats.size > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"response_too_large\",\n\t\t\t\t\t\t\t`image ${imagePath} is ${sourceStats.size} bytes, above the 20 MiB limit`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (source === undefined)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst response = await resolved.client.editImage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\timage: source,\n\t\t\t\t\t\t\t// The edits endpoint accepts png/jpeg/webp inputs; the\n\t\t\t\t\t\t\t// multipart blob must carry the source's real format\n\t\t\t\t\t\t\t// (a prior url-form download may be JPEG).\n\t\t\t\t\t\t\tmimeType: detectImageFormatV1(source).mimeType,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image edit request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n\nfunction failureFrom(\n\terror: unknown,\n\tsignal: AbortSignal | undefined,\n\tmodel: string,\n\tfallbackMessage: string,\n): ImageGenResultV1 {\n\tif (signal?.aborted) return cancelled(model);\n\tlet message = error instanceof Error ? error.message : fallbackMessage;\n\t// Append (never replace) a proxy hint for opaque network failures: the\n\t// single most common cause on machines that route out through a VPN.\n\tif (/fetch failed|terminated|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|UND_ERR/u.test(message))\n\t\tmessage = `${message} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`;\n\treturn failed(model, \"image_request_failed\", boundedImageMessageV1(message));\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -8,6 +8,13 @@ export declare const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1: number;
8
8
  * undici's default 300s body timeout.
9
9
  */
10
10
  export declare const IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1 = 120000;
11
+ /**
12
+ * Inline budget for image content parts. Inlined parts ride the tool result
13
+ * into the conversation and are re-sent to the model on every later turn, so
14
+ * a huge image balloons context and cost. Larger images are still written to
15
+ * disk and named in the text part — the model can read the file on demand.
16
+ */
17
+ export declare const IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1: number;
11
18
  /** One generated image in either wire form the OpenAI-compatible API returns. */
12
19
  export interface ImagesGeneratedItemV1 {
13
20
  /** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,qCAAqC,MAAM,CAAC;AAEzD,gEAAgE;AAChE,eAAO,MAAM,sCAAsC,QAAmB,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,SAAU,CAAC;AAE/D,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACrC,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,cAAc,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxG,SAAS,CAAC,OAAO,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC/F;AAED,qGAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAqBpE","sourcesContent":["/** Hard cap for user-facing error messages (truncated, never grown). */\nexport const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;\n\n/** Hard cap for a single downloaded image response (20 MiB). */\nexport const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;\n\n/**\n * Budget for one provider-CDN download. The generation request carries its\n * own 180s timeout; without this a slow proxy route could hang a download on\n * undici's default 300s body timeout.\n */\nexport const IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1 = 120_000;\n\n/** One generated image in either wire form the OpenAI-compatible API returns. */\nexport interface ImagesGeneratedItemV1 {\n\t/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */\n\treadonly b64?: string;\n\t/** Remote image URL; the capability layer downloads it. */\n\treadonly url?: string;\n\treadonly mimeType?: string;\n}\n\nexport interface ImagesClientResponseV1 {\n\treadonly data: readonly ImagesGeneratedItemV1[];\n\treadonly usage?: Record<string, unknown>;\n}\n\nexport interface ImagesGenerateRequestV1 {\n\treadonly model: string;\n\treadonly prompt: string;\n\treadonly n: number;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesEditRequestV1 {\n\treadonly image: Uint8Array;\n\treadonly mimeType: string;\n\treadonly prompt: string;\n\treadonly model: string;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesClientV1 {\n\tgenerateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n\teditImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n}\n\n/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */\nexport interface ImageGenResultV1 {\n\treadonly version: 1;\n\treadonly status: \"succeeded\" | \"failed\" | \"cancelled\";\n\treadonly files: readonly string[];\n\treadonly model: string;\n\treadonly usage?: Record<string, unknown>;\n\treadonly error?: { readonly code: string; readonly message: string };\n}\n\n/** Truncate a message to the bounded error budget; never grows the input. */\nexport function boundedImageMessageV1(message: string): string {\n\treturn message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1\n\t\t? message\n\t\t: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;\n}\n\n/** File naming + content-part mime for one produced image. */\nexport interface ImageFormatV1 {\n\treadonly extension: string;\n\treadonly mimeType: string;\n}\n\n/**\n * Detect the actual image format from magic bytes. Providers return whatever\n * the model produced regardless of the requested form: gpt-image-2 b64 is\n * PNG while grok-imagine-image url downloads are JPEG — naming every file\n * `.png` and labeling every content part `image/png` mislabels JPEG payloads\n * and breaks vision review for the revision loop. Unknown magic keeps the\n * legacy PNG assumption (fail-soft, same as before this detection existed).\n */\nexport function detectImageFormatV1(bytes: Uint8Array): ImageFormatV1 {\n\tif (bytes.length >= 4 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)\n\t\treturn { extension: \"png\", mimeType: \"image/png\" };\n\tif (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)\n\t\treturn { extension: \"jpg\", mimeType: \"image/jpeg\" };\n\t// RIFF....WEBP: both halves of the container magic, not just the WEBP tag.\n\tif (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t)\n\t\treturn { extension: \"webp\", mimeType: \"image/webp\" };\n\tif (bytes.length >= 3 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)\n\t\treturn { extension: \"gif\", mimeType: \"image/gif\" };\n\treturn { extension: \"png\", mimeType: \"image/png\" };\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,qCAAqC,MAAM,CAAC;AAEzD,gEAAgE;AAChE,eAAO,MAAM,sCAAsC,QAAmB,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,SAAU,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,sCAAsC,QAAkB,CAAC;AAEtE,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACrC,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,cAAc,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxG,SAAS,CAAC,OAAO,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC/F;AAED,qGAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAqBpE","sourcesContent":["/** Hard cap for user-facing error messages (truncated, never grown). */\nexport const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;\n\n/** Hard cap for a single downloaded image response (20 MiB). */\nexport const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;\n\n/**\n * Budget for one provider-CDN download. The generation request carries its\n * own 180s timeout; without this a slow proxy route could hang a download on\n * undici's default 300s body timeout.\n */\nexport const IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1 = 120_000;\n\n/**\n * Inline budget for image content parts. Inlined parts ride the tool result\n * into the conversation and are re-sent to the model on every later turn, so\n * a huge image balloons context and cost. Larger images are still written to\n * disk and named in the text part — the model can read the file on demand.\n */\nexport const IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1 = 4 * 1024 * 1024;\n\n/** One generated image in either wire form the OpenAI-compatible API returns. */\nexport interface ImagesGeneratedItemV1 {\n\t/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */\n\treadonly b64?: string;\n\t/** Remote image URL; the capability layer downloads it. */\n\treadonly url?: string;\n\treadonly mimeType?: string;\n}\n\nexport interface ImagesClientResponseV1 {\n\treadonly data: readonly ImagesGeneratedItemV1[];\n\treadonly usage?: Record<string, unknown>;\n}\n\nexport interface ImagesGenerateRequestV1 {\n\treadonly model: string;\n\treadonly prompt: string;\n\treadonly n: number;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesEditRequestV1 {\n\treadonly image: Uint8Array;\n\treadonly mimeType: string;\n\treadonly prompt: string;\n\treadonly model: string;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesClientV1 {\n\tgenerateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n\teditImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n}\n\n/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */\nexport interface ImageGenResultV1 {\n\treadonly version: 1;\n\treadonly status: \"succeeded\" | \"failed\" | \"cancelled\";\n\treadonly files: readonly string[];\n\treadonly model: string;\n\treadonly usage?: Record<string, unknown>;\n\treadonly error?: { readonly code: string; readonly message: string };\n}\n\n/** Truncate a message to the bounded error budget; never grows the input. */\nexport function boundedImageMessageV1(message: string): string {\n\treturn message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1\n\t\t? message\n\t\t: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;\n}\n\n/** File naming + content-part mime for one produced image. */\nexport interface ImageFormatV1 {\n\treadonly extension: string;\n\treadonly mimeType: string;\n}\n\n/**\n * Detect the actual image format from magic bytes. Providers return whatever\n * the model produced regardless of the requested form: gpt-image-2 b64 is\n * PNG while grok-imagine-image url downloads are JPEG — naming every file\n * `.png` and labeling every content part `image/png` mislabels JPEG payloads\n * and breaks vision review for the revision loop. Unknown magic keeps the\n * legacy PNG assumption (fail-soft, same as before this detection existed).\n */\nexport function detectImageFormatV1(bytes: Uint8Array): ImageFormatV1 {\n\tif (bytes.length >= 4 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)\n\t\treturn { extension: \"png\", mimeType: \"image/png\" };\n\tif (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)\n\t\treturn { extension: \"jpg\", mimeType: \"image/jpeg\" };\n\t// RIFF....WEBP: both halves of the container magic, not just the WEBP tag.\n\tif (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t)\n\t\treturn { extension: \"webp\", mimeType: \"image/webp\" };\n\tif (bytes.length >= 3 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)\n\t\treturn { extension: \"gif\", mimeType: \"image/gif\" };\n\treturn { extension: \"png\", mimeType: \"image/png\" };\n}\n"]}
package/dist/types.js CHANGED
@@ -8,6 +8,13 @@ export const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;
8
8
  * undici's default 300s body timeout.
9
9
  */
10
10
  export const IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1 = 120_000;
11
+ /**
12
+ * Inline budget for image content parts. Inlined parts ride the tool result
13
+ * into the conversation and are re-sent to the model on every later turn, so
14
+ * a huge image balloons context and cost. Larger images are still written to
15
+ * disk and named in the text part — the model can read the file on demand.
16
+ */
17
+ export const IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1 = 4 * 1024 * 1024;
11
18
  /** Truncate a message to the bounded error budget; never grows the input. */
12
19
  export function boundedImageMessageV1(message) {
13
20
  return message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,qCAAqC,GAAG,GAAG,CAAC;AAEzD,gEAAgE;AAChE,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,uCAAuC,GAAG,OAAO,CAAC;AAgD/D,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CAAC,OAAe,EAAU;IAC9D,OAAO,OAAO,CAAC,MAAM,IAAI,qCAAqC;QAC7D,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,qCAAqC,GAAG,CAAC,CAAC,KAAK,CAAC;AAAA,CACvE;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB,EAAiB;IACrE,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACxG,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACpD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACnF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACrD,2EAA2E;IAC3E,IACC,KAAK,CAAC,MAAM,IAAI,EAAE;QAClB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI;QAClB,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI;QAElB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACtD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACnF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACpD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAAA,CACnD","sourcesContent":["/** Hard cap for user-facing error messages (truncated, never grown). */\nexport const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;\n\n/** Hard cap for a single downloaded image response (20 MiB). */\nexport const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;\n\n/**\n * Budget for one provider-CDN download. The generation request carries its\n * own 180s timeout; without this a slow proxy route could hang a download on\n * undici's default 300s body timeout.\n */\nexport const IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1 = 120_000;\n\n/** One generated image in either wire form the OpenAI-compatible API returns. */\nexport interface ImagesGeneratedItemV1 {\n\t/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */\n\treadonly b64?: string;\n\t/** Remote image URL; the capability layer downloads it. */\n\treadonly url?: string;\n\treadonly mimeType?: string;\n}\n\nexport interface ImagesClientResponseV1 {\n\treadonly data: readonly ImagesGeneratedItemV1[];\n\treadonly usage?: Record<string, unknown>;\n}\n\nexport interface ImagesGenerateRequestV1 {\n\treadonly model: string;\n\treadonly prompt: string;\n\treadonly n: number;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesEditRequestV1 {\n\treadonly image: Uint8Array;\n\treadonly mimeType: string;\n\treadonly prompt: string;\n\treadonly model: string;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesClientV1 {\n\tgenerateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n\teditImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n}\n\n/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */\nexport interface ImageGenResultV1 {\n\treadonly version: 1;\n\treadonly status: \"succeeded\" | \"failed\" | \"cancelled\";\n\treadonly files: readonly string[];\n\treadonly model: string;\n\treadonly usage?: Record<string, unknown>;\n\treadonly error?: { readonly code: string; readonly message: string };\n}\n\n/** Truncate a message to the bounded error budget; never grows the input. */\nexport function boundedImageMessageV1(message: string): string {\n\treturn message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1\n\t\t? message\n\t\t: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;\n}\n\n/** File naming + content-part mime for one produced image. */\nexport interface ImageFormatV1 {\n\treadonly extension: string;\n\treadonly mimeType: string;\n}\n\n/**\n * Detect the actual image format from magic bytes. Providers return whatever\n * the model produced regardless of the requested form: gpt-image-2 b64 is\n * PNG while grok-imagine-image url downloads are JPEG — naming every file\n * `.png` and labeling every content part `image/png` mislabels JPEG payloads\n * and breaks vision review for the revision loop. Unknown magic keeps the\n * legacy PNG assumption (fail-soft, same as before this detection existed).\n */\nexport function detectImageFormatV1(bytes: Uint8Array): ImageFormatV1 {\n\tif (bytes.length >= 4 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)\n\t\treturn { extension: \"png\", mimeType: \"image/png\" };\n\tif (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)\n\t\treturn { extension: \"jpg\", mimeType: \"image/jpeg\" };\n\t// RIFF....WEBP: both halves of the container magic, not just the WEBP tag.\n\tif (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t)\n\t\treturn { extension: \"webp\", mimeType: \"image/webp\" };\n\tif (bytes.length >= 3 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)\n\t\treturn { extension: \"gif\", mimeType: \"image/gif\" };\n\treturn { extension: \"png\", mimeType: \"image/png\" };\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,qCAAqC,GAAG,GAAG,CAAC;AAEzD,gEAAgE;AAChE,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,uCAAuC,GAAG,OAAO,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sCAAsC,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAgDtE,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CAAC,OAAe,EAAU;IAC9D,OAAO,OAAO,CAAC,MAAM,IAAI,qCAAqC;QAC7D,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,qCAAqC,GAAG,CAAC,CAAC,KAAK,CAAC;AAAA,CACvE;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB,EAAiB;IACrE,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACxG,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACpD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACnF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACrD,2EAA2E;IAC3E,IACC,KAAK,CAAC,MAAM,IAAI,EAAE;QAClB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI;QAClB,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI;QAElB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACtD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACnF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACpD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAAA,CACnD","sourcesContent":["/** Hard cap for user-facing error messages (truncated, never grown). */\nexport const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;\n\n/** Hard cap for a single downloaded image response (20 MiB). */\nexport const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;\n\n/**\n * Budget for one provider-CDN download. The generation request carries its\n * own 180s timeout; without this a slow proxy route could hang a download on\n * undici's default 300s body timeout.\n */\nexport const IMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1 = 120_000;\n\n/**\n * Inline budget for image content parts. Inlined parts ride the tool result\n * into the conversation and are re-sent to the model on every later turn, so\n * a huge image balloons context and cost. Larger images are still written to\n * disk and named in the text part — the model can read the file on demand.\n */\nexport const IMAGE_GENERATION_INLINE_LIMIT_BYTES_V1 = 4 * 1024 * 1024;\n\n/** One generated image in either wire form the OpenAI-compatible API returns. */\nexport interface ImagesGeneratedItemV1 {\n\t/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */\n\treadonly b64?: string;\n\t/** Remote image URL; the capability layer downloads it. */\n\treadonly url?: string;\n\treadonly mimeType?: string;\n}\n\nexport interface ImagesClientResponseV1 {\n\treadonly data: readonly ImagesGeneratedItemV1[];\n\treadonly usage?: Record<string, unknown>;\n}\n\nexport interface ImagesGenerateRequestV1 {\n\treadonly model: string;\n\treadonly prompt: string;\n\treadonly n: number;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesEditRequestV1 {\n\treadonly image: Uint8Array;\n\treadonly mimeType: string;\n\treadonly prompt: string;\n\treadonly model: string;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesClientV1 {\n\tgenerateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n\teditImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n}\n\n/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */\nexport interface ImageGenResultV1 {\n\treadonly version: 1;\n\treadonly status: \"succeeded\" | \"failed\" | \"cancelled\";\n\treadonly files: readonly string[];\n\treadonly model: string;\n\treadonly usage?: Record<string, unknown>;\n\treadonly error?: { readonly code: string; readonly message: string };\n}\n\n/** Truncate a message to the bounded error budget; never grows the input. */\nexport function boundedImageMessageV1(message: string): string {\n\treturn message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1\n\t\t? message\n\t\t: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;\n}\n\n/** File naming + content-part mime for one produced image. */\nexport interface ImageFormatV1 {\n\treadonly extension: string;\n\treadonly mimeType: string;\n}\n\n/**\n * Detect the actual image format from magic bytes. Providers return whatever\n * the model produced regardless of the requested form: gpt-image-2 b64 is\n * PNG while grok-imagine-image url downloads are JPEG — naming every file\n * `.png` and labeling every content part `image/png` mislabels JPEG payloads\n * and breaks vision review for the revision loop. Unknown magic keeps the\n * legacy PNG assumption (fail-soft, same as before this detection existed).\n */\nexport function detectImageFormatV1(bytes: Uint8Array): ImageFormatV1 {\n\tif (bytes.length >= 4 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)\n\t\treturn { extension: \"png\", mimeType: \"image/png\" };\n\tif (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)\n\t\treturn { extension: \"jpg\", mimeType: \"image/jpeg\" };\n\t// RIFF....WEBP: both halves of the container magic, not just the WEBP tag.\n\tif (\n\t\tbytes.length >= 12 &&\n\t\tbytes[0] === 0x52 &&\n\t\tbytes[1] === 0x49 &&\n\t\tbytes[2] === 0x46 &&\n\t\tbytes[3] === 0x46 &&\n\t\tbytes[8] === 0x57 &&\n\t\tbytes[9] === 0x45 &&\n\t\tbytes[10] === 0x42 &&\n\t\tbytes[11] === 0x50\n\t)\n\t\treturn { extension: \"webp\", mimeType: \"image/webp\" };\n\tif (bytes.length >= 3 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)\n\t\treturn { extension: \"gif\", mimeType: \"image/gif\" };\n\treturn { extension: \"png\", mimeType: \"image/png\" };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-agent-forge/plugin-image-generation",
3
- "version": "0.85.18",
3
+ "version": "0.85.19",
4
4
  "description": "Image generation capability plugin for Agent Forge (image_generate / image_edit tools)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,11 +46,11 @@
46
46
  "dependencies": {
47
47
  "typebox": "1.3.27",
48
48
  "undici": "8.9.0",
49
- "@ai-agent-forge/plugin-sdk": "^0.85.17"
49
+ "@ai-agent-forge/plugin-sdk": "^0.85.18"
50
50
  },
51
51
  "devDependencies": {
52
52
  "shx": "0.4.0",
53
53
  "vitest": "4.1.9",
54
- "@ai-agent-forge/agent-forge": "^0.85.17"
54
+ "@ai-agent-forge/agent-forge": "^0.85.18"
55
55
  }
56
56
  }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.plugin.image-generation",
3
- "version": "0.85.17",
3
+ "version": "0.85.18",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.84.4",