@carrierllc/mcp 0.9.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-VACB3Z5V.js";
40
+ } from "./chunk-V4CYEMLJ.js";
41
41
  import "./chunk-SHKKVIIA.js";
42
42
 
43
43
  // src/index.ts
@@ -98,17 +98,206 @@ 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";
111
- }
112
301
  function platformOrgId(env) {
113
302
  const raw = env.CARRIER_PLATFORM_ORG_ID;
114
303
  if (typeof raw !== "string") return null;
@@ -123,7 +312,7 @@ function isPlatformOperator(env, orgId) {
123
312
  async function checkCallQuota(env, sub, tier) {
124
313
  const limit = TIER_CALL_LIMITS[tier];
125
314
  const resetAt = firstDayNextMonth();
126
- if (tier === "enterprise") {
315
+ if (tier === "pro" || tier === "enterprise" || tier === "superadmin") {
127
316
  return { allowed: true, remaining: Infinity, resetAt, tier };
128
317
  }
129
318
  if (!env.CARRIER_USERS) {
@@ -144,6 +333,7 @@ async function checkCallQuota(env, sub, tier) {
144
333
  function recordUsage(env, sub, tier) {
145
334
  if (!env.CARRIER_USERS) return;
146
335
  (async () => {
336
+ if (tier === "pro" || tier === "enterprise" || tier === "superadmin") return;
147
337
  const month = currentMonth();
148
338
  const usageKey = `usage:${sub}:${month}`;
149
339
  const raw = await env.CARRIER_USERS.get(usageKey, "json").catch(() => null);
@@ -154,35 +344,8 @@ function recordUsage(env, sub, tier) {
154
344
  JSON.stringify({ calls: next, updated_at: (/* @__PURE__ */ new Date()).toISOString() }),
155
345
  { expirationTtl: 35 * 24 * 60 * 60 }
156
346
  );
157
- if (tier === "pro" && next % 100 === 0 && getBillingPrimary(env) === "stripe") {
158
- await pushStripeUsageRecord(env, sub, 100).catch(() => {
159
- });
160
- }
161
347
  })();
162
348
  }
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
349
  function currentMonth() {
187
350
  const now = /* @__PURE__ */ new Date();
188
351
  return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -531,8 +694,8 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
531
694
  {
532
695
  type: "text",
533
696
  text: [
534
- `Quota exceeded: your ${quota.tier} plan has reached the safety limit of 100,000 regular tool calls/month.`,
535
- `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}.`,
536
699
  `Upgrade to Pro for unlimited calls at ${UPGRADE_URL}`
537
700
  ].join(" ")
538
701
  }
@@ -4675,196 +4838,6 @@ function registerAllBacklogTools(server2, ctx) {
4675
4838
  );
4676
4839
  }
4677
4840
 
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
4841
  // src/tools-carrier-ask.ts
4869
4842
  import { z as z11 } from "zod";
4870
4843
 
@@ -5132,6 +5105,7 @@ var UI_AGENT_TOOL_SCOPES = {
5132
5105
  ui_create_steering_list: "write",
5133
5106
  ui_build_steering_list: "write",
5134
5107
  ui_set_account_steering_list: "write",
5108
+ ui_request_reseller_relay_change: "write",
5135
5109
  ui_create_account: "admin",
5136
5110
  ui_create_destination_list: "write",
5137
5111
  ui_edit_destination_list: "write",
@@ -5217,6 +5191,66 @@ Open account ID ${args.account_id}.
5217
5191
  `
5218
5192
  )
5219
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
+ );
5220
5254
  server2.registerTool(
5221
5255
  "ui_create_account",
5222
5256
  {
@@ -7692,7 +7726,8 @@ function registerStorefrontDeployTools(server2) {
7692
7726
  var TIER_CREDIT_ALLOTMENTS = {
7693
7727
  free: 5e3,
7694
7728
  pro: 1e5,
7695
- enterprise: Infinity
7729
+ enterprise: Infinity,
7730
+ superadmin: Infinity
7696
7731
  };
7697
7732
  var DAILY_FREE_CREDITS = 50;
7698
7733
  var SCOPE_CREDIT_COSTS = {
@@ -7715,7 +7750,7 @@ async function checkCredits(env, sub, tier, scope) {
7715
7750
  const month = currentMonth2();
7716
7751
  const today = currentDay();
7717
7752
  const resetAt = firstDayNextMonth2();
7718
- if (tier === "enterprise") {
7753
+ if (tier === "enterprise" || tier === "superadmin") {
7719
7754
  return {
7720
7755
  allowed: true,
7721
7756
  credits_remaining: Infinity,
@@ -7755,7 +7790,7 @@ function deductCredits(env, sub, tier, scope) {
7755
7790
  const cost = SCOPE_CREDIT_COSTS[scope];
7756
7791
  const month = currentMonth2();
7757
7792
  const today = currentDay();
7758
- if (tier === "enterprise") return;
7793
+ if (tier === "enterprise" || tier === "superadmin") return;
7759
7794
  const dailyFree = await getDailyFreeCredits(env, sub, today);
7760
7795
  const dailyRemaining = dailyFree.granted - dailyFree.consumed;
7761
7796
  if (dailyRemaining >= cost) {
@@ -7961,7 +7996,7 @@ async function resolveUserTier(env, sub) {
7961
7996
  const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
7962
7997
  if (raw && typeof raw === "object" && "tier" in raw) {
7963
7998
  const t = raw.tier;
7964
- if (t === "pro" || t === "enterprise") return t;
7999
+ if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
7965
8000
  }
7966
8001
  return "free";
7967
8002
  }
@@ -8011,6 +8046,14 @@ var DEFAULT_THRESHOLD_CONFIGS = {
8011
8046
  auto_pause_on_cap: false,
8012
8047
  notification_email: null,
8013
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
8014
8057
  }
8015
8058
  };
8016
8059
  async function getThresholdConfig(env, sub, tier) {
@@ -8033,7 +8076,7 @@ async function resolveUserTier2(env, sub) {
8033
8076
  const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
8034
8077
  if (raw && typeof raw === "object" && "tier" in raw) {
8035
8078
  const t = raw.tier;
8036
- if (t === "pro" || t === "enterprise") return t;
8079
+ if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
8037
8080
  }
8038
8081
  return "free";
8039
8082
  }