@ai-agent-forge/plugin-image-generation 0.86.4 → 0.86.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/README.md +65 -6
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +146 -17
- package/dist/capability.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,19 +7,78 @@ host-configured provider's OpenAI-compatible images API:
|
|
|
7
7
|
- Credentials (`apiKey` + `baseUrl`) are resolved at runtime through the public
|
|
8
8
|
`CredentialAPI` (`api.credentials.resolve({ providerId })`); the plugin keeps
|
|
9
9
|
no credentials of its own.
|
|
10
|
-
- `provider`/`model`
|
|
11
|
-
`
|
|
12
|
-
|
|
10
|
+
- `provider`/`model` are resolved through the capability-slot chain (设计
|
|
11
|
+
`供应商能力槽位与主备切换设计.md` §3.2, 优先级从高到低):
|
|
12
|
+
1. **tool parameters** — explicit `provider`/`model` on the call;
|
|
13
|
+
2. **settings slot** — `capabilities.imageGeneration` in `settings.json`
|
|
14
|
+
(host-injected; `model` may be omitted: the provider's registered image
|
|
15
|
+
model is used — a single hit applies directly, multiple hits pick the
|
|
16
|
+
deterministic first by name and the result notes the source);
|
|
17
|
+
3. **env** — `AGENT_FORGE_IMAGEGEN_PROVIDER` / `AGENT_FORGE_IMAGEGEN_MODEL`
|
|
18
|
+
(legacy compatibility, below the settings slot);
|
|
19
|
+
4. **primary-provider inference** — when the session's current provider has
|
|
20
|
+
an image model registered in `models.json` (`"output": ["image"]`), it is
|
|
21
|
+
used automatically (follows mid-session model switches);
|
|
22
|
+
5. **single-candidate inference** — when exactly one provider registers
|
|
23
|
+
image models in `models.json`, it is used with zero settings;
|
|
24
|
+
6. **fail-closed** — `config_missing` with a configuration example;
|
|
25
|
+
multiple candidates are listed instead of guessed.
|
|
13
26
|
- Generated and edited image files are written under the workspace
|
|
14
27
|
`generated-images/` directory, named and typed by their actual format
|
|
15
28
|
(magic-byte sniff: png/jpg/webp/gif; unknown falls back to png); the tool
|
|
16
|
-
result returns their workspace-relative paths
|
|
17
|
-
|
|
18
|
-
review and revise.
|
|
29
|
+
result returns their workspace-relative paths only (see "Images never ride
|
|
30
|
+
the conversation" below) — the model reads a returned file to view it.
|
|
19
31
|
- Cancellation cascades through `api.sessionAbortSignal()` and the invocation
|
|
20
32
|
signal; requests time out after 180s and error messages never echo the API
|
|
21
33
|
key or request headers.
|
|
22
34
|
|
|
35
|
+
## Configuring an image provider
|
|
36
|
+
|
|
37
|
+
`settings.json` (host capability slot):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"capabilities": {
|
|
42
|
+
"imageGeneration": { "provider": "myimg", "model": "gpt-image-1" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`models.json` registers the provider and its image model (the `output` field
|
|
48
|
+
marks image-capable models; `cost` requires all four rate keys):
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"providers": {
|
|
53
|
+
"myimg": {
|
|
54
|
+
"baseUrl": "https://img.example.com/v1",
|
|
55
|
+
"apiKey": "sk-...",
|
|
56
|
+
"api": "openai-completions",
|
|
57
|
+
"models": [
|
|
58
|
+
{
|
|
59
|
+
"id": "gpt-image-1",
|
|
60
|
+
"reasoning": false,
|
|
61
|
+
"input": ["text"],
|
|
62
|
+
"output": ["image"],
|
|
63
|
+
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
|
|
64
|
+
"contextWindow": 8192,
|
|
65
|
+
"maxTokens": 4096
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
With only the `models.json` registration (no `capabilities` settings), the
|
|
74
|
+
plugin auto-uses the single registered image provider (inference layers 4/5).
|
|
75
|
+
Registering an image model in `models.json` implies that provider really has
|
|
76
|
+
an `/images/generations` endpoint; builtin chat catalog models never take part
|
|
77
|
+
in the inference. The slot travels to the plugin through the host's
|
|
78
|
+
assembly-time `configOverride` injection (`api.config.imageGeneration` +
|
|
79
|
+
`api.config.imageModelCatalog` snapshot) — the plugin never reads host
|
|
80
|
+
settings files itself.
|
|
81
|
+
|
|
23
82
|
## Proxy support
|
|
24
83
|
|
|
25
84
|
Every outbound request (API call and provider-CDN image download) honors the
|
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,EAAE,MAAM,yBAAyB,CAAC;AA0T1F;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,OAAO,CAAC,EAAE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,GAAG,2BAA2B,CAyJ9B","sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { wrapFetchWithEnvProxyV1 } from \"./env-proxy-fetch.ts\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tdetectImageFormatV1,\n\tIMAGE_GENERATION_DOWNLOAD_TIMEOUT_MS_V1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageFormatV1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/**\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): 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...(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/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"image_generate\",\n\t\t\tlabel: \"Image generate\",\n\t\t\tdescription:\n\t\t\t\t\"Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves image files (png/jpg/webp/gif as produced) under generated-images/ and returns their workspace-relative paths — 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 provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\t// Size-check before reading: users can drop their own files\n\t\t\t\t\t// into generated-images/, and the 20 MiB download cap must\n\t\t\t\t\t// not be bypassed by a renamed local file.\n\t\t\t\t\tconst sourceStats = await stat(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (sourceStats === undefined || !sourceStats.isFile())\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (sourceStats.size > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"response_too_large\",\n\t\t\t\t\t\t\t`image ${imagePath} is ${sourceStats.size} bytes, above the 20 MiB limit`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (source === undefined)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst response = await resolved.client.editImage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\timage: source,\n\t\t\t\t\t\t\t// The edits endpoint accepts png/jpeg/webp inputs; the\n\t\t\t\t\t\t\t// multipart blob must carry the source's real format\n\t\t\t\t\t\t\t// (a prior url-form download may be JPEG).\n\t\t\t\t\t\t\tmimeType: detectImageFormatV1(source).mimeType,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image edit request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n\nfunction failureFrom(\n\terror: unknown,\n\tsignal: AbortSignal | undefined,\n\tmodel: string,\n\tfallbackMessage: string,\n): ImageGenResultV1 {\n\tif (signal?.aborted) return cancelled(model);\n\tlet message = error instanceof Error ? error.message : fallbackMessage;\n\t// Append (never replace) a proxy hint for opaque network failures: the\n\t// single most common cause on machines that route out through a VPN.\n\tif (/fetch failed|terminated|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|UND_ERR/u.test(message))\n\t\tmessage = `${message} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`;\n\treturn failed(model, \"image_request_failed\", boundedImageMessageV1(message));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,2BAA2B,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/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl);\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"]}
|
package/dist/capability.js
CHANGED
|
@@ -59,16 +59,131 @@ function cancelled(model) {
|
|
|
59
59
|
error: { code: "cancelled", message: "Image generation was cancelled" },
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
/** `param` (non-empty string)
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const fromEnv = process.env[envName];
|
|
67
|
-
return typeof fromEnv === "string" && fromEnv.trim() !== "" ? fromEnv : undefined;
|
|
62
|
+
/** `param` (non-empty string), a non-empty env value, or undefined. */
|
|
63
|
+
function nonEmptyEnvValue(name) {
|
|
64
|
+
const value = process.env[name];
|
|
65
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
68
66
|
}
|
|
69
67
|
function boundedString(value, maxLength) {
|
|
70
68
|
return typeof value === "string" && value !== "" && value.length <= maxLength ? value : undefined;
|
|
71
69
|
}
|
|
70
|
+
function readInjectedSlot(config) {
|
|
71
|
+
const raw = config.imageGeneration;
|
|
72
|
+
if (raw === undefined)
|
|
73
|
+
return {};
|
|
74
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
75
|
+
return { error: "injected config imageGeneration must be an object" };
|
|
76
|
+
const record = raw;
|
|
77
|
+
if (typeof record.provider !== "string" || record.provider.trim() === "")
|
|
78
|
+
return {
|
|
79
|
+
error: `injected config imageGeneration.provider must be a non-empty string (got ${typeof record.provider})`,
|
|
80
|
+
};
|
|
81
|
+
if (record.model !== undefined && (typeof record.model !== "string" || record.model.trim() === ""))
|
|
82
|
+
return { error: "injected config imageGeneration.model must be a non-empty string when present" };
|
|
83
|
+
return { slot: { provider: record.provider, ...(record.model === undefined ? {} : { model: record.model }) } };
|
|
84
|
+
}
|
|
85
|
+
function readInjectedCatalog(config) {
|
|
86
|
+
const raw = config.imageModelCatalog;
|
|
87
|
+
if (raw === undefined)
|
|
88
|
+
return { catalog: {} };
|
|
89
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
90
|
+
return {
|
|
91
|
+
catalog: {},
|
|
92
|
+
error: "injected config imageModelCatalog must be an object mapping providers to image models",
|
|
93
|
+
};
|
|
94
|
+
const catalog = {};
|
|
95
|
+
for (const [providerId, entries] of Object.entries(raw)) {
|
|
96
|
+
if (!Array.isArray(entries))
|
|
97
|
+
return { catalog: {}, error: `injected config imageModelCatalog["${providerId}"] must be an array` };
|
|
98
|
+
const models = [];
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (typeof entry !== "object" || entry === null)
|
|
101
|
+
return { catalog: {}, error: `injected config imageModelCatalog["${providerId}"] entries must be objects` };
|
|
102
|
+
const record = entry;
|
|
103
|
+
if (typeof record.id !== "string" || record.id === "")
|
|
104
|
+
return {
|
|
105
|
+
catalog: {},
|
|
106
|
+
error: `injected config imageModelCatalog["${providerId}"] entries need a non-empty string id`,
|
|
107
|
+
};
|
|
108
|
+
models.push({ id: record.id, ...(typeof record.name === "string" ? { name: record.name } : {}) });
|
|
109
|
+
}
|
|
110
|
+
if (models.length > 0)
|
|
111
|
+
catalog[providerId] = models;
|
|
112
|
+
}
|
|
113
|
+
return { catalog };
|
|
114
|
+
}
|
|
115
|
+
/** 确定性排序键:name 优先(缺省回退 id),同键按 id;码点序,与宿主快照顺序无关。 */
|
|
116
|
+
function compareCatalogModels(left, right) {
|
|
117
|
+
const leftKey = left.name ?? left.id;
|
|
118
|
+
const rightKey = right.name ?? right.id;
|
|
119
|
+
if (leftKey !== rightKey)
|
|
120
|
+
return leftKey < rightKey ? -1 : 1;
|
|
121
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
122
|
+
}
|
|
123
|
+
const IMAGE_GENERATION_CONFIG_EXAMPLE = 'Example settings.json: { "capabilities": { "imageGeneration": { "provider": "myimg", "model": "gpt-image-1" } } }';
|
|
124
|
+
/**
|
|
125
|
+
* 六层取值链(供应商能力槽位设计 §3.2):
|
|
126
|
+
* 1. 工具参数 provider/model;
|
|
127
|
+
* 2. settings 槽位(宿主注入的 config.imageGeneration,model 可省);
|
|
128
|
+
* 3. env AGENT_FORGE_IMAGEGEN_PROVIDER/MODEL(存量兼容);
|
|
129
|
+
* 4. 主供应商推断——会话当前 provider 在目录快照中登记过生图模型时取用;
|
|
130
|
+
* 5. 唯一候选推断——全配置域恰好一个 provider 登记过生图模型(且非当前主)时取用;
|
|
131
|
+
* 6. fail-closed——config_missing,多候选时列出候选清单。
|
|
132
|
+
* model 在 1-3 层均未命中时按已解析 provider 的登记生图模型推断(唯一命中直接
|
|
133
|
+
* 用;多命中确定性排序取第一个并注明来源)。
|
|
134
|
+
*/
|
|
135
|
+
function resolveImageTarget(input, slot, catalog, sessionProvider) {
|
|
136
|
+
const paramProvider = boundedString(input.provider, 64);
|
|
137
|
+
const paramModel = boundedString(input.model, 128);
|
|
138
|
+
const envProvider = nonEmptyEnvValue("AGENT_FORGE_IMAGEGEN_PROVIDER");
|
|
139
|
+
const envModel = nonEmptyEnvValue("AGENT_FORGE_IMAGEGEN_MODEL");
|
|
140
|
+
let provider = paramProvider ?? slot?.provider ?? envProvider;
|
|
141
|
+
if (provider === undefined) {
|
|
142
|
+
if (sessionProvider !== undefined && (catalog[sessionProvider]?.length ?? 0) > 0)
|
|
143
|
+
provider = sessionProvider;
|
|
144
|
+
else {
|
|
145
|
+
const candidates = Object.keys(catalog)
|
|
146
|
+
.filter((providerId) => catalog[providerId].length > 0)
|
|
147
|
+
.sort();
|
|
148
|
+
if (candidates.length === 1)
|
|
149
|
+
provider = candidates[0];
|
|
150
|
+
else if (candidates.length > 1)
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
message: `Multiple image-generation providers are registered in models.json (${candidates.join(", ")}); image generation fails closed instead of guessing. ` +
|
|
154
|
+
`Configure capabilities.imageGeneration in settings.json or pass the provider parameter. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,
|
|
155
|
+
};
|
|
156
|
+
else
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
message: "Image generation is not configured: pass provider and model via the tool parameters, set capabilities.imageGeneration in settings.json, " +
|
|
160
|
+
'set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL, or register an image model ("output": ["image"]) for a provider in models.json. ' +
|
|
161
|
+
IMAGE_GENERATION_CONFIG_EXAMPLE,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let model = paramModel ?? slot?.model ?? envModel;
|
|
166
|
+
let modelSourceNote;
|
|
167
|
+
if (model === undefined) {
|
|
168
|
+
const registered = catalog[provider] ?? [];
|
|
169
|
+
if (registered.length === 1)
|
|
170
|
+
model = registered[0].id;
|
|
171
|
+
else if (registered.length > 1) {
|
|
172
|
+
const first = [...registered].sort(compareCatalogModels)[0];
|
|
173
|
+
model = first.id;
|
|
174
|
+
modelSourceNote =
|
|
175
|
+
`model ${first.id} was picked deterministically (first by name) from ${registered.length} image models registered for provider ${provider} in models.json; ` +
|
|
176
|
+
"pass an explicit model to choose another";
|
|
177
|
+
}
|
|
178
|
+
else
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
message: `No image model is configured for provider ${provider}: no model came from the tool parameters, settings, or env, ` +
|
|
182
|
+
`and provider ${provider} has no models.json model with "output": ["image"]. Register one or pass an explicit model. ${IMAGE_GENERATION_CONFIG_EXAMPLE}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return { ok: true, provider, model, ...(modelSourceNote === undefined ? {} : { modelSourceNote }) };
|
|
186
|
+
}
|
|
72
187
|
function promptFrom(value) {
|
|
73
188
|
return boundedString(value, 4000);
|
|
74
189
|
}
|
|
@@ -93,7 +208,7 @@ function localStampV1(date) {
|
|
|
93
208
|
const pad = (value) => String(value).padStart(2, "0");
|
|
94
209
|
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
95
210
|
}
|
|
96
|
-
function successResult(result, images) {
|
|
211
|
+
function successResult(result, images, modelSourceNote) {
|
|
97
212
|
if (result.status !== "succeeded")
|
|
98
213
|
return result;
|
|
99
214
|
// Zero inlining (user decision, 2026-09-30): image payloads never ride
|
|
@@ -109,6 +224,7 @@ function successResult(result, images) {
|
|
|
109
224
|
text: JSON.stringify({
|
|
110
225
|
files: result.files,
|
|
111
226
|
model: result.model,
|
|
227
|
+
...(modelSourceNote === undefined ? {} : { modelSource: modelSourceNote }),
|
|
112
228
|
...(result.usage === undefined ? {} : { usage: result.usage }),
|
|
113
229
|
note: "images are saved to disk only; read a file to view it",
|
|
114
230
|
}, null, 1),
|
|
@@ -247,6 +363,19 @@ export function createImageGenerationCapabilityFactoryV1(options) {
|
|
|
247
363
|
return (api) => {
|
|
248
364
|
if (api.host.mode === "readonly")
|
|
249
365
|
return;
|
|
366
|
+
// 宿主装配期注入(设计 §3.3):settings 槽位 + 生图模型目录快照经
|
|
367
|
+
// configOverride 合并进 api.config。注入形状非法不在装载期崩溃——每次
|
|
368
|
+
// 调用按 config_missing 可见失败(失败可见,不静默降级)。
|
|
369
|
+
const slotInjection = readInjectedSlot(api.config);
|
|
370
|
+
const catalogInjection = readInjectedCatalog(api.config);
|
|
371
|
+
const sessionProvider = () => api.session?.getModel?.()?.provider;
|
|
372
|
+
const resolveTarget = (input) => {
|
|
373
|
+
if (slotInjection.error !== undefined)
|
|
374
|
+
return { ok: false, message: `Invalid injected image-generation config: ${slotInjection.error}` };
|
|
375
|
+
if (catalogInjection.error !== undefined)
|
|
376
|
+
return { ok: false, message: `Invalid injected image-generation config: ${catalogInjection.error}` };
|
|
377
|
+
return resolveImageTarget(input, slotInjection.slot, catalogInjection.catalog, sessionProvider());
|
|
378
|
+
};
|
|
250
379
|
api.registerTool({
|
|
251
380
|
name: "image_generate",
|
|
252
381
|
label: "Image generate",
|
|
@@ -273,10 +402,10 @@ export function createImageGenerationCapabilityFactoryV1(options) {
|
|
|
273
402
|
const clientSignal = combineSignals([invocationSignal, sessionSignal]);
|
|
274
403
|
if (clientSignal?.aborted)
|
|
275
404
|
return cancelled("");
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
405
|
+
const target = resolveTarget(input);
|
|
406
|
+
if (!target.ok)
|
|
407
|
+
return failed("", "config_missing", boundedImageMessageV1(target.message));
|
|
408
|
+
const { provider, model, modelSourceNote } = target;
|
|
280
409
|
try {
|
|
281
410
|
const resolved = await resolveInvocation(api, provider, fetchImpl, model);
|
|
282
411
|
if (resolved.status === "failed")
|
|
@@ -292,7 +421,7 @@ export function createImageGenerationCapabilityFactoryV1(options) {
|
|
|
292
421
|
if (!Array.isArray(collected))
|
|
293
422
|
return failed(model, collected.code, collected.message);
|
|
294
423
|
const persisted = await persistImages(resolved.workspace, collected);
|
|
295
|
-
return successResult({ version: 1, status: "succeeded", files: persisted.files, model, ...usageOf(response) }, persisted.images);
|
|
424
|
+
return successResult({ version: 1, status: "succeeded", files: persisted.files, model, ...usageOf(response) }, persisted.images, modelSourceNote);
|
|
296
425
|
}
|
|
297
426
|
catch (error) {
|
|
298
427
|
return failureFrom(error, clientSignal, model, "Image generation request failed");
|
|
@@ -323,10 +452,10 @@ export function createImageGenerationCapabilityFactoryV1(options) {
|
|
|
323
452
|
const clientSignal = combineSignals([invocationSignal, sessionSignal]);
|
|
324
453
|
if (clientSignal?.aborted)
|
|
325
454
|
return cancelled("");
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
455
|
+
const target = resolveTarget(input);
|
|
456
|
+
if (!target.ok)
|
|
457
|
+
return failed("", "config_missing", boundedImageMessageV1(target.message));
|
|
458
|
+
const { provider, model, modelSourceNote } = target;
|
|
330
459
|
try {
|
|
331
460
|
const resolved = await resolveInvocation(api, provider, fetchImpl, model);
|
|
332
461
|
if (resolved.status === "failed")
|
|
@@ -355,7 +484,7 @@ export function createImageGenerationCapabilityFactoryV1(options) {
|
|
|
355
484
|
if (!Array.isArray(collected))
|
|
356
485
|
return failed(model, collected.code, collected.message);
|
|
357
486
|
const persisted = await persistImages(resolved.workspace, collected);
|
|
358
|
-
return successResult({ version: 1, status: "succeeded", files: persisted.files, model, ...usageOf(response) }, persisted.images);
|
|
487
|
+
return successResult({ version: 1, status: "succeeded", files: persisted.files, model, ...usageOf(response) }, persisted.images, modelSourceNote);
|
|
359
488
|
}
|
|
360
489
|
catch (error) {
|
|
361
490
|
return failureFrom(error, clientSignal, model, "Image edit request failed");
|
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 } 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) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/**\n * Local-time `yyyymmdd-HHmmss` stamp shared by every file of one invocation.\n * Local (not UTC): users find today's images by the date in the filename, and\n * on UTC+8 machines a UTC stamp labels evening images as \"tomorrow\". Sorting\n * stays monotonic wherever local time has no DST jumps (e.g. China).\n */\nfunction localStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(\n\t\tdate.getHours(),\n\t)}${pad(date.getMinutes())}${pad(date.getSeconds())}`;\n}\n\ninterface 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): 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...(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/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl);\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"image_generate\",\n\t\t\tlabel: \"Image generate\",\n\t\t\tdescription:\n\t\t\t\t\"Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves image files (png/jpg/webp/gif as produced) under generated-images/ and returns their workspace-relative paths — 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 provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\t// Size-check before reading: users can drop their own files\n\t\t\t\t\t// into generated-images/, and the 20 MiB download cap must\n\t\t\t\t\t// not be bypassed by a renamed local file.\n\t\t\t\t\tconst sourceStats = await stat(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (sourceStats === undefined || !sourceStats.isFile())\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tif (sourceStats.size > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"response_too_large\",\n\t\t\t\t\t\t\t`image ${imagePath} is ${sourceStats.size} bytes, above the 20 MiB limit`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);\n\t\t\t\t\tif (source === undefined)\n\t\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\"image_not_found\",\n\t\t\t\t\t\t\t`image ${imagePath} was not found under the workspace generated-images/ directory`,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst response = await resolved.client.editImage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\timage: source,\n\t\t\t\t\t\t\t// The edits endpoint accepts png/jpeg/webp inputs; the\n\t\t\t\t\t\t\t// multipart blob must carry the source's real format\n\t\t\t\t\t\t\t// (a prior url-form download may be JPEG).\n\t\t\t\t\t\t\tmimeType: detectImageFormatV1(source).mimeType,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image edit request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n\nfunction failureFrom(\n\terror: unknown,\n\tsignal: AbortSignal | undefined,\n\tmodel: string,\n\tfallbackMessage: string,\n): ImageGenResultV1 {\n\tif (signal?.aborted) return cancelled(model);\n\tlet message = error instanceof Error ? error.message : fallbackMessage;\n\t// Append (never replace) a proxy hint for opaque network failures: the\n\t// single most common cause on machines that route out through a VPN.\n\tif (/fetch failed|terminated|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|UND_ERR/u.test(message))\n\t\tmessage = `${message} (if this machine reaches the internet through a proxy, set HTTPS_PROXY/HTTP_PROXY — see the image-generation plugin README)`;\n\treturn failed(model, \"image_request_failed\", boundedImageMessageV1(message));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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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/models.json)`,\n\t);\n}\n\n/**\n * Registers the full-control `image_generate` and `image_edit` tools. The\n * factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is\n * resolved at call time through the host's public `CredentialAPI`. The\n * optional `fetchImpl` is a test seam only; production hosts use global fetch.\n */\nexport function createImageGenerationCapabilityFactoryV1(options?: {\n\treadonly fetchImpl?: typeof fetch;\n}): CapabilityPluginSyncFactory {\n\t// Every outbound request (generation and url download alike) rides the\n\t// same proxy-aware fetch: HTTPS_PROXY/HTTP_PROXY/NO_PROXY are honored\n\t// per request without touching the host's global dispatcher. The default\n\t// base is the undici package's fetch — the global fetch bundles another\n\t// undici version and rejects this package's dispatchers.\n\tconst fetchImpl: typeof fetch = wrapFetchWithEnvProxyV1(options?.fetchImpl);\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-agent-forge/plugin-image-generation",
|
|
3
|
-
"version": "0.86.
|
|
3
|
+
"version": "0.86.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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"shx": "0.4.0",
|
|
53
|
-
"vitest": "4.1.
|
|
53
|
+
"vitest": "4.1.11",
|
|
54
54
|
"@ai-agent-forge/agent-forge": "^0.86.4"
|
|
55
55
|
}
|
|
56
56
|
}
|