pi-typesafe-router 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 +21 -0
- package/README.md +77 -0
- package/docs/architecture.md +59 -0
- package/docs/configuration.md +135 -0
- package/docs/privacy.md +35 -0
- package/docs/releasing.md +26 -0
- package/examples/cloudflare.json +41 -0
- package/examples/typesafe.json +40 -0
- package/examples/vercel.json +41 -0
- package/package.json +74 -0
- package/src/classifier.ts +271 -0
- package/src/config.ts +100 -0
- package/src/context.ts +68 -0
- package/src/diagnostics.ts +144 -0
- package/src/generation-probe.ts +105 -0
- package/src/host.ts +81 -0
- package/src/index.ts +1118 -0
- package/src/routing.ts +84 -0
- package/src/settings.ts +63 -0
- package/src/types.ts +110 -0
- package/src/verification.ts +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pi-typesafe-router contributors
|
|
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,77 @@
|
|
|
1
|
+
# pi-typesafe-router
|
|
2
|
+
|
|
3
|
+
An opt-in [Pi](https://github.com/earendil-works/pi) extension that asks Jev to classify a task, then selects a generation model from your ordered mappings.
|
|
4
|
+
|
|
5
|
+
Classification supports **TypeSafe direct, Cloudflare Workers AI, and Vercel AI Gateway**. Model fallback happens only before generation. A failed task is never automatically replayed by this extension.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Requires Node.js 22.19+ and Pi 0.85.1. This is an unpublished local implementation; no live backend credentials have been tested.
|
|
10
|
+
|
|
11
|
+
From this checkout, install dependencies and load the extension for one session:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm ci
|
|
15
|
+
pi -e ./src/index.ts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Do not enable this alongside another automatic router. Choose a real provider/model in Pi first, then run:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
/typesafe-router setup typesafe
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This creates `~/.pi/agent/typesafe-router.json`, initially **off**, using your selected model for all three routes. It never overwrites a file. Edit the mappings, set `TYPESAFE_API_KEY` in the environment that launches Pi, and restart Pi. Never put the key in your prompt or config file.
|
|
25
|
+
|
|
26
|
+
For a persistent local installation, use `pi install /absolute/path/to/pi-typesafe-router`. Once published, the equivalent will be `pi install npm:pi-typesafe-router`.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Apply configuration and verify the classifier and generation targets, then enable routing:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
/typesafe-router doctor
|
|
34
|
+
/typesafe-router on
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`doctor` checks local eligibility, sends one synthetic classifier request, and probes each distinct configured provider/model with an isolated synthetic generation request. No actual conversation transcript or tools are sent. **These requests may incur charges**, even while routing is off; there is no strict monetary cap. Explicit invocation also works headless with `allowHeadless: false`. Doctor never changes the selected model or runs or replays your task.
|
|
38
|
+
|
|
39
|
+
Doctor must complete successfully, including the classifier check, with at least one locally eligible, successfully probed target in **each** of `quick`, `standard`, and `deep`. Until then, `on`, `shadow`, and automatic input are blocked; `off` and manual Pi use remain available. Subsequent preflight skips failed targets, even the first configured target, and preserves the configured order of remaining successful candidates. A probe is a health snapshot, not a guarantee that a later task will succeed.
|
|
40
|
+
|
|
41
|
+
Verification is session-only and never persisted. Restart or reload requires doctor again. Changes to routes, credential references, backend configuration, or model metadata—even under the same provider/model ID—invalidate verification. Each doctor refresh discards old proofs; cancellation or incomplete checks never partially unlock routing.
|
|
42
|
+
|
|
43
|
+
`/typesafe-router` (or `status`) is read-only: it reports the applied configuration, runtime mode, current model, and on-disk differences without network calls. Reports include next steps only where relevant. Pi's built-in `/reload` loads updated extension code; it is not needed for configuration changes—use `/typesafe-router doctor` instead.
|
|
44
|
+
|
|
45
|
+
Use `shadow` instead of `on` to classify without changing the model. **Shadow mode still transmits text and can incur charges.** `/typesafe-router off` stops classification. `/model` selection also turns automatic routing off. Session mode changes are recorded in that session. `doctor` reloads valid configuration while preserving the current session mode; it never enables routing that is off. Missing or invalid configuration disables routing.
|
|
46
|
+
|
|
47
|
+
Escape, Ctrl+C, or `/typesafe-router off` cancels preflight and stops the original prompt. If Pi is already resolving generation credentials, cancellation waits for its non-cancellable model setter; verify the selected model before resubmitting. Prompts rejected or cancelled during preflight are not automatically queued or replayed.
|
|
48
|
+
|
|
49
|
+
After a failed generation, use Pi's `/model` to choose a model, inspect completed tool effects, and manually continue when safe. The router never selects a fallback after generation or replays a task. Pi's own retry and compaction settings remain unchanged.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
See the [configuration reference](docs/configuration.md) and examples for [TypeSafe](examples/typesafe.json), [Cloudflare](examples/cloudflare.json), and [Vercel](examples/vercel.json). Only the global file is read; `PI_CODING_AGENT_DIR` relocates it. Project-local configuration is intentionally ignored.
|
|
54
|
+
|
|
55
|
+
| Backend | Credential | Model |
|
|
56
|
+
| --------------------- | -------------------------------------------- | ----------------- |
|
|
57
|
+
| TypeSafe | `TYPESAFE_API_KEY` | `jev-1.13.0` |
|
|
58
|
+
| Cloudflare Workers AI | `CLOUDFLARE_API_TOKEN`, account ID in config | `typesafe/jev` |
|
|
59
|
+
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `typesafe-ai/jev` |
|
|
60
|
+
|
|
61
|
+
Backends are explicit; there is no cross-backend failover. Vercel uses the experimental AI SDK evaluation protocol, not a chat-completions endpoint. Missing confidence uses the conservative route; confidence is **not** the probability that the generation model will solve your task.
|
|
62
|
+
|
|
63
|
+
## Project structure
|
|
64
|
+
|
|
65
|
+
- `src/classifier.ts`: transport adapters and response validation.
|
|
66
|
+
- `src/config.ts`, `context.ts`, `routing.ts`: schema, bounded text projection, and deterministic policy.
|
|
67
|
+
- `src/index.ts`, `settings.ts`: Pi lifecycle, commands, cancellation, and global settings.
|
|
68
|
+
- `tests/`: synthetic transport, policy, lifecycle, and real Pi SDK tests. No paid API calls.
|
|
69
|
+
- `docs/`: [privacy](docs/privacy.md), [architecture and limits](docs/architecture.md), and [release checklist](docs/releasing.md).
|
|
70
|
+
|
|
71
|
+
## Contributing
|
|
72
|
+
|
|
73
|
+
Run `npm run fmt` to format with Oxfmt, and `npm run lint:fix` to apply safe Oxlint fixes. Run `npm run check` before proposing changes; it checks formatting, lint, TypeScript, and tests, and also runs in CI. Tests must remain offline and must not read real credentials. Use `npm run smoke:package` to check the packed extension in an isolated Pi installation. Public benchmark or performance claims require clarification of TypeSafe's terms first.
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
[MIT](LICENSE). Third-party models, services, and dependencies retain their own terms.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Routing before generation
|
|
2
|
+
|
|
3
|
+
Jev classifies task demand. A local policy chooses eligible generation targets. Neither classifier output nor task text can supply a provider URL, invent a model target, or bypass the configured chains.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
idle user input
|
|
7
|
+
→ current session doctor verification gate
|
|
8
|
+
→ bounded text projection
|
|
9
|
+
→ one explicitly selected classifier adapter
|
|
10
|
+
→ validated quick / standard / deep / uncertain answer
|
|
11
|
+
→ local confidence policy
|
|
12
|
+
→ ordered candidate eligibility checks
|
|
13
|
+
→ serialized Pi model selection
|
|
14
|
+
→ original input continues once
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`classify` normalizes three protocols. `chooseRoute` and `candidateChecks` are pure. `registerRouter` owns lifecycle and session-local state. This is not an LLM proxy and does not register a synthetic generation model.
|
|
18
|
+
|
|
19
|
+
## Lifecycle contract
|
|
20
|
+
|
|
21
|
+
The implementation targets Pi **0.85.1**. Its idle `input` hook runs before initial generation authentication and compaction. `before_agent_start` is too late for a router that must rescue a missing initial model/auth configuration. Selection happens once at the idle input boundary; streaming steering/follow-up and extension-generated input pass through without classification. Tool-loop selection is sticky by router policy, not by an immutable host guarantee.
|
|
22
|
+
|
|
23
|
+
The extension returns `handled` when preflight must stop a prompt. Throwing would be unsafe: Pi catches input-hook exceptions and can continue generation. No path uses `sendUserMessage`, resends the original input, or chooses a new model after a generation error. Native Pi retry/overflow recovery is not disabled or replaced. Failure guidance waits for `agent_settled`, after native recovery finishes.
|
|
24
|
+
|
|
25
|
+
Model selection requires a current successful doctor generation proof, then checks exact catalogue identity, configured auth presence, nonempty scoped-model restrictions, image support, and conservative context/output budgets. A setter returning false or throwing skips to the next candidate before generation. Failed doctor targets are skipped, even when first in a chain; remaining successful candidates keep their configured order. Catalogue eligibility alone does not prove remote credentials, quotas, availability, or task quality. A successful probe is only a health snapshot, not a guarantee of future generation. Context sizing uses Pi's `ctx.getContextUsage()` count, which incorporates provider-reported usage and Pi's trailing-message estimates. Unsent input uses Pi's exported `estimateTokens`; the router does not count serialized metadata or treat bytes as tokens. If Pi reports unknown usage immediately after compaction, the router skips only its context-size check and leaves compaction/accounting to Pi. If no usage API result is available, it sums Pi's message estimates. Counts are not exact candidate-specific tokenization; Pi's current accounting is the shared basis. Unknown post-input changes from templates, context hooks, tools, or other extensions cannot be predicted.
|
|
26
|
+
|
|
27
|
+
## Cancellation and concurrency
|
|
28
|
+
|
|
29
|
+
Each operation has an epoch and an AbortController. Classifier credentials and HTTP share a deadline. Late classification/credential results are ignored. Escape/Ctrl+C are intercepted during TUI preflight, and `/typesafe-router off` also invalidates the operation. Headless callers must use `off` during preflight; Pi's normal agent abort signal is not yet active there.
|
|
30
|
+
|
|
31
|
+
Pi's `setModel()` is asynchronous and has **no cancellation argument**. A timeout race cannot prevent it from later changing the model. The router therefore awaits the actual setter, never starts another selection in parallel, and consumes the cancelled original input afterward. Concurrent input is rejected, even if routing was turned off while authentication was pending. Model selection may finish after cancellation; users must verify it before resubmitting. There is no unsafe rollback to a stale model.
|
|
32
|
+
|
|
33
|
+
Session navigation is refused while preflight is active, cancelling the operation first. Shutdown/reload cancels and awaits its completion before allowing teardown. If a credential plugin never resolves its model-auth promise, selection and reload can remain blocked; restarting Pi is the recovery. This is an explicit limitation of the host setter, not something a timer can safely fix.
|
|
34
|
+
|
|
35
|
+
## Failure and recovery
|
|
36
|
+
|
|
37
|
+
Classification errors, timeouts, invalid responses, or missing keys use `defaultRoute`. Low/missing confidence and explicit uncertainty use `uncertainRoute`. Oversized, empty, and unexpanded slash input skip classification and use the conservative route. No eligible candidate consumes the prompt with an error instead of silently using a stale model. After the doctor gate passes, shadow mode records the proposed route but neither changes the model nor blocks generation for an empty candidate chain. Both on and shadow modes, including automatic input, are blocked without current doctor verification; off/manual Pi use remains available.
|
|
38
|
+
|
|
39
|
+
After generation fails, guidance points to Pi's `/model`, inspection of completed tool effects, and manual continuation when safe. The router does not select a post-generation fallback or replay a task. Manual model selection disables routing. Do not run multiple routers: Pi does not provide exclusive model-selection ownership between extensions.
|
|
40
|
+
|
|
41
|
+
## Diagnostics and configuration
|
|
42
|
+
|
|
43
|
+
`/typesafe-router doctor` serializes with preflight and model selection. It applies valid configuration while retaining the current session's on/off/shadow mode; it never enables an off session. Missing or invalid configuration disables routing. Every refresh invalidates old proofs before checking anything. Readiness requires a completed, successful doctor run: the classifier check must succeed and every quick/standard/deep route must contain at least one locally eligible target with a successful generation probe. Cancellation or incomplete work cannot publish partial readiness.
|
|
44
|
+
|
|
45
|
+
Doctor checks local eligibility, then runs the synthetic classifier request and one isolated synthetic generation request per distinct configured provider/model concurrently, deduplicated across routes. A shared progress counter counts completed checks (including failures), while the final report preserves configured route order. TUI progress updates one widget above the editor, independent of the footer and routing mode, and clears on completion, failure, or cancellation. Other interfaces receive textual progress notifications. Cancelling doctor aborts the whole batch; partial results cannot grant verification. Generation probes use Pi's `modelRegistry.complete` with the actual credential providers, including OAuth, custom headers, and configured endpoints. Credentials may refresh. Probes contain no actual conversation transcript and use `tools: []`, `maxTokens: 128` where supported, and `maxRetries: 0`. `generationProbeTimeoutMs` bounds each model probe (default 15000 ms), separately from the classifier's `timeoutMs`. These limits are not a strict monetary cap. Explicit invocation authorizes potentially billable diagnostics even while off or headless with `allowHeadless: false`. Doctor never calls the generation model setter, changes the selected model, or runs or replays the original task.
|
|
46
|
+
|
|
47
|
+
Generation proofs and readiness live only in memory, not session records or config. Restart/reload requires doctor again. The verification identity covers routes, credential references, backend configuration, and model metadata, so changes under the same provider/model IDs still invalidate it. Native auth status and provider references contribute to an in-memory hash; no secrets are persisted. A changed identity requires a fresh successful doctor run, not reuse of old proofs. Effective Pi provider object identity covers native registrations and applied `models.json` changes; a provider-registry refresh can conservatively require doctor again. Classifier-only Pi credential providers are included. Before enabling or routing, every route must still have an eligible verified candidate, including current scope and context limits.
|
|
48
|
+
|
|
49
|
+
Probe cancellation releases doctor promptly and checks the abort signal again after Pi resolves authentication, before invoking provider transport. A credential plugin that ignores cancellation may still finish authentication or refresh credentials later; its late result cannot start the probe transport or publish verification.
|
|
50
|
+
|
|
51
|
+
Reports reflect runtime, config path/application result, actual mode, current model, backend/model, credential source, local eligibility, classifier result/latency, generation probe outcomes, and readiness, with conditional next steps. Cancellation and incomplete work are explicit; shutdown suppresses stale output. Off/Escape cancels pending work but cannot release a pending model setter's lock early.
|
|
52
|
+
|
|
53
|
+
`status`, also the default subcommand, is read-only and makes no network calls. It reports applied configuration and on-disk differences (or inability to check), current activity/model/mode, and a historical last decision. It does not apply configuration or renew verification. Classifier success alone does not prove generation-provider health; successful generation probes do not guarantee later availability, quota, or task quality.
|
|
54
|
+
|
|
55
|
+
## Verification boundaries
|
|
56
|
+
|
|
57
|
+
Offline tests validate schema/policy, request serialization, normalized responses, cancellation, no replay, and real SDK extension loading. Packed-install smoke tests exercise Pi's actual extension loader using an isolated profile and installed production dependencies. Synthetic transports do not establish live service contracts or quality. In particular, Gateway confidence metadata needs a live account check; absent confidence stays conservative.
|
|
58
|
+
|
|
59
|
+
Before claiming savings or quality, evaluate privately on representative coding tasks with pinned versions and per-model success/cost/latency outcomes. Classifier confidence measures concentration, not downstream success probability. TypeSafe's public benchmarking restrictions require permission/clarification before publishing results.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Configure task routing
|
|
2
|
+
|
|
3
|
+
The router reads only `typesafe-router.json` under Pi's agent directory (normally `~/.pi/agent`). `PI_CODING_AGENT_DIR` changes that directory. It does not read project files or discover credentials in a repository.
|
|
4
|
+
|
|
5
|
+
## Map classes to model chains
|
|
6
|
+
|
|
7
|
+
Copy an [example](../examples/typesafe.json), or run `/typesafe-router setup typesafe` in Pi. Replace every example target with exact IDs from Pi's model catalogue. A provider ID cannot contain a slash; a model ID can.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"version": 1,
|
|
12
|
+
"mode": "off",
|
|
13
|
+
"allowHeadless": false,
|
|
14
|
+
"backend": {
|
|
15
|
+
"type": "typesafe",
|
|
16
|
+
"model": "jev-1.13.0",
|
|
17
|
+
"auth": { "source": "env", "variable": "TYPESAFE_API_KEY" }
|
|
18
|
+
},
|
|
19
|
+
"timeoutMs": 1500,
|
|
20
|
+
"generationProbeTimeoutMs": 15000,
|
|
21
|
+
"minConfidence": 0.8,
|
|
22
|
+
"maxContextChars": 12000,
|
|
23
|
+
"historyMessages": 4,
|
|
24
|
+
"outputReserveTokens": 8192,
|
|
25
|
+
"defaultRoute": "deep",
|
|
26
|
+
"uncertainRoute": "deep",
|
|
27
|
+
"routes": {
|
|
28
|
+
"quick": [{ "provider": "your-provider", "model": "your-fast-model" }],
|
|
29
|
+
"standard": [{ "provider": "your-provider", "model": "your-balanced-model" }],
|
|
30
|
+
"deep": [
|
|
31
|
+
{ "provider": "your-provider", "model": "your-strong-model" },
|
|
32
|
+
{ "provider": "another-provider", "model": "your-backup-model" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each chain contains one to eight distinct targets. Order is authoritative: targets without a current successful doctor generation proof, or failing local eligibility, are skipped; selection stops at the first successful `pi.setModel`. A failed first target does not prevent selection of a later successful one. Remaining successful candidates keep their configured order. Doctor deduplicates probes across chains by exact provider/model identity. There is no random sampling, cost lookup, or per-task remote health probe. `auto`, `smart-router`, and `typesafe-router` virtual providers are rejected.
|
|
39
|
+
|
|
40
|
+
Unknown fields, duplicate targets, invalid types, and out-of-range settings reject the entire file. Missing, unreadable, or invalid configuration disables routing. Repair the file and run `/typesafe-router doctor` to apply it; explicitly enable routing afterward if desired.
|
|
41
|
+
|
|
42
|
+
| Setting | Default | Accepted values |
|
|
43
|
+
| -------------------------- | ------- | -------------------------------------------------------------------------------- |
|
|
44
|
+
| `version` | `1` | `1` |
|
|
45
|
+
| `mode` | `off` | `off`, `shadow`, `auto` |
|
|
46
|
+
| `allowHeadless` | `false` | Boolean; explicitly permit automatic routing outside TUI |
|
|
47
|
+
| `timeoutMs` | `1500` | Integer, 100–30000; credentials plus classification |
|
|
48
|
+
| `generationProbeTimeoutMs` | `15000` | Integer, 100–60000; timeout for each model’s doctor generation probe |
|
|
49
|
+
| `minConfidence` | `0.8` | Number, 0–1 |
|
|
50
|
+
| `maxContextChars` | `12000` | Integer, 256–32000; combined request and history text |
|
|
51
|
+
| `historyMessages` | `4` | Integer, 0–20 |
|
|
52
|
+
| `outputReserveTokens` | `8192` | Integer, 256–131072; capped at candidate's maximum output |
|
|
53
|
+
| `defaultRoute` | `deep` | `quick`, `standard`, `deep`; classifier failure/unavailability |
|
|
54
|
+
| `uncertainRoute` | `deep` | Same values; uncertainty, absent/low confidence, oversized or unexpanded request |
|
|
55
|
+
|
|
56
|
+
`generationProbeTimeoutMs` is a top-level setting applied separately to each distinct model probe, not a total doctor deadline. Probes request `maxTokens: 128` where supported and `maxRetries: 0`; these are fixed probe options, not configuration fields. They do not establish a strict monetary cap.
|
|
57
|
+
|
|
58
|
+
Character limits use JavaScript string length. The current request is never truncated. If it cannot fit, classification is skipped and the conservative route is used. Only contiguous newest fitting conversation text is included. Set `historyMessages: 0` for request-only classification.
|
|
59
|
+
|
|
60
|
+
## Select one classification backend
|
|
61
|
+
|
|
62
|
+
### TypeSafe direct
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"type": "typesafe",
|
|
67
|
+
"model": "jev-1.13.0",
|
|
68
|
+
"auth": { "source": "env", "variable": "TYPESAFE_API_KEY" }
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Requests go to `https://api.typesafe.ai/v1/systemone`. The versioned model ID is the default; explicitly configured aliases may change behavior over time. No automatic retries occur.
|
|
73
|
+
|
|
74
|
+
### Cloudflare Workers AI
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"type": "cloudflare",
|
|
79
|
+
"accountId": "0123456789abcdef0123456789abcdef",
|
|
80
|
+
"model": "typesafe/jev",
|
|
81
|
+
"auth": { "source": "env", "variable": "CLOUDFLARE_API_TOKEN" }
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use your own 32-hex-character account ID and a token authorized for Workers AI. The fixed account-scoped REST endpoint is `https://api.cloudflare.com/client/v4/accounts/{accountId}/ai/run`. This is Workers AI evaluation, not arbitrary Cloudflare AI Gateway chat compatibility.
|
|
86
|
+
|
|
87
|
+
### Vercel AI Gateway
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"type": "vercel",
|
|
92
|
+
"model": "typesafe-ai/jev",
|
|
93
|
+
"zeroDataRetention": true,
|
|
94
|
+
"auth": { "source": "env", "variable": "AI_GATEWAY_API_KEY" }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This uses AI SDK `experimental_evaluate`, pinned to SDK `7.0.105`, and the Gateway evaluation model interface. No OpenAI-compatible chat endpoint is involved. `zeroDataRetention` defaults to true; the provider may reject unsupported retention options instead of silently weakening them.
|
|
99
|
+
|
|
100
|
+
Confidence is read from the per-question `providerMetadata.typesafe.confidence.task_class` map. Public documentation identifies the enclosing confidence metadata but does not demonstrate its full shape. Missing confidence remains missing and selects `uncertainRoute`; malformed confidence fails validation and selects `defaultRoute`. Run `/typesafe-router doctor` with your account before relying on this experimental integration. No live service parity is claimed.
|
|
101
|
+
|
|
102
|
+
### Optional Pi-managed credentials
|
|
103
|
+
|
|
104
|
+
All backends also accept `{"source":"pi","provider":"your-provider-id"}` as `auth`. This calls Pi 0.85.1's `getProviderAuth` at request time. It is opt-in, never an implicit fallback from a missing environment key. Only the resolved API key is forwarded; custom auth headers are not. A provider credential does not establish that the target evaluation endpoint accepts it. Verify audience, permissions, and backend compatibility yourself. Environment credentials are the recommended initial setup.
|
|
105
|
+
|
|
106
|
+
## Commands
|
|
107
|
+
|
|
108
|
+
| Command | Effect |
|
|
109
|
+
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
110
|
+
| `setup [typesafe\|cloudflare\|vercel]` | Interactive creation of an off-mode config; never overwrites |
|
|
111
|
+
| `doctor` | Apply config, test the classifier and distinct generation targets, and refresh session verification |
|
|
112
|
+
| `status` (default) | Read-only report of applied config, runtime mode, current model/activity, and disk differences; no network |
|
|
113
|
+
| `on` / `shadow` / `off` | Change session mode; on/shadow require successful doctor; off cancels preflight |
|
|
114
|
+
|
|
115
|
+
Prefix each with `/typesafe-router`. With no subcommand, the command shows status.
|
|
116
|
+
|
|
117
|
+
`doctor` reloads valid configuration while preserving the current session's on/off/shadow mode, rather than adopting the file's mode. It never enables an off session. Missing or invalid configuration disables routing. It never changes the selected generation model or runs or replays a user task.
|
|
118
|
+
|
|
119
|
+
Doctor automatically sends one synthetic classifier request and one isolated synthetic generation request per distinct configured provider/model, without confirmation. No actual conversation transcript is sent, and generation probes have `tools: []`. **These requests may incur charges**; there is no strict monetary cap. Explicit invocation authorizes them even with routing off or in print/RPC mode with `allowHeadless: false`. Automatic routing in those modes still requires `allowHeadless: true` and an enabled mode. Headless diagnostics go to stderr, not the assistant's output.
|
|
120
|
+
|
|
121
|
+
Generation probes run through Pi's `modelRegistry.complete`, using the actual configured credential providers, including OAuth, custom headers, and custom endpoints. Credentials may refresh during diagnostics. This differs from the classifier's optional API-key-only Pi credential reuse described above.
|
|
122
|
+
|
|
123
|
+
Readiness requires doctor to complete successfully, including the classifier check, and at least one locally eligible, successfully probed target in each of `quick`, `standard`, and `deep`. Not every target must pass. Failed targets are skipped in subsequent preflight, preserving the configured order of remaining successful candidates. Until ready, `on`, `shadow`, and automatic input are blocked; `off` and manual Pi use remain available. A configured startup mode does not bypass verification.
|
|
124
|
+
|
|
125
|
+
Verification is session-only and never persisted. Restart/reload requires doctor again. Changes to routes, credential references, backend configuration, or model metadata invalidate it even if provider/model IDs stay the same. Native auth status and provider references contribute to an in-memory identity hash; no secrets are persisted. Each doctor refresh invalidates previous proofs. Cancellation or incomplete checks never partially unlock routing; run doctor again to recover.
|
|
126
|
+
|
|
127
|
+
Doctor reports runtime, config path/application result, actual session mode, current generation model, backend/model, credential source, local eligibility, classifier result/latency, generation probe outcomes, and readiness. Reports give conditional next steps for the actual state. Local eligibility alone does not prove generation-provider health; a successful probe is a health snapshot, not a guarantee of later availability, quota, or task quality.
|
|
128
|
+
|
|
129
|
+
Status reports the applied configuration, not merely the file on disk. It identifies disk differences or inability to check the file, and labels the last routing decision as historical. It neither applies changes nor tests services. Use doctor to apply and test changes.
|
|
130
|
+
|
|
131
|
+
Escape/Ctrl+C in the TUI or `off` cancels preflight. While Pi's non-cancellable model setter is pending, the selection lock remains held; verify the selected model before resubmitting. Doctor is serialized with preflight/model selection.
|
|
132
|
+
|
|
133
|
+
After generation fails, use `/model`, inspect completed tool effects, and manually continue when safe. The router performs no post-generation fallback and never replays a task.
|
|
134
|
+
|
|
135
|
+
Manual `/model` selection turns routing off. Ordinary tool loops and queued steering/follow-up messages do not reroute. Session-tree navigation turns routing off. Active preflight blocks concurrent submissions and session navigation; after cancellation settles, repeat the desired submission/navigation yourself.
|
package/docs/privacy.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# What leaves your machine
|
|
2
|
+
|
|
3
|
+
Enabling `auto` or `shadow` sends the current request and bounded recent user/assistant text to the configured classification backend. That text can contain private code, credentials, or personal data. **This extension is not a secret scanner or redaction system.** Do not enable it for material you cannot share with that backend.
|
|
4
|
+
|
|
5
|
+
The default projection excludes system prompts, raw tool calls/results, reasoning blocks, image bytes, attachments, compaction summaries, and arbitrary files. If an assistant quotes a tool result or secret in ordinary text, that text can still be included. Slash-command/template input is not classified before expansion; the conservative route is used instead. There is no background classification while the router is off.
|
|
6
|
+
|
|
7
|
+
## Explicit diagnostics
|
|
8
|
+
|
|
9
|
+
`/typesafe-router doctor` applies valid configuration, checks local eligibility, and automatically sends one synthetic classifier request plus one isolated synthetic generation request to each distinct configured provider/model. It sends no actual conversation transcript, uses no tools (`tools: []`), asks for no confirmation, and **may incur charges**. Generation probes request `maxTokens: 128` where supported and `maxRetries: 0`, with a per-model `generationProbeTimeoutMs` deadline (default 15000 ms). These bounds are not a strict monetary cap.
|
|
10
|
+
|
|
11
|
+
Invoking doctor explicitly authorizes these requests even while routing is off or headless with `allowHeadless: false`; that setting still gates automatic routing. Doctor preserves the current session mode and never enables an off session. Missing or invalid configuration disables routing. Doctor never changes the selected model or runs or replays a user task. Isolated synthetic generation probes are not retries of the actual task.
|
|
12
|
+
|
|
13
|
+
Doctor must complete successfully, including the classifier check, and establish a locally eligible, successful generation candidate for each route before `on`, `shadow`, or automatic input is allowed. Off/manual Pi use remains available. Verification is session-only: restart/reload and identity changes require doctor again. Refresh discards old proofs; cancellation never partially unlocks routing.
|
|
14
|
+
|
|
15
|
+
`/typesafe-router status` (also the default) is read-only and makes no network calls. It reports applied configuration, runtime mode/current model, and on-disk differences. Local eligibility is not remote generation verification, and successful probes are only health snapshots, not future guarantees.
|
|
16
|
+
|
|
17
|
+
## Recipients and retention
|
|
18
|
+
|
|
19
|
+
- Direct: TypeSafe's API.
|
|
20
|
+
- Cloudflare: your account's Workers AI endpoint and its upstream service arrangement.
|
|
21
|
+
- Vercel: AI Gateway and its upstream service arrangement. The request explicitly enables `zeroDataRetention` by default.
|
|
22
|
+
|
|
23
|
+
The router never silently changes classification backends, follows HTTP redirects, or retries classification. A failed classifier uses a local configured generation route instead. Generation content goes to the model provider selected in Pi, under that provider's normal settings. Doctor also sends synthetic probe content to every distinct configured generation target through Pi's `modelRegistry.complete`, including custom endpoints and their normal provider arrangements.
|
|
24
|
+
|
|
25
|
+
No-training promises do not establish zero data retention. TypeSafe's enterprise ZDR offering, gateway logging, account controls, and contract terms need separate review. The extension cannot verify a service's retention behavior. Review [TypeSafe's terms](https://typesafe.ai/legal/mca), [Cloudflare's model documentation](https://developers.cloudflare.com/ai/models/typesafe/jev/), and [Vercel's evaluation documentation](https://vercel.com/docs/ai-gateway/modalities/evaluation) before sharing sensitive content.
|
|
26
|
+
|
|
27
|
+
## Local data and credentials
|
|
28
|
+
|
|
29
|
+
Configuration is global and contains credential references, not keys. Setup creates it with mode `0600` where supported. Existing files and symlinks are never overwritten. Credentials are resolved at request time and are not placed in session records. For classification, Pi-managed credential reuse is explicit and forwards only an API key, not provider-specific headers. Generation probes instead use Pi's actual credential providers, including OAuth and custom headers. Credentials may refresh during doctor, subject to Pi's normal credential storage behavior.
|
|
30
|
+
|
|
31
|
+
Generation proofs and readiness are held only in memory; the router never persists them. The verification identity covers routes, credential references, backend configuration, and model metadata, including changes under unchanged provider/model IDs. Native auth status and provider references contribute to an in-memory hash. No secrets are persisted by the router for verification.
|
|
32
|
+
|
|
33
|
+
Session custom entries contain route/model IDs, classifier labels/probabilities/confidence, normalized usage, elapsed time, safe failure reasons, and skipped candidates. They do not contain request text, raw responses, HTTP error bodies, or API keys. Pi itself already records ordinary conversation content independently of this extension. There is no extension telemetry or external analytics.
|
|
34
|
+
|
|
35
|
+
Diagnostics report configuration errors without printing Zod values or filesystem error bodies. Model IDs and config paths are shown locally; do not use secrets as identifiers. Gateway/provider warnings are suppressed rather than printed verbatim.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Release checklist
|
|
2
|
+
|
|
3
|
+
Nothing in this repository publishes automatically. The package name was available during research, but is not reserved. There is no configured remote or maintainer identity yet.
|
|
4
|
+
|
|
5
|
+
## Before the first release
|
|
6
|
+
|
|
7
|
+
1. Confirm ownership of the npm name and choose the public Git repository. Add accurate `repository`, `homepage`, and `bugs` metadata to `package.json`.
|
|
8
|
+
2. Confirm the MIT license choice and maintainer identity. Review the public artifact list; research, local paths, tests, and development scripts are not shipped.
|
|
9
|
+
3. Run `npm ci`, `npm run check`, and `npm run smoke:package` on the supported Node/Pi versions. Inspect `npm pack --dry-run`.
|
|
10
|
+
4. With explicit approval for network charges, run `/typesafe-router doctor` for each backend and configured generation target set with least-privilege credentials. Doctor preserves session mode and sends a synthetic classifier request plus one isolated generation probe per distinct provider/model, without confirmation, actual conversation transcript, or tools. It may incur charges even while off or headless with `allowHeadless: false`; token/deadline limits are not a strict monetary cap. Confirm response shape, confidence extraction, permissions, and cancellation. Do not capture keys or raw private state in fixtures. Check the following:
|
|
11
|
+
- Probes use Pi's `modelRegistry.complete` and actual credential providers, including OAuth refresh, custom headers, and custom endpoints. Options are `tools: []`, `maxTokens: 128` where supported, and `maxRetries: 0`. The top-level `generationProbeTimeoutMs` defaults to 15000 and accepts only integers from 100 through 60000, applied per model.
|
|
12
|
+
- Readiness requires completed doctor success, including the classifier, and at least one locally eligible, successfully probed candidate in every quick/standard/deep route. Failed targets, including the first configured target, are skipped during later preflight; successful targets retain their configured order.
|
|
13
|
+
- `on`, `shadow`, and automatic input are blocked without readiness; off/manual Pi use remains available. Doctor does not change the selected model or replay a user task.
|
|
14
|
+
- Proofs are session-only and never persisted. Restart/reload requires doctor again. Route, credential-reference, backend, and model-metadata changes invalidate verification even under unchanged provider/model IDs. Native auth status/provider references contribute to an in-memory hash without persisting secrets.
|
|
15
|
+
- Refresh invalidates old proofs, and cancellation or incomplete work never partially unlocks routing. Treat successful probes as health snapshots, not guarantees of future generation success.
|
|
16
|
+
5. Privately evaluate representative tasks. Seek clarification of TypeSafe MCA §2.3(f) before public benchmarks, performance numbers, or comparative claims.
|
|
17
|
+
6. Review privacy disclosures and experimental Vercel limitations. Choose the package version and record changes.
|
|
18
|
+
7. Configure npm trusted publishing for the chosen repository/workflow and an approved release environment. Use OIDC provenance rather than storing a long-lived npm token. This step depends on the final repository identity and is intentionally not preconfigured.
|
|
19
|
+
8. Only after release approval, publish the reviewed artifact with public access and provenance. Verify installation into a clean Pi profile.
|
|
20
|
+
9. Verify discovery in Pi's package gallery. The manifest already has the `pi-package` keyword and `pi.extensions` entry; gallery indexing is external and is not guaranteed by a local test.
|
|
21
|
+
|
|
22
|
+
The local smoke script installs the packed tarball and Pi into a temporary directory, disables resource discovery and network during extension loading, and checks the registered command through the actual loader. It does not alter the user's Pi settings or installed extensions.
|
|
23
|
+
|
|
24
|
+
## Ongoing releases
|
|
25
|
+
|
|
26
|
+
Keep host compatibility explicit. The tested host is Pi 0.85.1; wildcard optional peers avoid bundling a competing runtime but do not imply every Pi version is compatible. Re-characterize lifecycle/auth changes on a new host version. AI SDK evaluation is experimental and pinned; review serialization/metadata tests before upgrading it.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"mode": "off",
|
|
4
|
+
"allowHeadless": false,
|
|
5
|
+
"backend": {
|
|
6
|
+
"type": "cloudflare",
|
|
7
|
+
"model": "typesafe/jev",
|
|
8
|
+
"accountId": "00000000000000000000000000000000",
|
|
9
|
+
"auth": {
|
|
10
|
+
"source": "env",
|
|
11
|
+
"variable": "CLOUDFLARE_API_TOKEN"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"timeoutMs": 1500,
|
|
15
|
+
"minConfidence": 0.8,
|
|
16
|
+
"maxContextChars": 12000,
|
|
17
|
+
"historyMessages": 4,
|
|
18
|
+
"outputReserveTokens": 8192,
|
|
19
|
+
"routes": {
|
|
20
|
+
"quick": [
|
|
21
|
+
{
|
|
22
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
23
|
+
"model": "REPLACE_WITH_QUICK_MODEL_ID"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"standard": [
|
|
27
|
+
{
|
|
28
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
29
|
+
"model": "REPLACE_WITH_STANDARD_MODEL_ID"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"deep": [
|
|
33
|
+
{
|
|
34
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
35
|
+
"model": "REPLACE_WITH_DEEP_MODEL_ID"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"defaultRoute": "deep",
|
|
40
|
+
"uncertainRoute": "deep"
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"mode": "off",
|
|
4
|
+
"allowHeadless": false,
|
|
5
|
+
"backend": {
|
|
6
|
+
"type": "typesafe",
|
|
7
|
+
"model": "jev-1.13.0",
|
|
8
|
+
"auth": {
|
|
9
|
+
"source": "env",
|
|
10
|
+
"variable": "TYPESAFE_API_KEY"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"timeoutMs": 1500,
|
|
14
|
+
"minConfidence": 0.8,
|
|
15
|
+
"maxContextChars": 12000,
|
|
16
|
+
"historyMessages": 4,
|
|
17
|
+
"outputReserveTokens": 8192,
|
|
18
|
+
"routes": {
|
|
19
|
+
"quick": [
|
|
20
|
+
{
|
|
21
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
22
|
+
"model": "REPLACE_WITH_QUICK_MODEL_ID"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"standard": [
|
|
26
|
+
{
|
|
27
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
28
|
+
"model": "REPLACE_WITH_STANDARD_MODEL_ID"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"deep": [
|
|
32
|
+
{
|
|
33
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
34
|
+
"model": "REPLACE_WITH_DEEP_MODEL_ID"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"defaultRoute": "deep",
|
|
39
|
+
"uncertainRoute": "deep"
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"mode": "off",
|
|
4
|
+
"allowHeadless": false,
|
|
5
|
+
"backend": {
|
|
6
|
+
"type": "vercel",
|
|
7
|
+
"model": "typesafe-ai/jev",
|
|
8
|
+
"auth": {
|
|
9
|
+
"source": "env",
|
|
10
|
+
"variable": "AI_GATEWAY_API_KEY"
|
|
11
|
+
},
|
|
12
|
+
"zeroDataRetention": true
|
|
13
|
+
},
|
|
14
|
+
"timeoutMs": 1500,
|
|
15
|
+
"minConfidence": 0.8,
|
|
16
|
+
"maxContextChars": 12000,
|
|
17
|
+
"historyMessages": 4,
|
|
18
|
+
"outputReserveTokens": 8192,
|
|
19
|
+
"routes": {
|
|
20
|
+
"quick": [
|
|
21
|
+
{
|
|
22
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
23
|
+
"model": "REPLACE_WITH_QUICK_MODEL_ID"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"standard": [
|
|
27
|
+
{
|
|
28
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
29
|
+
"model": "REPLACE_WITH_STANDARD_MODEL_ID"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"deep": [
|
|
33
|
+
{
|
|
34
|
+
"provider": "REPLACE_WITH_PI_PROVIDER",
|
|
35
|
+
"model": "REPLACE_WITH_DEEP_MODEL_ID"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"defaultRoute": "deep",
|
|
40
|
+
"uncertainRoute": "deep"
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-typesafe-router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opt-in Jev task routing for Pi with TypeSafe, Cloudflare and Vercel backends and ordered preflight model fallbacks",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jev",
|
|
7
|
+
"model-routing",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"pi-package",
|
|
10
|
+
"typesafe"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"examples",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"docs/privacy.md",
|
|
19
|
+
"docs/configuration.md",
|
|
20
|
+
"docs/architecture.md",
|
|
21
|
+
"docs/releasing.md"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "tsx --test tests/*.test.ts",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"fmt": "oxfmt --write .",
|
|
28
|
+
"fmt:check": "oxfmt --check .",
|
|
29
|
+
"lint": "oxlint --deny-warnings src tests scripts",
|
|
30
|
+
"lint:fix": "oxlint --fix --deny-warnings src tests scripts",
|
|
31
|
+
"check": "npm run fmt:check && npm run lint && npm run typecheck && npm test",
|
|
32
|
+
"smoke:package": "node scripts/package-smoke.mjs",
|
|
33
|
+
"prepack": "npm run typecheck"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"ai": "7.0.105",
|
|
37
|
+
"zod": "^4.4.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
41
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
42
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
43
|
+
"@oxlint/plugins": "1.83.0",
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"oxfmt": "0.68.0",
|
|
46
|
+
"oxlint": "1.83.0",
|
|
47
|
+
"tsx": "^4.20.0",
|
|
48
|
+
"typescript": "^5.8.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@earendil-works/pi-ai": "*",
|
|
52
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
53
|
+
"@earendil-works/pi-tui": "*"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@earendil-works/pi-coding-agent": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"@earendil-works/pi-ai": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"@earendil-works/pi-tui": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22.19.0"
|
|
68
|
+
},
|
|
69
|
+
"pi": {
|
|
70
|
+
"extensions": [
|
|
71
|
+
"./src/index.ts"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|