@carrierllc/mcp 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  subscriberIdParams,
38
38
  usageOverPeriodParams,
39
39
  verifyStorefront
40
- } from "./chunk-JROD4YAU.js";
40
+ } from "./chunk-V4CYEMLJ.js";
41
41
  import "./chunk-SHKKVIIA.js";
42
42
 
43
43
  // src/index.ts
@@ -69,11 +69,11 @@ var OcsClient = class {
69
69
  async call(method, params = {}) {
70
70
  await acquireEndpointSlot(this.token, method, "interactive");
71
71
  const url = `${this.baseUrl}/v1?token=${this.token}`;
72
- const body = JSON.stringify({ [method]: params });
72
+ const body2 = JSON.stringify({ [method]: params });
73
73
  const res = await fetch(url, {
74
74
  method: "POST",
75
75
  headers: { "Content-Type": "application/json" },
76
- body
76
+ body: body2
77
77
  });
78
78
  if (!res.ok) {
79
79
  throw new OcsApiError(res.status, `HTTP ${res.status} ${res.statusText}`, method);
@@ -98,21 +98,221 @@ 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(req, accept) {
238
+ const headers = {
239
+ "Content-Type": "application/json",
240
+ Accept: accept
241
+ };
242
+ const meta = req.attribution ? requestMetadata(req.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 { aws, region, modelId } = client(creds);
265
+ const controller = new AbortController();
266
+ const timer = setTimeout(() => controller.abort(), req.timeoutMs ?? BEDROCK_TIMEOUT_MS);
267
+ const onAbort = () => controller.abort();
268
+ signal?.addEventListener("abort", onAbort, { once: true });
269
+ try {
270
+ const headers = headersFor(req, "application/json");
271
+ const resp = await aws.fetch(
272
+ `https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`,
273
+ {
274
+ method: "POST",
275
+ headers,
276
+ body: JSON.stringify(body(req)),
277
+ signal: controller.signal
278
+ }
279
+ );
280
+ if (!resp.ok) await fail(resp);
281
+ const json = await resp.json();
282
+ reportUsage(req, extractUsage(json));
283
+ const block = json.content?.find((b) => b.type === "tool_use");
284
+ return block?.name ? { name: block.name, input: block.input ?? {} } : null;
285
+ } finally {
286
+ clearTimeout(timer);
287
+ signal?.removeEventListener("abort", onAbort);
288
+ }
289
+ }
290
+
101
291
  // src/billing.ts
102
292
  var TIER_CALL_LIMITS = {
103
- free: 5e3,
104
- pro: 5e4,
105
- enterprise: Infinity
293
+ free: 1e3,
294
+ pro: Infinity,
295
+ enterprise: Infinity,
296
+ superadmin: Infinity
106
297
  };
298
+ var CARRIER_ASK_PRO_MONTHLY_LIMIT = AI_TIER_POLICY.pro.limit;
299
+ var CARRIER_ASK_FREE_DAILY_LIMIT = AI_TIER_POLICY.free.limit;
107
300
  var UPGRADE_URL = "https://mcp.carrier.llc/upgrade";
108
- function getBillingPrimary(env) {
109
- const raw = env.BILLING_PRIMARY;
110
- return raw === "clerk" ? "clerk" : "stripe";
301
+ function platformOrgId(env) {
302
+ const raw = env.CARRIER_PLATFORM_ORG_ID;
303
+ if (typeof raw !== "string") return null;
304
+ const trimmed = raw.trim();
305
+ return trimmed.length > 0 ? trimmed : null;
306
+ }
307
+ function isPlatformOperator(env, orgId) {
308
+ const platform = platformOrgId(env);
309
+ if (!platform) return false;
310
+ return Boolean(orgId) && orgId === platform;
111
311
  }
112
312
  async function checkCallQuota(env, sub, tier) {
113
313
  const limit = TIER_CALL_LIMITS[tier];
114
314
  const resetAt = firstDayNextMonth();
115
- if (tier === "enterprise") {
315
+ if (tier === "pro" || tier === "enterprise" || tier === "superadmin") {
116
316
  return { allowed: true, remaining: Infinity, resetAt, tier };
117
317
  }
118
318
  if (!env.CARRIER_USERS) {
@@ -133,6 +333,7 @@ async function checkCallQuota(env, sub, tier) {
133
333
  function recordUsage(env, sub, tier) {
134
334
  if (!env.CARRIER_USERS) return;
135
335
  (async () => {
336
+ if (tier === "pro" || tier === "enterprise" || tier === "superadmin") return;
136
337
  const month = currentMonth();
137
338
  const usageKey = `usage:${sub}:${month}`;
138
339
  const raw = await env.CARRIER_USERS.get(usageKey, "json").catch(() => null);
@@ -143,35 +344,8 @@ function recordUsage(env, sub, tier) {
143
344
  JSON.stringify({ calls: next, updated_at: (/* @__PURE__ */ new Date()).toISOString() }),
144
345
  { expirationTtl: 35 * 24 * 60 * 60 }
145
346
  );
146
- if (tier === "pro" && next % 100 === 0 && getBillingPrimary(env) === "stripe") {
147
- await pushStripeUsageRecord(env, sub, 100).catch(() => {
148
- });
149
- }
150
347
  })();
151
348
  }
152
- async function pushStripeUsageRecord(env, sub, quantity) {
153
- const stripeKey = env.STRIPE_SECRET_KEY;
154
- if (!stripeKey) return;
155
- if (!env.CARRIER_USERS) return;
156
- const subItemId = await env.CARRIER_USERS.get(`stripe_sub_item_id:${sub}`);
157
- if (!subItemId) return;
158
- const body = new URLSearchParams({
159
- quantity: String(quantity),
160
- timestamp: String(Math.floor(Date.now() / 1e3)),
161
- action: "increment"
162
- });
163
- await fetch(
164
- `https://api.stripe.com/v1/subscription_items/${subItemId}/usage_records`,
165
- {
166
- method: "POST",
167
- headers: {
168
- Authorization: `Bearer ${stripeKey}`,
169
- "Content-Type": "application/x-www-form-urlencoded"
170
- },
171
- body: body.toString()
172
- }
173
- );
174
- }
175
349
  function currentMonth() {
176
350
  const now = /* @__PURE__ */ new Date();
177
351
  return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -520,8 +694,8 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
520
694
  {
521
695
  type: "text",
522
696
  text: [
523
- `Quota exceeded: your ${quota.tier} plan has reached the safety limit of 100,000 regular tool calls/month.`,
524
- `This limit exists to prevent runaway automation. Resets ${quota.resetAt}.`,
697
+ `Quota exceeded: your ${quota.tier} plan has reached the monthly limit of ${TIER_CALL_LIMITS.free.toLocaleString()} regular tool calls.`,
698
+ `Resets ${quota.resetAt}.`,
525
699
  `Upgrade to Pro for unlimited calls at ${UPGRADE_URL}`
526
700
  ].join(" ")
527
701
  }
@@ -589,8 +763,8 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
589
763
  };
590
764
  }
591
765
  async function ocsCall(env, token2, method, params = {}) {
592
- const client = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
593
- const result2 = await client.call(method, params);
766
+ const client2 = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
767
+ const result2 = await client2.call(method, params);
594
768
  return {
595
769
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
596
770
  };
@@ -598,14 +772,14 @@ async function ocsCall(env, token2, method, params = {}) {
598
772
  async function resolveSubscriberByIccid(env, token2, iccid, cache) {
599
773
  const hit = cache.get(iccid);
600
774
  if (hit) return hit;
601
- const client = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
602
- const record = await client.call("getSingleSubscriber", { iccid });
775
+ const client2 = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
776
+ const record = await client2.call("getSingleSubscriber", { iccid });
603
777
  cache.set(iccid, record);
604
778
  return record;
605
779
  }
606
780
  async function getDefaultResellerId(env, token2) {
607
- const client = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
608
- const info = await client.call("getResellerInfo", {});
781
+ const client2 = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
782
+ const info = await client2.call("getResellerInfo", {});
609
783
  const id = info?.id;
610
784
  if (typeof id !== "number") {
611
785
  throw new Error("Could not determine resellerId from getResellerInfo");
@@ -809,8 +983,8 @@ function registerAllTools(server2, ctx) {
809
983
  ctx,
810
984
  async (args, token2) => {
811
985
  const params = buildListSubscriberParams(args);
812
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
813
- const raw = await client.call("listSubscriber", params);
986
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
987
+ const raw = await client2.call("listSubscriber", params);
814
988
  const payload = applySubscriberFilters(raw, args);
815
989
  return {
816
990
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
@@ -3286,8 +3460,8 @@ var ocsAppMethods = ocsSpec.v1_app_methods;
3286
3460
  // src/intelligence.ts
3287
3461
  async function safeCall(env, token2, method, params = {}) {
3288
3462
  try {
3289
- const client = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
3290
- const data = await client.call(method, params);
3463
+ const client2 = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
3464
+ const data = await client2.call(method, params);
3291
3465
  return { data, error: null };
3292
3466
  } catch (err7) {
3293
3467
  return { data: null, error: err7 instanceof Error ? err7.message : String(err7) };
@@ -4304,8 +4478,8 @@ function registerAllBacklogTools(server2, ctx) {
4304
4478
  ctx,
4305
4479
  async ({ iccid, phone_number, phone_type }, token2) => {
4306
4480
  const ocsMethod = phone_type === "fake" ? "affectSubscriberFakePhoneNumber" : "affectSubscriberRealPhoneNumber";
4307
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4308
- const result2 = await client.call(ocsMethod, { subscriber: iccid, phoneNumber: phone_number });
4481
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4482
+ const result2 = await client2.call(ocsMethod, { subscriber: iccid, phoneNumber: phone_number });
4309
4483
  return {
4310
4484
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
4311
4485
  };
@@ -4342,11 +4516,11 @@ function registerAllBacklogTools(server2, ctx) {
4342
4516
  cell_id,
4343
4517
  signal_strength
4344
4518
  }, token2) => {
4345
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4519
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4346
4520
  const params = { radioType: radio_type, mcc, mnc, lac };
4347
4521
  if (cell_id !== void 0) params.cellId = cell_id;
4348
4522
  if (signal_strength !== void 0) params.signalStrength = signal_strength;
4349
- const result2 = await client.call("getSubscriberLocationByCellId", params);
4523
+ const result2 = await client2.call("getSubscriberLocationByCellId", params);
4350
4524
  return {
4351
4525
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
4352
4526
  };
@@ -4370,16 +4544,16 @@ function registerAllBacklogTools(server2, ctx) {
4370
4544
  ctx,
4371
4545
  async ({ resellerId }, token2) => {
4372
4546
  const id = resellerId ?? await (async () => {
4373
- const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4374
- const info = await client2.call("getResellerInfo", {});
4547
+ const client3 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4548
+ const info = await client3.call("getResellerInfo", {});
4375
4549
  const resolvedId = info?.id;
4376
4550
  if (typeof resolvedId !== "number") {
4377
4551
  throw new Error("Could not determine resellerId from getResellerInfo");
4378
4552
  }
4379
4553
  return resolvedId;
4380
4554
  })();
4381
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4382
- const result2 = await client.call("listDetailedDestinationList", id);
4555
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4556
+ const result2 = await client2.call("listDetailedDestinationList", id);
4383
4557
  return {
4384
4558
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
4385
4559
  };
@@ -4404,8 +4578,8 @@ function registerAllBacklogTools(server2, ctx) {
4404
4578
  BACKLOG_TOOL_SCOPES["modify_subscriber_mobile_plan"],
4405
4579
  ctx,
4406
4580
  async ({ iccid, mobile_plan_id }, token2) => {
4407
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4408
- const result2 = await client.call(
4581
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4582
+ const result2 = await client2.call(
4409
4583
  "modifySubscriberMobilePlan",
4410
4584
  { subscriber: iccid, mobilePlanId: mobile_plan_id }
4411
4585
  );
@@ -4438,8 +4612,8 @@ function registerAllBacklogTools(server2, ctx) {
4438
4612
  const params = { subscriber: iccid, packageId: package_id };
4439
4613
  if (start_date !== void 0) params.startDate = start_date;
4440
4614
  if (end_date !== void 0) params.endDate = end_date;
4441
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4442
- const result2 = await client.call(
4615
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4616
+ const result2 = await client2.call(
4443
4617
  "modifySubscriberPrepaidPackageActivePeriod",
4444
4618
  params
4445
4619
  );
@@ -4467,8 +4641,8 @@ function registerAllBacklogTools(server2, ctx) {
4467
4641
  BACKLOG_TOOL_SCOPES["modify_subscriber_voip_plan"],
4468
4642
  ctx,
4469
4643
  async ({ iccid, voip_plan_id }, token2) => {
4470
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4471
- const result2 = await client.call(
4644
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4645
+ const result2 = await client2.call(
4472
4646
  "modifySubscriberVoipPlan",
4473
4647
  { subscriber: iccid, voipPlanId: voip_plan_id }
4474
4648
  );
@@ -4498,8 +4672,8 @@ function registerAllBacklogTools(server2, ctx) {
4498
4672
  const cache = /* @__PURE__ */ new Map();
4499
4673
  const sub = await resolveSubscriberByIccid(ctx.env, token2, iccid, cache);
4500
4674
  const subscriberId = sub.id ?? sub.subscriberId ?? iccid;
4501
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4502
- const result2 = await client.call(
4675
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4676
+ const result2 = await client2.call(
4503
4677
  "pushSteeringToSubs",
4504
4678
  { subscriber: subscriberId }
4505
4679
  );
@@ -4526,8 +4700,8 @@ function registerAllBacklogTools(server2, ctx) {
4526
4700
  BACKLOG_TOOL_SCOPES["reset_subscriber_gz_counter"],
4527
4701
  ctx,
4528
4702
  async ({ iccid }, token2) => {
4529
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4530
- const result2 = await client.call("resetSubsGzCounter", { subscriber: iccid });
4703
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4704
+ const result2 = await client2.call("resetSubsGzCounter", { subscriber: iccid });
4531
4705
  return {
4532
4706
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
4533
4707
  };
@@ -4550,14 +4724,14 @@ function registerAllBacklogTools(server2, ctx) {
4550
4724
  BACKLOG_TOOL_SCOPES["carrier_webhook_config"],
4551
4725
  ctx,
4552
4726
  async ({ reseller_id }, token2) => {
4553
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4727
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4554
4728
  let resellerId = reseller_id;
4555
4729
  if (resellerId === void 0) {
4556
4730
  resellerId = await getDefaultResellerId(ctx.env, token2);
4557
4731
  }
4558
4732
  const params = {};
4559
4733
  if (resellerId !== void 0) params.id = resellerId;
4560
- const raw = await client.call("getResellerInfo", params);
4734
+ const raw = await client2.call("getResellerInfo", params);
4561
4735
  const traffic = raw.trafficInfo ?? {};
4562
4736
  const notification = raw.notificationInfo ?? {};
4563
4737
  const result2 = {
@@ -4592,12 +4766,12 @@ function registerAllBacklogTools(server2, ctx) {
4592
4766
  BACKLOG_TOOL_SCOPES["list_subscriber_voip_tariff"],
4593
4767
  ctx,
4594
4768
  async ({ reseller_id }, token2) => {
4595
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4769
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4596
4770
  const params = {};
4597
4771
  if (reseller_id !== void 0) {
4598
4772
  params.resellerId = reseller_id;
4599
4773
  }
4600
- const result2 = await client.call("listSubscriberVoipTariff", params);
4774
+ const result2 = await client2.call("listSubscriberVoipTariff", params);
4601
4775
  return {
4602
4776
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
4603
4777
  };
@@ -4622,8 +4796,8 @@ function registerAllBacklogTools(server2, ctx) {
4622
4796
  BACKLOG_TOOL_SCOPES["list_voip_tariff_rule"],
4623
4797
  ctx,
4624
4798
  async ({ voip_plan_id }, token2) => {
4625
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4626
- const result2 = await client.call("listVoipTariffRule", voip_plan_id);
4799
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4800
+ const result2 = await client2.call("listVoipTariffRule", voip_plan_id);
4627
4801
  return {
4628
4802
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
4629
4803
  };
@@ -4651,8 +4825,8 @@ function registerAllBacklogTools(server2, ctx) {
4651
4825
  location_zone_id,
4652
4826
  network_profile_id
4653
4827
  }, token2) => {
4654
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4655
- const result2 = await client.call("changeNetworkProfileOfLocationZone", {
4828
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
4829
+ const result2 = await client2.call("changeNetworkProfileOfLocationZone", {
4656
4830
  locationZoneId: location_zone_id,
4657
4831
  networkProfileId: network_profile_id
4658
4832
  });
@@ -4665,7 +4839,6 @@ function registerAllBacklogTools(server2, ctx) {
4665
4839
  }
4666
4840
 
4667
4841
  // src/tools-carrier-ask.ts
4668
- import { AwsClient } from "aws4fetch";
4669
4842
  import { z as z11 } from "zod";
4670
4843
 
4671
4844
  // src/tools-ui-agent.ts
@@ -4711,7 +4884,7 @@ var MANUS_API_BASE = "https://api.manus.ai/v2";
4711
4884
 
4712
4885
  // src/tools-ui-agent.ts
4713
4886
  async function createManusTask(apiKey, prompt, title, outputSchema) {
4714
- const body = {
4887
+ const body2 = {
4715
4888
  message: { content: prompt },
4716
4889
  agent_profile: "manus-1.6-lite",
4717
4890
  hide_in_task_list: true,
@@ -4719,7 +4892,7 @@ async function createManusTask(apiKey, prompt, title, outputSchema) {
4719
4892
  title
4720
4893
  };
4721
4894
  if (outputSchema) {
4722
- body.structured_output_schema = outputSchema;
4895
+ body2.structured_output_schema = outputSchema;
4723
4896
  }
4724
4897
  const res = await fetch(`${MANUS_API_BASE}/task.create`, {
4725
4898
  method: "POST",
@@ -4727,7 +4900,7 @@ async function createManusTask(apiKey, prompt, title, outputSchema) {
4727
4900
  "x-manus-api-key": apiKey,
4728
4901
  "Content-Type": "application/json"
4729
4902
  },
4730
- body: JSON.stringify(body)
4903
+ body: JSON.stringify(body2)
4731
4904
  });
4732
4905
  return await res.json();
4733
4906
  }
@@ -4932,6 +5105,7 @@ var UI_AGENT_TOOL_SCOPES = {
4932
5105
  ui_create_steering_list: "write",
4933
5106
  ui_build_steering_list: "write",
4934
5107
  ui_set_account_steering_list: "write",
5108
+ ui_request_reseller_relay_change: "write",
4935
5109
  ui_create_account: "admin",
4936
5110
  ui_create_destination_list: "write",
4937
5111
  ui_edit_destination_list: "write",
@@ -5017,6 +5191,66 @@ Open account ID ${args.account_id}.
5017
5191
  `
5018
5192
  )
5019
5193
  );
5194
+ server2.registerTool(
5195
+ "ui_request_reseller_relay_change",
5196
+ {
5197
+ title: "Request Reseller Relay Change",
5198
+ 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.",
5199
+ inputSchema: {
5200
+ reseller_id: z4.number().optional().describe("Reseller id; defaults to the authenticated reseller"),
5201
+ relay_gy: z4.boolean().optional().describe("Desired Relay Gy state"),
5202
+ relay_calls_sms: z4.boolean().optional().describe("Desired calls/SMS relay state"),
5203
+ relay_voip: z4.boolean().optional().describe("Desired VoIP relay state"),
5204
+ reason: z4.string().optional().describe("Operational reason for the requested change")
5205
+ }
5206
+ },
5207
+ async (args) => {
5208
+ if (!ctx.props.scope.includes("write")) {
5209
+ return {
5210
+ isError: true,
5211
+ content: [{ type: "text", text: "Scope denied: tool 'ui_request_reseller_relay_change' requires 'write' scope." }]
5212
+ };
5213
+ }
5214
+ const resellerId = args.reseller_id ?? ctx.props.reseller_id;
5215
+ const requested = [
5216
+ args.relay_gy === void 0 ? null : `Relay Gy: ${args.relay_gy ? "enable" : "disable"}`,
5217
+ args.relay_calls_sms === void 0 ? null : `Calls/SMS relay: ${args.relay_calls_sms ? "enable" : "disable"}`,
5218
+ args.relay_voip === void 0 ? null : `VoIP relay: ${args.relay_voip ? "enable" : "disable"}`
5219
+ ].filter((value) => value !== null);
5220
+ if (requested.length === 0) {
5221
+ return {
5222
+ isError: true,
5223
+ content: [{ type: "text", text: JSON.stringify({ error: "relay_state_required", message: "Choose at least one relay flag and desired state." }) }]
5224
+ };
5225
+ }
5226
+ const draft = [
5227
+ `Please update the parent-controlled OCS relay settings for reseller ${resellerId}.`,
5228
+ "",
5229
+ ...requested.map((line) => `- ${line}`),
5230
+ ...args.reason ? ["", `Reason: ${args.reason}`] : [],
5231
+ "",
5232
+ "Please confirm once the change is active so we can verify the dependent OCS operation."
5233
+ ].join("\n");
5234
+ ctx.audit({
5235
+ tool_name: "ui_request_reseller_relay_change",
5236
+ ocs_method: "[ui-agent:G-20]",
5237
+ status: "parent_request_drafted",
5238
+ dry_run: true,
5239
+ duration_ms: 0,
5240
+ event_type: "ui_agent_dispatch"
5241
+ });
5242
+ return {
5243
+ content: [{ type: "text", text: JSON.stringify({
5244
+ status: "parent_request_drafted",
5245
+ capability: "parent_controlled",
5246
+ reseller_id: resellerId,
5247
+ parent_reseller: resellerId === 1170 ? "Bridge4IP" : "parent reseller/support",
5248
+ draft,
5249
+ sent: false
5250
+ }, null, 2) }]
5251
+ };
5252
+ }
5253
+ );
5020
5254
  server2.registerTool(
5021
5255
  "ui_create_account",
5022
5256
  {
@@ -5252,14 +5486,14 @@ async function withFallback(fn, keys) {
5252
5486
  true
5253
5487
  );
5254
5488
  }
5255
- async function manusPost(path, apiKey, body) {
5489
+ async function manusPost(path, apiKey, body2) {
5256
5490
  const res = await fetch(`${MANUS_API_BASE2}/${path}`, {
5257
5491
  method: "POST",
5258
5492
  headers: {
5259
5493
  "x-manus-api-key": apiKey,
5260
5494
  "Content-Type": "application/json"
5261
5495
  },
5262
- body: JSON.stringify(body)
5496
+ body: JSON.stringify(body2)
5263
5497
  });
5264
5498
  const data = await res.json();
5265
5499
  return { _httpStatus: res.status, data, key_used: "primary" };
@@ -5269,14 +5503,14 @@ async function sendMessage(keys, taskId, content, opts) {
5269
5503
  if (opts?.connectors?.length) message.connectors = opts.connectors;
5270
5504
  if (opts?.enableSkills?.length) message.enable_skills = opts.enableSkills;
5271
5505
  if (opts?.forceSkills?.length) message.force_skills = opts.forceSkills;
5272
- const body = {
5506
+ const body2 = {
5273
5507
  task_id: taskId,
5274
5508
  message
5275
5509
  };
5276
- if (opts?.agentProfile) body.agent_profile = opts.agentProfile;
5277
- if (opts?.outputSchema) body.structured_output_schema = opts.outputSchema;
5510
+ if (opts?.agentProfile) body2.agent_profile = opts.agentProfile;
5511
+ if (opts?.outputSchema) body2.structured_output_schema = opts.outputSchema;
5278
5512
  return withFallback(
5279
- (apiKey) => manusPost("task.sendMessage", apiKey, body),
5513
+ (apiKey) => manusPost("task.sendMessage", apiKey, body2),
5280
5514
  keys
5281
5515
  );
5282
5516
  }
@@ -5601,7 +5835,7 @@ function validateCron(cron) {
5601
5835
  return minuteFieldViolatesFiveMinuteRule(minuteField ?? "", cron);
5602
5836
  }
5603
5837
  async function createSchedule(apiKey, params) {
5604
- const body = {
5838
+ const body2 = {
5605
5839
  name: params.name,
5606
5840
  cron: params.cron,
5607
5841
  prompt_template: params.prompt_template,
@@ -5615,7 +5849,7 @@ async function createSchedule(apiKey, params) {
5615
5849
  "x-manus-api-key": apiKey,
5616
5850
  "Content-Type": "application/json"
5617
5851
  },
5618
- body: JSON.stringify(body)
5852
+ body: JSON.stringify(body2)
5619
5853
  });
5620
5854
  if (!res.ok) {
5621
5855
  throw new ManusScheduleError(
@@ -6245,19 +6479,33 @@ function registerScheduleAndUsageTools(server2, ctx) {
6245
6479
  import { z as z7 } from "zod";
6246
6480
  import * as Sentry2 from "@sentry/cloudflare";
6247
6481
 
6482
+ // src/audit-schema.ts
6483
+ var MARKER_INDEX = 11;
6484
+ var SCHEMA_GENERIC = "v1-generic";
6485
+ var SCHEMA_CARRIER_ASK = "v1-carrier-ask";
6486
+ function withSchemaMarker(blobs, marker) {
6487
+ const out = [...blobs];
6488
+ while (out.length < MARKER_INDEX) out.push("");
6489
+ out.push(marker);
6490
+ return out;
6491
+ }
6492
+
6248
6493
  // src/audit.ts
6249
6494
  function writeAudit(env, row) {
6250
6495
  env.AUDIT_LOG.writeDataPoint({
6251
- blobs: [
6252
- row.tool_name,
6253
- row.ocs_method,
6254
- row.status,
6255
- row.dry_run ? "1" : "0",
6256
- row.sub,
6257
- row.manus_task_id ?? "",
6258
- row.manus_profile ?? "",
6259
- row.manus_key_used ?? ""
6260
- ],
6496
+ blobs: withSchemaMarker(
6497
+ [
6498
+ row.tool_name,
6499
+ row.ocs_method,
6500
+ row.status,
6501
+ row.dry_run ? "1" : "0",
6502
+ row.sub,
6503
+ row.manus_task_id ?? "",
6504
+ row.manus_profile ?? "",
6505
+ row.manus_key_used ?? ""
6506
+ ],
6507
+ SCHEMA_GENERIC
6508
+ ),
6261
6509
  doubles: [row.duration_ms, row.ocs_status_code ?? 0],
6262
6510
  indexes: [String(row.reseller_id)]
6263
6511
  });
@@ -6396,14 +6644,14 @@ function resolveConfig(env) {
6396
6644
  async function atlasFetch(env, path, init) {
6397
6645
  const { baseUrl: baseUrl2, key } = resolveConfig(env);
6398
6646
  const headers = { "X-Carrier-Internal-Key": key };
6399
- let body;
6647
+ let body2;
6400
6648
  if (init.body !== void 0) {
6401
6649
  headers["Content-Type"] = "application/json";
6402
- body = JSON.stringify(init.body);
6650
+ body2 = JSON.stringify(init.body);
6403
6651
  }
6404
6652
  let res;
6405
6653
  try {
6406
- res = await fetch(`${baseUrl2}${path}`, { method: init.method, headers, body });
6654
+ res = await fetch(`${baseUrl2}${path}`, { method: init.method, headers, body: body2 });
6407
6655
  } catch (e) {
6408
6656
  throw new WalletClientError(
6409
6657
  `ATLAS wallet request failed: ${e instanceof Error ? e.message : "network error"}`,
@@ -6564,7 +6812,7 @@ async function runAutoTopup(env, args) {
6564
6812
  return { charged: false, creditedCents: 0, reason: "no_payment_method" };
6565
6813
  }
6566
6814
  const bonusCents = bonusCentsFor(pack);
6567
- const body = new URLSearchParams({
6815
+ const body2 = new URLSearchParams({
6568
6816
  amount: String(pack.eurCents),
6569
6817
  currency: "eur",
6570
6818
  customer: customerId,
@@ -6581,7 +6829,7 @@ async function runAutoTopup(env, args) {
6581
6829
  const resp = await stripePostForm(
6582
6830
  stripeKey,
6583
6831
  "/v1/payment_intents",
6584
- body
6832
+ body2
6585
6833
  );
6586
6834
  if (!resp.ok) {
6587
6835
  return {
@@ -6855,6 +7103,13 @@ function registerStripeConnectTools(server2, ctx) {
6855
7103
  annotations: annotationsFor("stripe_connect_status", "read")
6856
7104
  },
6857
7105
  async ({ operator_id }) => {
7106
+ if (operator_id !== void 0 && operator_id !== operatorId) {
7107
+ if (!isPlatformOperator(env, props2.org_id)) {
7108
+ return err2(
7109
+ "operator_id may only be overridden by Carrier's own organization. Omit it to read your own Stripe Connect status."
7110
+ );
7111
+ }
7112
+ }
6858
7113
  const opId = operator_id ?? operatorId;
6859
7114
  const opKvKey = operator_id ? `stripe_account:${operator_id}` : kvKey;
6860
7115
  const start = Date.now();
@@ -7471,7 +7726,8 @@ function registerStorefrontDeployTools(server2) {
7471
7726
  var TIER_CREDIT_ALLOTMENTS = {
7472
7727
  free: 5e3,
7473
7728
  pro: 1e5,
7474
- enterprise: Infinity
7729
+ enterprise: Infinity,
7730
+ superadmin: Infinity
7475
7731
  };
7476
7732
  var DAILY_FREE_CREDITS = 50;
7477
7733
  var SCOPE_CREDIT_COSTS = {
@@ -7494,7 +7750,7 @@ async function checkCredits(env, sub, tier, scope) {
7494
7750
  const month = currentMonth2();
7495
7751
  const today = currentDay();
7496
7752
  const resetAt = firstDayNextMonth2();
7497
- if (tier === "enterprise") {
7753
+ if (tier === "enterprise" || tier === "superadmin") {
7498
7754
  return {
7499
7755
  allowed: true,
7500
7756
  credits_remaining: Infinity,
@@ -7534,7 +7790,7 @@ function deductCredits(env, sub, tier, scope) {
7534
7790
  const cost = SCOPE_CREDIT_COSTS[scope];
7535
7791
  const month = currentMonth2();
7536
7792
  const today = currentDay();
7537
- if (tier === "enterprise") return;
7793
+ if (tier === "enterprise" || tier === "superadmin") return;
7538
7794
  const dailyFree = await getDailyFreeCredits(env, sub, today);
7539
7795
  const dailyRemaining = dailyFree.granted - dailyFree.consumed;
7540
7796
  if (dailyRemaining >= cost) {
@@ -7593,7 +7849,7 @@ async function triggerBillingThreshold(env, sub, amountCents) {
7593
7849
  if (!stripeKey) return;
7594
7850
  const customerId = await env.CARRIER_USERS.get(`stripe_customer_id:${sub}`);
7595
7851
  if (!customerId) return;
7596
- const body = new URLSearchParams({
7852
+ const body2 = new URLSearchParams({
7597
7853
  customer: customerId,
7598
7854
  amount: String(Math.round(amountCents)),
7599
7855
  currency: "usd",
@@ -7605,7 +7861,7 @@ async function triggerBillingThreshold(env, sub, amountCents) {
7605
7861
  Authorization: `Bearer ${stripeKey}`,
7606
7862
  "Content-Type": "application/x-www-form-urlencoded"
7607
7863
  },
7608
- body: body.toString()
7864
+ body: body2.toString()
7609
7865
  });
7610
7866
  if (!resp.ok) return;
7611
7867
  const invoiceBody = new URLSearchParams({
@@ -7629,7 +7885,7 @@ async function pushOverageToStripe(env, sub, quantity, ratePerCredit) {
7629
7885
  if (!stripeKey) return;
7630
7886
  const subItemId = await env.CARRIER_USERS.get(`stripe_sub_item_id:${sub}`);
7631
7887
  if (!subItemId) return;
7632
- const body = new URLSearchParams({
7888
+ const body2 = new URLSearchParams({
7633
7889
  quantity: String(quantity),
7634
7890
  timestamp: String(Math.floor(Date.now() / 1e3)),
7635
7891
  action: "increment"
@@ -7642,7 +7898,7 @@ async function pushOverageToStripe(env, sub, quantity, ratePerCredit) {
7642
7898
  Authorization: `Bearer ${stripeKey}`,
7643
7899
  "Content-Type": "application/x-www-form-urlencoded"
7644
7900
  },
7645
- body: body.toString()
7901
+ body: body2.toString()
7646
7902
  }
7647
7903
  );
7648
7904
  await env.CARRIER_USERS.put(
@@ -7740,7 +7996,7 @@ async function resolveUserTier(env, sub) {
7740
7996
  const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
7741
7997
  if (raw && typeof raw === "object" && "tier" in raw) {
7742
7998
  const t = raw.tier;
7743
- if (t === "pro" || t === "enterprise") return t;
7999
+ if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
7744
8000
  }
7745
8001
  return "free";
7746
8002
  }
@@ -7790,6 +8046,14 @@ var DEFAULT_THRESHOLD_CONFIGS = {
7790
8046
  auto_pause_on_cap: false,
7791
8047
  notification_email: null,
7792
8048
  webhook_url: null
8049
+ },
8050
+ superadmin: {
8051
+ notification_thresholds: [],
8052
+ hard_cap_cents: 0,
8053
+ invoice_threshold_cents: 0,
8054
+ auto_pause_on_cap: false,
8055
+ notification_email: null,
8056
+ webhook_url: null
7793
8057
  }
7794
8058
  };
7795
8059
  async function getThresholdConfig(env, sub, tier) {
@@ -7812,7 +8076,7 @@ async function resolveUserTier2(env, sub) {
7812
8076
  const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
7813
8077
  if (raw && typeof raw === "object" && "tier" in raw) {
7814
8078
  const t = raw.tier;
7815
- if (t === "pro" || t === "enterprise") return t;
8079
+ if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
7816
8080
  }
7817
8081
  return "free";
7818
8082
  }
@@ -9316,37 +9580,9 @@ function routerTools() {
9316
9580
  var ROUTER_SYSTEM_PROMPT = `You are a carrier fleet operations router. Your job is to map a user's natural-language intent to exactly ONE tool from the Carrier MCP tool registry.
9317
9581
 
9318
9582
  ${ROUTER_RULES}`;
9319
- var DEFAULT_REGION = "us-east-1";
9320
- var DEFAULT_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
9321
- async function callBedrock(env, payload) {
9322
- const region = env.AWS_REGION ?? DEFAULT_REGION;
9323
- const modelId = env.BEDROCK_MODEL_ID ?? DEFAULT_MODEL_ID;
9324
- const aws = new AwsClient({
9325
- accessKeyId: env.AWS_ACCESS_KEY_ID,
9326
- secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
9327
- region,
9328
- service: "bedrock"
9329
- });
9330
- const url = `https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`;
9331
- const resp = await aws.fetch(url, {
9332
- method: "POST",
9333
- headers: { "Content-Type": "application/json", Accept: "application/json" },
9334
- body: JSON.stringify(payload)
9335
- });
9336
- if (!resp.ok) {
9337
- const body = await resp.text();
9338
- if (resp.status === 429) {
9339
- const retryAfter = resp.headers.get("retry-after");
9340
- const err7 = new Error(`Bedrock rate limit: ${body}`);
9341
- err7.isRateLimit = true;
9342
- err7.retryAfter = retryAfter;
9343
- throw err7;
9344
- }
9345
- throw new Error(`Bedrock invoke failed: ${resp.status} ${body}`);
9346
- }
9347
- return await resp.json();
9348
- }
9349
- async function _routeIntent(intent, context, env) {
9583
+ var ROUTER_TIMEOUT_MS = 12e3;
9584
+ async function _routeIntent(intent, context, env, opts = {}) {
9585
+ const { attribution, onUsage } = opts;
9350
9586
  if (env.CARRIER_ASK_ENABLED !== "true" || !env.AWS_ACCESS_KEY_ID || !env.AWS_SECRET_ACCESS_KEY) {
9351
9587
  return {
9352
9588
  match: "routing_pending",
@@ -9362,26 +9598,32 @@ async function _routeIntent(intent, context, env) {
9362
9598
 
9363
9599
  Pre-resolved context: ${JSON.stringify(definedContext)}` : "";
9364
9600
  const userMessage = `${intent}${contextNote}`;
9365
- const payload = {
9366
- anthropic_version: "bedrock-2023-05-31",
9367
- max_tokens: 512,
9368
- system: ROUTER_SYSTEM_PROMPT,
9369
- messages: [{ role: "user", content: userMessage }],
9370
- tools: routerTools(),
9371
- tool_choice: { type: "any" }
9372
- };
9373
- let response;
9601
+ const routerToolList = routerTools();
9602
+ const tools = routerToolList.map(
9603
+ (tool, idx) => idx === routerToolList.length - 1 ? { ...tool, cache_control: { type: "ephemeral" } } : tool
9604
+ );
9605
+ let toolCall;
9374
9606
  try {
9375
- response = await callBedrock({
9376
- AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID,
9377
- AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY,
9378
- AWS_REGION: env.AWS_REGION,
9379
- BEDROCK_MODEL_ID: env.BEDROCK_MODEL_ID
9380
- }, payload);
9607
+ toolCall = await invokeTool(
9608
+ {
9609
+ accessKeyId: env.AWS_ACCESS_KEY_ID,
9610
+ secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
9611
+ ...env.AWS_REGION ? { region: env.AWS_REGION } : {},
9612
+ ...env.BEDROCK_MODEL_ID ? { modelId: env.BEDROCK_MODEL_ID } : {}
9613
+ },
9614
+ {
9615
+ system: ROUTER_SYSTEM_PROMPT,
9616
+ messages: [{ role: "user", content: userMessage }],
9617
+ maxTokens: 512,
9618
+ tools,
9619
+ timeoutMs: ROUTER_TIMEOUT_MS,
9620
+ ...attribution ? { attribution } : {},
9621
+ ...onUsage ? { onUsage } : {}
9622
+ }
9623
+ );
9381
9624
  } catch (err7) {
9382
- if (err7 instanceof Error && err7.isRateLimit) {
9383
- const retryAfter = err7.retryAfter;
9384
- const parsedSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
9625
+ if (err7 instanceof BedrockRateLimitError) {
9626
+ const parsedSeconds = err7.retryAfter ? parseInt(err7.retryAfter, 10) : 60;
9385
9627
  return {
9386
9628
  match: "rate_limited",
9387
9629
  retry_after_seconds: Number.isFinite(parsedSeconds) ? parsedSeconds : 60,
@@ -9390,17 +9632,14 @@ Pre-resolved context: ${JSON.stringify(definedContext)}` : "";
9390
9632
  }
9391
9633
  throw err7;
9392
9634
  }
9393
- const toolUseBlock = response.content.find(
9394
- (block) => block.type === "tool_use"
9395
- );
9396
- if (!toolUseBlock) {
9635
+ if (!toolCall) {
9397
9636
  return {
9398
9637
  match: "none",
9399
9638
  closest: [],
9400
9639
  suggestion: "Could not determine the right tool for this intent. Try rephrasing or use carrier_ask_describe to explore available tools."
9401
9640
  };
9402
9641
  }
9403
- const { name: resolvedTool, input } = toolUseBlock;
9642
+ const { name: resolvedTool, input } = toolCall;
9404
9643
  const resolvedParams = input ?? {};
9405
9644
  if (resolvedTool === "carrier_clarify") {
9406
9645
  const candidates = resolvedParams.candidates ?? [];
@@ -9443,23 +9682,47 @@ Pre-resolved context: ${JSON.stringify(definedContext)}` : "";
9443
9682
  function writeCarrierAskAudit(env, row) {
9444
9683
  try {
9445
9684
  env.AUDIT_LOG.writeDataPoint({
9446
- blobs: [
9447
- "carrier_ask",
9448
- // blob[0] tool_name
9449
- row.resolved_tool,
9450
- // blob[1] resolved tool (or "none")
9451
- row.status,
9452
- // blob[2] ok | error
9453
- row.match,
9454
- // blob[3] match type
9455
- row.intent_hash,
9456
- // blob[4] SHA-256 of intent
9457
- row.confirm_token_state,
9458
- // blob[5] confirm token state
9459
- row.sub
9460
- // blob[6] user subject
9685
+ // The marker at blob[11] is what separates these rows from writeAudit's,
9686
+ // which writes a different layout to this same dataset — its blob[4] is
9687
+ // `sub` where this one's is an intent hash. Filter on it before reading
9688
+ // any other position.
9689
+ blobs: withSchemaMarker(
9690
+ [
9691
+ "carrier_ask",
9692
+ // blob[0] tool_name
9693
+ row.resolved_tool,
9694
+ // blob[1] resolved tool (or "none")
9695
+ row.status,
9696
+ // blob[2] ok | error
9697
+ row.match,
9698
+ // blob[3] match type
9699
+ row.intent_hash,
9700
+ // blob[4] SHA-256 of intent
9701
+ row.confirm_token_state,
9702
+ // blob[5] confirm token state
9703
+ row.sub,
9704
+ // blob[6] user subject
9705
+ // Appended, never reordered: blob positions are the query contract and
9706
+ // an existing dashboard reading blob[3] must keep reading match.
9707
+ row.model_id,
9708
+ // blob[7] model id, or "none"
9709
+ row.tier
9710
+ // blob[8] tier at call time
9711
+ ],
9712
+ SCHEMA_CARRIER_ASK
9713
+ ),
9714
+ // Same rule for doubles. latency stays at [0].
9715
+ doubles: [
9716
+ row.latency_ms,
9717
+ row.usage.inputTokens,
9718
+ row.usage.outputTokens,
9719
+ row.usage.cacheReadTokens,
9720
+ row.usage.cacheWriteTokens,
9721
+ // An estimate from a rate card we maintain, not a billed figure. It is
9722
+ // here so a per-tenant cost query is one SQL statement rather than a
9723
+ // join against a rate table that lives in another package.
9724
+ estimateCostUsd(row.usage)
9461
9725
  ],
9462
- doubles: [row.latency_ms],
9463
9726
  indexes: [String(row.reseller_id)]
9464
9727
  });
9465
9728
  } catch {
@@ -9505,7 +9768,10 @@ function registerAllCarrierAskTools(server2, ctx) {
9505
9768
  status: "error",
9506
9769
  latency_ms: Date.now() - start,
9507
9770
  sub: ctx.props.sub,
9508
- reseller_id: ctx.props.reseller_id
9771
+ reseller_id: ctx.props.reseller_id,
9772
+ model_id: "none",
9773
+ tier: ctx.props.tier,
9774
+ usage: ZERO_USAGE
9509
9775
  });
9510
9776
  return {
9511
9777
  content: [
@@ -9543,7 +9809,10 @@ function registerAllCarrierAskTools(server2, ctx) {
9543
9809
  status: "ok",
9544
9810
  latency_ms: Date.now() - start,
9545
9811
  sub: ctx.props.sub,
9546
- reseller_id: ctx.props.reseller_id
9812
+ reseller_id: ctx.props.reseller_id,
9813
+ model_id: "none",
9814
+ tier: ctx.props.tier,
9815
+ usage: ZERO_USAGE
9547
9816
  });
9548
9817
  return {
9549
9818
  content: [
@@ -9560,8 +9829,21 @@ function registerAllCarrierAskTools(server2, ctx) {
9560
9829
  };
9561
9830
  }
9562
9831
  let route;
9832
+ let routeUsage = ZERO_USAGE;
9833
+ let modelRan = false;
9563
9834
  try {
9564
- route = await _routeIntent(intent, context, ctx.env);
9835
+ route = await _routeIntent(intent, context, ctx.env, {
9836
+ attribution: {
9837
+ ...ctx.props.org_id ? { orgId: ctx.props.org_id } : {},
9838
+ ...ctx.props.reseller_id ? { subAccountId: String(ctx.props.reseller_id) } : {},
9839
+ surface: "carrier_ask",
9840
+ tier: ctx.props.tier
9841
+ },
9842
+ onUsage: (u) => {
9843
+ routeUsage = u;
9844
+ modelRan = true;
9845
+ }
9846
+ });
9565
9847
  } catch (err7) {
9566
9848
  writeCarrierAskAudit(ctx.env, {
9567
9849
  intent_hash: intentHash,
@@ -9571,7 +9853,14 @@ function registerAllCarrierAskTools(server2, ctx) {
9571
9853
  status: "error",
9572
9854
  latency_ms: Date.now() - start,
9573
9855
  sub: ctx.props.sub,
9574
- reseller_id: ctx.props.reseller_id
9856
+ reseller_id: ctx.props.reseller_id,
9857
+ model_id: modelRan ? ctx.env.BEDROCK_MODEL_ID ?? DEFAULT_MODEL_ID : "none",
9858
+ tier: ctx.props.tier,
9859
+ // Not ZERO_USAGE: a call can throw AFTER the model answered, and
9860
+ // those tokens were billed regardless. Recording zero here is how
9861
+ // measured spend drifts below real spend in exactly the rows used
9862
+ // to check it.
9863
+ usage: routeUsage
9575
9864
  });
9576
9865
  return {
9577
9866
  content: [
@@ -9608,7 +9897,10 @@ function registerAllCarrierAskTools(server2, ctx) {
9608
9897
  status: "ok",
9609
9898
  latency_ms: Date.now() - start,
9610
9899
  sub: ctx.props.sub,
9611
- reseller_id: ctx.props.reseller_id
9900
+ reseller_id: ctx.props.reseller_id,
9901
+ model_id: modelRan ? ctx.env.BEDROCK_MODEL_ID ?? DEFAULT_MODEL_ID : "none",
9902
+ tier: ctx.props.tier,
9903
+ usage: routeUsage
9612
9904
  });
9613
9905
  return {
9614
9906
  content: [
@@ -10150,22 +10442,22 @@ function toFleetInput(raw) {
10150
10442
 
10151
10443
  // src/apps/fleet-health-app.ts
10152
10444
  var FLEET_APP_RESOURCE_URI = "ui://fleet-health-dashboard";
10153
- async function safeCall2(client, method, params) {
10445
+ async function safeCall2(client2, method, params) {
10154
10446
  try {
10155
- return { data: await client.call(method, params), error: null };
10447
+ return { data: await client2.call(method, params), error: null };
10156
10448
  } catch (err7) {
10157
10449
  return { data: null, error: err7 instanceof Error ? err7.message : String(err7) };
10158
10450
  }
10159
10451
  }
10160
10452
  async function loadFleet(ctx, accountId) {
10161
10453
  const token2 = await ctx.getUserToken(ctx.props.sub);
10162
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
10454
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
10163
10455
  const resellerId = await getDefaultResellerId(ctx.env, token2).catch(() => void 0);
10164
10456
  const statusParams = accountId !== void 0 ? { accountId } : resellerId !== void 0 ? { resellerId } : {};
10165
10457
  const accountParams = resellerId !== void 0 ? { resellerId } : {};
10166
10458
  const [status, accounts] = await Promise.all([
10167
- safeCall2(client, "esimStatusPerAccount", statusParams),
10168
- safeCall2(client, "listResellerAccount", accountParams)
10459
+ safeCall2(client2, "esimStatusPerAccount", statusParams),
10460
+ safeCall2(client2, "listResellerAccount", accountParams)
10169
10461
  ]);
10170
10462
  return toFleetInput({
10171
10463
  status: status.data,
@@ -10311,9 +10603,9 @@ async function deleteWizardSession(env, sub, wizardId) {
10311
10603
  }
10312
10604
 
10313
10605
  // src/apps/provisioning-wizard.ts
10314
- async function safeCallWithToken(client, _token, method, params = {}) {
10606
+ async function safeCallWithToken(client2, _token, method, params = {}) {
10315
10607
  try {
10316
- return { data: await client.call(method, params), error: null };
10608
+ return { data: await client2.call(method, params), error: null };
10317
10609
  } catch (err7) {
10318
10610
  return {
10319
10611
  data: null,
@@ -10380,11 +10672,11 @@ function registerProvisioningWizard(server2, ctx) {
10380
10672
  },
10381
10673
  async ({ step, wizardId, subscriber_iccid, package_template_id }) => {
10382
10674
  const token2 = await ctx.getUserToken(ctx.props.sub);
10383
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
10675
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
10384
10676
  if (step === "init") {
10385
10677
  const newWizardId = generateWizardId();
10386
10678
  const subscribersResult = await safeCallWithToken(
10387
- client,
10679
+ client2,
10388
10680
  token2,
10389
10681
  "listResellerAccount",
10390
10682
  {}
@@ -10462,7 +10754,7 @@ function registerProvisioningWizard(server2, ctx) {
10462
10754
  }
10463
10755
  const resellerIdForTemplates = await getDefaultResellerId(ctx.env, token2);
10464
10756
  const packagesResult = await safeCallWithToken(
10465
- client,
10757
+ client2,
10466
10758
  token2,
10467
10759
  "listPrepaidPackageTemplate",
10468
10760
  { resellerId: resellerIdForTemplates }
@@ -10529,7 +10821,7 @@ function registerProvisioningWizard(server2, ctx) {
10529
10821
  };
10530
10822
  }
10531
10823
  const previewResult = await safeCallWithToken(
10532
- client,
10824
+ client2,
10533
10825
  token2,
10534
10826
  "listPrepaidPackageTemplate",
10535
10827
  {}
@@ -10601,7 +10893,7 @@ function registerProvisioningWizard(server2, ctx) {
10601
10893
  };
10602
10894
  }
10603
10895
  const subRecord = await safeCallWithToken(
10604
- client,
10896
+ client2,
10605
10897
  token2,
10606
10898
  "getSingleSubscriber",
10607
10899
  { iccid: session.subscriber_iccid }
@@ -10630,7 +10922,7 @@ function registerProvisioningWizard(server2, ctx) {
10630
10922
  };
10631
10923
  }
10632
10924
  const result2 = await safeCallWithToken(
10633
- client,
10925
+ client2,
10634
10926
  token2,
10635
10927
  "affectPackageToSubscriber",
10636
10928
  {
@@ -10674,9 +10966,9 @@ import {
10674
10966
  registerAppResource as registerAppResource3,
10675
10967
  RESOURCE_MIME_TYPE as RESOURCE_MIME_TYPE3
10676
10968
  } from "@modelcontextprotocol/ext-apps/server";
10677
- async function safeCallWithToken2(client, _token, method, params = {}) {
10969
+ async function safeCallWithToken2(client2, _token, method, params = {}) {
10678
10970
  try {
10679
- return { data: await client.call(method, params), error: null };
10971
+ return { data: await client2.call(method, params), error: null };
10680
10972
  } catch (err7) {
10681
10973
  return {
10682
10974
  data: null,
@@ -10740,10 +11032,10 @@ function registerBalanceTopupApp(server2, ctx) {
10740
11032
  ctx,
10741
11033
  async ({ iccid, delta, preview }) => {
10742
11034
  const token2 = await ctx.getUserToken(ctx.props.sub);
10743
- const client = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
11035
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
10744
11036
  if (preview === true) {
10745
11037
  const result2 = await safeCallWithToken2(
10746
- client,
11038
+ client2,
10747
11039
  token2,
10748
11040
  "getSingleSubscriber",
10749
11041
  { iccid }
@@ -10768,7 +11060,7 @@ function registerBalanceTopupApp(server2, ctx) {
10768
11060
  };
10769
11061
  }
10770
11062
  const execResult = await safeCallWithToken2(
10771
- client,
11063
+ client2,
10772
11064
  token2,
10773
11065
  // CAR-78: subscriber top-up uses modifySubscriberBalance with { subscriber, amount }
10774
11066
  // (delta). modifyAccountBalance is account-level and expects { accountId, amount, mode }.