openrouter-genimage-kote-opencode 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/.opencode/skills/openrouter-image-generation/SKILL.md +379 -0
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/README.ru.md +229 -0
- package/SECURITY.md +31 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +277 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/balance-command.d.ts +5 -0
- package/dist/commands/balance-command.d.ts.map +1 -0
- package/dist/commands/balance-command.js +20 -0
- package/dist/commands/balance-command.js.map +1 -0
- package/dist/config/resolve.d.ts +5 -0
- package/dist/config/resolve.d.ts.map +1 -0
- package/dist/config/resolve.js +113 -0
- package/dist/config/resolve.js.map +1 -0
- package/dist/config/schema.d.ts +188 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +144 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/types.d.ts +75 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +30 -0
- package/dist/config/types.js.map +1 -0
- package/dist/logging.d.ts +46 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +58 -0
- package/dist/logging.js.map +1 -0
- package/dist/openrouter/capabilities.d.ts +22 -0
- package/dist/openrouter/capabilities.d.ts.map +1 -0
- package/dist/openrouter/capabilities.js +137 -0
- package/dist/openrouter/capabilities.js.map +1 -0
- package/dist/openrouter/client.d.ts +54 -0
- package/dist/openrouter/client.d.ts.map +1 -0
- package/dist/openrouter/client.js +440 -0
- package/dist/openrouter/client.js.map +1 -0
- package/dist/openrouter/errors.d.ts +22 -0
- package/dist/openrouter/errors.d.ts.map +1 -0
- package/dist/openrouter/errors.js +75 -0
- package/dist/openrouter/errors.js.map +1 -0
- package/dist/openrouter/models.d.ts +37 -0
- package/dist/openrouter/models.d.ts.map +1 -0
- package/dist/openrouter/models.js +113 -0
- package/dist/openrouter/models.js.map +1 -0
- package/dist/openrouter/requests.d.ts +6 -0
- package/dist/openrouter/requests.d.ts.map +1 -0
- package/dist/openrouter/requests.js +74 -0
- package/dist/openrouter/requests.js.map +1 -0
- package/dist/openrouter/schemas.d.ts +37 -0
- package/dist/openrouter/schemas.d.ts.map +1 -0
- package/dist/openrouter/schemas.js +2 -0
- package/dist/openrouter/schemas.js.map +1 -0
- package/dist/output/attachments.d.ts +10 -0
- package/dist/output/attachments.d.ts.map +1 -0
- package/dist/output/attachments.js +13 -0
- package/dist/output/attachments.js.map +1 -0
- package/dist/output/formats.d.ts +42 -0
- package/dist/output/formats.d.ts.map +1 -0
- package/dist/output/formats.js +126 -0
- package/dist/output/formats.js.map +1 -0
- package/dist/output/index.d.ts +5 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +5 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/paths.d.ts +38 -0
- package/dist/output/paths.d.ts.map +1 -0
- package/dist/output/paths.js +296 -0
- package/dist/output/paths.js.map +1 -0
- package/dist/output/writer.d.ts +33 -0
- package/dist/output/writer.d.ts.map +1 -0
- package/dist/output/writer.js +176 -0
- package/dist/output/writer.js.map +1 -0
- package/dist/references/attachments.d.ts +82 -0
- package/dist/references/attachments.d.ts.map +1 -0
- package/dist/references/attachments.js +238 -0
- package/dist/references/attachments.js.map +1 -0
- package/dist/references/data-url.d.ts +12 -0
- package/dist/references/data-url.d.ts.map +1 -0
- package/dist/references/data-url.js +75 -0
- package/dist/references/data-url.js.map +1 -0
- package/dist/references/formats.d.ts +13 -0
- package/dist/references/formats.d.ts.map +1 -0
- package/dist/references/formats.js +50 -0
- package/dist/references/formats.js.map +1 -0
- package/dist/references/index.d.ts +7 -0
- package/dist/references/index.d.ts.map +1 -0
- package/dist/references/index.js +7 -0
- package/dist/references/index.js.map +1 -0
- package/dist/references/limits.d.ts +15 -0
- package/dist/references/limits.d.ts.map +1 -0
- package/dist/references/limits.js +62 -0
- package/dist/references/limits.js.map +1 -0
- package/dist/references/project-files.d.ts +13 -0
- package/dist/references/project-files.d.ts.map +1 -0
- package/dist/references/project-files.js +148 -0
- package/dist/references/project-files.js.map +1 -0
- package/dist/references/remote-urls.d.ts +3 -0
- package/dist/references/remote-urls.d.ts.map +1 -0
- package/dist/references/remote-urls.js +22 -0
- package/dist/references/remote-urls.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +97 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/balance.d.ts +8 -0
- package/dist/tools/balance.d.ts.map +1 -0
- package/dist/tools/balance.js +133 -0
- package/dist/tools/balance.js.map +1 -0
- package/dist/tools/common.d.ts +3 -0
- package/dist/tools/common.d.ts.map +1 -0
- package/dist/tools/common.js +18 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/edit-image.d.ts +90 -0
- package/dist/tools/edit-image.d.ts.map +1 -0
- package/dist/tools/edit-image.js +47 -0
- package/dist/tools/edit-image.js.map +1 -0
- package/dist/tools/generate-image.d.ts +70 -0
- package/dist/tools/generate-image.d.ts.map +1 -0
- package/dist/tools/generate-image.js +36 -0
- package/dist/tools/generate-image.js.map +1 -0
- package/dist/tools/generation.d.ts +19 -0
- package/dist/tools/generation.d.ts.map +1 -0
- package/dist/tools/generation.js +154 -0
- package/dist/tools/generation.js.map +1 -0
- package/dist/tools/schema.d.ts +127 -0
- package/dist/tools/schema.d.ts.map +1 -0
- package/dist/tools/schema.js +47 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/types.d.ts +18 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/transport/direct.d.ts +10 -0
- package/dist/transport/direct.d.ts.map +1 -0
- package/dist/transport/direct.js +15 -0
- package/dist/transport/direct.js.map +1 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +4 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/kote-gateway.d.ts +19 -0
- package/dist/transport/kote-gateway.d.ts.map +1 -0
- package/dist/transport/kote-gateway.js +224 -0
- package/dist/transport/kote-gateway.js.map +1 -0
- package/dist/transport/types.d.ts +10 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +9 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/utils/abort.d.ts +19 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +121 -0
- package/dist/utils/abort.js.map +1 -0
- package/docs/architecture.md +75 -0
- package/docs/configuration.md +110 -0
- package/docs/image-to-image.md +101 -0
- package/docs/security.md +49 -0
- package/docs/testing.md +81 -0
- package/docs/troubleshooting.md +73 -0
- package/package.json +88 -0
- package/schema/plugin-options.schema.json +182 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Image-to-image guide
|
|
2
|
+
|
|
3
|
+
`openrouter_edit_image` is the primary tool. Use it whenever an existing image must influence the result: editing, variations, identity preservation, pose/background/object changes, style transfer, multi-reference composition, or creation of a matching game/UI asset.
|
|
4
|
+
|
|
5
|
+
It requires at least one reference and never falls back to text-to-image. Use `openrouter_generate_image` only when no existing image needs to be preserved or consulted.
|
|
6
|
+
|
|
7
|
+
## Reference sources
|
|
8
|
+
|
|
9
|
+
Three explicit reference types are available:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
[
|
|
13
|
+
{ "type": "message_attachment", "index": 1 },
|
|
14
|
+
{ "type": "project_file", "path": "assets/reference.webp" },
|
|
15
|
+
{ "type": "https_url", "url": "https://example.com/reference.jpeg" }
|
|
16
|
+
]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `message_attachment` indexes image attachments only, starting at 1. It resolves against the parent user message, not the full session history.
|
|
20
|
+
- `project_file` is relative to `context.worktree`. Only regular PNG, JPEG, and WebP files inside the real worktree are accepted.
|
|
21
|
+
- `https_url` accepts only public HTTPS URLs without embedded credentials. Tool arguments cannot contain `http:`, `file:`, `data:`, `blob:`, or `javascript:` references.
|
|
22
|
+
|
|
23
|
+
If `references` is omitted, all image attachments from the parent user message are used in original order. The plugin does not search older messages, scan the project, or reuse previous generations automatically.
|
|
24
|
+
|
|
25
|
+
## Ordering and prompt roles
|
|
26
|
+
|
|
27
|
+
Reference order is significant and preserved exactly. Reference 1 is primary; later references are supporting inputs. The plugin never sorts, drops, collages, or infers a role for a reference. State roles explicitly:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Reference image 1 defines the character identity, face, proportions, and illustration style.
|
|
31
|
+
Reference image 2 defines clothing only.
|
|
32
|
+
Reference image 3 defines the palette and lighting.
|
|
33
|
+
Change only the pose. Keep the full body visible. Do not add text or other characters.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A useful edit prompt describes:
|
|
37
|
+
|
|
38
|
+
1. the role of each reference;
|
|
39
|
+
2. properties to preserve;
|
|
40
|
+
3. the exact requested change;
|
|
41
|
+
4. composition, pose, camera, lighting, and output purpose;
|
|
42
|
+
5. exclusions, especially text or extra objects.
|
|
43
|
+
|
|
44
|
+
The plugin sends the prompt without translation, style injection, or a separate prompt-improvement LLM. Prompts are required, trimmed for validation, and limited to 32,000 characters.
|
|
45
|
+
|
|
46
|
+
## Examples
|
|
47
|
+
|
|
48
|
+
Single project asset:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"prompt": "Reference 1 is the source crate. Preserve the isometric camera, proportions, palette, and painted style. Create a damaged version with broken boards and scratched metal corners. Keep the silhouette recognizable.",
|
|
53
|
+
"references": [
|
|
54
|
+
{ "type": "project_file", "path": "assets/props/wooden-crate.png" }
|
|
55
|
+
],
|
|
56
|
+
"output_path": "assets/props/wooden-crate-damaged.png",
|
|
57
|
+
"n": 1
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Two attachments:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"prompt": "Reference 1 is the primary character and defines identity, face, and body proportions. Reference 2 defines clothing only. Preserve the rendering style from reference 1 and show the character waving with the right hand.",
|
|
66
|
+
"references": [
|
|
67
|
+
{ "type": "message_attachment", "index": 1 },
|
|
68
|
+
{ "type": "message_attachment", "index": 2 }
|
|
69
|
+
],
|
|
70
|
+
"output_format": "png",
|
|
71
|
+
"background": "transparent"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Style URL plus local subject:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"prompt": "Reference 1 defines the subject and silhouette. Reference 2 defines only palette, brushwork, and lighting. Preserve the subject while matching that visual style.",
|
|
80
|
+
"references": [
|
|
81
|
+
{ "type": "project_file", "path": "assets/source.png" },
|
|
82
|
+
{ "type": "https_url", "url": "https://example.com/style.webp" }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Encoding and limits
|
|
88
|
+
|
|
89
|
+
Local references are validated by MIME and magic bytes, then encoded in memory as `data:image/...;base64,...` inside `input_references`. Explicit HTTPS references are passed without download when no technical reason requires one. No third-party storage is used.
|
|
90
|
+
|
|
91
|
+
Defaults are 16 references, 20 MiB per local file, and 50 MiB total local bytes. All are configurable within documented constraints. Base64 growth and final request size are checked before building a huge JSON string. When limits are exceeded, the plugin does not silently resize, transcode, drop references, or fall back. Compress/resize the source, use fewer references, or supply a public HTTPS URL.
|
|
92
|
+
|
|
93
|
+
## Output
|
|
94
|
+
|
|
95
|
+
Outputs remain inside the worktree. `output_path` must be relative, may not escape through traversal or symlinks, and may not name a source or existing file. Multiple results receive `-01`, `-02`, and similar suffixes. Writes are atomic.
|
|
96
|
+
|
|
97
|
+
The actual returned bytes determine PNG/JPEG/WebP format and extension. A provider may return fewer results than requested or one invalid item; valid results are preserved and returned as OpenCode attachments with warnings. Cost is displayed only when OpenRouter supplies `usage.cost`.
|
|
98
|
+
|
|
99
|
+
## Capability failures
|
|
100
|
+
|
|
101
|
+
The configured model must advertise image input, image output, and `input_references`. Unsupported models or parameters fail before the paid request when discoverable. The plugin never substitutes another model and never converts an edit into generation.
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
## Trust boundaries
|
|
4
|
+
|
|
5
|
+
The plugin handles user-controlled prompts, references, paths, OpenRouter responses, and gateway descriptors. It treats all of them as untrusted until validated. OpenCode's current worktree and trusted attachment parts define filesystem and attachment boundaries.
|
|
6
|
+
|
|
7
|
+
## Secrets and diagnostics
|
|
8
|
+
|
|
9
|
+
OpenRouter generation and management keys are used in memory and are not persisted by the plugin. They must never appear in tool output, metadata, errors, logs, cache keys, gateway credentials, or bootstrap documents. Cache identity uses only an 8–12 character prefix of a SHA-256 key fingerprint.
|
|
10
|
+
|
|
11
|
+
Full prompts, base64, binary bytes, request/response bodies, authorization headers, proxy authorization, cookies, and full external URLs with query strings are also excluded. Structured diagnostics may contain safe fields such as model, routing, request ID, HTTP status, counts, byte sizes, relative output path, prompt length, and a short prompt hash.
|
|
12
|
+
|
|
13
|
+
Redaction covers at least `sk-or-*`, bearer/authorization values, proxy authorization, `OPENROUTER_API_KEY`, and `OPENROUTER_MANAGEMENT_API_KEY`. Upstream failures expose only a safe status, error code/message, and request ID—not an unredacted body or HTML response.
|
|
14
|
+
|
|
15
|
+
## Network controls
|
|
16
|
+
|
|
17
|
+
Production OpenRouter origin is fixed to `https://openrouter.ai/api/v1`. User-configurable base URLs are intentionally absent to prevent accidental key disclosure. Redirects are handled manually and all 3xx responses fail, so authorization is never forwarded to a redirect target.
|
|
18
|
+
|
|
19
|
+
Idempotent GETs may retry only temporary statuses, at most twice. `POST /images` is never automatically retried because its charge/result may be ambiguous. A lost connection after submission returns `GENIMAGE_REQUEST_STATUS_UNKNOWN`.
|
|
20
|
+
|
|
21
|
+
Explicit tool URLs must use HTTPS, contain no username/password, and stay within a bounded length. The plugin does not attach OpenRouter authorization to external URLs and does not automatically download arbitrary tool-supplied URLs. Internal OpenCode attachment URLs are resolved through trusted SDK/context mechanisms and are never forwarded externally.
|
|
22
|
+
|
|
23
|
+
Response bodies and decoded image bytes have independent configured limits. All operations combine caller abort, timeout, and transport abort.
|
|
24
|
+
|
|
25
|
+
## KoteGateway
|
|
26
|
+
|
|
27
|
+
Proxy routing is per request and does not replace `globalThis.fetch`. Gateway bootstrap signatures and descriptor validity are checked by reusable gateway core. Target headers and proxy headers remain separate.
|
|
28
|
+
|
|
29
|
+
The gateway sees a CONNECT tunnel. OpenRouter API keys, management keys, prompts, base64 references, and generated bytes stay inside TLS to OpenRouter and are never proxy credentials. Any bootstrap, signature, descriptor, cache, CONNECT, or proxy timeout failure ends the request. Direct fallback is forbidden.
|
|
30
|
+
|
|
31
|
+
Synthetic network tests exercise both load orders with one real CONNECT and verify that the image request carries no global route marker. Real OpenCode runtime compatibility remains a release smoke requirement.
|
|
32
|
+
|
|
33
|
+
## Files and images
|
|
34
|
+
|
|
35
|
+
Project input/output paths are relative to `context.worktree`. Absolute Unix paths, Windows drive paths, UNC paths, `..` traversal, directories, and realpath/symlink escapes are rejected. Project references must be regular files.
|
|
36
|
+
|
|
37
|
+
Input and output are limited to PNG, JPEG, and WebP and validated by magic bytes rather than extension/MIME alone. Source images are not modified or transcoded on disk. Output cannot overwrite a source or existing destination. Complete temporary writes are atomically published without replacement; failures clean up temporary data.
|
|
38
|
+
|
|
39
|
+
Static symlink and junction escapes are rejected and local reads stay on one bounded file handle. The worktree itself remains an operating-system trust boundary: a hostile local process that can rename directories concurrently with an operation can create filesystem races that portable Node.js APIs cannot close with descriptor-relative `openat` operations. Do not run the plugin against a worktree concurrently controlled by an untrusted local account.
|
|
40
|
+
|
|
41
|
+
Local images become base64 only in memory for the OpenRouter request. Base64 is prohibited in logs, errors, metadata, tool text output, command templates, and temporary text files.
|
|
42
|
+
|
|
43
|
+
## Attachments
|
|
44
|
+
|
|
45
|
+
Arbitrary file/data/blob URLs are not accepted from tool arguments. A file URL is trusted only when it originates from the current OpenCode message part. Attachment data URLs are parsed, bounded, decoded, magic-byte checked, and normalized. Internal OpenCode URLs are fetched only through the official SDK or a verified `context.serverUrl` origin.
|
|
46
|
+
|
|
47
|
+
## Reporting vulnerabilities
|
|
48
|
+
|
|
49
|
+
Do not open a public issue containing a vulnerability, key, prompt, private image, gateway descriptor, or full upstream response. Follow the private reporting process in [`SECURITY.md`](../SECURITY.md). Revoke any exposed OpenRouter key immediately.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
Tests are layered so most behavior is verified without paid network calls.
|
|
4
|
+
|
|
5
|
+
## Local checks
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun run lint
|
|
10
|
+
bun run typecheck
|
|
11
|
+
bun run test
|
|
12
|
+
bun run build
|
|
13
|
+
npm pack
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Useful focused commands:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun run test:unit
|
|
20
|
+
bun run test:integration
|
|
21
|
+
bun run test:smoke
|
|
22
|
+
bun run package:smoke
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Unit coverage
|
|
26
|
+
|
|
27
|
+
Unit tests cover strict configuration and environment-key resolution; ordered attachment/project/HTTPS references; MIME, magic-byte, base64, path, count, and size validation; generation parameter ranges and cross-field rules; model/capability caching; response parsing and partial success; atomic collision-safe output; balance terminology and partial credits failure; and redaction of secrets, prompts, and base64.
|
|
28
|
+
|
|
29
|
+
Important invariants include:
|
|
30
|
+
|
|
31
|
+
- edit requires a reference and cannot become text-to-image;
|
|
32
|
+
- model always comes from configuration;
|
|
33
|
+
- `n` is 1–10 and reference count is 1–16;
|
|
34
|
+
- transparent JPEG and PNG compression fail locally;
|
|
35
|
+
- source/existing files cannot be overwritten;
|
|
36
|
+
- absolute/traversing/symlink-escaping paths fail;
|
|
37
|
+
- key limit is never labeled account balance.
|
|
38
|
+
|
|
39
|
+
## Synthetic integration coverage
|
|
40
|
+
|
|
41
|
+
OpenRouter client tests use an injected fetch implementation to verify exact endpoints, authorization separation, manual redirect behavior, GET retry, no POST retry, timeout/abort, bounded bodies, and sanitized upstream failures. They do not start a real HTTPS server.
|
|
42
|
+
|
|
43
|
+
Tool integration tests decode received data URLs and compare bytes, preserve multiple-reference order, preserve prompts, use only the configured model, save results, and return attachment-shaped tool results.
|
|
44
|
+
|
|
45
|
+
Transport unit tests use injected gateway and fetch fakes to verify direct/gateway separation, lazy bootstrap, fail-closed errors, descriptor forwarding, target-header isolation, cancellation, and disposal.
|
|
46
|
+
|
|
47
|
+
The socket-level harness additionally starts a TLS signed-bootstrap service, TLS CONNECT proxy, and separate HTTPS OpenRouter target. It verifies direct bypass, one CONNECT in proxy mode, Ed25519 verification, ordered target bodies, TLS secrecy at the proxy boundary, invalid/expired bootstrap failures, refused CONNECT without direct fallback, and both image/gateway-plugin load orders without a route marker or double CONNECT. Test trust is scoped to per-request custom CA configuration; TLS verification is not disabled globally.
|
|
48
|
+
|
|
49
|
+
## OpenCode smoke tests
|
|
50
|
+
|
|
51
|
+
The baseline runtime is OpenCode **1.18.21**; the scaffold currently pins `@opencode-ai/plugin` **1.18.18**. The automated CLI smoke loads the installed tarball through a tuple and verifies its options, all three executable tool definitions, `/openrouter-balance`, plugin context (`directory`, `worktree`, `serverUrl`), the message-client attachment access shape, permissions, and CLI skill installation without executing a model request.
|
|
52
|
+
|
|
53
|
+
The CLI configuration probe does not execute a tool, so it does not validate `sessionID`/`messageID` tool context, a returned ToolResult attachment, or TUI/Desktop/Web rendering. Those are separate manual real-host release checks. Test the latest stable OpenCode again at release time.
|
|
54
|
+
|
|
55
|
+
The synthetic KoteGateway proxy/coexistence matrix runs in ordinary integration tests. CI also loads the installed tarball in real baseline/latest OpenCode runtimes. The KoteGateway adapter's narrower peer metadata is documented in the README; this plugin consumes its runtime-independent `/core` export.
|
|
56
|
+
|
|
57
|
+
Skill CLI tests cover project and global paths (the latter is always `~/.config/opencode/...`, including Windows), `check-skill --global`, nonzero checks for missing/modified files, SHA-256 no-op installs, refusal to replace modifications without `--force`, and atomic forced replacement.
|
|
58
|
+
|
|
59
|
+
## Real OpenRouter E2E (paid, opt-in)
|
|
60
|
+
|
|
61
|
+
Real tests are disabled in ordinary CI and pull requests. Run them only with an explicitly budgeted key/model:
|
|
62
|
+
|
|
63
|
+
Set `OPENROUTER_IMAGE_REFERENCE_URL_E2E` to a public PNG/JPEG/WebP URL to include the real HTTPS-reference check, or omit that assignment.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
RUN_OPENROUTER_E2E=1 \
|
|
67
|
+
OPENROUTER_API_KEY=... \
|
|
68
|
+
OPENROUTER_IMAGE_MODEL_E2E=... \
|
|
69
|
+
OPENROUTER_IMAGE_REFERENCE_URL_E2E=https://... \
|
|
70
|
+
bun test tests/e2e
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The current harness covers `/key`, text-to-image, one/two ordered inline data references, and an optional `usage.cost` field. One combined tool-level request also resolves an OpenCode message attachment plus a project file, optionally appends `OPENROUTER_IMAGE_REFERENCE_URL_E2E` unchanged, persists the generated image, and validates the returned ToolResult attachment. `/credits` is a separate opt-in test: set `RUN_OPENROUTER_CREDITS_E2E=1` and `OPENROUTER_MANAGEMENT_API_KEY`. Configure the public HTTPS variable for that release check; TUI/Desktop/Web rendering remains manual.
|
|
74
|
+
|
|
75
|
+
Never place E2E credentials, prompts with private content, or generated base64 in fixtures, logs, snapshots, artifacts, or the npm tarball.
|
|
76
|
+
|
|
77
|
+
## Package smoke
|
|
78
|
+
|
|
79
|
+
After `npm pack`, the package smoke installs the produced tarball with an isolated npm cache, imports its package-root and `/server` exports under Node with network blocked, invokes its installed bin to install/check the skill, runs the OpenCode smoke against the installed package, checks the file list, and scans for secrets/test credentials. It does not load the source tree as the plugin under test.
|
|
80
|
+
|
|
81
|
+
Do not claim a compatibility target or workflow as working until its automated test or documented manual smoke has passed.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Errors use a stable `GENIMAGE_*` code, a safe message, optional sanitized details, and a suggested action. They must not contain keys, prompts, base64, full upstream bodies, query-bearing URLs, or bootstrap documents.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
| Code | Cause and action |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `GENIMAGE_CONFIG_INVALID` | A required field is absent, an object contains unknown fields, a string is blank, a numeric/cross-field limit is invalid, or a forbidden base URL was supplied. Validate against `schema/plugin-options.schema.json`; also ensure `maxTotalBytes >= maxFileBytes`. No network request is made while blocked. |
|
|
10
|
+
| `GENIMAGE_OPENROUTER_KEY_MISSING` | Set `apiKey` via `{env:OPENROUTER_API_KEY}` or expose `OPENROUTER_API_KEY` to the OpenCode process. Restart OpenCode if its environment changed. |
|
|
11
|
+
|
|
12
|
+
## References
|
|
13
|
+
|
|
14
|
+
| Code | Cause and action |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `GENIMAGE_REFERENCE_REQUIRED` | `openrouter_edit_image` found no explicit reference and no image in the parent user message. Attach an image or pass a reference; there is no text-to-image fallback. |
|
|
17
|
+
| `GENIMAGE_REFERENCE_NOT_FOUND` | Verify the project-relative path and current worktree. |
|
|
18
|
+
| `GENIMAGE_REFERENCE_INDEX_INVALID` | Attachment indexes start at 1 and count only image attachments in the parent user message. |
|
|
19
|
+
| `GENIMAGE_REFERENCE_FORMAT_UNSUPPORTED` | Convert the input to PNG, JPEG, or WebP. Renaming an extension is insufficient. |
|
|
20
|
+
| `GENIMAGE_REFERENCE_TOO_LARGE` | Resize/compress that local image or use a public HTTPS URL. |
|
|
21
|
+
| `GENIMAGE_TOTAL_REFERENCE_SIZE_EXCEEDED` | Use fewer/smaller local references or public HTTPS URLs. |
|
|
22
|
+
| `GENIMAGE_REFERENCE_INVALID` | The MIME, base64, magic bytes, or file contents are invalid/mismatched. Re-export the image. |
|
|
23
|
+
| `GENIMAGE_REFERENCE_URL_INVALID` | Use a public `https://` URL without embedded credentials. `http:`, `file:`, `data:`, `blob:`, and `javascript:` tool URLs are rejected. |
|
|
24
|
+
| `GENIMAGE_TOO_MANY_REFERENCES` | Reduce references to the configured limit (maximum 16). Order is never changed automatically. |
|
|
25
|
+
| `GENIMAGE_ATTACHMENT_ACCESS_FAILED` | Reattach the image to the current message. For internal OpenCode URLs, confirm the client/runtime smoke target supports attachment retrieval. |
|
|
26
|
+
|
|
27
|
+
## Model and parameters
|
|
28
|
+
|
|
29
|
+
| Code | Cause and action |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `GENIMAGE_MODEL_NOT_FOUND` | Correct the configured OpenRouter model ID. The tool cannot select another model. |
|
|
32
|
+
| `GENIMAGE_MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT` | Configure a model/endpoint with image input and `input_references`; edit never falls back to generation. |
|
|
33
|
+
| `GENIMAGE_MODEL_DOES_NOT_SUPPORT_IMAGE_OUTPUT` | Configure a model with image output. |
|
|
34
|
+
| `GENIMAGE_PARAMETER_UNSUPPORTED` | Remove the unsupported tool/default parameter or deliberately change the configured model. Also check transparent JPEG, PNG compression, `n`, resolution, and endpoint-specific capabilities. |
|
|
35
|
+
|
|
36
|
+
## OpenRouter
|
|
37
|
+
|
|
38
|
+
| Code | Cause and action |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `GENIMAGE_OPENROUTER_AUTH` | Verify/recreate the generation key and its environment mapping. Do not paste it into logs/issues. |
|
|
41
|
+
| `GENIMAGE_OPENROUTER_FORBIDDEN` | Check key permissions and endpoint/model access. `/credits` needs management authorization. |
|
|
42
|
+
| `GENIMAGE_INSUFFICIENT_CREDITS` | Add credits or use a key/account with available funds. The plugin cannot purchase credits. |
|
|
43
|
+
| `GENIMAGE_RATE_LIMITED` | Wait for `Retry-After`; safe GETs have bounded retries, but paid POST generation is not retried. |
|
|
44
|
+
| `GENIMAGE_BAD_REQUEST` | Recheck prompt and parameters against model capabilities. |
|
|
45
|
+
| `GENIMAGE_REQUEST_TOO_LARGE` | Reduce image count/size. Base64 expands local payloads. |
|
|
46
|
+
| `GENIMAGE_PROVIDER_ERROR` | Retry later after checking OpenRouter/provider status. The plugin does not switch provider/model automatically. |
|
|
47
|
+
| `GENIMAGE_TIMEOUT` | Increase `requestTimeoutMs` only if appropriate and check connectivity. An image POST is not retried automatically. |
|
|
48
|
+
| `GENIMAGE_REQUEST_STATUS_UNKNOWN` | The connection failed after submission and the charge/result may be ambiguous. Check OpenRouter activity before manually repeating. |
|
|
49
|
+
| `GENIMAGE_RESPONSE_INVALID` / `GENIMAGE_IMAGE_INVALID` | OpenRouter returned missing, malformed, oversized, unsupported, or MIME-mismatched data. Valid partial results may still be saved with warnings. |
|
|
50
|
+
|
|
51
|
+
## KoteGateway
|
|
52
|
+
|
|
53
|
+
`GENIMAGE_GATEWAY_BOOTSTRAP_FAILED`, `GENIMAGE_GATEWAY_SIGNATURE_INVALID`, `GENIMAGE_GATEWAY_CACHE_EXPIRED`, `GENIMAGE_GATEWAY_CONNECT_FAILED`, and `GENIMAGE_GATEWAY_TIMEOUT` all stop a proxy-mode operation. Check bootstrap reachability, clock/signature/cache state, descriptor, proxy connectivity, and TLS CONNECT. Proxy mode intentionally never retries direct. Switch to `routing: "direct"` only as an explicit user configuration decision, not as an automated recovery.
|
|
54
|
+
|
|
55
|
+
When both gateway packages are installed, verify the tested versions and plugin load-order smoke matrix. The intended behavior is one per-request proxy for image traffic and no impact on ordinary OpenCode provider traffic.
|
|
56
|
+
|
|
57
|
+
## Output filesystem
|
|
58
|
+
|
|
59
|
+
| Code | Cause and action |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `GENIMAGE_OUTPUT_PATH_INVALID` | Use a relative file path, not an absolute path, drive/UNC path, directory, or traversal. |
|
|
62
|
+
| `GENIMAGE_OUTPUT_OUTSIDE_WORKTREE` | Remove `..` or a symlink that resolves outside `context.worktree`. |
|
|
63
|
+
| `GENIMAGE_OUTPUT_FORMAT_MISMATCH` | Use an extension consistent with the actual PNG/JPEG/WebP bytes; the plugin may correct the saved extension and warn. |
|
|
64
|
+
| `GENIMAGE_OUTPUT_WRITE_FAILED` | Check directory permissions/free space. Atomic writes should leave no partial final file. Existing files are not overwritten; choose another name. |
|
|
65
|
+
| `GENIMAGE_SOURCE_OVERWRITE_FORBIDDEN` | Choose a destination different from every source reference. |
|
|
66
|
+
|
|
67
|
+
## Balance terminology
|
|
68
|
+
|
|
69
|
+
`Remaining API-key spending limit` comes from `/key` and is not account balance. Full `Account credits` come from `/credits`, require a management key, and may legitimately calculate to a negative value. If credits are unavailable but key usage appears, this is intentional partial success.
|
|
70
|
+
|
|
71
|
+
## Safe diagnostic report
|
|
72
|
+
|
|
73
|
+
Include package version, OpenCode version/client, OS/runtime, routing mode, configured model ID, error code, safe HTTP status/request ID, reference count and byte sizes, and relative output path. Remove keys, authorization/proxy headers, full prompts, base64, private images, query strings, full upstream bodies, and gateway bootstrap data.
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openrouter-genimage-kote-opencode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenRouter image generation and image-to-image tools for OpenCode with optional KoteGateway routing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/koteyye/openrouter-genimage-kote-opencode.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/koteyye/openrouter-genimage-kote-opencode/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/koteyye/openrouter-genimage-kote-opencode#readme",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"opencode",
|
|
18
|
+
"openrouter",
|
|
19
|
+
"image-generation",
|
|
20
|
+
"image-to-image",
|
|
21
|
+
"plugin",
|
|
22
|
+
"kote-gateway"
|
|
23
|
+
],
|
|
24
|
+
"packageManager": "bun@1.3.14",
|
|
25
|
+
"main": "./dist/server.js",
|
|
26
|
+
"types": "./dist/server.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/server.d.ts",
|
|
30
|
+
"import": "./dist/server.js"
|
|
31
|
+
},
|
|
32
|
+
"./server": {
|
|
33
|
+
"types": "./dist/server.d.ts",
|
|
34
|
+
"import": "./dist/server.js"
|
|
35
|
+
},
|
|
36
|
+
"./skill": "./.opencode/skills/openrouter-image-generation/SKILL.md",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"openrouter-genimage-kote-opencode": "dist/cli.js"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
".opencode/skills",
|
|
45
|
+
"docs",
|
|
46
|
+
"schema",
|
|
47
|
+
"README.md",
|
|
48
|
+
"README.ru.md",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"SECURITY.md",
|
|
51
|
+
"THIRD_PARTY_NOTICES.md",
|
|
52
|
+
"CHANGELOG.md"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"provenance": true
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=20.0.0",
|
|
60
|
+
"opencode": ">=1.18.21 <2"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "bun scripts/build.ts",
|
|
64
|
+
"lint": "eslint .",
|
|
65
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
66
|
+
"test": "bun test",
|
|
67
|
+
"test:unit": "bun test tests/unit",
|
|
68
|
+
"test:integration": "bun test tests/integration tests/contract",
|
|
69
|
+
"test:smoke": "bun test tests/smoke",
|
|
70
|
+
"test:e2e": "bun test tests/e2e",
|
|
71
|
+
"opencode:smoke": "bun run build && bun scripts/opencode-smoke.ts",
|
|
72
|
+
"package:smoke": "bun run build && bun scripts/package-smoke.ts",
|
|
73
|
+
"prepack": "bun run build"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@koteye/kote-gateway-opencode": "0.1.1",
|
|
77
|
+
"@opencode-ai/plugin": "1.18.18",
|
|
78
|
+
"undici": "6.28.0",
|
|
79
|
+
"zod": "4.1.8"
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@eslint/js": "9.35.0",
|
|
83
|
+
"@types/bun": "1.3.14",
|
|
84
|
+
"eslint": "9.35.0",
|
|
85
|
+
"typescript": "5.9.2",
|
|
86
|
+
"typescript-eslint": "8.42.0"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/koteyye/openrouter-genimage-kote-opencode/blob/v0.1.0/schema/plugin-options.schema.json",
|
|
4
|
+
"title": "openrouter-genimage-kote-opencode plugin options",
|
|
5
|
+
"description": "Strict options for the OpenCode plugin tuple. Runtime validation additionally enforces trim-aware strings and cross-field relationships.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"routing",
|
|
10
|
+
"model"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"routing": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": [
|
|
16
|
+
"direct",
|
|
17
|
+
"proxy"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"model": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1,
|
|
23
|
+
"pattern": "\\S"
|
|
24
|
+
},
|
|
25
|
+
"apiKey": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"minLength": 1,
|
|
28
|
+
"maxLength": 4096,
|
|
29
|
+
"pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F]+$"
|
|
30
|
+
},
|
|
31
|
+
"managementApiKey": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"minLength": 1,
|
|
34
|
+
"maxLength": 4096,
|
|
35
|
+
"pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F]+$"
|
|
36
|
+
},
|
|
37
|
+
"outputDirectory": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 1,
|
|
40
|
+
"maxLength": 4096,
|
|
41
|
+
"pattern": "^(?![A-Za-z]:)(?![\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$))[^\\u0000-\\u001F\\u007F]+$",
|
|
42
|
+
"default": "assets/generated"
|
|
43
|
+
},
|
|
44
|
+
"defaults": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"properties": {
|
|
48
|
+
"aspect_ratio": {
|
|
49
|
+
"$ref": "#/$defs/imageAspectRatio"
|
|
50
|
+
},
|
|
51
|
+
"background": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": [
|
|
54
|
+
"auto",
|
|
55
|
+
"transparent",
|
|
56
|
+
"opaque"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"n": {
|
|
60
|
+
"type": "integer",
|
|
61
|
+
"minimum": 1,
|
|
62
|
+
"maximum": 10,
|
|
63
|
+
"default": 1
|
|
64
|
+
},
|
|
65
|
+
"output_format": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"enum": [
|
|
68
|
+
"png",
|
|
69
|
+
"jpeg",
|
|
70
|
+
"webp"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"output_compression": {
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"minimum": 0,
|
|
76
|
+
"maximum": 100
|
|
77
|
+
},
|
|
78
|
+
"quality": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": [
|
|
81
|
+
"auto",
|
|
82
|
+
"low",
|
|
83
|
+
"medium",
|
|
84
|
+
"high"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"resolution": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"enum": [
|
|
90
|
+
"512",
|
|
91
|
+
"1K",
|
|
92
|
+
"2K",
|
|
93
|
+
"4K"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"seed": {
|
|
97
|
+
"type": "integer",
|
|
98
|
+
"minimum": 0,
|
|
99
|
+
"maximum": 9007199254740991
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"input": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"additionalProperties": false,
|
|
106
|
+
"properties": {
|
|
107
|
+
"maxReferences": {
|
|
108
|
+
"type": "integer",
|
|
109
|
+
"minimum": 1,
|
|
110
|
+
"maximum": 16,
|
|
111
|
+
"default": 16
|
|
112
|
+
},
|
|
113
|
+
"maxFileBytes": {
|
|
114
|
+
"type": "integer",
|
|
115
|
+
"minimum": 1,
|
|
116
|
+
"maximum": 9007199254740991,
|
|
117
|
+
"default": 20971520
|
|
118
|
+
},
|
|
119
|
+
"maxTotalBytes": {
|
|
120
|
+
"type": "integer",
|
|
121
|
+
"minimum": 1,
|
|
122
|
+
"maximum": 9007199254740991,
|
|
123
|
+
"default": 52428800,
|
|
124
|
+
"description": "Runtime validation requires maxTotalBytes to be greater than or equal to maxFileBytes. Standard JSON Schema cannot compare arbitrary sibling numeric values."
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"requestTimeoutMs": {
|
|
129
|
+
"type": "integer",
|
|
130
|
+
"minimum": 1,
|
|
131
|
+
"maximum": 9007199254740991,
|
|
132
|
+
"default": 300000
|
|
133
|
+
},
|
|
134
|
+
"modelCacheTtlMs": {
|
|
135
|
+
"type": "integer",
|
|
136
|
+
"minimum": 1,
|
|
137
|
+
"maximum": 9007199254740991,
|
|
138
|
+
"default": 3600000
|
|
139
|
+
},
|
|
140
|
+
"maxResponseBytes": {
|
|
141
|
+
"type": "integer",
|
|
142
|
+
"minimum": 1,
|
|
143
|
+
"maximum": 9007199254740991,
|
|
144
|
+
"default": 134217728
|
|
145
|
+
},
|
|
146
|
+
"maxDecodedBytes": {
|
|
147
|
+
"type": "integer",
|
|
148
|
+
"minimum": 1,
|
|
149
|
+
"maximum": 9007199254740991,
|
|
150
|
+
"default": 134217728
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"$defs": {
|
|
154
|
+
"imageAspectRatio": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"enum": [
|
|
157
|
+
"1:1",
|
|
158
|
+
"1:2",
|
|
159
|
+
"1:4",
|
|
160
|
+
"1:8",
|
|
161
|
+
"2:1",
|
|
162
|
+
"2:3",
|
|
163
|
+
"3:2",
|
|
164
|
+
"3:4",
|
|
165
|
+
"4:1",
|
|
166
|
+
"4:3",
|
|
167
|
+
"4:5",
|
|
168
|
+
"5:4",
|
|
169
|
+
"8:1",
|
|
170
|
+
"9:16",
|
|
171
|
+
"16:9",
|
|
172
|
+
"9:19.5",
|
|
173
|
+
"19.5:9",
|
|
174
|
+
"9:20",
|
|
175
|
+
"20:9",
|
|
176
|
+
"9:21",
|
|
177
|
+
"21:9",
|
|
178
|
+
"auto"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|