pi-qtools 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 +24 -3
- package/extensions/qtools.ts +47 -14
- package/package.json +31 -7
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Requires a `qwen-token-plan*` provider authenticated in pi (`/login`).
|
|
|
19
19
|
| `qwen_image_search` | `/responses` `web_search_image` | text → image URLs. **qwen3.8-max only** |
|
|
20
20
|
| `qwen_reverse_image_search` | `/responses` `image_search` | image URL or local file → similar images; accepts a local path and inlines it as a data URL (4 MB cap) |
|
|
21
21
|
| `qwen_deep_search` | chat `search_strategy: "agent"` | multi-step research; very expensive (~340k prompt tokens observed) |
|
|
22
|
-
| `qwen_generate_image` | native `multimodal-generation` | `wan2.7-image`, `wan2.7-image-pro`, `qwen-image-3.0-pro`; sync; `save: true` writes into `<cwd>/out/qtools/` |
|
|
22
|
+
| `qwen_generate_image` | native `multimodal-generation` | `wan2.7-image`, `wan2.7-image-pro`, `qwen-image-3.0-pro`; sync; `image` edits a reference picture; `save: true` writes into `<cwd>/out/qtools/` |
|
|
23
23
|
| `qwen_generate_video` | native `video-generation` + task polling | `happyhorse-1.1-t2v` / `-i2v` / `-r2v`; async, ~90-120s; `save: true` writes the mp4 |
|
|
24
24
|
|
|
25
25
|
## Passive main-loop search
|
|
@@ -40,10 +40,31 @@ sends one. That is why those two capabilities are standalone tools instead.
|
|
|
40
40
|
## Commands
|
|
41
41
|
|
|
42
42
|
- `/qtools-config` — settings TUI (search mode, model defaults, capability matrix)
|
|
43
|
-
- `/qimage <prompt> [-m model] [-s WxH]` — saves into `out/qtools/`
|
|
43
|
+
- `/qimage <prompt> [-i img] [-m model] [-s WxH]` — saves into `out/qtools/`
|
|
44
44
|
- `/qvideo <prompt> [-i <path|url>] [-m model] [-s WxH]` — `-i` selects image-to-video; saves into `out/qtools/`
|
|
45
45
|
|
|
46
|
-
###
|
|
46
|
+
### Image editing (measured)
|
|
47
|
+
|
|
48
|
+
Passing a reference image turns the same endpoint from text-to-image into
|
|
49
|
+
image+prompt editing. The shape is one extra content part:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{ "input": { "messages": [ { "role": "user", "content": [
|
|
53
|
+
{ "image": "https://... or data:image/png;base64,..." },
|
|
54
|
+
{ "text": "turn this into a red panda, keep the flat vector style" }
|
|
55
|
+
] } ] } }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Verified: a flat-vector pig + that prompt came back a red panda **in the same
|
|
59
|
+
style**, and a local file inlined as a data URL worked too. Two reference images
|
|
60
|
+
in one message are accepted.
|
|
61
|
+
|
|
62
|
+
The trap here is that a wrong request shape still returns a valid image, just
|
|
63
|
+
generated from the text alone. Every claim above was checked by asking a vision
|
|
64
|
+
model (`qwen3.8-max`, which has real image input on this plan) to describe the
|
|
65
|
+
output, not by trusting the HTTP 200.
|
|
66
|
+
|
|
67
|
+
## Getting parameter hints
|
|
47
68
|
|
|
48
69
|
pi exposes no `argumentHint` for extension commands, so `/qimage` and `/qvideo` make
|
|
49
70
|
themselves discoverable three ways:
|
package/extensions/qtools.ts
CHANGED
|
@@ -643,18 +643,40 @@ function extractImageUrls(body: any): string[] {
|
|
|
643
643
|
/** Shared core so the tool and the /qimage command behave identically. */
|
|
644
644
|
async function generateImage(
|
|
645
645
|
ctx: ExtensionContext,
|
|
646
|
-
opts: {
|
|
647
|
-
|
|
646
|
+
opts: {
|
|
647
|
+
prompt: string;
|
|
648
|
+
model?: string;
|
|
649
|
+
negativePrompt?: string;
|
|
650
|
+
size?: string;
|
|
651
|
+
/** Reference image to edit: local path, http(s) URL, or data: URL. */
|
|
652
|
+
image?: string;
|
|
653
|
+
signal?: AbortSignal;
|
|
654
|
+
},
|
|
655
|
+
): Promise<{ model: string; urls: string[]; raw: unknown; edited?: string }> {
|
|
648
656
|
const model = opts.model && IMAGE_MODELS.includes(opts.model) ? opts.model : config.imageModel;
|
|
649
657
|
const parameters: Record<string, unknown> = { result_format: "message" };
|
|
650
658
|
if (opts.size) parameters.size = opts.size;
|
|
651
659
|
if (opts.negativePrompt) parameters.negative_prompt = opts.negativePrompt;
|
|
660
|
+
|
|
661
|
+
// Verified shape: an extra {image} content part next to the text switches this
|
|
662
|
+
// endpoint from text-to-image to image+prompt editing. Confirmed by vision:
|
|
663
|
+
// a flat-vector pig + "turn this into a red panda" came back a red panda in the
|
|
664
|
+
// same style, and a local file inlined as a data URL worked too.
|
|
665
|
+
const content: Array<Record<string, unknown>> = [];
|
|
666
|
+
let edited: string | undefined;
|
|
667
|
+
if (opts.image?.trim()) {
|
|
668
|
+
const ref = toImageUrl(opts.image.trim());
|
|
669
|
+
content.push({ image: ref });
|
|
670
|
+
edited = ref.startsWith("data:") ? `inline image (${Math.round(ref.length / 1024)} KB base64)` : ref;
|
|
671
|
+
}
|
|
672
|
+
content.push({ text: opts.prompt });
|
|
673
|
+
|
|
652
674
|
const r = await nativeCall(ctx, MG_PATH, {
|
|
653
675
|
model,
|
|
654
|
-
input: { messages: [{ role: "user", content
|
|
676
|
+
input: { messages: [{ role: "user", content }] },
|
|
655
677
|
parameters,
|
|
656
678
|
}, { signal: opts.signal });
|
|
657
|
-
return { model, urls: extractImageUrls(r.body), raw: r.body };
|
|
679
|
+
return { model, urls: extractImageUrls(r.body), raw: r.body, edited };
|
|
658
680
|
}
|
|
659
681
|
|
|
660
682
|
/**
|
|
@@ -761,9 +783,11 @@ function genUsage(kind: "image" | "video", cwd: string): string {
|
|
|
761
783
|
const outDir = join(cwd, "out", "qtools");
|
|
762
784
|
if (kind === "image") {
|
|
763
785
|
return [
|
|
764
|
-
"/qimage <prompt> [-m model] [-s WxH]",
|
|
786
|
+
"/qimage <prompt> [-i img] [-m model] [-s WxH]",
|
|
765
787
|
"",
|
|
766
788
|
" prompt what to draw. No quotes needed: flags are stripped, the rest is the prompt.",
|
|
789
|
+
" -i reference image to edit (local path, http(s) or data URL).",
|
|
790
|
+
" With -i this is image+prompt editing, not text-to-image.",
|
|
767
791
|
` -m model: ${IMAGE_MODELS.join(", ")} (default: ${config.imageModel})`,
|
|
768
792
|
" -s e.g. 1024x1024. Must be 589824-16777216 total pixels (768x768 .. 4096x4096).",
|
|
769
793
|
" --help this text",
|
|
@@ -1342,14 +1366,23 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1342
1366
|
description:
|
|
1343
1367
|
"Generate images with the Qwen plan's image models (wan2.7-image, wan2.7-image-pro, qwen-image-3.0-pro) " +
|
|
1344
1368
|
"through the native multimodal-generation endpoint. These are not chat models, so they are unreachable " +
|
|
1345
|
-
"from pi's normal model list.
|
|
1346
|
-
|
|
1369
|
+
"from pi's normal model list. Pass `image` to edit an existing picture instead of drawing from scratch: " +
|
|
1370
|
+
"the reference plus the prompt produce a new image that keeps the source's style and subject layout. " +
|
|
1371
|
+
"Returns temporary hosted image URLs.",
|
|
1372
|
+
promptSnippet: "Generate or edit images with Qwen/Wan image models",
|
|
1347
1373
|
promptGuidelines: [
|
|
1348
1374
|
"Use qwen_generate_image when the user asks to create or draw an image; report the returned URLs as links and note they are temporary hosted files.",
|
|
1375
|
+
"Use qwen_generate_image with the image parameter when the user wants an existing picture restyled, edited, or used as a reference.",
|
|
1349
1376
|
"qwen_generate_image cannot render images in the terminal, so hand the user the URL rather than describing pixels you cannot see.",
|
|
1350
1377
|
],
|
|
1351
1378
|
parameters: Type.Object({
|
|
1352
|
-
prompt: Type.String({ description: "What to draw." }),
|
|
1379
|
+
prompt: Type.String({ description: "What to draw, or how to change the reference image." }),
|
|
1380
|
+
image: Type.Optional(
|
|
1381
|
+
Type.String({
|
|
1382
|
+
description:
|
|
1383
|
+
"Reference image to edit: http(s) URL, data: URL, or local file path. Omit for text-to-image.",
|
|
1384
|
+
}),
|
|
1385
|
+
),
|
|
1353
1386
|
model: Type.Optional(Type.String({ description: `Override model. One of: ${IMAGE_MODELS.join(", ")}.` })),
|
|
1354
1387
|
negativePrompt: Type.Optional(Type.String({ description: "What to avoid." })),
|
|
1355
1388
|
size: Type.Optional(Type.String({ description: "Pixel size, e.g. 1024*1024. Must be 589824-16777216 total pixels (768² to 4096²)." })),
|
|
@@ -1366,7 +1399,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1366
1399
|
files = await Promise.all(r.urls.map((u) => saveMedia(ctx, u, r.model, signal)));
|
|
1367
1400
|
text += `\n\nSaved:\n${files.map((f) => ` ${f}`).join("\n")}`;
|
|
1368
1401
|
}
|
|
1369
|
-
return { content: [{ type: "text", text }], details: { model: r.model, urls: r.urls, files } };
|
|
1402
|
+
return { content: [{ type: "text", text }], details: { model: r.model, urls: r.urls, files, edited: r.edited } };
|
|
1370
1403
|
},
|
|
1371
1404
|
});
|
|
1372
1405
|
|
|
@@ -1422,20 +1455,20 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1422
1455
|
|
|
1423
1456
|
// ---------------------------------------------------------------- commands
|
|
1424
1457
|
pi.registerCommand("qimage", {
|
|
1425
|
-
description: `Generate an image — /qimage <prompt> [-m model] [-s WxH]
|
|
1426
|
-
getArgumentCompletions: (prefix) => genCompletions(prefix, { models: IMAGE_MODELS, sizes: [], image:
|
|
1458
|
+
description: `Generate or edit an image — /qimage <prompt> [-i img] [-m model] [-s WxH]`,
|
|
1459
|
+
getArgumentCompletions: (prefix) => genCompletions(prefix, { models: IMAGE_MODELS, sizes: [], image: true }),
|
|
1427
1460
|
handler: async (args, ctx) => {
|
|
1428
1461
|
if (wantsHelp(args)) {
|
|
1429
1462
|
ctx.ui.notify(genUsage("image", ctx.cwd), "info");
|
|
1430
1463
|
return;
|
|
1431
1464
|
}
|
|
1432
|
-
const { prompt, model, size } = parseGenArgs(args, IMAGE_MODELS);
|
|
1465
|
+
const { prompt, model, size, image } = parseGenArgs(args, IMAGE_MODELS);
|
|
1433
1466
|
if (!prompt) {
|
|
1434
1467
|
ctx.ui.notify(genUsage("image", ctx.cwd), "error");
|
|
1435
1468
|
return;
|
|
1436
1469
|
}
|
|
1437
|
-
ctx.ui.notify(
|
|
1438
|
-
const r = await generateImage(ctx, { prompt, model, size });
|
|
1470
|
+
ctx.ui.notify(`${image ? "Editing" : "Generating"} image with ${model || config.imageModel}...`, "info");
|
|
1471
|
+
const r = await generateImage(ctx, { prompt, model, size, image });
|
|
1439
1472
|
if (!r.urls.length) {
|
|
1440
1473
|
ctx.ui.notify(`No image URL from ${r.model}`, "error");
|
|
1441
1474
|
return;
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-qtools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Qwen Token Plan bridge for pi: web search, scraping, code interpreter, image search, and image/video generation via DashScope built-in tools and native task endpoints.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"keywords": [
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi",
|
|
9
|
+
"pi-coding-agent",
|
|
10
|
+
"qwen",
|
|
11
|
+
"dashscope",
|
|
12
|
+
"web-search",
|
|
13
|
+
"web-scraping",
|
|
14
|
+
"code-interpreter",
|
|
15
|
+
"image-search",
|
|
16
|
+
"image-generation",
|
|
17
|
+
"video-generation"
|
|
18
|
+
],
|
|
7
19
|
"license": "MIT",
|
|
8
20
|
"publishConfig": {
|
|
9
21
|
"access": "public"
|
|
@@ -17,18 +29,30 @@
|
|
|
17
29
|
"bugs": {
|
|
18
30
|
"url": "https://github.com/YasribProyas/pi-qtools/issues"
|
|
19
31
|
},
|
|
20
|
-
"files": [
|
|
32
|
+
"files": [
|
|
33
|
+
"extensions",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
21
37
|
"peerDependencies": {
|
|
22
38
|
"@earendil-works/pi-coding-agent": "*",
|
|
23
39
|
"@earendil-works/pi-tui": "*",
|
|
24
40
|
"typebox": "*"
|
|
25
41
|
},
|
|
26
42
|
"peerDependenciesMeta": {
|
|
27
|
-
"@earendil-works/pi-coding-agent": {
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
"@earendil-works/pi-coding-agent": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"@earendil-works/pi-tui": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"typebox": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
30
52
|
},
|
|
31
53
|
"pi": {
|
|
32
|
-
"extensions": [
|
|
54
|
+
"extensions": [
|
|
55
|
+
"./extensions/qtools.ts"
|
|
56
|
+
]
|
|
33
57
|
}
|
|
34
58
|
}
|