opencode-img 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # opencode-img
2
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.
3
+ An OpenCode V2 plugin that generates and edits bitmap images through OpenAI, Gemini, and xAI image APIs. One tool, `gpt_imagegen`, takes a prompt and an output path, calls the API with your key, and writes one image to disk.
4
4
 
5
5
  [![OpenCode V2 plugin](https://img.shields.io/badge/OpenCode-V2%20plugin-blue.svg)](https://opencode.ai/v2/docs/build/plugins)
6
6
  [![npm version](https://img.shields.io/npm/v/opencode-img.svg)](https://www.npmjs.com/package/opencode-img)
@@ -9,13 +9,13 @@ An OpenCode V2 plugin that generates and edits bitmap images through the OpenAI
9
9
 
10
10
  Source: [github.com/mattsafaii/opencode-img](https://github.com/mattsafaii/opencode-img)
11
11
 
12
- **Status:** 0.1.0, early release.
12
+ **Status:** early release.
13
13
 
14
14
  ## What it does
15
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.
16
+ - **Generates and edits.** A prompt makes a new image; local `referenceImages` edit an existing one.
17
+ - **Three providers.** `openai` (default), `gemini` (Nano Banana), and `grok` (Grok Imagine), selectable per call.
17
18
  - **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
19
  - **No SDK.** Native `fetch`, `FormData`, and `Blob` only.
20
20
 
21
21
  ## Install
@@ -35,16 +35,55 @@ Or add it to `opencode.json`:
35
35
  }
36
36
  ```
37
37
 
38
- ## Add your OpenAI API key
38
+ ## Plugin options
39
39
 
40
- Connect OpenAI in OpenCode, or set `OPENAI_API_KEY`. When both are present, the environment variable wins.
40
+ Set call defaults in `opencode.json` with the object form:
41
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.
42
+ ```jsonc
43
+ {
44
+ "$schema": "https://opencode.ai/config.json",
45
+ "plugins": [
46
+ {
47
+ "package": "opencode-img",
48
+ "options": {
49
+ "provider": "gemini",
50
+ "models": {
51
+ "openai": "gpt-image-1.5",
52
+ "gemini": "gemini-3.1-flash-image",
53
+ "grok": "grok-imagine-image-2.0"
54
+ }
55
+ }
56
+ }
57
+ ]
58
+ }
59
+ ```
60
+
61
+ - `provider` — used when a call does not name one. Defaults to `openai`.
62
+ - `models` — per-provider default model. Each provider falls back to its built-in default.
63
+
64
+ A call can still pass `provider` or `model` to override either.
65
+
66
+ ## Providers
67
+
68
+ | `provider` | Default model | Key |
69
+ | --- | --- | --- |
70
+ | `openai` (default) | `gpt-image-1.5` | `OPENAI_API_KEY`, or the OpenAI connection from `/connect` |
71
+ | `gemini` | `gemini-3.1-flash-image` (Nano Banana 2) | `GEMINI_API_KEY` (or `GOOGLE_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`), or the Google connection from `/connect` |
72
+ | `grok` | `grok-imagine-image-2.0` (Grok Imagine) | `XAI_API_KEY`, or the xAI connection from `/connect` |
73
+
74
+ Pass `provider` to pick one.
75
+
76
+ ## Add your API key
77
+
78
+ Set the environment variable for the provider you use, or connect it in OpenCode. When both are present, the environment variable wins.
79
+
80
+ - **OpenAI:** `OPENAI_API_KEY`, or `/connect` → OpenAI.
81
+ - **Gemini:** `GEMINI_API_KEY`, or `/connect` → Google.
82
+ - **Grok:** `XAI_API_KEY`, or `/connect` → xAI.
44
83
 
45
- A missing key fails before any network request, and the error names both.
84
+ Use the environment variable for headless setups, or when your key lives in the OpenCode Console (BYOK), which plugins cannot read.
46
85
 
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.
86
+ A missing key fails before any network request, and the error names the provider's variables and integration.
48
87
 
49
88
  ## Use
50
89
 
@@ -60,14 +99,17 @@ Generate a 1024x1024 image of a red square on a white background and save it to
60
99
  | --- | --- | --- |
61
100
  | `prompt` | yes | What to generate, or the edit to make. |
62
101
  | `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`. |
102
+ | `provider` | no | `openai` (default), `gemini`, or `grok`. |
103
+ | `model` | no | Image model. Defaults to the provider's default. |
104
+ | `quality` | no | `auto`, `low`, `medium`, `high`, `standard`, `hd`, `xhigh`, or `max` (OpenAI); `auto`, `low`, or `medium` (Grok). |
105
+ | `size` | no | `WIDTHxHEIGHT` (for example `1024x1024`) or `auto` (OpenAI only). |
106
+ | `outputFormat` | no | `png`, `jpeg`, or `webp` (OpenAI); `png` or `jpeg` (Gemini). Inferred from the output path extension when omitted. |
107
+ | `referenceImages` | no | Local image paths. When present, the tool edits them. |
68
108
 
69
109
  ## Worked examples
70
110
 
111
+ Each image below is a real output of this plugin.
112
+
71
113
  ### Edit an existing image
72
114
 
73
115
  Pass local paths in `referenceImages` and the tool edits them.
@@ -76,12 +118,16 @@ Pass local paths in `referenceImages` and the tool edits them.
76
118
  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
119
  ```
78
120
 
121
+ ![A teacup edited from the coffee-cup reference](./assets/example-edit.png)
122
+
79
123
  ### Generate from a prompt
80
124
 
81
125
  ```text
82
126
  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
127
  ```
84
128
 
129
+ ![A generated flat illustration of a coffee cup](./assets/example-generate.png)
130
+
85
131
  ### Run the same path twice
86
132
 
87
133
  The first file is left untouched; the new image lands at `example-generate-1.png`.
@@ -90,6 +136,8 @@ The first file is left untouched; the new image lands at `example-generate-1.png
90
136
  Now do the same path, but a slice of cake on a plate instead.
91
137
  ```
92
138
 
139
+ ![The second image, written to the versioned path](./assets/example-generate-1.png)
140
+
93
141
  ## Output
94
142
 
95
143
  - One image per request.
@@ -100,7 +148,7 @@ Now do the same path, but a slice of cake on a plate instead.
100
148
 
101
149
  ## Disclaimer
102
150
 
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.
151
+ opencode-img is an independent project. It is not made by, affiliated with, or endorsed by any provider it supports or the OpenCode team. Calls to a provider's API are subject to that provider's terms and usage policies.
104
152
 
105
153
  ## Development
106
154
 
package/dist/config.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- /** The OpenAI image model used when the caller does not name one. */
2
- export declare const DEFAULT_MODEL = "gpt-image-1.5";
3
1
  /** Quality values accepted by the OpenAI Images API. */
4
2
  export declare const QUALITY_VALUES: readonly ["auto", "standard", "hd", "low", "medium", "high", "xhigh", "max"];
5
3
  /** Output formats accepted by the GPT image models. */
@@ -26,18 +24,19 @@ export declare function isGptImageModel(model: string): boolean;
26
24
  */
27
25
  export declare function outputFormatForExtension(filePath: string): OutputFormat | undefined;
28
26
  /**
29
- * Validate the optional generation settings and apply the model default.
27
+ * Validate the optional generation settings and apply the provider's default
28
+ * model.
30
29
  *
31
30
  * Size is checked as a shape (`auto` or `WIDTHxHEIGHT`) rather than a fixed
32
31
  * list so newer models that accept arbitrary resolutions keep working.
33
32
  */
34
- export declare function resolveSettings(input: GenerationSettingsInput): GenerationSettings;
33
+ export declare function resolveSettings(input: GenerationSettingsInput, defaultModel: string): GenerationSettings;
35
34
  /**
36
35
  * Keep the encoded format and the output filename in agreement.
37
36
  *
38
37
  * An explicit `outputFormat` must match a known image extension on the path.
39
38
  * 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.
39
+ * contradict the name.
41
40
  */
42
41
  export declare function reconcileOutputFormat(settings: GenerationSettings, targetPath: string): GenerationSettings;
43
42
  //# sourceMappingURL=config.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,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;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,uBAAuB,EAC9B,YAAY,EAAE,MAAM,GACnB,kBAAkB,CAiCpB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB"}
package/dist/config.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { extname } from "node:path";
2
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
3
  /** Quality values accepted by the OpenAI Images API. */
6
4
  export const QUALITY_VALUES = [
7
5
  "auto",
@@ -37,13 +35,14 @@ export function outputFormatForExtension(filePath) {
37
35
  }
38
36
  }
39
37
  /**
40
- * Validate the optional generation settings and apply the model default.
38
+ * Validate the optional generation settings and apply the provider's default
39
+ * model.
41
40
  *
42
41
  * Size is checked as a shape (`auto` or `WIDTHxHEIGHT`) rather than a fixed
43
42
  * list so newer models that accept arbitrary resolutions keep working.
44
43
  */
45
- export function resolveSettings(input) {
46
- const model = input.model === undefined ? DEFAULT_MODEL : requireNonEmptyString(input.model, "model");
44
+ export function resolveSettings(input, defaultModel) {
45
+ const model = input.model === undefined ? defaultModel : requireNonEmptyString(input.model, "model");
47
46
  const settings = { model };
48
47
  if (input.quality !== undefined) {
49
48
  const quality = requireNonEmptyString(input.quality, "quality");
@@ -64,9 +63,6 @@ export function resolveSettings(input) {
64
63
  if (!OUTPUT_FORMAT_VALUES.includes(outputFormat)) {
65
64
  throw invalidArgument(`\`outputFormat\` must be one of ${OUTPUT_FORMAT_VALUES.join(", ")}.`);
66
65
  }
67
- if (!isGptImageModel(model)) {
68
- throw invalidArgument("`outputFormat` is only supported by the GPT image models; DALL·E models always return PNG.");
69
- }
70
66
  settings.outputFormat = outputFormat;
71
67
  }
72
68
  return settings;
@@ -76,7 +72,7 @@ export function resolveSettings(input) {
76
72
  *
77
73
  * An explicit `outputFormat` must match a known image extension on the path.
78
74
  * 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.
75
+ * contradict the name.
80
76
  */
81
77
  export function reconcileOutputFormat(settings, targetPath) {
82
78
  const extensionFormat = outputFormatForExtension(targetPath);
@@ -88,12 +84,6 @@ export function reconcileOutputFormat(settings, targetPath) {
88
84
  }
89
85
  return settings;
90
86
  }
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
87
  return { ...settings, outputFormat: extensionFormat };
98
88
  }
99
89
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type GenerationSettings, type GenerationSettingsInput } from "./config.ts";
2
+ import type { ImageProvider, ImageProviderOptions } from "./provider.ts";
3
+ export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
4
+ /** Nano Banana 2, the generalist Gemini image model. */
5
+ export declare const GEMINI_DEFAULT_MODEL = "gemini-3.1-flash-image";
6
+ /** Resolve the settings Gemini accepts. Quality, size, and webp do not apply. */
7
+ export declare function resolveGeminiSettings(input: GenerationSettingsInput, targetPath: string): GenerationSettings;
8
+ /**
9
+ * Create the Gemini (Nano Banana) adapter for the internal {@link ImageProvider}
10
+ * seam.
11
+ *
12
+ * Generation and editing use one endpoint. The prompt and every reference image
13
+ * travel together as content parts, and the generated image comes back in the
14
+ * response steps.
15
+ */
16
+ export declare function createGeminiImageProvider(options: ImageProviderOptions): ImageProvider;
17
+ //# sourceMappingURL=gemini-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../src/gemini-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAA6B,MAAM,eAAe,CAAA;AAEnG,eAAO,MAAM,gBAAgB,qDAAqD,CAAA;AAElF,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAK5D,iFAAiF;AACjF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,uBAAuB,EAC9B,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAoBpB;AAqDD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAgEtF"}
@@ -0,0 +1,130 @@
1
+ import { reconcileOutputFormat, resolveSettings, } from "./config.js";
2
+ import { ImageToolError, invalidArgument } from "./errors.js";
3
+ import { sendJsonRequest } from "./http.js";
4
+ import { decodeBase64Image, mimeTypeForOutputFormat } from "./image.js";
5
+ export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
6
+ /** Nano Banana 2, the generalist Gemini image model. */
7
+ export const GEMINI_DEFAULT_MODEL = "gemini-3.1-flash-image";
8
+ /** Output formats the Gemini image models can produce. */
9
+ const SUPPORTED_OUTPUT_FORMATS = ["png", "jpeg"];
10
+ /** Resolve the settings Gemini accepts. Quality, size, and webp do not apply. */
11
+ export function resolveGeminiSettings(input, targetPath) {
12
+ const settings = resolveSettings(input, GEMINI_DEFAULT_MODEL);
13
+ if (settings.quality !== undefined) {
14
+ throw invalidArgument("`quality` is not supported by the gemini provider.");
15
+ }
16
+ if (settings.size !== undefined) {
17
+ throw invalidArgument("`size` is not supported by the gemini provider; Gemini uses aspect ratios.");
18
+ }
19
+ const reconciled = reconcileOutputFormat(settings, targetPath);
20
+ if (reconciled.outputFormat !== undefined &&
21
+ !SUPPORTED_OUTPUT_FORMATS.includes(reconciled.outputFormat)) {
22
+ throw invalidArgument(`The gemini provider cannot produce ${reconciled.outputFormat}; use png or jpeg.`);
23
+ }
24
+ return reconciled;
25
+ }
26
+ function asRecord(value) {
27
+ return typeof value === "object" && value !== null
28
+ ? value
29
+ : undefined;
30
+ }
31
+ function imageBlockFrom(value) {
32
+ const block = asRecord(value);
33
+ if (block === undefined)
34
+ return undefined;
35
+ if (block.type !== undefined && block.type !== "image")
36
+ return undefined;
37
+ const data = block.data;
38
+ if (typeof data !== "string" || data === "")
39
+ return undefined;
40
+ const mimeType = typeof block.mime_type === "string"
41
+ ? block.mime_type
42
+ : typeof block.mimeType === "string"
43
+ ? block.mimeType
44
+ : "image/png";
45
+ return { data, mimeType };
46
+ }
47
+ /** Find the last image block in an Interactions response. */
48
+ function findImage(payload) {
49
+ const root = asRecord(payload);
50
+ if (root === undefined)
51
+ return undefined;
52
+ const direct = imageBlockFrom(root.output_image ?? root.outputImage);
53
+ if (direct !== undefined)
54
+ return direct;
55
+ const steps = root.steps;
56
+ if (!Array.isArray(steps))
57
+ return undefined;
58
+ let found;
59
+ for (const step of steps) {
60
+ const content = asRecord(step)?.content;
61
+ if (!Array.isArray(content))
62
+ continue;
63
+ for (const block of content) {
64
+ const image = imageBlockFrom(block);
65
+ if (image !== undefined)
66
+ found = image;
67
+ }
68
+ }
69
+ return found;
70
+ }
71
+ /**
72
+ * Create the Gemini (Nano Banana) adapter for the internal {@link ImageProvider}
73
+ * seam.
74
+ *
75
+ * Generation and editing use one endpoint. The prompt and every reference image
76
+ * travel together as content parts, and the generated image comes back in the
77
+ * response steps.
78
+ */
79
+ export function createGeminiImageProvider(options) {
80
+ const apiKey = options.apiKey;
81
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
82
+ const fetchImpl = options.fetch ?? globalThis.fetch;
83
+ function buildBody(request) {
84
+ const input = [{ type: "text", text: request.prompt }];
85
+ for (const reference of request.references ?? []) {
86
+ input.push({
87
+ type: "image",
88
+ mime_type: reference.mimeType,
89
+ data: Buffer.from(reference.data).toString("base64"),
90
+ });
91
+ }
92
+ const body = { model: request.settings.model, input };
93
+ if (request.settings.outputFormat !== undefined) {
94
+ body.response_format = {
95
+ type: "image",
96
+ mime_type: mimeTypeForOutputFormat(request.settings.outputFormat),
97
+ };
98
+ }
99
+ return body;
100
+ }
101
+ async function send(request, operation, signal) {
102
+ const payload = await sendJsonRequest({
103
+ provider: "Gemini",
104
+ operation,
105
+ fetchImpl,
106
+ url: `${baseUrl}/interactions`,
107
+ init: {
108
+ method: "POST",
109
+ headers: {
110
+ "x-goog-api-key": apiKey,
111
+ "Content-Type": "application/json",
112
+ },
113
+ body: JSON.stringify(buildBody(request)),
114
+ },
115
+ signal,
116
+ });
117
+ const image = findImage(payload);
118
+ if (image === undefined) {
119
+ throw new ImageToolError("malformed_response", "decode image response", "The image response could not be read: the response contained no image.");
120
+ }
121
+ return { data: decodeBase64Image(image.data), mimeType: image.mimeType };
122
+ }
123
+ return {
124
+ generate(request, signal) {
125
+ const hasReferences = (request.references?.length ?? 0) > 0;
126
+ return send(request, hasReferences ? "edit image" : "generate image", signal);
127
+ },
128
+ };
129
+ }
130
+ //# sourceMappingURL=gemini-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-provider.js","sourceRoot":"","sources":["../src/gemini-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,eAAe,GAIhB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAGvE,MAAM,CAAC,MAAM,gBAAgB,GAAG,kDAAkD,CAAA;AAElF,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,wBAAwB,CAAA;AAE5D,0DAA0D;AAC1D,MAAM,wBAAwB,GAA4B,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;AAEzE,iFAAiF;AACjF,MAAM,UAAU,qBAAqB,CACnC,KAA8B,EAC9B,UAAkB;IAElB,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAA;IAE7D,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,eAAe,CAAC,oDAAoD,CAAC,CAAA;IAC7E,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,eAAe,CAAC,4EAA4E,CAAC,CAAA;IACrG,CAAC;IAED,MAAM,UAAU,GAAG,qBAAqB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IAC9D,IACE,UAAU,CAAC,YAAY,KAAK,SAAS;QACrC,CAAC,wBAAwB,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,EAC3D,CAAC;QACD,MAAM,eAAe,CACnB,sCAAsC,UAAU,CAAC,YAAY,oBAAoB,CAClF,CAAA;IACH,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAOD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAChD,CAAC,CAAE,KAAiC;QACpC,CAAC,CAAC,SAAS,CAAA;AACf,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,SAAS,CAAA;IAExE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IAE7D,MAAM,QAAQ,GACZ,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ;QACjC,CAAC,CAAC,KAAK,CAAC,SAAS;QACjB,CAAC,CAAC,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;YAClC,CAAC,CAAC,KAAK,CAAC,QAAQ;YAChB,CAAC,CAAC,WAAW,CAAA;IACnB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAC3B,CAAC;AAED,6DAA6D;AAC7D,SAAS,SAAS,CAAC,OAAgB;IACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC9B,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAExC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,WAAW,CAAC,CAAA;IACpE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAEvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACxB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAE3C,IAAI,KAA6B,CAAA;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;QACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,SAAQ;QACrC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;YACnC,IAAI,KAAK,KAAK,SAAS;gBAAE,KAAK,GAAG,KAAK,CAAA;QACxC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAA6B;IACrE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAC7B,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACzE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAA;IAEnD,SAAS,SAAS,CAAC,OAAqB;QACtC,MAAM,KAAK,GAAmC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QACtF,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;YACjD,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,OAAO;gBACb,SAAS,EAAE,SAAS,CAAC,QAAQ;gBAC7B,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;aACrD,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,IAAI,GAA4B,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;QAC9E,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YAChD,IAAI,CAAC,eAAe,GAAG;gBACrB,IAAI,EAAE,OAAO;gBACb,SAAS,EAAE,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;aAClE,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,UAAU,IAAI,CACjB,OAAqB,EACrB,SAA0C,EAC1C,MAA+B;QAE/B,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;YACpC,QAAQ,EAAE,QAAQ;YAClB,SAAS;YACT,SAAS;YACT,GAAG,EAAE,GAAG,OAAO,eAAe;YAC9B,IAAI,EAAE;gBACJ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,gBAAgB,EAAE,MAAM;oBACxB,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;aACzC;YACD,MAAM;SACP,CAAC,CAAA;QAEF,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;QAChC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,cAAc,CACtB,oBAAoB,EACpB,uBAAuB,EACvB,wEAAwE,CACzE,CAAA;QACH,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAA;IAC1E,CAAC;IAED,OAAO;QACL,QAAQ,CAAC,OAAqB,EAAE,MAAoB;YAClD,MAAM,aAAa,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;YAC3D,OAAO,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAA;QAC/E,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type GenerationSettings, type GenerationSettingsInput } from "./config.ts";
2
+ import type { ImageProvider, ImageProviderOptions } from "./provider.ts";
3
+ export declare const DEFAULT_BASE_URL = "https://api.x.ai/v1";
4
+ /** Grok Imagine Image 2.0, the current xAI image model. */
5
+ export declare const GROK_DEFAULT_MODEL = "grok-imagine-image-2.0";
6
+ /** Resolve the settings xAI accepts. Size and output format do not apply. */
7
+ export declare function resolveGrokSettings(input: GenerationSettingsInput, _targetPath: string): GenerationSettings;
8
+ /**
9
+ * Create the Grok Imagine (xAI) adapter for the internal {@link ImageProvider}
10
+ * seam.
11
+ *
12
+ * Generation posts JSON to `/images/generations`. Editing posts JSON to
13
+ * `/images/edits` with the source images as data URIs; the endpoint does not
14
+ * accept multipart form data.
15
+ */
16
+ export declare function createGrokImageProvider(options: ImageProviderOptions): ImageProvider;
17
+ //# sourceMappingURL=grok-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grok-provider.d.ts","sourceRoot":"","sources":["../src/grok-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,aAAa,CAAA;AAIpB,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EAIrB,MAAM,eAAe,CAAA;AAEtB,eAAO,MAAM,gBAAgB,wBAAwB,CAAA;AAErD,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,2BAA2B,CAAA;AAK1D,6EAA6E;AAC7E,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,uBAAuB,EAC9B,WAAW,EAAE,MAAM,GAClB,kBAAkB,CAepB;AAMD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAiEpF"}
@@ -0,0 +1,94 @@
1
+ import { resolveSettings, } from "./config.js";
2
+ import { invalidArgument } from "./errors.js";
3
+ import { sendJsonRequest } from "./http.js";
4
+ import { decodeImageResponse, mimeTypeForImageBytes } from "./image.js";
5
+ export const DEFAULT_BASE_URL = "https://api.x.ai/v1";
6
+ /** Grok Imagine Image 2.0, the current xAI image model. */
7
+ export const GROK_DEFAULT_MODEL = "grok-imagine-image-2.0";
8
+ /** Quality values the xAI image model accepts. */
9
+ const GROK_QUALITY_VALUES = ["auto", "low", "medium"];
10
+ /** Resolve the settings xAI accepts. Size and output format do not apply. */
11
+ export function resolveGrokSettings(input, _targetPath) {
12
+ const settings = resolveSettings(input, GROK_DEFAULT_MODEL);
13
+ if (settings.size !== undefined) {
14
+ throw invalidArgument("`size` is not supported by the grok provider; xAI uses aspect ratios.");
15
+ }
16
+ if (settings.outputFormat !== undefined) {
17
+ throw invalidArgument("`outputFormat` is not supported by the grok provider.");
18
+ }
19
+ if (settings.quality !== undefined && !GROK_QUALITY_VALUES.includes(settings.quality)) {
20
+ throw invalidArgument(`The grok provider accepts quality ${GROK_QUALITY_VALUES.join(", ")}.`);
21
+ }
22
+ return settings;
23
+ }
24
+ function referenceDataUri(reference) {
25
+ return `data:${reference.mimeType};base64,${Buffer.from(reference.data).toString("base64")}`;
26
+ }
27
+ /**
28
+ * Create the Grok Imagine (xAI) adapter for the internal {@link ImageProvider}
29
+ * seam.
30
+ *
31
+ * Generation posts JSON to `/images/generations`. Editing posts JSON to
32
+ * `/images/edits` with the source images as data URIs; the endpoint does not
33
+ * accept multipart form data.
34
+ */
35
+ export function createGrokImageProvider(options) {
36
+ const apiKey = options.apiKey;
37
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
38
+ const fetchImpl = options.fetch ?? globalThis.fetch;
39
+ function buildBody(request) {
40
+ const body = {
41
+ model: request.settings.model,
42
+ prompt: request.prompt,
43
+ n: 1,
44
+ response_format: "b64_json",
45
+ };
46
+ if (request.settings.quality !== undefined)
47
+ body.quality = request.settings.quality;
48
+ const references = request.references ?? [];
49
+ if (references.length === 1) {
50
+ body.image = { url: referenceDataUri(references[0]), type: "image_url" };
51
+ }
52
+ else if (references.length > 1) {
53
+ body.image = references.map((reference) => ({
54
+ url: referenceDataUri(reference),
55
+ type: "image_url",
56
+ }));
57
+ }
58
+ return body;
59
+ }
60
+ async function send(request, operation, signal) {
61
+ const hasReferences = (request.references?.length ?? 0) > 0;
62
+ const url = hasReferences ? `${baseUrl}/images/edits` : `${baseUrl}/images/generations`;
63
+ const payload = await sendJsonRequest({
64
+ provider: "xAI",
65
+ operation,
66
+ fetchImpl,
67
+ url,
68
+ init: {
69
+ method: "POST",
70
+ headers: {
71
+ Authorization: `Bearer ${apiKey}`,
72
+ "Content-Type": "application/json",
73
+ },
74
+ body: JSON.stringify(buildBody(request)),
75
+ },
76
+ signal,
77
+ });
78
+ const decoded = decodeImageResponse(payload);
79
+ const result = {
80
+ data: decoded.data,
81
+ mimeType: mimeTypeForImageBytes(decoded.data),
82
+ };
83
+ if (decoded.revisedPrompt !== undefined)
84
+ result.revisedPrompt = decoded.revisedPrompt;
85
+ return result;
86
+ }
87
+ return {
88
+ generate(request, signal) {
89
+ const hasReferences = (request.references?.length ?? 0) > 0;
90
+ return send(request, hasReferences ? "edit image" : "generate image", signal);
91
+ },
92
+ };
93
+ }
94
+ //# sourceMappingURL=grok-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grok-provider.js","sourceRoot":"","sources":["../src/grok-provider.ts"],"names":[],"mappings":"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"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { type ImageToolOperation } from "./errors.ts";
2
+ /** True when a fetch failure is an abort rather than a real error. */
3
+ export declare function isAbort(error: unknown, signal: AbortSignal | undefined): boolean;
4
+ /** A short, safe detail string pulled from an error response body. */
5
+ export declare function safeErrorDetail(body: string): string;
6
+ export interface JsonRequest {
7
+ /** Provider name used in error messages, for example `OpenAI`. */
8
+ provider: string;
9
+ operation: ImageToolOperation;
10
+ fetchImpl: typeof globalThis.fetch;
11
+ url: string;
12
+ init: RequestInit;
13
+ signal: AbortSignal | undefined;
14
+ }
15
+ /**
16
+ * Send a JSON request and return the parsed body.
17
+ *
18
+ * Shared by every adapter so the failure semantics stay identical: an abort is a
19
+ * cancellation, a non-OK status is an API error carrying the status and a
20
+ * bounded detail, and an unreadable body is a malformed response.
21
+ */
22
+ export declare function sendJsonRequest(request: JsonRequest): Promise<unknown>;
23
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIrE,sEAAsE;AACtE,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAapD;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,kBAAkB,CAAA;IAC7B,SAAS,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;CAChC;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAqC5E"}
package/dist/http.js ADDED
@@ -0,0 +1,55 @@
1
+ import { ImageToolError } from "./errors.js";
2
+ const MAX_ERROR_DETAIL = 500;
3
+ /** True when a fetch failure is an abort rather than a real error. */
4
+ export function isAbort(error, signal) {
5
+ return (error instanceof Error && error.name === "AbortError") || signal?.aborted === true;
6
+ }
7
+ /** A short, safe detail string pulled from an error response body. */
8
+ export function safeErrorDetail(body) {
9
+ const trimmed = body.trim();
10
+ if (trimmed === "")
11
+ return "no response body";
12
+ try {
13
+ const parsed = JSON.parse(trimmed);
14
+ const message = parsed.error?.message;
15
+ if (typeof message === "string" && message !== "") {
16
+ return message.slice(0, MAX_ERROR_DETAIL);
17
+ }
18
+ }
19
+ catch {
20
+ // Not JSON; fall through to the raw text.
21
+ }
22
+ return trimmed.slice(0, MAX_ERROR_DETAIL);
23
+ }
24
+ /**
25
+ * Send a JSON request and return the parsed body.
26
+ *
27
+ * Shared by every adapter so the failure semantics stay identical: an abort is a
28
+ * cancellation, a non-OK status is an API error carrying the status and a
29
+ * bounded detail, and an unreadable body is a malformed response.
30
+ */
31
+ export async function sendJsonRequest(request) {
32
+ let response;
33
+ try {
34
+ response = await request.fetchImpl(request.url, { ...request.init, signal: request.signal });
35
+ }
36
+ catch (error) {
37
+ if (isAbort(error, request.signal)) {
38
+ throw new ImageToolError("cancelled", request.operation, "The image request was cancelled.", {
39
+ cause: error,
40
+ });
41
+ }
42
+ throw new ImageToolError("api_error", request.operation, `Could not reach ${request.provider} while trying to ${request.operation}: ${error.message}.`, { cause: error });
43
+ }
44
+ if (!response.ok) {
45
+ const detail = safeErrorDetail(await response.text().catch(() => ""));
46
+ throw new ImageToolError("api_error", request.operation, `${request.provider} returned HTTP ${response.status} while trying to ${request.operation}: ${detail}`);
47
+ }
48
+ try {
49
+ return await response.json();
50
+ }
51
+ catch (error) {
52
+ throw new ImageToolError("malformed_response", request.operation, `${request.provider} returned a response body that was not valid JSON.`, { cause: error });
53
+ }
54
+ }
55
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA2B,MAAM,aAAa,CAAA;AAErE,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAE5B,sEAAsE;AACtE,MAAM,UAAU,OAAO,CAAC,KAAc,EAAE,MAA+B;IACrE,OAAO,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;AAC5F,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,kBAAkB,CAAA;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAsC,CAAA;QACvE,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAA;QACrC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YAClD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAA;AAC3C,CAAC;AAYD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAoB;IACxD,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAC9F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,EAAE,kCAAkC,EAAE;gBAC3F,KAAK,EAAE,KAAK;aACb,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,IAAI,cAAc,CACtB,WAAW,EACX,OAAO,CAAC,SAAS,EACjB,mBAAmB,OAAO,CAAC,QAAQ,oBAAoB,OAAO,CAAC,SAAS,KAAM,KAAe,CAAC,OAAO,GAAG,EACxG,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAA;IACH,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACrE,MAAM,IAAI,cAAc,CACtB,WAAW,EACX,OAAO,CAAC,SAAS,EACjB,GAAG,OAAO,CAAC,QAAQ,kBAAkB,QAAQ,CAAC,MAAM,oBAAoB,OAAO,CAAC,SAAS,KAAK,MAAM,EAAE,CACvG,CAAA;IACH,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,cAAc,CACtB,oBAAoB,EACpB,OAAO,CAAC,SAAS,EACjB,GAAG,OAAO,CAAC,QAAQ,oDAAoD,EACvE,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAA;IACH,CAAC;AACH,CAAC"}