@gonrocca/zero-pi 0.1.24 → 0.1.25

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
@@ -195,12 +195,13 @@ re-derived.
195
195
 
196
196
  ### Provider guard — `provider-guard.ts`
197
197
 
198
- pi reaches the same Claude models through two providers: `pi-claude-cli` (billed
199
- against your subscription's plan limits) and `anthropic` (the direct API, billed
200
- per token from your metered extra-usage pool). The guard watches model switches:
201
- on a deliberate switch to a metered provider it offers via a confirmation
202
- dialog to redirect you to the equivalent model on `pi-claude-cli`. Say no and
203
- you stay put, no nagging. Switching to a subscription provider is a no-op.
198
+ The `anthropic` provider runs two ways: a Claude Pro/Max **subscription** login
199
+ (OAuth, via `/login` `pi-claude-oauth-adapter` smooths this path) or an **API
200
+ key**, which bills per token from your metered extra-usage pool. Same provider
201
+ id, different billing. The guard watches model switches and reads pi's auth mode
202
+ (`modelRegistry.isUsingOAuth`): when a model runs on `anthropic` with an API key
203
+ it emits a single non-blocking warning suggesting `/login`. On OAuth, or on any
204
+ other provider, it stays silent.
204
205
 
205
206
  ### Startup banner — `startup-banner.ts`
206
207
 
@@ -1,166 +1,105 @@
1
- // zero-pi — metered-provider guard, pi wiring.
1
+ // zero-pi — Anthropic metered-billing guard, pi wiring.
2
2
  //
3
3
  // A thin pi extension that wires the pure decision logic in `provider-guard.ts`
4
- // to the `model_select` hook. Whenever pi reports a model switch, this handler
5
- // classifies it (in the pure module) and executes the resulting `GuardAction`:
6
- // a silent no-op for subscription providers, a non-blocking `warning` for a
7
- // metered provider with no equivalent (or a session `restore`), or — for a
8
- // deliberate switch to a metered provider that has an equivalent — a `confirm`
9
- // dialog offering to redirect to the subscription provider.
4
+ // to the `model_select` hook. On every model switch it reads the auth mode from
5
+ // `ctx.modelRegistry.isUsingOAuth` and, when the `anthropic` provider runs on an
6
+ // API key (metered extra usage) rather than a Claude Pro/Max subscription OAuth
7
+ // login, emits a single non-blocking `warning`.
10
8
  //
11
- // All decisions live in `provider-guard.ts`; this file only declares the
12
- // minimal pi-API surface it uses, hooks `model_select`, and performs the I/O
13
- // (`confirm`/`notify`/`setModel`). Both `register` and the handler are wrapped
14
- // in a swallowing `try/catch` exactly like `autotune-extension.ts`, a failure
15
- // must never break a pi session. The package stays dependency-free: only
16
- // `provider-guard.ts` is imported (no `node:*` is needed here, no third party).
9
+ // All decisions live in `provider-guard.ts`; this file declares the minimal
10
+ // pi-API surface it uses, hooks `model_select`, and performs the I/O (`notify`).
11
+ // Both `register` and the handler are wrapped in a swallowing `try/catch` — a
12
+ // failure must never break a pi session. Dependency-free: only
13
+ // `provider-guard.ts` is imported (no `node:*`, no third party).
17
14
 
18
- import {
19
- classifyModelSwitch,
20
- redirectFailedMessage,
21
- type ModelLike,
22
- } from "./provider-guard.ts";
15
+ import { classifyModelSwitch } from "./provider-guard.ts";
23
16
 
24
17
  // ---------------------------------------------------------------------------
25
18
  // Minimal local pi-API interfaces
26
19
  // ---------------------------------------------------------------------------
27
- //
28
- // `provider-guard-extension.ts` declares only the slice of pi's API it uses,
29
- // exactly like `autotune-extension.ts` and `zero-models.ts`. The guard never
30
- // depends on pi's full type surface — just the shapes below.
31
20
 
32
- /** The minimum a `Model` of pi must expose for the guard. Mirrors `ModelLike`
33
- * of the pure module — `setModel`/the registry both speak this shape. */
21
+ /** The minimum a `Model` of pi must expose for the guard. */
34
22
  interface PiModel {
35
23
  provider: string;
36
24
  id: string;
37
25
  }
38
26
 
