@carrierllc/mcp 0.9.0 → 0.9.2
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/dist/{chunk-VACB3Z5V.js → chunk-4XHSOF62.js} +4 -3
- package/dist/chunk-4XHSOF62.js.map +1 -0
- package/dist/cli.js +38 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +540 -242
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-VACB3Z5V.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
subscriberIdParams,
|
|
38
38
|
usageOverPeriodParams,
|
|
39
39
|
verifyStorefront
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-4XHSOF62.js";
|
|
41
41
|
import "./chunk-SHKKVIIA.js";
|
|
42
42
|
|
|
43
43
|
// src/index.ts
|
|
@@ -98,17 +98,213 @@ var OcsClient = class {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
// ../../packages/carrier-ai/dist/index.js
|
|
102
|
+
import { AwsClient } from "aws4fetch";
|
|
103
|
+
var AI_TIER_POLICY = {
|
|
104
|
+
free: { limit: 5, window: "day" },
|
|
105
|
+
pro: { limit: 1e3, window: "month" },
|
|
106
|
+
enterprise: { limit: 1e4, window: "month" },
|
|
107
|
+
// Finite, deliberately. This was `Infinity` when the tier was introduced in
|
|
108
|
+
// #762, and Infinity breaks in two ways that both hide themselves:
|
|
109
|
+
//
|
|
110
|
+
// 1. JSON.stringify(Infinity) is "null". The quota endpoint returned
|
|
111
|
+
// {"limit":null,"remaining":null} and the console badge rendered
|
|
112
|
+
// "1 of null used". That is the whole reason this was investigated.
|
|
113
|
+
// 2. An infinite allowance is unmetered Bedrock. A staff account could
|
|
114
|
+
// spend without bound and nothing would refuse it or show a number.
|
|
115
|
+
//
|
|
116
|
+
// 100,000 is roughly $580 of Haiku at the measured $0.0058/call. Nobody on
|
|
117
|
+
// staff will reach it by using the product, and a runaway loop stops.
|
|
118
|
+
superadmin: { limit: 1e5, window: "month" }
|
|
119
|
+
};
|
|
120
|
+
for (const [tier, policy] of Object.entries(AI_TIER_POLICY)) {
|
|
121
|
+
if (!Number.isFinite(policy.limit) || policy.limit <= 0) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`AI_TIER_POLICY.${tier}.limit must be a positive finite number, got ${policy.limit}. A non-finite limit serialises to null in JSON and leaves spend unmetered.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
var PRELUDE_BYTES = 12;
|
|
128
|
+
var MESSAGE_CRC_BYTES = 4;
|
|
129
|
+
var MIN_FRAME_BYTES = PRELUDE_BYTES + MESSAGE_CRC_BYTES;
|
|
130
|
+
var MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
131
|
+
var MAX_ENTRIES = 16;
|
|
132
|
+
var MAX_KEY_LEN = 256;
|
|
133
|
+
var MAX_VALUE_LEN = 256;
|
|
134
|
+
var ALLOWED = /[^a-zA-Z0-9\s:_@$#=/+,\-.]/g;
|
|
135
|
+
function clean(value, max) {
|
|
136
|
+
return value.replace(ALLOWED, "").slice(0, max);
|
|
137
|
+
}
|
|
138
|
+
var REQUEST_METADATA_HEADER = "X-Amzn-Bedrock-Request-Metadata";
|
|
139
|
+
function requestMetadata(attr2) {
|
|
140
|
+
const out = {};
|
|
141
|
+
const pairs = [
|
|
142
|
+
["product", "carrier"],
|
|
143
|
+
["org", attr2.orgId],
|
|
144
|
+
["subaccount", attr2.subAccountId],
|
|
145
|
+
["surface", attr2.surface],
|
|
146
|
+
["tier", attr2.tier]
|
|
147
|
+
];
|
|
148
|
+
for (const [k, v] of pairs) {
|
|
149
|
+
if (v === void 0 || v === "") continue;
|
|
150
|
+
const key = clean(k, MAX_KEY_LEN);
|
|
151
|
+
const value = clean(v, MAX_VALUE_LEN);
|
|
152
|
+
if (key && value && Object.keys(out).length < MAX_ENTRIES) out[key] = value;
|
|
153
|
+
}
|
|
154
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
155
|
+
}
|
|
156
|
+
var ZERO_USAGE = {
|
|
157
|
+
inputTokens: 0,
|
|
158
|
+
outputTokens: 0,
|
|
159
|
+
cacheReadTokens: 0,
|
|
160
|
+
cacheWriteTokens: 0
|
|
161
|
+
};
|
|
162
|
+
function extractUsage(payload) {
|
|
163
|
+
if (!payload || typeof payload !== "object") return ZERO_USAGE;
|
|
164
|
+
const root = payload;
|
|
165
|
+
const holder = root.usage ?? root.message?.usage;
|
|
166
|
+
if (!holder) return ZERO_USAGE;
|
|
167
|
+
const num = (v) => {
|
|
168
|
+
const n = Number(v);
|
|
169
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
inputTokens: num(holder.input_tokens),
|
|
173
|
+
outputTokens: num(holder.output_tokens),
|
|
174
|
+
cacheReadTokens: num(holder.cache_read_input_tokens),
|
|
175
|
+
cacheWriteTokens: num(holder.cache_creation_input_tokens)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
var HAIKU_RATES = {
|
|
179
|
+
inputPerMTok: 1,
|
|
180
|
+
outputPerMTok: 5,
|
|
181
|
+
cacheReadPerMTok: 0.1,
|
|
182
|
+
cacheWritePerMTok: 1.25,
|
|
183
|
+
asOf: "2026-08-22"
|
|
184
|
+
};
|
|
185
|
+
function estimateCostUsd(u, rates = HAIKU_RATES) {
|
|
186
|
+
const perTok = (mtok) => mtok / 1e6;
|
|
187
|
+
return u.inputTokens * perTok(rates.inputPerMTok) + u.outputTokens * perTok(rates.outputPerMTok) + u.cacheReadTokens * perTok(rates.cacheReadPerMTok) + u.cacheWriteTokens * perTok(rates.cacheWritePerMTok);
|
|
188
|
+
}
|
|
189
|
+
var DEFAULT_BEDROCK_REGION = "us-east-1";
|
|
190
|
+
var DEFAULT_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
191
|
+
var BEDROCK_TIMEOUT_MS = 3e4;
|
|
192
|
+
var BedrockRateLimitError = class extends Error {
|
|
193
|
+
isRateLimit = true;
|
|
194
|
+
/**
|
|
195
|
+
* The raw `retry-after` header, when Bedrock sent one. Callers surface it as
|
|
196
|
+
* the delay they tell the user to wait; without it they have to guess.
|
|
197
|
+
*/
|
|
198
|
+
retryAfter;
|
|
199
|
+
constructor(message, retryAfter = null) {
|
|
200
|
+
super(message);
|
|
201
|
+
this.name = "BedrockRateLimitError";
|
|
202
|
+
this.retryAfter = retryAfter;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var BedrockError = class extends Error {
|
|
206
|
+
constructor(message, status) {
|
|
207
|
+
super(message);
|
|
208
|
+
this.status = status;
|
|
209
|
+
this.name = "BedrockError";
|
|
210
|
+
}
|
|
211
|
+
status;
|
|
212
|
+
};
|
|
213
|
+
function client(creds) {
|
|
214
|
+
const region = creds.region ?? DEFAULT_BEDROCK_REGION;
|
|
215
|
+
return {
|
|
216
|
+
aws: new AwsClient({
|
|
217
|
+
accessKeyId: creds.accessKeyId,
|
|
218
|
+
secretAccessKey: creds.secretAccessKey,
|
|
219
|
+
...creds.sessionToken ? { sessionToken: creds.sessionToken } : {},
|
|
220
|
+
region,
|
|
221
|
+
service: "bedrock"
|
|
222
|
+
}),
|
|
223
|
+
region,
|
|
224
|
+
modelId: creds.modelId ?? DEFAULT_MODEL_ID
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function body(req) {
|
|
228
|
+
return {
|
|
229
|
+
anthropic_version: "bedrock-2023-05-31",
|
|
230
|
+
max_tokens: req.maxTokens,
|
|
231
|
+
...req.temperature !== void 0 ? { temperature: req.temperature } : {},
|
|
232
|
+
system: [{ type: "text", text: req.system, cache_control: { type: "ephemeral" } }],
|
|
233
|
+
messages: req.messages.map((m) => ({ role: m.role, content: m.content })),
|
|
234
|
+
...req.tools ? { tools: req.tools, tool_choice: { type: "any" } } : {}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function headersFor(attribution, accept) {
|
|
238
|
+
const headers = {
|
|
239
|
+
"Content-Type": "application/json",
|
|
240
|
+
Accept: accept
|
|
241
|
+
};
|
|
242
|
+
const meta = attribution ? requestMetadata(attribution) : null;
|
|
243
|
+
if (meta) headers[REQUEST_METADATA_HEADER] = JSON.stringify(meta);
|
|
244
|
+
return headers;
|
|
245
|
+
}
|
|
246
|
+
function reportUsage(req, usage) {
|
|
247
|
+
if (!req.onUsage) return;
|
|
248
|
+
try {
|
|
249
|
+
req.onUsage(usage);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function fail(resp) {
|
|
254
|
+
const text = await resp.text().catch(() => "");
|
|
255
|
+
if (resp.status === 429 || /throttl/i.test(text)) {
|
|
256
|
+
throw new BedrockRateLimitError(
|
|
257
|
+
text || "Bedrock throttled the request",
|
|
258
|
+
resp.headers.get("retry-after")
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
throw new BedrockError(text || `Bedrock returned HTTP ${resp.status}`, resp.status);
|
|
262
|
+
}
|
|
263
|
+
async function invokeTool(creds, req, signal) {
|
|
264
|
+
const json = await invokeModelRaw(
|
|
265
|
+
creds,
|
|
266
|
+
body(req),
|
|
267
|
+
{ attribution: req.attribution, timeoutMs: req.timeoutMs },
|
|
268
|
+
signal
|
|
269
|
+
);
|
|
270
|
+
reportUsage(req, extractUsage(json));
|
|
271
|
+
const block = json.content?.find((b) => b.type === "tool_use");
|
|
272
|
+
return block?.name ? { name: block.name, input: block.input ?? {} } : null;
|
|
273
|
+
}
|
|
274
|
+
async function invokeModelRaw(creds, payload, opts = {}, signal) {
|
|
275
|
+
const { aws, region, modelId } = client(creds);
|
|
276
|
+
const controller = new AbortController();
|
|
277
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? BEDROCK_TIMEOUT_MS);
|
|
278
|
+
const onAbort = () => controller.abort();
|
|
279
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
280
|
+
try {
|
|
281
|
+
const resp = await aws.fetch(
|
|
282
|
+
`https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`,
|
|
283
|
+
{
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: headersFor(opts.attribution, "application/json"),
|
|
286
|
+
body: JSON.stringify(payload),
|
|
287
|
+
signal: controller.signal
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
if (!resp.ok) await fail(resp);
|
|
291
|
+
return await resp.json();
|
|
292
|
+
} finally {
|
|
293
|
+
clearTimeout(timer);
|
|
294
|
+
signal?.removeEventListener("abort", onAbort);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
101
298
|
// src/billing.ts
|
|
102
299
|
var TIER_CALL_LIMITS = {
|
|
103
|
-
free:
|
|
104
|
-
pro:
|
|
105
|
-
enterprise: Infinity
|
|
300
|
+
free: 1e3,
|
|
301
|
+
pro: Infinity,
|
|
302
|
+
enterprise: Infinity,
|
|
303
|
+
superadmin: Infinity
|
|
106
304
|
};
|
|
305
|
+
var CARRIER_ASK_PRO_MONTHLY_LIMIT = AI_TIER_POLICY.pro.limit;
|
|
306
|
+
var CARRIER_ASK_FREE_DAILY_LIMIT = AI_TIER_POLICY.free.limit;
|
|
107
307
|
var UPGRADE_URL = "https://mcp.carrier.llc/upgrade";
|
|
108
|
-
function getBillingPrimary(env) {
|
|
109
|
-
const raw = env.BILLING_PRIMARY;
|
|
110
|
-
return raw === "clerk" ? "clerk" : "stripe";
|
|
111
|
-
}
|
|
112
308
|
function platformOrgId(env) {
|
|
113
309
|
const raw = env.CARRIER_PLATFORM_ORG_ID;
|
|
114
310
|
if (typeof raw !== "string") return null;
|
|
@@ -123,7 +319,7 @@ function isPlatformOperator(env, orgId) {
|
|
|
123
319
|
async function checkCallQuota(env, sub, tier) {
|
|
124
320
|
const limit = TIER_CALL_LIMITS[tier];
|
|
125
321
|
const resetAt = firstDayNextMonth();
|
|
126
|
-
if (tier === "enterprise") {
|
|
322
|
+
if (tier === "pro" || tier === "enterprise" || tier === "superadmin") {
|
|
127
323
|
return { allowed: true, remaining: Infinity, resetAt, tier };
|
|
128
324
|
}
|
|
129
325
|
if (!env.CARRIER_USERS) {
|
|
@@ -144,6 +340,7 @@ async function checkCallQuota(env, sub, tier) {
|
|
|
144
340
|
function recordUsage(env, sub, tier) {
|
|
145
341
|
if (!env.CARRIER_USERS) return;
|
|
146
342
|
(async () => {
|
|
343
|
+
if (tier === "pro" || tier === "enterprise" || tier === "superadmin") return;
|
|
147
344
|
const month = currentMonth();
|
|
148
345
|
const usageKey = `usage:${sub}:${month}`;
|
|
149
346
|
const raw = await env.CARRIER_USERS.get(usageKey, "json").catch(() => null);
|
|
@@ -154,35 +351,8 @@ function recordUsage(env, sub, tier) {
|
|
|
154
351
|
JSON.stringify({ calls: next, updated_at: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
155
352
|
{ expirationTtl: 35 * 24 * 60 * 60 }
|
|
156
353
|
);
|
|
157
|
-
if (tier === "pro" && next % 100 === 0 && getBillingPrimary(env) === "stripe") {
|
|
158
|
-
await pushStripeUsageRecord(env, sub, 100).catch(() => {
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
354
|
})();
|
|
162
355
|
}
|
|
163
|
-
async function pushStripeUsageRecord(env, sub, quantity) {
|
|
164
|
-
const stripeKey = env.STRIPE_SECRET_KEY;
|
|
165
|
-
if (!stripeKey) return;
|
|
166
|
-
if (!env.CARRIER_USERS) return;
|
|
167
|
-
const subItemId = await env.CARRIER_USERS.get(`stripe_sub_item_id:${sub}`);
|
|
168
|
-
if (!subItemId) return;
|
|
169
|
-
const body2 = new URLSearchParams({
|
|
170
|
-
quantity: String(quantity),
|
|
171
|
-
timestamp: String(Math.floor(Date.now() / 1e3)),
|
|
172
|
-
action: "increment"
|
|
173
|
-
});
|
|
174
|
-
await fetch(
|
|
175
|
-
`https://api.stripe.com/v1/subscription_items/${subItemId}/usage_records`,
|
|
176
|
-
{
|
|
177
|
-
method: "POST",
|
|
178
|
-
headers: {
|
|
179
|
-
Authorization: `Bearer ${stripeKey}`,
|
|
180
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
181
|
-
},
|
|
182
|
-
body: body2.toString()
|
|
183
|
-
}
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
356
|
function currentMonth() {
|
|
187
357
|
const now = /* @__PURE__ */ new Date();
|
|
188
358
|
return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
@@ -531,8 +701,8 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
|
|
|
531
701
|
{
|
|
532
702
|
type: "text",
|
|
533
703
|
text: [
|
|
534
|
-
`Quota exceeded: your ${quota.tier} plan has reached the
|
|
535
|
-
`
|
|
704
|
+
`Quota exceeded: your ${quota.tier} plan has reached the monthly limit of ${TIER_CALL_LIMITS.free.toLocaleString()} regular tool calls.`,
|
|
705
|
+
`Resets ${quota.resetAt}.`,
|
|
536
706
|
`Upgrade to Pro for unlimited calls at ${UPGRADE_URL}`
|
|
537
707
|
].join(" ")
|
|
538
708
|
}
|
|
@@ -4675,196 +4845,6 @@ function registerAllBacklogTools(server2, ctx) {
|
|
|
4675
4845
|
);
|
|
4676
4846
|
}
|
|
4677
4847
|
|
|
4678
|
-
// ../../packages/carrier-ai/dist/index.js
|
|
4679
|
-
import { AwsClient } from "aws4fetch";
|
|
4680
|
-
var AI_TIER_POLICY = {
|
|
4681
|
-
free: { limit: 5, window: "day" },
|
|
4682
|
-
pro: { limit: 1e3, window: "month" },
|
|
4683
|
-
enterprise: { limit: 1e4, window: "month" },
|
|
4684
|
-
// Finite, deliberately. This was `Infinity` when the tier was introduced in
|
|
4685
|
-
// #762, and Infinity breaks in two ways that both hide themselves:
|
|
4686
|
-
//
|
|
4687
|
-
// 1. JSON.stringify(Infinity) is "null". The quota endpoint returned
|
|
4688
|
-
// {"limit":null,"remaining":null} and the console badge rendered
|
|
4689
|
-
// "1 of null used". That is the whole reason this was investigated.
|
|
4690
|
-
// 2. An infinite allowance is unmetered Bedrock. A staff account could
|
|
4691
|
-
// spend without bound and nothing would refuse it or show a number.
|
|
4692
|
-
//
|
|
4693
|
-
// 100,000 is roughly $580 of Haiku at the measured $0.0058/call. Nobody on
|
|
4694
|
-
// staff will reach it by using the product, and a runaway loop stops.
|
|
4695
|
-
superadmin: { limit: 1e5, window: "month" }
|
|
4696
|
-
};
|
|
4697
|
-
for (const [tier, policy] of Object.entries(AI_TIER_POLICY)) {
|
|
4698
|
-
if (!Number.isFinite(policy.limit) || policy.limit <= 0) {
|
|
4699
|
-
throw new Error(
|
|
4700
|
-
`AI_TIER_POLICY.${tier}.limit must be a positive finite number, got ${policy.limit}. A non-finite limit serialises to null in JSON and leaves spend unmetered.`
|
|
4701
|
-
);
|
|
4702
|
-
}
|
|
4703
|
-
}
|
|
4704
|
-
var PRELUDE_BYTES = 12;
|
|
4705
|
-
var MESSAGE_CRC_BYTES = 4;
|
|
4706
|
-
var MIN_FRAME_BYTES = PRELUDE_BYTES + MESSAGE_CRC_BYTES;
|
|
4707
|
-
var MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
4708
|
-
var MAX_ENTRIES = 16;
|
|
4709
|
-
var MAX_KEY_LEN = 256;
|
|
4710
|
-
var MAX_VALUE_LEN = 256;
|
|
4711
|
-
var ALLOWED = /[^a-zA-Z0-9\s:_@$#=/+,\-.]/g;
|
|
4712
|
-
function clean(value, max) {
|
|
4713
|
-
return value.replace(ALLOWED, "").slice(0, max);
|
|
4714
|
-
}
|
|
4715
|
-
var REQUEST_METADATA_HEADER = "X-Amzn-Bedrock-Request-Metadata";
|
|
4716
|
-
function requestMetadata(attr2) {
|
|
4717
|
-
const out = {};
|
|
4718
|
-
const pairs = [
|
|
4719
|
-
["product", "carrier"],
|
|
4720
|
-
["org", attr2.orgId],
|
|
4721
|
-
["subaccount", attr2.subAccountId],
|
|
4722
|
-
["surface", attr2.surface],
|
|
4723
|
-
["tier", attr2.tier]
|
|
4724
|
-
];
|
|
4725
|
-
for (const [k, v] of pairs) {
|
|
4726
|
-
if (v === void 0 || v === "") continue;
|
|
4727
|
-
const key = clean(k, MAX_KEY_LEN);
|
|
4728
|
-
const value = clean(v, MAX_VALUE_LEN);
|
|
4729
|
-
if (key && value && Object.keys(out).length < MAX_ENTRIES) out[key] = value;
|
|
4730
|
-
}
|
|
4731
|
-
return Object.keys(out).length > 0 ? out : null;
|
|
4732
|
-
}
|
|
4733
|
-
var ZERO_USAGE = {
|
|
4734
|
-
inputTokens: 0,
|
|
4735
|
-
outputTokens: 0,
|
|
4736
|
-
cacheReadTokens: 0,
|
|
4737
|
-
cacheWriteTokens: 0
|
|
4738
|
-
};
|
|
4739
|
-
function extractUsage(payload) {
|
|
4740
|
-
if (!payload || typeof payload !== "object") return ZERO_USAGE;
|
|
4741
|
-
const root = payload;
|
|
4742
|
-
const holder = root.usage ?? root.message?.usage;
|
|
4743
|
-
if (!holder) return ZERO_USAGE;
|
|
4744
|
-
const num = (v) => {
|
|
4745
|
-
const n = Number(v);
|
|
4746
|
-
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
4747
|
-
};
|
|
4748
|
-
return {
|
|
4749
|
-
inputTokens: num(holder.input_tokens),
|
|
4750
|
-
outputTokens: num(holder.output_tokens),
|
|
4751
|
-
cacheReadTokens: num(holder.cache_read_input_tokens),
|
|
4752
|
-
cacheWriteTokens: num(holder.cache_creation_input_tokens)
|
|
4753
|
-
};
|
|
4754
|
-
}
|
|
4755
|
-
var HAIKU_RATES = {
|
|
4756
|
-
inputPerMTok: 1,
|
|
4757
|
-
outputPerMTok: 5,
|
|
4758
|
-
cacheReadPerMTok: 0.1,
|
|
4759
|
-
cacheWritePerMTok: 1.25,
|
|
4760
|
-
asOf: "2026-08-22"
|
|
4761
|
-
};
|
|
4762
|
-
function estimateCostUsd(u, rates = HAIKU_RATES) {
|
|
4763
|
-
const perTok = (mtok) => mtok / 1e6;
|
|
4764
|
-
return u.inputTokens * perTok(rates.inputPerMTok) + u.outputTokens * perTok(rates.outputPerMTok) + u.cacheReadTokens * perTok(rates.cacheReadPerMTok) + u.cacheWriteTokens * perTok(rates.cacheWritePerMTok);
|
|
4765
|
-
}
|
|
4766
|
-
var DEFAULT_BEDROCK_REGION = "us-east-1";
|
|
4767
|
-
var DEFAULT_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
4768
|
-
var BEDROCK_TIMEOUT_MS = 3e4;
|
|
4769
|
-
var BedrockRateLimitError = class extends Error {
|
|
4770
|
-
isRateLimit = true;
|
|
4771
|
-
/**
|
|
4772
|
-
* The raw `retry-after` header, when Bedrock sent one. Callers surface it as
|
|
4773
|
-
* the delay they tell the user to wait; without it they have to guess.
|
|
4774
|
-
*/
|
|
4775
|
-
retryAfter;
|
|
4776
|
-
constructor(message, retryAfter = null) {
|
|
4777
|
-
super(message);
|
|
4778
|
-
this.name = "BedrockRateLimitError";
|
|
4779
|
-
this.retryAfter = retryAfter;
|
|
4780
|
-
}
|
|
4781
|
-
};
|
|
4782
|
-
var BedrockError = class extends Error {
|
|
4783
|
-
constructor(message, status) {
|
|
4784
|
-
super(message);
|
|
4785
|
-
this.status = status;
|
|
4786
|
-
this.name = "BedrockError";
|
|
4787
|
-
}
|
|
4788
|
-
status;
|
|
4789
|
-
};
|
|
4790
|
-
function client(creds) {
|
|
4791
|
-
const region = creds.region ?? DEFAULT_BEDROCK_REGION;
|
|
4792
|
-
return {
|
|
4793
|
-
aws: new AwsClient({
|
|
4794
|
-
accessKeyId: creds.accessKeyId,
|
|
4795
|
-
secretAccessKey: creds.secretAccessKey,
|
|
4796
|
-
...creds.sessionToken ? { sessionToken: creds.sessionToken } : {},
|
|
4797
|
-
region,
|
|
4798
|
-
service: "bedrock"
|
|
4799
|
-
}),
|
|
4800
|
-
region,
|
|
4801
|
-
modelId: creds.modelId ?? DEFAULT_MODEL_ID
|
|
4802
|
-
};
|
|
4803
|
-
}
|
|
4804
|
-
function body(req) {
|
|
4805
|
-
return {
|
|
4806
|
-
anthropic_version: "bedrock-2023-05-31",
|
|
4807
|
-
max_tokens: req.maxTokens,
|
|
4808
|
-
...req.temperature !== void 0 ? { temperature: req.temperature } : {},
|
|
4809
|
-
system: [{ type: "text", text: req.system, cache_control: { type: "ephemeral" } }],
|
|
4810
|
-
messages: req.messages.map((m) => ({ role: m.role, content: m.content })),
|
|
4811
|
-
...req.tools ? { tools: req.tools, tool_choice: { type: "any" } } : {}
|
|
4812
|
-
};
|
|
4813
|
-
}
|
|
4814
|
-
function headersFor(req, accept) {
|
|
4815
|
-
const headers = {
|
|
4816
|
-
"Content-Type": "application/json",
|
|
4817
|
-
Accept: accept
|
|
4818
|
-
};
|
|
4819
|
-
const meta = req.attribution ? requestMetadata(req.attribution) : null;
|
|
4820
|
-
if (meta) headers[REQUEST_METADATA_HEADER] = JSON.stringify(meta);
|
|
4821
|
-
return headers;
|
|
4822
|
-
}
|
|
4823
|
-
function reportUsage(req, usage) {
|
|
4824
|
-
if (!req.onUsage) return;
|
|
4825
|
-
try {
|
|
4826
|
-
req.onUsage(usage);
|
|
4827
|
-
} catch {
|
|
4828
|
-
}
|
|
4829
|
-
}
|
|
4830
|
-
async function fail(resp) {
|
|
4831
|
-
const text = await resp.text().catch(() => "");
|
|
4832
|
-
if (resp.status === 429 || /throttl/i.test(text)) {
|
|
4833
|
-
throw new BedrockRateLimitError(
|
|
4834
|
-
text || "Bedrock throttled the request",
|
|
4835
|
-
resp.headers.get("retry-after")
|
|
4836
|
-
);
|
|
4837
|
-
}
|
|
4838
|
-
throw new BedrockError(text || `Bedrock returned HTTP ${resp.status}`, resp.status);
|
|
4839
|
-
}
|
|
4840
|
-
async function invokeTool(creds, req, signal) {
|
|
4841
|
-
const { aws, region, modelId } = client(creds);
|
|
4842
|
-
const controller = new AbortController();
|
|
4843
|
-
const timer = setTimeout(() => controller.abort(), req.timeoutMs ?? BEDROCK_TIMEOUT_MS);
|
|
4844
|
-
const onAbort = () => controller.abort();
|
|
4845
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4846
|
-
try {
|
|
4847
|
-
const headers = headersFor(req, "application/json");
|
|
4848
|
-
const resp = await aws.fetch(
|
|
4849
|
-
`https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`,
|
|
4850
|
-
{
|
|
4851
|
-
method: "POST",
|
|
4852
|
-
headers,
|
|
4853
|
-
body: JSON.stringify(body(req)),
|
|
4854
|
-
signal: controller.signal
|
|
4855
|
-
}
|
|
4856
|
-
);
|
|
4857
|
-
if (!resp.ok) await fail(resp);
|
|
4858
|
-
const json = await resp.json();
|
|
4859
|
-
reportUsage(req, extractUsage(json));
|
|
4860
|
-
const block = json.content?.find((b) => b.type === "tool_use");
|
|
4861
|
-
return block?.name ? { name: block.name, input: block.input ?? {} } : null;
|
|
4862
|
-
} finally {
|
|
4863
|
-
clearTimeout(timer);
|
|
4864
|
-
signal?.removeEventListener("abort", onAbort);
|
|
4865
|
-
}
|
|
4866
|
-
}
|
|
4867
|
-
|
|
4868
4848
|
// src/tools-carrier-ask.ts
|
|
4869
4849
|
import { z as z11 } from "zod";
|
|
4870
4850
|
|
|
@@ -5132,6 +5112,7 @@ var UI_AGENT_TOOL_SCOPES = {
|
|
|
5132
5112
|
ui_create_steering_list: "write",
|
|
5133
5113
|
ui_build_steering_list: "write",
|
|
5134
5114
|
ui_set_account_steering_list: "write",
|
|
5115
|
+
ui_request_reseller_relay_change: "write",
|
|
5135
5116
|
ui_create_account: "admin",
|
|
5136
5117
|
ui_create_destination_list: "write",
|
|
5137
5118
|
ui_edit_destination_list: "write",
|
|
@@ -5217,6 +5198,66 @@ Open account ID ${args.account_id}.
|
|
|
5217
5198
|
`
|
|
5218
5199
|
)
|
|
5219
5200
|
);
|
|
5201
|
+
server2.registerTool(
|
|
5202
|
+
"ui_request_reseller_relay_change",
|
|
5203
|
+
{
|
|
5204
|
+
title: "Request Reseller Relay Change",
|
|
5205
|
+
description: "Drafts a parent-reseller/support request for Relay Gy, calls/SMS relay, or VoIP relay when those portal controls are read-only at the current reseller tier. Does not send the request or mutate the portal.",
|
|
5206
|
+
inputSchema: {
|
|
5207
|
+
reseller_id: z4.number().optional().describe("Reseller id; defaults to the authenticated reseller"),
|
|
5208
|
+
relay_gy: z4.boolean().optional().describe("Desired Relay Gy state"),
|
|
5209
|
+
relay_calls_sms: z4.boolean().optional().describe("Desired calls/SMS relay state"),
|
|
5210
|
+
relay_voip: z4.boolean().optional().describe("Desired VoIP relay state"),
|
|
5211
|
+
reason: z4.string().optional().describe("Operational reason for the requested change")
|
|
5212
|
+
}
|
|
5213
|
+
},
|
|
5214
|
+
async (args) => {
|
|
5215
|
+
if (!ctx.props.scope.includes("write")) {
|
|
5216
|
+
return {
|
|
5217
|
+
isError: true,
|
|
5218
|
+
content: [{ type: "text", text: "Scope denied: tool 'ui_request_reseller_relay_change' requires 'write' scope." }]
|
|
5219
|
+
};
|
|
5220
|
+
}
|
|
5221
|
+
const resellerId = args.reseller_id ?? ctx.props.reseller_id;
|
|
5222
|
+
const requested = [
|
|
5223
|
+
args.relay_gy === void 0 ? null : `Relay Gy: ${args.relay_gy ? "enable" : "disable"}`,
|
|
5224
|
+
args.relay_calls_sms === void 0 ? null : `Calls/SMS relay: ${args.relay_calls_sms ? "enable" : "disable"}`,
|
|
5225
|
+
args.relay_voip === void 0 ? null : `VoIP relay: ${args.relay_voip ? "enable" : "disable"}`
|
|
5226
|
+
].filter((value) => value !== null);
|
|
5227
|
+
if (requested.length === 0) {
|
|
5228
|
+
return {
|
|
5229
|
+
isError: true,
|
|
5230
|
+
content: [{ type: "text", text: JSON.stringify({ error: "relay_state_required", message: "Choose at least one relay flag and desired state." }) }]
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
5233
|
+
const draft = [
|
|
5234
|
+
`Please update the parent-controlled OCS relay settings for reseller ${resellerId}.`,
|
|
5235
|
+
"",
|
|
5236
|
+
...requested.map((line) => `- ${line}`),
|
|
5237
|
+
...args.reason ? ["", `Reason: ${args.reason}`] : [],
|
|
5238
|
+
"",
|
|
5239
|
+
"Please confirm once the change is active so we can verify the dependent OCS operation."
|
|
5240
|
+
].join("\n");
|
|
5241
|
+
ctx.audit({
|
|
5242
|
+
tool_name: "ui_request_reseller_relay_change",
|
|
5243
|
+
ocs_method: "[ui-agent:G-20]",
|
|
5244
|
+
status: "parent_request_drafted",
|
|
5245
|
+
dry_run: true,
|
|
5246
|
+
duration_ms: 0,
|
|
5247
|
+
event_type: "ui_agent_dispatch"
|
|
5248
|
+
});
|
|
5249
|
+
return {
|
|
5250
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
5251
|
+
status: "parent_request_drafted",
|
|
5252
|
+
capability: "parent_controlled",
|
|
5253
|
+
reseller_id: resellerId,
|
|
5254
|
+
parent_reseller: resellerId === 1170 ? "Bridge4IP" : "parent reseller/support",
|
|
5255
|
+
draft,
|
|
5256
|
+
sent: false
|
|
5257
|
+
}, null, 2) }]
|
|
5258
|
+
};
|
|
5259
|
+
}
|
|
5260
|
+
);
|
|
5220
5261
|
server2.registerTool(
|
|
5221
5262
|
"ui_create_account",
|
|
5222
5263
|
{
|
|
@@ -5494,6 +5535,21 @@ function computeExpiresAt(askedAt) {
|
|
|
5494
5535
|
if (isNaN(asked)) return "";
|
|
5495
5536
|
return new Date(asked + PENDING_ASK_TTL_SECONDS * 1e3).toISOString();
|
|
5496
5537
|
}
|
|
5538
|
+
async function callerOwnsTask(ctx, taskId) {
|
|
5539
|
+
let raw;
|
|
5540
|
+
try {
|
|
5541
|
+
raw = await ctx.env.CARRIER_USERS.get(`steel_task:${taskId}`);
|
|
5542
|
+
} catch {
|
|
5543
|
+
return false;
|
|
5544
|
+
}
|
|
5545
|
+
if (!raw) return false;
|
|
5546
|
+
try {
|
|
5547
|
+
const task = JSON.parse(raw);
|
|
5548
|
+
return task.owner_sub !== void 0 && task.owner_sub === ctx.props.sub;
|
|
5549
|
+
} catch {
|
|
5550
|
+
return false;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5497
5553
|
function registerUiAgentAskTools(server2, ctx) {
|
|
5498
5554
|
server2.registerTool(
|
|
5499
5555
|
"ui_agent_reply",
|
|
@@ -5543,7 +5599,7 @@ function registerUiAgentAskTools(server2, ctx) {
|
|
|
5543
5599
|
}
|
|
5544
5600
|
const pendingKey = `${PENDING_ASK_PREFIX}${task_id}`;
|
|
5545
5601
|
const pendingRaw = await ctx.env.CARRIER_USERS.get(pendingKey);
|
|
5546
|
-
if (pendingRaw === null) {
|
|
5602
|
+
if (pendingRaw === null || !await callerOwnsTask(ctx, task_id)) {
|
|
5547
5603
|
return {
|
|
5548
5604
|
isError: true,
|
|
5549
5605
|
content: [
|
|
@@ -5685,6 +5741,8 @@ function registerUiAgentAskTools(server2, ctx) {
|
|
|
5685
5741
|
if (!raw) return null;
|
|
5686
5742
|
try {
|
|
5687
5743
|
const parsed = JSON.parse(raw);
|
|
5744
|
+
const taskId = parsed.task_id ?? name.slice(PENDING_ASK_PREFIX.length);
|
|
5745
|
+
if (!await callerOwnsTask(ctx, taskId)) return null;
|
|
5688
5746
|
return {
|
|
5689
5747
|
...parsed,
|
|
5690
5748
|
expires_at: computeExpiresAt(parsed.asked_at)
|
|
@@ -6662,6 +6720,33 @@ async function resolveBillingSub(env, orgId) {
|
|
|
6662
6720
|
return pointer ?? orgId;
|
|
6663
6721
|
}
|
|
6664
6722
|
|
|
6723
|
+
// src/scope-guard.ts
|
|
6724
|
+
function denyUnlessScoped(props2, toolName, scopes) {
|
|
6725
|
+
const required = scopes[toolName];
|
|
6726
|
+
if (required === void 0) {
|
|
6727
|
+
return {
|
|
6728
|
+
isError: true,
|
|
6729
|
+
content: [
|
|
6730
|
+
{
|
|
6731
|
+
type: "text",
|
|
6732
|
+
text: `Scope denied: tool '${toolName}' declares no scope, so it cannot be authorised. This is a server-side omission \u2014 add it to the tool's scope table.`
|
|
6733
|
+
}
|
|
6734
|
+
]
|
|
6735
|
+
};
|
|
6736
|
+
}
|
|
6737
|
+
const held = props2.scope ?? [];
|
|
6738
|
+
if (held.includes(required)) return null;
|
|
6739
|
+
return {
|
|
6740
|
+
isError: true,
|
|
6741
|
+
content: [
|
|
6742
|
+
{
|
|
6743
|
+
type: "text",
|
|
6744
|
+
text: `Scope denied: tool '${toolName}' requires '${required}' scope. Your token has: [${held.join(", ")}].`
|
|
6745
|
+
}
|
|
6746
|
+
]
|
|
6747
|
+
};
|
|
6748
|
+
}
|
|
6749
|
+
|
|
6665
6750
|
// src/wallet-tools.ts
|
|
6666
6751
|
var PACKS = [
|
|
6667
6752
|
{ id: "pack_500", eurCents: 5e4, bonusPct: 0 },
|
|
@@ -6858,6 +6943,8 @@ function registerWalletTools(server2, ctx) {
|
|
|
6858
6943
|
annotations: annotationsFor("wallet_balance", "read")
|
|
6859
6944
|
},
|
|
6860
6945
|
async () => {
|
|
6946
|
+
const denied = denyUnlessScoped(props2, "wallet_balance", WALLET_TOOL_SCOPES);
|
|
6947
|
+
if (denied) return denied;
|
|
6861
6948
|
const orgId = defaultOrgId;
|
|
6862
6949
|
const start = Date.now();
|
|
6863
6950
|
try {
|
|
@@ -6900,6 +6987,8 @@ function registerWalletTools(server2, ctx) {
|
|
|
6900
6987
|
annotations: annotationsFor("wallet_topup_checkout", "write")
|
|
6901
6988
|
},
|
|
6902
6989
|
async ({ pack }) => {
|
|
6990
|
+
const denied = denyUnlessScoped(props2, "wallet_topup_checkout", WALLET_TOOL_SCOPES);
|
|
6991
|
+
if (denied) return denied;
|
|
6903
6992
|
const orgId = defaultOrgId;
|
|
6904
6993
|
const start = Date.now();
|
|
6905
6994
|
if (!pack) {
|
|
@@ -6959,6 +7048,8 @@ function registerWalletTools(server2, ctx) {
|
|
|
6959
7048
|
annotations: annotationsFor("wallet_auto_topup", "write")
|
|
6960
7049
|
},
|
|
6961
7050
|
async ({ pack_cents }) => {
|
|
7051
|
+
const denied = denyUnlessScoped(props2, "wallet_auto_topup", WALLET_TOOL_SCOPES);
|
|
7052
|
+
if (denied) return denied;
|
|
6962
7053
|
const orgId = defaultOrgId;
|
|
6963
7054
|
const start = Date.now();
|
|
6964
7055
|
try {
|
|
@@ -7069,6 +7160,8 @@ function registerStripeConnectTools(server2, ctx) {
|
|
|
7069
7160
|
annotations: annotationsFor("stripe_connect_status", "read")
|
|
7070
7161
|
},
|
|
7071
7162
|
async ({ operator_id }) => {
|
|
7163
|
+
const denied = denyUnlessScoped(props2, "stripe_connect_status", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7164
|
+
if (denied) return denied;
|
|
7072
7165
|
if (operator_id !== void 0 && operator_id !== operatorId) {
|
|
7073
7166
|
if (!isPlatformOperator(env, props2.org_id)) {
|
|
7074
7167
|
return err2(
|
|
@@ -7129,6 +7222,8 @@ function registerStripeConnectTools(server2, ctx) {
|
|
|
7129
7222
|
annotations: annotationsFor("stripe_connect_payouts", "read")
|
|
7130
7223
|
},
|
|
7131
7224
|
async ({ limit, status }) => {
|
|
7225
|
+
const denied = denyUnlessScoped(props2, "stripe_connect_payouts", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7226
|
+
if (denied) return denied;
|
|
7132
7227
|
const start = Date.now();
|
|
7133
7228
|
try {
|
|
7134
7229
|
const stripeKey = env.STRIPE_SECRET_KEY;
|
|
@@ -7164,6 +7259,8 @@ function registerStripeConnectTools(server2, ctx) {
|
|
|
7164
7259
|
annotations: annotationsFor("stripe_connect_balance", "read")
|
|
7165
7260
|
},
|
|
7166
7261
|
async () => {
|
|
7262
|
+
const denied = denyUnlessScoped(props2, "stripe_connect_balance", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7263
|
+
if (denied) return denied;
|
|
7167
7264
|
const start = Date.now();
|
|
7168
7265
|
try {
|
|
7169
7266
|
const stripeKey = env.STRIPE_SECRET_KEY;
|
|
@@ -7206,6 +7303,8 @@ function registerStripeConnectTools(server2, ctx) {
|
|
|
7206
7303
|
annotations: annotationsFor("stripe_connect_refund", "write")
|
|
7207
7304
|
},
|
|
7208
7305
|
async ({ charge_id, amount_cents, reason, confirm_token }) => {
|
|
7306
|
+
const denied = denyUnlessScoped(props2, "stripe_connect_refund", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7307
|
+
if (denied) return denied;
|
|
7209
7308
|
const start = Date.now();
|
|
7210
7309
|
const toolName = "stripe_connect_refund";
|
|
7211
7310
|
if (!confirm_token) {
|
|
@@ -7258,6 +7357,8 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
|
|
|
7258
7357
|
annotations: annotationsFor("stripe_connect_dispute_list", "read")
|
|
7259
7358
|
},
|
|
7260
7359
|
async ({ limit, status }) => {
|
|
7360
|
+
const denied = denyUnlessScoped(props2, "stripe_connect_dispute_list", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7361
|
+
if (denied) return denied;
|
|
7261
7362
|
const start = Date.now();
|
|
7262
7363
|
try {
|
|
7263
7364
|
const stripeKey = env.STRIPE_SECRET_KEY;
|
|
@@ -7296,6 +7397,13 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
|
|
|
7296
7397
|
annotations: annotationsFor("radar_review_list", "read")
|
|
7297
7398
|
},
|
|
7298
7399
|
async ({ open_only, limit }) => {
|
|
7400
|
+
const denied = denyUnlessScoped(props2, "radar_review_list", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7401
|
+
if (denied) return denied;
|
|
7402
|
+
if (!isPlatformOperator(env, props2.org_id)) {
|
|
7403
|
+
return err2(
|
|
7404
|
+
"This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
|
|
7405
|
+
);
|
|
7406
|
+
}
|
|
7299
7407
|
const start = Date.now();
|
|
7300
7408
|
try {
|
|
7301
7409
|
const stripeKey = env.STRIPE_SECRET_KEY;
|
|
@@ -7332,6 +7440,13 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
|
|
|
7332
7440
|
annotations: annotationsFor("radar_review_approve", "admin")
|
|
7333
7441
|
},
|
|
7334
7442
|
async ({ review_id, confirm_token }) => {
|
|
7443
|
+
const denied = denyUnlessScoped(props2, "radar_review_approve", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7444
|
+
if (denied) return denied;
|
|
7445
|
+
if (!isPlatformOperator(env, props2.org_id)) {
|
|
7446
|
+
return err2(
|
|
7447
|
+
"This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
|
|
7448
|
+
);
|
|
7449
|
+
}
|
|
7335
7450
|
const toolName = "radar_review_approve";
|
|
7336
7451
|
const start = Date.now();
|
|
7337
7452
|
if (!confirm_token) {
|
|
@@ -7381,6 +7496,13 @@ Call again with confirm_token="${token2}" to execute. Expires in 5 minutes.`
|
|
|
7381
7496
|
annotations: annotationsFor("radar_review_decline", "admin")
|
|
7382
7497
|
},
|
|
7383
7498
|
async ({ review_id, confirm_token }) => {
|
|
7499
|
+
const denied = denyUnlessScoped(props2, "radar_review_decline", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7500
|
+
if (denied) return denied;
|
|
7501
|
+
if (!isPlatformOperator(env, props2.org_id)) {
|
|
7502
|
+
return err2(
|
|
7503
|
+
"This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
|
|
7504
|
+
);
|
|
7505
|
+
}
|
|
7384
7506
|
const toolName = "radar_review_decline";
|
|
7385
7507
|
const start = Date.now();
|
|
7386
7508
|
if (!confirm_token) {
|
|
@@ -7431,6 +7553,13 @@ Expires in 5 minutes.`
|
|
|
7431
7553
|
annotations: annotationsFor("radar_value_list_add", "admin")
|
|
7432
7554
|
},
|
|
7433
7555
|
async ({ value_list_id, value, confirm_token }) => {
|
|
7556
|
+
const denied = denyUnlessScoped(props2, "radar_value_list_add", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7557
|
+
if (denied) return denied;
|
|
7558
|
+
if (!isPlatformOperator(env, props2.org_id)) {
|
|
7559
|
+
return err2(
|
|
7560
|
+
"This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
|
|
7561
|
+
);
|
|
7562
|
+
}
|
|
7434
7563
|
const toolName = "radar_value_list_add";
|
|
7435
7564
|
const start = Date.now();
|
|
7436
7565
|
if (!confirm_token) {
|
|
@@ -7480,6 +7609,13 @@ Expires in 5 minutes.`
|
|
|
7480
7609
|
annotations: annotationsFor("radar_rule_toggle", "admin")
|
|
7481
7610
|
},
|
|
7482
7611
|
async ({ rule_id, enabled }) => {
|
|
7612
|
+
const denied = denyUnlessScoped(props2, "radar_rule_toggle", STRIPE_CONNECT_TOOL_SCOPES);
|
|
7613
|
+
if (denied) return denied;
|
|
7614
|
+
if (!isPlatformOperator(env, props2.org_id)) {
|
|
7615
|
+
return err2(
|
|
7616
|
+
"This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
|
|
7617
|
+
);
|
|
7618
|
+
}
|
|
7483
7619
|
return ok2(
|
|
7484
7620
|
`Stripe Radar does not expose rule enable/disable via the public API.
|
|
7485
7621
|
To ${enabled ? "enable" : "disable"} rule ${rule_id}:
|
|
@@ -7692,7 +7828,8 @@ function registerStorefrontDeployTools(server2) {
|
|
|
7692
7828
|
var TIER_CREDIT_ALLOTMENTS = {
|
|
7693
7829
|
free: 5e3,
|
|
7694
7830
|
pro: 1e5,
|
|
7695
|
-
enterprise: Infinity
|
|
7831
|
+
enterprise: Infinity,
|
|
7832
|
+
superadmin: Infinity
|
|
7696
7833
|
};
|
|
7697
7834
|
var DAILY_FREE_CREDITS = 50;
|
|
7698
7835
|
var SCOPE_CREDIT_COSTS = {
|
|
@@ -7715,7 +7852,7 @@ async function checkCredits(env, sub, tier, scope) {
|
|
|
7715
7852
|
const month = currentMonth2();
|
|
7716
7853
|
const today = currentDay();
|
|
7717
7854
|
const resetAt = firstDayNextMonth2();
|
|
7718
|
-
if (tier === "enterprise") {
|
|
7855
|
+
if (tier === "enterprise" || tier === "superadmin") {
|
|
7719
7856
|
return {
|
|
7720
7857
|
allowed: true,
|
|
7721
7858
|
credits_remaining: Infinity,
|
|
@@ -7755,7 +7892,7 @@ function deductCredits(env, sub, tier, scope) {
|
|
|
7755
7892
|
const cost = SCOPE_CREDIT_COSTS[scope];
|
|
7756
7893
|
const month = currentMonth2();
|
|
7757
7894
|
const today = currentDay();
|
|
7758
|
-
if (tier === "enterprise") return;
|
|
7895
|
+
if (tier === "enterprise" || tier === "superadmin") return;
|
|
7759
7896
|
const dailyFree = await getDailyFreeCredits(env, sub, today);
|
|
7760
7897
|
const dailyRemaining = dailyFree.granted - dailyFree.consumed;
|
|
7761
7898
|
if (dailyRemaining >= cost) {
|
|
@@ -7961,7 +8098,7 @@ async function resolveUserTier(env, sub) {
|
|
|
7961
8098
|
const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
|
|
7962
8099
|
if (raw && typeof raw === "object" && "tier" in raw) {
|
|
7963
8100
|
const t = raw.tier;
|
|
7964
|
-
if (t === "pro" || t === "enterprise") return t;
|
|
8101
|
+
if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
|
|
7965
8102
|
}
|
|
7966
8103
|
return "free";
|
|
7967
8104
|
}
|
|
@@ -8011,6 +8148,14 @@ var DEFAULT_THRESHOLD_CONFIGS = {
|
|
|
8011
8148
|
auto_pause_on_cap: false,
|
|
8012
8149
|
notification_email: null,
|
|
8013
8150
|
webhook_url: null
|
|
8151
|
+
},
|
|
8152
|
+
superadmin: {
|
|
8153
|
+
notification_thresholds: [],
|
|
8154
|
+
hard_cap_cents: 0,
|
|
8155
|
+
invoice_threshold_cents: 0,
|
|
8156
|
+
auto_pause_on_cap: false,
|
|
8157
|
+
notification_email: null,
|
|
8158
|
+
webhook_url: null
|
|
8014
8159
|
}
|
|
8015
8160
|
};
|
|
8016
8161
|
async function getThresholdConfig(env, sub, tier) {
|
|
@@ -8033,7 +8178,7 @@ async function resolveUserTier2(env, sub) {
|
|
|
8033
8178
|
const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
|
|
8034
8179
|
if (raw && typeof raw === "object" && "tier" in raw) {
|
|
8035
8180
|
const t = raw.tier;
|
|
8036
|
-
if (t === "pro" || t === "enterprise") return t;
|
|
8181
|
+
if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
|
|
8037
8182
|
}
|
|
8038
8183
|
return "free";
|
|
8039
8184
|
}
|
|
@@ -9971,7 +10116,82 @@ function buildExamples(toolName) {
|
|
|
9971
10116
|
// src/list-recent-ocs-events.ts
|
|
9972
10117
|
import { z as z12 } from "zod";
|
|
9973
10118
|
|
|
10119
|
+
// src/ocs-scoping.ts
|
|
10120
|
+
function managedOwnAccountId(auth) {
|
|
10121
|
+
if (!auth.managed || auth.accountId === void 0) return void 0;
|
|
10122
|
+
const n = Number(auth.accountId);
|
|
10123
|
+
return Number.isFinite(n) ? n : void 0;
|
|
10124
|
+
}
|
|
10125
|
+
function subscriberAccountId(data) {
|
|
10126
|
+
if (!data || typeof data !== "object") return void 0;
|
|
10127
|
+
const root = data;
|
|
10128
|
+
const sub = root.subscriber && typeof root.subscriber === "object" ? root.subscriber : root;
|
|
10129
|
+
const raw = sub.accountId ?? sub.account_id ?? sub.account;
|
|
10130
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
10131
|
+
const n = Number(raw);
|
|
10132
|
+
return Number.isFinite(n) ? n : void 0;
|
|
10133
|
+
}
|
|
10134
|
+
function isManagedSubscriberDenied(auth, subscriberData) {
|
|
10135
|
+
const ownId = managedOwnAccountId(auth);
|
|
10136
|
+
if (ownId === void 0) return false;
|
|
10137
|
+
const subAccountId = subscriberAccountId(subscriberData);
|
|
10138
|
+
if (subAccountId === void 0) return true;
|
|
10139
|
+
return subAccountId !== ownId;
|
|
10140
|
+
}
|
|
10141
|
+
async function assertManagedIccidAccess(baseUrl2, auth, iccid) {
|
|
10142
|
+
if (managedOwnAccountId(auth) === void 0) return { ok: true };
|
|
10143
|
+
try {
|
|
10144
|
+
const client2 = new OcsClient(baseUrl2, auth.token);
|
|
10145
|
+
const data = await client2.call("getSingleSubscriber", { iccid });
|
|
10146
|
+
if (isManagedSubscriberDenied(auth, data)) {
|
|
10147
|
+
return { ok: false, error: "Subscriber not found" };
|
|
10148
|
+
}
|
|
10149
|
+
return { ok: true };
|
|
10150
|
+
} catch (err7) {
|
|
10151
|
+
return {
|
|
10152
|
+
ok: false,
|
|
10153
|
+
error: err7 instanceof Error ? err7.message : String(err7)
|
|
10154
|
+
};
|
|
10155
|
+
}
|
|
10156
|
+
}
|
|
10157
|
+
|
|
9974
10158
|
// ../../packages/ocs-spec/src/ocs-event-buffer-read.ts
|
|
10159
|
+
function ocsEventRingBufferIccidFromDigitsReference(raw) {
|
|
10160
|
+
const digits = raw.replace(/\D/g, "");
|
|
10161
|
+
if (digits.length === 0) {
|
|
10162
|
+
throw new Error("reference must contain at least one digit");
|
|
10163
|
+
}
|
|
10164
|
+
if (digits.length > 20) {
|
|
10165
|
+
return digits.slice(-20);
|
|
10166
|
+
}
|
|
10167
|
+
if (digits.length >= 19) {
|
|
10168
|
+
return digits;
|
|
10169
|
+
}
|
|
10170
|
+
return digits.padStart(19, "0");
|
|
10171
|
+
}
|
|
10172
|
+
function msisdnFieldFromSubscriberPayload(data) {
|
|
10173
|
+
if (!data || typeof data !== "object") return null;
|
|
10174
|
+
const r = data;
|
|
10175
|
+
const v = r["msisdn"] ?? r["MSISDN"];
|
|
10176
|
+
if (v === void 0 || v === null) return null;
|
|
10177
|
+
if (typeof v === "string") return /\d/.test(v) ? v : null;
|
|
10178
|
+
if (typeof v === "number") return String(v);
|
|
10179
|
+
return null;
|
|
10180
|
+
}
|
|
10181
|
+
function mergeOcsEventOutputsNewestFirst(parts, limit) {
|
|
10182
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10183
|
+
const out = [];
|
|
10184
|
+
const combined = [...parts].flat().sort(
|
|
10185
|
+
(x, y) => x.timestamp < y.timestamp ? 1 : x.timestamp > y.timestamp ? -1 : x.event_id.localeCompare(y.event_id)
|
|
10186
|
+
);
|
|
10187
|
+
for (const e of combined) {
|
|
10188
|
+
if (seen.has(e.event_id)) continue;
|
|
10189
|
+
seen.add(e.event_id);
|
|
10190
|
+
out.push(e);
|
|
10191
|
+
if (out.length >= limit) break;
|
|
10192
|
+
}
|
|
10193
|
+
return out;
|
|
10194
|
+
}
|
|
9975
10195
|
async function listRecentOcsEvents(iccid, limit, eventTypes, since, kv) {
|
|
9976
10196
|
const routingRaw = await kv.get(`iccid:${iccid}`, "json");
|
|
9977
10197
|
const resellerId = routingRaw?.reseller_id ?? 0;
|
|
@@ -10054,7 +10274,8 @@ var listRecentOcsEventsSchema = {
|
|
|
10054
10274
|
async function listRecentOcsEvents2(iccid, limit, eventTypes, since, kv) {
|
|
10055
10275
|
return listRecentOcsEvents(iccid, limit, eventTypes, since, kv);
|
|
10056
10276
|
}
|
|
10057
|
-
function registerListRecentOcsEventsTool(server2,
|
|
10277
|
+
function registerListRecentOcsEventsTool(server2, ctx) {
|
|
10278
|
+
const env = ctx.env;
|
|
10058
10279
|
server2.registerTool(
|
|
10059
10280
|
"list_recent_ocs_events",
|
|
10060
10281
|
{
|
|
@@ -10070,6 +10291,19 @@ function registerListRecentOcsEventsTool(server2, env) {
|
|
|
10070
10291
|
async (args) => {
|
|
10071
10292
|
const { iccid, limit, event_types, since } = args;
|
|
10072
10293
|
try {
|
|
10294
|
+
if (managedOwnAccountId(ctx.managedAuth) !== void 0) {
|
|
10295
|
+
const access = await assertManagedIccidAccess(
|
|
10296
|
+
env.CARRIER_OCS_BASE_URL,
|
|
10297
|
+
{ token: await ctx.getUserToken(ctx.props.sub), ...ctx.managedAuth },
|
|
10298
|
+
iccid
|
|
10299
|
+
);
|
|
10300
|
+
if (!access.ok) {
|
|
10301
|
+
return {
|
|
10302
|
+
isError: true,
|
|
10303
|
+
content: [{ type: "text", text: access.error }]
|
|
10304
|
+
};
|
|
10305
|
+
}
|
|
10306
|
+
}
|
|
10073
10307
|
const result2 = await listRecentOcsEvents2(
|
|
10074
10308
|
iccid,
|
|
10075
10309
|
limit,
|
|
@@ -10187,6 +10421,20 @@ function ok5(payload) {
|
|
|
10187
10421
|
function err5(message) {
|
|
10188
10422
|
return { isError: true, content: [{ type: "text", text: message }] };
|
|
10189
10423
|
}
|
|
10424
|
+
async function ownershipToken(ctx) {
|
|
10425
|
+
if (managedOwnAccountId(ctx.managedAuth) === void 0) return null;
|
|
10426
|
+
return ctx.getUserToken(ctx.props.sub);
|
|
10427
|
+
}
|
|
10428
|
+
async function ownsEvent(ctx, token2, iccid) {
|
|
10429
|
+
if (token2 === null) return true;
|
|
10430
|
+
if (!iccid) return false;
|
|
10431
|
+
const access = await assertManagedIccidAccess(
|
|
10432
|
+
ctx.env.CARRIER_OCS_BASE_URL,
|
|
10433
|
+
{ token: token2, ...ctx.managedAuth },
|
|
10434
|
+
iccid
|
|
10435
|
+
);
|
|
10436
|
+
return access.ok;
|
|
10437
|
+
}
|
|
10190
10438
|
function registerDepletionEventsTool(server2, ctx) {
|
|
10191
10439
|
server2.registerTool(
|
|
10192
10440
|
"subscriber_depletion_events",
|
|
@@ -10216,6 +10464,14 @@ function registerDepletionEventsTool(server2, ctx) {
|
|
|
10216
10464
|
);
|
|
10217
10465
|
}
|
|
10218
10466
|
const { subscriberId, since } = args;
|
|
10467
|
+
let scopeToken;
|
|
10468
|
+
try {
|
|
10469
|
+
scopeToken = await ownershipToken(ctx);
|
|
10470
|
+
} catch (e) {
|
|
10471
|
+
return err5(
|
|
10472
|
+
`Could not resolve the OCS credential needed to scope this read: ${e instanceof Error ? e.message : String(e)}`
|
|
10473
|
+
);
|
|
10474
|
+
}
|
|
10219
10475
|
if (subscriberId) {
|
|
10220
10476
|
const raw = await kv.get(`bundle-depleted:${subscriberId}`, "text");
|
|
10221
10477
|
if (!raw) {
|
|
@@ -10237,6 +10493,14 @@ function registerDepletionEventsTool(server2, ctx) {
|
|
|
10237
10493
|
note: "Stored depletion entry was invalid JSON and was ignored."
|
|
10238
10494
|
});
|
|
10239
10495
|
}
|
|
10496
|
+
if (!await ownsEvent(ctx, scopeToken, event.iccid)) {
|
|
10497
|
+
return ok5({
|
|
10498
|
+
subscriber_id: subscriberId,
|
|
10499
|
+
depleted: false,
|
|
10500
|
+
event: null,
|
|
10501
|
+
note: "No bundle depletion event found in the last 7 days."
|
|
10502
|
+
});
|
|
10503
|
+
}
|
|
10240
10504
|
if (since && event.depletedAt < since) {
|
|
10241
10505
|
return ok5({
|
|
10242
10506
|
subscriber_id: subscriberId,
|
|
@@ -10273,6 +10537,7 @@ function registerDepletionEventsTool(server2, ctx) {
|
|
|
10273
10537
|
try {
|
|
10274
10538
|
const ev = JSON.parse(raw);
|
|
10275
10539
|
if (since && ev.depletedAt < since) return;
|
|
10540
|
+
if (!await ownsEvent(ctx, scopeToken, ev.iccid)) return;
|
|
10276
10541
|
events.push({
|
|
10277
10542
|
subscriber_id: ev.subscriberId,
|
|
10278
10543
|
iccid: ev.iccid,
|
|
@@ -10310,19 +10575,46 @@ function registerCountryHistoryTool(server2, ctx) {
|
|
|
10310
10575
|
},
|
|
10311
10576
|
wrapHandler(
|
|
10312
10577
|
"subscriber_country_history",
|
|
10313
|
-
"
|
|
10578
|
+
"getSingleSubscriber",
|
|
10314
10579
|
"read",
|
|
10315
10580
|
ctx,
|
|
10316
|
-
async ({ subscriberId, limit }) => {
|
|
10581
|
+
async ({ subscriberId, limit }, token2) => {
|
|
10317
10582
|
const effectiveLimit = limit ?? 20;
|
|
10318
|
-
const
|
|
10583
|
+
const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
|
|
10584
|
+
const subscriberPayload = await client2.call("getSingleSubscriber", {
|
|
10585
|
+
iccid: subscriberId
|
|
10586
|
+
});
|
|
10587
|
+
let syntheticRingIccid = null;
|
|
10588
|
+
const msisdnRaw = msisdnFieldFromSubscriberPayload(subscriberPayload);
|
|
10589
|
+
if (msisdnRaw) {
|
|
10590
|
+
try {
|
|
10591
|
+
const k = ocsEventRingBufferIccidFromDigitsReference(msisdnRaw);
|
|
10592
|
+
if (k !== subscriberId) syntheticRingIccid = k;
|
|
10593
|
+
} catch {
|
|
10594
|
+
syntheticRingIccid = null;
|
|
10595
|
+
}
|
|
10596
|
+
}
|
|
10597
|
+
const primary = await listRecentOcsEvents(
|
|
10319
10598
|
subscriberId,
|
|
10320
10599
|
effectiveLimit,
|
|
10321
10600
|
["country.entered"],
|
|
10322
10601
|
void 0,
|
|
10323
10602
|
ctx.env.OCS_EVENT_ROUTING
|
|
10324
10603
|
);
|
|
10325
|
-
|
|
10604
|
+
let countryEvents = primary.events;
|
|
10605
|
+
if (syntheticRingIccid) {
|
|
10606
|
+
const secondary = await listRecentOcsEvents(
|
|
10607
|
+
syntheticRingIccid,
|
|
10608
|
+
effectiveLimit,
|
|
10609
|
+
["country.entered"],
|
|
10610
|
+
void 0,
|
|
10611
|
+
ctx.env.OCS_EVENT_ROUTING
|
|
10612
|
+
);
|
|
10613
|
+
countryEvents = mergeOcsEventOutputsNewestFirst(
|
|
10614
|
+
[primary.events, secondary.events],
|
|
10615
|
+
effectiveLimit
|
|
10616
|
+
);
|
|
10617
|
+
}
|
|
10326
10618
|
return {
|
|
10327
10619
|
content: [
|
|
10328
10620
|
{
|
|
@@ -10340,7 +10632,7 @@ function registerCountryHistoryTool(server2, ctx) {
|
|
|
10340
10632
|
source: e.data["source"] ?? "relay-lu"
|
|
10341
10633
|
})),
|
|
10342
10634
|
total_country_events: countryEvents.length,
|
|
10343
|
-
total_in_buffer:
|
|
10635
|
+
total_in_buffer: primary.total_in_buffer,
|
|
10344
10636
|
note: countryEvents.length === 0 ? "No cross-border events recorded in the 24h ring-buffer for this subscriber." : void 0
|
|
10345
10637
|
},
|
|
10346
10638
|
null,
|
|
@@ -11906,7 +12198,13 @@ var props = {
|
|
|
11906
12198
|
var audit = (_row) => {
|
|
11907
12199
|
};
|
|
11908
12200
|
var getUserToken = async (_sub) => token;
|
|
11909
|
-
var toolCtx = {
|
|
12201
|
+
var toolCtx = {
|
|
12202
|
+
env: stdioEnv,
|
|
12203
|
+
props,
|
|
12204
|
+
managedAuth: {},
|
|
12205
|
+
audit,
|
|
12206
|
+
getUserToken
|
|
12207
|
+
};
|
|
11910
12208
|
var server = new McpServer(
|
|
11911
12209
|
{ name: "carrier-mcp", version: CARRIER_VERSION },
|
|
11912
12210
|
{
|
|
@@ -11918,7 +12216,7 @@ registerAllTools(server, toolCtx);
|
|
|
11918
12216
|
registerIntelligenceTools(server, toolCtx);
|
|
11919
12217
|
registerAllBacklogTools(server, toolCtx);
|
|
11920
12218
|
registerAllCarrierAskTools(server, toolCtx);
|
|
11921
|
-
registerListRecentOcsEventsTool(server,
|
|
12219
|
+
registerListRecentOcsEventsTool(server, toolCtx);
|
|
11922
12220
|
registerRateLimitStatusTool(server, toolCtx);
|
|
11923
12221
|
registerCountryHistoryTool(server, toolCtx);
|
|
11924
12222
|
registerDepletionEventsTool(server, toolCtx);
|