@velocirouter/pi 1.0.1 → 1.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/README.md +41 -7
- package/cost-stream.js +71 -0
- package/index.js +3 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
It registers VelociRouter as a provider and loads the model catalog from the
|
|
5
5
|
API.
|
|
6
6
|
|
|
7
|
-
Requires **Pi 0.
|
|
8
|
-
|
|
9
|
-
package's catalog refresh. Upgrade Pi before installing the package:
|
|
7
|
+
Requires **Pi 0.86.1 or later** from `@earendil-works/pi-coding-agent`.
|
|
8
|
+
If you already have a supported version, install the package:
|
|
10
9
|
|
|
11
10
|
```bash
|
|
12
|
-
npm uninstall -g @mariozechner/pi-coding-agent
|
|
13
|
-
npm install -g @earendil-works/pi-coding-agent@latest
|
|
14
11
|
pi install npm:@velocirouter/pi
|
|
15
12
|
```
|
|
16
13
|
|
|
@@ -18,6 +15,27 @@ Then run `/login` inside pi, pick **VelociRouter**, and paste your `rt-…` key.
|
|
|
18
15
|
Pi refreshes the catalog right after login, so the models show up in `/model`
|
|
19
16
|
straight away.
|
|
20
17
|
|
|
18
|
+
## Install or update Pi (if needed)
|
|
19
|
+
|
|
20
|
+
If Pi is not installed, or your version is below 0.86.1, install the latest
|
|
21
|
+
version, then run the package installation command above:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @earendil-works/pi-coding-agent@latest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Migrating from the legacy npm package
|
|
28
|
+
|
|
29
|
+
Only if you installed Pi through `@mariozechner/pi-coding-agent`, replace it
|
|
30
|
+
with the current distribution using the commands below. The legacy distribution
|
|
31
|
+
does not support this package's catalog refresh. Then run the package
|
|
32
|
+
installation command above.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm uninstall -g @mariozechner/pi-coding-agent
|
|
36
|
+
npm install -g @earendil-works/pi-coding-agent@latest
|
|
37
|
+
```
|
|
38
|
+
|
|
21
39
|
## What it registers
|
|
22
40
|
|
|
23
41
|
- **Provider** — `velocirouter`, an `openai-completions` endpoint.
|
|
@@ -30,8 +48,11 @@ straight away.
|
|
|
30
48
|
tokens pass it, and those tokens are the next request's input, so the
|
|
31
49
|
combined figure would let a long session overshoot the input ceiling.
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
Session costs use the billed USD total returned by VelociRouter in streaming
|
|
52
|
+
`usage.cost`. Enable **Cost in response** for your user in VelociRouter first.
|
|
53
|
+
This is reporting only; it does not add another charge. If the server omits cost,
|
|
54
|
+
the default model prices remain zero. Per-category cost breakdowns are not
|
|
55
|
+
available, and earlier turns recorded with zero cost are not updated.
|
|
35
56
|
|
|
36
57
|
The catalog is fetched when an interactive session starts, after `/login`, and
|
|
37
58
|
from the `/model` picker. `pi -p` and `pi --list-models` read the cached copy,
|
|
@@ -44,6 +65,19 @@ so open pi interactively once before using them.
|
|
|
44
65
|
| `VELOCIROUTER_API_KEY` | Used when no key was stored by `/login`. Set this in CI. |
|
|
45
66
|
| `VELOCIROUTER_BASE_URL` | Override the API host (default `https://api.velocirouter.site`). |
|
|
46
67
|
|
|
68
|
+
## Development checks
|
|
69
|
+
|
|
70
|
+
Run `npm test` with the Pi AI peer dependency available. To also check host
|
|
71
|
+
module resolution and streaming through Pi's actual extension loader, run:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
JITI_FS_CACHE=0 PI_TEST_INSTALL_DIR="$(npm root -g)/@earendil-works/pi-coding-agent" \
|
|
75
|
+
node --test loader.test.js
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The loader check uses the host Pi installation and needs no local Pi AI install.
|
|
79
|
+
It is skipped when `PI_TEST_INSTALL_DIR` is unset.
|
|
80
|
+
|
|
47
81
|
## Publishing
|
|
48
82
|
|
|
49
83
|
```bash
|
package/cost-stream.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createAssistantMessageEventStream, openAICompletionsApi } from "@earendil-works/pi-ai/compat"
|
|
2
|
+
|
|
3
|
+
const completions = openAICompletionsApi()
|
|
4
|
+
|
|
5
|
+
// Observe bytes inline: cloning the response would buffer a second copy and
|
|
6
|
+
// could finish reading usage after Pi has already emitted its final message.
|
|
7
|
+
function observeCost(body, onCost) {
|
|
8
|
+
const decoder = new TextDecoder()
|
|
9
|
+
let pending = ""
|
|
10
|
+
function consume(text) {
|
|
11
|
+
pending += text
|
|
12
|
+
let boundary
|
|
13
|
+
while ((boundary = /\r\n\r\n|\n\n|\r\r/.exec(pending))) {
|
|
14
|
+
const event = pending.slice(0, boundary.index)
|
|
15
|
+
pending = pending.slice(boundary.index + boundary[0].length)
|
|
16
|
+
const data = event.split(/\r\n|\r|\n/)
|
|
17
|
+
.filter((line) => line.startsWith("data:"))
|
|
18
|
+
.map((line) => line.slice(5).replace(/^ /, ""))
|
|
19
|
+
.join("\n")
|
|
20
|
+
try {
|
|
21
|
+
const cost = JSON.parse(data)?.usage?.cost
|
|
22
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) onCost(cost)
|
|
23
|
+
} catch {
|
|
24
|
+
// Non-JSON events (including [DONE]) remain the SDK's responsibility.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return body.pipeThrough(new TransformStream({
|
|
29
|
+
transform(chunk, controller) {
|
|
30
|
+
consume(decoder.decode(chunk, { stream: true }))
|
|
31
|
+
controller.enqueue(chunk)
|
|
32
|
+
},
|
|
33
|
+
flush() {
|
|
34
|
+
consume(decoder.decode() + "\n\n")
|
|
35
|
+
},
|
|
36
|
+
}))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function streamWithCost(model, context, options = {}) {
|
|
40
|
+
const output = createAssistantMessageEventStream()
|
|
41
|
+
const upstreamFetch = options.fetch ?? globalThis.fetch
|
|
42
|
+
let billedCost
|
|
43
|
+
const fetch = async (...args) => {
|
|
44
|
+
billedCost = undefined
|
|
45
|
+
const response = await upstreamFetch(...args)
|
|
46
|
+
if (!response.ok || !response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
47
|
+
return response
|
|
48
|
+
}
|
|
49
|
+
return new Response(observeCost(response.body, (cost) => { billedCost = cost }), {
|
|
50
|
+
status: response.status,
|
|
51
|
+
statusText: response.statusText,
|
|
52
|
+
headers: response.headers,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
// The built-in adapter retains ownership of requests, retries, tools, and errors.
|
|
56
|
+
const upstream = completions.streamSimple(model, context, { ...options, fetch })
|
|
57
|
+
void (async () => {
|
|
58
|
+
for await (const event of upstream) {
|
|
59
|
+
if (event.type === "done" || event.type === "error") {
|
|
60
|
+
const message = event.type === "done" ? event.message : event.error
|
|
61
|
+
if (billedCost !== undefined) {
|
|
62
|
+
// The server supplies a total, not a per-token-category breakdown.
|
|
63
|
+
message.usage.cost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: billedCost }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
output.push(event)
|
|
67
|
+
}
|
|
68
|
+
output.end()
|
|
69
|
+
})()
|
|
70
|
+
return output
|
|
71
|
+
}
|
package/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { streamWithCost } from "./cost-stream.js"
|
|
2
|
+
|
|
1
3
|
const PROVIDER = "velocirouter"
|
|
2
4
|
const API = "openai-completions"
|
|
3
5
|
const DEFAULT_BASE_URL = "https://api.velocirouter.site"
|
|
@@ -87,5 +89,6 @@ export default function velocirouter(pi) {
|
|
|
87
89
|
// app without these.
|
|
88
90
|
headers: { "HTTP-Referer": "https://pi.dev", "X-Title": "Pi" },
|
|
89
91
|
refreshModels,
|
|
92
|
+
streamSimple: streamWithCost,
|
|
90
93
|
})
|
|
91
94
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velocirouter/pi",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Pi coding agent package for VelociRouter — registers the provider and its model catalog.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"files": [
|
|
9
9
|
"index.js",
|
|
10
|
+
"cost-stream.js",
|
|
10
11
|
"README.md"
|
|
11
12
|
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@earendil-works/pi-ai": ">=0.86.1"
|
|
18
|
+
},
|
|
12
19
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
20
|
+
"node": ">=22.19.0"
|
|
14
21
|
},
|
|
15
22
|
"pi": {
|
|
16
23
|
"extensions": [
|