39
- /** The model registry slice the guard uses: a `find` that resolves a model by
40
- * `(provider, id)` or returns `undefined` when no such model exists. */
27
+ /**
28
+ * The model-registry slice the guard uses. `isUsingOAuth` reports whether a
29
+ * model authenticates through a Claude subscription OAuth login rather than an
30
+ * API key. It is optional — older pi builds may not expose it (see
31
+ * `resolveOAuth`).
32
+ */
41
33
  interface PiModelRegistry {
42
- find(provider: string, modelId: string): PiModel | undefined;
34
+ isUsingOAuth?(model: PiModel): boolean;
43
35
  }
44
36
 
45
- /** The slice of pi's UI surface the guard uses. `confirm` may be sync or async
46
- * (pi versions differ); both are handled uniformly with `await`. */
37
+ /** The slice of pi's UI surface the guard uses. */
47
38
  interface PiUI {
48
- confirm(title: string, message: string): Promise<boolean> | boolean;
49
39
  notify(message: string, type?: "info" | "warning" | "error"): void;
50
40
  }
51
41
 
52
- /** The context pi passes to a `model_select` handler. `modelRegistry` is
53
- * optional — if absent the guard degrades to warn-only (it can no longer
54
- * resolve equivalents) rather than breaking. */
42
+ /** The context pi passes to a `model_select` handler. */
55
43
  interface PiModelSelectContext {
56
44
  ui: PiUI;
57
45
  modelRegistry?: PiModelRegistry;
58
46
  }
59
47
 
60
- /** The `model_select` event pi emits on a model change. The guard only reads
61
- * `model` and `source`; `source` is `set`/`cycle`/`restore` or, defensively,
62
- * any other string a future pi version might report. */
48
+ /** The `model_select` event pi emits on a model change. */
63
49
  interface PiModelSelectEvent {
64
50
  type: "model_select";
65
51
  model?: PiModel;
66
- source?: "set" | "cycle" | "restore" | string;
67
52
  }
68
53
 
69
- /** The slice of pi's extension API the guard uses: `on` to hook events and
70
- * `setModel` to apply a redirection. */
54
+ /** The slice of pi's extension API the guard uses. */
71
55
  interface PiExtensionAPI {
72
56
  on(
73
57
  event: string,
74
- handler: (event: PiModelSelectEvent, ctx: PiModelSelectContext) => void | Promise<void>,
58
+ handler: (event: PiModelSelectEvent, ctx: PiModelSelectContext) => void,
75
59
  ): void;
76
- setModel(model: PiModel): Promise<boolean>;
77
60
  }
78
61
 
79
62
  // ---------------------------------------------------------------------------
80
63
  // Handler
81
64
  // ---------------------------------------------------------------------------
82
65
 
66
+ /**
67
+ * Resolve whether `model` authenticates through subscription OAuth.
68
+ *
69
+ * Calls `modelRegistry.isUsingOAuth` when available. When the method is absent
70
+ * (older pi) or throws, it returns `true` — an unknown auth mode must never
71
+ * produce a false metered warning.
72
+ */
73
+ function resolveOAuth(registry: PiModelRegistry | undefined, model: PiModel): boolean {
74
+ if (!registry || typeof registry.isUsingOAuth !== "function") return true;
75
+ try {
76
+ return registry.isUsingOAuth(model) !== false;
77
+ } catch {
78
+ // A registry failure degrades to "assume OAuth" — never a false warning.
79
+ return true;
80
+ }
81
+ }
82
+
83
83
  /**
84
84
  * The `model_select` guard handler.
85
85
  *
86
- * Runs entirely inside `register`'s swallowing `try/catch`, but is also wrapped
87
- * again here so no failure of `find`/`confirm`/`notify`/`setModel` can ever
88
- * propagate out of a pi session. The flow:
86
+ * Wrapped in a swallowing `try/catch` so no failure can propagate out of a pi
87
+ * session. The flow:
89
88
  *
90
- * 1. Validate `event`/`event.model` — a malformed event returns cleanly.
91
- * 2. Build the injected `lookup` over `ctx.modelRegistry.find`, wrapped in a
92
- * `try/catch` that returns `undefined`. If `modelRegistry` is absent,
93
- * degrade to an always-`undefined` lookup (warn-only).
94
- * 3. `classifyModelSwitch(model, source, lookup)` → a `GuardAction`.
95
- * 4. Execute the action: `ignore` → no-op; `warn` → `notify(..., "warning")`;
96
- * `offer-redirect` → `confirm`, and on a truthy answer `setModel` +
97
- * `notify(..., "info")` (or the failure notice if `setModel` returns
98
- * `false`). A falsy `confirm` leaves the user on the metered provider.
89
+ * 1. Validate `event`/`event.model`/`ctx.ui` — a malformed event bails out.
90
+ * 2. Resolve the auth mode via `resolveOAuth`.
91
+ * 3. `classifyModelSwitch(model, isOAuth)` a `GuardAction`.
92
+ * 4. `warn` `notify(..., "warning")`; `ignore` → no-op.
99
93
  */
