opencode-dashscope-imagegen 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/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "opencode-dashscope-imagegen",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode plugin: text-to-image generation via Alibaba DashScope (qwen-image / wan models). Adds an image_generate tool.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "src"
13
+ ],
14
+ "scripts": {
15
+ "typecheck": "tsc --noEmit",
16
+ "build": "bun build src/index.ts --target node --format esm --outdir dist"
17
+ },
18
+ "keywords": [
19
+ "opencode",
20
+ "plugin",
21
+ "dashscope",
22
+ "qwen",
23
+ "image-generation",
24
+ "text-to-image"
25
+ ],
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@opencode-ai/plugin": "latest"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.5.0",
32
+ "@types/node": "^22.0.0"
33
+ }
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,171 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import { tool } from "@opencode-ai/plugin";
3
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
4
+ import { join, isAbsolute, dirname } from "node:path";
5
+
6
+ const LOG = "[dashscope-imagegen]";
7
+ const ENDPOINT =
8
+ "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation";
9
+ const DEFAULT_MODEL = "qwen-image-2.0";
10
+
11
+ console.log(`${LOG} plugin module loaded (pid=${process.pid}, platform=${process.platform})`);
12
+
13
+ function configDir(): string {
14
+ return (
15
+ process.env.OPENCODE_CONFIG_DIR ??
16
+ join(process.env.USERPROFILE ?? process.env.HOME ?? ".", ".config", "opencode")
17
+ );
18
+ }
19
+
20
+ /** Resolve DashScope API key: env first, then opencode config (dashscope* providers). */
21
+ function resolveApiKey(): string {
22
+ const fromEnv =
23
+ process.env.DASHSCOPE_IMAGEGEN_API_KEY ?? process.env.DASHSCOPE_API_KEY;
24
+ if (fromEnv) return fromEnv;
25
+
26
+ try {
27
+ const cfgPath = join(configDir(), "opencode.json");
28
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf-8")) as {
29
+ provider?: Record<string, { options?: { apiKey?: string } }>;
30
+ };
31
+ for (const [name, prov] of Object.entries(cfg.provider ?? {})) {
32
+ if (name.startsWith("dashscope") && prov?.options?.apiKey) {
33
+ return prov.options.apiKey;
34
+ }
35
+ }
36
+ } catch {
37
+ // fall through
38
+ }
39
+ throw new Error(
40
+ "No DashScope API key found. Set DASHSCOPE_API_KEY env or configure a dashscope* provider in opencode.json.",
41
+ );
42
+ }
43
+
44
+ function outputDir(): string {
45
+ return (
46
+ process.env.DASHSCOPE_IMAGEGEN_DIR ?? join(configDir(), "gen-images")
47
+ );
48
+ }
49
+
50
+ async function generateImageUrl(
51
+ apiKey: string,
52
+ model: string,
53
+ prompt: string,
54
+ size: string,
55
+ ): Promise<string> {
56
+ const res = await fetch(ENDPOINT, {
57
+ method: "POST",
58
+ headers: {
59
+ Authorization: `Bearer ${apiKey}`,
60
+ "Content-Type": "application/json",
61
+ },
62
+ body: JSON.stringify({
63
+ model,
64
+ input: {
65
+ messages: [{ role: "user", content: [{ text: prompt }] }],
66
+ },
67
+ parameters: { size },
68
+ }),
69
+ });
70
+ const json = (await res.json()) as {
71
+ code?: string;
72
+ message?: string;
73
+ output?: {
74
+ choices?: { message?: { content?: { image?: string }[] } }[];
75
+ };
76
+ };
77
+ const url = json.output?.choices?.[0]?.message?.content?.[0]?.image;
78
+ if (!url) {
79
+ throw new Error(
80
+ `DashScope image generation failed: ${json.code ?? res.status} ${json.message ?? ""}`,
81
+ );
82
+ }
83
+ return url;
84
+ }
85
+
86
+ interface GenArgs {
87
+ prompt: string;
88
+ model?: string;
89
+ size?: string;
90
+ output_path?: string;
91
+ }
92
+
93
+ async function runGenerate(args: GenArgs) {
94
+ const apiKey = resolveApiKey();
95
+ const model = args.model ?? DEFAULT_MODEL;
96
+ const size = args.size ?? "1024*1024";
97
+
98
+ const url = await generateImageUrl(apiKey, model, args.prompt, size);
99
+ const imgRes = await fetch(url);
100
+ if (!imgRes.ok) {
101
+ throw new Error(`Image download failed: ${imgRes.status}`);
102
+ }
103
+ const buf = Buffer.from(await imgRes.arrayBuffer());
104
+
105
+ let outPath = args.output_path;
106
+ if (!outPath) {
107
+ const dir = outputDir();
108
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
109
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
110
+ outPath = join(dir, `gen-${ts}.png`);
111
+ } else if (!isAbsolute(outPath)) {
112
+ throw new Error("output_path must be an absolute path");
113
+ } else if (!existsSync(dirname(outPath))) {
114
+ mkdirSync(dirname(outPath), { recursive: true });
115
+ }
116
+
117
+ writeFileSync(outPath, buf);
118
+ const dataUrl = `data:image/png;base64,${buf.toString("base64")}`;
119
+ return {
120
+ title: "Generated image",
121
+ output: `Image generated and saved to: ${outPath}`,
122
+ attachments: [
123
+ {
124
+ type: "file" as const,
125
+ mime: "image/png",
126
+ url: dataUrl,
127
+ filename: outPath.split(/[\\/]/).pop(),
128
+ },
129
+ ],
130
+ };
131
+ }
132
+
133
+ const DashscopeImagegenPlugin: Plugin = async () => {
134
+ console.log(`${LOG} plugin() invoked, registering image_generate tool`);
135
+ return {
136
+ tool: {
137
+ image_generate: tool({
138
+ description:
139
+ "Generate an image from a text prompt via Alibaba DashScope (qwen-image-2.0 / qwen-image-3.0 / wan2.7-image). Saves PNG to disk and returns the absolute file path plus an image attachment.",
140
+ args: {
141
+ prompt: tool.schema.string().describe("Image description"),
142
+ model: tool.schema
143
+ .string()
144
+ .optional()
145
+ .describe("Model id (default: qwen-image-2.0)"),
146
+ size: tool.schema
147
+ .string()
148
+ .optional()
149
+ .describe("Size as WxH with star, e.g. 1024*1024 (default)"),
150
+ output_path: tool.schema
151
+ .string()
152
+ .optional()
153
+ .describe("Absolute output file path (default: auto-named in gen-images dir)"),
154
+ },
155
+ async execute(args) {
156
+ console.log(
157
+ `${LOG} execute: prompt="${args.prompt.slice(0, 80)}" model=${args.model ?? DEFAULT_MODEL} size=${args.size ?? "1024*1024"}`,
158
+ );
159
+ try {
160
+ return await runGenerate(args);
161
+ } catch (e) {
162
+ console.error(`${LOG} execute failed:`, e);
163
+ throw e;
164
+ }
165
+ },
166
+ }),
167
+ },
168
+ };
169
+ };
170
+
171
+ export default DashscopeImagegenPlugin;