pi-braid-providers 1.0.5 → 1.0.7

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 CHANGED
@@ -1,231 +1,94 @@
1
1
  # pi-braid-providers
2
2
 
3
- Additional LLM providers for `pi-coding-agent`, built on a generalized **API key ring** for multi-key rotation and self-healing.
4
-
5
- The `providers` workspace registers providers into pi at session start. The key-ring machinery that both the agent loop and direct extension LLM calls use ships here; the provider itself is the **`agent-zero`** setup: the multi-vendor gateway that proxies Venice's models, configured from a catalog file resolved at runtime.
6
-
7
- > **A fresh install registers no provider.** The `agent-zero` model catalog
8
- > (Venice's models) and gateway URL are read from a userland catalog file
9
- > resolved at runtime (see `venice-catalog.ts`) that this package does not
10
- > include. Without it the extension loads with the key-ring machinery active
11
- > and zero registered providers; a synthetic route can be registered through
12
- > `registerRoute` (`ring-lifecycle.ts`), the seam the test suite uses for its
13
- > multi-route coverage.
14
-
15
- ## The key ring
16
-
17
- Each provider gets its own key ring with **exponential backoff**:
18
-
19
- - **Multi-key rotation.** Keys load from `${PREFIX}_P0` … `${PREFIX}_P9` in the
20
- environment, falling back to the bare `PREFIX` var when no numbered keys
21
- exist. On a quota/auth/403 error the active key is exhausted and the next
22
- request serves the next healthy key.
23
- - **Persisted state.** Exhausted-key state persists to
24
- `~/.pi/agent/.key-ring-state-<provider>.json`, so restarts skip known-dead
25
- keys instead of retrying them and hitting the same 403. The file lives in
26
- pi's agent directory (`state-paths.ts` owns the resolution); if no absolute
27
- home can be resolved, the ring refuses to persist and says so rather than
28
- writing a path relative to the cwd.
29
- - **Self-healing turn retry.** When a turn fails and a fresh key remains, an
30
- explicit marker is appended so pi-core re-runs the turn on the next key
31
- automatically. Fatal account-level errors (quota exhausted, billing, balance)
32
- are never masqueraded as retryable.
33
- - **Direct-call feedback.** Direct extension LLM calls (an extension's own
34
- `completeSimple`/`streamSimple` use)
35
- feed 429/403 back into the ring too, so no dead key is served on those paths.
36
- - **Diagnostics.** Rotation/exhaustion events route through a throttled notify
37
- sink (not `console.*`); a loud _all-keys-exhausted_ warning fires when every
38
- key is in backoff.
39
-
40
- ## Installation
3
+ Key rotation, provider failover, and route health for the [pi](https://github.com/earendil-works/pi)
4
+ coding agent: several keys per provider, a dead key retired instead of retried, and a failover to a
5
+ healthier route when a provider's ring runs dry.
6
+
7
+ ## Install
41
8
 
42
9
  ```bash
43
10
  pi install npm:pi-braid-providers
44
11
  ```
45
12
 
46
- Or register the workspace in `~/.pi/agent/settings.json`:
47
-
48
- ```json
49
- {
50
- "packages": ["file:~/pi-braid/packages/providers"]
51
- }
52
- ```
13
+ Then name your routes (see Configuration). Until a route and its key are configured, the extension
14
+ loads the machinery and registers no provider: inert, not broken.
53
15
 
54
- ## What this does not do
16
+ ## What you get
55
17
 
56
- - It does not ship a provider catalog. The `agent-zero` model list and gateway URL are read from a
57
- catalog file resolved at runtime; without it the extension loads the key-ring machinery and
58
- registers zero providers.
59
- - It does not rotate to a route you did not configure - a route is recognized only by the model ids
60
- it declares.
61
- - It does not retry a fatal account error (quota exhausted, billing, balance) as if it were transient.
18
+ | Surface | What it gives you |
19
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | Multi-key rotation | Numbered keys per provider, falling back to the bare variable. On a quota, auth or 403 error the active key is retired and the next request uses the next healthy one |
21
+ | Persisted state | Key-ring state on disk, so a restart skips keys already known dead instead of retrying the same 403 |
22
+ | Turn retry | When a turn fails and a fresh key remains, the next key serves the retry; fatal account errors are never disguised as retryable |
23
+ | Failover | An ordered route table, walked one rotation per episode, with one notice to the owner and a bounded fail-back probe that returns when the dead route recovers |
24
+ | Route health | `ring_health` reports each ring and each route's last probe: read-only, key hashes only, never writes |
25
+ | Request guard | `braid.context.presendGuard` flushes oversized strings to disk above 90% of a known window, so a large request shrinks instead of being rejected |
62
26
 
63
- ## Activation preconditions
27
+ ## Configuration
64
28
 
65
- - `AGENT_ZERO_API_KEY` (or the numbered `AGENT_ZERO_API_KEY_P0`-`P9` rotation keys) set.
66
- - A userland catalog file for the `agent-zero` route; otherwise zero providers register and only a
67
- synthetic route (via `registerRoute`) can serve traffic.
29
+ ### Routes
68
30
 
69
- ## Environment variables
31
+ `braid.providers.routes` is an ordered array; array order is rotation order. Each entry names the
32
+ provider id pi will expose, the environment variable prefix its keys load from, and optionally the
33
+ model ids it serves and a non-billable status probe:
70
34
 
71
- ```bash
72
- AGENT_ZERO_API_KEY=<your-agent-zero-gateway-key>
73
- # Optional: multiple gateway keys for rotation
74
- AGENT_ZERO_API_KEY_P0=<key-1>
75
- AGENT_ZERO_API_KEY_P1=<key-2>
35
+ ```json
36
+ {
37
+ "braid": {
38
+ "providers": {
39
+ "routes": [
40
+ {
41
+ "provider": "venice-direct",
42
+ "keys": "VENICE_API_KEY",
43
+ "probe": {
44
+ "url": "https://api.venice.ai/api/v1/api_keys/rate_limits",
45
+ "balanceFloorUsd": 1
46
+ }
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ }
76
52
  ```
77
53
 
78
- Env vars are resolved at request time by the key ring (`auth.apiKey.resolve`).
79
- `warnBelowKeyCount` (default 4) surfaces keys-remaining on each rotation so
80
- you know when to top up.
81
-
82
- ## The two routes
83
-
84
- When the userland catalog exists, two providers register:
54
+ A route is recognized by the model ids it declares, so a route you did not configure is never
55
+ routed to. With no `routes` key, the two routes this build carries are used in order: the gateway
56
+ route, whose model catalog comes from a userland file that this package does not ship, and
57
+ `venice-direct`, which serves Venice's own API. Without that catalog the gateway route does not
58
+ register, and only routes you configure serve traffic. A route's `probe` declares a status
59
+ endpoint that is checked with the route's own key, at registration and on a bounded interval
60
+ afterwards; the check is never a billable call, and its result steers routing only while it is
61
+ fresher than `braid.providers.probeFreshnessMs`.
85
62
 
86
- | Provider id | Serves | Via | Key prefix |
87
- | --------------- | ------------------ | --------------------------------------------------- | -------------------- |
88
- | `agent-zero` | Venice's models | agent-zero gateway (`https://llm.agent-zero.ai/v1`) | `AGENT_ZERO_API_KEY` |
89
- | `venice-direct` | The same model ids | Venice's own API (`https://api.venice.ai/api/v1`) | `VENICE_API_KEY` |
63
+ ### Settings
90
64
 
91
- `agent-zero` is the primary. On any registered route's key-ring death (a quota/auth/403 error that leaves its ring with zero usable keys), the **rotation engine** (ring-lifecycle.ts) walks the ordered route table and engages the first route that is not the dead route, has a usable key, and can serve the dead turn's model id - directly (a shared id) or through its `models` map. The order is `braid.providers.routes` (`[{ provider, keys, modelIds?, models?, venicePayload? }]`), array order being the rotation order; with no `routes` key the default pair is `agent-zero` then `venice-direct`, so an `agent-zero` death still falls over to Venice's own API. The table is the only attribution source: a route is recognized by the model ids it declares (`modelIds` plus the provider ids its `models` map resolves to) and takes the Venice payload tweak only when it sets `venicePayload` - off by default for a configured route, on for the default pair. A route the owner did not configure is neither routed to nor shaped. Exactly one rotation per episode: the engage swaps the session model for the retrying turn and emits one owner-visible whisper. A bounded fail-back probe (`FAILBACK_PROBE_INTERVAL_MS`, 10 min) re-checks the dead route at most once per interval at `turn_end` boundaries; when it recovers the session fails back automatically (one fail-back whisper) and the engage re-arms. State is in-memory per extension load; `session_start` resets the engine.
65
+ | Key | Default | Effect |
66
+ | ------------------------------------ | --------------------- | --------------------------------------------------------------------------------- |
67
+ | `braid.providers.routes` | the pair above | Ordered route table; a configured array replaces the default pair entirely |
68
+ | `braid.providers.probeFreshnessMs` | `900000` (15 minutes) | How long a recorded probe or death may steer routing; `0` disables probe steering |
69
+ | `braid.context.presendGuard.enabled` | `true` | The 90% request guard described above |
92
70
 
93
- ## Health-routed entries (`auto`)
71
+ ### Environment
94
72
 
95
- A route can carry a virtual entry selectable as `<provider>/auto`: one model
96
- that answers each request with the first healthy route in the configured order,
97
- so a per-spawn route decision becomes a per-request one. The auto route spans
98
- several providers, so its entry lives under this package's own `braid`
99
- namespace: `braid/auto` exists whenever this build carries the agent-zero route;
100
- `opencode-go/auto` whenever the owner's route table names that provider -
101
- `opencode-go` sits ahead of the gateway in the live table, so a session that
102
- starts there leaves by the health walk instead of by hand. The entry declares
103
- no context window, max tokens or input types (pi reads those from the physical
104
- model that answered) and never dispatches its own id: a request with no
105
- previous physical model resolves its base id from the route table, or from the
106
- configured `defaultModel`, and the thinking level is resolved against the
107
- target model's own map. A provider that already ships a physical `auto` skips
108
- its own entry with one diagnostic, so a virtual entry can never shadow a real
109
- model.
73
+ `AGENT_ZERO_API_KEY` for the gateway route, `VENICE_API_KEY` for the direct route; numbered
74
+ suffixes (`_P0` through `_P9`) add rotation keys, and the bare variable is used when no numbered
75
+ key exists.
110
76
 
111
- ## Route probes and cross-session state
77
+ ## Limits
112
78
 
113
- A route may declare a non-billable status capability in the ordered route table:
79
+ - It is not a proxy or a gateway you can point other tools at: it plugs into pi's model registry.
80
+ - A route you did not configure is never routed to.
81
+ - Fatal account errors (quota, billing, balance) stop the turn rather than rotate.
82
+ - Ring and route state live directly under `~/.pi/agent/`, not under `braid/`:
83
+ `.key-ring-state-<provider>.json` and `.provider-route-state.json`. Key material is never
84
+ written, only key hashes.
114
85
 
115
- ```json
116
- {
117
- "provider": "venice-direct",
118
- "keys": "VENICE_API_KEY",
119
- "probe": {
120
- "url": "https://api.venice.ai/api/v1/api_keys/rate_limits",
121
- "balanceFloorUsd": 1
122
- }
123
- }
124
- ```
86
+ ## For maintainers
125
87
 
126
- When present, one probe runs at extension registration - a plain GET at the
127
- declared URL with the route's own key as a bearer token, never an inference
128
- call - and the outcome is recorded per route in
129
- `~/.pi/agent/.provider-route-state.json`: the last probe, the last recorded
130
- death (its key identity as a hash, never the key), the fail-back target and the
131
- warmed model ids. A later session reads that file instead of re-learning it.
132
- The probe never throws, never rotates a key and never starts a billable call;
133
- the write is lock- and CAS-guarded exactly like the key-ring state, because
134
- every pi process on the host shares the file.
135
-
136
- A recorded outcome carries the endpoint's own remaining figure as
137
- `remainingUsd` (Venice's `balances.USD`, OpenRouter's `limit_remaining`), so no
138
- consumer has to derive remaining by subtracting a monthly figure from a cap. A
139
- route whose status document reports a spendable USD balance is classified
140
- against `probe.balanceFloorUsd` (default `1`, owner-configurable per route):
141
- above the floor is `ok`, at or below it but above zero is `degraded`, and zero
142
- or below is `unavailable`. A route whose status endpoint answers 200 while the
143
- account has no spendable balance therefore no longer reads as healthy.
144
-
145
- Two rules keep the layers honest:
146
-
147
- - **The runner is provider-agnostic.** Which provider ids exist and what their
148
- status responses look like lives in ONE adapter table in `probe.ts`. A
149
- provider absent from it is reported unavailable rather than guessed at.
150
- - **The state store interprets nothing.** It writes down an opaque record per
151
- route and reads it back; the probe vocabulary and the death classes belong to
152
- the layers that produce them.
153
-
154
- A route without a `probe` descriptor is never probed. Absent probes, resolution
155
- and rotation are unchanged.
156
-
157
- ## Ring and route health
158
-
159
- `ring-health.ts` is the read side of those two files: it summarizes them for a
160
- human or a consumer script without writing, locking or interpreting anything, so
161
- "which keys are in backoff, and what did each route last look like" needs no
162
- knowledge of either store's write protocol.
163
-
164
- - `ringHealth(providerId, { stateDir?, file? })` reads one ring file
165
- (`~/.pi/agent/.key-ring-state-<provider>.json`, or an explicit path).
166
- `{ provider, present: false }` means no file, or one this reader cannot
167
- trust; otherwise it carries `rev`, `savedAt`, `activeIndex` and one entry per
168
- key: `{ exhausted, exhaustedAt, retryCount, exhaustedBy, keyHash }`.
169
- - `routeHealth({ stateDir?, providers?, routeState? })` reports every route it
170
- can see - the ids the caller names, the routes the route store already
171
- carries, and the providers with a ring file - as
172
- `{ provider, ring, lastProbe, deathClass, deathAt, failback, warmModels }`.
173
- Facts the route store has not recorded come back as `null`, `0` or `[]`.
174
-
175
- Four rules hold the reader to its layer:
176
-
177
- - **Read-only.** No write, no lock, no migration. The stores own the one write
178
- protocol; a reader that could write would be a second one.
179
- - **Hash-only.** The ring file holds `hash`, never the key, and the reader
180
- copies named fields instead of forwarding the document - so a value the ring
181
- never wrote cannot leak through here.
182
- - **Provider-blind.** Every provider id is an argument or is discovered from
183
- the files; none is hardcoded, so a configured route table needs no code
184
- change here.
185
- - **Tolerant, never throwing.** An absent file is the normal empty state and
186
- stays silent; a malformed, partial or wrong-version file is the same
187
- `present: false` plus exactly one diagnostic.
188
-
189
- The reader takes its file paths from its caller or derives them from the
190
- provider id; it writes no state file of its own and reads the route document
191
- through `route-state.ts`'s reader. Nothing in `packages/core` imports it: an
192
- external consumer imports the package, never the other way round.
193
-
194
- `formatRingHealth(routes, freshness)` and `summarizeRouteHealth(routes, freshness)`
195
- turn a report into the `ring_health` tool's text and its `details` payload.
196
- Both take the death window the caller resolved
197
- (`braid.providers.probeFreshnessMs`, the same value the router's death gate is
198
- built with) and mark a recorded death `(expired)` when that gate would no
199
- longer act on it: an age at or past the window, a death with no recorded time,
200
- or any death while the window is switched off. The age rule is the gate's own
201
- predicate (`deathInForce` in `virtual-router.ts`), so the surface and the
202
- router cannot disagree about which deaths act.
203
-
204
- ## Disabling the provider
205
-
206
- The registration lives in `index.ts` behind the venice-catalog seam: remove
207
- (or chmod) the userland catalog file and no provider is registered. All
208
- ring-lifecycle logic stays in `ring-lifecycle.ts`, unit-testable without
209
- loading pi-ai.
210
-
211
- ## Package layout
88
+ The route table internals, the probe adapter table, the state store protocol and the failure
89
+ classes are in [AGENTS.md](https://github.com/kifirkin/pi-braid/blob/main/packages/providers/AGENTS.md),
90
+ which the tarball does not carry.
212
91
 
213
- ```
214
- providers/
215
- ├── index.ts # registers the configured routes + wires key-ring/rotation events
216
- ├── venice-catalog.ts # userland catalog seam (the models file, resolved at runtime)
217
- ├── venice-direct.ts # the venice-direct route module: constants, key ring, model configs
218
- ├── key-ring.ts # multi-key rotation + backoff + persistence
219
- ├── ring-lifecycle.ts # per-request ring attribution, exhaust/self-heal, route-order rotation engine
220
- ├── probe.ts # provider-agnostic probe runner + the adapter table
221
- ├── route-state.ts # cross-session route state, lock + CAS writes
222
- ├── ring-health.ts # read-only health surface over the key-ring + route state files
223
- ├── log.ts # notify-sink routing (no console.*)
224
- └── test/ # fixture-driven behavior pins
225
- ```
92
+ ## License
226
93
 
227
- > The registered id was `venice`, then `a0`, now `agent-zero` - each rename
228
- > follows the gateway the provider serves models through. `venice-direct`, the
229
- > `VENICE_API_KEY` family and `venice-models.json` are unchanged; the gateway's
230
- > own credential namespace moved from `A0_VENICE_API_KEY` to `AGENT_ZERO_API_KEY`
231
- > with the last rename.
94
+ [MIT](LICENSE)
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "pi-braid-providers",
3
+ "version": "1.0.7",
4
+ "sha": "3a3de965131cd125b58a807b064a43d37b7f466f",
5
+ "shaShort": "3a3de965",
6
+ "branch": "detached",
7
+ "dirty": false,
8
+ "packedAt": "2026-11-11T22:45:21.903Z"
9
+ }
package/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { createProvider } from "@earendil-works/pi-ai";
3
- // `Type` ships with pi-ai's own typebox re-export, so the tool schema adds no
4
- // dependency to a package that resolves only @earendil-works/* from npm.
5
- import { Type } from "@earendil-works/pi-ai";
3
+ // pi-ai re-exported typebox's `Type` until 1.5.0 and no longer does, so the
4
+ // builder comes from the package that owns it, at the peer floor the sibling
5
+ // packages declare.
6
+ import { Type } from "typebox";
6
7
  import {
7
8
  completeSimple,
8
9
  openAICompletionsApi,
@@ -48,7 +49,11 @@ import {
48
49
  import { loadVeniceCatalog } from "./venice-catalog.js";
49
50
  import type { ChatModelConfig } from "./venice-catalog.js";
50
51
  import { keyRingAuth, withKeyRingAuth } from "./ring-provider";
51
- import { shrinkDoomedPayload, isPresendGuardEnabled } from "./presend-guard";
52
+ import {
53
+ buildContextWindowIndex,
54
+ readPresendPolicy,
55
+ shrinkDoomedPayload,
56
+ } from "./presend-guard";
52
57
  import { probeRoute } from "./probe.js";
53
58
  import {
54
59
  formatRingHealth,
@@ -850,25 +855,29 @@ export default async function (pi: ExtensionAPI) {
850
855
  }
851
856
 
852
857
  // Model-id → catalog context window for the pre-send guard.
853
- // Built once at factory load from the catalog this package owns (venice-catalog.ts).
854
- const presendContextWindows = new Map<string, number>();
855
- for (const m of veniceCatalog?.models ?? []) {
856
- if (typeof m.contextWindow === "number" && m.contextWindow > 0) {
857
- presendContextWindows.set(m.id, m.contextWindow);
858
- }
859
- }
858
+ // Built once at factory load from the catalog this package owns
859
+ // (venice-catalog.ts): exact ids, plus the normalized spelling a route may
860
+ // use for the same model. A dot-form request id (the route sends
861
+ // `deepseek-v4.1-flash`, the catalog spells `deepseek-v4-1-flash`) would
862
+ // otherwise resolve to no window and the guard would silently no-op.
863
+ const presendWindows = buildContextWindowIndex(veniceCatalog?.models ?? []);
860
864
 
861
865
  pi.on("before_provider_request", async (event: any) => {
862
866
  // Ring-lifecycle first (WAF/key shaping), then the pre-send guard on
863
- // the (possibly reshaped) payload. The guard no-ops unless the payload is
864
- // DOOMED for a KNOWN model; its returned payload (when present) replaces
865
- // the outgoing body per pi-core's emitBeforeProviderRequest semantics.
867
+ // the (possibly reshaped) payload. The token arm no-ops unless the payload
868
+ // is DOOMED for a KNOWN model; the byte arm no-ops unless the owner
869
+ // declared a cap (a request above the report threshold still writes one
870
+ // line). Its returned payload (when present) replaces the outgoing body
871
+ // per pi-core's emitBeforeProviderRequest semantics.
866
872
  const shaped = handleBeforeProviderRequest(event.payload);
867
873
  const outgoing = shaped ?? event.payload;
868
874
  try {
869
- if (!isPresendGuardEnabled()) return shaped;
875
+ const policy = readPresendPolicy();
876
+ if (!policy.enabled) return shaped;
870
877
  const guard = shrinkDoomedPayload(outgoing, {
871
- resolveContextWindow: (modelId) => presendContextWindows.get(modelId),
878
+ resolveContextWindow: (modelId) => presendWindows.resolve(modelId),
879
+ maxRequestBytes: policy.maxRequestBytes,
880
+ reportBytesAbove: policy.reportBytesAbove,
872
881
  });
873
882
  return guard?.payload ?? shaped;
874
883
  } catch {
package/package.json CHANGED
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "name": "pi-braid-providers",
3
- "version": "1.0.5",
4
- "description": "LLM provider for pi-coding-agent: Venice models via the agent-zero gateway, with a multi-key ring",
3
+ "version": "1.0.7",
4
+ "description": "Key rotation, provider failover, and route health for the pi coding agent, so one dead key or provider does not end the session.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "*.ts",
8
8
  "README.md",
9
- "LICENSE"
9
+ "LICENSE",
10
+ "build-info.json"
10
11
  ],
11
12
  "exports": {
12
13
  ".": "./index.ts"
13
14
  },
14
15
  "keywords": [
15
16
  "pi-package",
16
- "pi",
17
+ "braid",
17
18
  "pi-coding-agent",
18
- "extension",
19
- "providers",
20
- "llm",
21
- "venice.ai"
19
+ "llm-provider",
20
+ "api-key-rotation",
21
+ "failover",
22
+ "venice"
22
23
  ],
23
24
  "pi": {
24
25
  "extensions": [
@@ -30,16 +31,19 @@
30
31
  "typecheck": "tsc --noEmit",
31
32
  "test": "node test/provider-race.test.mjs && node test/route-table.test.mjs && node test/probe.test.mjs && node test/route-state.test.mjs && node test/ring-health.test.mjs && node test/rotation-engine.test.mjs && node test/route-failback.test.mjs && node test/providers-log.test.mjs && node test/key-ring-log.test.mjs && node test/key-ring-feedback.test.mjs && node test/key-ring-exhausted.test.mjs && node test/key-ring-hammer.test.mjs && node test/key-ring-cas-veto.test.mjs && node test/key-ring-selfheal.test.mjs && node test/ring-provider.test.mjs && node test/fallback-policy.test.mjs && node test/presend-guard.test.mjs && node test/venice-catalog.test.mjs && node test/waf-sanitize.test.mjs && node test/venice-direct-route.test.mjs && node test/venice-catalog-gen.test.mjs && node test/venice-catalog-cost.test.mjs && node test/route-walk-classes.test.mjs && node test/waf-repeat-rotation.test.mjs && node test/waf-marker-retryability.test.mjs && node test/route-usable.test.mjs && node test/virtual-router.test.mjs && node test/outage-watch.test.mjs && node test/failure-translation.test.mjs && node test/state-paths.test.mjs",
32
33
  "publish:local": "node ../core/scripts/publish-local.mjs providers",
34
+ "prepack": "node ../core/scripts/pack-provenance.mjs write",
35
+ "postpack": "node ../core/scripts/pack-provenance.mjs remove",
33
36
  "clean": "rm -rf node_modules package-lock.json",
34
37
  "reinstall": "npm run clean && npm install"
35
38
  },
36
39
  "devDependencies": {
37
40
  "typescript": "6.0.2",
38
- "@earendil-works/pi-ai": "^1.2.0"
41
+ "@earendil-works/pi-ai": "^1.5.0"
39
42
  },
40
43
  "peerDependencies": {
41
- "@earendil-works/pi-ai": "*",
42
- "@earendil-works/pi-coding-agent": "*"
44
+ "@earendil-works/pi-ai": "^1.5.0",
45
+ "@earendil-works/pi-coding-agent": "^1.5.0",
46
+ "typebox": "^1.1.38"
43
47
  },
44
48
  "author": "kifirkin",
45
49
  "license": "MIT",
@@ -53,6 +57,6 @@
53
57
  },
54
58
  "homepage": "https://github.com/kifirkin/pi-braid/tree/main/packages/providers",
55
59
  "engines": {
56
- "node": ">=22.19.0"
60
+ "node": ">=24.0.0"
57
61
  }
58
62
  }