opencode-img 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +99 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/image.d.ts +20 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +66 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/openai-provider.d.ts +16 -0
- package/dist/openai-provider.d.ts.map +1 -0
- package/dist/openai-provider.js +132 -0
- package/dist/openai-provider.js.map +1 -0
- package/dist/paths.d.ts +17 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +62 -0
- package/dist/paths.js.map +1 -0
- package/dist/provider.d.ts +28 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +2 -0
- package/dist/provider.js.map +1 -0
- package/dist/tool.d.ts +25 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +164 -0
- package/dist/tool.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Safaii
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# opencode-img
|
|
2
|
+
|
|
3
|
+
An OpenCode V2 plugin that generates and edits bitmap images through the OpenAI Image API. One tool, `gpt_imagegen`, takes a prompt and an output path, calls the API with your key, and writes one image to disk.
|
|
4
|
+
|
|
5
|
+
[](https://opencode.ai/v2/docs/build/plugins)
|
|
6
|
+
[](https://www.npmjs.com/package/opencode-img)
|
|
7
|
+
[](https://github.com/mattsafaii/opencode-img/actions/workflows/ci.yml)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
Source: [github.com/mattsafaii/opencode-img](https://github.com/mattsafaii/opencode-img)
|
|
11
|
+
|
|
12
|
+
**Status:** 0.1.0, early release.
|
|
13
|
+
|
|
14
|
+
## What it does
|
|
15
|
+
|
|
16
|
+
- **Generates and edits.** A prompt makes a new image; local `referenceImages` edit an existing one through the real `/v1/images/edits` multipart endpoint.
|
|
17
|
+
- **Never overwrites.** A taken output path gets the next version instead.
|
|
18
|
+
- **Your key, two ways.** The OpenCode connection from `/connect`, or `OPENAI_API_KEY`.
|
|
19
|
+
- **No SDK.** Native `fetch`, `FormData`, and `Blob` only.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
OpenCode installs the package on next launch, so there is no separate install step. Add it with the CLI:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
opencode plugin add opencode-img
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or add it to `opencode.json`:
|
|
30
|
+
|
|
31
|
+
```jsonc
|
|
32
|
+
{
|
|
33
|
+
"$schema": "https://opencode.ai/config.json",
|
|
34
|
+
"plugins": ["opencode-img"]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Add your OpenAI API key
|
|
39
|
+
|
|
40
|
+
Connect OpenAI in OpenCode, or set `OPENAI_API_KEY`. When both are present, the environment variable wins.
|
|
41
|
+
|
|
42
|
+
1. **OpenCode connection (easiest).** Run `/connect`, choose OpenAI, and paste your key. The tool uses that connection with no further setup.
|
|
43
|
+
2. **Environment variable.** Set `OPENAI_API_KEY` in the environment of the OpenCode server before it starts. Use it for headless setups, or when your key lives in the OpenCode Console (BYOK), which plugins cannot read.
|
|
44
|
+
|
|
45
|
+
A missing key fails before any network request, and the error names both.
|
|
46
|
+
|
|
47
|
+
A key connected through the OpenCode Console (BYOK) stays in Console and is not available to plugins; set `OPENAI_API_KEY` in that case.
|
|
48
|
+
|
|
49
|
+
## Use
|
|
50
|
+
|
|
51
|
+
Ask the agent for what you want, and it calls `gpt_imagegen`:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
Generate a 1024x1024 image of a red square on a white background and save it to assets/square.png.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Tool arguments
|
|
58
|
+
|
|
59
|
+
| Argument | Required | Description |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `prompt` | yes | What to generate, or the edit to make. |
|
|
62
|
+
| `outputPath` | yes | Where to save the image. Relative paths resolve against the session directory. |
|
|
63
|
+
| `model` | no | OpenAI image model. Defaults to `gpt-image-1.5`. |
|
|
64
|
+
| `quality` | no | `auto`, `low`, `medium`, `high`, `standard`, `hd`, `xhigh`, or `max`. |
|
|
65
|
+
| `size` | no | `WIDTHxHEIGHT` (for example `1024x1024`) or `auto`. |
|
|
66
|
+
| `outputFormat` | no | `png`, `jpeg`, or `webp` for GPT image models. Inferred from the output path extension when omitted. |
|
|
67
|
+
| `referenceImages` | no | Local image paths. When present, the tool edits them through `/v1/images/edits`. |
|
|
68
|
+
|
|
69
|
+
## Worked examples
|
|
70
|
+
|
|
71
|
+
### Edit an existing image
|
|
72
|
+
|
|
73
|
+
Pass local paths in `referenceImages` and the tool edits them.
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
Take assets/example-generate.png, change the coffee cup to a teacup, and add a spoon on the saucer. Save it to assets/example-edit.png.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Generate from a prompt
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Generate a flat vector illustration of a steaming coffee cup on a saucer, two-tone, centered on a plain white background, and save it to assets/example-generate.png.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Run the same path twice
|
|
86
|
+
|
|
87
|
+
The first file is left untouched; the new image lands at `example-generate-1.png`.
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
Now do the same path, but a slice of cake on a plate instead.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Output
|
|
94
|
+
|
|
95
|
+
- One image per request.
|
|
96
|
+
- An existing file is never replaced: `image.png` becomes `image-1.png`, then `image-2.png`.
|
|
97
|
+
- An explicit `outputFormat` must match a known image extension on the path; a conflict is rejected.
|
|
98
|
+
- A failure — a missing key, an unreadable reference image, an API error, or a cancellation — stops before the write, so no file is created.
|
|
99
|
+
- The tool returns the saved path plus metadata: model, MIME type, byte count, and the settings used.
|
|
100
|
+
|
|
101
|
+
## Disclaimer
|
|
102
|
+
|
|
103
|
+
opencode-img is an independent project. It is not made by, affiliated with, or endorsed by OpenAI or the OpenCode team. Using the OpenAI Image API is subject to OpenAI's terms and usage policies.
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
pnpm install
|
|
109
|
+
pnpm test
|
|
110
|
+
pnpm typecheck
|
|
111
|
+
pnpm build
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Built and tested on Node 24. OpenCode runs the plugin in its bundled Bun runtime.
|
|
115
|
+
|
|
116
|
+
To load a local build in OpenCode, point `plugins` at the package's `dist` directory (a configured plugin path must be a directory containing an entry file):
|
|
117
|
+
|
|
118
|
+
```jsonc
|
|
119
|
+
{
|
|
120
|
+
"$schema": "https://opencode.ai/config.json",
|
|
121
|
+
"plugins": ["/absolute/path/to/opencode-img/dist"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Publish with `pnpm publish`, or push a `v*` tag to publish through `.github/workflows/publish.yml`. The workflow uses npm trusted publishing (OIDC), so it needs no token — only a trusted publisher configured for the repository on npmjs.com.
|
|
126
|
+
|
|
127
|
+
The provider boundary stays internal so another image service can be added later without a public plugin registry.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** The OpenAI image model used when the caller does not name one. */
|
|
2
|
+
export declare const DEFAULT_MODEL = "gpt-image-1.5";
|
|
3
|
+
/** Quality values accepted by the OpenAI Images API. */
|
|
4
|
+
export declare const QUALITY_VALUES: readonly ["auto", "standard", "hd", "low", "medium", "high", "xhigh", "max"];
|
|
5
|
+
/** Output formats accepted by the GPT image models. */
|
|
6
|
+
export declare const OUTPUT_FORMAT_VALUES: readonly ["png", "jpeg", "webp"];
|
|
7
|
+
export type Quality = (typeof QUALITY_VALUES)[number];
|
|
8
|
+
export type OutputFormat = (typeof OUTPUT_FORMAT_VALUES)[number];
|
|
9
|
+
export interface GenerationSettings {
|
|
10
|
+
model: string;
|
|
11
|
+
quality?: Quality;
|
|
12
|
+
size?: string;
|
|
13
|
+
outputFormat?: OutputFormat;
|
|
14
|
+
}
|
|
15
|
+
export interface GenerationSettingsInput {
|
|
16
|
+
model?: unknown;
|
|
17
|
+
quality?: unknown;
|
|
18
|
+
size?: unknown;
|
|
19
|
+
outputFormat?: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** True when the model is one of the GPT image models rather than a DALL·E model. */
|
|
22
|
+
export declare function isGptImageModel(model: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The output format implied by a path's extension, or `undefined` when the
|
|
25
|
+
* extension is absent or not one the API produces.
|
|
26
|
+
*/
|
|
27
|
+
export declare function outputFormatForExtension(filePath: string): OutputFormat | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Validate the optional generation settings and apply the model default.
|
|
30
|
+
*
|
|
31
|
+
* Size is checked as a shape (`auto` or `WIDTHxHEIGHT`) rather than a fixed
|
|
32
|
+
* list so newer models that accept arbitrary resolutions keep working.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveSettings(input: GenerationSettingsInput): GenerationSettings;
|
|
35
|
+
/**
|
|
36
|
+
* Keep the encoded format and the output filename in agreement.
|
|
37
|
+
*
|
|
38
|
+
* An explicit `outputFormat` must match a known image extension on the path.
|
|
39
|
+
* When the format is omitted, a known extension selects it so the bytes never
|
|
40
|
+
* contradict the name. DALL·E models can only produce PNG.
|
|
41
|
+
*/
|
|
42
|
+
export declare function reconcileOutputFormat(settings: GenerationSettings, targetPath: string): GenerationSettings;
|
|
43
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,qEAAqE;AACrE,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,wDAAwD;AACxD,eAAO,MAAM,cAAc,8EASjB,CAAA;AAEV,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,kCAAmC,CAAA;AAEpE,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AACrD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAYnF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,uBAAuB,GAAG,kBAAkB,CAsClF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAuBpB"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { extname } from "node:path";
|
|
2
|
+
import { invalidArgument, requireNonEmptyString } from "./errors.js";
|
|
3
|
+
/** The OpenAI image model used when the caller does not name one. */
|
|
4
|
+
export const DEFAULT_MODEL = "gpt-image-1.5";
|
|
5
|
+
/** Quality values accepted by the OpenAI Images API. */
|
|
6
|
+
export const QUALITY_VALUES = [
|
|
7
|
+
"auto",
|
|
8
|
+
"standard",
|
|
9
|
+
"hd",
|
|
10
|
+
"low",
|
|
11
|
+
"medium",
|
|
12
|
+
"high",
|
|
13
|
+
"xhigh",
|
|
14
|
+
"max",
|
|
15
|
+
];
|
|
16
|
+
/** Output formats accepted by the GPT image models. */
|
|
17
|
+
export const OUTPUT_FORMAT_VALUES = ["png", "jpeg", "webp"];
|
|
18
|
+
/** True when the model is one of the GPT image models rather than a DALL·E model. */
|
|
19
|
+
export function isGptImageModel(model) {
|
|
20
|
+
return !model.startsWith("dall-e");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The output format implied by a path's extension, or `undefined` when the
|
|
24
|
+
* extension is absent or not one the API produces.
|
|
25
|
+
*/
|
|
26
|
+
export function outputFormatForExtension(filePath) {
|
|
27
|
+
switch (extname(filePath).toLowerCase()) {
|
|
28
|
+
case ".png":
|
|
29
|
+
return "png";
|
|
30
|
+
case ".jpg":
|
|
31
|
+
case ".jpeg":
|
|
32
|
+
return "jpeg";
|
|
33
|
+
case ".webp":
|
|
34
|
+
return "webp";
|
|
35
|
+
default:
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate the optional generation settings and apply the model default.
|
|
41
|
+
*
|
|
42
|
+
* Size is checked as a shape (`auto` or `WIDTHxHEIGHT`) rather than a fixed
|
|
43
|
+
* list so newer models that accept arbitrary resolutions keep working.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveSettings(input) {
|
|
46
|
+
const model = input.model === undefined ? DEFAULT_MODEL : requireNonEmptyString(input.model, "model");
|
|
47
|
+
const settings = { model };
|
|
48
|
+
if (input.quality !== undefined) {
|
|
49
|
+
const quality = requireNonEmptyString(input.quality, "quality");
|
|
50
|
+
if (!QUALITY_VALUES.includes(quality)) {
|
|
51
|
+
throw invalidArgument(`\`quality\` must be one of ${QUALITY_VALUES.join(", ")}.`);
|
|
52
|
+
}
|
|
53
|
+
settings.quality = quality;
|
|
54
|
+
}
|
|
55
|
+
if (input.size !== undefined) {
|
|
56
|
+
const size = requireNonEmptyString(input.size, "size");
|
|
57
|
+
if (size !== "auto" && !/^\d+x\d+$/.test(size)) {
|
|
58
|
+
throw invalidArgument("`size` must be `auto` or a `WIDTHxHEIGHT` string such as `1024x1024`.");
|
|
59
|
+
}
|
|
60
|
+
settings.size = size;
|
|
61
|
+
}
|
|
62
|
+
if (input.outputFormat !== undefined) {
|
|
63
|
+
const outputFormat = requireNonEmptyString(input.outputFormat, "outputFormat");
|
|
64
|
+
if (!OUTPUT_FORMAT_VALUES.includes(outputFormat)) {
|
|
65
|
+
throw invalidArgument(`\`outputFormat\` must be one of ${OUTPUT_FORMAT_VALUES.join(", ")}.`);
|
|
66
|
+
}
|
|
67
|
+
if (!isGptImageModel(model)) {
|
|
68
|
+
throw invalidArgument("`outputFormat` is only supported by the GPT image models; DALL·E models always return PNG.");
|
|
69
|
+
}
|
|
70
|
+
settings.outputFormat = outputFormat;
|
|
71
|
+
}
|
|
72
|
+
return settings;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Keep the encoded format and the output filename in agreement.
|
|
76
|
+
*
|
|
77
|
+
* An explicit `outputFormat` must match a known image extension on the path.
|
|
78
|
+
* When the format is omitted, a known extension selects it so the bytes never
|
|
79
|
+
* contradict the name. DALL·E models can only produce PNG.
|
|
80
|
+
*/
|
|
81
|
+
export function reconcileOutputFormat(settings, targetPath) {
|
|
82
|
+
const extensionFormat = outputFormatForExtension(targetPath);
|
|
83
|
+
if (extensionFormat === undefined)
|
|
84
|
+
return settings;
|
|
85
|
+
if (settings.outputFormat !== undefined) {
|
|
86
|
+
if (settings.outputFormat !== extensionFormat) {
|
|
87
|
+
throw invalidArgument(`\`outputFormat\` is \`${settings.outputFormat}\` but \`outputPath\` ends in \`.${extensionFormat}\`. Align them or drop \`outputFormat\`.`);
|
|
88
|
+
}
|
|
89
|
+
return settings;
|
|
90
|
+
}
|
|
91
|
+
if (!isGptImageModel(settings.model)) {
|
|
92
|
+
if (extensionFormat !== "png") {
|
|
93
|
+
throw invalidArgument(`DALL·E models always return PNG, so \`outputPath\` cannot end in \`.${extensionFormat}\`.`);
|
|
94
|
+
}
|
|
95
|
+
return settings;
|
|
96
|
+
}
|
|
97
|
+
return { ...settings, outputFormat: extensionFormat };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error raised by the `gpt_imagegen` tool and its internal image provider.
|
|
3
|
+
*
|
|
4
|
+
* Every error names the operation that failed and carries a stable `code` so
|
|
5
|
+
* callers and tests can branch on the failure without matching message text.
|
|
6
|
+
* Messages never include the API key or other request credentials.
|
|
7
|
+
*/
|
|
8
|
+
export type ImageToolErrorCode = "missing_api_key" | "invalid_argument" | "invalid_reference" | "api_error" | "malformed_response" | "cancelled" | "write_failed";
|
|
9
|
+
/** The operations the tool and provider report failures against. */
|
|
10
|
+
export type ImageToolOperation = "validate API key" | "validate arguments" | "read reference image" | "generate image" | "edit image" | "decode image response" | "write image file";
|
|
11
|
+
export declare class ImageToolError extends Error {
|
|
12
|
+
readonly code: ImageToolErrorCode;
|
|
13
|
+
readonly operation: ImageToolOperation;
|
|
14
|
+
constructor(code: ImageToolErrorCode, operation: ImageToolOperation, message: string, options?: {
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** An invalid-argument error with the shared validation operation. */
|
|
19
|
+
export declare function invalidArgument(message: string): ImageToolError;
|
|
20
|
+
/** Return `value` when it is a non-empty string, otherwise fail validation. */
|
|
21
|
+
export declare function requireNonEmptyString(value: unknown, field: string): string;
|
|
22
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,GACnB,WAAW,GACX,oBAAoB,GACpB,WAAW,GACX,cAAc,CAAA;AAElB,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAC1B,kBAAkB,GAClB,oBAAoB,GACpB,sBAAsB,GACtB,gBAAgB,GAChB,YAAY,GACZ,uBAAuB,GACvB,kBAAkB,CAAA;AAEtB,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;IACjC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;gBAGpC,IAAI,EAAE,kBAAkB,EACxB,SAAS,EAAE,kBAAkB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAOhC;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAE/D;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK3E"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class ImageToolError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
operation;
|
|
4
|
+
constructor(code, operation, message, options) {
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.name = "ImageToolError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.operation = operation;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** An invalid-argument error with the shared validation operation. */
|
|
12
|
+
export function invalidArgument(message) {
|
|
13
|
+
return new ImageToolError("invalid_argument", "validate arguments", message);
|
|
14
|
+
}
|
|
15
|
+
/** Return `value` when it is a non-empty string, otherwise fail validation. */
|
|
16
|
+
export function requireNonEmptyString(value, field) {
|
|
17
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
18
|
+
throw invalidArgument(`\`${field}\` must be a non-empty string.`);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA0BA,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAoB;IACxB,SAAS,CAAoB;IAEtC,YACE,IAAwB,EACxB,SAA6B,EAC7B,OAAe,EACf,OAA6B;QAE7B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;IAC5B,CAAC;CACF;AAED,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,IAAI,cAAc,CAAC,kBAAkB,EAAE,oBAAoB,EAAE,OAAO,CAAC,CAAA;AAC9E,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,qBAAqB,CAAC,KAAc,EAAE,KAAa;IACjE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,eAAe,CAAC,KAAK,KAAK,gCAAgC,CAAC,CAAA;IACnE,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
|
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OutputFormat } from "./config.ts";
|
|
2
|
+
export interface DecodedImage {
|
|
3
|
+
data: Uint8Array;
|
|
4
|
+
revisedPrompt?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pull the first image out of an OpenAI Images response.
|
|
8
|
+
*
|
|
9
|
+
* GPT image models and DALL·E models configured with `response_format=b64_json`
|
|
10
|
+
* both return `data[0].b64_json`. The bytes are decoded here and checked for
|
|
11
|
+
* emptiness so a zero-byte or corrupt payload never reaches the filesystem.
|
|
12
|
+
*/
|
|
13
|
+
export declare function decodeImageResponse(payload: unknown): DecodedImage;
|
|
14
|
+
/** The MIME type OpenAI returns for a given output format. */
|
|
15
|
+
export declare function mimeTypeForOutputFormat(format: OutputFormat | undefined): string;
|
|
16
|
+
/** The MIME type of a reference image, or `undefined` for an unsupported type. */
|
|
17
|
+
export declare function mimeTypeForReferencePath(filePath: string): string | undefined;
|
|
18
|
+
/** The extensions the edits endpoint accepts for reference images. */
|
|
19
|
+
export declare const SUPPORTED_REFERENCE_EXTENSIONS: string[];
|
|
20
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAYD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CA6BlE;AAED,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,CAUhF;AASD,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI7E;AAED,sEAAsE;AACtE,eAAO,MAAM,8BAA8B,UAAiC,CAAA"}
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ImageToolError } from "./errors.js";
|
|
2
|
+
const BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
3
|
+
function malformed(detail) {
|
|
4
|
+
return new ImageToolError("malformed_response", "decode image response", `OpenAI returned image data that could not be read: ${detail}.`);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pull the first image out of an OpenAI Images response.
|
|
8
|
+
*
|
|
9
|
+
* GPT image models and DALL·E models configured with `response_format=b64_json`
|
|
10
|
+
* both return `data[0].b64_json`. The bytes are decoded here and checked for
|
|
11
|
+
* emptiness so a zero-byte or corrupt payload never reaches the filesystem.
|
|
12
|
+
*/
|
|
13
|
+
export function decodeImageResponse(payload) {
|
|
14
|
+
if (typeof payload !== "object" || payload === null) {
|
|
15
|
+
throw malformed("the response body was not a JSON object");
|
|
16
|
+
}
|
|
17
|
+
const data = payload.data;
|
|
18
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
19
|
+
throw malformed("the response contained no images");
|
|
20
|
+
}
|
|
21
|
+
const first = data[0];
|
|
22
|
+
const encoded = first?.b64_json;
|
|
23
|
+
if (typeof encoded !== "string" || encoded === "") {
|
|
24
|
+
throw malformed("the image entry had no base64 payload");
|
|
25
|
+
}
|
|
26
|
+
if (encoded.length % 4 !== 0 || !BASE64_PATTERN.test(encoded)) {
|
|
27
|
+
throw malformed("the base64 payload was not valid base64");
|
|
28
|
+
}
|
|
29
|
+
const bytes = Buffer.from(encoded, "base64");
|
|
30
|
+
if (bytes.byteLength === 0) {
|
|
31
|
+
throw malformed("the decoded image was empty");
|
|
32
|
+
}
|
|
33
|
+
const decoded = { data: bytes };
|
|
34
|
+
if (typeof first.revised_prompt === "string" && first.revised_prompt !== "") {
|
|
35
|
+
decoded.revisedPrompt = first.revised_prompt;
|
|
36
|
+
}
|
|
37
|
+
return decoded;
|
|
38
|
+
}
|
|
39
|
+
/** The MIME type OpenAI returns for a given output format. */
|
|
40
|
+
export function mimeTypeForOutputFormat(format) {
|
|
41
|
+
switch (format) {
|
|
42
|
+
case "jpeg":
|
|
43
|
+
return "image/jpeg";
|
|
44
|
+
case "webp":
|
|
45
|
+
return "image/webp";
|
|
46
|
+
case "png":
|
|
47
|
+
case undefined:
|
|
48
|
+
return "image/png";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const MIME_BY_EXTENSION = {
|
|
52
|
+
".png": "image/png",
|
|
53
|
+
".jpg": "image/jpeg",
|
|
54
|
+
".jpeg": "image/jpeg",
|
|
55
|
+
".webp": "image/webp",
|
|
56
|
+
};
|
|
57
|
+
/** The MIME type of a reference image, or `undefined` for an unsupported type. */
|
|
58
|
+
export function mimeTypeForReferencePath(filePath) {
|
|
59
|
+
const dot = filePath.lastIndexOf(".");
|
|
60
|
+
if (dot === -1)
|
|
61
|
+
return undefined;
|
|
62
|
+
return MIME_BY_EXTENSION[filePath.slice(dot).toLowerCase()];
|
|
63
|
+
}
|
|
64
|
+
/** The extensions the edits endpoint accepts for reference images. */
|
|
65
|
+
export const SUPPORTED_REFERENCE_EXTENSIONS = Object.keys(MIME_BY_EXTENSION);
|
|
66
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAQ5C,MAAM,cAAc,GAAG,wBAAwB,CAAA;AAE/C,SAAS,SAAS,CAAC,MAAc;IAC/B,OAAO,IAAI,cAAc,CACvB,oBAAoB,EACpB,uBAAuB,EACvB,sDAAsD,MAAM,GAAG,CAChE,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,SAAS,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IAED,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAA;IACjD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,SAAS,CAAC,kCAAkC,CAAC,CAAA;IACrD,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAqD,CAAA;IACzE,MAAM,OAAO,GAAG,KAAK,EAAE,QAAQ,CAAA;IAC/B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAClD,MAAM,SAAS,CAAC,uCAAuC,CAAC,CAAA;IAC1D,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9D,MAAM,SAAS,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC5C,IAAI,KAAK,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,SAAS,CAAC,6BAA6B,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,OAAO,GAAiB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IAC7C,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ,IAAI,KAAK,CAAC,cAAc,KAAK,EAAE,EAAE,CAAC;QAC5E,OAAO,CAAC,aAAa,GAAG,KAAK,CAAC,cAAc,CAAA;IAC9C,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,uBAAuB,CAAC,MAAgC;IACtE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,OAAO,YAAY,CAAA;QACrB,KAAK,MAAM;YACT,OAAO,YAAY,CAAA;QACrB,KAAK,KAAK,CAAC;QACX,KAAK,SAAS;YACZ,OAAO,WAAW,CAAA;IACtB,CAAC;AACH,CAAC;AAED,MAAM,iBAAiB,GAAqC;IAC1D,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,OAAO,EAAE,YAAY;CACtB,CAAA;AAED,kFAAkF;AAClF,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACrC,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IAChC,OAAO,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;AAC7D,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,MAAM,8BAA8B,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* opencode-img — an OpenCode V2 plugin that adds the `gpt_imagegen` tool.
|
|
4
|
+
*
|
|
5
|
+
* Relative output and reference-image paths resolve against the session
|
|
6
|
+
* directory. The OpenAI API key comes from `OPENAI_API_KEY`, then the OpenAI
|
|
7
|
+
* connection the user made with `/connect`.
|
|
8
|
+
*/
|
|
9
|
+
declare const _default: Plugin.Plugin;
|
|
10
|
+
export default _default;
|
|
11
|
+
//# 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,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAMzC;;;;;;GAMG;;AACH,wBAuBE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
import { createGptImagegenTool } from "./tool.js";
|
|
3
|
+
/** OpenCode's built-in integration id for OpenAI. */
|
|
4
|
+
const OPENAI_INTEGRATION_ID = "openai";
|
|
5
|
+
/**
|
|
6
|
+
* opencode-img — an OpenCode V2 plugin that adds the `gpt_imagegen` tool.
|
|
7
|
+
*
|
|
8
|
+
* Relative output and reference-image paths resolve against the session
|
|
9
|
+
* directory. The OpenAI API key comes from `OPENAI_API_KEY`, then the OpenAI
|
|
10
|
+
* connection the user made with `/connect`.
|
|
11
|
+
*/
|
|
12
|
+
export default Plugin.define({
|
|
13
|
+
id: "opencode-img",
|
|
14
|
+
async setup(ctx) {
|
|
15
|
+
await ctx.tool.transform((editor) => {
|
|
16
|
+
editor.add(createGptImagegenTool({
|
|
17
|
+
resolveSessionDirectory: async (sessionID) => {
|
|
18
|
+
const session = await ctx.session.get({ sessionID });
|
|
19
|
+
return session.location.directory;
|
|
20
|
+
},
|
|
21
|
+
resolveApiKey: async () => {
|
|
22
|
+
const connection = await ctx.integration.connection.active(OPENAI_INTEGRATION_ID);
|
|
23
|
+
if (!connection)
|
|
24
|
+
return undefined;
|
|
25
|
+
const credential = await ctx.integration.connection.resolve(connection);
|
|
26
|
+
if (credential?.type === "key" && typeof credential.key === "string") {
|
|
27
|
+
return credential.key;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAEjD,qDAAqD;AACrD,MAAM,qBAAqB,GAAG,QAAQ,CAAA;AAEtC;;;;;;GAMG;AACH,eAAe,MAAM,CAAC,MAAM,CAAC;IAC3B,EAAE,EAAE,cAAc;IAClB,KAAK,CAAC,KAAK,CAAC,GAAG;QACb,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE;YAClC,MAAM,CAAC,GAAG,CACR,qBAAqB,CAAC;gBACpB,uBAAuB,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;oBAC3C,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;oBACpD,OAAO,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAA;gBACnC,CAAC;gBACD,aAAa,EAAE,KAAK,IAAI,EAAE;oBACxB,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;oBACjF,IAAI,CAAC,UAAU;wBAAE,OAAO,SAAS,CAAA;oBACjC,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;oBACvE,IAAI,UAAU,EAAE,IAAI,KAAK,KAAK,IAAI,OAAO,UAAU,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;wBACrE,OAAO,UAAU,CAAC,GAAG,CAAA;oBACvB,CAAC;oBACD,OAAO,SAAS,CAAA;gBAClB,CAAC;aACF,CAAC,CACH,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ImageProvider } from "./provider.ts";
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
3
|
+
export interface OpenAIImageProviderOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
fetch?: typeof globalThis.fetch;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create the OpenAI adapter for the internal {@link ImageProvider} seam.
|
|
10
|
+
*
|
|
11
|
+
* With no reference images the request goes to `/images/generations` as JSON.
|
|
12
|
+
* With reference images it becomes a multipart request to `/images/edits` and
|
|
13
|
+
* every image is attached under the `image[]` field.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createOpenAIImageProvider(options: OpenAIImageProviderOptions): ImageProvider;
|
|
16
|
+
//# sourceMappingURL=openai-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-provider.d.ts","sourceRoot":"","sources":["../src/openai-provider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAA6B,MAAM,eAAe,CAAA;AAE7E,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAE3D,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAChC;AA8DD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,aAAa,CAiFf"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { isGptImageModel } from "./config.js";
|
|
2
|
+
import { ImageToolError } from "./errors.js";
|
|
3
|
+
import { decodeImageResponse, mimeTypeForOutputFormat } from "./image.js";
|
|
4
|
+
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
5
|
+
const MAX_ERROR_DETAIL = 500;
|
|
6
|
+
function isAbort(error, signal) {
|
|
7
|
+
return ((error instanceof Error && error.name === "AbortError") || signal?.aborted === true);
|
|
8
|
+
}
|
|
9
|
+
function safeErrorDetail(body) {
|
|
10
|
+
const trimmed = body.trim();
|
|
11
|
+
if (trimmed === "")
|
|
12
|
+
return "no response body";
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(trimmed);
|
|
15
|
+
const message = parsed.error?.message;
|
|
16
|
+
if (typeof message === "string" && message !== "") {
|
|
17
|
+
return message.slice(0, MAX_ERROR_DETAIL);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Not JSON; fall through to the raw text.
|
|
22
|
+
}
|
|
23
|
+
return trimmed.slice(0, MAX_ERROR_DETAIL);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The settings the request carries, as ordered field/value pairs. One mapping
|
|
27
|
+
* feeds both the JSON generation body and the multipart edit form, so adding a
|
|
28
|
+
* setting means editing this list once.
|
|
29
|
+
*/
|
|
30
|
+
function requestFields(request) {
|
|
31
|
+
const { model, quality, size, outputFormat } = request.settings;
|
|
32
|
+
const fields = [
|
|
33
|
+
["model", model],
|
|
34
|
+
["prompt", request.prompt],
|
|
35
|
+
["n", 1],
|
|
36
|
+
];
|
|
37
|
+
if (quality !== undefined)
|
|
38
|
+
fields.push(["quality", quality]);
|
|
39
|
+
if (size !== undefined)
|
|
40
|
+
fields.push(["size", size]);
|
|
41
|
+
if (isGptImageModel(model)) {
|
|
42
|
+
if (outputFormat !== undefined)
|
|
43
|
+
fields.push(["output_format", outputFormat]);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// DALL·E models default to a URL; ask for base64 so one decode path serves both.
|
|
47
|
+
fields.push(["response_format", "b64_json"]);
|
|
48
|
+
}
|
|
49
|
+
return fields;
|
|
50
|
+
}
|
|
51
|
+
function buildGenerationBody(request) {
|
|
52
|
+
return Object.fromEntries(requestFields(request));
|
|
53
|
+
}
|
|
54
|
+
function buildEditForm(request) {
|
|
55
|
+
const form = new FormData();
|
|
56
|
+
for (const [key, value] of requestFields(request))
|
|
57
|
+
form.set(key, String(value));
|
|
58
|
+
for (const reference of request.references ?? []) {
|
|
59
|
+
const blob = new Blob([reference.data], { type: reference.mimeType });
|
|
60
|
+
form.append("image[]", blob, reference.filename);
|
|
61
|
+
}
|
|
62
|
+
return form;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create the OpenAI adapter for the internal {@link ImageProvider} seam.
|
|
66
|
+
*
|
|
67
|
+
* With no reference images the request goes to `/images/generations` as JSON.
|
|
68
|
+
* With reference images it becomes a multipart request to `/images/edits` and
|
|
69
|
+
* every image is attached under the `image[]` field.
|
|
70
|
+
*/
|
|
71
|
+
export function createOpenAIImageProvider(options) {
|
|
72
|
+
const apiKey = options.apiKey;
|
|
73
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
74
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
75
|
+
async function send(request, operation, signal) {
|
|
76
|
+
const hasReferences = (request.references?.length ?? 0) > 0;
|
|
77
|
+
const url = hasReferences ? `${baseUrl}/images/edits` : `${baseUrl}/images/generations`;
|
|
78
|
+
let response;
|
|
79
|
+
try {
|
|
80
|
+
response = hasReferences
|
|
81
|
+
? await fetchImpl(url, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
84
|
+
body: buildEditForm(request),
|
|
85
|
+
signal,
|
|
86
|
+
})
|
|
87
|
+
: await fetchImpl(url, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${apiKey}`,
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify(buildGenerationBody(request)),
|
|
94
|
+
signal,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (isAbort(error, signal)) {
|
|
99
|
+
throw new ImageToolError("cancelled", operation, "The image request was cancelled.", {
|
|
100
|
+
cause: error,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
throw new ImageToolError("api_error", operation, `Could not reach OpenAI while trying to ${operation}: ${error.message}.`, { cause: error });
|
|
104
|
+
}
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const detail = safeErrorDetail(await response.text().catch(() => ""));
|
|
107
|
+
throw new ImageToolError("api_error", operation, `OpenAI returned HTTP ${response.status} while trying to ${operation}: ${detail}`);
|
|
108
|
+
}
|
|
109
|
+
let payload;
|
|
110
|
+
try {
|
|
111
|
+
payload = await response.json();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new ImageToolError("malformed_response", operation, "OpenAI returned a response body that was not valid JSON.", { cause: error });
|
|
115
|
+
}
|
|
116
|
+
const decoded = decodeImageResponse(payload);
|
|
117
|
+
const result = {
|
|
118
|
+
data: decoded.data,
|
|
119
|
+
mimeType: mimeTypeForOutputFormat(request.settings.outputFormat),
|
|
120
|
+
};
|
|
121
|
+
if (decoded.revisedPrompt !== undefined)
|
|
122
|
+
result.revisedPrompt = decoded.revisedPrompt;
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
generate(request, signal) {
|
|
127
|
+
const hasReferences = (request.references?.length ?? 0) > 0;
|
|
128
|
+
return send(request, hasReferences ? "edit image" : "generate image", signal);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=openai-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-provider.js","sourceRoot":"","sources":["../src/openai-provider.ts"],"names":[],"mappings":"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"}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a requested output path against the OpenCode session directory.
|
|
3
|
+
* Absolute paths are returned unchanged.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveOutputPath(requested: string, sessionDirectory: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Return the first path at or after `filePath` that does not yet exist.
|
|
8
|
+
* `report.png` becomes `report-1.png`, then `report-2.png`, and so on.
|
|
9
|
+
*/
|
|
10
|
+
export declare function chooseAvailablePath(filePath: string): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Write the image at the first available path without replacing an existing
|
|
13
|
+
* file. Uses the exclusive-create flag so a concurrent writer cannot be
|
|
14
|
+
* overwritten; a collision advances to the next versioned name.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeImageWithoutOverwrite(targetPath: string, data: Uint8Array): Promise<string>;
|
|
17
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAErF;AAiBD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM3E;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, extname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { ImageToolError } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a requested output path against the OpenCode session directory.
|
|
7
|
+
* Absolute paths are returned unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveOutputPath(requested, sessionDirectory) {
|
|
10
|
+
return isAbsolute(requested) ? requested : resolve(sessionDirectory, requested);
|
|
11
|
+
}
|
|
12
|
+
async function exists(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
await access(filePath, constants.F_OK);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function versionedPath(filePath, version) {
|
|
22
|
+
const extension = extname(filePath);
|
|
23
|
+
const base = extension === "" ? filePath : filePath.slice(0, -extension.length);
|
|
24
|
+
return `${base}-${version}${extension}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Return the first path at or after `filePath` that does not yet exist.
|
|
28
|
+
* `report.png` becomes `report-1.png`, then `report-2.png`, and so on.
|
|
29
|
+
*/
|
|
30
|
+
export async function chooseAvailablePath(filePath) {
|
|
31
|
+
if (!(await exists(filePath)))
|
|
32
|
+
return filePath;
|
|
33
|
+
for (let version = 1;; version += 1) {
|
|
34
|
+
const candidate = versionedPath(filePath, version);
|
|
35
|
+
if (!(await exists(candidate)))
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Write the image at the first available path without replacing an existing
|
|
41
|
+
* file. Uses the exclusive-create flag so a concurrent writer cannot be
|
|
42
|
+
* overwritten; a collision advances to the next versioned name.
|
|
43
|
+
*/
|
|
44
|
+
export async function writeImageWithoutOverwrite(targetPath, data) {
|
|
45
|
+
let candidate = await chooseAvailablePath(targetPath);
|
|
46
|
+
for (let attempt = 0; attempt < 1000; attempt += 1) {
|
|
47
|
+
try {
|
|
48
|
+
await mkdir(dirname(candidate), { recursive: true });
|
|
49
|
+
await writeFile(candidate, data, { flag: "wx" });
|
|
50
|
+
return candidate;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error.code === "EEXIST") {
|
|
54
|
+
candidate = await chooseAvailablePath(candidate);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
throw new ImageToolError("write_failed", "write image file", `Could not write the image to \`${candidate}\`: ${error.message}.`, { cause: error });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new ImageToolError("write_failed", "write image file", `Could not find an available filename for \`${targetPath}\`.`);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,gBAAwB;IAC3E,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAA;AACjF,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,QAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;QACtC,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB,EAAE,OAAe;IACtD,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnC,MAAM,IAAI,GAAG,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC/E,OAAO,GAAG,IAAI,IAAI,OAAO,GAAG,SAAS,EAAE,CAAA;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACxD,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAA;IAC9C,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAClD,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YAAE,OAAO,SAAS,CAAA;IAClD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,UAAkB,EAClB,IAAgB;IAEhB,IAAI,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAA;IACrD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACpD,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAChD,OAAO,SAAS,CAAA;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,SAAS,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAA;gBAChD,SAAQ;YACV,CAAC;YACD,MAAM,IAAI,cAAc,CACtB,cAAc,EACd,kBAAkB,EAClB,kCAAkC,SAAS,OAAQ,KAAe,CAAC,OAAO,GAAG,EAC7E,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAA;QACH,CAAC;IACH,CAAC;IACD,MAAM,IAAI,cAAc,CACtB,cAAc,EACd,kBAAkB,EAClB,8CAA8C,UAAU,KAAK,CAC9D,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { GenerationSettings } from "./config.ts";
|
|
2
|
+
/** A local reference image passed to the edits endpoint. */
|
|
3
|
+
export interface ImageReference {
|
|
4
|
+
filename: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
data: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
export interface ImageRequest {
|
|
9
|
+
prompt: string;
|
|
10
|
+
settings: GenerationSettings;
|
|
11
|
+
references?: ImageReference[];
|
|
12
|
+
}
|
|
13
|
+
export interface ImageResult {
|
|
14
|
+
data: Uint8Array;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
revisedPrompt?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The internal seam between the tool and an image service.
|
|
20
|
+
*
|
|
21
|
+
* It stays private to the package: the tool depends on this interface, and the
|
|
22
|
+
* OpenAI adapter implements it. A second service would be added by writing
|
|
23
|
+
* another implementation, not by exposing a plugin registry.
|
|
24
|
+
*/
|
|
25
|
+
export interface ImageProvider {
|
|
26
|
+
generate(request: ImageRequest, signal?: AbortSignal): Promise<ImageResult>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,UAAU,CAAC,EAAE,cAAc,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAC5E"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":""}
|
package/dist/tool.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Info as ToolInfo } from "@opencode/plugin/promise/tool";
|
|
2
|
+
export declare const TOOL_NAME = "gpt_imagegen";
|
|
3
|
+
export interface GptImagegenDependencies {
|
|
4
|
+
/** Resolve the OpenCode session directory used as the base for relative paths. */
|
|
5
|
+
resolveSessionDirectory: (sessionID: string) => Promise<string>;
|
|
6
|
+
/** Environment source for `OPENAI_API_KEY`; defaults to `process.env`. */
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a key from OpenCode's OpenAI connection. Consulted only when the
|
|
10
|
+
* option and the environment variable are absent.
|
|
11
|
+
*/
|
|
12
|
+
resolveApiKey?: () => Promise<string | undefined>;
|
|
13
|
+
/** Override the OpenAI API base URL. */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
/** Inject a fetch implementation (used by tests). */
|
|
16
|
+
fetch?: typeof globalThis.fetch;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the `gpt_imagegen` tool definition.
|
|
20
|
+
*
|
|
21
|
+
* The definition is a plain value so tests can call its `execute` with a fake
|
|
22
|
+
* context, a temporary session directory, and a stubbed fetch implementation.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createGptImagegenTool(dependencies: GptImagegenDependencies): ToolInfo;
|
|
25
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAqC,MAAM,+BAA+B,CAAA;AAcxG,eAAO,MAAM,SAAS,iBAAiB,CAAA;AASvC,MAAM,WAAW,uBAAuB;IACtC,kFAAkF;IAClF,uBAAuB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/D,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACxC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACjD,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAChC;AAyHD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,uBAAuB,GAAG,QAAQ,CAmErF"}
|
package/dist/tool.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { DEFAULT_MODEL, OUTPUT_FORMAT_VALUES, QUALITY_VALUES, reconcileOutputFormat, resolveSettings, } from "./config.js";
|
|
4
|
+
import { ImageToolError, invalidArgument, requireNonEmptyString } from "./errors.js";
|
|
5
|
+
import { SUPPORTED_REFERENCE_EXTENSIONS, mimeTypeForReferencePath } from "./image.js";
|
|
6
|
+
import { createOpenAIImageProvider } from "./openai-provider.js";
|
|
7
|
+
import { resolveOutputPath, writeImageWithoutOverwrite } from "./paths.js";
|
|
8
|
+
export const TOOL_NAME = "gpt_imagegen";
|
|
9
|
+
/**
|
|
10
|
+
* Find the OpenAI API key, in precedence order: the `OPENAI_API_KEY`
|
|
11
|
+
* environment variable, then OpenCode's OpenAI connection. Fails before any
|
|
12
|
+
* network request when neither is present.
|
|
13
|
+
*/
|
|
14
|
+
async function resolveApiKey(dependencies) {
|
|
15
|
+
const env = dependencies.env ?? process.env;
|
|
16
|
+
const fromEnv = env.OPENAI_API_KEY;
|
|
17
|
+
if (typeof fromEnv === "string" && fromEnv !== "")
|
|
18
|
+
return fromEnv;
|
|
19
|
+
const fromConnection = await dependencies.resolveApiKey?.();
|
|
20
|
+
if (typeof fromConnection === "string" && fromConnection !== "")
|
|
21
|
+
return fromConnection;
|
|
22
|
+
throw new ImageToolError("missing_api_key", "validate API key", "No OpenAI API key was found. Connect OpenAI in OpenCode (run /connect) or set OPENAI_API_KEY.");
|
|
23
|
+
}
|
|
24
|
+
const INPUT_SCHEMA = {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
prompt: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Text description of the image to generate or the edit to make.",
|
|
30
|
+
},
|
|
31
|
+
outputPath: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Where to save the image. Relative paths resolve against the session directory; an existing file is never replaced.",
|
|
34
|
+
},
|
|
35
|
+
model: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: `OpenAI image model. Defaults to ${DEFAULT_MODEL}.`,
|
|
38
|
+
},
|
|
39
|
+
quality: {
|
|
40
|
+
type: "string",
|
|
41
|
+
enum: [...QUALITY_VALUES],
|
|
42
|
+
description: "Generation quality. Defaults to the model's own default.",
|
|
43
|
+
},
|
|
44
|
+
size: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Image size as WIDTHxHEIGHT, or auto. Defaults to the model's own default.",
|
|
47
|
+
},
|
|
48
|
+
outputFormat: {
|
|
49
|
+
type: "string",
|
|
50
|
+
enum: [...OUTPUT_FORMAT_VALUES],
|
|
51
|
+
description: "Output format for GPT image models. Defaults to the format implied by the output path extension.",
|
|
52
|
+
},
|
|
53
|
+
referenceImages: {
|
|
54
|
+
type: "array",
|
|
55
|
+
items: { type: "string" },
|
|
56
|
+
description: "Local reference image paths. When present the tool edits them through the OpenAI edits endpoint.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["prompt", "outputPath"],
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
};
|
|
62
|
+
function readReferencePaths(value) {
|
|
63
|
+
if (value === undefined)
|
|
64
|
+
return [];
|
|
65
|
+
if (!Array.isArray(value)) {
|
|
66
|
+
throw invalidArgument("`referenceImages` must be an array of file paths.");
|
|
67
|
+
}
|
|
68
|
+
return value.map((entry) => {
|
|
69
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
70
|
+
throw invalidArgument("Every `referenceImages` entry must be a file path.");
|
|
71
|
+
}
|
|
72
|
+
return entry;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function loadReferences(paths, sessionDirectory) {
|
|
76
|
+
const references = [];
|
|
77
|
+
for (const requested of paths) {
|
|
78
|
+
const filePath = resolveOutputPath(requested, sessionDirectory);
|
|
79
|
+
const mimeType = mimeTypeForReferencePath(filePath);
|
|
80
|
+
if (mimeType === undefined) {
|
|
81
|
+
throw new ImageToolError("invalid_reference", "read reference image", `Reference image \`${filePath}\` has an unsupported type. Use ${SUPPORTED_REFERENCE_EXTENSIONS.join(", ")}.`);
|
|
82
|
+
}
|
|
83
|
+
let data;
|
|
84
|
+
try {
|
|
85
|
+
data = await readFile(filePath);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const code = error.code;
|
|
89
|
+
const detail = code === "ENOENT" ? "was not found" : `could not be read (${error.message})`;
|
|
90
|
+
throw new ImageToolError("invalid_reference", "read reference image", `Reference image \`${filePath}\` ${detail}.`, { cause: error });
|
|
91
|
+
}
|
|
92
|
+
if (data.byteLength === 0) {
|
|
93
|
+
throw new ImageToolError("invalid_reference", "read reference image", `Reference image \`${filePath}\` is empty.`);
|
|
94
|
+
}
|
|
95
|
+
references.push({ filename: basename(filePath), mimeType, data });
|
|
96
|
+
}
|
|
97
|
+
return references;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build the `gpt_imagegen` tool definition.
|
|
101
|
+
*
|
|
102
|
+
* The definition is a plain value so tests can call its `execute` with a fake
|
|
103
|
+
* context, a temporary session directory, and a stubbed fetch implementation.
|
|
104
|
+
*/
|
|
105
|
+
export function createGptImagegenTool(dependencies) {
|
|
106
|
+
return {
|
|
107
|
+
name: TOOL_NAME,
|
|
108
|
+
description: "Generate a new bitmap image from a prompt, or edit one from local reference images, using the OpenAI Image API. Saves one image to disk and returns its path.",
|
|
109
|
+
input: INPUT_SCHEMA,
|
|
110
|
+
options: { codemode: true },
|
|
111
|
+
async execute(input, context) {
|
|
112
|
+
const apiKey = await resolveApiKey(dependencies);
|
|
113
|
+
const record = (input ?? {});
|
|
114
|
+
const prompt = requireNonEmptyString(record.prompt, "prompt");
|
|
115
|
+
const outputPath = requireNonEmptyString(record.outputPath, "outputPath");
|
|
116
|
+
const requestedSettings = resolveSettings({
|
|
117
|
+
model: record.model,
|
|
118
|
+
quality: record.quality,
|
|
119
|
+
size: record.size,
|
|
120
|
+
outputFormat: record.outputFormat,
|
|
121
|
+
});
|
|
122
|
+
const referencePaths = readReferencePaths(record.referenceImages);
|
|
123
|
+
const sessionDirectory = await dependencies.resolveSessionDirectory(context.sessionID);
|
|
124
|
+
const targetPath = resolveOutputPath(outputPath, sessionDirectory);
|
|
125
|
+
const settings = reconcileOutputFormat(requestedSettings, targetPath);
|
|
126
|
+
const references = await loadReferences(referencePaths, sessionDirectory);
|
|
127
|
+
await context.progress({
|
|
128
|
+
status: references.length > 0 ? "editing image" : "generating image",
|
|
129
|
+
});
|
|
130
|
+
const provider = createOpenAIImageProvider({
|
|
131
|
+
apiKey,
|
|
132
|
+
baseUrl: dependencies.baseUrl,
|
|
133
|
+
fetch: dependencies.fetch,
|
|
134
|
+
});
|
|
135
|
+
const result = await provider.generate({ prompt, settings, references }, context.signal);
|
|
136
|
+
if (context.signal?.aborted === true) {
|
|
137
|
+
throw new ImageToolError("cancelled", "write image file", "The image request was cancelled before the file was written.");
|
|
138
|
+
}
|
|
139
|
+
await context.progress({ status: "writing image" });
|
|
140
|
+
const savedPath = await writeImageWithoutOverwrite(targetPath, result.data);
|
|
141
|
+
const metadata = {
|
|
142
|
+
path: savedPath,
|
|
143
|
+
model: settings.model,
|
|
144
|
+
mimeType: result.mimeType,
|
|
145
|
+
bytes: result.data.byteLength,
|
|
146
|
+
};
|
|
147
|
+
if (settings.quality !== undefined)
|
|
148
|
+
metadata.quality = settings.quality;
|
|
149
|
+
if (settings.size !== undefined)
|
|
150
|
+
metadata.size = settings.size;
|
|
151
|
+
if (settings.outputFormat !== undefined)
|
|
152
|
+
metadata.outputFormat = settings.outputFormat;
|
|
153
|
+
if (result.revisedPrompt !== undefined)
|
|
154
|
+
metadata.revisedPrompt = result.revisedPrompt;
|
|
155
|
+
if (references.length > 0)
|
|
156
|
+
metadata.referenceImages = references.map((ref) => ref.filename);
|
|
157
|
+
return {
|
|
158
|
+
content: `Saved image to \`${savedPath}\` (${result.mimeType}, ${result.data.byteLength} bytes).`,
|
|
159
|
+
metadata,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=tool.js.map
|
package/dist/tool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-img",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An OpenCode V2 plugin that generates and edits bitmap images through the OpenAI Image API.",
|
|
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
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"opencode",
|
|
21
|
+
"opencode-plugin",
|
|
22
|
+
"openai",
|
|
23
|
+
"image-generation"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/mattsafaii/opencode-img.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/mattsafaii/opencode-img#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/mattsafaii/opencode-img/issues"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@opencode/plugin": "^2.0.10"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^24.0.0",
|
|
42
|
+
"typescript": "^5.9.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"test": "node --test \"test/**/*.test.ts\"",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"build": "tsc -p tsconfig.build.json"
|
|
48
|
+
}
|
|
49
|
+
}
|