100
- async function handleModelSelect(
101
- event: PiModelSelectEvent,
102
- ctx: PiModelSelectContext,
103
- pi: PiExtensionAPI,
104
- ): Promise<void> {
94
+ function handleModelSelect(event: PiModelSelectEvent, ctx: PiModelSelectContext): void {
105
95
  try {
106
- // 1. Malformed event — no event, no model, or no UI to talk to: bail out.
107
96
  if (!event || !event.model || !ctx || !ctx.ui || typeof ctx.ui.notify !== "function") {
108
97
  return;
109
98
  }
110
-
111
- // 2. Build the injected lookup. `classifyModelSwitch` assumes a total
112
- // lookup, so `find` is wrapped in a `try/catch` returning `undefined`.
113
- // If `modelRegistry` is absent the guard cannot resolve equivalents —
114
- // degrade to an always-`undefined` lookup so a metered provider still
115
- // produces a warning rather than breaking.
116
- const registry = ctx.modelRegistry;
117
- const lookup = (provider: string, id: string): ModelLike | undefined => {
118
- if (!registry || typeof registry.find !== "function") return undefined;
119
- try {
120
- return registry.find(provider, id);
121
- } catch {
122
- // A registry lookup failure degrades to "no equivalent", never throws.
123
- return undefined;
124
- }
125
- };
126
-
127
- // 3. Classify the switch in the pure module.
128
- const action = classifyModelSwitch(event.model, event.source, lookup);
129
-
130
- // 4. Execute the action.
131
- if (action.kind === "ignore") {
132
- // Non-metered provider or malformed event — nothing to do.
133
- return;
134
- }
135
-
99
+ const isOAuth = resolveOAuth(ctx.modelRegistry, event.model);
100
+ const action = classifyModelSwitch(event.model, isOAuth);
136
101
  if (action.kind === "warn") {
137
- // Metered provider with no equivalent, or a session `restore` — a single
138
- // non-blocking warning, no modal, no `setModel`.
139
102
  ctx.ui.notify(action.message, "warning");
140
- return;
141
- }
142
-
143
- // action.kind === "offer-redirect" — deliberate switch to a metered
144
- // provider that has a subscription equivalent. Offer the redirect; the
145
- // `confirm` dialog itself is the user's escape (AC 1.5).
146
- const accepted = await ctx.ui.confirm(action.confirmTitle, action.confirmMessage);
147
- if (!accepted) {
148
- // User declined or cancelled — leave them on the metered provider, no
149
- // second warning, no `setModel`.
150
- return;
151
- }
152
-
153
- const applied = await pi.setModel(action.safeModel);
154
- if (applied) {
155
- // Redirection took effect — confirm it with an `info` notification.
156
- ctx.ui.notify(action.redirectMessage, "info");
157
- } else {
158
- // `setModel` returned `false`: the switch did not apply. Do not claim
159
- // "redirected" — emit the optional failure notice instead.
160
- ctx.ui.notify(
161
- redirectFailedMessage(action.safeModel.id, action.safeModel.provider),
162
- "warning",
163
- );
164
103
  }
165
104
  } catch {
166
105
  // Any failure of the guard must never break a pi session.
@@ -175,20 +114,16 @@ async function handleModelSelect(
175
114
  * The pi extension entry point.
176
115
  *
177
116
  * pi calls this once when the extension loads. It wires the guard handler to
178
- * the `model_select` event. The whole body is wrapped in a swallowing
179
- * `try/catch`, and the handler is wrapped again, so neither registration nor a
180
- * later failure can ever break a pi session. Called with no or an invalid
181
- * `pi` (missing, or no `.on` function), it no-ops cleanly.
117
+ * the `model_select` event. The body is wrapped in a swallowing `try/catch`,
118
+ * and the handler is wrapped again, so neither registration nor a later failure
119
+ * can break a pi session. Called with no or an invalid `pi`, it no-ops cleanly.
182
120
  */
183
121
  export default function register(pi?: unknown): void {
184
122
  try {
185
123
  if (!pi || typeof (pi as PiExtensionAPI).on !== "function") {
186
124
  return;
187
125
  }
188
- const api = pi as PiExtensionAPI;
189
- api.on("model_select", (event: PiModelSelectEvent, ctx: PiModelSelectContext): Promise<void> => {
190
- return handleModelSelect(event, ctx, api);
191
- });
126
+ (pi as PiExtensionAPI).on("model_select", handleModelSelect);
192
127
  } catch {
193
128
  // Registration itself must never break a pi session.
194
129
  }
@@ -1,48 +1,26 @@
1
- // zero-pi — metered-provider guard, pure-logic module.
1
+ // zero-pi — Anthropic metered-billing guard, pure-logic module.
2
2
  //
3
- // pi can reach the same Claude models through two providers: `pi-claude-cli`
4
- // (routes via the locally installed Claude CLI, billed against the user's
5
- // subscription PLAN — no per-token charge) and `anthropic` (the direct
6
- // Anthropic API provider, which consumes a metered "extra usage" pool and
7
- // bills per token). `/model` lists the same models under both, so it is easy
8
- // to slide into metered billing unnoticed.
3
+ // pi reaches Claude models through the `anthropic` provider with one of two
4
+ // auth modes:
9
5
  //
10
- // This module holds every *decision* of the guard — classifying a model switch
11
- // into the action the wiring must run — in plain, dependency-free TypeScript so
12
- // it is testable with plain objects via `node --test`. The pi wiring (the
13
- // `model_select` handler that performs `confirm`/`notify`/`setModel`) lives in
14
- // `provider-guard-extension.ts`.
6
+ // OAuth a Claude Pro/Max subscription login (`/login`). The
7
+ // `pi-claude-oauth-adapter` package smooths this path. This is the mode
8
+ // to prefer: it authenticates against the Claude subscription.
9
+ // API key — `ANTHROPIC_API_KEY`. This bills per token from the metered
10
+ // "extra usage" pool.
15
11
  //
16
- // This file has no pi imports, no filesystem access, and no side-effecting
17
- // top-level code; it is pure data plus an injected lookup function.
18
-
19
- /**
20
- * Mapping metered-provider → equivalent subscription-provider.
21
- *
22
- * A provider that appears as a *key* here is "metered" switching to it
23
- * triggers the guard. The mapped *value* is the subscription provider the
24
- * guard offers to redirect to. v1 has exactly one pair; adding another
25
- * metered↔subscription pair is a single extra line.
26
- *
27
- * Invariant the no-redirect-loop guarantee depends on: a redirection target
28
- * (a *value*) must never itself be a *key*. `pi-claude-cli` is a value, not a
29
- * key, so the `setModel`-triggered second `model_select` event classifies as
30
- * `ignore` and the chain terminates.
31
- */
32
- export const METERED_TO_SUBSCRIPTION: Readonly<Record<string, string>> = {
33
- anthropic: "pi-claude-cli",
34
- };
35
-
36
- /**
37
- * Origin of a model change, as pi reports it on `ModelSelectEvent.source`.
38
- * `set`/`cycle` are deliberate user actions; `restore` is a non-deliberate
39
- * session restore that must never raise a modal.
40
- */
41
- export type GuardSource = "set" | "cycle" | "restore";
12
+ // Same provider id (`anthropic`), different billing. pi reports the auth mode
13
+ // via `modelRegistry.isUsingOAuth(model)`, so the guard keys off that not off
14
+ // the provider name. (The earlier guard redirected `anthropic` → `pi-claude-cli`;
15
+ // that provider is no longer used, so the redirect is gone.)
16
+ //
17
+ // This module holds the guard's decision in plain, dependency-free TypeScript
18
+ // so it is testable with plain objects via `node --test`. The pi wiring (the
19
+ // `model_select` handler that reads `isUsingOAuth` and calls `notify`) lives in
20
+ // `provider-guard-extension.ts`. No pi imports, no filesystem, no side effects.
42
21
 
43
- /** The deliberate sources that may raise a redirect modal. Any other source
44
- * (including `restore` and unknown strings) is treated as non-deliberate. */
45
- const DELIBERATE_SOURCES: readonly string[] = ["set", "cycle"];
22
+ /** The pi provider whose billing depends on the auth mode. */
23
+ export const METERED_PROVIDER = "anthropic";
46
24
 
47
25
  /** The minimum a `Model` of pi must expose for the guard to classify it. */
48
26
  export interface ModelLike {
@@ -51,67 +29,24 @@ export interface ModelLike {
51
29
  }
52
30
 
53
31
  /**
54
- * Injected registry-lookup function. The wiring builds this over
55
- * `ctx.modelRegistry.find`; the pure module never knows `modelRegistry` or any
56
- * pi type. Returns `undefined` when no equivalent model exists. The wiring is
57
- * responsible for wrapping `find` so this lookup is total — `classifyModelSwitch`
58
- * assumes a `lookup` that never throws.
59
- */
60
- export type RegistryLookup = (provider: string, id: string) => ModelLike | undefined;
61
-
62
- /**
63
- * The action the wiring must execute, as a discriminated union keyed on `kind`.
32
+ * The action the wiring must execute, a discriminated union keyed on `kind`.
64
33
  *
65
- * - `ignore` — no-op: non-metered provider, or a malformed event.
66
- * - `warn` — emit only `ctx.ui.notify(message, "warning")`; no modal, no
67
- * `setModel`. Used for `restore`, for a metered provider with no equivalent,
68
- * and for any non-deliberate source.
69
- * - `offer-redirect` — show `ctx.ui.confirm(confirmTitle, confirmMessage)`; on
70
- * a truthy answer `setModel(safeModel)` and, if applied, notify with
71
- * `redirectMessage`.
34
+ * - `ignore` — no-op: not the `anthropic` provider, `anthropic` on OAuth
35
+ * (subscription), or a malformed event.
36
+ * - `warn` emit `ctx.ui.notify(message, "warning")`: `anthropic` on an API
37
+ * key, which bills metered extra usage.
72
38
  */
73
39
  export type GuardAction =
74
40
  | { kind: "ignore" }
75
- | { kind: "warn"; message: string }
76
- | {
77
- kind: "offer-redirect";
78
- safeModel: ModelLike;
79
- confirmTitle: string;
80
- confirmMessage: string;
81
- redirectMessage: string;
82
- };
41
+ | { kind: "warn"; message: string };
83
42
 
84
43
  // ---------------------------------------------------------------------------
85
- // User-facing Spanish strings
44
+ // User-facing Spanish string
86
45
  // ---------------------------------------------------------------------------
87
- //
88
- // Exported as constants/functions so the test can assert them verbatim. `id`
89
- // is the model id (e.g. `claude-opus-4-7`); `metered`/`subscription` are the
90
- // provider names from `METERED_TO_SUBSCRIPTION`.
91
-
92
- /** Title of the redirect `confirm` dialog. */
93
- export const CONFIRM_TITLE = "zero · proveedor con cobro por token";
94
-
95
- /** Body of the redirect `confirm` dialog — explains the metered billing and
96
- * asks whether to switch to the subscription equivalent. */
97
- export function confirmMessage(id: string, metered: string, subscription: string): string {
98
- return `Estás cambiando a «${id}» vía el proveedor «${metered}», que factura por token y consume tu pool medido de extra usage. ¿Querés cambiar al equivalente «${id}» en «${subscription}», que usa los límites de tu suscripción?`;
99
- }
100
-
101
- /** `info` notification emitted after `setModel` succeeds. */
102
- export function redirectMessage(id: string, subscription: string): string {
103
- return `zero: redirigido a «${id}» en ${subscription} — usando los límites de tu suscripción.`;
104
- }
105
46
 
106
- /** `warning` notification — no equivalent available, or `restore`. */
107
- export function warnMessage(id: string, metered: string): string {
108
- return `zero: «${id}» está activo vía el proveedor «${metered}», que factura por token y consume tu pool medido de extra usage.`;
109
- }
110
-
111
- /** `warning` notification — `setModel` returned `false` (redirection did not
112
- * apply). Optional; emitted by the wiring as a defensive courtesy. */
113
- export function redirectFailedMessage(id: string, subscription: string): string {
114
- return `zero: no se pudo cambiar a «${id}» en ${subscription} — seguís en el proveedor medido.`;
47
+ /** `warning` notification — the model runs on the metered `anthropic` API key. */
48
+ export function warnMessage(id: string): string {
49
+ return `zero: «${id}» usa el provider «anthropic» con API key factura por token (extra usage). Logueá con Claude Pro/Max vía /login para usar tu suscripción.`;
115
50
  }
116
51
 
117
52
  // ---------------------------------------------------------------------------
@@ -126,58 +61,38 @@ function isNonEmptyString(value: unknown): value is string {
126
61
  /**
127
62
  * Classify a model switch into the `GuardAction` the wiring must execute.
128
63
  *
129
- * Pure and total — never throws (assuming `lookup` is total, which is the
130
- * wiring's contract). The branches, in order:
64
+ * Pure and total — never throws. The branches, in order:
65
+ *
66
+ * 1. `model` falsy, or `provider`/`id` not non-empty strings → `ignore`
67
+ * (malformed event).
68
+ * 2. `model.provider` is not `anthropic` → `ignore` (other providers carry
69
+ * no OAuth-vs-API-key billing split this guard reasons about).
70
+ * 3. The provider is `anthropic`:
71
+ * - `isOAuth` truthy → `ignore` (subscription auth — the intended path).
72
+ * - `isOAuth` falsy → `warn` (API key — metered extra usage).
131
73
  *
132
- * 1. `model` falsy, or `model.provider`/`model.id` not non-empty strings
133
- * `ignore` (malformed event, AC 6.3).
134
- * 2. `model.provider` is not a key of `METERED_TO_SUBSCRIPTION` → `ignore`
135
- * (non-metered provider, AC 4.1/4.2 — this also classifies `pi-claude-cli`,
136
- * which is the structural no-redirect-loop guarantee, Story 5).
137
- * 3. The provider is metered. Resolve the subscription provider and call
138
- * `lookup(subProvider, model.id)`:
139
- * - `source === "restore"` → always `warn`, never a modal, regardless of
140
- * whether an equivalent exists (AC 3.x).
141
- * - `source` deliberate (`set`/`cycle`):
142
- * - `lookup` found a model → `offer-redirect` (AC 1).
143
- * - `lookup` returned `undefined` → `warn` (AC 2).
144
- * - any other (unknown) `source` → treated as non-deliberate → `warn`
145
- * (defensive: no modal unless the switch is known to be deliberate).
74
+ * `isOAuth` is supplied by the wiring from `modelRegistry.isUsingOAuth`. When
75
+ * the wiring cannot determine the auth mode it passes `true`, so an unknown
76
+ * state never produces a false warning.
146
77
  */
147
78
  export function classifyModelSwitch(
148
79
  model: ModelLike | null | undefined,
149
- source: GuardSource | string | null | undefined,
150
- lookup: RegistryLookup,
80
+ isOAuth: boolean,
151
81
  ): GuardAction {
152
82
  // 1. Malformed event — no model, or missing/empty provider/id.
153
83
  if (!model || !isNonEmptyString(model.provider) || !isNonEmptyString(model.id)) {
154
84
  return { kind: "ignore" };
155
85
  }
156
86
 
157
- // 2. Non-metered provider — silent no-op (covers `pi-claude-cli`).
158
- const subscriptionProvider = METERED_TO_SUBSCRIPTION[model.provider];
159
- if (subscriptionProvider === undefined) {
87
+ // 2. Any provider other than `anthropic` out of scope, silent no-op.
88
+ if (model.provider !== METERED_PROVIDER) {
160
89
  return { kind: "ignore" };
161
90
  }
162
91
 
163
- // 3. Metered provider. A deliberate switch with an equivalent gets a modal;
164
- // everything else (restore, no equivalent, unknown source) gets a warn.
165
- const meteredProvider = model.provider;
166
- const id = model.id;
167
- const isDeliberate = typeof source === "string" && DELIBERATE_SOURCES.includes(source);
168
-
169
- if (isDeliberate) {
170
- const safeModel = lookup(subscriptionProvider, id);
171
- if (safeModel) {
172
- return {
173
- kind: "offer-redirect",
174
- safeModel,
175
- confirmTitle: CONFIRM_TITLE,
176
- confirmMessage: confirmMessage(id, meteredProvider, subscriptionProvider),
177
- redirectMessage: redirectMessage(id, subscriptionProvider),
178
- };
179
- }
92
+ // 3. `anthropic`: OAuth is the subscription path (fine); an API key is the
93
+ // metered path (warn).
94
+ if (isOAuth) {
95
+ return { kind: "ignore" };
180
96
  }
181
-
182
- return { kind: "warn", message: warnMessage(id, meteredProvider) };
97
+ return { kind: "warn", message: warnMessage(model.id) };
183
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/zero-pi",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "zero-pi — an installable layer for pi (pi.dev): the zero spec-driven development workflow, skill auto-learning, and an animated ZERO startup banner. Adds capability to pi without modifying pi.",
5
5
  "type": "module",
6
6
  "keywords": [