pi-qtools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 proyas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # pi-qtools
2
+
3
+ Bridge between [pi](https://pi.dev) and the **Qwen Token Plan** subscription.
4
+
5
+ The plan ships server-side tools and media models that pi cannot reach on its own:
6
+ pi talks `/chat/completions` to a chat model, while these live on `/responses`, on
7
+ native `/api/v1/services/...` endpoints, or behind async task polling. This package
8
+ exposes them as pi tools *and* slash commands.
9
+
10
+ Requires a `qwen-token-plan*` provider authenticated in pi (`/login`).
11
+
12
+ ## Tools (agent-callable)
13
+
14
+ | Tool | Backing capability | Notes |
15
+ |---|---|---|
16
+ | `qwen_web_search` | `/responses` `web_search` | answer + queries issued + source URLs |
17
+ | `qwen_web_extractor` | `/responses` `web_search` + `web_extractor` | page text filtered to a stated goal; the API requires the search tool alongside |
18
+ | `qwen_code_interpreter` | `/responses` `code_interpreter` | returns the Python it ran, container id, real stdout |
19
+ | `qwen_image_search` | `/responses` `web_search_image` | text → image URLs. **qwen3.8-max only** |
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
+ | `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/` |
23
+ | `qwen_generate_video` | native `video-generation` + task polling | `happyhorse-1.1-t2v` / `-i2v` / `-r2v`; async, ~90-120s; `save: true` writes the mp4 |
24
+
25
+ ## Passive main-loop search
26
+
27
+ When the active model is a Qwen plan chat model, `enable_search` is injected into
28
+ pi's own requests, so the model can search without calling a tool. Modes:
29
+
30
+ | mode | cost/turn | behaviour |
31
+ |---|---|---|
32
+ | `off` | 0 | memory only |
33
+ | `turbo` (default) | ~330 tok | searches, fresh answers |
34
+ | `max` | ~420 tok | wider search + inline `[n]` citations |
35
+
36
+ `search_strategy: "agent"` and `enable_code_interpreter` are **never** injected:
37
+ both flip Alibaba "Agent mode", which rejects any `tools` array, and pi always
38
+ sends one. That is why those two capabilities are standalone tools instead.
39
+
40
+ ## Commands
41
+
42
+ - `/qtools-config` — settings TUI (search mode, model defaults, capability matrix)
43
+ - `/qimage <prompt> [-m model] [-s WxH]` — saves into `out/qtools/`
44
+ - `/qvideo <prompt> [-i <path|url>] [-m model] [-s WxH]` — `-i` selects image-to-video; saves into `out/qtools/`
45
+
46
+ ### Getting parameter hints
47
+
48
+ pi exposes no `argumentHint` for extension commands, so `/qimage` and `/qvideo` make
49
+ themselves discoverable three ways:
50
+
51
+ 1. their **signature is in the command description**, visible in the `/` picker
52
+ 2. **`--help`** (or running with no prompt) prints every flag, the allowed values, the
53
+ active default and the exact output path
54
+ 3. **argument completion**: after `/qimage ` or on a partial flag, suggestions list
55
+ `-m`/`-s`/`-i`/`--help`, and after `-m `/`-s ` they list the valid models/sizes
56
+
57
+ Completions are suppressed while a prompt is being typed, because pi replaces the
58
+ whole argument region with the chosen value — offering a flag mid-prompt would
59
+ delete what was already written.
60
+
61
+ ## Video generation schema (measured)
62
+
63
+ All three models post to `/api/v1/services/aigc/video-generation/video-synthesis`
64
+ with `X-DashScope-Async: enable`, then poll `GET /api/v1/tasks/{id}`.
65
+
66
+ ```json
67
+ {
68
+ "model": "happyhorse-1.1-i2v",
69
+ "input": {
70
+ "prompt": "the pig blinks slowly",
71
+ "media": [{ "type": "first_frame", "url": "https://... or data:image/png;base64,..." }]
72
+ },
73
+ "parameters": { "size": "1280*720" }
74
+ }
75
+ ```
76
+
77
+ | fact | value |
78
+ |---|---|
79
+ | required field | `input.media` (an array; `img_url` / `image_url` / `ref_url` are all ignored) |
80
+ | `media[].type` for i2v | exactly `"first_frame"` |
81
+ | `media[].type` for r2v | exactly `"reference_image"` |
82
+ | t2v | takes no `media` at all |
83
+ | status of each mode | t2v, i2v and r2v all reached `SUCCEEDED` end to end |
84
+ | minimum source image | **300x300** |
85
+ | local files | supported by inlining a `data:` URL; verified with a 5.0 MB PNG (6.7 MB base64) |
86
+ | output resolution | follows the **source aspect ratio**; `size` is advisory for i2v/r2v (a square frame returned 1440x1440) |
87
+
88
+ **Critical gotcha:** the submit call returns **HTTP 200 with a `task_id` even when the
89
+ request is wrong**. Every shape above was discovered by polling a *failed* task and
90
+ reading `output.message`, not by reading the submit response. Never treat a successful
91
+ submit as a successful generation — this tool surfaces `status` and `detail` for that reason.
92
+
93
+ Image generation sizes must be **589824-16777216 total pixels** (768² to 4096²); `512*512`
94
+ is rejected.
95
+
96
+ ## Configuration
97
+
98
+ Persisted to `<agentDir>/qtools.json` (migrated automatically from the older
99
+ `qwen-tools.json` on first load):
100
+
101
+ ```json
102
+ {
103
+ "searchMode": "turbo",
104
+ "fallbackModel": "qwen3.7-max",
105
+ "showReasoning": false,
106
+ "deepSearchMaxTokens": 16384,
107
+ "imageModel": "wan2.7-image",
108
+ "videoModel": "happyhorse-1.1-t2v"
109
+ }
110
+ ```
111
+
112
+ ## Verified capability matrix
113
+
114
+ Measured against the live endpoint rather than taken from docs, because the two
115
+ surfaces differ per model:
116
+
117
+ | model | chat search | agent | chat code_int | `/responses` tools | `web_search_image` | `image_search` |
118
+ |---|---|---|---|---|---|---|
119
+ | qwen3.8-max | yes | no | no | all | **yes** | yes |
120
+ | qwen3.8-flash | yes | no | no | s/c/e | no | yes |
121
+ | qwen3.7-max | yes | yes | yes | s/c/e | no | no |
122
+ | qwen3.7-plus | yes | yes | yes | s/c/e | no | yes |
123
+ | qwen3.6-flash | yes | yes | yes | s/c/e | no | yes |
124
+ | deepseek-v4-* | yes | yes | yes | s/c/e | no | no |
125
+ | glm-5.2 | **no** | no | no | s/c/e | no | no |
126
+
127
+ `glm-5.2` rejects `enable_search` with a hard error, so injection is skipped for it;
128
+ without that guard every turn would fail after a model switch.
129
+
130
+ ## Known gaps
131
+
132
+ - **TTS / ASR are not implemented.** `qwen-audio-3.0-tts-plus` resolves as a model on
133
+ `/api/v1/services/audio/tts/SpeechSynthesizer` but the engine returns
134
+ `InvalidParameter [cosyvoice:] Engine error [411]: TTS speak operation failed` for
135
+ every request shape tried (plain, `format`/`sample_rate`, `parameters`, and with
136
+ `X-DashScope-SSE: enable`). `/api/v1/services/audio/asr/transcription` reports it
137
+ needs async on one call and `url error` when given the async header. The correct
138
+ request contract is still unknown — likely WebSocket-only.
139
+ - `happyhorse-1.1-r2v` reaches `SUCCEEDED` with `media[].type: "reference_image"`, so the
140
+ request shape is confirmed. Whether the output faithfully follows the reference was not
141
+ checked (that needs vision comparison, which this package does not add).
142
+ - Realtime voice (`qwen-audio-3.0-realtime-plus`) is intentionally out of scope.
143
+ - pi's model catalogue labels `qwen3.7-plus` as `input: ['text','image']`, but the
144
+ gateway rejects image input on it ("only supports text modality"). Upstream bug.
145
+ - Console capability labels differ from API `tools[].type` strings. The Qwen UI says
146
+ `t2i_search`/`i2i_search`; the API wants `web_search_image`/`image_search`. Sending
147
+ a label is **silently ignored** — the endpoint accepts any type string, including
148
+ nonsense, so "no error" does not mean "supported". `/qtools-config` lists the mapping.
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ pi install ./path/to/qtools # loads from this path, no copy
154
+ pi -e ./extensions/qtools.ts "prompt" # one-off load
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT