@deeprelay/mcp 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -1
- package/dist/auth.d.ts +52 -0
- package/dist/auth.js +122 -0
- package/dist/auth.js.map +1 -0
- package/dist/errors.d.ts +83 -0
- package/dist/errors.js +307 -0
- package/dist/errors.js.map +1 -0
- package/dist/guardrail/budget.d.ts +95 -0
- package/dist/guardrail/budget.js +225 -0
- package/dist/guardrail/budget.js.map +1 -0
- package/dist/guardrail/elicitation.d.ts +12 -0
- package/dist/guardrail/elicitation.js +79 -0
- package/dist/guardrail/elicitation.js.map +1 -0
- package/dist/guardrail/estimate.d.ts +67 -0
- package/dist/guardrail/estimate.js +171 -0
- package/dist/guardrail/estimate.js.map +1 -0
- package/dist/guardrail/handshake.d.ts +32 -0
- package/dist/guardrail/handshake.js +164 -0
- package/dist/guardrail/handshake.js.map +1 -0
- package/dist/guardrail/types.d.ts +61 -0
- package/dist/guardrail/types.js +10 -0
- package/dist/guardrail/types.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/roster.d.ts +36 -0
- package/dist/roster.js +130 -0
- package/dist/roster.js.map +1 -0
- package/dist/tools/crypto-deposit-gate.d.ts +21 -0
- package/dist/tools/crypto-deposit-gate.js +91 -0
- package/dist/tools/crypto-deposit-gate.js.map +1 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +108 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/readonly/define.d.ts +32 -0
- package/dist/tools/readonly/define.js +64 -0
- package/dist/tools/readonly/define.js.map +1 -0
- package/dist/tools/readonly/index.d.ts +7 -0
- package/dist/tools/readonly/index.js +292 -0
- package/dist/tools/readonly/index.js.map +1 -0
- package/dist/tools/readonly/video-reads.d.ts +11 -0
- package/dist/tools/readonly/video-reads.js +170 -0
- package/dist/tools/readonly/video-reads.js.map +1 -0
- package/dist/tools/result.d.ts +22 -0
- package/dist/tools/result.js +63 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/tools/spend/define.d.ts +93 -0
- package/dist/tools/spend/define.js +286 -0
- package/dist/tools/spend/define.js.map +1 -0
- package/dist/tools/spend/index.d.ts +11 -0
- package/dist/tools/spend/index.js +163 -0
- package/dist/tools/spend/index.js.map +1 -0
- package/dist/tools/spend/media.d.ts +9 -0
- package/dist/tools/spend/media.js +164 -0
- package/dist/tools/spend/media.js.map +1 -0
- package/dist/tools/spend/pricing.d.ts +17 -0
- package/dist/tools/spend/pricing.js +58 -0
- package/dist/tools/spend/pricing.js.map +1 -0
- package/dist/tools/types.d.ts +45 -0
- package/dist/tools/types.js +3 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/write/define.d.ts +60 -0
- package/dist/tools/write/define.js +110 -0
- package/dist/tools/write/define.js.map +1 -0
- package/dist/tools/write/index.d.ts +12 -0
- package/dist/tools/write/index.js +159 -0
- package/dist/tools/write/index.js.map +1 -0
- package/dist/tools/write/payment-links.d.ts +3 -0
- package/dist/tools/write/payment-links.js +161 -0
- package/dist/tools/write/payment-links.js.map +1 -0
- package/dist/tools/write/spending-limit.d.ts +2 -0
- package/dist/tools/write/spending-limit.js +229 -0
- package/dist/tools/write/spending-limit.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +36 -0
- package/dist/version.js.map +1 -0
- package/package.json +39 -3
package/dist/errors.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuoteUnavailableError = exports.CREDENTIAL_KEYS = exports.REDACTED = void 0;
|
|
4
|
+
exports.redactSecrets = redactSecrets;
|
|
5
|
+
exports.sanitizeCredentialFields = sanitizeCredentialFields;
|
|
6
|
+
exports.httpStatusOf = httpStatusOf;
|
|
7
|
+
exports.mapSdkError = mapSdkError;
|
|
8
|
+
exports.readErrorEnvelope = readErrorEnvelope;
|
|
9
|
+
exports.mapSdkErrorAsync = mapSdkErrorAsync;
|
|
10
|
+
// The error and redaction funnel every tool result goes through
|
|
11
|
+
// (Phase 104, D-12: authored fresh against the invariants below).
|
|
12
|
+
//
|
|
13
|
+
// 1. Agent-legible failures. The SDK throws the same ResponseError for every
|
|
14
|
+
// non-2xx. An agent that cannot tell "your key is bad" from "retry later"
|
|
15
|
+
// loops or gives up, so each status becomes one sentence that names the
|
|
16
|
+
// cause and the fix, and names deeprelay as the only party involved.
|
|
17
|
+
// 2. No credential reaches the agent. Response bodies are untrusted text.
|
|
18
|
+
// Only a short summary field is ever lifted out of one, and every string
|
|
19
|
+
// this module returns has been through redactSecrets().
|
|
20
|
+
// 3. No upstream provider name reaches the agent (INFPRIV-06). The server
|
|
21
|
+
// scrubs provider names from every /v1/* error before it leaves the wire,
|
|
22
|
+
// and that scrub is authoritative. This package ships to npm, so it
|
|
23
|
+
// deliberately carries no list of upstream names of its own; nothing here
|
|
24
|
+
// adds a provider name to a sentence.
|
|
25
|
+
//
|
|
26
|
+
// This module logs nothing. On a stdio MCP server stdout is the JSON-RPC
|
|
27
|
+
// channel, and errors travel back to the caller as return values only.
|
|
28
|
+
//
|
|
29
|
+
// It is a leaf: it imports nothing from auth.ts, because auth.ts imports
|
|
30
|
+
// redactSecrets from here. The two env-var names are restated below and the
|
|
31
|
+
// tests assert they agree with auth.ts.
|
|
32
|
+
const sdk_1 = require("@deeprelay/sdk");
|
|
33
|
+
/** What replaces anything credential-shaped. */
|
|
34
|
+
exports.REDACTED = "[REDACTED]";
|
|
35
|
+
const KEY_ENV = "DEEPRELAY_API_KEY";
|
|
36
|
+
const BASE_ENV = "DEEPRELAY_API_BASE";
|
|
37
|
+
/** Named in network messages when no override is set. Mirrors auth.ts PROD_API_BASE. */
|
|
38
|
+
const DEFAULT_BASE_FOR_MESSAGES = "https://api.deeprelay.ai/v1";
|
|
39
|
+
/**
|
|
40
|
+
* deeprelay credentials: `deeprelay_live_…` and `deeprelay_test_…` keys.
|
|
41
|
+
* Narrow on purpose: other `deeprelay_`-prefixed strings (request ids such as
|
|
42
|
+
* `deeprelay_inf_…`, this server's own tool names) are not secrets and support
|
|
43
|
+
* needs them intact. Case-sensitive, so the env-var NAME `DEEPRELAY_API_KEY`
|
|
44
|
+
* in a remedy sentence is not itself redacted.
|
|
45
|
+
*/
|
|
46
|
+
const TOKEN_SHAPE = /deeprelay_(live|test)_[A-Za-z0-9_]+/g;
|
|
47
|
+
/**
|
|
48
|
+
* Strip credentials out of arbitrary text. Two passes, because either alone
|
|
49
|
+
* leaves a hole:
|
|
50
|
+
*
|
|
51
|
+
* 1. the configured key's literal value, which catches a key of any shape
|
|
52
|
+
* (a placeholder, another vendor's key pasted by mistake);
|
|
53
|
+
* 2. the deeprelay token shape, which catches a DIFFERENT key echoed back by
|
|
54
|
+
* the API, one this process never held and so cannot match by value.
|
|
55
|
+
*/
|
|
56
|
+
function redactSecrets(text, env = process.env) {
|
|
57
|
+
let out = text;
|
|
58
|
+
const key = env[KEY_ENV];
|
|
59
|
+
if (key !== undefined && key.trim() !== "") {
|
|
60
|
+
out = out.split(key).join(exports.REDACTED);
|
|
61
|
+
}
|
|
62
|
+
return out.replace(TOKEN_SHAPE, exports.REDACTED);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Field names whose string VALUES are credential material. Both spellings,
|
|
66
|
+
* because SDK models are camelCase and raw bodies are snake_case. Exact match:
|
|
67
|
+
* a substring rule would also swallow ordinary fields such as `secret_rotated_at`
|
|
68
|
+
* style metadata or URLs the user needs.
|
|
69
|
+
*
|
|
70
|
+
* The webhook create tool reveals the new endpoint's signing secret exactly
|
|
71
|
+
* once through an explicit reveal path in tools/result.ts. Nothing else ever
|
|
72
|
+
* bypasses this set.
|
|
73
|
+
*/
|
|
74
|
+
exports.CREDENTIAL_KEYS = new Set([
|
|
75
|
+
"secret",
|
|
76
|
+
"signing_secret",
|
|
77
|
+
"signingSecret",
|
|
78
|
+
"api_key",
|
|
79
|
+
"apiKey",
|
|
80
|
+
"access_token",
|
|
81
|
+
"accessToken",
|
|
82
|
+
"authorization",
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* Return a copy of `value` with every string under a CREDENTIAL_KEYS field
|
|
86
|
+
* replaced by REDACTED, at any depth and inside arrays. The input is never
|
|
87
|
+
* mutated. Non-string credential values (null, absent) are left alone so the
|
|
88
|
+
* agent is not told a secret exists where none does. Class instances such as
|
|
89
|
+
* Date are returned whole rather than flattened.
|
|
90
|
+
*/
|
|
91
|
+
function sanitizeCredentialFields(value) {
|
|
92
|
+
if (Array.isArray(value)) {
|
|
93
|
+
return value.map((item) => sanitizeCredentialFields(item));
|
|
94
|
+
}
|
|
95
|
+
if (typeof value !== "object" || value === null) {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
const proto = Object.getPrototypeOf(value);
|
|
99
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const [field, item] of Object.entries(value)) {
|
|
104
|
+
out[field] =
|
|
105
|
+
exports.CREDENTIAL_KEYS.has(field) && typeof item === "string"
|
|
106
|
+
? exports.REDACTED
|
|
107
|
+
: sanitizeCredentialFields(item);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A spend or write tool's quote step refusing to price a request. Throwing is
|
|
113
|
+
* what keeps a guardrail from minting a confirmation token for something that
|
|
114
|
+
* has no quote: no quote, no token, no execution. The mapper keeps this
|
|
115
|
+
* error's own wording.
|
|
116
|
+
*/
|
|
117
|
+
class QuoteUnavailableError extends Error {
|
|
118
|
+
constructor(message) {
|
|
119
|
+
super(message);
|
|
120
|
+
this.name = "QuoteUnavailableError";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.QuoteUnavailableError = QuoteUnavailableError;
|
|
124
|
+
/**
|
|
125
|
+
* The HTTP status of an SDK error, or undefined for anything that never got a
|
|
126
|
+
* response. Duck-typed as a fallback so a bundling or version skew that breaks
|
|
127
|
+
* instanceof cannot change behaviour. Exported so callers can branch on a
|
|
128
|
+
* status (e.g. a 404 spending limit meaning "none configured") consistently
|
|
129
|
+
* with the mapper.
|
|
130
|
+
*/
|
|
131
|
+
function httpStatusOf(err) {
|
|
132
|
+
if (err instanceof sdk_1.ResponseError) {
|
|
133
|
+
return err.response.status;
|
|
134
|
+
}
|
|
135
|
+
const status = err?.response?.status;
|
|
136
|
+
return typeof status === "number" ? status : undefined;
|
|
137
|
+
}
|
|
138
|
+
const MAX_DETAIL_LENGTH = 300;
|
|
139
|
+
function oneLine(text) {
|
|
140
|
+
return text.replace(/\s+/g, " ").trim();
|
|
141
|
+
}
|
|
142
|
+
/** Prepare API-supplied text for embedding: one line, bounded. */
|
|
143
|
+
function cleanDetail(text) {
|
|
144
|
+
if (text === undefined) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const cleaned = oneLine(text);
|
|
148
|
+
if (cleaned === "") {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
return cleaned.length > MAX_DETAIL_LENGTH ? `${cleaned.slice(0, MAX_DETAIL_LENGTH - 1)}…` : cleaned;
|
|
152
|
+
}
|
|
153
|
+
function withDetails(sentence, details) {
|
|
154
|
+
const cleaned = cleanDetail(details);
|
|
155
|
+
return cleaned === undefined ? sentence : `${sentence} ${cleaned}`;
|
|
156
|
+
}
|
|
157
|
+
/** The base URL named in a network message, with any userinfo stripped. */
|
|
158
|
+
function baseForMessages(env) {
|
|
159
|
+
const raw = env[BASE_ENV]?.trim();
|
|
160
|
+
if (raw === undefined || raw === "") {
|
|
161
|
+
return DEFAULT_BASE_FOR_MESSAGES;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const url = new URL(raw);
|
|
165
|
+
url.username = "";
|
|
166
|
+
url.password = "";
|
|
167
|
+
return url.toString().replace(/\/+$/, "");
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return `the configured ${BASE_ENV}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function formatZodIssues(err) {
|
|
174
|
+
const issues = err.issues;
|
|
175
|
+
if (!Array.isArray(issues) || issues.length === 0) {
|
|
176
|
+
return err instanceof Error ? err.message : "the arguments did not match the tool's schema";
|
|
177
|
+
}
|
|
178
|
+
return issues
|
|
179
|
+
.map((issue) => {
|
|
180
|
+
const { path, message } = issue;
|
|
181
|
+
const where = Array.isArray(path) && path.length > 0 ? path.map(String).join(".") : "(root)";
|
|
182
|
+
return `${where}: ${typeof message === "string" ? message : "invalid"}`;
|
|
183
|
+
})
|
|
184
|
+
.join("; ");
|
|
185
|
+
}
|
|
186
|
+
function statusSentence(status, context, details, code) {
|
|
187
|
+
switch (status) {
|
|
188
|
+
case 401:
|
|
189
|
+
// Deliberately carries no response detail: a 401 is about the
|
|
190
|
+
// credential, and echoing the body is the likeliest leak path.
|
|
191
|
+
return `deeprelay API key invalid or expired — check the ${KEY_ENV} environment variable in your MCP client configuration (while ${context}).`;
|
|
192
|
+
case 402: {
|
|
193
|
+
const base = `Insufficient balance — add credit at deeprelay.ai, then retry (while ${context}).`;
|
|
194
|
+
const withCode = code === "daily_limit_reached"
|
|
195
|
+
? `${base} Code: daily_limit_reached — the account's daily spending limit has been reached.`
|
|
196
|
+
: base;
|
|
197
|
+
return withDetails(withCode, details);
|
|
198
|
+
}
|
|
199
|
+
case 403:
|
|
200
|
+
return withDetails(`Not permitted — this API key lacks the scope required for ${context}, or the account lacks the org-admin role.`, details);
|
|
201
|
+
case 404:
|
|
202
|
+
return withDetails(`Not found — ${context} referred to something that does not exist or is already gone.`, details);
|
|
203
|
+
case 409:
|
|
204
|
+
return withDetails(`Conflict — ${context} collided with the current state of the resource.`, details);
|
|
205
|
+
case 422:
|
|
206
|
+
return withDetails(`Invalid request — the deeprelay API rejected ${context}.`, details);
|
|
207
|
+
case 429:
|
|
208
|
+
return `Rate limited — wait a moment and retry (while ${context}). Do not retry in a tight loop.`;
|
|
209
|
+
default:
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
if (status >= 500) {
|
|
213
|
+
return `deeprelay API error (${status}) while ${context} — retry later; if it persists check https://deeprelay.ai/status.`;
|
|
214
|
+
}
|
|
215
|
+
if (status >= 400) {
|
|
216
|
+
return withDetails(`Request rejected by the deeprelay API (HTTP ${status}) while ${context}.`, details);
|
|
217
|
+
}
|
|
218
|
+
return `Unexpected response from the deeprelay API (HTTP ${status}) while ${context}.`;
|
|
219
|
+
}
|
|
220
|
+
function noStatusSentence(err, context, env, details) {
|
|
221
|
+
const name = err instanceof Error ? err.name : undefined;
|
|
222
|
+
switch (name) {
|
|
223
|
+
case "ZodError":
|
|
224
|
+
return `Invalid arguments while ${context}: ${cleanDetail(formatZodIssues(err)) ?? "see the tool schema"}`;
|
|
225
|
+
case "StartupConfigError":
|
|
226
|
+
case "QuoteUnavailableError":
|
|
227
|
+
// Already a diagnosis in its own words; keep it.
|
|
228
|
+
return `${cleanDetail(err.message) ?? "Request refused."} (while ${context})`;
|
|
229
|
+
case "RequiredError": {
|
|
230
|
+
const field = err.field;
|
|
231
|
+
return `Missing required parameter${typeof field === "string" ? ` "${field}"` : ""} while ${context}.`;
|
|
232
|
+
}
|
|
233
|
+
case "FetchError":
|
|
234
|
+
case "AbortError":
|
|
235
|
+
case "TimeoutError":
|
|
236
|
+
return networkSentence(context, env);
|
|
237
|
+
default:
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (name === "TypeError" && /fetch/i.test(err.message)) {
|
|
241
|
+
return networkSentence(context, env);
|
|
242
|
+
}
|
|
243
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
244
|
+
return withDetails(`Unexpected error while ${context}.`, details ?? raw);
|
|
245
|
+
}
|
|
246
|
+
function networkSentence(context, env) {
|
|
247
|
+
return `Could not reach the deeprelay API at ${baseForMessages(env)} while ${context} — check ${BASE_ENV} and your network.`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Turn any thrown value into an agent-legible Error. Synchronous so the
|
|
251
|
+
* mapping is directly testable; tool handlers normally call mapSdkErrorAsync,
|
|
252
|
+
* which reads the response body first.
|
|
253
|
+
*
|
|
254
|
+
* `context` names what was being attempted ("listing models"), so a failure
|
|
255
|
+
* several calls deep stays attributable.
|
|
256
|
+
*/
|
|
257
|
+
function mapSdkError(err, context, options = {}) {
|
|
258
|
+
const env = options.env ?? process.env;
|
|
259
|
+
const status = httpStatusOf(err);
|
|
260
|
+
const sentence = status === undefined
|
|
261
|
+
? noStatusSentence(err, context, env, options.details)
|
|
262
|
+
: statusSentence(status, context, options.details, options.code);
|
|
263
|
+
return new Error(redactSecrets(oneLine(sentence), env));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Read `{ code, message }` out of an SDK error's response body. Accepts both
|
|
267
|
+
* shapes the API returns: the OpenAI envelope `{ error: { code, message } }`
|
|
268
|
+
* and problem+json `{ code, detail }`. Fails closed: anything that is not
|
|
269
|
+
* recognisably one of those yields an empty envelope, never the raw body.
|
|
270
|
+
* Reads a clone, so the caller's Response is left unconsumed.
|
|
271
|
+
*/
|
|
272
|
+
async function readErrorEnvelope(err) {
|
|
273
|
+
const response = err?.response;
|
|
274
|
+
if (response === undefined || response === null || typeof response.clone !== "function") {
|
|
275
|
+
return {};
|
|
276
|
+
}
|
|
277
|
+
let body;
|
|
278
|
+
try {
|
|
279
|
+
body = JSON.parse(await response.clone().text());
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
if (typeof body !== "object" || body === null) {
|
|
285
|
+
return {};
|
|
286
|
+
}
|
|
287
|
+
const record = body;
|
|
288
|
+
const nested = record.error;
|
|
289
|
+
const source = typeof nested === "object" && nested !== null ? nested : record;
|
|
290
|
+
const code = typeof source.code === "string" && source.code !== "" ? source.code : undefined;
|
|
291
|
+
const messageField = source === record ? (record.detail ?? record.message) : source.message;
|
|
292
|
+
const message = typeof messageField === "string" && messageField.trim() !== "" ? messageField : undefined;
|
|
293
|
+
return { code, message };
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The funnel tool handlers call: read the body once, then map. A 401 never
|
|
297
|
+
* uses the body (see statusSentence).
|
|
298
|
+
*/
|
|
299
|
+
async function mapSdkErrorAsync(err, context, env = process.env) {
|
|
300
|
+
const status = httpStatusOf(err);
|
|
301
|
+
if (status === undefined || status === 401) {
|
|
302
|
+
return mapSdkError(err, context, { env });
|
|
303
|
+
}
|
|
304
|
+
const envelope = await readErrorEnvelope(err);
|
|
305
|
+
return mapSdkError(err, context, { env, details: envelope.message, code: envelope.code });
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AAoDA,sCAOC;AA8BD,4DAmBC;AAsBD,oCAMC;AAsJD,kCAQC;AASD,8CAsBC;AAMD,4CAOC;AAlVD,gEAAgE;AAChE,kEAAkE;AAClE,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,6EAA6E;AAC7E,4DAA4D;AAC5D,2EAA2E;AAC3E,8EAA8E;AAC9E,wEAAwE;AACxE,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,EAAE;AACF,yEAAyE;AACzE,4EAA4E;AAC5E,wCAAwC;AACxC,wCAA+C;AAI/C,gDAAgD;AACnC,QAAA,QAAQ,GAAG,YAAY,CAAC;AAErC,MAAM,OAAO,GAAG,mBAAmB,CAAC;AACpC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AACtC,wFAAwF;AACxF,MAAM,yBAAyB,GAAG,6BAA6B,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,sCAAsC,CAAC;AAE3D;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,IAAY,EAAE,MAAW,OAAO,CAAC,GAAG;IAChE,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC3C,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAQ,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,gBAAQ,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;GASG;AACU,QAAA,eAAe,GAAwB,IAAI,GAAG,CAAC;IAC1D,QAAQ;IACR,gBAAgB;IAChB,eAAe;IACf,SAAS;IACT,QAAQ;IACR,cAAc;IACd,aAAa;IACb,eAAe;CAChB,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,SAAgB,wBAAwB,CAAC,KAAc;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAY,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACpD,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC7E,GAAG,CAAC,KAAK,CAAC;YACR,uBAAe,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,OAAO,IAAI,KAAK,QAAQ;gBACpD,CAAC,CAAC,gBAAQ;gBACV,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED;;;;;;GAMG;AACH,SAAgB,YAAY,CAAC,GAAY;IACvC,IAAI,GAAG,YAAY,mBAAa,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC7B,CAAC;IACD,MAAM,MAAM,GAAI,GAA8D,EAAE,QAAQ,EAAE,MAAM,CAAC;IACjG,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAgBD,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,kEAAkE;AAClE,SAAS,WAAW,CAAC,IAAwB;IAC3C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AACtG,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,OAA2B;IAChE,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC;AACrE,CAAC;AAED,2EAA2E;AAC3E,SAAS,eAAe,CAAC,GAAQ;IAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;IAClC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;QACpC,OAAO,yBAAyB,CAAC;IACnC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;QAClB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,kBAAkB,QAAQ,EAAE,CAAC;IACtC,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,MAAM,MAAM,GAAI,GAA4B,CAAC,MAAM,CAAC;IACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,+CAA+C,CAAC;IAC9F,CAAC;IACD,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAA8C,CAAC;QACzE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC7F,OAAO,GAAG,KAAK,KAAK,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IAC1E,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,OAAe,EAAE,OAA2B,EAAE,IAAwB;IAC5G,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,GAAG;YACN,8DAA8D;YAC9D,+DAA+D;YAC/D,OAAO,oDAAoD,OAAO,iEAAiE,OAAO,IAAI,CAAC;QACjJ,KAAK,GAAG,CAAC,CAAC,CAAC;YACT,MAAM,IAAI,GAAG,wEAAwE,OAAO,IAAI,CAAC;YACjG,MAAM,QAAQ,GACZ,IAAI,KAAK,qBAAqB;gBAC5B,CAAC,CAAC,GAAG,IAAI,mFAAmF;gBAC5F,CAAC,CAAC,IAAI,CAAC;YACX,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;QACD,KAAK,GAAG;YACN,OAAO,WAAW,CAChB,6DAA6D,OAAO,4CAA4C,EAChH,OAAO,CACR,CAAC;QACJ,KAAK,GAAG;YACN,OAAO,WAAW,CAChB,eAAe,OAAO,gEAAgE,EACtF,OAAO,CACR,CAAC;QACJ,KAAK,GAAG;YACN,OAAO,WAAW,CAAC,cAAc,OAAO,mDAAmD,EAAE,OAAO,CAAC,CAAC;QACxG,KAAK,GAAG;YACN,OAAO,WAAW,CAAC,gDAAgD,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC;QAC1F,KAAK,GAAG;YACN,OAAO,iDAAiD,OAAO,kCAAkC,CAAC;QACpG;YACE,MAAM;IACV,CAAC;IACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClB,OAAO,wBAAwB,MAAM,WAAW,OAAO,mEAAmE,CAAC;IAC7H,CAAC;IACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClB,OAAO,WAAW,CAAC,+CAA+C,MAAM,WAAW,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1G,CAAC;IACD,OAAO,oDAAoD,MAAM,WAAW,OAAO,GAAG,CAAC;AACzF,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY,EAAE,OAAe,EAAE,GAAQ,EAAE,OAA2B;IAC5F,MAAM,IAAI,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,UAAU;YACb,OAAO,2BAA2B,OAAO,KAAK,WAAW,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,qBAAqB,EAAE,CAAC;QAC7G,KAAK,oBAAoB,CAAC;QAC1B,KAAK,uBAAuB;YAC1B,iDAAiD;YACjD,OAAO,GAAG,WAAW,CAAE,GAAa,CAAC,OAAO,CAAC,IAAI,kBAAkB,WAAW,OAAO,GAAG,CAAC;QAC3F,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,MAAM,KAAK,GAAI,GAA2B,CAAC,KAAK,CAAC;YACjD,OAAO,6BAA6B,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,UAAU,OAAO,GAAG,CAAC;QACzG,CAAC;QACD,KAAK,YAAY,CAAC;QAClB,KAAK,YAAY,CAAC;QAClB,KAAK,cAAc;YACjB,OAAO,eAAe,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACvC;YACE,MAAM;IACV,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,IAAI,QAAQ,CAAC,IAAI,CAAE,GAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAClE,OAAO,eAAe,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,WAAW,CAAC,0BAA0B,OAAO,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,eAAe,CAAC,OAAe,EAAE,GAAQ;IAChD,OAAO,wCAAwC,eAAe,CAAC,GAAG,CAAC,UAAU,OAAO,YAAY,QAAQ,oBAAoB,CAAC;AAC/H,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,GAAY,EAAE,OAAe,EAAE,UAA2B,EAAE;IACtF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,QAAQ,GACZ,MAAM,KAAK,SAAS;QAClB,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;QACtD,CAAC,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,IAAI,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,iBAAiB,CAAC,GAAY;IAClD,MAAM,QAAQ,GAAI,GAAkD,EAAE,QAAQ,CAAC;IAC/E,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACxF,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,MAAM,MAAM,GACV,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/F,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7F,MAAM,YAAY,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAC5F,MAAM,OAAO,GAAG,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1G,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAY,EAAE,OAAe,EAAE,MAAW,OAAO,CAAC,GAAG;IAC1F,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QAC3C,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC5F,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { BudgetDecision } from "./types.js";
|
|
2
|
+
/** Micro-cents (1e-6 cent) per cent. Mirrors catalog.MicroCentsPerCent. */
|
|
3
|
+
export declare const MICRO_CENTS_PER_CENT = 1000000;
|
|
4
|
+
/** Default session budget: $5.00 (D-08). */
|
|
5
|
+
export declare const SESSION_BUDGET_DEFAULT_CENTS = 500;
|
|
6
|
+
/** Upper bound for a user-set session budget: $100.00. */
|
|
7
|
+
export declare const SESSION_BUDGET_MAX_CENTS = 10000;
|
|
8
|
+
/** Default per-call ceiling: $0.50 (D-08). */
|
|
9
|
+
export declare const PER_CALL_CEILING_CENTS = 50;
|
|
10
|
+
export declare const PER_CALL_CEILING_MICRO_CENTS: number;
|
|
11
|
+
/**
|
|
12
|
+
* Round one invocation's raw cost UP to whole cents, expressed in micro-cents.
|
|
13
|
+
*
|
|
14
|
+
* The server bills each invocation as ceil(microcents / 1e6) cents
|
|
15
|
+
* (costCentsFor / db.CentsFromMicroCentsCeil). The rounding happens per call and
|
|
16
|
+
* always upward, so its error grows with CALL COUNT. Aggregating first and
|
|
17
|
+
* rounding once would under-count a session of sub-cent calls by up to 100x
|
|
18
|
+
* (the D-82-A regression). Every estimate and every actual goes through this
|
|
19
|
+
* function one invocation at a time.
|
|
20
|
+
*
|
|
21
|
+
* A non-positive or non-finite raw cost bills nothing, which mirrors the
|
|
22
|
+
* server's `if sum <= 0 { return 0 }`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function billedMicroCents(rawMicroCents: number): number;
|
|
25
|
+
export interface BudgetStatus {
|
|
26
|
+
approved: boolean;
|
|
27
|
+
limitCents: number;
|
|
28
|
+
ceilingMicroCents: number;
|
|
29
|
+
settledMicroCents: number;
|
|
30
|
+
pendingMicroCents: number;
|
|
31
|
+
/** limit - settled - pending, floored at 0. */
|
|
32
|
+
remainingMicroCents: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function budgetStatus(): BudgetStatus;
|
|
35
|
+
/**
|
|
36
|
+
* Approve a session budget. This is called only after the user confirms the
|
|
37
|
+
* budget handshake. The amount is clamped to [1, SESSION_BUDGET_MAX_CENTS].
|
|
38
|
+
* A fresh approval opens a fresh window: settled spend resets to 0.
|
|
39
|
+
* Outstanding reservations stay, because those calls may still bill.
|
|
40
|
+
* The per-call ceiling can also be set here. It is clamped the same way.
|
|
41
|
+
*/
|
|
42
|
+
export declare function approveBudget(cents: number, opts?: {
|
|
43
|
+
ceilingCents?: number;
|
|
44
|
+
}): BudgetStatus;
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether a spend call with this estimate may run.
|
|
47
|
+
*
|
|
48
|
+
* Order (a safety property, D-08):
|
|
49
|
+
* 1. over_ceiling estimate above the per-call ceiling, even when unapproved
|
|
50
|
+
* 2. needs_budget_handshake no budget approved yet
|
|
51
|
+
* 3. needs_budget_handshake settled actuals have reached the limit (the wall)
|
|
52
|
+
* 4. needs_budget_handshake estimate exceeds remaining headroom (including pending)
|
|
53
|
+
* 5. ok
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkCall(estimateMicroCents: number, opts?: {
|
|
56
|
+
ceilingMicroCents?: number;
|
|
57
|
+
}): BudgetDecision;
|
|
58
|
+
/** Reserve an estimate before the network call. It reduces headroom until settled or released. */
|
|
59
|
+
export declare function recordEstimate(callId: string, microCents: number): void;
|
|
60
|
+
/** Settle a call at its actual cost. Removes the reservation and adds the billed actual to settled spend. */
|
|
61
|
+
export declare function reconcileActual(callId: string, actualMicroCents: number): void;
|
|
62
|
+
/** Drop a reservation for a call that was never billed (error, cancel, failure). */
|
|
63
|
+
export declare function releaseEstimate(callId: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Hold a video job's floor estimate under callId and map the video id to it,
|
|
66
|
+
* so a later read (deeprelay_videos_get / _list) can settle it.
|
|
67
|
+
*/
|
|
68
|
+
export declare function registerPendingVideo(videoId: string, callId: string, floorMicroCents: number): void;
|
|
69
|
+
export type VideoStatus = "queued" | "in_progress" | "completed" | "failed" | "cancelled" | "expired";
|
|
70
|
+
/**
|
|
71
|
+
* Settle a video reservation from an observed job status.
|
|
72
|
+
* completed -> reconcile at costCents (or the reserved floor if costCents is absent)
|
|
73
|
+
* failed | cancelled | expired -> release. These are never billed.
|
|
74
|
+
* queued | in_progress (or anything else) -> no change
|
|
75
|
+
*/
|
|
76
|
+
export declare function settleVideo(videoId: string, outcome: {
|
|
77
|
+
status: VideoStatus | string;
|
|
78
|
+
costCents?: number;
|
|
79
|
+
}): {
|
|
80
|
+
settled: boolean;
|
|
81
|
+
microCents?: number;
|
|
82
|
+
};
|
|
83
|
+
/** One outstanding video reservation, keyed by the video id a read can settle. */
|
|
84
|
+
export interface PendingVideoReservation {
|
|
85
|
+
videoId: string;
|
|
86
|
+
microCents: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The video reservations still waiting on a terminal status. Reported by
|
|
90
|
+
* deeprelay_auth_status and used by the video reads to tell "still pending"
|
|
91
|
+
* apart from "never tracked by this session".
|
|
92
|
+
*/
|
|
93
|
+
export declare function pendingVideoReservations(): PendingVideoReservation[];
|
|
94
|
+
/** Test-only: restore the fresh-process state. */
|
|
95
|
+
export declare function resetBudgetStateForTests(): void;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PER_CALL_CEILING_MICRO_CENTS = exports.PER_CALL_CEILING_CENTS = exports.SESSION_BUDGET_MAX_CENTS = exports.SESSION_BUDGET_DEFAULT_CENTS = exports.MICRO_CENTS_PER_CENT = void 0;
|
|
4
|
+
exports.billedMicroCents = billedMicroCents;
|
|
5
|
+
exports.budgetStatus = budgetStatus;
|
|
6
|
+
exports.approveBudget = approveBudget;
|
|
7
|
+
exports.checkCall = checkCall;
|
|
8
|
+
exports.recordEstimate = recordEstimate;
|
|
9
|
+
exports.reconcileActual = reconcileActual;
|
|
10
|
+
exports.releaseEstimate = releaseEstimate;
|
|
11
|
+
exports.registerPendingVideo = registerPendingVideo;
|
|
12
|
+
exports.settleVideo = settleVideo;
|
|
13
|
+
exports.pendingVideoReservations = pendingVideoReservations;
|
|
14
|
+
exports.resetBudgetStateForTests = resetBudgetStateForTests;
|
|
15
|
+
/** Micro-cents (1e-6 cent) per cent. Mirrors catalog.MicroCentsPerCent. */
|
|
16
|
+
exports.MICRO_CENTS_PER_CENT = 1_000_000;
|
|
17
|
+
/** Default session budget: $5.00 (D-08). */
|
|
18
|
+
exports.SESSION_BUDGET_DEFAULT_CENTS = 500;
|
|
19
|
+
/** Upper bound for a user-set session budget: $100.00. */
|
|
20
|
+
exports.SESSION_BUDGET_MAX_CENTS = 10000;
|
|
21
|
+
/** Default per-call ceiling: $0.50 (D-08). */
|
|
22
|
+
exports.PER_CALL_CEILING_CENTS = 50;
|
|
23
|
+
exports.PER_CALL_CEILING_MICRO_CENTS = exports.PER_CALL_CEILING_CENTS * exports.MICRO_CENTS_PER_CENT;
|
|
24
|
+
/**
|
|
25
|
+
* Round one invocation's raw cost UP to whole cents, expressed in micro-cents.
|
|
26
|
+
*
|
|
27
|
+
* The server bills each invocation as ceil(microcents / 1e6) cents
|
|
28
|
+
* (costCentsFor / db.CentsFromMicroCentsCeil). The rounding happens per call and
|
|
29
|
+
* always upward, so its error grows with CALL COUNT. Aggregating first and
|
|
30
|
+
* rounding once would under-count a session of sub-cent calls by up to 100x
|
|
31
|
+
* (the D-82-A regression). Every estimate and every actual goes through this
|
|
32
|
+
* function one invocation at a time.
|
|
33
|
+
*
|
|
34
|
+
* A non-positive or non-finite raw cost bills nothing, which mirrors the
|
|
35
|
+
* server's `if sum <= 0 { return 0 }`.
|
|
36
|
+
*/
|
|
37
|
+
function billedMicroCents(rawMicroCents) {
|
|
38
|
+
if (!Number.isFinite(rawMicroCents) || rawMicroCents <= 0) {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
return Math.ceil(rawMicroCents / exports.MICRO_CENTS_PER_CENT) * exports.MICRO_CENTS_PER_CENT;
|
|
42
|
+
}
|
|
43
|
+
function freshState() {
|
|
44
|
+
return {
|
|
45
|
+
approved: false,
|
|
46
|
+
limitCents: exports.SESSION_BUDGET_DEFAULT_CENTS,
|
|
47
|
+
ceilingMicroCents: exports.PER_CALL_CEILING_MICRO_CENTS,
|
|
48
|
+
settledMicroCents: 0,
|
|
49
|
+
pending: new Map(),
|
|
50
|
+
pendingVideos: new Map(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
let state = freshState();
|
|
54
|
+
function nonNegative(microCents) {
|
|
55
|
+
return Number.isFinite(microCents) && microCents > 0 ? microCents : 0;
|
|
56
|
+
}
|
|
57
|
+
function clampCents(cents, fallback) {
|
|
58
|
+
if (!Number.isFinite(cents))
|
|
59
|
+
return fallback;
|
|
60
|
+
return Math.min(exports.SESSION_BUDGET_MAX_CENTS, Math.max(1, Math.floor(cents)));
|
|
61
|
+
}
|
|
62
|
+
function dollars(microCents) {
|
|
63
|
+
return `$${(microCents / (exports.MICRO_CENTS_PER_CENT * 100)).toFixed(2)}`;
|
|
64
|
+
}
|
|
65
|
+
function pendingTotal() {
|
|
66
|
+
let sum = 0;
|
|
67
|
+
for (const v of state.pending.values())
|
|
68
|
+
sum += v;
|
|
69
|
+
return sum;
|
|
70
|
+
}
|
|
71
|
+
function budgetStatus() {
|
|
72
|
+
const limitMicroCents = state.limitCents * exports.MICRO_CENTS_PER_CENT;
|
|
73
|
+
const pendingMicroCents = pendingTotal();
|
|
74
|
+
return {
|
|
75
|
+
approved: state.approved,
|
|
76
|
+
limitCents: state.limitCents,
|
|
77
|
+
ceilingMicroCents: state.ceilingMicroCents,
|
|
78
|
+
settledMicroCents: state.settledMicroCents,
|
|
79
|
+
pendingMicroCents,
|
|
80
|
+
remainingMicroCents: Math.max(0, limitMicroCents - state.settledMicroCents - pendingMicroCents),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Approve a session budget. This is called only after the user confirms the
|
|
85
|
+
* budget handshake. The amount is clamped to [1, SESSION_BUDGET_MAX_CENTS].
|
|
86
|
+
* A fresh approval opens a fresh window: settled spend resets to 0.
|
|
87
|
+
* Outstanding reservations stay, because those calls may still bill.
|
|
88
|
+
* The per-call ceiling can also be set here. It is clamped the same way.
|
|
89
|
+
*/
|
|
90
|
+
function approveBudget(cents, opts) {
|
|
91
|
+
state.approved = true;
|
|
92
|
+
state.limitCents = clampCents(cents, exports.SESSION_BUDGET_DEFAULT_CENTS);
|
|
93
|
+
state.settledMicroCents = 0;
|
|
94
|
+
if (opts?.ceilingCents !== undefined) {
|
|
95
|
+
state.ceilingMicroCents = clampCents(opts.ceilingCents, exports.PER_CALL_CEILING_CENTS) * exports.MICRO_CENTS_PER_CENT;
|
|
96
|
+
}
|
|
97
|
+
return budgetStatus();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Decide whether a spend call with this estimate may run.
|
|
101
|
+
*
|
|
102
|
+
* Order (a safety property, D-08):
|
|
103
|
+
* 1. over_ceiling estimate above the per-call ceiling, even when unapproved
|
|
104
|
+
* 2. needs_budget_handshake no budget approved yet
|
|
105
|
+
* 3. needs_budget_handshake settled actuals have reached the limit (the wall)
|
|
106
|
+
* 4. needs_budget_handshake estimate exceeds remaining headroom (including pending)
|
|
107
|
+
* 5. ok
|
|
108
|
+
*/
|
|
109
|
+
function checkCall(estimateMicroCents, opts) {
|
|
110
|
+
const ceiling = opts?.ceilingMicroCents ?? state.ceilingMicroCents;
|
|
111
|
+
if (!Number.isFinite(estimateMicroCents)) {
|
|
112
|
+
return {
|
|
113
|
+
verdict: "over_ceiling",
|
|
114
|
+
reason: "This call could not be priced, so it must be confirmed on its own.",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const estimate = nonNegative(estimateMicroCents);
|
|
118
|
+
if (estimate > ceiling) {
|
|
119
|
+
return {
|
|
120
|
+
verdict: "over_ceiling",
|
|
121
|
+
reason: `This call is estimated at ${dollars(estimate)}, above the ${dollars(ceiling)} per-call ceiling. It must be confirmed on its own.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (!state.approved) {
|
|
125
|
+
return {
|
|
126
|
+
verdict: "needs_budget_handshake",
|
|
127
|
+
reason: `No session budget is approved yet. Approve one (default ${dollars(exports.SESSION_BUDGET_DEFAULT_CENTS * exports.MICRO_CENTS_PER_CENT)}) before running spend tools.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const status = budgetStatus();
|
|
131
|
+
const limit = state.limitCents * exports.MICRO_CENTS_PER_CENT;
|
|
132
|
+
if (state.settledMicroCents >= limit) {
|
|
133
|
+
return {
|
|
134
|
+
verdict: "needs_budget_handshake",
|
|
135
|
+
reason: `The ${dollars(limit)} session budget is used up (${dollars(state.settledMicroCents)} settled). Approve a new budget to continue.`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (estimate > status.remainingMicroCents) {
|
|
139
|
+
return {
|
|
140
|
+
verdict: "needs_budget_handshake",
|
|
141
|
+
reason: `This call is estimated at ${dollars(estimate)}, but only ${dollars(status.remainingMicroCents)} of the ${dollars(limit)} session budget remains. Approve a new budget to continue.`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
verdict: "ok",
|
|
146
|
+
reason: `Estimated ${dollars(estimate)}, within the remaining ${dollars(status.remainingMicroCents)} of the ${dollars(limit)} session budget.`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** Reserve an estimate before the network call. It reduces headroom until settled or released. */
|
|
150
|
+
function recordEstimate(callId, microCents) {
|
|
151
|
+
state.pending.set(callId, nonNegative(microCents));
|
|
152
|
+
}
|
|
153
|
+
/** Settle a call at its actual cost. Removes the reservation and adds the billed actual to settled spend. */
|
|
154
|
+
function reconcileActual(callId, actualMicroCents) {
|
|
155
|
+
state.pending.delete(callId);
|
|
156
|
+
dropVideoLinks(callId);
|
|
157
|
+
state.settledMicroCents += billedMicroCents(actualMicroCents);
|
|
158
|
+
}
|
|
159
|
+
/** Drop a reservation for a call that was never billed (error, cancel, failure). */
|
|
160
|
+
function releaseEstimate(callId) {
|
|
161
|
+
state.pending.delete(callId);
|
|
162
|
+
dropVideoLinks(callId);
|
|
163
|
+
}
|
|
164
|
+
function dropVideoLinks(callId) {
|
|
165
|
+
for (const [videoId, linked] of state.pendingVideos) {
|
|
166
|
+
if (linked === callId)
|
|
167
|
+
state.pendingVideos.delete(videoId);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Hold a video job's floor estimate under callId and map the video id to it,
|
|
172
|
+
* so a later read (deeprelay_videos_get / _list) can settle it.
|
|
173
|
+
*/
|
|
174
|
+
function registerPendingVideo(videoId, callId, floorMicroCents) {
|
|
175
|
+
state.pending.set(callId, nonNegative(floorMicroCents));
|
|
176
|
+
state.pendingVideos.set(videoId, callId);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Settle a video reservation from an observed job status.
|
|
180
|
+
* completed -> reconcile at costCents (or the reserved floor if costCents is absent)
|
|
181
|
+
* failed | cancelled | expired -> release. These are never billed.
|
|
182
|
+
* queued | in_progress (or anything else) -> no change
|
|
183
|
+
*/
|
|
184
|
+
function settleVideo(videoId, outcome) {
|
|
185
|
+
const callId = state.pendingVideos.get(videoId);
|
|
186
|
+
if (callId === undefined) {
|
|
187
|
+
return { settled: false };
|
|
188
|
+
}
|
|
189
|
+
switch (outcome.status) {
|
|
190
|
+
case "completed": {
|
|
191
|
+
const reserved = state.pending.get(callId) ?? 0;
|
|
192
|
+
const cost = outcome.costCents;
|
|
193
|
+
const actual = typeof cost === "number" && Number.isFinite(cost) && cost >= 0
|
|
194
|
+
? cost * exports.MICRO_CENTS_PER_CENT
|
|
195
|
+
: reserved;
|
|
196
|
+
const billed = billedMicroCents(actual);
|
|
197
|
+
reconcileActual(callId, billed);
|
|
198
|
+
return { settled: true, microCents: billed };
|
|
199
|
+
}
|
|
200
|
+
case "failed":
|
|
201
|
+
case "cancelled":
|
|
202
|
+
case "expired":
|
|
203
|
+
releaseEstimate(callId);
|
|
204
|
+
return { settled: true, microCents: 0 };
|
|
205
|
+
default:
|
|
206
|
+
return { settled: false };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The video reservations still waiting on a terminal status. Reported by
|
|
211
|
+
* deeprelay_auth_status and used by the video reads to tell "still pending"
|
|
212
|
+
* apart from "never tracked by this session".
|
|
213
|
+
*/
|
|
214
|
+
function pendingVideoReservations() {
|
|
215
|
+
const out = [];
|
|
216
|
+
for (const [videoId, callId] of state.pendingVideos) {
|
|
217
|
+
out.push({ videoId, microCents: state.pending.get(callId) ?? 0 });
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
/** Test-only: restore the fresh-process state. */
|
|
222
|
+
function resetBudgetStateForTests() {
|
|
223
|
+
state = freshState();
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/guardrail/budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ElicitationHost, QuoteResult } from "./types.js";
|
|
2
|
+
export type ElicitationAnswer = "confirmed" | "declined" | "unavailable";
|
|
3
|
+
/** True only when the connected client advertised the elicitation capability. */
|
|
4
|
+
export declare function clientSupportsElicitation(host?: ElicitationHost): boolean;
|
|
5
|
+
/** The text the user reads: summary, price (if any), expiry, then the question. */
|
|
6
|
+
export declare function buildPrompt(quote: QuoteResult, question: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Ask the user to confirm a quote. Executes nothing. Returns "unavailable" when
|
|
9
|
+
* the client cannot be asked or the ask fails, "confirmed" only for an explicit
|
|
10
|
+
* accept with `confirm === true`, and "declined" for everything else.
|
|
11
|
+
*/
|
|
12
|
+
export declare function elicitConfirmation(host: ElicitationHost | undefined, quote: QuoteResult, question: string): Promise<ElicitationAnswer>;
|