@ai-agent-forge/plugin-image-generation 0.88.5 → 0.88.6
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 +1 -1
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +33 -2
- package/dist/capability.js.map +1 -1
- package/package.json +2 -2
- package/plugin.json +1 -1
package/agent-forge.json
CHANGED
package/dist/capability.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,2BAA2B,EAAgB,MAAM,yBAAyB,CAAC;AA0cxG;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,OAAO,CAAC,EAAE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,GAAG,2BAA2B,CA8J9B","sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory, PluginConfig } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { 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), a non-empty env value, or undefined. */\nfunction nonEmptyEnvValue(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\n/** settings 槽位形状(宿主经 configOverride 注入,设计 §3.1/§3.3)。 */\ninterface ImageGenerationSlotV1 {\n\treadonly provider: string;\n\treadonly model?: string;\n}\n\n/** 目录快照条目:宿主装配期从 models.json 登记项投影(设计 §3.2 推断供料)。 */\ninterface CatalogModelV1 {\n\treadonly id: string;\n\treadonly name?: string;\n}\n\ntype ImageModelCatalogV1 = Readonly<Record<string, ReadonlyArray<CatalogModelV1>>>;\n\nfunction readInjectedSlot(config: PluginConfig): { readonly slot?: ImageGenerationSlotV1; readonly error?: string } {\n\tconst raw = config.imageGeneration;\n\tif (raw === undefined) return {};\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn { error: \"injected config imageGeneration must be an object\" };\n\tconst record = raw as Record<string, unknown>;\n\tif (typeof record.provider !== \"string\" || record.provider.trim() === \"\")\n\t\treturn {\n\t\t\terror: `injected config imageGeneration.provider must be a non-empty string (got ${typeof record.provider})`,\n\t\t};\n\tif (record.model !== undefined && (typeof record.model !== \"string\" || record.model.trim() === \"\"))\n\t\treturn { error: \"injected config imageGeneration.model must be a non-empty string when present\" };\n\treturn { slot: { provider: record.provider, ...(record.model === undefined ? {} : { model: record.model }) } };\n}\n\nfunction readInjectedCatalog(config: PluginConfig): { readonly catalog: ImageModelCatalogV1; readonly error?: string } {\n\tconst raw = config.imageModelCatalog;\n\tif (raw === undefined) return { catalog: {} };\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn {\n\t\t\tcatalog: {},\n\t\t\terror: \"injected config imageModelCatalog must be an object mapping providers to image models\",\n\t\t};\n\tconst catalog: Record<string, Array<{ id: string; name?: string }>> = {};\n\tfor (const [providerId, entries] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(entries))\n\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] must be an array` };\n\t\tconst models: Array<{ id: string; name?: string }> = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (typeof entry !== \"object\" || entry === null)\n\t\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] entries must be objects` };\n\t\t\tconst record = entry as Record<string, unknown>;\n\t\t\tif (typeof record.id !== \"string\" || record.id === \"\")\n\t\t\t\treturn {\n\t\t\t\t\tcatalog: {},\n\t\t\t\t\terror: `injected config imageModelCatalog[\"${providerId}\"] entries need a non-empty string id`,\n\t\t\t\t};\n\t\t\tmodels.push({ id: record.id, ...(typeof record.name === \"string\" ? { name: record.name } : {}) });\n\t\t}\n\t\tif (models.length > 0) catalog[providerId] = models;\n\t}\n\treturn { catalog };\n}\n\n/** 确定性排序键:name 优先(缺省回退 id),同键按 id;码点序,与宿主快照顺序无关。 */\nfunction compareCatalogModels(left: CatalogModelV1, right: CatalogModelV1): number {\n\tconst leftKey = left.name ?? left.id;\n\tconst rightKey = right.name ?? right.id;\n\tif (leftKey !== rightKey) return leftKey < rightKey ? -1 : 1;\n\treturn left.id < right.id ? -1 : left.id > right.id ? 1 : 0;\n}\n\ntype ImageTargetResolution =\n\t| { readonly ok: true; readonly provider: string; readonly model: string; readonly modelSourceNote?: string }\n\t| { readonly ok: false; readonly message: string };\n\nconst IMAGE_GENERATION_CONFIG_EXAMPLE =\n\t'Example settings.json: { \"capabilities\": { \"imageGeneration\": { \"provider\": \"myimg\", \"model\": \"gpt-image-1\" } } }';\n\n/**\n * 六层取值链(供应商能力槽位设计 §3.2):\n * 1. 工具参数 provider/model;\n * 2. settings 槽位(宿主注入的 config.imageGeneration,model 可省);\n * 3. env AGENT_FORGE_IMAGEGEN_PROVIDER/MODEL(存量兼容);\n * 4. 主供应商推断——会话当前 provider 在目录快照中登记过生图模型时取用;\n * 5. 唯一候选推断——全配置域恰好一个 provider 登记过生图模型(且非当前主)时取用;\n * 6. fail-closed——config_missing,多候选时列出候选清单。\n * model 在 1-3 层均未命中时按已解析 provider 的登记生图模型推断(唯一命中直接\n * 用;多命中确定性排序取第一个并注明来源)。\n */\nfunction resolveImageTarget(\n\tinput: Record<string, unknown>,\n\tslot: ImageGenerationSlotV1 | undefined,\n\tcatalog: ImageModelCatalogV1,\n\tsessionProvider: string | undefined,\n): ImageTargetResolution {\n\tconst paramProvider = boundedString(input.provider, 64);\n\tconst paramModel = boundedString(input.model, 128);\n\tconst envProvider = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\tconst envModel = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_MODEL\");\n\n\tlet provider = paramProvider ?? slot?.provider ?? envProvider;\n\tif (provider === undefined) {\n\t\tif (sessionProvider !== undefined && (catalog[sessionProvider]?.length ?? 0) > 0) provider = sessionProvider;\n\t\telse {\n\t\t\tconst candidates = Object.keys(catalog)\n\t\t\t\t.filter((providerId) => catalog[providerId].length > 0)\n\t\t\t\t.sort();\n\t\t\tif (candidates.length === 1) provider = candidates[0];\n\t\t\telse if (candidates.length > 1)\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Multiple image-generation providers are registered in models.json (${candidates.join(\", \")}); image generation fails closed instead of guessing. ` +\n\t\t\t\t\t\t`Configure capabilities.imageGeneration in settings.json or pass the provider parameter. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t\t};\n\t\t\telse\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters, set capabilities.imageGeneration in settings.json, \" +\n\t\t\t\t\t\t'set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL, or register an image model (\"output\": [\"image\"]) for a provider in models.json. ' +\n\t\t\t\t\t\tIMAGE_GENERATION_CONFIG_EXAMPLE,\n\t\t\t\t};\n\t\t}\n\t}\n\n\tlet model = paramModel ?? slot?.model ?? envModel;\n\tlet modelSourceNote: string | undefined;\n\tif (model === undefined) {\n\t\tconst registered = catalog[provider] ?? [];\n\t\tif (registered.length === 1) model = registered[0].id;\n\t\telse if (registered.length > 1) {\n\t\t\tconst first = [...registered].sort(compareCatalogModels)[0];\n\t\t\tmodel = first.id;\n\t\t\tmodelSourceNote =\n\t\t\t\t`model ${first.id} was picked deterministically (first by name) from ${registered.length} image models registered for provider ${provider} in models.json; ` +\n\t\t\t\t\"pass an explicit model to choose another\";\n\t\t} else\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tmessage:\n\t\t\t\t\t`No image model is configured for provider ${provider}: no model came from the tool parameters, settings, or env, ` +\n\t\t\t\t\t`and provider ${provider} has no models.json model with \"output\": [\"image\"]. Register one or pass an explicit model. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t};\n\t}\n\treturn { ok: true, provider, model, ...(modelSourceNote === undefined ? {} : { modelSourceNote }) };\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 ImageToolSuccessV1 {\n\treadonly content: readonly { readonly type: \"text\"; readonly text: string }[];\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\tmodelSourceNote?: string,\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\t// Zero inlining (user decision, 2026-09-30): image payloads never ride\n\t// the conversation — they would cost context on every later turn. The\n\t// tool result carries conclusions only (paths/model/usage); the model\n\t// reads a file on demand when it actually needs to look, which the host\n\t// read tool serves as an auto-resized image attachment.\n\tvoid images;\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...(modelSourceNote === undefined ? {} : { modelSource: modelSourceNote }),\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t\tnote: \"images are saved to disk only; read a file to view it\",\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],\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\tlet download: Response;\n\t\ttry {\n\t\t\tdownload = await fetchImpl(item.url, { signal: downloadSignal });\n\t\t} catch (error) {\n\t\t\t// Network-level download failures must name the host and the proxy\n\t\t\t// fix: a bare \"fetch failed\" filed under the *request* code sent the\n\t\t\t// seat diagnosis down the wrong path for an hour (the generation\n\t\t\t// itself was succeeding; the CDN was unreachable without a proxy).\n\t\t\tlet host = \"the provider cdn\";\n\t\t\ttry {\n\t\t\t\thost = new URL(item.url).host;\n\t\t\t} catch {}\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from ${host} failed (${error instanceof Error ? error.message : \"network error\"}); if that host is only reachable through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README`,\n\t\t\t};\n\t\t}\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; store the key via /login)`,\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);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\t// 宿主装配期注入(设计 §3.3):settings 槽位 + 生图模型目录快照经\n\t\t// configOverride 合并进 api.config。注入形状非法不在装载期崩溃——每次\n\t\t// 调用按 config_missing 可见失败(失败可见,不静默降级)。\n\t\tconst slotInjection = readInjectedSlot(api.config);\n\t\tconst catalogInjection = readInjectedCatalog(api.config);\n\t\tconst sessionProvider = (): string | undefined => api.session?.getModel?.()?.provider;\n\t\tconst resolveTarget = (input: Record<string, unknown>): ImageTargetResolution => {\n\t\t\tif (slotInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${slotInjection.error}` };\n\t\t\tif (catalogInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${catalogInjection.error}` };\n\t\t\treturn resolveImageTarget(input, slotInjection.slot, catalogInjection.catalog, sessionProvider());\n\t\t};\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 — images are never inlined into the conversation, read a returned file to view it\",\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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\tmodelSourceNote,\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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\tmodelSourceNote,\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,EAAgB,MAAM,yBAAyB,CAAC;AA8dxG;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,OAAO,CAAC,EAAE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,GAAG,2BAA2B,CA+K9B","sourcesContent":["import { mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport { join, sep } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory, PluginConfig } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { 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), a non-empty env value, or undefined. */\nfunction nonEmptyEnvValue(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\n/** settings 槽位形状(宿主经 configOverride 注入,设计 §3.1/§3.3)。 */\ninterface ImageGenerationSlotV1 {\n\treadonly provider: string;\n\treadonly model?: string;\n}\n\n/** 目录快照条目:宿主装配期从 models.json 登记项投影(设计 §3.2 推断供料)。 */\ninterface CatalogModelV1 {\n\treadonly id: string;\n\treadonly name?: string;\n}\n\ntype ImageModelCatalogV1 = Readonly<Record<string, ReadonlyArray<CatalogModelV1>>>;\n\nfunction readInjectedSlot(config: PluginConfig): { readonly slot?: ImageGenerationSlotV1; readonly error?: string } {\n\tconst raw = config.imageGeneration;\n\tif (raw === undefined) return {};\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn { error: \"injected config imageGeneration must be an object\" };\n\tconst record = raw as Record<string, unknown>;\n\tif (typeof record.provider !== \"string\" || record.provider.trim() === \"\")\n\t\treturn {\n\t\t\terror: `injected config imageGeneration.provider must be a non-empty string (got ${typeof record.provider})`,\n\t\t};\n\tif (record.model !== undefined && (typeof record.model !== \"string\" || record.model.trim() === \"\"))\n\t\treturn { error: \"injected config imageGeneration.model must be a non-empty string when present\" };\n\treturn { slot: { provider: record.provider, ...(record.model === undefined ? {} : { model: record.model }) } };\n}\n\nfunction readInjectedCatalog(config: PluginConfig): { readonly catalog: ImageModelCatalogV1; readonly error?: string } {\n\tconst raw = config.imageModelCatalog;\n\tif (raw === undefined) return { catalog: {} };\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn {\n\t\t\tcatalog: {},\n\t\t\terror: \"injected config imageModelCatalog must be an object mapping providers to image models\",\n\t\t};\n\tconst catalog: Record<string, Array<{ id: string; name?: string }>> = {};\n\tfor (const [providerId, entries] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(entries))\n\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] must be an array` };\n\t\tconst models: Array<{ id: string; name?: string }> = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (typeof entry !== \"object\" || entry === null)\n\t\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] entries must be objects` };\n\t\t\tconst record = entry as Record<string, unknown>;\n\t\t\tif (typeof record.id !== \"string\" || record.id === \"\")\n\t\t\t\treturn {\n\t\t\t\t\tcatalog: {},\n\t\t\t\t\terror: `injected config imageModelCatalog[\"${providerId}\"] entries need a non-empty string id`,\n\t\t\t\t};\n\t\t\tmodels.push({ id: record.id, ...(typeof record.name === \"string\" ? { name: record.name } : {}) });\n\t\t}\n\t\tif (models.length > 0) catalog[providerId] = models;\n\t}\n\treturn { catalog };\n}\n\n/** 确定性排序键:name 优先(缺省回退 id),同键按 id;码点序,与宿主快照顺序无关。 */\nfunction compareCatalogModels(left: CatalogModelV1, right: CatalogModelV1): number {\n\tconst leftKey = left.name ?? left.id;\n\tconst rightKey = right.name ?? right.id;\n\tif (leftKey !== rightKey) return leftKey < rightKey ? -1 : 1;\n\treturn left.id < right.id ? -1 : left.id > right.id ? 1 : 0;\n}\n\ntype ImageTargetResolution =\n\t| { readonly ok: true; readonly provider: string; readonly model: string; readonly modelSourceNote?: string }\n\t| { readonly ok: false; readonly message: string };\n\nconst IMAGE_GENERATION_CONFIG_EXAMPLE =\n\t'Example settings.json: { \"capabilities\": { \"imageGeneration\": { \"provider\": \"myimg\", \"model\": \"gpt-image-1\" } } }';\n\n/**\n * 六层取值链(供应商能力槽位设计 §3.2):\n * 1. 工具参数 provider/model;\n * 2. settings 槽位(宿主注入的 config.imageGeneration,model 可省);\n * 3. env AGENT_FORGE_IMAGEGEN_PROVIDER/MODEL(存量兼容);\n * 4. 主供应商推断——会话当前 provider 在目录快照中登记过生图模型时取用;\n * 5. 唯一候选推断——全配置域恰好一个 provider 登记过生图模型(且非当前主)时取用;\n * 6. fail-closed——config_missing,多候选时列出候选清单。\n * model 在 1-3 层均未命中时按已解析 provider 的登记生图模型推断(唯一命中直接\n * 用;多命中确定性排序取第一个并注明来源)。\n */\nfunction resolveImageTarget(\n\tinput: Record<string, unknown>,\n\tslot: ImageGenerationSlotV1 | undefined,\n\tcatalog: ImageModelCatalogV1,\n\tsessionProvider: string | undefined,\n): ImageTargetResolution {\n\tconst paramProvider = boundedString(input.provider, 64);\n\tconst paramModel = boundedString(input.model, 128);\n\tconst envProvider = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\tconst envModel = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_MODEL\");\n\n\tlet provider = paramProvider ?? slot?.provider ?? envProvider;\n\tif (provider === undefined) {\n\t\tif (sessionProvider !== undefined && (catalog[sessionProvider]?.length ?? 0) > 0) provider = sessionProvider;\n\t\telse {\n\t\t\tconst candidates = Object.keys(catalog)\n\t\t\t\t.filter((providerId) => catalog[providerId].length > 0)\n\t\t\t\t.sort();\n\t\t\tif (candidates.length === 1) provider = candidates[0];\n\t\t\telse if (candidates.length > 1)\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Multiple image-generation providers are registered in models.json (${candidates.join(\", \")}); image generation fails closed instead of guessing. ` +\n\t\t\t\t\t\t`Configure capabilities.imageGeneration in settings.json or pass the provider parameter. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t\t};\n\t\t\telse\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters, set capabilities.imageGeneration in settings.json, \" +\n\t\t\t\t\t\t'set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL, or register an image model (\"output\": [\"image\"]) for a provider in models.json. ' +\n\t\t\t\t\t\tIMAGE_GENERATION_CONFIG_EXAMPLE,\n\t\t\t\t};\n\t\t}\n\t}\n\n\tlet model = paramModel ?? slot?.model ?? envModel;\n\tlet modelSourceNote: string | undefined;\n\tif (model === undefined) {\n\t\tconst registered = catalog[provider] ?? [];\n\t\tif (registered.length === 1) model = registered[0].id;\n\t\telse if (registered.length > 1) {\n\t\t\tconst first = [...registered].sort(compareCatalogModels)[0];\n\t\t\tmodel = first.id;\n\t\t\tmodelSourceNote =\n\t\t\t\t`model ${first.id} was picked deterministically (first by name) from ${registered.length} image models registered for provider ${provider} in models.json; ` +\n\t\t\t\t\"pass an explicit model to choose another\";\n\t\t} else\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tmessage:\n\t\t\t\t\t`No image model is configured for provider ${provider}: no model came from the tool parameters, settings, or env, ` +\n\t\t\t\t\t`and provider ${provider} has no models.json model with \"output\": [\"image\"]. Register one or pass an explicit model. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t};\n\t}\n\treturn { ok: true, provider, model, ...(modelSourceNote === undefined ? {} : { modelSourceNote }) };\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 * image_edit ships workspace bytes to an external image provider, so the\n * lexical validImagePath gate is not enough: a symlink inside\n * generated-images/ can point anywhere on disk. Same sandbox model as the\n * workspace-files-write host: resolve the workspace root and the candidate to\n * their real paths and require the candidate to stay inside the real root.\n * A missing file (or dangling link) fails realpath and maps to the regular\n * not-found outcome; a link resolving outside the workspace is a boundary\n * escape and is refused. Links that stay inside the workspace keep working —\n * their bytes never leave the sandbox boundary.\n */\nasync function sandboxedImagePath(workspace: string, imagePath: string): Promise<\"missing\" | \"escape\" | \"ok\"> {\n\tconst root = await realpath(workspace).catch(() => undefined);\n\tif (root === undefined) return \"missing\";\n\tconst real = await realpath(join(workspace, imagePath)).catch(() => undefined);\n\tif (real === undefined) return \"missing\";\n\tif (real !== root && !real.startsWith(root + sep)) return \"escape\";\n\treturn \"ok\";\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 ImageToolSuccessV1 {\n\treadonly content: readonly { readonly type: \"text\"; readonly text: string }[];\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\tmodelSourceNote?: string,\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\t// Zero inlining (user decision, 2026-09-30): image payloads never ride\n\t// the conversation — they would cost context on every later turn. The\n\t// tool result carries conclusions only (paths/model/usage); the model\n\t// reads a file on demand when it actually needs to look, which the host\n\t// read tool serves as an auto-resized image attachment.\n\tvoid images;\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...(modelSourceNote === undefined ? {} : { modelSource: modelSourceNote }),\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t\tnote: \"images are saved to disk only; read a file to view it\",\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],\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\tlet download: Response;\n\t\ttry {\n\t\t\tdownload = await fetchImpl(item.url, { signal: downloadSignal });\n\t\t} catch (error) {\n\t\t\t// Network-level download failures must name the host and the proxy\n\t\t\t// fix: a bare \"fetch failed\" filed under the *request* code sent the\n\t\t\t// seat diagnosis down the wrong path for an hour (the generation\n\t\t\t// itself was succeeding; the CDN was unreachable without a proxy).\n\t\t\tlet host = \"the provider cdn\";\n\t\t\ttry {\n\t\t\t\thost = new URL(item.url).host;\n\t\t\t} catch {}\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from ${host} failed (${error instanceof Error ? error.message : \"network error\"}); if that host is only reachable through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README`,\n\t\t\t};\n\t\t}\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; store the key via /login)`,\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);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\t// 宿主装配期注入(设计 §3.3):settings 槽位 + 生图模型目录快照经\n\t\t// configOverride 合并进 api.config。注入形状非法不在装载期崩溃——每次\n\t\t// 调用按 config_missing 可见失败(失败可见,不静默降级)。\n\t\tconst slotInjection = readInjectedSlot(api.config);\n\t\tconst catalogInjection = readInjectedCatalog(api.config);\n\t\tconst sessionProvider = (): string | undefined => api.session?.getModel?.()?.provider;\n\t\tconst resolveTarget = (input: Record<string, unknown>): ImageTargetResolution => {\n\t\t\tif (slotInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${slotInjection.error}` };\n\t\t\tif (catalogInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${catalogInjection.error}` };\n\t\t\treturn resolveImageTarget(input, slotInjection.slot, catalogInjection.catalog, sessionProvider());\n\t\t};\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 — images are never inlined into the conversation, read a returned file to view it\",\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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\tmodelSourceNote,\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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// Defense in depth (readonly never registers these tools): the\n\t\t\t\t\t// lexical validImagePath gate cannot see symlinks, so refuse any\n\t\t\t\t\t// candidate whose real path escapes the workspace before a byte\n\t\t\t\t\t// is read and shipped to the provider.\n\t\t\t\t\tconst sandbox = await sandboxedImagePath(resolved.workspace, imagePath);\n\t\t\t\t\tif (sandbox === \"missing\")\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 (sandbox === \"escape\")\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\t`imagePath ${imagePath} resolves outside the workspace; only files under the workspace generated-images/ directory can be read`,\n\t\t\t\t\t\t);\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\tmodelSourceNote,\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/capability.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, sep } from "node:path";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import { wrapFetchWithEnvProxyV1 } from "./env-proxy-fetch.js";
|
|
5
5
|
import { createImagesClientV1 } from "./images-client.js";
|
|
@@ -198,6 +198,28 @@ function validImagePath(imagePath) {
|
|
|
198
198
|
return false;
|
|
199
199
|
return !imagePath.split("/").includes("..");
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* image_edit ships workspace bytes to an external image provider, so the
|
|
203
|
+
* lexical validImagePath gate is not enough: a symlink inside
|
|
204
|
+
* generated-images/ can point anywhere on disk. Same sandbox model as the
|
|
205
|
+
* workspace-files-write host: resolve the workspace root and the candidate to
|
|
206
|
+
* their real paths and require the candidate to stay inside the real root.
|
|
207
|
+
* A missing file (or dangling link) fails realpath and maps to the regular
|
|
208
|
+
* not-found outcome; a link resolving outside the workspace is a boundary
|
|
209
|
+
* escape and is refused. Links that stay inside the workspace keep working —
|
|
210
|
+
* their bytes never leave the sandbox boundary.
|
|
211
|
+
*/
|
|
212
|
+
async function sandboxedImagePath(workspace, imagePath) {
|
|
213
|
+
const root = await realpath(workspace).catch(() => undefined);
|
|
214
|
+
if (root === undefined)
|
|
215
|
+
return "missing";
|
|
216
|
+
const real = await realpath(join(workspace, imagePath)).catch(() => undefined);
|
|
217
|
+
if (real === undefined)
|
|
218
|
+
return "missing";
|
|
219
|
+
if (real !== root && !real.startsWith(root + sep))
|
|
220
|
+
return "escape";
|
|
221
|
+
return "ok";
|
|
222
|
+
}
|
|
201
223
|
/**
|
|
202
224
|
* Local-time `yyyymmdd-HHmmss` stamp shared by every file of one invocation.
|
|
203
225
|
* Local (not UTC): users find today's images by the date in the filename, and
|
|
@@ -460,6 +482,15 @@ export function createImageGenerationCapabilityFactoryV1(options) {
|
|
|
460
482
|
const resolved = await resolveInvocation(api, provider, fetchImpl, model);
|
|
461
483
|
if (resolved.status === "failed")
|
|
462
484
|
return resolved.result;
|
|
485
|
+
// Defense in depth (readonly never registers these tools): the
|
|
486
|
+
// lexical validImagePath gate cannot see symlinks, so refuse any
|
|
487
|
+
// candidate whose real path escapes the workspace before a byte
|
|
488
|
+
// is read and shipped to the provider.
|
|
489
|
+
const sandbox = await sandboxedImagePath(resolved.workspace, imagePath);
|
|
490
|
+
if (sandbox === "missing")
|
|
491
|
+
return failed(model, "image_not_found", `image ${imagePath} was not found under the workspace generated-images/ directory`);
|
|
492
|
+
if (sandbox === "escape")
|
|
493
|
+
return failed(model, "invalid_path", `imagePath ${imagePath} resolves outside the workspace; only files under the workspace generated-images/ directory can be read`);
|
|
463
494
|
// Size-check before reading: users can drop their own files
|
|
464
495
|
// into generated-images/, and the 20 MiB download cap must
|
|
465
496
|
// not be bypassed by a renamed local file.
|
package/dist/capability.js.map
CHANGED
|
@@ -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, PluginConfig } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { 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), a non-empty env value, or undefined. */\nfunction nonEmptyEnvValue(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\n/** settings 槽位形状(宿主经 configOverride 注入,设计 §3.1/§3.3)。 */\ninterface ImageGenerationSlotV1 {\n\treadonly provider: string;\n\treadonly model?: string;\n}\n\n/** 目录快照条目:宿主装配期从 models.json 登记项投影(设计 §3.2 推断供料)。 */\ninterface CatalogModelV1 {\n\treadonly id: string;\n\treadonly name?: string;\n}\n\ntype ImageModelCatalogV1 = Readonly<Record<string, ReadonlyArray<CatalogModelV1>>>;\n\nfunction readInjectedSlot(config: PluginConfig): { readonly slot?: ImageGenerationSlotV1; readonly error?: string } {\n\tconst raw = config.imageGeneration;\n\tif (raw === undefined) return {};\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn { error: \"injected config imageGeneration must be an object\" };\n\tconst record = raw as Record<string, unknown>;\n\tif (typeof record.provider !== \"string\" || record.provider.trim() === \"\")\n\t\treturn {\n\t\t\terror: `injected config imageGeneration.provider must be a non-empty string (got ${typeof record.provider})`,\n\t\t};\n\tif (record.model !== undefined && (typeof record.model !== \"string\" || record.model.trim() === \"\"))\n\t\treturn { error: \"injected config imageGeneration.model must be a non-empty string when present\" };\n\treturn { slot: { provider: record.provider, ...(record.model === undefined ? {} : { model: record.model }) } };\n}\n\nfunction readInjectedCatalog(config: PluginConfig): { readonly catalog: ImageModelCatalogV1; readonly error?: string } {\n\tconst raw = config.imageModelCatalog;\n\tif (raw === undefined) return { catalog: {} };\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn {\n\t\t\tcatalog: {},\n\t\t\terror: \"injected config imageModelCatalog must be an object mapping providers to image models\",\n\t\t};\n\tconst catalog: Record<string, Array<{ id: string; name?: string }>> = {};\n\tfor (const [providerId, entries] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(entries))\n\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] must be an array` };\n\t\tconst models: Array<{ id: string; name?: string }> = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (typeof entry !== \"object\" || entry === null)\n\t\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] entries must be objects` };\n\t\t\tconst record = entry as Record<string, unknown>;\n\t\t\tif (typeof record.id !== \"string\" || record.id === \"\")\n\t\t\t\treturn {\n\t\t\t\t\tcatalog: {},\n\t\t\t\t\terror: `injected config imageModelCatalog[\"${providerId}\"] entries need a non-empty string id`,\n\t\t\t\t};\n\t\t\tmodels.push({ id: record.id, ...(typeof record.name === \"string\" ? { name: record.name } : {}) });\n\t\t}\n\t\tif (models.length > 0) catalog[providerId] = models;\n\t}\n\treturn { catalog };\n}\n\n/** 确定性排序键:name 优先(缺省回退 id),同键按 id;码点序,与宿主快照顺序无关。 */\nfunction compareCatalogModels(left: CatalogModelV1, right: CatalogModelV1): number {\n\tconst leftKey = left.name ?? left.id;\n\tconst rightKey = right.name ?? right.id;\n\tif (leftKey !== rightKey) return leftKey < rightKey ? -1 : 1;\n\treturn left.id < right.id ? -1 : left.id > right.id ? 1 : 0;\n}\n\ntype ImageTargetResolution =\n\t| { readonly ok: true; readonly provider: string; readonly model: string; readonly modelSourceNote?: string }\n\t| { readonly ok: false; readonly message: string };\n\nconst IMAGE_GENERATION_CONFIG_EXAMPLE =\n\t'Example settings.json: { \"capabilities\": { \"imageGeneration\": { \"provider\": \"myimg\", \"model\": \"gpt-image-1\" } } }';\n\n/**\n * 六层取值链(供应商能力槽位设计 §3.2):\n * 1. 工具参数 provider/model;\n * 2. settings 槽位(宿主注入的 config.imageGeneration,model 可省);\n * 3. env AGENT_FORGE_IMAGEGEN_PROVIDER/MODEL(存量兼容);\n * 4. 主供应商推断——会话当前 provider 在目录快照中登记过生图模型时取用;\n * 5. 唯一候选推断——全配置域恰好一个 provider 登记过生图模型(且非当前主)时取用;\n * 6. fail-closed——config_missing,多候选时列出候选清单。\n * model 在 1-3 层均未命中时按已解析 provider 的登记生图模型推断(唯一命中直接\n * 用;多命中确定性排序取第一个并注明来源)。\n */\nfunction resolveImageTarget(\n\tinput: Record<string, unknown>,\n\tslot: ImageGenerationSlotV1 | undefined,\n\tcatalog: ImageModelCatalogV1,\n\tsessionProvider: string | undefined,\n): ImageTargetResolution {\n\tconst paramProvider = boundedString(input.provider, 64);\n\tconst paramModel = boundedString(input.model, 128);\n\tconst envProvider = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\tconst envModel = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_MODEL\");\n\n\tlet provider = paramProvider ?? slot?.provider ?? envProvider;\n\tif (provider === undefined) {\n\t\tif (sessionProvider !== undefined && (catalog[sessionProvider]?.length ?? 0) > 0) provider = sessionProvider;\n\t\telse {\n\t\t\tconst candidates = Object.keys(catalog)\n\t\t\t\t.filter((providerId) => catalog[providerId].length > 0)\n\t\t\t\t.sort();\n\t\t\tif (candidates.length === 1) provider = candidates[0];\n\t\t\telse if (candidates.length > 1)\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Multiple image-generation providers are registered in models.json (${candidates.join(\", \")}); image generation fails closed instead of guessing. ` +\n\t\t\t\t\t\t`Configure capabilities.imageGeneration in settings.json or pass the provider parameter. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t\t};\n\t\t\telse\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters, set capabilities.imageGeneration in settings.json, \" +\n\t\t\t\t\t\t'set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL, or register an image model (\"output\": [\"image\"]) for a provider in models.json. ' +\n\t\t\t\t\t\tIMAGE_GENERATION_CONFIG_EXAMPLE,\n\t\t\t\t};\n\t\t}\n\t}\n\n\tlet model = paramModel ?? slot?.model ?? envModel;\n\tlet modelSourceNote: string | undefined;\n\tif (model === undefined) {\n\t\tconst registered = catalog[provider] ?? [];\n\t\tif (registered.length === 1) model = registered[0].id;\n\t\telse if (registered.length > 1) {\n\t\t\tconst first = [...registered].sort(compareCatalogModels)[0];\n\t\t\tmodel = first.id;\n\t\t\tmodelSourceNote =\n\t\t\t\t`model ${first.id} was picked deterministically (first by name) from ${registered.length} image models registered for provider ${provider} in models.json; ` +\n\t\t\t\t\"pass an explicit model to choose another\";\n\t\t} else\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tmessage:\n\t\t\t\t\t`No image model is configured for provider ${provider}: no model came from the tool parameters, settings, or env, ` +\n\t\t\t\t\t`and provider ${provider} has no models.json model with \"output\": [\"image\"]. Register one or pass an explicit model. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t};\n\t}\n\treturn { ok: true, provider, model, ...(modelSourceNote === undefined ? {} : { modelSourceNote }) };\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 ImageToolSuccessV1 {\n\treadonly content: readonly { readonly type: \"text\"; readonly text: string }[];\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\tmodelSourceNote?: string,\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\t// Zero inlining (user decision, 2026-09-30): image payloads never ride\n\t// the conversation — they would cost context on every later turn. The\n\t// tool result carries conclusions only (paths/model/usage); the model\n\t// reads a file on demand when it actually needs to look, which the host\n\t// read tool serves as an auto-resized image attachment.\n\tvoid images;\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...(modelSourceNote === undefined ? {} : { modelSource: modelSourceNote }),\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t\tnote: \"images are saved to disk only; read a file to view it\",\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],\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\tlet download: Response;\n\t\ttry {\n\t\t\tdownload = await fetchImpl(item.url, { signal: downloadSignal });\n\t\t} catch (error) {\n\t\t\t// Network-level download failures must name the host and the proxy\n\t\t\t// fix: a bare \"fetch failed\" filed under the *request* code sent the\n\t\t\t// seat diagnosis down the wrong path for an hour (the generation\n\t\t\t// itself was succeeding; the CDN was unreachable without a proxy).\n\t\t\tlet host = \"the provider cdn\";\n\t\t\ttry {\n\t\t\t\thost = new URL(item.url).host;\n\t\t\t} catch {}\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from ${host} failed (${error instanceof Error ? error.message : \"network error\"}); if that host is only reachable through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README`,\n\t\t\t};\n\t\t}\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; store the key via /login)`,\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);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\t// 宿主装配期注入(设计 §3.3):settings 槽位 + 生图模型目录快照经\n\t\t// configOverride 合并进 api.config。注入形状非法不在装载期崩溃——每次\n\t\t// 调用按 config_missing 可见失败(失败可见,不静默降级)。\n\t\tconst slotInjection = readInjectedSlot(api.config);\n\t\tconst catalogInjection = readInjectedCatalog(api.config);\n\t\tconst sessionProvider = (): string | undefined => api.session?.getModel?.()?.provider;\n\t\tconst resolveTarget = (input: Record<string, unknown>): ImageTargetResolution => {\n\t\t\tif (slotInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${slotInjection.error}` };\n\t\t\tif (catalogInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${catalogInjection.error}` };\n\t\t\treturn resolveImageTarget(input, slotInjection.slot, catalogInjection.catalog, sessionProvider());\n\t\t};\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 — images are never inlined into the conversation, read a returned file to view it\",\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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\tmodelSourceNote,\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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\tmodelSourceNote,\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":"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","sourcesContent":["import { mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport { join, sep } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory, PluginConfig } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { 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), a non-empty env value, or undefined. */\nfunction nonEmptyEnvValue(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\n/** settings 槽位形状(宿主经 configOverride 注入,设计 §3.1/§3.3)。 */\ninterface ImageGenerationSlotV1 {\n\treadonly provider: string;\n\treadonly model?: string;\n}\n\n/** 目录快照条目:宿主装配期从 models.json 登记项投影(设计 §3.2 推断供料)。 */\ninterface CatalogModelV1 {\n\treadonly id: string;\n\treadonly name?: string;\n}\n\ntype ImageModelCatalogV1 = Readonly<Record<string, ReadonlyArray<CatalogModelV1>>>;\n\nfunction readInjectedSlot(config: PluginConfig): { readonly slot?: ImageGenerationSlotV1; readonly error?: string } {\n\tconst raw = config.imageGeneration;\n\tif (raw === undefined) return {};\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn { error: \"injected config imageGeneration must be an object\" };\n\tconst record = raw as Record<string, unknown>;\n\tif (typeof record.provider !== \"string\" || record.provider.trim() === \"\")\n\t\treturn {\n\t\t\terror: `injected config imageGeneration.provider must be a non-empty string (got ${typeof record.provider})`,\n\t\t};\n\tif (record.model !== undefined && (typeof record.model !== \"string\" || record.model.trim() === \"\"))\n\t\treturn { error: \"injected config imageGeneration.model must be a non-empty string when present\" };\n\treturn { slot: { provider: record.provider, ...(record.model === undefined ? {} : { model: record.model }) } };\n}\n\nfunction readInjectedCatalog(config: PluginConfig): { readonly catalog: ImageModelCatalogV1; readonly error?: string } {\n\tconst raw = config.imageModelCatalog;\n\tif (raw === undefined) return { catalog: {} };\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw))\n\t\treturn {\n\t\t\tcatalog: {},\n\t\t\terror: \"injected config imageModelCatalog must be an object mapping providers to image models\",\n\t\t};\n\tconst catalog: Record<string, Array<{ id: string; name?: string }>> = {};\n\tfor (const [providerId, entries] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(entries))\n\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] must be an array` };\n\t\tconst models: Array<{ id: string; name?: string }> = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (typeof entry !== \"object\" || entry === null)\n\t\t\t\treturn { catalog: {}, error: `injected config imageModelCatalog[\"${providerId}\"] entries must be objects` };\n\t\t\tconst record = entry as Record<string, unknown>;\n\t\t\tif (typeof record.id !== \"string\" || record.id === \"\")\n\t\t\t\treturn {\n\t\t\t\t\tcatalog: {},\n\t\t\t\t\terror: `injected config imageModelCatalog[\"${providerId}\"] entries need a non-empty string id`,\n\t\t\t\t};\n\t\t\tmodels.push({ id: record.id, ...(typeof record.name === \"string\" ? { name: record.name } : {}) });\n\t\t}\n\t\tif (models.length > 0) catalog[providerId] = models;\n\t}\n\treturn { catalog };\n}\n\n/** 确定性排序键:name 优先(缺省回退 id),同键按 id;码点序,与宿主快照顺序无关。 */\nfunction compareCatalogModels(left: CatalogModelV1, right: CatalogModelV1): number {\n\tconst leftKey = left.name ?? left.id;\n\tconst rightKey = right.name ?? right.id;\n\tif (leftKey !== rightKey) return leftKey < rightKey ? -1 : 1;\n\treturn left.id < right.id ? -1 : left.id > right.id ? 1 : 0;\n}\n\ntype ImageTargetResolution =\n\t| { readonly ok: true; readonly provider: string; readonly model: string; readonly modelSourceNote?: string }\n\t| { readonly ok: false; readonly message: string };\n\nconst IMAGE_GENERATION_CONFIG_EXAMPLE =\n\t'Example settings.json: { \"capabilities\": { \"imageGeneration\": { \"provider\": \"myimg\", \"model\": \"gpt-image-1\" } } }';\n\n/**\n * 六层取值链(供应商能力槽位设计 §3.2):\n * 1. 工具参数 provider/model;\n * 2. settings 槽位(宿主注入的 config.imageGeneration,model 可省);\n * 3. env AGENT_FORGE_IMAGEGEN_PROVIDER/MODEL(存量兼容);\n * 4. 主供应商推断——会话当前 provider 在目录快照中登记过生图模型时取用;\n * 5. 唯一候选推断——全配置域恰好一个 provider 登记过生图模型(且非当前主)时取用;\n * 6. fail-closed——config_missing,多候选时列出候选清单。\n * model 在 1-3 层均未命中时按已解析 provider 的登记生图模型推断(唯一命中直接\n * 用;多命中确定性排序取第一个并注明来源)。\n */\nfunction resolveImageTarget(\n\tinput: Record<string, unknown>,\n\tslot: ImageGenerationSlotV1 | undefined,\n\tcatalog: ImageModelCatalogV1,\n\tsessionProvider: string | undefined,\n): ImageTargetResolution {\n\tconst paramProvider = boundedString(input.provider, 64);\n\tconst paramModel = boundedString(input.model, 128);\n\tconst envProvider = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\tconst envModel = nonEmptyEnvValue(\"AGENT_FORGE_IMAGEGEN_MODEL\");\n\n\tlet provider = paramProvider ?? slot?.provider ?? envProvider;\n\tif (provider === undefined) {\n\t\tif (sessionProvider !== undefined && (catalog[sessionProvider]?.length ?? 0) > 0) provider = sessionProvider;\n\t\telse {\n\t\t\tconst candidates = Object.keys(catalog)\n\t\t\t\t.filter((providerId) => catalog[providerId].length > 0)\n\t\t\t\t.sort();\n\t\t\tif (candidates.length === 1) provider = candidates[0];\n\t\t\telse if (candidates.length > 1)\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Multiple image-generation providers are registered in models.json (${candidates.join(\", \")}); image generation fails closed instead of guessing. ` +\n\t\t\t\t\t\t`Configure capabilities.imageGeneration in settings.json or pass the provider parameter. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t\t};\n\t\t\telse\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters, set capabilities.imageGeneration in settings.json, \" +\n\t\t\t\t\t\t'set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL, or register an image model (\"output\": [\"image\"]) for a provider in models.json. ' +\n\t\t\t\t\t\tIMAGE_GENERATION_CONFIG_EXAMPLE,\n\t\t\t\t};\n\t\t}\n\t}\n\n\tlet model = paramModel ?? slot?.model ?? envModel;\n\tlet modelSourceNote: string | undefined;\n\tif (model === undefined) {\n\t\tconst registered = catalog[provider] ?? [];\n\t\tif (registered.length === 1) model = registered[0].id;\n\t\telse if (registered.length > 1) {\n\t\t\tconst first = [...registered].sort(compareCatalogModels)[0];\n\t\t\tmodel = first.id;\n\t\t\tmodelSourceNote =\n\t\t\t\t`model ${first.id} was picked deterministically (first by name) from ${registered.length} image models registered for provider ${provider} in models.json; ` +\n\t\t\t\t\"pass an explicit model to choose another\";\n\t\t} else\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tmessage:\n\t\t\t\t\t`No image model is configured for provider ${provider}: no model came from the tool parameters, settings, or env, ` +\n\t\t\t\t\t`and provider ${provider} has no models.json model with \"output\": [\"image\"]. Register one or pass an explicit model. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,\n\t\t\t};\n\t}\n\treturn { ok: true, provider, model, ...(modelSourceNote === undefined ? {} : { modelSourceNote }) };\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 * image_edit ships workspace bytes to an external image provider, so the\n * lexical validImagePath gate is not enough: a symlink inside\n * generated-images/ can point anywhere on disk. Same sandbox model as the\n * workspace-files-write host: resolve the workspace root and the candidate to\n * their real paths and require the candidate to stay inside the real root.\n * A missing file (or dangling link) fails realpath and maps to the regular\n * not-found outcome; a link resolving outside the workspace is a boundary\n * escape and is refused. Links that stay inside the workspace keep working —\n * their bytes never leave the sandbox boundary.\n */\nasync function sandboxedImagePath(workspace: string, imagePath: string): Promise<\"missing\" | \"escape\" | \"ok\"> {\n\tconst root = await realpath(workspace).catch(() => undefined);\n\tif (root === undefined) return \"missing\";\n\tconst real = await realpath(join(workspace, imagePath)).catch(() => undefined);\n\tif (real === undefined) return \"missing\";\n\tif (real !== root && !real.startsWith(root + sep)) return \"escape\";\n\treturn \"ok\";\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 ImageToolSuccessV1 {\n\treadonly content: readonly { readonly type: \"text\"; readonly text: string }[];\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\tmodelSourceNote?: string,\n): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\t// Zero inlining (user decision, 2026-09-30): image payloads never ride\n\t// the conversation — they would cost context on every later turn. The\n\t// tool result carries conclusions only (paths/model/usage); the model\n\t// reads a file on demand when it actually needs to look, which the host\n\t// read tool serves as an auto-resized image attachment.\n\tvoid images;\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...(modelSourceNote === undefined ? {} : { modelSource: modelSourceNote }),\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t\tnote: \"images are saved to disk only; read a file to view it\",\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],\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\tlet download: Response;\n\t\ttry {\n\t\t\tdownload = await fetchImpl(item.url, { signal: downloadSignal });\n\t\t} catch (error) {\n\t\t\t// Network-level download failures must name the host and the proxy\n\t\t\t// fix: a bare \"fetch failed\" filed under the *request* code sent the\n\t\t\t// seat diagnosis down the wrong path for an hour (the generation\n\t\t\t// itself was succeeding; the CDN was unreachable without a proxy).\n\t\t\tlet host = \"the provider cdn\";\n\t\t\ttry {\n\t\t\t\thost = new URL(item.url).host;\n\t\t\t} catch {}\n\t\t\treturn {\n\t\t\t\tcode: \"download_failed\",\n\t\t\t\tmessage: `image download from ${host} failed (${error instanceof Error ? error.message : \"network error\"}); if that host is only reachable through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README`,\n\t\t\t};\n\t\t}\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; store the key via /login)`,\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);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\t// 宿主装配期注入(设计 §3.3):settings 槽位 + 生图模型目录快照经\n\t\t// configOverride 合并进 api.config。注入形状非法不在装载期崩溃——每次\n\t\t// 调用按 config_missing 可见失败(失败可见,不静默降级)。\n\t\tconst slotInjection = readInjectedSlot(api.config);\n\t\tconst catalogInjection = readInjectedCatalog(api.config);\n\t\tconst sessionProvider = (): string | undefined => api.session?.getModel?.()?.provider;\n\t\tconst resolveTarget = (input: Record<string, unknown>): ImageTargetResolution => {\n\t\t\tif (slotInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${slotInjection.error}` };\n\t\t\tif (catalogInjection.error !== undefined)\n\t\t\t\treturn { ok: false, message: `Invalid injected image-generation config: ${catalogInjection.error}` };\n\t\t\treturn resolveImageTarget(input, slotInjection.slot, catalogInjection.catalog, sessionProvider());\n\t\t};\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 — images are never inlined into the conversation, read a returned file to view it\",\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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\tmodelSourceNote,\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 target = resolveTarget(input);\n\t\t\t\tif (!target.ok) return failed(\"\", \"config_missing\", boundedImageMessageV1(target.message));\n\t\t\t\tconst { provider, model, modelSourceNote } = target;\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// Defense in depth (readonly never registers these tools): the\n\t\t\t\t\t// lexical validImagePath gate cannot see symlinks, so refuse any\n\t\t\t\t\t// candidate whose real path escapes the workspace before a byte\n\t\t\t\t\t// is read and shipped to the provider.\n\t\t\t\t\tconst sandbox = await sandboxedImagePath(resolved.workspace, imagePath);\n\t\t\t\t\tif (sandbox === \"missing\")\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 (sandbox === \"escape\")\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\t`imagePath ${imagePath} resolves outside the workspace; only files under the workspace generated-images/ directory can be read`,\n\t\t\t\t\t\t);\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\tmodelSourceNote,\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-agent-forge/plugin-image-generation",
|
|
3
|
-
"version": "0.88.
|
|
3
|
+
"version": "0.88.6",
|
|
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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"typebox": "1.3.27",
|
|
49
49
|
"undici": "8.9.0",
|
|
50
|
-
"@ai-agent-forge/plugin-sdk": "^0.88.
|
|
50
|
+
"@ai-agent-forge/plugin-sdk": "^0.88.6"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"shx": "0.4.0",
|