@ai-agent-forge/plugin-image-generation 0.85.10
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 +19 -0
- package/agent-forge.json +25 -0
- package/dist/capability.d.ts +11 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +308 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +8 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +8 -0
- package/dist/entry.js.map +1 -0
- package/dist/images-client.d.ts +14 -0
- package/dist/images-client.d.ts.map +1 -0
- package/dist/images-client.js +90 -0
- package/dist/images-client.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/plugin.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @agent-forge/plugin-image-generation
|
|
2
|
+
|
|
3
|
+
First-party image generation capability plugin for Agent Forge. Registers the
|
|
4
|
+
`image_generate` and `image_edit` tools (full-control hosts) against the
|
|
5
|
+
host-configured provider's OpenAI-compatible images API:
|
|
6
|
+
|
|
7
|
+
- Credentials (`apiKey` + `baseUrl`) are resolved at runtime through the public
|
|
8
|
+
`CredentialAPI` (`api.credentials.resolve({ providerId })`); the plugin keeps
|
|
9
|
+
no credentials of its own.
|
|
10
|
+
- `provider`/`model` come from the tool parameters or the
|
|
11
|
+
`AGENT_FORGE_IMAGEGEN_PROVIDER` / `AGENT_FORGE_IMAGEGEN_MODEL` environment
|
|
12
|
+
variables; missing configuration fails closed with `config_missing`.
|
|
13
|
+
- Generated and edited PNG files are written under the workspace
|
|
14
|
+
`generated-images/` directory; the tool result returns their
|
|
15
|
+
workspace-relative paths plus one `image` content part per image so
|
|
16
|
+
vision-capable models can review and revise.
|
|
17
|
+
- Cancellation cascades through `api.sessionAbortSignal()` and the invocation
|
|
18
|
+
signal; requests time out after 180s and error messages never echo the API
|
|
19
|
+
key or request headers.
|
package/agent-forge.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.image-generation",
|
|
4
|
+
"pluginVersion": "0.85.10",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "image.generation",
|
|
10
|
+
"version": 1
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"platform": {
|
|
14
|
+
"os": [
|
|
15
|
+
"windows",
|
|
16
|
+
"macos",
|
|
17
|
+
"linux"
|
|
18
|
+
],
|
|
19
|
+
"runtime": [
|
|
20
|
+
{
|
|
21
|
+
"name": "node"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Registers the full-control `image_generate` and `image_edit` tools. The
|
|
4
|
+
* factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is
|
|
5
|
+
* resolved at call time through the host's public `CredentialAPI`. The
|
|
6
|
+
* optional `fetchImpl` is a test seam only; production hosts use global fetch.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createImageGenerationCapabilityFactoryV1(options?: {
|
|
9
|
+
readonly fetchImpl?: typeof fetch;
|
|
10
|
+
}): CapabilityPluginSyncFactory;
|
|
11
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AA4Q1F;;;;;GAKG;AACH,wBAAgB,wCAAwC,CAAC,OAAO,CAAC,EAAE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,GAAG,2BAA2B,CA4H9B","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/** UTC `yyyymmdd-HHmmss` stamp shared by every file of one invocation. */\nfunction utcStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(\n\t\tdate.getUTCHours(),\n\t)}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;\n}\n\ninterface ImageContentPartV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\ninterface ImageToolSuccessV1 {\n\treadonly content: readonly ({ readonly type: \"text\"; readonly text: string } | ImageContentPartV1)[];\n\treadonly details: Record<string, never>;\n}\n\nfunction successResult(result: ImageGenResultV1, images: readonly Uint8Array[]): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tfiles: result.files,\n\t\t\t\t\t\tmodel: result.model,\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t1,\n\t\t\t\t),\n\t\t\t},\n\t\t\t...images.map(\n\t\t\t\t(image): ImageContentPartV1 => ({\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tdata: Buffer.from(image).toString(\"base64\"),\n\t\t\t\t\tmimeType: \"image/png\",\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tdetails: {},\n\t};\n}\n\n/** Write one image, skipping sequence numbers already taken in the same second. */\nasync function writeImage(\n\tworkspace: string,\n\tstamp: string,\n\tstartSequence: number,\n\tbytes: Uint8Array,\n): Promise<{ readonly relativePath: string; readonly sequence: number }> {\n\tconst directory = join(workspace, GENERATED_IMAGES_DIRECTORY);\n\tawait mkdir(directory, { recursive: true });\n\tlet sequence = startSequence;\n\twhile (existsSync(join(directory, `img-${stamp}-${sequence}.png`))) sequence += 1;\n\tconst name = `img-${stamp}-${sequence}.png`;\n\tawait writeFile(join(directory, name), bytes);\n\treturn { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };\n}\n\n/** Persist every produced image file for a settled response. */\nasync function persistImages(\n\tworkspace: string,\n\tproduced: readonly Uint8Array[],\n): Promise<{ readonly files: string[]; readonly images: Uint8Array[] }> {\n\tconst stamp = utcStampV1(new Date());\n\tconst files: string[] = [];\n\tconst images: Uint8Array[] = [];\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<Uint8Array[] | ImageFailureV1> {\n\tconst images: Uint8Array[] = [];\n\tfor (const item of response.data) {\n\t\tif (item.b64 !== undefined) {\n\t\t\timages.push(Buffer.from(item.b64, \"base64\"));\n\t\t\tcontinue;\n\t\t}\n\t\tif (item.url === undefined) continue;\n\t\tconst download = signal === undefined ? await fetchImpl(item.url) : await fetchImpl(item.url, { signal });\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}`,\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);\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\tconst fetchImpl: typeof fetch = options?.fetchImpl ?? fetch;\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 PNG files under generated-images/ and returns their workspace-relative paths\",\n\t\t\tparameters: IMAGE_GENERATE_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst size = boundedString(input.size, 32);\n\t\t\t\tconst quality = boundedString(input.quality, 32);\n\t\t\t\tif (input.n !== undefined && countFrom(input.n) === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"n must be an integer between 1 and 4\");\n\t\t\t\tconst n = countFrom(input.n) ?? 1;\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\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{ image: source, mimeType: \"image/png\", prompt, model },\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\treturn failed(model, \"image_request_failed\", error instanceof Error ? error.message : fallbackMessage);\n}\n"]}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import { createImagesClientV1 } from "./images-client.js";
|
|
6
|
+
import { boundedImageMessageV1, IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1, } from "./types.js";
|
|
7
|
+
const IMAGE_GENERATE_PARAMETERS = Type.Object({
|
|
8
|
+
prompt: Type.String({ minLength: 1, maxLength: 4000 }),
|
|
9
|
+
size: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),
|
|
10
|
+
quality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),
|
|
11
|
+
n: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),
|
|
12
|
+
provider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),
|
|
13
|
+
model: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),
|
|
14
|
+
}, { additionalProperties: false });
|
|
15
|
+
const IMAGE_EDIT_PARAMETERS = Type.Object({
|
|
16
|
+
imagePath: Type.String({ minLength: 1, maxLength: 512, pattern: "^[A-Za-z0-9_][A-Za-z0-9._/-]*$" }),
|
|
17
|
+
prompt: Type.String({ minLength: 1, maxLength: 4000 }),
|
|
18
|
+
provider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),
|
|
19
|
+
model: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),
|
|
20
|
+
}, { additionalProperties: false });
|
|
21
|
+
const IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;
|
|
22
|
+
const GENERATED_IMAGES_DIRECTORY = "generated-images";
|
|
23
|
+
function objectInput(first, second) {
|
|
24
|
+
// Shape-based dual-convention read (aligned with subagent-delegate): the
|
|
25
|
+
// runtime invoke face passes the input object first and the host
|
|
26
|
+
// CapabilityToolContext second; the agent tool face passes the tool call
|
|
27
|
+
// id (a string) first and the params object second.
|
|
28
|
+
const value = first !== null && typeof first === "object" && !Array.isArray(first) ? first : second;
|
|
29
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
30
|
+
throw new Error("Image generation input must be an object");
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function signalFrom(value) {
|
|
34
|
+
return value instanceof AbortSignal ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
function combineSignals(signals) {
|
|
37
|
+
const present = signals.filter((signal) => signal !== undefined);
|
|
38
|
+
if (present.length === 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (present.length === 1)
|
|
41
|
+
return present[0];
|
|
42
|
+
return AbortSignal.any(present);
|
|
43
|
+
}
|
|
44
|
+
function failed(model, code, message) {
|
|
45
|
+
return {
|
|
46
|
+
version: 1,
|
|
47
|
+
status: "failed",
|
|
48
|
+
files: [],
|
|
49
|
+
model,
|
|
50
|
+
error: { code, message: boundedImageMessageV1(message) },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function cancelled(model) {
|
|
54
|
+
return {
|
|
55
|
+
version: 1,
|
|
56
|
+
status: "cancelled",
|
|
57
|
+
files: [],
|
|
58
|
+
model,
|
|
59
|
+
error: { code: "cancelled", message: "Image generation was cancelled" },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** `param` (non-empty string) wins, then the env fallback, else undefined. */
|
|
63
|
+
function paramOrEnv(value, envName) {
|
|
64
|
+
if (typeof value === "string" && value.trim() !== "")
|
|
65
|
+
return value;
|
|
66
|
+
const fromEnv = process.env[envName];
|
|
67
|
+
return typeof fromEnv === "string" && fromEnv.trim() !== "" ? fromEnv : undefined;
|
|
68
|
+
}
|
|
69
|
+
function boundedString(value, maxLength) {
|
|
70
|
+
return typeof value === "string" && value !== "" && value.length <= maxLength ? value : undefined;
|
|
71
|
+
}
|
|
72
|
+
function promptFrom(value) {
|
|
73
|
+
return boundedString(value, 4000);
|
|
74
|
+
}
|
|
75
|
+
function countFrom(value) {
|
|
76
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
/** Workspace-relative identifier confined to the generated-images directory. */
|
|
79
|
+
function validImagePath(imagePath) {
|
|
80
|
+
if (!IMAGE_PATH_PATTERN_V1.test(imagePath))
|
|
81
|
+
return false;
|
|
82
|
+
if (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`))
|
|
83
|
+
return false;
|
|
84
|
+
return !imagePath.split("/").includes("..");
|
|
85
|
+
}
|
|
86
|
+
/** UTC `yyyymmdd-HHmmss` stamp shared by every file of one invocation. */
|
|
87
|
+
function utcStampV1(date) {
|
|
88
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
89
|
+
return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;
|
|
90
|
+
}
|
|
91
|
+
function successResult(result, images) {
|
|
92
|
+
if (result.status !== "succeeded")
|
|
93
|
+
return result;
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({
|
|
99
|
+
files: result.files,
|
|
100
|
+
model: result.model,
|
|
101
|
+
...(result.usage === undefined ? {} : { usage: result.usage }),
|
|
102
|
+
}, null, 1),
|
|
103
|
+
},
|
|
104
|
+
...images.map((image) => ({
|
|
105
|
+
type: "image",
|
|
106
|
+
data: Buffer.from(image).toString("base64"),
|
|
107
|
+
mimeType: "image/png",
|
|
108
|
+
})),
|
|
109
|
+
],
|
|
110
|
+
details: {},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Write one image, skipping sequence numbers already taken in the same second. */
|
|
114
|
+
async function writeImage(workspace, stamp, startSequence, bytes) {
|
|
115
|
+
const directory = join(workspace, GENERATED_IMAGES_DIRECTORY);
|
|
116
|
+
await mkdir(directory, { recursive: true });
|
|
117
|
+
let sequence = startSequence;
|
|
118
|
+
while (existsSync(join(directory, `img-${stamp}-${sequence}.png`)))
|
|
119
|
+
sequence += 1;
|
|
120
|
+
const name = `img-${stamp}-${sequence}.png`;
|
|
121
|
+
await writeFile(join(directory, name), bytes);
|
|
122
|
+
return { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };
|
|
123
|
+
}
|
|
124
|
+
/** Persist every produced image file for a settled response. */
|
|
125
|
+
async function persistImages(workspace, produced) {
|
|
126
|
+
const stamp = utcStampV1(new Date());
|
|
127
|
+
const files = [];
|
|
128
|
+
const images = [];
|
|
129
|
+
let sequence = 1;
|
|
130
|
+
for (const image of produced) {
|
|
131
|
+
const written = await writeImage(workspace, stamp, sequence, image);
|
|
132
|
+
files.push(written.relativePath);
|
|
133
|
+
images.push(image);
|
|
134
|
+
sequence = written.sequence + 1;
|
|
135
|
+
}
|
|
136
|
+
return { files, images };
|
|
137
|
+
}
|
|
138
|
+
/** Inline b64 items pass through; url items are downloaded with the same fetch injection. */
|
|
139
|
+
async function collectImages(response, fetchImpl, signal) {
|
|
140
|
+
const images = [];
|
|
141
|
+
for (const item of response.data) {
|
|
142
|
+
if (item.b64 !== undefined) {
|
|
143
|
+
images.push(Buffer.from(item.b64, "base64"));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (item.url === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
const download = signal === undefined ? await fetchImpl(item.url) : await fetchImpl(item.url, { signal });
|
|
149
|
+
if (!download.ok)
|
|
150
|
+
return {
|
|
151
|
+
code: "download_failed",
|
|
152
|
+
message: `image download from the provider url failed with status ${download.status}`,
|
|
153
|
+
};
|
|
154
|
+
const bytes = Buffer.from(await download.arrayBuffer());
|
|
155
|
+
if (bytes.byteLength > IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1)
|
|
156
|
+
return { code: "response_too_large", message: "downloaded image exceeds the 20 MiB response limit" };
|
|
157
|
+
images.push(bytes);
|
|
158
|
+
}
|
|
159
|
+
return images;
|
|
160
|
+
}
|
|
161
|
+
function usageOf(response) {
|
|
162
|
+
return response.usage === undefined ? {} : { usage: response.usage };
|
|
163
|
+
}
|
|
164
|
+
/** Resolve provider config/credentials/workspace; a miss returns the failed result directly. */
|
|
165
|
+
async function resolveInvocation(api, providerId, fetchImpl, model) {
|
|
166
|
+
const workspace = api.host.workspaceDirectory;
|
|
167
|
+
if (typeof workspace !== "string" || workspace === "")
|
|
168
|
+
return {
|
|
169
|
+
status: "failed",
|
|
170
|
+
result: failed(model, "workspace_unavailable", "Image generation requires the host workspaceDirectory service to write generated-images/"),
|
|
171
|
+
};
|
|
172
|
+
const resolution = api.credentials === undefined ? undefined : await api.credentials.resolve({ providerId });
|
|
173
|
+
if (resolution === undefined) {
|
|
174
|
+
return { status: "failed", result: credentialsFailure(model, providerId) };
|
|
175
|
+
}
|
|
176
|
+
if (typeof resolution.apiKey !== "string" ||
|
|
177
|
+
resolution.apiKey === "" ||
|
|
178
|
+
typeof resolution.baseUrl !== "string" ||
|
|
179
|
+
resolution.baseUrl === "") {
|
|
180
|
+
return { status: "failed", result: credentialsFailure(model, providerId) };
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
status: "ok",
|
|
184
|
+
workspace,
|
|
185
|
+
client: createImagesClientV1({ baseUrl: resolution.baseUrl, apiKey: resolution.apiKey, fetchImpl }),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function credentialsFailure(model, providerId) {
|
|
189
|
+
return failed(model, "credentials_unavailable", `Image generation credentials are unavailable for provider ${providerId}: the host must resolve apiKey and baseUrl for this provider (auth.json/models.json)`);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Registers the full-control `image_generate` and `image_edit` tools. The
|
|
193
|
+
* factory takes no credentials: provider auth (`apiKey` + `baseUrl`) is
|
|
194
|
+
* resolved at call time through the host's public `CredentialAPI`. The
|
|
195
|
+
* optional `fetchImpl` is a test seam only; production hosts use global fetch.
|
|
196
|
+
*/
|
|
197
|
+
export function createImageGenerationCapabilityFactoryV1(options) {
|
|
198
|
+
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
199
|
+
return (api) => {
|
|
200
|
+
if (api.host.mode === "readonly")
|
|
201
|
+
return;
|
|
202
|
+
api.registerTool({
|
|
203
|
+
name: "image_generate",
|
|
204
|
+
label: "Image generate",
|
|
205
|
+
description: "Generate images from a prompt via the host-configured provider's OpenAI-compatible images API; saves PNG files under generated-images/ and returns their workspace-relative paths",
|
|
206
|
+
parameters: IMAGE_GENERATE_PARAMETERS,
|
|
207
|
+
execute: async (first, second, third) => {
|
|
208
|
+
let input;
|
|
209
|
+
try {
|
|
210
|
+
input = objectInput(first, second);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return failed("", "invalid_input", "Image generation input must be an object");
|
|
214
|
+
}
|
|
215
|
+
const prompt = promptFrom(input.prompt);
|
|
216
|
+
if (prompt === undefined)
|
|
217
|
+
return failed("", "invalid_input", "prompt must be a non-empty string of at most 4000 characters");
|
|
218
|
+
const size = boundedString(input.size, 32);
|
|
219
|
+
const quality = boundedString(input.quality, 32);
|
|
220
|
+
if (input.n !== undefined && countFrom(input.n) === undefined)
|
|
221
|
+
return failed("", "invalid_input", "n must be an integer between 1 and 4");
|
|
222
|
+
const n = countFrom(input.n) ?? 1;
|
|
223
|
+
const sessionSignal = api.sessionAbortSignal?.();
|
|
224
|
+
const invocationSignal = signalFrom(third);
|
|
225
|
+
const clientSignal = combineSignals([invocationSignal, sessionSignal]);
|
|
226
|
+
if (clientSignal?.aborted)
|
|
227
|
+
return cancelled("");
|
|
228
|
+
const provider = paramOrEnv(input.provider, "AGENT_FORGE_IMAGEGEN_PROVIDER");
|
|
229
|
+
const model = paramOrEnv(input.model, "AGENT_FORGE_IMAGEGEN_MODEL");
|
|
230
|
+
if (provider === undefined || model === undefined)
|
|
231
|
+
return failed("", "config_missing", "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");
|
|
232
|
+
try {
|
|
233
|
+
const resolved = await resolveInvocation(api, provider, fetchImpl, model);
|
|
234
|
+
if (resolved.status === "failed")
|
|
235
|
+
return resolved.result;
|
|
236
|
+
const response = await resolved.client.generateImages({
|
|
237
|
+
model,
|
|
238
|
+
prompt,
|
|
239
|
+
n,
|
|
240
|
+
...(size === undefined ? {} : { size }),
|
|
241
|
+
...(quality === undefined ? {} : { quality }),
|
|
242
|
+
}, clientSignal);
|
|
243
|
+
const collected = await collectImages(response, fetchImpl, clientSignal);
|
|
244
|
+
if (!Array.isArray(collected))
|
|
245
|
+
return failed(model, collected.code, collected.message);
|
|
246
|
+
const persisted = await persistImages(resolved.workspace, collected);
|
|
247
|
+
return successResult({ version: 1, status: "succeeded", files: persisted.files, model, ...usageOf(response) }, persisted.images);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return failureFrom(error, clientSignal, model, "Image generation request failed");
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
api.registerTool({
|
|
255
|
+
name: "image_edit",
|
|
256
|
+
label: "Image edit",
|
|
257
|
+
description: "Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path",
|
|
258
|
+
parameters: IMAGE_EDIT_PARAMETERS,
|
|
259
|
+
execute: async (first, second, third) => {
|
|
260
|
+
let input;
|
|
261
|
+
try {
|
|
262
|
+
input = objectInput(first, second);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return failed("", "invalid_input", "Image generation input must be an object");
|
|
266
|
+
}
|
|
267
|
+
const prompt = promptFrom(input.prompt);
|
|
268
|
+
if (prompt === undefined)
|
|
269
|
+
return failed("", "invalid_input", "prompt must be a non-empty string of at most 4000 characters");
|
|
270
|
+
const imagePath = typeof input.imagePath === "string" ? input.imagePath : undefined;
|
|
271
|
+
if (imagePath === undefined || !validImagePath(imagePath))
|
|
272
|
+
return failed("", "invalid_path", "imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)");
|
|
273
|
+
const sessionSignal = api.sessionAbortSignal?.();
|
|
274
|
+
const invocationSignal = signalFrom(third);
|
|
275
|
+
const clientSignal = combineSignals([invocationSignal, sessionSignal]);
|
|
276
|
+
if (clientSignal?.aborted)
|
|
277
|
+
return cancelled("");
|
|
278
|
+
const provider = paramOrEnv(input.provider, "AGENT_FORGE_IMAGEGEN_PROVIDER");
|
|
279
|
+
const model = paramOrEnv(input.model, "AGENT_FORGE_IMAGEGEN_MODEL");
|
|
280
|
+
if (provider === undefined || model === undefined)
|
|
281
|
+
return failed("", "config_missing", "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");
|
|
282
|
+
try {
|
|
283
|
+
const resolved = await resolveInvocation(api, provider, fetchImpl, model);
|
|
284
|
+
if (resolved.status === "failed")
|
|
285
|
+
return resolved.result;
|
|
286
|
+
const source = await readFile(join(resolved.workspace, imagePath)).catch(() => undefined);
|
|
287
|
+
if (source === undefined)
|
|
288
|
+
return failed(model, "image_not_found", `image ${imagePath} was not found under the workspace generated-images/ directory`);
|
|
289
|
+
const response = await resolved.client.editImage({ image: source, mimeType: "image/png", prompt, model }, clientSignal);
|
|
290
|
+
const collected = await collectImages(response, fetchImpl, clientSignal);
|
|
291
|
+
if (!Array.isArray(collected))
|
|
292
|
+
return failed(model, collected.code, collected.message);
|
|
293
|
+
const persisted = await persistImages(resolved.workspace, collected);
|
|
294
|
+
return successResult({ version: 1, status: "succeeded", files: persisted.files, model, ...usageOf(response) }, persisted.images);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
return failureFrom(error, clientSignal, model, "Image edit request failed");
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function failureFrom(error, signal, model, fallbackMessage) {
|
|
304
|
+
if (signal?.aborted)
|
|
305
|
+
return cancelled(model);
|
|
306
|
+
return failed(model, "image_request_failed", error instanceof Error ? error.message : fallbackMessage);
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { CapabilityAPI, CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { createImagesClientV1 } from \"./images-client.ts\";\nimport {\n\tboundedImageMessageV1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\ttype ImageGenResultV1,\n\ttype ImagesClientResponseV1,\n} from \"./types.ts\";\n\nconst IMAGE_GENERATE_PARAMETERS = Type.Object(\n\t{\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tsize: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tquality: Type.Optional(Type.String({ minLength: 1, maxLength: 32 })),\n\t\tn: Type.Optional(Type.Integer({ minimum: 1, maximum: 4 })),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_EDIT_PARAMETERS = Type.Object(\n\t{\n\t\timagePath: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\tprompt: Type.String({ minLength: 1, maxLength: 4000 }),\n\t\tprovider: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),\n\t\tmodel: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_PATH_PATTERN_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/u;\n\nconst GENERATED_IMAGES_DIRECTORY = \"generated-images\";\n\ninterface ImageFailureV1 {\n\treadonly code: string;\n\treadonly message: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\t// Shape-based dual-convention read (aligned with subagent-delegate): the\n\t// runtime invoke face passes the input object first and the host\n\t// CapabilityToolContext second; the agent tool face passes the tool call\n\t// id (a string) first and the params object second.\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Image generation input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction failed(model: string, code: string, message: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"failed\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code, message: boundedImageMessageV1(message) },\n\t};\n}\n\nfunction cancelled(model: string): ImageGenResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\tstatus: \"cancelled\",\n\t\tfiles: [],\n\t\tmodel,\n\t\terror: { code: \"cancelled\", message: \"Image generation was cancelled\" },\n\t};\n}\n\n/** `param` (non-empty string) wins, then the env fallback, else undefined. */\nfunction paramOrEnv(value: unknown, envName: string): string | undefined {\n\tif (typeof value === \"string\" && value.trim() !== \"\") return value;\n\tconst fromEnv = process.env[envName];\n\treturn typeof fromEnv === \"string\" && fromEnv.trim() !== \"\" ? fromEnv : undefined;\n}\n\nfunction boundedString(value: unknown, maxLength: number): string | undefined {\n\treturn typeof value === \"string\" && value !== \"\" && value.length <= maxLength ? value : undefined;\n}\n\nfunction promptFrom(value: unknown): string | undefined {\n\treturn boundedString(value, 4000);\n}\n\nfunction countFrom(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value >= 1 && value <= 4 ? value : undefined;\n}\n\n/** Workspace-relative identifier confined to the generated-images directory. */\nfunction validImagePath(imagePath: string): boolean {\n\tif (!IMAGE_PATH_PATTERN_V1.test(imagePath)) return false;\n\tif (!imagePath.startsWith(`${GENERATED_IMAGES_DIRECTORY}/`)) return false;\n\treturn !imagePath.split(\"/\").includes(\"..\");\n}\n\n/** UTC `yyyymmdd-HHmmss` stamp shared by every file of one invocation. */\nfunction utcStampV1(date: Date): string {\n\tconst pad = (value: number): string => String(value).padStart(2, \"0\");\n\treturn `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(\n\t\tdate.getUTCHours(),\n\t)}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}`;\n}\n\ninterface ImageContentPartV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\ninterface ImageToolSuccessV1 {\n\treadonly content: readonly ({ readonly type: \"text\"; readonly text: string } | ImageContentPartV1)[];\n\treadonly details: Record<string, never>;\n}\n\nfunction successResult(result: ImageGenResultV1, images: readonly Uint8Array[]): ImageToolSuccessV1 | ImageGenResultV1 {\n\tif (result.status !== \"succeeded\") return result;\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\tfiles: result.files,\n\t\t\t\t\t\tmodel: result.model,\n\t\t\t\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t1,\n\t\t\t\t),\n\t\t\t},\n\t\t\t...images.map(\n\t\t\t\t(image): ImageContentPartV1 => ({\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tdata: Buffer.from(image).toString(\"base64\"),\n\t\t\t\t\tmimeType: \"image/png\",\n\t\t\t\t}),\n\t\t\t),\n\t\t],\n\t\tdetails: {},\n\t};\n}\n\n/** Write one image, skipping sequence numbers already taken in the same second. */\nasync function writeImage(\n\tworkspace: string,\n\tstamp: string,\n\tstartSequence: number,\n\tbytes: Uint8Array,\n): Promise<{ readonly relativePath: string; readonly sequence: number }> {\n\tconst directory = join(workspace, GENERATED_IMAGES_DIRECTORY);\n\tawait mkdir(directory, { recursive: true });\n\tlet sequence = startSequence;\n\twhile (existsSync(join(directory, `img-${stamp}-${sequence}.png`))) sequence += 1;\n\tconst name = `img-${stamp}-${sequence}.png`;\n\tawait writeFile(join(directory, name), bytes);\n\treturn { relativePath: `${GENERATED_IMAGES_DIRECTORY}/${name}`, sequence };\n}\n\n/** Persist every produced image file for a settled response. */\nasync function persistImages(\n\tworkspace: string,\n\tproduced: readonly Uint8Array[],\n): Promise<{ readonly files: string[]; readonly images: Uint8Array[] }> {\n\tconst stamp = utcStampV1(new Date());\n\tconst files: string[] = [];\n\tconst images: Uint8Array[] = [];\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<Uint8Array[] | ImageFailureV1> {\n\tconst images: Uint8Array[] = [];\n\tfor (const item of response.data) {\n\t\tif (item.b64 !== undefined) {\n\t\t\timages.push(Buffer.from(item.b64, \"base64\"));\n\t\t\tcontinue;\n\t\t}\n\t\tif (item.url === undefined) continue;\n\t\tconst download = signal === undefined ? await fetchImpl(item.url) : await fetchImpl(item.url, { signal });\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}`,\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);\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\tconst fetchImpl: typeof fetch = options?.fetchImpl ?? fetch;\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 PNG files under generated-images/ and returns their workspace-relative paths\",\n\t\t\tparameters: IMAGE_GENERATE_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst size = boundedString(input.size, 32);\n\t\t\t\tconst quality = boundedString(input.quality, 32);\n\t\t\t\tif (input.n !== undefined && countFrom(input.n) === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"n must be an integer between 1 and 4\");\n\t\t\t\tconst n = countFrom(input.n) ?? 1;\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\tconst response = await resolved.client.generateImages(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tn,\n\t\t\t\t\t\t\t...(size === undefined ? {} : { size }),\n\t\t\t\t\t\t\t...(quality === undefined ? {} : { quality }),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclientSignal,\n\t\t\t\t\t);\n\t\t\t\t\tconst collected = await collectImages(response, fetchImpl, clientSignal);\n\t\t\t\t\tif (!Array.isArray(collected)) return failed(model, collected.code, collected.message);\n\t\t\t\t\tconst persisted = await persistImages(resolved.workspace, collected);\n\t\t\t\t\treturn successResult(\n\t\t\t\t\t\t{ version: 1, status: \"succeeded\", files: persisted.files, model, ...usageOf(response) },\n\t\t\t\t\t\tpersisted.images,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failureFrom(error, clientSignal, model, \"Image generation request failed\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"image_edit\",\n\t\t\tlabel: \"Image edit\",\n\t\t\tdescription:\n\t\t\t\t\"Edit a previously generated image (workspace-relative path under generated-images/) with a revision prompt; returns the new file path\",\n\t\t\tparameters: IMAGE_EDIT_PARAMETERS,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"Image generation input must be an object\");\n\t\t\t\t}\n\t\t\t\tconst prompt = promptFrom(input.prompt);\n\t\t\t\tif (prompt === undefined)\n\t\t\t\t\treturn failed(\"\", \"invalid_input\", \"prompt must be a non-empty string of at most 4000 characters\");\n\t\t\t\tconst imagePath = typeof input.imagePath === \"string\" ? input.imagePath : undefined;\n\t\t\t\tif (imagePath === undefined || !validImagePath(imagePath))\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"invalid_path\",\n\t\t\t\t\t\t\"imagePath must be a workspace-relative identifier inside the generated-images/ directory (no .. segments)\",\n\t\t\t\t\t);\n\t\t\t\tconst sessionSignal = api.sessionAbortSignal?.();\n\t\t\t\tconst invocationSignal = signalFrom(third);\n\t\t\t\tconst clientSignal = combineSignals([invocationSignal, sessionSignal]);\n\t\t\t\tif (clientSignal?.aborted) return cancelled(\"\");\n\t\t\t\tconst provider = paramOrEnv(input.provider, \"AGENT_FORGE_IMAGEGEN_PROVIDER\");\n\t\t\t\tconst model = paramOrEnv(input.model, \"AGENT_FORGE_IMAGEGEN_MODEL\");\n\t\t\t\tif (provider === undefined || model === undefined)\n\t\t\t\t\treturn failed(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\"config_missing\",\n\t\t\t\t\t\t\"Image generation is not configured: pass provider and model via the tool parameters or set AGENT_FORGE_IMAGEGEN_PROVIDER and AGENT_FORGE_IMAGEGEN_MODEL in the environment\",\n\t\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await resolveInvocation(api, provider, fetchImpl, model);\n\t\t\t\t\tif (resolved.status === \"failed\") return resolved.result;\n\t\t\t\t\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{ image: source, mimeType: \"image/png\", prompt, model },\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\treturn failed(model, \"image_request_failed\", error instanceof Error ? error.message : fallbackMessage);\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
3
|
+
* Credentials stay runtime-resolved through `api.credentials`; the factory
|
|
4
|
+
* itself takes no configuration.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: (api: import("@agent-forge/plugin-sdk").CapabilityAPI) => void | import("@agent-forge/plugin-sdk").CapabilityPluginSyncInstance;
|
|
7
|
+
export default _default;
|
|
8
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAGA;;;;GAIG;;AACH,wBAAgH","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { createImageGenerationCapabilityFactoryV1 } from \"./capability.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n * Credentials stay runtime-resolved through `api.credentials`; the factory\n * itself takes no configuration.\n */\nexport default ((api) => createImageGenerationCapabilityFactoryV1()(api)) satisfies CapabilityPluginSyncFactory;\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createImageGenerationCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
/**
|
|
3
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
4
|
+
* Credentials stay runtime-resolved through `api.credentials`; the factory
|
|
5
|
+
* itself takes no configuration.
|
|
6
|
+
*/
|
|
7
|
+
export default ((api) => createImageGenerationCapabilityFactoryV1()(api));
|
|
8
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wCAAwC,EAAE,MAAM,iBAAiB,CAAC;AAE3E;;;;GAIG;AACH,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,wCAAwC,EAAE,CAAC,GAAG,CAAC,CAAuC,CAAC","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { createImageGenerationCapabilityFactoryV1 } from \"./capability.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n * Credentials stay runtime-resolved through `api.credentials`; the factory\n * itself takes no configuration.\n */\nexport default ((api) => createImageGenerationCapabilityFactoryV1()(api)) satisfies CapabilityPluginSyncFactory;\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ImagesClientV1 } from "./types.ts";
|
|
2
|
+
export interface ImagesClientOptionsV1 {
|
|
3
|
+
readonly baseUrl: string;
|
|
4
|
+
readonly apiKey: string;
|
|
5
|
+
readonly fetchImpl?: typeof fetch;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Minimal OpenAI-compatible images API client (globals only: fetch, FormData,
|
|
9
|
+
* Blob, AbortSignal.timeout/any on Node 22). Error messages carry the status
|
|
10
|
+
* and the first 300 characters of the response body only — never the API key
|
|
11
|
+
* or request headers.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createImagesClientV1(options: ImagesClientOptionsV1): ImagesClientV1;
|
|
14
|
+
//# sourceMappingURL=images-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images-client.d.ts","sourceRoot":"","sources":["../src/images-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA0B,cAAc,EAAyB,MAAM,YAAY,CAAC;AAKhG,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC;AAyCD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAuDnF","sourcesContent":["import type { ImagesClientResponseV1, ImagesClientV1, ImagesGeneratedItemV1 } from \"./types.ts\";\n\n/** Per-request budget: image edits with large inputs legitimately take minutes. */\nconst IMAGES_REQUEST_TIMEOUT_MS_V1 = 180_000;\n\nexport interface ImagesClientOptionsV1 {\n\treadonly baseUrl: string;\n\treadonly apiKey: string;\n\treadonly fetchImpl?: typeof fetch;\n}\n\n/** Combine the caller's signal with the fixed request timeout. */\nfunction requestSignal(signal: AbortSignal | undefined): AbortSignal {\n\tconst timeout = AbortSignal.timeout(IMAGES_REQUEST_TIMEOUT_MS_V1);\n\treturn signal === undefined ? timeout : AbortSignal.any([signal, timeout]);\n}\n\nfunction trimTrailingSlash(baseUrl: string): string {\n\treturn baseUrl.replace(/\\/+$/u, \"\");\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Map the OpenAI images response onto the client's normalized shape:\n * `b64_json`/`b64` stays inline, `url` is left for the capability layer to\n * download; unknown item shapes are dropped rather than guessed at.\n */\nfunction parseResponse(payload: unknown): ImagesClientResponseV1 {\n\tif (!isPlainObject(payload) || !Array.isArray(payload.data))\n\t\tthrow new Error(\"images api response has no data array\");\n\tconst data: ImagesGeneratedItemV1[] = [];\n\tfor (const entry of payload.data) {\n\t\tif (!isPlainObject(entry)) continue;\n\t\tif (typeof entry.b64_json === \"string\") data.push({ b64: entry.b64_json });\n\t\telse if (typeof entry.b64 === \"string\") data.push({ b64: entry.b64 });\n\t\telse if (typeof entry.url === \"string\")\n\t\t\tdata.push({\n\t\t\t\turl: entry.url,\n\t\t\t\t...(typeof entry.mime_type === \"string\" ? { mimeType: entry.mime_type } : {}),\n\t\t\t});\n\t}\n\treturn {\n\t\tdata,\n\t\t...(isPlainObject(payload.usage) ? { usage: payload.usage as Record<string, unknown> } : {}),\n\t};\n}\n\n/**\n * Minimal OpenAI-compatible images API client (globals only: fetch, FormData,\n * Blob, AbortSignal.timeout/any on Node 22). Error messages carry the status\n * and the first 300 characters of the response body only — never the API key\n * or request headers.\n */\nexport function createImagesClientV1(options: ImagesClientOptionsV1): ImagesClientV1 {\n\tconst fetchImpl: typeof fetch = options.fetchImpl ?? fetch;\n\tconst base = trimTrailingSlash(options.baseUrl);\n\n\tasync function post(path: string, init: RequestInit, signal: AbortSignal | undefined): Promise<unknown> {\n\t\tconst response = await fetchImpl(`${base}${path}`, { ...init, signal: requestSignal(signal) });\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(`images api ${path} failed with status ${response.status}: ${body.slice(0, 300)}`);\n\t\t}\n\t\treturn response.json();\n\t}\n\n\treturn {\n\t\tgenerateImages: async (request, signal) =>\n\t\t\tparseResponse(\n\t\t\t\tawait post(\n\t\t\t\t\t\"/images/generations\",\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"content-type\": \"application/json\", authorization: `Bearer ${options.apiKey}` },\n\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\tmodel: request.model,\n\t\t\t\t\t\t\tprompt: request.prompt,\n\t\t\t\t\t\t\tn: request.n,\n\t\t\t\t\t\t\t...(request.size === undefined ? {} : { size: request.size }),\n\t\t\t\t\t\t\t...(request.quality === undefined ? {} : { quality: request.quality }),\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t\t),\n\t\teditImage: async (request, signal) => {\n\t\t\t// Native FormData + Blob: fetch sets the multipart Content-Type with\n\t\t\t// its own boundary, so it must not be set manually here.\n\t\t\tconst form = new FormData();\n\t\t\tform.set(\"model\", request.model);\n\t\t\tform.set(\"prompt\", request.prompt);\n\t\t\tif (request.size !== undefined) form.set(\"size\", request.size);\n\t\t\tif (request.quality !== undefined) form.set(\"quality\", request.quality);\n\t\t\tform.set(\"image\", new Blob([request.image], { type: request.mimeType }), \"input.png\");\n\t\t\treturn parseResponse(\n\t\t\t\tawait post(\n\t\t\t\t\t\"/images/edits\",\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t// No content-type header: the boundary is owned by fetch.\n\t\t\t\t\t\theaders: { authorization: `Bearer ${options.apiKey}` },\n\t\t\t\t\t\tbody: form,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** Per-request budget: image edits with large inputs legitimately take minutes. */
|
|
2
|
+
const IMAGES_REQUEST_TIMEOUT_MS_V1 = 180_000;
|
|
3
|
+
/** Combine the caller's signal with the fixed request timeout. */
|
|
4
|
+
function requestSignal(signal) {
|
|
5
|
+
const timeout = AbortSignal.timeout(IMAGES_REQUEST_TIMEOUT_MS_V1);
|
|
6
|
+
return signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
|
|
7
|
+
}
|
|
8
|
+
function trimTrailingSlash(baseUrl) {
|
|
9
|
+
return baseUrl.replace(/\/+$/u, "");
|
|
10
|
+
}
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Map the OpenAI images response onto the client's normalized shape:
|
|
16
|
+
* `b64_json`/`b64` stays inline, `url` is left for the capability layer to
|
|
17
|
+
* download; unknown item shapes are dropped rather than guessed at.
|
|
18
|
+
*/
|
|
19
|
+
function parseResponse(payload) {
|
|
20
|
+
if (!isPlainObject(payload) || !Array.isArray(payload.data))
|
|
21
|
+
throw new Error("images api response has no data array");
|
|
22
|
+
const data = [];
|
|
23
|
+
for (const entry of payload.data) {
|
|
24
|
+
if (!isPlainObject(entry))
|
|
25
|
+
continue;
|
|
26
|
+
if (typeof entry.b64_json === "string")
|
|
27
|
+
data.push({ b64: entry.b64_json });
|
|
28
|
+
else if (typeof entry.b64 === "string")
|
|
29
|
+
data.push({ b64: entry.b64 });
|
|
30
|
+
else if (typeof entry.url === "string")
|
|
31
|
+
data.push({
|
|
32
|
+
url: entry.url,
|
|
33
|
+
...(typeof entry.mime_type === "string" ? { mimeType: entry.mime_type } : {}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
data,
|
|
38
|
+
...(isPlainObject(payload.usage) ? { usage: payload.usage } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Minimal OpenAI-compatible images API client (globals only: fetch, FormData,
|
|
43
|
+
* Blob, AbortSignal.timeout/any on Node 22). Error messages carry the status
|
|
44
|
+
* and the first 300 characters of the response body only — never the API key
|
|
45
|
+
* or request headers.
|
|
46
|
+
*/
|
|
47
|
+
export function createImagesClientV1(options) {
|
|
48
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
49
|
+
const base = trimTrailingSlash(options.baseUrl);
|
|
50
|
+
async function post(path, init, signal) {
|
|
51
|
+
const response = await fetchImpl(`${base}${path}`, { ...init, signal: requestSignal(signal) });
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
const body = await response.text().catch(() => "");
|
|
54
|
+
throw new Error(`images api ${path} failed with status ${response.status}: ${body.slice(0, 300)}`);
|
|
55
|
+
}
|
|
56
|
+
return response.json();
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
generateImages: async (request, signal) => parseResponse(await post("/images/generations", {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${options.apiKey}` },
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
model: request.model,
|
|
64
|
+
prompt: request.prompt,
|
|
65
|
+
n: request.n,
|
|
66
|
+
...(request.size === undefined ? {} : { size: request.size }),
|
|
67
|
+
...(request.quality === undefined ? {} : { quality: request.quality }),
|
|
68
|
+
}),
|
|
69
|
+
}, signal)),
|
|
70
|
+
editImage: async (request, signal) => {
|
|
71
|
+
// Native FormData + Blob: fetch sets the multipart Content-Type with
|
|
72
|
+
// its own boundary, so it must not be set manually here.
|
|
73
|
+
const form = new FormData();
|
|
74
|
+
form.set("model", request.model);
|
|
75
|
+
form.set("prompt", request.prompt);
|
|
76
|
+
if (request.size !== undefined)
|
|
77
|
+
form.set("size", request.size);
|
|
78
|
+
if (request.quality !== undefined)
|
|
79
|
+
form.set("quality", request.quality);
|
|
80
|
+
form.set("image", new Blob([request.image], { type: request.mimeType }), "input.png");
|
|
81
|
+
return parseResponse(await post("/images/edits", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
// No content-type header: the boundary is owned by fetch.
|
|
84
|
+
headers: { authorization: `Bearer ${options.apiKey}` },
|
|
85
|
+
body: form,
|
|
86
|
+
}, signal));
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=images-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images-client.js","sourceRoot":"","sources":["../src/images-client.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,MAAM,4BAA4B,GAAG,OAAO,CAAC;AAQ7C,kEAAkE;AAClE,SAAS,aAAa,CAAC,MAA+B,EAAe;IACpE,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAClE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,CAC3E;AAED,SAAS,iBAAiB,CAAC,OAAe,EAAU;IACnD,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAAA,CACpC;AAED,SAAS,aAAa,CAAC,KAAc,EAAoC;IACxE,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAAA,CAC5E;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,OAAgB,EAA0B;IAChE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC1D,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;aACtE,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;aACjE,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;YACrC,IAAI,CAAC,IAAI,CAAC;gBACT,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,GAAG,CAAC,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC7E,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACN,IAAI;QACJ,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAgC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5F,CAAC;AAAA,CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA8B,EAAkB;IACpF,MAAM,SAAS,GAAiB,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC3D,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhD,KAAK,UAAU,IAAI,CAAC,IAAY,EAAE,IAAiB,EAAE,MAA+B,EAAoB;QACvG,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/F,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,uBAAuB,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACpG,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IAAA,CACvB;IAED,OAAO;QACN,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CACzC,aAAa,CACZ,MAAM,IAAI,CACT,qBAAqB,EACrB;YACC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE,EAAE;YAC1F,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,CAAC,EAAE,OAAO,CAAC,CAAC;gBACZ,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC7D,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;aACtE,CAAC;SACF,EACD,MAAM,CACN,CACD;QACF,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;YACrC,qEAAqE;YACrE,yDAAyD;YACzD,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/D,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACxE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC;YACtF,OAAO,aAAa,CACnB,MAAM,IAAI,CACT,eAAe,EACf;gBACC,MAAM,EAAE,MAAM;gBACd,0DAA0D;gBAC1D,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE,EAAE;gBACtD,IAAI,EAAE,IAAI;aACV,EACD,MAAM,CACN,CACD,CAAC;QAAA,CACF;KACD,CAAC;AAAA,CACF","sourcesContent":["import type { ImagesClientResponseV1, ImagesClientV1, ImagesGeneratedItemV1 } from \"./types.ts\";\n\n/** Per-request budget: image edits with large inputs legitimately take minutes. */\nconst IMAGES_REQUEST_TIMEOUT_MS_V1 = 180_000;\n\nexport interface ImagesClientOptionsV1 {\n\treadonly baseUrl: string;\n\treadonly apiKey: string;\n\treadonly fetchImpl?: typeof fetch;\n}\n\n/** Combine the caller's signal with the fixed request timeout. */\nfunction requestSignal(signal: AbortSignal | undefined): AbortSignal {\n\tconst timeout = AbortSignal.timeout(IMAGES_REQUEST_TIMEOUT_MS_V1);\n\treturn signal === undefined ? timeout : AbortSignal.any([signal, timeout]);\n}\n\nfunction trimTrailingSlash(baseUrl: string): string {\n\treturn baseUrl.replace(/\\/+$/u, \"\");\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Map the OpenAI images response onto the client's normalized shape:\n * `b64_json`/`b64` stays inline, `url` is left for the capability layer to\n * download; unknown item shapes are dropped rather than guessed at.\n */\nfunction parseResponse(payload: unknown): ImagesClientResponseV1 {\n\tif (!isPlainObject(payload) || !Array.isArray(payload.data))\n\t\tthrow new Error(\"images api response has no data array\");\n\tconst data: ImagesGeneratedItemV1[] = [];\n\tfor (const entry of payload.data) {\n\t\tif (!isPlainObject(entry)) continue;\n\t\tif (typeof entry.b64_json === \"string\") data.push({ b64: entry.b64_json });\n\t\telse if (typeof entry.b64 === \"string\") data.push({ b64: entry.b64 });\n\t\telse if (typeof entry.url === \"string\")\n\t\t\tdata.push({\n\t\t\t\turl: entry.url,\n\t\t\t\t...(typeof entry.mime_type === \"string\" ? { mimeType: entry.mime_type } : {}),\n\t\t\t});\n\t}\n\treturn {\n\t\tdata,\n\t\t...(isPlainObject(payload.usage) ? { usage: payload.usage as Record<string, unknown> } : {}),\n\t};\n}\n\n/**\n * Minimal OpenAI-compatible images API client (globals only: fetch, FormData,\n * Blob, AbortSignal.timeout/any on Node 22). Error messages carry the status\n * and the first 300 characters of the response body only — never the API key\n * or request headers.\n */\nexport function createImagesClientV1(options: ImagesClientOptionsV1): ImagesClientV1 {\n\tconst fetchImpl: typeof fetch = options.fetchImpl ?? fetch;\n\tconst base = trimTrailingSlash(options.baseUrl);\n\n\tasync function post(path: string, init: RequestInit, signal: AbortSignal | undefined): Promise<unknown> {\n\t\tconst response = await fetchImpl(`${base}${path}`, { ...init, signal: requestSignal(signal) });\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(`images api ${path} failed with status ${response.status}: ${body.slice(0, 300)}`);\n\t\t}\n\t\treturn response.json();\n\t}\n\n\treturn {\n\t\tgenerateImages: async (request, signal) =>\n\t\t\tparseResponse(\n\t\t\t\tawait post(\n\t\t\t\t\t\"/images/generations\",\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"content-type\": \"application/json\", authorization: `Bearer ${options.apiKey}` },\n\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\tmodel: request.model,\n\t\t\t\t\t\t\tprompt: request.prompt,\n\t\t\t\t\t\t\tn: request.n,\n\t\t\t\t\t\t\t...(request.size === undefined ? {} : { size: request.size }),\n\t\t\t\t\t\t\t...(request.quality === undefined ? {} : { quality: request.quality }),\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t\t),\n\t\teditImage: async (request, signal) => {\n\t\t\t// Native FormData + Blob: fetch sets the multipart Content-Type with\n\t\t\t// its own boundary, so it must not be set manually here.\n\t\t\tconst form = new FormData();\n\t\t\tform.set(\"model\", request.model);\n\t\t\tform.set(\"prompt\", request.prompt);\n\t\t\tif (request.size !== undefined) form.set(\"size\", request.size);\n\t\t\tif (request.quality !== undefined) form.set(\"quality\", request.quality);\n\t\t\tform.set(\"image\", new Blob([request.image], { type: request.mimeType }), \"input.png\");\n\t\t\treturn parseResponse(\n\t\t\t\tawait post(\n\t\t\t\t\t\"/images/edits\",\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t// No content-type header: the boundary is owned by fetch.\n\t\t\t\t\t\theaders: { authorization: `Bearer ${options.apiKey}` },\n\t\t\t\t\t\tbody: form,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\t};\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createImageGenerationCapabilityFactoryV1 } from "./capability.ts";
|
|
2
|
+
export { createImagesClientV1 } from "./images-client.ts";
|
|
3
|
+
export type * from "./types.ts";
|
|
4
|
+
export { boundedImageMessageV1, IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1, } from "./types.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,mBAAmB,YAAY,CAAC;AAChC,OAAO,EACN,qBAAqB,EACrB,sCAAsC,EACtC,qCAAqC,GACrC,MAAM,YAAY,CAAC","sourcesContent":["export { createImageGenerationCapabilityFactoryV1 } from \"./capability.ts\";\nexport { createImagesClientV1 } from \"./images-client.ts\";\nexport type * from \"./types.ts\";\nexport {\n\tboundedImageMessageV1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\tIMAGE_GENERATION_MAX_MESSAGE_CHARS_V1,\n} from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createImageGenerationCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
export { createImagesClientV1 } from "./images-client.js";
|
|
3
|
+
export { boundedImageMessageV1, IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1, } from "./types.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,EACN,qBAAqB,EACrB,sCAAsC,EACtC,qCAAqC,GACrC,MAAM,YAAY,CAAC","sourcesContent":["export { createImageGenerationCapabilityFactoryV1 } from \"./capability.ts\";\nexport { createImagesClientV1 } from \"./images-client.ts\";\nexport type * from \"./types.ts\";\nexport {\n\tboundedImageMessageV1,\n\tIMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1,\n\tIMAGE_GENERATION_MAX_MESSAGE_CHARS_V1,\n} from \"./types.ts\";\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Hard cap for user-facing error messages (truncated, never grown). */
|
|
2
|
+
export declare const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;
|
|
3
|
+
/** Hard cap for a single downloaded image response (20 MiB). */
|
|
4
|
+
export declare const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1: number;
|
|
5
|
+
/** One generated image in either wire form the OpenAI-compatible API returns. */
|
|
6
|
+
export interface ImagesGeneratedItemV1 {
|
|
7
|
+
/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */
|
|
8
|
+
readonly b64?: string;
|
|
9
|
+
/** Remote image URL; the capability layer downloads it. */
|
|
10
|
+
readonly url?: string;
|
|
11
|
+
readonly mimeType?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ImagesClientResponseV1 {
|
|
14
|
+
readonly data: readonly ImagesGeneratedItemV1[];
|
|
15
|
+
readonly usage?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface ImagesGenerateRequestV1 {
|
|
18
|
+
readonly model: string;
|
|
19
|
+
readonly prompt: string;
|
|
20
|
+
readonly n: number;
|
|
21
|
+
readonly size?: string;
|
|
22
|
+
readonly quality?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ImagesEditRequestV1 {
|
|
25
|
+
readonly image: Uint8Array;
|
|
26
|
+
readonly mimeType: string;
|
|
27
|
+
readonly prompt: string;
|
|
28
|
+
readonly model: string;
|
|
29
|
+
readonly size?: string;
|
|
30
|
+
readonly quality?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ImagesClientV1 {
|
|
33
|
+
generateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;
|
|
34
|
+
editImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;
|
|
35
|
+
}
|
|
36
|
+
/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */
|
|
37
|
+
export interface ImageGenResultV1 {
|
|
38
|
+
readonly version: 1;
|
|
39
|
+
readonly status: "succeeded" | "failed" | "cancelled";
|
|
40
|
+
readonly files: readonly string[];
|
|
41
|
+
readonly model: string;
|
|
42
|
+
readonly usage?: Record<string, unknown>;
|
|
43
|
+
readonly error?: {
|
|
44
|
+
readonly code: string;
|
|
45
|
+
readonly message: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Truncate a message to the bounded error budget; never grows the input. */
|
|
49
|
+
export declare function boundedImageMessageV1(message: string): string;
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,qCAAqC,MAAM,CAAC;AAEzD,gEAAgE;AAChE,eAAO,MAAM,sCAAsC,QAAmB,CAAC;AAEvE,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACrC,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,cAAc,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxG,SAAS,CAAC,OAAO,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC/F;AAED,qGAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI7D","sourcesContent":["/** Hard cap for user-facing error messages (truncated, never grown). */\nexport const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;\n\n/** Hard cap for a single downloaded image response (20 MiB). */\nexport const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;\n\n/** One generated image in either wire form the OpenAI-compatible API returns. */\nexport interface ImagesGeneratedItemV1 {\n\t/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */\n\treadonly b64?: string;\n\t/** Remote image URL; the capability layer downloads it. */\n\treadonly url?: string;\n\treadonly mimeType?: string;\n}\n\nexport interface ImagesClientResponseV1 {\n\treadonly data: readonly ImagesGeneratedItemV1[];\n\treadonly usage?: Record<string, unknown>;\n}\n\nexport interface ImagesGenerateRequestV1 {\n\treadonly model: string;\n\treadonly prompt: string;\n\treadonly n: number;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesEditRequestV1 {\n\treadonly image: Uint8Array;\n\treadonly mimeType: string;\n\treadonly prompt: string;\n\treadonly model: string;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesClientV1 {\n\tgenerateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n\teditImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n}\n\n/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */\nexport interface ImageGenResultV1 {\n\treadonly version: 1;\n\treadonly status: \"succeeded\" | \"failed\" | \"cancelled\";\n\treadonly files: readonly string[];\n\treadonly model: string;\n\treadonly usage?: Record<string, unknown>;\n\treadonly error?: { readonly code: string; readonly message: string };\n}\n\n/** Truncate a message to the bounded error budget; never grows the input. */\nexport function boundedImageMessageV1(message: string): string {\n\treturn message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1\n\t\t? message\n\t\t: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;\n}\n"]}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Hard cap for user-facing error messages (truncated, never grown). */
|
|
2
|
+
export const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;
|
|
3
|
+
/** Hard cap for a single downloaded image response (20 MiB). */
|
|
4
|
+
export const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;
|
|
5
|
+
/** Truncate a message to the bounded error budget; never grows the input. */
|
|
6
|
+
export function boundedImageMessageV1(message) {
|
|
7
|
+
return message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1
|
|
8
|
+
? message
|
|
9
|
+
: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,qCAAqC,GAAG,GAAG,CAAC;AAEzD,gEAAgE;AAChE,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAgDvE,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CAAC,OAAe,EAAU;IAC9D,OAAO,OAAO,CAAC,MAAM,IAAI,qCAAqC;QAC7D,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,qCAAqC,GAAG,CAAC,CAAC,KAAK,CAAC;AAAA,CACvE","sourcesContent":["/** Hard cap for user-facing error messages (truncated, never grown). */\nexport const IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 = 512;\n\n/** Hard cap for a single downloaded image response (20 MiB). */\nexport const IMAGE_GENERATION_MAX_DOWNLOAD_BYTES_V1 = 20 * 1024 * 1024;\n\n/** One generated image in either wire form the OpenAI-compatible API returns. */\nexport interface ImagesGeneratedItemV1 {\n\t/** Inline base64 image payload (`data[i].b64_json` / `data[i].b64`). */\n\treadonly b64?: string;\n\t/** Remote image URL; the capability layer downloads it. */\n\treadonly url?: string;\n\treadonly mimeType?: string;\n}\n\nexport interface ImagesClientResponseV1 {\n\treadonly data: readonly ImagesGeneratedItemV1[];\n\treadonly usage?: Record<string, unknown>;\n}\n\nexport interface ImagesGenerateRequestV1 {\n\treadonly model: string;\n\treadonly prompt: string;\n\treadonly n: number;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesEditRequestV1 {\n\treadonly image: Uint8Array;\n\treadonly mimeType: string;\n\treadonly prompt: string;\n\treadonly model: string;\n\treadonly size?: string;\n\treadonly quality?: string;\n}\n\nexport interface ImagesClientV1 {\n\tgenerateImages(request: ImagesGenerateRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n\teditImage(request: ImagesEditRequestV1, signal?: AbortSignal): Promise<ImagesClientResponseV1>;\n}\n\n/** Normalized image-generation tool result (design `基础插件生态与控制能力设计.md`). */\nexport interface ImageGenResultV1 {\n\treadonly version: 1;\n\treadonly status: \"succeeded\" | \"failed\" | \"cancelled\";\n\treadonly files: readonly string[];\n\treadonly model: string;\n\treadonly usage?: Record<string, unknown>;\n\treadonly error?: { readonly code: string; readonly message: string };\n}\n\n/** Truncate a message to the bounded error budget; never grows the input. */\nexport function boundedImageMessageV1(message: string): string {\n\treturn message.length <= IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1\n\t\t? message\n\t\t: `${message.slice(0, IMAGE_GENERATION_MAX_MESSAGE_CHARS_V1 - 3)}...`;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-agent-forge/plugin-image-generation",
|
|
3
|
+
"version": "0.85.10",
|
|
4
|
+
"description": "Image generation capability plugin for Agent Forge (image_generate / image_edit tools)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"agent-forge.json",
|
|
17
|
+
"plugin.json",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "shx rm -rf dist",
|
|
22
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
23
|
+
"test": "vitest --run",
|
|
24
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"agent-forge",
|
|
28
|
+
"plugin",
|
|
29
|
+
"image",
|
|
30
|
+
"generation"
|
|
31
|
+
],
|
|
32
|
+
"author": "Agent Forge contributors",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/agent-forge/agent-forge.git",
|
|
37
|
+
"directory": "plugins/image-generation"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/agent-forge/agent-forge/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/agent-forge/agent-forge#readme",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.19.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"typebox": "1.3.27",
|
|
48
|
+
"@ai-agent-forge/plugin-sdk": "^0.85.10"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"shx": "0.4.0",
|
|
52
|
+
"vitest": "4.1.9",
|
|
53
|
+
"@ai-agent-forge/agent-forge": "^0.85.10"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "agent-forge.plugin.image-generation",
|
|
3
|
+
"version": "0.85.10",
|
|
4
|
+
"apiVersion": "1-draft",
|
|
5
|
+
"entry": "./dist/entry.js",
|
|
6
|
+
"hostVersion": ">=0.84.4",
|
|
7
|
+
"requiredCapabilities": [
|
|
8
|
+
"tools"
|
|
9
|
+
],
|
|
10
|
+
"provides": [
|
|
11
|
+
{
|
|
12
|
+
"id": "image.generation",
|
|
13
|
+
"version": 1,
|
|
14
|
+
"kind": "contribution"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|