@centerforagenticai/pi-multi-account 0.1.1
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/NOTICE +29 -0
- package/README.md +999 -0
- package/config/models/pi-multi-account.v1.json +32 -0
- package/config/subscription-plans.v1.json +122 -0
- package/package.json +76 -0
- package/packages/pi-anthropic-oauth/LICENSE +21 -0
- package/packages/pi-anthropic-oauth/package.json +54 -0
- package/packages/pi-anthropic-oauth/src/auth.ts +396 -0
- package/packages/pi-anthropic-oauth/src/context.ts +116 -0
- package/packages/pi-anthropic-oauth/src/convert.ts +303 -0
- package/packages/pi-anthropic-oauth/src/index.ts +37 -0
- package/packages/pi-anthropic-oauth/src/prompt.ts +137 -0
- package/packages/pi-anthropic-oauth/src/stream.ts +476 -0
- package/packages/pi-antigravity/LICENSE +21 -0
- package/packages/pi-antigravity/package.json +77 -0
- package/packages/pi-antigravity/src/auth/index.ts +14 -0
- package/packages/pi-antigravity/src/auth/oauth.ts +442 -0
- package/packages/pi-antigravity/src/client/client.ts +561 -0
- package/packages/pi-antigravity/src/client/index.ts +1 -0
- package/packages/pi-antigravity/src/context.ts +110 -0
- package/packages/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/packages/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/packages/pi-antigravity/src/image/image.ts +336 -0
- package/packages/pi-antigravity/src/image/index.ts +1 -0
- package/packages/pi-antigravity/src/index.ts +280 -0
- package/packages/pi-antigravity/src/models/discovery.ts +154 -0
- package/packages/pi-antigravity/src/models/grouping.ts +424 -0
- package/packages/pi-antigravity/src/models/index.ts +3 -0
- package/packages/pi-antigravity/src/models/models.ts +500 -0
- package/packages/pi-antigravity/src/stream/index.ts +1 -0
- package/packages/pi-antigravity/src/stream/stream.ts +1478 -0
- package/packages/pi-antigravity/src/types/enums.ts +42 -0
- package/packages/pi-antigravity/src/types/index.ts +2 -0
- package/packages/pi-antigravity/src/types/types.ts +292 -0
- package/packages/pi-antigravity/src/usage/index.ts +1 -0
- package/packages/pi-antigravity/src/usage/usage.ts +416 -0
- package/packages/pi-antigravity/src/utils/http.ts +91 -0
- package/packages/pi-antigravity/src/utils/index.ts +3 -0
- package/packages/pi-antigravity/src/utils/security.ts +73 -0
- package/packages/pi-antigravity/src/utils/util.ts +132 -0
- package/scripts/multi-account.mjs +44 -0
- package/src/account-labels.ts +223 -0
- package/src/account-plan-assignment.ts +340 -0
- package/src/account-rate-history.ts +372 -0
- package/src/anthropic-adaptive-stream.ts +531 -0
- package/src/anthropic-alias-stream.ts +140 -0
- package/src/anthropic-context-compat.ts +80 -0
- package/src/api-pricing.ts +579 -0
- package/src/bounded-file-lines.ts +97 -0
- package/src/catalog-rebinding.ts +177 -0
- package/src/catalog-registration-probe.ts +111 -0
- package/src/codex-adapter.ts +345 -0
- package/src/codex-model-defaults.ts +785 -0
- package/src/command-completions.ts +404 -0
- package/src/commands.ts +2000 -0
- package/src/compaction.ts +14 -0
- package/src/config.ts +1317 -0
- package/src/continuation.ts +569 -0
- package/src/cooldowns.ts +110 -0
- package/src/cost-digest-store.ts +332 -0
- package/src/cost-digest.ts +1044 -0
- package/src/cost-history.ts +251 -0
- package/src/cost-period-closer.ts +160 -0
- package/src/cost-report-json.ts +318 -0
- package/src/cost-report-reader.ts +368 -0
- package/src/cost-report-render.ts +207 -0
- package/src/cost-report.ts +1104 -0
- package/src/coverage-attestation.ts +397 -0
- package/src/credential-lifecycle.ts +169 -0
- package/src/credential-refresh.ts +248 -0
- package/src/declaration-notice-marker.ts +238 -0
- package/src/diagnostic-store.ts +276 -0
- package/src/diagnostics.ts +309 -0
- package/src/discovery.ts +471 -0
- package/src/duration.ts +13 -0
- package/src/error-classification.ts +256 -0
- package/src/fuzzy.ts +15 -0
- package/src/group-policy.ts +81 -0
- package/src/history-store.ts +897 -0
- package/src/index.ts +5572 -0
- package/src/lifecycle.ts +378 -0
- package/src/logical-dispatch.ts +279 -0
- package/src/logical-model-selector.ts +254 -0
- package/src/logical-model-switcher.ts +430 -0
- package/src/logical-provider-attribution.ts +544 -0
- package/src/logical-provider.ts +1237 -0
- package/src/logical-route-indicator.ts +215 -0
- package/src/machine-lease.ts +445 -0
- package/src/model-support.ts +66 -0
- package/src/models-declaration.ts +1091 -0
- package/src/openai-adapter.ts +117 -0
- package/src/openrouter-budget.ts +304 -0
- package/src/openrouter-fallback.ts +146 -0
- package/src/period-boundaries.ts +376 -0
- package/src/pi-anthropic-oauth.d.ts +6 -0
- package/src/preflight.ts +253 -0
- package/src/pricing-cache.ts +235 -0
- package/src/project-identity.ts +100 -0
- package/src/provider-registration.ts +942 -0
- package/src/rate-formula.ts +163 -0
- package/src/recovery-engine.ts +853 -0
- package/src/recovery-output.ts +837 -0
- package/src/recovery-plan.ts +239 -0
- package/src/report-range.ts +203 -0
- package/src/route-resolver.ts +789 -0
- package/src/routing-config-transaction.ts +232 -0
- package/src/routing.ts +1163 -0
- package/src/runtime-state.ts +630 -0
- package/src/session-account-groups.ts +284 -0
- package/src/session-restore.ts +287 -0
- package/src/shared-usage.ts +1392 -0
- package/src/standalone-cli.ts +720 -0
- package/src/status-view.ts +578 -0
- package/src/subscription-plan-catalog.ts +346 -0
- package/src/tier-model-resolver.ts +46 -0
- package/src/upstream-anthropic.ts +315 -0
- package/src/upstream-antigravity.ts +327 -0
- package/src/usage-fetch.ts +1634 -0
- package/src/usage.ts +1026 -0
- package/src/vendor.ts +87 -0
- package/src/warmer.ts +231 -0
- package/src/watchdog.ts +219 -0
- package/src/window-history.ts +270 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Context, Tool } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
type TranscriptTextBlock = {
|
|
4
|
+
readonly type: "text";
|
|
5
|
+
readonly text: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type TranscriptSystemMessage = {
|
|
9
|
+
readonly role: "system";
|
|
10
|
+
readonly content: string | readonly TranscriptTextBlock[];
|
|
11
|
+
readonly sections?: Readonly<Record<string, string | null>>;
|
|
12
|
+
readonly toolsAdded?: readonly Tool[];
|
|
13
|
+
readonly toolsRemoved?: readonly { readonly name: string }[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function isTranscriptSystemMessage(
|
|
17
|
+
message: unknown,
|
|
18
|
+
): message is TranscriptSystemMessage {
|
|
19
|
+
return (
|
|
20
|
+
typeof message === "object" &&
|
|
21
|
+
message !== null &&
|
|
22
|
+
"role" in message &&
|
|
23
|
+
message.role === "system"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function transcriptContentText(
|
|
28
|
+
content: TranscriptSystemMessage["content"],
|
|
29
|
+
): string {
|
|
30
|
+
if (typeof content === "string") return content;
|
|
31
|
+
return content
|
|
32
|
+
.filter(
|
|
33
|
+
(block): block is TranscriptTextBlock =>
|
|
34
|
+
block.type === "text" && typeof block.text === "string",
|
|
35
|
+
)
|
|
36
|
+
.map((block) => block.text)
|
|
37
|
+
.join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Replays Pi 0.87 provider-facing transcript declarations into the Pi 0.84
|
|
42
|
+
* Context shape consumed by the exact-pinned OAuth stream and local adaptive
|
|
43
|
+
* derivative. Legacy contexts contain no system messages and pass through by
|
|
44
|
+
* identity.
|
|
45
|
+
*/
|
|
46
|
+
export function toPinnedAnthropicContext(context: Context): Context {
|
|
47
|
+
const transcriptMessages = context.messages as readonly unknown[];
|
|
48
|
+
if (!transcriptMessages.some(isTranscriptSystemMessage)) return context;
|
|
49
|
+
|
|
50
|
+
const promptContent: string[] = [];
|
|
51
|
+
const promptSections = new Map<string, string>();
|
|
52
|
+
const tools = new Map<string, Tool>();
|
|
53
|
+
const messages: Context["messages"] = [];
|
|
54
|
+
|
|
55
|
+
for (const message of transcriptMessages) {
|
|
56
|
+
if (!isTranscriptSystemMessage(message)) {
|
|
57
|
+
messages.push(message as Context["messages"][number]);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const content = transcriptContentText(message.content);
|
|
62
|
+
if (content.length > 0) promptContent.push(content);
|
|
63
|
+
|
|
64
|
+
for (const [name, value] of Object.entries(message.sections ?? {})) {
|
|
65
|
+
if (value === null) promptSections.delete(name);
|
|
66
|
+
else promptSections.set(name, value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const tool of message.toolsRemoved ?? []) tools.delete(tool.name);
|
|
70
|
+
for (const tool of message.toolsAdded ?? []) tools.set(tool.name, tool);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const systemPrompt = [...promptContent, ...promptSections.values()]
|
|
74
|
+
.filter((part) => part.length > 0)
|
|
75
|
+
.join("\n\n");
|
|
76
|
+
const pinnedContext: Context = { messages };
|
|
77
|
+
if (systemPrompt.length > 0) pinnedContext.systemPrompt = systemPrompt;
|
|
78
|
+
if (tools.size > 0) pinnedContext.tools = [...tools.values()];
|
|
79
|
+
return pinnedContext;
|
|
80
|
+
}
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { vendorForFamily, type Vendor } from "./vendor.js";
|
|
2
|
+
|
|
3
|
+
const MANAGED_AUTHORS = ["anthropic", "openai", "google"] as const;
|
|
4
|
+
const MAX_MODEL_COUNT = 4_096;
|
|
5
|
+
const MAX_MODEL_ID_LENGTH = 200;
|
|
6
|
+
const MAX_TIER_COUNT = 16;
|
|
7
|
+
export const MAX_METHOD_LIMITS_LENGTH = 400;
|
|
8
|
+
|
|
9
|
+
export interface ApiModelRate {
|
|
10
|
+
readonly sourceModelId: string;
|
|
11
|
+
readonly inputPerToken: number;
|
|
12
|
+
readonly outputPerToken: number;
|
|
13
|
+
readonly cacheReadPerToken?: number;
|
|
14
|
+
readonly cacheWritePerToken?: number;
|
|
15
|
+
readonly cacheWrite1hPerToken?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ApiRateSnapshot {
|
|
19
|
+
readonly schemaVersion: 1;
|
|
20
|
+
readonly source: "openrouter";
|
|
21
|
+
readonly fetchedAtMs: number;
|
|
22
|
+
readonly rates: Readonly<Record<string, ApiModelRate>>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Per-token USD rates for one matched cost tier or a model's base rates,
|
|
27
|
+
* exactly as Pi's own `calculateCost` reads `model.cost` / `model.cost.tiers`.
|
|
28
|
+
* Values are USD per 1,000,000 tokens.
|
|
29
|
+
*/
|
|
30
|
+
export interface PiModelCostRates {
|
|
31
|
+
readonly input: number;
|
|
32
|
+
readonly output: number;
|
|
33
|
+
readonly cacheRead: number;
|
|
34
|
+
readonly cacheWrite: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A request-wide pricing tier: applies when observed input tokens exceed `inputTokensAbove`. */
|
|
38
|
+
export interface PiModelCostTier extends PiModelCostRates {
|
|
39
|
+
readonly inputTokensAbove: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One installed-catalog model's base rates plus any higher-volume tiers. */
|
|
43
|
+
export interface PiModelCost extends PiModelCostRates {
|
|
44
|
+
readonly sourceModelId: string;
|
|
45
|
+
readonly tiers?: readonly PiModelCostTier[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A caller-supplied snapshot of Pi's installed model catalog cost data.
|
|
50
|
+
* `catalogVersion` is an opaque identity (for example the pinned pi-ai
|
|
51
|
+
* package version) recorded verbatim as immutable pricing-method provenance;
|
|
52
|
+
* this module never fetches or infers it.
|
|
53
|
+
*/
|
|
54
|
+
export interface PiCatalogSnapshot {
|
|
55
|
+
readonly schemaVersion: 1;
|
|
56
|
+
readonly source: "pi-installed-catalog";
|
|
57
|
+
readonly catalogVersion: string;
|
|
58
|
+
readonly capturedAtMs: number;
|
|
59
|
+
readonly costsBySourceModelId: Readonly<Record<string, PiModelCost>>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Immutable provenance for one API-equivalent price: which source produced
|
|
64
|
+
* it, which tier (if any) applied, and the bounded, human-readable limits of
|
|
65
|
+
* that method. Persisted verbatim on priced digest rows and never recomputed.
|
|
66
|
+
*/
|
|
67
|
+
export interface ApiEquivalentMethod {
|
|
68
|
+
readonly source: "pi-installed-catalog" | "openrouter" | "legacy-day-aggregate";
|
|
69
|
+
readonly catalogVersion: string;
|
|
70
|
+
readonly tierInputTokensAbove: number | undefined;
|
|
71
|
+
readonly limits: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface EstimateTokens {
|
|
75
|
+
readonly input: number;
|
|
76
|
+
readonly output: number;
|
|
77
|
+
readonly cacheRead: number;
|
|
78
|
+
readonly cacheWrite: number;
|
|
79
|
+
readonly cacheWrite1h?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type ApiEquivalentEstimate =
|
|
83
|
+
| {
|
|
84
|
+
readonly status: "priced";
|
|
85
|
+
readonly totalUsd: number;
|
|
86
|
+
readonly rateAsOfMs: number;
|
|
87
|
+
readonly sourceModelId: string;
|
|
88
|
+
readonly components: {
|
|
89
|
+
readonly inputUsd: number;
|
|
90
|
+
readonly outputUsd: number;
|
|
91
|
+
readonly cacheReadUsd: number;
|
|
92
|
+
readonly cacheWriteUsd: number;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
| { readonly status: "unpriced"; readonly reason: string };
|
|
96
|
+
|
|
97
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
98
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parsePrice(value: unknown): number | undefined {
|
|
102
|
+
if (typeof value !== "string" || value.trim().length === 0) return undefined;
|
|
103
|
+
const parsed = Number(value);
|
|
104
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function managedModelId(value: unknown): value is string {
|
|
108
|
+
if (
|
|
109
|
+
typeof value !== "string" ||
|
|
110
|
+
value.length === 0 ||
|
|
111
|
+
value.length > MAX_MODEL_ID_LENGTH
|
|
112
|
+
) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return MANAGED_AUTHORS.some((author) => value.startsWith(`${author}/`));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseRate(value: unknown, sourceModelId: string): ApiModelRate | undefined {
|
|
119
|
+
if (!isRecord(value)) return undefined;
|
|
120
|
+
const input = parsePrice(value.prompt);
|
|
121
|
+
const output = parsePrice(value.completion);
|
|
122
|
+
if (input === undefined || output === undefined) return undefined;
|
|
123
|
+
const discount = value.discount === undefined ? 0 : value.discount;
|
|
124
|
+
if (
|
|
125
|
+
typeof discount !== "number" ||
|
|
126
|
+
!Number.isFinite(discount) ||
|
|
127
|
+
discount < 0 ||
|
|
128
|
+
discount > 1
|
|
129
|
+
) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const multiplier = 1 - discount;
|
|
133
|
+
const optionalPrice = (field: string): number | undefined => {
|
|
134
|
+
const raw = value[field];
|
|
135
|
+
if (raw === undefined) return undefined;
|
|
136
|
+
const parsed = parsePrice(raw);
|
|
137
|
+
return parsed === undefined ? Number.NaN : parsed * multiplier;
|
|
138
|
+
};
|
|
139
|
+
const cacheRead = optionalPrice("input_cache_read");
|
|
140
|
+
const cacheWrite = optionalPrice("input_cache_write");
|
|
141
|
+
const cacheWrite1h = optionalPrice("input_cache_write_1h");
|
|
142
|
+
if ([cacheRead, cacheWrite, cacheWrite1h].some(Number.isNaN)) return undefined;
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
sourceModelId,
|
|
145
|
+
inputPerToken: input * multiplier,
|
|
146
|
+
outputPerToken: output * multiplier,
|
|
147
|
+
...(cacheRead === undefined ? {} : { cacheReadPerToken: cacheRead }),
|
|
148
|
+
...(cacheWrite === undefined ? {} : { cacheWritePerToken: cacheWrite }),
|
|
149
|
+
...(cacheWrite1h === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { cacheWrite1hPerToken: cacheWrite1h }),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Parse and bound the public OpenRouter model catalogue into a small rate map. */
|
|
156
|
+
export function parseOpenRouterRateSnapshot(
|
|
157
|
+
payload: unknown,
|
|
158
|
+
fetchedAtMs: number,
|
|
159
|
+
): ApiRateSnapshot {
|
|
160
|
+
if (!Number.isFinite(fetchedAtMs) || fetchedAtMs < 0) {
|
|
161
|
+
throw new RangeError("fetchedAtMs must be a finite non-negative timestamp.");
|
|
162
|
+
}
|
|
163
|
+
if (!isRecord(payload) || !Array.isArray(payload.data)) {
|
|
164
|
+
throw new TypeError("OpenRouter model response must contain a data array.");
|
|
165
|
+
}
|
|
166
|
+
if (payload.data.length > MAX_MODEL_COUNT) {
|
|
167
|
+
throw new RangeError("OpenRouter model response exceeds the model limit.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const rates: Record<string, ApiModelRate> = {};
|
|
171
|
+
for (const candidate of payload.data) {
|
|
172
|
+
if (!isRecord(candidate) || !managedModelId(candidate.id)) continue;
|
|
173
|
+
const rate = parseRate(candidate.pricing, candidate.id);
|
|
174
|
+
if (rate === undefined) continue;
|
|
175
|
+
if (rates[candidate.id] === undefined) rates[candidate.id] = rate;
|
|
176
|
+
if (
|
|
177
|
+
managedModelId(candidate.canonical_slug) &&
|
|
178
|
+
rates[candidate.canonical_slug] === undefined
|
|
179
|
+
) {
|
|
180
|
+
rates[candidate.canonical_slug] = rate;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return Object.freeze({
|
|
184
|
+
schemaVersion: 1,
|
|
185
|
+
source: "openrouter",
|
|
186
|
+
fetchedAtMs,
|
|
187
|
+
rates: Object.freeze(rates),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parseStoredRate(value: unknown): ApiModelRate | undefined {
|
|
192
|
+
if (!isRecord(value) || !managedModelId(value.sourceModelId)) return undefined;
|
|
193
|
+
const required = [value.inputPerToken, value.outputPerToken];
|
|
194
|
+
const optional = [
|
|
195
|
+
value.cacheReadPerToken,
|
|
196
|
+
value.cacheWritePerToken,
|
|
197
|
+
value.cacheWrite1hPerToken,
|
|
198
|
+
].filter((entry) => entry !== undefined);
|
|
199
|
+
if (
|
|
200
|
+
![...required, ...optional].every(
|
|
201
|
+
(entry) =>
|
|
202
|
+
typeof entry === "number" && Number.isFinite(entry) && entry >= 0,
|
|
203
|
+
)
|
|
204
|
+
) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
return Object.freeze({
|
|
208
|
+
sourceModelId: value.sourceModelId,
|
|
209
|
+
inputPerToken: value.inputPerToken as number,
|
|
210
|
+
outputPerToken: value.outputPerToken as number,
|
|
211
|
+
...(value.cacheReadPerToken === undefined
|
|
212
|
+
? {}
|
|
213
|
+
: { cacheReadPerToken: value.cacheReadPerToken as number }),
|
|
214
|
+
...(value.cacheWritePerToken === undefined
|
|
215
|
+
? {}
|
|
216
|
+
: { cacheWritePerToken: value.cacheWritePerToken as number }),
|
|
217
|
+
...(value.cacheWrite1hPerToken === undefined
|
|
218
|
+
? {}
|
|
219
|
+
: { cacheWrite1hPerToken: value.cacheWrite1hPerToken as number }),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Re-validate a persisted snapshot before any cached rate is trusted. */
|
|
224
|
+
export function parseStoredApiRateSnapshot(value: unknown): ApiRateSnapshot {
|
|
225
|
+
if (
|
|
226
|
+
!isRecord(value) ||
|
|
227
|
+
value.schemaVersion !== 1 ||
|
|
228
|
+
value.source !== "openrouter" ||
|
|
229
|
+
typeof value.fetchedAtMs !== "number" ||
|
|
230
|
+
!Number.isFinite(value.fetchedAtMs) ||
|
|
231
|
+
value.fetchedAtMs < 0 ||
|
|
232
|
+
!isRecord(value.rates)
|
|
233
|
+
) {
|
|
234
|
+
throw new TypeError("Stored API rate snapshot is invalid.");
|
|
235
|
+
}
|
|
236
|
+
const entries = Object.entries(value.rates);
|
|
237
|
+
if (entries.length > MAX_MODEL_COUNT * 2) {
|
|
238
|
+
throw new RangeError("Stored API rate snapshot exceeds the model limit.");
|
|
239
|
+
}
|
|
240
|
+
const rates: Record<string, ApiModelRate> = {};
|
|
241
|
+
for (const [modelId, candidate] of entries) {
|
|
242
|
+
if (!managedModelId(modelId)) {
|
|
243
|
+
throw new TypeError("Stored API rate snapshot has an invalid model id.");
|
|
244
|
+
}
|
|
245
|
+
const rate = parseStoredRate(candidate);
|
|
246
|
+
if (rate === undefined) {
|
|
247
|
+
throw new TypeError("Stored API rate snapshot has an invalid rate.");
|
|
248
|
+
}
|
|
249
|
+
rates[modelId] = rate;
|
|
250
|
+
}
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
schemaVersion: 1,
|
|
253
|
+
source: "openrouter",
|
|
254
|
+
fetchedAtMs: value.fetchedAtMs,
|
|
255
|
+
rates: Object.freeze(rates),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function accountAuthor(canonicalAccountId: string): Vendor | undefined {
|
|
260
|
+
const family = canonicalAccountId.startsWith("anthropic")
|
|
261
|
+
? "anthropic"
|
|
262
|
+
: canonicalAccountId.startsWith("openai-codex")
|
|
263
|
+
? "openai-codex"
|
|
264
|
+
: canonicalAccountId.startsWith("google-antigravity")
|
|
265
|
+
? "google-antigravity"
|
|
266
|
+
: undefined;
|
|
267
|
+
if (family === undefined) return undefined;
|
|
268
|
+
if (canonicalAccountId !== family) {
|
|
269
|
+
const prefix = `${family}-account-`;
|
|
270
|
+
if (!canonicalAccountId.startsWith(prefix)) return undefined;
|
|
271
|
+
const suffix = canonicalAccountId.slice(prefix.length);
|
|
272
|
+
if (!/^[1-9]\d*$/.test(suffix) || Number(suffix) < 2) return undefined;
|
|
273
|
+
}
|
|
274
|
+
return vendorForFamily(family);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Resolve the exact managed-catalog key for a request: never borrow a neighbouring model's price. */
|
|
278
|
+
function resolveSourceModelId(
|
|
279
|
+
canonicalAccountId: string,
|
|
280
|
+
requestedModel: string,
|
|
281
|
+
): string | undefined {
|
|
282
|
+
const author = accountAuthor(canonicalAccountId);
|
|
283
|
+
if (
|
|
284
|
+
author === undefined ||
|
|
285
|
+
requestedModel.length === 0 ||
|
|
286
|
+
requestedModel.length > MAX_MODEL_ID_LENGTH
|
|
287
|
+
) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
const sourceId = requestedModel.includes("/")
|
|
291
|
+
? requestedModel
|
|
292
|
+
: `${author}/${requestedModel}`;
|
|
293
|
+
if (!sourceId.startsWith(`${author}/`)) return undefined;
|
|
294
|
+
return sourceId;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Exact only: never borrow a neighbouring model's price. */
|
|
298
|
+
export function lookupApiRate(
|
|
299
|
+
snapshot: ApiRateSnapshot,
|
|
300
|
+
canonicalAccountId: string,
|
|
301
|
+
requestedModel: string,
|
|
302
|
+
): ApiModelRate | undefined {
|
|
303
|
+
const sourceId = resolveSourceModelId(canonicalAccountId, requestedModel);
|
|
304
|
+
return sourceId === undefined ? undefined : snapshot.rates[sourceId];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Exact only: look up one model's cost in Pi's installed catalog snapshot.
|
|
309
|
+
* This is the authoritative tier metadata source; `lookupApiRate` above only
|
|
310
|
+
* extends coverage for models this catalog lacks.
|
|
311
|
+
*/
|
|
312
|
+
export function lookupPiCatalogCost(
|
|
313
|
+
snapshot: PiCatalogSnapshot,
|
|
314
|
+
canonicalAccountId: string,
|
|
315
|
+
requestedModel: string,
|
|
316
|
+
): PiModelCost | undefined {
|
|
317
|
+
const sourceId = resolveSourceModelId(canonicalAccountId, requestedModel);
|
|
318
|
+
return sourceId === undefined
|
|
319
|
+
? undefined
|
|
320
|
+
: snapshot.costsBySourceModelId[sourceId];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function validTokenCount(value: number): boolean {
|
|
324
|
+
return Number.isFinite(value) && value >= 0 && Number.isSafeInteger(value);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Price one retained token tuple without consulting or replacing retained cost. */
|
|
328
|
+
export function estimateApiEquivalentCost(
|
|
329
|
+
tokens: EstimateTokens,
|
|
330
|
+
rate: ApiModelRate,
|
|
331
|
+
rateAsOfMs: number,
|
|
332
|
+
): ApiEquivalentEstimate {
|
|
333
|
+
const longWrite = tokens.cacheWrite1h ?? 0;
|
|
334
|
+
if (
|
|
335
|
+
![
|
|
336
|
+
tokens.input,
|
|
337
|
+
tokens.output,
|
|
338
|
+
tokens.cacheRead,
|
|
339
|
+
tokens.cacheWrite,
|
|
340
|
+
longWrite,
|
|
341
|
+
].every(validTokenCount) ||
|
|
342
|
+
longWrite > tokens.cacheWrite ||
|
|
343
|
+
!Number.isFinite(rateAsOfMs)
|
|
344
|
+
) {
|
|
345
|
+
return { status: "unpriced", reason: "invalid-input" };
|
|
346
|
+
}
|
|
347
|
+
const shortWrite = tokens.cacheWrite - longWrite;
|
|
348
|
+
if (tokens.cacheRead > 0 && rate.cacheReadPerToken === undefined) {
|
|
349
|
+
return { status: "unpriced", reason: "missing-cache-read-rate" };
|
|
350
|
+
}
|
|
351
|
+
if (shortWrite > 0 && rate.cacheWritePerToken === undefined) {
|
|
352
|
+
return { status: "unpriced", reason: "missing-cache-write-rate" };
|
|
353
|
+
}
|
|
354
|
+
if (longWrite > 0 && rate.cacheWrite1hPerToken === undefined) {
|
|
355
|
+
return { status: "unpriced", reason: "missing-cache-write-1h-rate" };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const components = {
|
|
359
|
+
inputUsd: tokens.input * rate.inputPerToken,
|
|
360
|
+
outputUsd: tokens.output * rate.outputPerToken,
|
|
361
|
+
cacheReadUsd: tokens.cacheRead * (rate.cacheReadPerToken ?? 0),
|
|
362
|
+
cacheWriteUsd:
|
|
363
|
+
shortWrite * (rate.cacheWritePerToken ?? 0) +
|
|
364
|
+
longWrite * (rate.cacheWrite1hPerToken ?? 0),
|
|
365
|
+
};
|
|
366
|
+
const totalUsd =
|
|
367
|
+
components.inputUsd +
|
|
368
|
+
components.outputUsd +
|
|
369
|
+
components.cacheReadUsd +
|
|
370
|
+
components.cacheWriteUsd;
|
|
371
|
+
if (!Number.isFinite(totalUsd)) {
|
|
372
|
+
return { status: "unpriced", reason: "estimate-overflow" };
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
status: "priced",
|
|
376
|
+
totalUsd,
|
|
377
|
+
rateAsOfMs,
|
|
378
|
+
sourceModelId: rate.sourceModelId,
|
|
379
|
+
components,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Bounded, human-readable limits disclosed on every Pi-installed-catalog-priced digest row. */
|
|
384
|
+
export const PI_CATALOG_METHOD_LIMITS =
|
|
385
|
+
"Priced under this response's own token-volume tier from Pi's installed model catalog (model.cost.tiers matched by inputTokensAbove), independent of Pi's retained cost estimate.";
|
|
386
|
+
|
|
387
|
+
/** Bounded, human-readable limits disclosed when OpenRouter extends coverage for a model the installed catalog lacks. */
|
|
388
|
+
export const OPENROUTER_METHOD_LIMITS =
|
|
389
|
+
"Priced from the cached OpenRouter public rate snapshot because this model is absent from Pi's installed catalog; OpenRouter publishes one flat rate with no per-volume tiering.";
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Select the tier whose rates apply to one response, exactly mirroring
|
|
393
|
+
* Pi's own `calculateCost`: the highest `inputTokensAbove` threshold that
|
|
394
|
+
* `input + cacheRead + cacheWrite` strictly exceeds. No matching tier keeps
|
|
395
|
+
* the model's base rates and an undefined tier boundary.
|
|
396
|
+
*/
|
|
397
|
+
export function selectPiCostTier(
|
|
398
|
+
cost: PiModelCost,
|
|
399
|
+
tokens: EstimateTokens,
|
|
400
|
+
): {
|
|
401
|
+
readonly rates: PiModelCostRates;
|
|
402
|
+
readonly tierInputTokensAbove: number | undefined;
|
|
403
|
+
} {
|
|
404
|
+
const inputTokens = tokens.input + tokens.cacheRead + tokens.cacheWrite;
|
|
405
|
+
let rates: PiModelCostRates = cost;
|
|
406
|
+
let matchedThreshold = -1;
|
|
407
|
+
let tierInputTokensAbove: number | undefined;
|
|
408
|
+
for (const tier of cost.tiers ?? []) {
|
|
409
|
+
if (
|
|
410
|
+
inputTokens > tier.inputTokensAbove &&
|
|
411
|
+
tier.inputTokensAbove > matchedThreshold
|
|
412
|
+
) {
|
|
413
|
+
rates = tier;
|
|
414
|
+
matchedThreshold = tier.inputTokensAbove;
|
|
415
|
+
tierInputTokensAbove = tier.inputTokensAbove;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { rates, tierInputTokensAbove };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export type PiCatalogApiEquivalentEstimate =
|
|
422
|
+
| {
|
|
423
|
+
readonly status: "priced";
|
|
424
|
+
readonly totalUsd: number;
|
|
425
|
+
readonly rateAsOfMs: number;
|
|
426
|
+
readonly sourceModelId: string;
|
|
427
|
+
readonly tierInputTokensAbove: number | undefined;
|
|
428
|
+
readonly components: {
|
|
429
|
+
readonly inputUsd: number;
|
|
430
|
+
readonly outputUsd: number;
|
|
431
|
+
readonly cacheReadUsd: number;
|
|
432
|
+
readonly cacheWriteUsd: number;
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
| { readonly status: "unpriced"; readonly reason: string };
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Price one retained token tuple against Pi's installed catalog, exactly
|
|
439
|
+
* matching Pi's own `calculateCost` formula (including the 2x-base-input
|
|
440
|
+
* one-hour cache write surcharge). Never consults or replaces retained cost.
|
|
441
|
+
*/
|
|
442
|
+
export function estimatePiCatalogCost(
|
|
443
|
+
tokens: EstimateTokens,
|
|
444
|
+
cost: PiModelCost,
|
|
445
|
+
rateAsOfMs: number,
|
|
446
|
+
): PiCatalogApiEquivalentEstimate {
|
|
447
|
+
const longWrite = tokens.cacheWrite1h ?? 0;
|
|
448
|
+
if (
|
|
449
|
+
![
|
|
450
|
+
tokens.input,
|
|
451
|
+
tokens.output,
|
|
452
|
+
tokens.cacheRead,
|
|
453
|
+
tokens.cacheWrite,
|
|
454
|
+
longWrite,
|
|
455
|
+
].every(validTokenCount) ||
|
|
456
|
+
longWrite > tokens.cacheWrite ||
|
|
457
|
+
!Number.isFinite(rateAsOfMs)
|
|
458
|
+
) {
|
|
459
|
+
return { status: "unpriced", reason: "invalid-input" };
|
|
460
|
+
}
|
|
461
|
+
const { rates, tierInputTokensAbove } = selectPiCostTier(cost, tokens);
|
|
462
|
+
const shortWrite = tokens.cacheWrite - longWrite;
|
|
463
|
+
const components = {
|
|
464
|
+
inputUsd: (tokens.input * rates.input) / 1_000_000,
|
|
465
|
+
outputUsd: (tokens.output * rates.output) / 1_000_000,
|
|
466
|
+
cacheReadUsd: (tokens.cacheRead * rates.cacheRead) / 1_000_000,
|
|
467
|
+
cacheWriteUsd:
|
|
468
|
+
(rates.cacheWrite * shortWrite + rates.input * 2 * longWrite) / 1_000_000,
|
|
469
|
+
};
|
|
470
|
+
const totalUsd =
|
|
471
|
+
components.inputUsd +
|
|
472
|
+
components.outputUsd +
|
|
473
|
+
components.cacheReadUsd +
|
|
474
|
+
components.cacheWriteUsd;
|
|
475
|
+
if (!Number.isFinite(totalUsd)) {
|
|
476
|
+
return { status: "unpriced", reason: "estimate-overflow" };
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
status: "priced",
|
|
480
|
+
totalUsd,
|
|
481
|
+
rateAsOfMs,
|
|
482
|
+
sourceModelId: cost.sourceModelId,
|
|
483
|
+
tierInputTokensAbove,
|
|
484
|
+
components,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function finiteNonNegativeNumber(value: unknown): value is number {
|
|
489
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function parsePiModelCostRates(value: unknown): PiModelCostRates | undefined {
|
|
493
|
+
if (
|
|
494
|
+
!isRecord(value) ||
|
|
495
|
+
![value.input, value.output, value.cacheRead, value.cacheWrite].every(
|
|
496
|
+
finiteNonNegativeNumber,
|
|
497
|
+
)
|
|
498
|
+
) {
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
input: value.input as number,
|
|
503
|
+
output: value.output as number,
|
|
504
|
+
cacheRead: value.cacheRead as number,
|
|
505
|
+
cacheWrite: value.cacheWrite as number,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function parsePiModelCostTier(value: unknown): PiModelCostTier | undefined {
|
|
510
|
+
const rates = parsePiModelCostRates(value);
|
|
511
|
+
if (rates === undefined || !isRecord(value)) return undefined;
|
|
512
|
+
if (!finiteNonNegativeNumber(value.inputTokensAbove)) return undefined;
|
|
513
|
+
return Object.freeze({ ...rates, inputTokensAbove: value.inputTokensAbove });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function parsePiModelCost(
|
|
517
|
+
value: unknown,
|
|
518
|
+
sourceModelId: string,
|
|
519
|
+
): PiModelCost | undefined {
|
|
520
|
+
const rates = parsePiModelCostRates(value);
|
|
521
|
+
if (rates === undefined || !isRecord(value)) return undefined;
|
|
522
|
+
if (value.tiers === undefined) {
|
|
523
|
+
return Object.freeze({ sourceModelId, ...rates });
|
|
524
|
+
}
|
|
525
|
+
if (!Array.isArray(value.tiers) || value.tiers.length > MAX_TIER_COUNT) {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
const tiers: PiModelCostTier[] = [];
|
|
529
|
+
for (const candidate of value.tiers) {
|
|
530
|
+
const tier = parsePiModelCostTier(candidate);
|
|
531
|
+
if (tier === undefined) return undefined;
|
|
532
|
+
tiers.push(tier);
|
|
533
|
+
}
|
|
534
|
+
return Object.freeze({
|
|
535
|
+
sourceModelId,
|
|
536
|
+
...rates,
|
|
537
|
+
tiers: Object.freeze(tiers),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Re-validate a caller-supplied installed-catalog snapshot before any cost is trusted. */
|
|
542
|
+
export function parsePiCatalogSnapshot(value: unknown): PiCatalogSnapshot {
|
|
543
|
+
if (
|
|
544
|
+
!isRecord(value) ||
|
|
545
|
+
value.schemaVersion !== 1 ||
|
|
546
|
+
value.source !== "pi-installed-catalog" ||
|
|
547
|
+
typeof value.catalogVersion !== "string" ||
|
|
548
|
+
value.catalogVersion.length === 0 ||
|
|
549
|
+
value.catalogVersion.length > MAX_MODEL_ID_LENGTH ||
|
|
550
|
+
typeof value.capturedAtMs !== "number" ||
|
|
551
|
+
!Number.isFinite(value.capturedAtMs) ||
|
|
552
|
+
value.capturedAtMs < 0 ||
|
|
553
|
+
!isRecord(value.costsBySourceModelId)
|
|
554
|
+
) {
|
|
555
|
+
throw new TypeError("Pi installed-catalog snapshot is invalid.");
|
|
556
|
+
}
|
|
557
|
+
const entries = Object.entries(value.costsBySourceModelId);
|
|
558
|
+
if (entries.length > MAX_MODEL_COUNT) {
|
|
559
|
+
throw new RangeError("Pi installed-catalog snapshot exceeds the model limit.");
|
|
560
|
+
}
|
|
561
|
+
const costsBySourceModelId: Record<string, PiModelCost> = {};
|
|
562
|
+
for (const [modelId, candidate] of entries) {
|
|
563
|
+
if (!managedModelId(modelId)) {
|
|
564
|
+
throw new TypeError("Pi installed-catalog snapshot has an invalid model id.");
|
|
565
|
+
}
|
|
566
|
+
const cost = parsePiModelCost(candidate, modelId);
|
|
567
|
+
if (cost === undefined) {
|
|
568
|
+
throw new TypeError("Pi installed-catalog snapshot has an invalid model cost.");
|
|
569
|
+
}
|
|
570
|
+
costsBySourceModelId[modelId] = cost;
|
|
571
|
+
}
|
|
572
|
+
return Object.freeze({
|
|
573
|
+
schemaVersion: 1,
|
|
574
|
+
source: "pi-installed-catalog",
|
|
575
|
+
catalogVersion: value.catalogVersion,
|
|
576
|
+
capturedAtMs: value.capturedAtMs,
|
|
577
|
+
costsBySourceModelId: Object.freeze(costsBySourceModelId),
|
|
578
|
+
});
|
|
579
|
+
}
|