@gonrocca/zero-pi 0.1.12 → 0.1.14
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 -21
- package/README.md +296 -258
- package/extensions/autotune-extension.ts +250 -250
- package/extensions/autotune.ts +558 -520
- package/extensions/conversation-resume.ts +400 -399
- package/extensions/provider-guard-extension.ts +195 -195
- package/extensions/provider-guard.ts +183 -183
- package/extensions/spec-merge-extension.ts +286 -286
- package/extensions/spec-merge.ts +373 -373
- package/extensions/startup-banner.ts +237 -237
- package/extensions/win-tree-kill.ts +114 -0
- package/extensions/working-phrases.ts +295 -295
- package/extensions/zero-models.ts +463 -333
- package/package.json +75 -73
- package/prompts/forge.md +34 -34
- package/prompts/orchestrator.md +292 -246
- package/prompts/phases/build.md +20 -20
- package/prompts/phases/explore.md +22 -22
- package/prompts/phases/plan.md +82 -82
- package/prompts/phases/veredicto.md +30 -30
- package/skills/{sdd-routing.md → sdd-routing/SKILL.md} +51 -51
- package/skills/{skill-loop.md → skill-loop/SKILL.md} +29 -29
- package/themes/zero-sdd.json +76 -76
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
// zero-pi — metered-provider guard, pure-logic module.
|
|
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.
|
|
9
|
-
//
|
|
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`.
|
|
15
|
-
//
|
|
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";
|
|
42
|
-
|
|
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"];
|
|
46
|
-
|
|
47
|
-
/** The minimum a `Model` of pi must expose for the guard to classify it. */
|
|
48
|
-
export interface ModelLike {
|
|
49
|
-
provider: string;
|
|
50
|
-
id: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
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`.
|
|
64
|
-
*
|
|
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`.
|
|
72
|
-
*/
|
|
73
|
-
export type GuardAction =
|
|
74
|
-
| { 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
|
-
};
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// User-facing Spanish strings
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
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
|
-
|
|
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.`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Classification
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
/** Whether a value is a non-empty string. */
|
|
122
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
123
|
-
return typeof value === "string" && value !== "";
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Classify a model switch into the `GuardAction` the wiring must execute.
|
|
128
|
-
*
|
|
129
|
-
* Pure and total — never throws (assuming `lookup` is total, which is the
|
|
130
|
-
* wiring's contract). The branches, in order:
|
|
131
|
-
*
|
|
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).
|
|
146
|
-
*/
|
|
147
|
-
export function classifyModelSwitch(
|
|
148
|
-
model: ModelLike | null | undefined,
|
|
149
|
-
source: GuardSource | string | null | undefined,
|
|
150
|
-
lookup: RegistryLookup,
|
|
151
|
-
): GuardAction {
|
|
152
|
-
// 1. Malformed event — no model, or missing/empty provider/id.
|
|
153
|
-
if (!model || !isNonEmptyString(model.provider) || !isNonEmptyString(model.id)) {
|
|
154
|
-
return { kind: "ignore" };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 2. Non-metered provider — silent no-op (covers `pi-claude-cli`).
|
|
158
|
-
const subscriptionProvider = METERED_TO_SUBSCRIPTION[model.provider];
|
|
159
|
-
if (subscriptionProvider === undefined) {
|
|
160
|
-
return { kind: "ignore" };
|
|
161
|
-
}
|
|
162
|
-
|
|
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
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { kind: "warn", message: warnMessage(id, meteredProvider) };
|
|
183
|
-
}
|
|
1
|
+
// zero-pi — metered-provider guard, pure-logic module.
|
|
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.
|
|
9
|
+
//
|
|
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`.
|
|
15
|
+
//
|
|
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";
|
|
42
|
+
|
|
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"];
|
|
46
|
+
|
|
47
|
+
/** The minimum a `Model` of pi must expose for the guard to classify it. */
|
|
48
|
+
export interface ModelLike {
|
|
49
|
+
provider: string;
|
|
50
|
+
id: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
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`.
|
|
64
|
+
*
|
|
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`.
|
|
72
|
+
*/
|
|
73
|
+
export type GuardAction =
|
|
74
|
+
| { 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
|
+
};
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// User-facing Spanish strings
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
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
|
+
|
|
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.`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Classification
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
/** Whether a value is a non-empty string. */
|
|
122
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
123
|
+
return typeof value === "string" && value !== "";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Classify a model switch into the `GuardAction` the wiring must execute.
|
|
128
|
+
*
|
|
129
|
+
* Pure and total — never throws (assuming `lookup` is total, which is the
|
|
130
|
+
* wiring's contract). The branches, in order:
|
|
131
|
+
*
|
|
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).
|
|
146
|
+
*/
|
|
147
|
+
export function classifyModelSwitch(
|
|
148
|
+
model: ModelLike | null | undefined,
|
|
149
|
+
source: GuardSource | string | null | undefined,
|
|
150
|
+
lookup: RegistryLookup,
|
|
151
|
+
): GuardAction {
|
|
152
|
+
// 1. Malformed event — no model, or missing/empty provider/id.
|
|
153
|
+
if (!model || !isNonEmptyString(model.provider) || !isNonEmptyString(model.id)) {
|
|
154
|
+
return { kind: "ignore" };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 2. Non-metered provider — silent no-op (covers `pi-claude-cli`).
|
|
158
|
+
const subscriptionProvider = METERED_TO_SUBSCRIPTION[model.provider];
|
|
159
|
+
if (subscriptionProvider === undefined) {
|
|
160
|
+
return { kind: "ignore" };
|
|
161
|
+
}
|
|
162
|
+
|
|
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
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { kind: "warn", message: warnMessage(id, meteredProvider) };
|
|
183
|
+
}
|