@carrierllc/mcp 0.12.0 → 0.13.3

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.
@@ -523,6 +523,13 @@ function renderEnv(brand) {
523
523
  `# hosted page and back. Both take money \u2014 the embedded one loses fewer`,
524
524
  `# people on the way.`,
525
525
  `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=`,
526
+ `# Optional. Sends the eSIM QR code by email once an order is ready.`,
527
+ `# \`carrier site email\` authenticates you at Resend in the browser and`,
528
+ `# fills this in. Without it the storefront still sells and activates; the`,
529
+ `# activation page just says email delivery is not set up.`,
530
+ `# The from-address is composed from brand.config.ts (emailFromMailbox +`,
531
+ `# domain), so verify that domain at resend.com/domains first.`,
532
+ `RESEND_API_KEY=`,
526
533
  `# Clerk \u2014 \`carrier site clerk\` fills these in, or paste them from dashboard.clerk.com`,
527
534
  `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=`,
528
535
  `CLERK_SECRET_KEY=`,
@@ -543,7 +550,11 @@ var TEMPLATE_ENV_KEYS = {
543
550
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: "public",
544
551
  CARRIER_API_KEY: "secret",
545
552
  CLERK_SECRET_KEY: "secret",
546
- STRIPE_SECRET_KEY: "secret"
553
+ STRIPE_SECRET_KEY: "secret",
554
+ // Optional. Present: the storefront emails the QR code to whoever paid.
555
+ // Absent: the activation page says email delivery is not set up, and every
556
+ // install route it shows on screen still works.
557
+ RESEND_API_KEY: "secret"
547
558
  };
548
559
  function keysOfKind(kind) {
549
560
  return Object.keys(TEMPLATE_ENV_KEYS).filter(
@@ -1804,7 +1815,7 @@ function storefrontScreen(input) {
1804
1815
  // package.json
1805
1816
  var package_default = {
1806
1817
  name: "@carrierllc/mcp",
1807
- version: "0.12.0",
1818
+ version: "0.13.3",
1808
1819
  description: "Carrier MCP \u2014 natural-language control of MVNO/eSIM fleets via eSIMVault OCS. Stdio mode for direct integration with Claude Desktop, Cursor, Windsurf, and MCP-compatible clients. Ships the `carrier` CLI (plugin install + white-label eSIM storefront scaffold).",
1809
1820
  license: "MIT",
1810
1821
  author: "Carrier (Lifecycle Innovations Limited)",
@@ -1888,1218 +1899,1610 @@ var package_default = {
1888
1899
  // src/version.ts
1889
1900
  var CARRIER_VERSION = package_default.version;
1890
1901
 
1891
- // ../../packages/ocs-client/dist/chunk-6C2DHWD4.js
1892
- import { z } from "zod";
1893
- var OCS_V1_METHODS = /* @__PURE__ */ new Set([
1894
- "getResellerInfo",
1895
- "listResellerAccount",
1896
- "listPrepaidPackageTemplate",
1897
- "listSubscriberPrepaidPackages",
1898
- "listSubscriber",
1899
- "getSingleSubscriber",
1900
- "getSimProviderStatus",
1901
- "getSubscriberLocation",
1902
- "getSubscriberLocationByCellId",
1903
- "esimStatusPerAccount",
1904
- "subscriberUsageOverPeriod",
1905
- "affectRecurringPackageToSubscriber",
1906
- "deleteSubscriberPackage",
1907
- "modifyAccountBalance",
1908
- "modifySubscriberBalance",
1909
- "modifySubscriberContactInfo",
1910
- "modifySubscriberMobilePlan",
1911
- "modifySubscriberSteeringList",
1912
- "modifySubscriberVoipPlan",
1913
- "modifySubscriberPrepaidPackageActivePeriod",
1914
- "modifySubscriberPrepaidPackageLimits",
1915
- "modifySubscriberPrepaidPackageStatus",
1916
- "modifyPPTCore",
1917
- "modifyPPTRecurring",
1918
- "createPrepaidPackageTemplate",
1919
- "createLocationZone",
1920
- "changeNetworkProfileOfLocationZone",
1921
- "hlrGetBitrate",
1922
- "hlrSetBitrate",
1923
- "listSponsor",
1924
- "listDestinationListPrefix",
1925
- "listDetailedLocationZone",
1926
- "listLocationZoneElement",
1927
- "listSubscriberVoipTariff",
1928
- "listVoipTariffRule",
1929
- "affectSubscriberFakePhoneNumber",
1930
- "affectSubscriberRealPhoneNumber",
1931
- "getCustomerTariff",
1932
- "resetSubsGzCounter",
1933
- "sendMtSms"
1934
- ]);
1935
- var OcsResponseEnvelope = z.object({
1936
- status: z.object({
1937
- code: z.number(),
1938
- // Required to match the `OcsStatus` interface in client.ts ({ code, msg }).
1939
- msg: z.string()
1940
- }).passthrough()
1941
- }).passthrough();
1942
- var GetResellerInfoResponse = z.object({
1943
- id: z.number(),
1944
- name: z.string(),
1945
- parentId: z.number(),
1946
- parentName: z.string(),
1947
- // Production eSIMVault returns decimal money as a JSON string ("495.52").
1948
- // Staging fixtures used a number, so accept both exact provider variants.
1949
- balance: z.union([z.number(), z.string().regex(/^-?\d+(?:\.\d+)?$/)]),
1950
- cdrFolder: z.string().optional(),
1951
- trafficInfo: z.object({}).passthrough().optional(),
1952
- chargingInfo: z.object({}).passthrough().optional(),
1953
- contactInfo: z.object({}).passthrough().optional()
1954
- }).passthrough();
1955
- var ListResellerAccountResponse = z.object({
1956
- reseller: z.array(
1957
- z.object({
1958
- id: z.number(),
1959
- name: z.string(),
1960
- account: z.array(z.object({}).passthrough())
1961
- }).passthrough()
1962
- )
1963
- }).passthrough();
1964
- var ListPrepaidPackageTemplateResponse = z.object({
1965
- template: z.array(
1966
- z.object({
1967
- prepaidpackagetemplateid: z.number(),
1968
- prepaidpackagetemplatename: z.string(),
1969
- resellerid: z.number(),
1970
- locationzoneid: z.number()
1971
- }).passthrough()
1972
- )
1973
- }).passthrough();
1974
- var SubscriberStatusName = z.string().regex(/^[A-Za-z]/, "subscriber status must be a non-empty alpha-leading name");
1975
- var GetSingleSubscriberResponse = z.object({
1976
- status: z.array(
1977
- z.object({
1978
- status: SubscriberStatusName
1979
- }).passthrough()
1980
- ).min(1, "getSingleSubscriber.status must be a non-empty array")
1981
- }).passthrough();
1982
- var RESPONSE_SCHEMAS = {
1983
- getResellerInfo: GetResellerInfoResponse,
1984
- listResellerAccount: ListResellerAccountResponse,
1985
- listPrepaidPackageTemplate: ListPrepaidPackageTemplateResponse,
1986
- getSingleSubscriber: GetSingleSubscriberResponse
1987
- };
1988
- var IccidParam = z.object({ iccid: z.string().min(1) });
1989
- var AccountIdParam = z.object({ accountId: z.coerce.number().int().positive() });
1990
- var PackageIdParam = z.object({ packageId: z.coerce.number().int().positive() });
1991
- var TemplateIdParam = z.object({ templateId: z.coerce.number().int().positive() });
1992
- var ResellerIdParam = z.object({ resellerId: z.coerce.number().int().positive() });
1993
- var DateRangeBody = z.object({
1994
- start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD"),
1995
- end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD")
1996
- });
1997
- var ErrorResponse = z.object({
1998
- error: z.string(),
1999
- code: z.number().optional(),
2000
- retryAfter: z.number().optional()
2001
- });
2002
- var OkResponse = z.object({
2003
- ok: z.boolean(),
2004
- data: z.unknown().optional()
2005
- });
2006
- var ModifySubscriberStatusBody = z.object({
2007
- newStatus: z.enum(["active", "suspended", "deactivated"])
2008
- });
2009
- var ModifySubscriberBalanceBody = z.object({
2010
- amount: z.number().optional(),
2011
- setBalance: z.number().optional()
2012
- }).refine((v) => v.amount !== void 0 || v.setBalance !== void 0, {
2013
- message: "Provide either amount (delta) or setBalance (absolute)"
2014
- });
2015
- var ModifySubscriberContactInfoBody = z.object({
2016
- name: z.string().optional(),
2017
- mail: z.string().email().optional(),
2018
- phone: z.string().optional()
2019
- });
2020
- var SetTrafficRestrictionsBody = z.object({
2021
- blockData: z.boolean().optional(),
2022
- blockVoice: z.boolean().optional(),
2023
- blockSms: z.boolean().optional(),
2024
- blockRoaming: z.boolean().optional()
2025
- });
2026
- var ModifySteeringListBody = z.object({
2027
- steeringListId: z.number().int().positive()
2028
- });
2029
- var HlrSetBitrateBody = z.object({
2030
- limit: z.number().int().positive()
2031
- });
2032
- var AssignPackageBody = z.object({
2033
- packageTemplateId: z.number().int().positive(),
2034
- startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
2035
- });
2036
- var AssignRecurringPackageBody = z.object({
2037
- packageTemplateId: z.number().int().positive()
2038
- });
2039
- var ModifyPackageLimitsBody = z.object({
2040
- dataLimitMb: z.number().positive().optional(),
2041
- voiceLimitMin: z.number().nonnegative().optional(),
2042
- smsLimit: z.number().nonnegative().optional()
2043
- });
2044
- var ModifyPackageExpiryBody = z.object({
2045
- expiryDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
2046
- });
2047
- var ModifyPackageStatusBody = z.object({
2048
- status: z.enum(["active", "paused"])
2049
- });
2050
- var StopResumeRecurringBody = z.object({
2051
- action: z.enum(["stop", "resume"])
2052
- });
2053
- var CreatePackageTemplateBody = z.object({
2054
- name: z.string().min(1),
2055
- dataLimitMb: z.number().positive(),
2056
- validityDays: z.number().int().positive(),
2057
- price: z.number().nonnegative().optional(),
2058
- recurring: z.boolean().optional()
2059
- });
2060
- var ModifyTemplateCoreBody = z.object({
2061
- name: z.string().optional(),
2062
- dataLimitMb: z.number().positive().optional(),
2063
- validityDays: z.number().int().positive().optional()
2064
- });
2065
- var ModifyTemplateRecurringBody = z.object({
2066
- billingCycleDays: z.number().int().positive().optional(),
2067
- autoRenew: z.boolean().optional()
2068
- });
2069
- var ModifyTemplateThrottlingBody = z.object({
2070
- throttleSpeedKbps: z.number().int().nonnegative()
2071
- });
2072
- var ModifyAccountBalanceBody = z.object({
2073
- amount: z.number(),
2074
- mode: z.enum(["add", "subtract", "set"]).default("add")
2075
- });
2076
- var MoveSubscribersBody = z.object({
2077
- fromAccountId: z.number().int().positive(),
2078
- iccidStart: z.string().min(1),
2079
- iccidEnd: z.string().min(1)
2080
- });
2081
- var CreateLocationZoneBody = z.object({
2082
- name: z.string().min(1),
2083
- countries: z.array(z.string().length(2)).min(1)
2084
- });
2085
- var SendSmsBody = z.object({
2086
- text: z.string().min(1).max(160),
2087
- senderId: z.string().optional()
2088
- });
2089
- var ChangeSimStatusBody = z.object({
2090
- newStatus: z.enum(["Enable", "Disable"])
2091
- });
2092
- var DiagnoseSubscriberBody = z.object({
2093
- iccid: z.string().min(1)
2094
- });
2095
- var FleetHealthQuery = z.object({
2096
- accountId: z.coerce.number().int().positive().optional()
2097
- });
2098
- var ChurnRiskQuery = z.object({
2099
- daysSinceUsage: z.coerce.number().int().positive().default(14),
2100
- limit: z.coerce.number().int().positive().max(500).default(50)
2101
- });
2102
- var DetectUsageAnomaliesBody = z.object({
2103
- start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
2104
- end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
2105
- thresholdMultiplier: z.number().positive().default(2.5)
2106
- });
2107
- var OptimizePackageBody = z.object({
2108
- iccid: z.string().min(1)
2109
- });
2110
- var AuditNetworkCoverageBody = z.object({
2111
- locationZoneId: z.number().int().positive().optional()
2112
- });
2113
- var MarketingIntelligenceQuery = z.object({
2114
- segment: z.enum(["low_usage", "high_usage", "expiring_soon", "churned"]).optional()
2115
- });
2116
- var HighCostSubscribersQuery = z.object({
2117
- limit: z.coerce.number().int().positive().max(200).default(20),
2118
- start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
2119
- end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
2120
- });
2121
- var WebhookEventBody = z.record(z.string(), z.unknown());
2122
- var HealthResponse = z.object({
2123
- ok: z.literal(true),
2124
- version: z.string(),
2125
- build_sha: z.string()
2126
- });
2127
-
2128
- // ../../packages/ocs-client/dist/chunk-CGMK4FTD.js
2129
- var DEFAULT_OCS_BUDGET = {
2130
- attemptMs: 3e4,
2131
- maxAttempts: 3,
2132
- totalMs: 75e3
2133
- };
2134
- var OCS_METHOD_BUDGETS = {
2135
- // 3.4 MB, 88-128 s measured. 180 s is 1.4x the slowest observation. One
2136
- // attempt: payload size does not vary, so a retry only doubles the wait.
2137
- getCustomerTariff: { attemptMs: 18e4, maxAttempts: 1, totalMs: 18e4 }
2138
- };
2139
- var MIN_ATTEMPT_BUDGET_MS = 1e3;
2140
- var BACKOFF_BASE_MS = 500;
2141
- var OcsTimeoutError = class extends Error {
2142
- constructor(method, timeoutMs, budgetMs = timeoutMs, attempts = 1) {
2143
- super(
2144
- `[${method}] OCS request timed out after ${timeoutMs}ms` + (attempts > 1 ? ` on each of ${attempts} attempts` : "") + (budgetMs > timeoutMs ? ` (budget ${budgetMs}ms)` : "") + `. The upstream did not deliver a complete response in time \u2014 usually an oversized payload or a wedged upstream. Narrow the request, or raise OCS_TIMEOUT_MS / OCS_TOTAL_TIMEOUT_MS if this method is legitimately slower here.`
2145
- );
2146
- this.method = method;
2147
- this.timeoutMs = timeoutMs;
2148
- this.budgetMs = budgetMs;
2149
- this.attempts = attempts;
2150
- this.name = "OcsTimeoutError";
1902
+ // ../../packages/ocs-client/dist/chunk-LLQXWRFM.js
1903
+ var OCS_MAX_USAGE_WINDOW_DAYS = 7;
1904
+ function clampUsagePeriod(start, end, maxDays = OCS_MAX_USAGE_WINDOW_DAYS) {
1905
+ const endMs = Date.parse(`${end}T00:00:00Z`);
1906
+ const startMs = Date.parse(`${start}T00:00:00Z`);
1907
+ if (Number.isNaN(endMs) || Number.isNaN(startMs) || startMs > endMs) {
1908
+ return { start, end };
1909
+ }
1910
+ const minStartMs = endMs - (maxDays - 1) * 864e5;
1911
+ if (startMs < minStartMs) {
1912
+ return { start: new Date(minStartMs).toISOString().slice(0, 10), end };
1913
+ }
1914
+ return { start, end };
1915
+ }
1916
+ function lastNDaysPeriod(days = OCS_MAX_USAGE_WINDOW_DAYS, now = /* @__PURE__ */ new Date()) {
1917
+ const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
1918
+ const start = new Date(end);
1919
+ start.setUTCDate(end.getUTCDate() - (days - 1));
1920
+ return {
1921
+ start: start.toISOString().slice(0, 10),
1922
+ end: end.toISOString().slice(0, 10)
1923
+ };
1924
+ }
1925
+ function locationParams(iccid) {
1926
+ return { iccid };
1927
+ }
1928
+ function subscriberIdParams(iccid) {
1929
+ return { iccid };
1930
+ }
1931
+ function recurringPackageParams(iccid, packageTemplateId, options = {}) {
1932
+ if (options.activationAtFirstUse !== void 0 && options.startTimeUTC !== void 0) {
1933
+ throw new Error("activationAtFirstUse cannot be combined with startTimeUTC");
2151
1934
  }
2152
- method;
2153
- timeoutMs;
2154
- budgetMs;
2155
- attempts;
2156
- };
2157
- function envInt(name) {
2158
- const proc = globalThis["process"];
2159
- const parsed = Number(proc?.env?.[name]);
2160
- return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1935
+ return {
1936
+ subscriber: { iccid },
1937
+ packageTemplateId,
1938
+ ...options.startTimeUTC !== void 0 ? { startTimeUTC: options.startTimeUTC } : { activationAtFirstUse: options.activationAtFirstUse ?? true }
1939
+ };
2161
1940
  }
2162
- function ocsBudgetFor(method) {
2163
- const base = OCS_METHOD_BUDGETS[method] ?? DEFAULT_OCS_BUDGET;
2164
- const attemptMs = envInt("OCS_TIMEOUT_MS") ?? base.attemptMs;
2165
- const totalMs = Math.max(envInt("OCS_TOTAL_TIMEOUT_MS") ?? base.totalMs, attemptMs);
2166
- return { attemptMs, maxAttempts: base.maxAttempts, totalMs };
1941
+ function networkEventsOverPeriodParams(iccid, start, end) {
1942
+ return usageOverPeriodParams(iccid, start, end);
2167
1943
  }
2168
- function sleep2(ms, signal) {
2169
- return new Promise((resolve) => {
2170
- const timer = setTimeout(finish, ms);
2171
- function finish() {
2172
- clearTimeout(timer);
2173
- signal.removeEventListener("abort", finish);
2174
- resolve();
2175
- }
2176
- signal.addEventListener("abort", finish, { once: true });
2177
- });
1944
+ function usageOverPeriodParams(iccid, start, end) {
1945
+ const period = clampUsagePeriod(start, end);
1946
+ return { subscriber: { iccid }, period };
2178
1947
  }
2179
- async function withDeadline(work, deadline, ms) {
2180
- let timer;
2181
- try {
2182
- return await Promise.race([
2183
- work,
2184
- new Promise((_resolve, reject) => {
2185
- timer = setTimeout(() => reject(deadline), ms);
2186
- })
2187
- ]);
2188
- } finally {
2189
- if (timer !== void 0) clearTimeout(timer);
1948
+ function imsiFromSubscriberRecord(record) {
1949
+ if (!record || typeof record !== "object") return void 0;
1950
+ const rec = record;
1951
+ const inner = rec.getSingleSubscriber && typeof rec.getSingleSubscriber === "object" ? rec.getSingleSubscriber : rec;
1952
+ const direct = inner.imsi;
1953
+ if (typeof direct === "string" && direct.length > 0) return direct;
1954
+ for (const key of ["imsiList", "multiImsi"]) {
1955
+ const list = inner[key];
1956
+ if (!Array.isArray(list)) continue;
1957
+ for (const entry of list) {
1958
+ if (!entry || typeof entry !== "object") continue;
1959
+ const value = entry.imsi;
1960
+ if (typeof value === "string" && value.length > 0) return value;
1961
+ }
2190
1962
  }
1963
+ return void 0;
2191
1964
  }
2192
- async function runWithBudget(method, attempt, budget = ocsBudgetFor(method)) {
2193
- const deadlineAt = Date.now() + budget.totalMs;
2194
- const budgetController = new AbortController();
2195
- const budgetTimer = setTimeout(() => budgetController.abort(), budget.totalMs);
2196
- let attempts = 0;
2197
- let lastTimeout;
2198
- try {
2199
- for (let i = 0; i < budget.maxAttempts; i++) {
2200
- const remaining = deadlineAt - Date.now();
2201
- if (remaining < MIN_ATTEMPT_BUDGET_MS) break;
2202
- const attemptMs = Math.min(budget.attemptMs, remaining);
2203
- attempts++;
2204
- const attemptController = new AbortController();
2205
- const onBudgetAbort = () => attemptController.abort();
2206
- budgetController.signal.addEventListener("abort", onBudgetAbort, { once: true });
2207
- const attemptTimer = setTimeout(() => attemptController.abort(), attemptMs);
2208
- const breach = new OcsTimeoutError(method, attemptMs, budget.totalMs, attempts);
2209
- try {
2210
- return await withDeadline(
2211
- attempt(attemptController.signal, attemptMs),
2212
- breach,
2213
- attemptMs
2214
- );
2215
- } catch (err) {
2216
- if (!(err instanceof OcsTimeoutError)) throw err;
2217
- lastTimeout = new OcsTimeoutError(method, attemptMs, budget.totalMs, attempts);
2218
- } finally {
2219
- clearTimeout(attemptTimer);
2220
- budgetController.signal.removeEventListener("abort", onBudgetAbort);
2221
- attemptController.abort();
2222
- }
2223
- if (i === budget.maxAttempts - 1) break;
2224
- const backoff = Math.random() * BACKOFF_BASE_MS * 2 ** i;
2225
- if (Date.now() + backoff + MIN_ATTEMPT_BUDGET_MS > deadlineAt) break;
2226
- await sleep2(backoff, budgetController.signal);
1965
+ var OCS_PREPAID_PACKAGE_LIMIT_FIELDS = [
1966
+ "dataByte",
1967
+ "mocSecond",
1968
+ "mtcSecond",
1969
+ "moSms",
1970
+ "mtSms"
1971
+ ];
1972
+ var LIMIT_ALIASES = {
1973
+ dataByte: "dataByte",
1974
+ dataBytes: "dataByte",
1975
+ dataLimit: "dataByte",
1976
+ databyte: "dataByte",
1977
+ mocSecond: "mocSecond",
1978
+ mocSeconds: "mocSecond",
1979
+ voiceOutLimit: "mocSecond",
1980
+ mtcSecond: "mtcSecond",
1981
+ mtcSeconds: "mtcSecond",
1982
+ voiceInLimit: "mtcSecond",
1983
+ moSms: "moSms",
1984
+ moSmsNumber: "moSms",
1985
+ smsOutLimit: "moSms",
1986
+ mtSms: "mtSms",
1987
+ mtSmsNumber: "mtSms",
1988
+ smsInLimit: "mtSms"
1989
+ };
1990
+ function prepaidPackageLimits(limits) {
1991
+ const out = {};
1992
+ const unknown = [];
1993
+ for (const [key, value] of Object.entries(limits)) {
1994
+ const target = LIMIT_ALIASES[key];
1995
+ if (!target) {
1996
+ unknown.push(key);
1997
+ continue;
2227
1998
  }
2228
- } finally {
2229
- clearTimeout(budgetTimer);
1999
+ out[target] = value;
2230
2000
  }
2231
- throw lastTimeout ?? new OcsTimeoutError(method, budget.attemptMs, budget.totalMs, Math.max(attempts, 1));
2001
+ if (unknown.length > 0) {
2002
+ throw new Error(
2003
+ `Unknown package limit field(s): ${unknown.join(", ")}. OCS accepts only ${OCS_PREPAID_PACKAGE_LIMIT_FIELDS.join(", ")} inside newLimits (aliases: ${Object.keys(LIMIT_ALIASES).join(", ")}).`
2004
+ );
2005
+ }
2006
+ if (Object.keys(out).length === 0) {
2007
+ throw new Error(
2008
+ `No package limits to change. Provide at least one of ${OCS_PREPAID_PACKAGE_LIMIT_FIELDS.join(", ")}.`
2009
+ );
2010
+ }
2011
+ return out;
2232
2012
  }
2233
-
2234
- // ../../packages/ocs-client/dist/chunk-KELMML73.js
2235
- var RATE_LIMIT = 10;
2236
- var WINDOW_MS = 1e3;
2237
- var windows = /* @__PURE__ */ new Map();
2238
- function getWindowState(key) {
2239
- let s = windows.get(key);
2240
- if (!s) {
2241
- s = { log: [], gate: Promise.resolve() };
2242
- windows.set(key, s);
2013
+ function ocsLocalDateTime(value, bareDate = "end") {
2014
+ const trimmed = value.trim();
2015
+ if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
2016
+ return bareDate === "start" ? `${trimmed}T00:00:00` : `${trimmed}T23:59:59`;
2243
2017
  }
2244
- return s;
2018
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(trimmed)) return `${trimmed}:00`;
2019
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(trimmed)) {
2020
+ return trimmed.replace(/(?:Z|[+-]\d{2}:?\d{2})$/, "").slice(0, 19);
2021
+ }
2022
+ throw new Error(
2023
+ `"${value}" is not a date OCS can read. Use YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss \u2014 this field is a java.time.LocalDateTime and a zone offset is rejected.`
2024
+ );
2245
2025
  }
2246
- async function acquireToken(resellerId) {
2247
- const proc = globalThis["process"];
2248
- if (proc?.env?.["RATE_FLOOR_DISABLED"] === "true" || globalThis["RATE_FLOOR_DISABLED"] === "true") {
2026
+ function activePeriodFromPackages(listResponse, packageId) {
2027
+ const root = listResponse ?? {};
2028
+ const payload = root.listSubscriberPrepaidPackages ?? root.data ?? root;
2029
+ const packages = Array.isArray(payload?.packages) ? payload.packages : [];
2030
+ for (const entry of packages) {
2031
+ if (!entry || typeof entry !== "object") continue;
2032
+ const rec = entry;
2033
+ const id = rec.subscriberprepaidpackageid ?? rec.subscriberPrepaidPackageId;
2034
+ if (Number(id) !== packageId) continue;
2035
+ const activation = rec.tsactivationutc ?? rec.tsActivationUtc;
2036
+ const expiration = rec.tsexpirationutc ?? rec.tsExpirationUtc;
2037
+ const window = {};
2038
+ if (typeof activation === "string" && activation) window.activation = activation;
2039
+ if (typeof expiration === "string" && expiration) window.expiration = expiration;
2040
+ return window;
2041
+ }
2042
+ return void 0;
2043
+ }
2044
+ var OCS_ACTIVE_PERIOD_DEFAULT_COMMENT = "Active period changed via Carrier";
2045
+ function packageActivePeriodParams(packageId, change) {
2046
+ if (change.startDate === void 0 && change.endDate === void 0) {
2047
+ throw new Error("Provide startDate and/or endDate to change a package's active period.");
2048
+ }
2049
+ const activation = change.startDate !== void 0 ? ocsLocalDateTime(change.startDate, "start") : change.current?.activation;
2050
+ const expiration = change.endDate !== void 0 ? ocsLocalDateTime(change.endDate, "end") : change.current?.expiration;
2051
+ for (const [field, value, omitted] of [
2052
+ ["newActivationDateUtc", activation, "startDate"],
2053
+ ["newExpirationDateUtc", expiration, "endDate"]
2054
+ ]) {
2055
+ if (value !== void 0) continue;
2056
+ throw new Error(
2057
+ `OCS requires both bounds on an active-period change and rejects the whole request with "Missing '${field}'" when one is absent. Supply ${omitted}, or read the package's current window first (activePeriodFromPackages) and pass it as \`current\`.`
2058
+ );
2059
+ }
2060
+ return {
2061
+ packageId,
2062
+ // Already LocalDateTime-shaped when it came from `current`; normalising
2063
+ // again is a no-op and keeps a hand-passed value honest.
2064
+ newActivationDateUtc: ocsLocalDateTime(activation, "start"),
2065
+ newExpirationDateUtc: ocsLocalDateTime(expiration, "end"),
2066
+ comment: change.comment?.trim() || OCS_ACTIVE_PERIOD_DEFAULT_COMMENT
2067
+ };
2068
+ }
2069
+ function recurringIdForPackage(listResponse, packageId) {
2070
+ const root = listResponse ?? {};
2071
+ const payload = root.listSubscriberPrepaidPackages ?? root.data ?? root;
2072
+ const packages = Array.isArray(payload?.packages) ? payload.packages : [];
2073
+ for (const entry of packages) {
2074
+ if (!entry || typeof entry !== "object") continue;
2075
+ const rec = entry;
2076
+ const id = rec.subscriberprepaidpackageid ?? rec.subscriberPrepaidPackageId;
2077
+ if (Number(id) !== packageId) continue;
2078
+ const recurring = rec.recurringPackage ?? rec.recurringId;
2079
+ const value = Number(recurring);
2080
+ return Number.isFinite(value) && value > 0 ? value : void 0;
2081
+ }
2082
+ return void 0;
2083
+ }
2084
+ function emptyEsimStatusCounts() {
2085
+ return { active: 0, suspended: 0, inventory: 0, other: 0, total: 0 };
2086
+ }
2087
+ function addEsimStatusRow(acc, rec) {
2088
+ const count = Number(rec.count ?? 0);
2089
+ if (!Number.isFinite(count) || count === 0) return;
2090
+ const str2 = String(rec.statusStr ?? rec.name ?? rec.status ?? "").toUpperCase();
2091
+ if (str2 === "ACTIVATED" || str2 === "ACTIVE" || str2 === "AFFECTED") {
2092
+ acc.active += count;
2093
+ return;
2094
+ }
2095
+ if (str2 === "SUSPENDED") {
2096
+ acc.suspended += count;
2097
+ return;
2098
+ }
2099
+ if (str2 === "FREE" || str2 === "INVENTORY" || str2 === "NOT_ACTIVATED" || str2 === "AVAILABLE") {
2100
+ acc.inventory += count;
2101
+ return;
2102
+ }
2103
+ const num = rec.statusNum;
2104
+ if (typeof num === "number") {
2105
+ if (num === 1) acc.active += count;
2106
+ else if (num === 0) acc.inventory += count;
2107
+ else if (num === 2) acc.suspended += count;
2108
+ else acc.other += count;
2249
2109
  return;
2250
2110
  }
2251
- const state = getWindowState(resellerId);
2252
- const ticket = state.gate.then(async () => {
2253
- while (true) {
2254
- const now = Date.now();
2255
- if (state.log.length < RATE_LIMIT) {
2256
- state.log.push(now);
2257
- return;
2258
- }
2259
- const oldest = state.log[0];
2260
- const age = now - oldest;
2261
- if (age >= WINDOW_MS) {
2262
- state.log.shift();
2263
- state.log.push(now);
2264
- return;
2111
+ acc.other += count;
2112
+ }
2113
+ function extractEsimStatusCounts(json) {
2114
+ const acc = emptyEsimStatusCounts();
2115
+ const root = json ?? {};
2116
+ const payload = root.data ?? root.esimStatusPerAccount ?? root;
2117
+ const accounts = Array.isArray(payload) ? payload : Array.isArray(payload.account) ? payload.account : [];
2118
+ for (const account of accounts) {
2119
+ const rec = account ?? {};
2120
+ const sponsors = Array.isArray(rec.sponsor) ? rec.sponsor : [];
2121
+ if (sponsors.length > 0) {
2122
+ for (const sp of sponsors) {
2123
+ const esim = sp?.esim ?? {};
2124
+ const statuses = Array.isArray(esim.status) ? esim.status : [];
2125
+ for (const row of statuses) addEsimStatusRow(acc, row ?? {});
2265
2126
  }
2266
- const waitMs = WINDOW_MS - age + 1;
2267
- await new Promise((r) => setTimeout(r, waitMs));
2127
+ continue;
2268
2128
  }
2269
- });
2270
- state.gate = ticket;
2271
- return ticket;
2129
+ acc.active += Number(rec.active ?? 0);
2130
+ acc.suspended += Number(rec.suspended ?? 0);
2131
+ acc.inventory += Number(rec.inventory ?? rec.notActivated ?? rec.free ?? 0);
2132
+ acc.other += Number(rec.other ?? rec.terminated ?? 0);
2133
+ }
2134
+ acc.total = acc.active + acc.suspended + acc.inventory + acc.other;
2135
+ return acc;
2272
2136
  }
2273
- var ENDPOINT_LIMITS_PER_MIN = {
2274
- // Global sentinel (keyed as "__global__")
2275
- __global__: 600,
2276
- // 30/min — tightest endpoint
2277
- subscriberNetworkEventsOverPeriod: 30,
2278
- // 100/min
2279
- subscriberUsageOverPeriod: 100,
2280
- // 150/min
2281
- listLocationZoneElement: 150,
2282
- sendMtSms: 150,
2283
- // 300/min (explicit; also the fallback default)
2284
- modifySubscriberMobilePlan: 300,
2285
- affectRecurringPackageToSubscriber: 300,
2286
- affectSubscriberFakePhoneNumber: 300,
2287
- affectSubscriberRealPhoneNumber: 300,
2288
- esimStatusPerAccount: 300,
2289
- getSubscriberActivePeriod: 300,
2290
- hlrGetBitrate: 300,
2291
- hlrSetBitrate: 300,
2292
- listSubscriber: 300,
2293
- listSubscriberPrepaidPackages: 300,
2294
- moveSubscriberRangeToAccount: 300,
2295
- pushSteeringToSubs: 300,
2296
- setSubscriberTrafficRestrictions: 300,
2297
- getSingleSubscriber: 300,
2298
- getSubscriberLocationByCellId: 300
2299
- };
2300
- var DEFAULT_LIMIT_PER_MIN = 300;
2301
- var WINDOW_MS2 = 6e4;
2302
- var METRIC_EMIT_MIN_INTERVAL_MS = 1e4;
2303
- var BATCH_FRACTION = 0.2;
2304
- function resellerKeyHash(resellerKey) {
2305
- if (!resellerKey) return "00000000";
2306
- let h = 5381;
2307
- for (let i = 0; i < resellerKey.length; i++) {
2308
- h = ((h << 5) + h ^ resellerKey.charCodeAt(i)) >>> 0;
2137
+ function esimStatusPerAccountParams(args) {
2138
+ if (typeof args === "number") return { accountId: args };
2139
+ if (args.accountId !== void 0) return { accountId: args.accountId };
2140
+ if (args.resellerId !== void 0) return { resellerId: args.resellerId };
2141
+ throw new Error("esimStatusPerAccountParams needs an accountId or a resellerId");
2142
+ }
2143
+ var UNLIMITED_DATA_BYTES = 100 * 1024 * 1024 * 1024;
2144
+ var BYTES_PER_MB = 1048576;
2145
+ var TEMPLATE_DATA_KEYS = ["databyte", "dataBytes", "dataLimit"];
2146
+ function readTemplateDataBytes(t) {
2147
+ for (const key of TEMPLATE_DATA_KEYS) {
2148
+ const v = t[key];
2149
+ if (typeof v === "number" && Number.isFinite(v)) return v;
2309
2150
  }
2310
- return h.toString(16).padStart(8, "0");
2151
+ return void 0;
2311
2152
  }
2312
- function emitMetric(endpoint, resellerKey, callsInWindow, limitPerMin) {
2313
- const pct2 = Math.round(callsInWindow / limitPerMin * 100);
2314
- const reseller_hash = resellerKeyHash(resellerKey);
2315
- console.error(
2316
- JSON.stringify({
2317
- metric: "carrier_ocs_calls_per_min",
2318
- endpoint,
2319
- reseller_hash,
2320
- calls_in_window: callsInWindow,
2321
- limit_per_min: limitPerMin,
2322
- utilisation_pct: pct2,
2323
- alert: pct2 >= 80,
2324
- ts: Date.now()
2325
- })
2326
- );
2327
- if (pct2 >= 80) {
2328
- console.warn(
2329
- `[rate-governor] ALERT: ${endpoint} at ${pct2}% capacity (${callsInWindow}/${limitPerMin} per min) reseller=${reseller_hash}`
2330
- );
2153
+ function throttlePercentFromMb(mb, totalBytes = UNLIMITED_DATA_BYTES) {
2154
+ if (!Number.isFinite(mb) || mb <= 0 || totalBytes <= 0) return 100;
2155
+ const pct2 = Math.round(mb * BYTES_PER_MB / totalBytes * 100);
2156
+ return Math.min(100, Math.max(1, pct2));
2157
+ }
2158
+ var OCS_THROTTLE_KBPS = [
2159
+ 128,
2160
+ 256,
2161
+ 384,
2162
+ 512,
2163
+ 1024,
2164
+ 3072,
2165
+ 5120,
2166
+ 7680,
2167
+ 10240,
2168
+ 20480,
2169
+ 51200,
2170
+ 102400
2171
+ ];
2172
+ var OCS_HLR_BITRATE_LIMITS = [
2173
+ "KB_32",
2174
+ "KB_64",
2175
+ "KB_128",
2176
+ "KB_256",
2177
+ "KB_384",
2178
+ "KB_512",
2179
+ "KB_1024",
2180
+ "KB_2048",
2181
+ "KB_3072",
2182
+ "KB_5120",
2183
+ "KB_7680",
2184
+ "KB_10240",
2185
+ "KB_20480",
2186
+ "KB_51200",
2187
+ "KB_102400",
2188
+ "UNLIMITED"
2189
+ ];
2190
+ var OCS_HLR_BITRATE_SET = new Set(OCS_HLR_BITRATE_LIMITS);
2191
+ function ocsHlrBitrate(raw) {
2192
+ const token = hlrBitrateToken(raw);
2193
+ if (token !== void 0 && OCS_HLR_BITRATE_SET.has(token)) return token;
2194
+ return {
2195
+ error: "limit must be an HLR bitrate in Kbps (32, 64, 128, 256, 384, 512, 1024, 2048, 3072, 5120, 7680, 10240, 20480, 51200, 102400) or UNLIMITED"
2196
+ };
2197
+ }
2198
+ function ocsHlrBitrateFromBps(raw) {
2199
+ if (raw === 0) return "UNLIMITED";
2200
+ if (!Number.isInteger(raw) || raw < 0 || raw % 1e3 !== 0) {
2201
+ return {
2202
+ error: "bitrate must be an HLR speed in bits per second that matches a KB_ level (for example 256000), or 0 for UNLIMITED"
2203
+ };
2331
2204
  }
2205
+ const mapped = ocsHlrBitrate(raw / 1e3);
2206
+ if (typeof mapped === "string") return mapped;
2207
+ return {
2208
+ error: "bitrate must be an HLR speed in bits per second that matches a KB_ level (for example 256000), or 0 for UNLIMITED"
2209
+ };
2332
2210
  }
2333
- var _endpointWindows = /* @__PURE__ */ new Map();
2334
- function _getEndpointState(key) {
2335
- let s = _endpointWindows.get(key);
2336
- if (!s) {
2337
- s = {
2338
- log: [],
2339
- batchLog: [],
2340
- gate: Promise.resolve(),
2341
- lastMetricEmitAt: 0,
2342
- metricBelowHighUtil: true
2211
+ function hlrBitrateToken(raw) {
2212
+ if (typeof raw === "number" && Number.isInteger(raw) && raw > 0) return `KB_${raw}`;
2213
+ if (typeof raw !== "string") return void 0;
2214
+ const key = raw.trim().toUpperCase().replace(/\s+/g, "_");
2215
+ if (key === "UNLIMITED") return "UNLIMITED";
2216
+ if (key.startsWith("KB_")) return key;
2217
+ if (/^\d+$/.test(key)) return `KB_${key}`;
2218
+ return void 0;
2219
+ }
2220
+ function ocsSimStatus(raw) {
2221
+ const key = raw.trim().toLowerCase();
2222
+ if (key === "enable" || key === "enabled" || key === "affected") return "Affected";
2223
+ if (key === "disable" || key === "disabled" || key === "free") return "Free";
2224
+ if (key === "deleted") {
2225
+ return {
2226
+ error: "DELETED is not a changeSimStatus value. newStatus must be Enable or Disable"
2343
2227
  };
2344
- _endpointWindows.set(key, s);
2345
2228
  }
2346
- return s;
2229
+ return { error: "newStatus must be Enable or Disable" };
2347
2230
  }
2348
- function _prune(log, windowStart) {
2349
- let i = 0;
2350
- while (i < log.length && log[i] <= windowStart) i++;
2351
- return i > 0 ? log.slice(i) : log;
2231
+ var OCS_PERIODICITY = { DAILY: 0, WEEKLY: 1, MONTHLY: 2 };
2232
+ var OCS_PERIODICITY_VALUES = [0, 1, 2];
2233
+ var CEILING_MULTIPLIERS = [20, 25, 50, 100];
2234
+ var SECONDS_PER_DAY = 86400;
2235
+ function throttledBytesPerPeriod(kbps, periodDays = 1) {
2236
+ if (!Number.isFinite(kbps) || kbps <= 0) return 0;
2237
+ return kbps * 1e3 * SECONDS_PER_DAY * Math.max(1, periodDays) / 8;
2352
2238
  }
2353
- async function acquireEndpointSlot(resellerId, endpoint, priority = "interactive") {
2354
- const proc = globalThis["process"];
2355
- if (proc?.env?.["RATE_FLOOR_DISABLED"] === "true" || globalThis["RATE_FLOOR_DISABLED"] === "true") {
2356
- return;
2239
+ function unlimitedCeilingFor(windowMb, throttleKbps, periodDays = 1) {
2240
+ const windowBytes = windowMb * BYTES_PER_MB;
2241
+ const unreachableAbove = windowBytes + throttledBytesPerPeriod(throttleKbps ?? 0, periodDays);
2242
+ for (const multiplier of CEILING_MULTIPLIERS) {
2243
+ const ceilingBytes = windowBytes * multiplier;
2244
+ if (ceilingBytes > unreachableAbove) {
2245
+ return { ceilingBytes, percent: Math.round(100 / multiplier) };
2246
+ }
2357
2247
  }
2358
- await _acquireOneSlot(resellerId, "__global__", priority);
2359
- await _acquireOneSlot(resellerId, endpoint, priority);
2248
+ const last = CEILING_MULTIPLIERS[CEILING_MULTIPLIERS.length - 1];
2249
+ return { ceilingBytes: windowBytes * last, percent: Math.round(100 / last) };
2360
2250
  }
2361
- async function _acquireOneSlot(resellerId, endpoint, priority) {
2362
- const bucketKey = `${resellerId}:${endpoint}`;
2363
- const limitPerMin = ENDPOINT_LIMITS_PER_MIN[endpoint] ?? DEFAULT_LIMIT_PER_MIN;
2364
- const batchCap = Math.floor(limitPerMin * BATCH_FRACTION);
2365
- const state = _getEndpointState(bucketKey);
2366
- const ticket = state.gate.then(async () => {
2367
- while (true) {
2368
- const now = Date.now();
2369
- const windowStart = now - WINDOW_MS2;
2370
- state.log = _prune(state.log, windowStart);
2371
- state.batchLog = _prune(state.batchLog, windowStart);
2372
- const totalInWindow = state.log.length;
2373
- const batchInWindow = state.batchLog.length;
2374
- if (totalInWindow > 0 && endpoint !== "__global__") {
2375
- const utilPct = Math.round(totalInWindow / limitPerMin * 100);
2376
- const intervalOk = now - state.lastMetricEmitAt >= METRIC_EMIT_MIN_INTERVAL_MS;
2377
- const enteredHighUtil = utilPct >= 80 && state.metricBelowHighUtil;
2378
- if (intervalOk || enteredHighUtil) {
2379
- emitMetric(endpoint, resellerId, totalInWindow, limitPerMin);
2380
- state.lastMetricEmitAt = now;
2381
- if (utilPct >= 80) {
2382
- state.metricBelowHighUtil = false;
2383
- }
2384
- }
2385
- if (utilPct < 80) {
2386
- state.metricBelowHighUtil = true;
2387
- }
2388
- }
2389
- const globalFull = totalInWindow >= limitPerMin;
2390
- const batchFull = priority === "batch" && batchInWindow >= batchCap;
2391
- if (!globalFull && !batchFull) {
2392
- state.log.push(now);
2393
- if (priority === "batch") {
2394
- state.batchLog.push(now);
2395
- }
2396
- return;
2397
- }
2398
- let waitMs;
2399
- if (globalFull) {
2400
- const oldest = state.log[0];
2401
- waitMs = WINDOW_MS2 - (now - oldest) + 1;
2402
- } else {
2403
- const oldestBatch = state.batchLog[0];
2404
- waitMs = WINDOW_MS2 - (now - oldestBatch) + 1;
2405
- }
2406
- await new Promise((r) => setTimeout(r, waitMs));
2251
+ function normalizePackageTemplate(template) {
2252
+ const out = { ...template };
2253
+ const unlimitedFlag = out.unlimited === true;
2254
+ const current = readTemplateDataBytes(out);
2255
+ const isUnlimited = unlimitedFlag || current === void 0 || current === 0;
2256
+ const throttleAfterMb = typeof out.throttleAfterMb === "number" ? out.throttleAfterMb : void 0;
2257
+ const throttleKbps = typeof out.throttleKbps === "number" ? out.throttleKbps : void 0;
2258
+ const periodDays = typeof out.perioddays === "number" ? out.perioddays : 1;
2259
+ if (throttleKbps !== void 0 && !OCS_THROTTLE_KBPS.includes(throttleKbps)) {
2260
+ throw new Error(
2261
+ `throttleKbps ${throttleKbps} is not an OCS bitrate. Allowed: ${OCS_THROTTLE_KBPS.join(", ")}. This field is a SPEED in Kbps, not a data allowance.`
2262
+ );
2263
+ }
2264
+ const periodicity = out.recurringPeriodicityType;
2265
+ if (periodicity !== void 0 && !OCS_PERIODICITY_VALUES.includes(periodicity)) {
2266
+ throw new Error(
2267
+ `recurringPeriodicityType ${periodicity} is out of range. Allowed: 0=Daily, 1=Weekly, 2=Monthly. (The live v2 Mango templates use 3, which is not a valid value.)`
2268
+ );
2269
+ }
2270
+ if (out.recurring === true && (periodicity === OCS_PERIODICITY.DAILY || periodicity === OCS_PERIODICITY.WEEKLY) && out.nbOccurrence === void 0) {
2271
+ throw new Error(
2272
+ "nbOccurrence is required for a daily or weekly recurring template; OCS only treats it as optional for monthly templates."
2273
+ );
2274
+ }
2275
+ if (throttleKbps !== void 0) out.throttlingThreshold1Limit = throttleKbps;
2276
+ delete out.throttleKbps;
2277
+ if (isUnlimited) {
2278
+ let ceiling = UNLIMITED_DATA_BYTES;
2279
+ if (throttleAfterMb !== void 0 && throttleAfterMb > 0) {
2280
+ const sized = unlimitedCeilingFor(throttleAfterMb, throttleKbps, periodDays);
2281
+ ceiling = sized.ceilingBytes;
2282
+ out.throttlingThreshold1Perc = sized.percent;
2407
2283
  }
2408
- });
2409
- state.gate = ticket;
2410
- return ticket;
2411
- }
2412
- function getLimitForEndpoint(endpoint) {
2413
- return ENDPOINT_LIMITS_PER_MIN[endpoint] ?? DEFAULT_LIMIT_PER_MIN;
2284
+ for (const key of TEMPLATE_DATA_KEYS) {
2285
+ if (key in out) out[key] = ceiling;
2286
+ }
2287
+ if (!TEMPLATE_DATA_KEYS.some((k) => k in out)) out.databyte = ceiling;
2288
+ }
2289
+ delete out.unlimited;
2290
+ if (throttleAfterMb !== void 0) {
2291
+ if (!isUnlimited) {
2292
+ const total = readTemplateDataBytes(out) ?? UNLIMITED_DATA_BYTES;
2293
+ out.throttlingThreshold1Perc = throttlePercentFromMb(throttleAfterMb, total);
2294
+ }
2295
+ out.throttlingActive = out.throttlingActive ?? true;
2296
+ }
2297
+ delete out.throttleAfterMb;
2298
+ return out;
2414
2299
  }
2415
- function getRateLimitWindowCounts(resellerKey, endpoint) {
2416
- const bucketKey = `${resellerKey}:${endpoint}`;
2417
- const state = _endpointWindows.get(bucketKey);
2418
- if (!state) {
2419
- return { calls_in_window: 0, batch_calls_in_window: 0 };
2300
+ function normalizePackageTemplateChanges(changes) {
2301
+ const out = { ...changes };
2302
+ const explicitlyUnlimited = out.unlimited === true;
2303
+ for (const key of TEMPLATE_DATA_KEYS) {
2304
+ if (!(key in out)) continue;
2305
+ if (out[key] === 0 || explicitlyUnlimited) out[key] = UNLIMITED_DATA_BYTES;
2420
2306
  }
2421
- const windowStart = Date.now() - WINDOW_MS2;
2422
- let calls_in_window = 0;
2423
- for (const ts of state.log) {
2424
- if (ts > windowStart) calls_in_window++;
2307
+ if (explicitlyUnlimited && !TEMPLATE_DATA_KEYS.some((k) => k in out)) {
2308
+ out.databyte = UNLIMITED_DATA_BYTES;
2425
2309
  }
2426
- let batch_calls_in_window = 0;
2427
- for (const ts of state.batchLog) {
2428
- if (ts > windowStart) batch_calls_in_window++;
2310
+ delete out.unlimited;
2311
+ const throttleAfterMb = out.throttleAfterMb;
2312
+ if (typeof throttleAfterMb === "number") {
2313
+ const total = readTemplateDataBytes(out) ?? UNLIMITED_DATA_BYTES;
2314
+ out.throttlingThreshold1Perc = throttlePercentFromMb(throttleAfterMb, total);
2429
2315
  }
2430
- return { calls_in_window, batch_calls_in_window };
2316
+ delete out.throttleAfterMb;
2317
+ return out;
2431
2318
  }
2432
- var OCS_BARE_INT_RESELLER_METHODS = [
2433
- "listSponsor",
2434
- "listSteeringList",
2435
- "listDetailedDestinationList",
2436
- "listDetailedLocationZone",
2437
- "getCustomerTariff"
2319
+ var LOCATION_ZONE_KEYS = /* @__PURE__ */ new Set(["locationZoneName", "name", "tadigList", "tadigs", "networkProfileId"]);
2320
+ function zoneRecord(input) {
2321
+ if (typeof input !== "string") {
2322
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
2323
+ return { ok: false, error: "zone must be a JSON object" };
2324
+ }
2325
+ return { ok: true, body: input };
2326
+ }
2327
+ let parsed;
2328
+ try {
2329
+ parsed = JSON.parse(input);
2330
+ } catch {
2331
+ return { ok: false, error: "zone must be a JSON object" };
2332
+ }
2333
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2334
+ return { ok: false, error: "zone must be a JSON object" };
2335
+ }
2336
+ return { ok: true, body: parsed };
2337
+ }
2338
+ function zoneName(raw) {
2339
+ if (raw === void 0 || raw === null || raw === "") return { error: "locationZoneName is required" };
2340
+ if (typeof raw !== "string") return { error: "locationZoneName must be a string" };
2341
+ const name = raw.trim();
2342
+ if (!name) return { error: "locationZoneName is required" };
2343
+ return name;
2344
+ }
2345
+ function zoneTadigs(raw) {
2346
+ if (raw === void 0 || raw === null || raw === "") return { error: "tadigList is required" };
2347
+ const parts = typeof raw === "string" ? raw.split(/[\s,]+/) : Array.isArray(raw) ? raw : null;
2348
+ if (!parts) return { error: "tadigList must be a list of TADIG codes" };
2349
+ const list = [];
2350
+ for (const part of parts) {
2351
+ if (typeof part !== "string") return { error: "tadigList must be a list of TADIG codes" };
2352
+ const code = part.trim();
2353
+ if (code) list.push(code);
2354
+ }
2355
+ if (list.length === 0) return { error: "tadigList is required" };
2356
+ return list;
2357
+ }
2358
+ function zoneProfile(raw) {
2359
+ if (typeof raw === "number" && Number.isInteger(raw) && raw > 0) return raw;
2360
+ if (typeof raw === "string" && /^\d+$/.test(raw.trim())) {
2361
+ const n = Number(raw.trim());
2362
+ return n > 0 ? n : void 0;
2363
+ }
2364
+ return void 0;
2365
+ }
2366
+ function locationZoneCreateParams(input) {
2367
+ const parsed = zoneRecord(input);
2368
+ if (!parsed.ok) return { error: parsed.error };
2369
+ const body = parsed.body;
2370
+ if ("countries" in body || "operators" in body) {
2371
+ return {
2372
+ error: "createLocationZone has no countries or operators field. Send tadigList (TADIG codes) and networkProfileId."
2373
+ };
2374
+ }
2375
+ for (const key of Object.keys(body)) {
2376
+ if (!LOCATION_ZONE_KEYS.has(key)) {
2377
+ return { error: "createLocationZone accepts locationZoneName, tadigList, and networkProfileId" };
2378
+ }
2379
+ }
2380
+ const name = zoneName("locationZoneName" in body ? body.locationZoneName : body.name);
2381
+ if (typeof name !== "string") return name;
2382
+ const tadigList = zoneTadigs("tadigList" in body ? body.tadigList : body.tadigs);
2383
+ if (!Array.isArray(tadigList)) return tadigList;
2384
+ const networkProfileId = zoneProfile(body.networkProfileId);
2385
+ if (networkProfileId === void 0) {
2386
+ return { error: "networkProfileId must be a positive integer" };
2387
+ }
2388
+ return { locationZoneName: name, tadigList, networkProfileId };
2389
+ }
2390
+ var PACKAGE_TEMPLATE_CREATE_FIELDS = [
2391
+ "cost",
2392
+ "databyte",
2393
+ "deleted",
2394
+ "destinationzoneid",
2395
+ "esimSponsor",
2396
+ "hotspotBlocked",
2397
+ "locationzoneid",
2398
+ "mocsecond",
2399
+ "mosmsnumber",
2400
+ "mtcsecond",
2401
+ "mtsmsnumber",
2402
+ "nbOccurrence",
2403
+ "pckSubPrePaidPackagesDataSlicesTmpl",
2404
+ "perioddays",
2405
+ "picture",
2406
+ "prepaidpackagetemplateid",
2407
+ "prepaidpackagetemplatename",
2408
+ "priority",
2409
+ "rdbDestinationZones",
2410
+ "rdbLocationZones",
2411
+ "recurring",
2412
+ "recurringPeriodicityFrequency",
2413
+ "recurringPeriodicityType",
2414
+ "reportUnitsPreviousPackage",
2415
+ "reseller",
2416
+ "resellerid",
2417
+ "sponsors",
2418
+ "throttleTillEnd",
2419
+ "throttlingActive",
2420
+ "throttlingErrorAction",
2421
+ "throttlingThreshold1Limit",
2422
+ "throttlingThreshold1Perc",
2423
+ "throttlingThreshold2Limit",
2424
+ "throttlingThreshold2Perc",
2425
+ "throttlingThreshold3Limit",
2426
+ "throttlingThreshold3Perc",
2427
+ "throttlingThreshold4Limit",
2428
+ "throttlingThreshold4Perc",
2429
+ "uiEndAvailibilityPeriod",
2430
+ "uiStartAvailablePeriod",
2431
+ "uiVisible",
2432
+ "userUiName"
2438
2433
  ];
2439
- var OCS_BARE_INT_OTHER_METHODS = {
2440
- listLocationZoneElement: "locationZoneId",
2441
- listDestinationListPrefix: "destinationListId",
2442
- listVoipTariffRule: "voipPlanId",
2443
- getSimProviderStatus: "simId",
2444
- deleteSubscriberPackage: "packageId"
2445
- };
2446
- var OCS_PARAM_RENAMES = {
2447
- getResellerInfo: { resellerId: "id" },
2448
- modifyPPTCore: {
2449
- templateId: "prepaidpackagetemplateid",
2450
- name: "prepaidpackagetemplatename",
2451
- volume: "databyte",
2452
- validityDays: "perioddays"
2453
- },
2454
- modifyPPTRecurring: { templateId: "prepaidpackagetemplateid" },
2455
- modifyPPTThrottling: { templateId: "prepaidpackagetemplateid" },
2456
- // Console NL-create and the MCP tool both advertise `name` / `volume` /
2457
- // `validityDays`. OCS createPrepaidPackageTemplate rejects unknown keys
2458
- // (Jackson code 2) — live failure was Unrecognized field "name".
2459
- createPrepaidPackageTemplate: {
2460
- name: "prepaidpackagetemplatename",
2461
- volume: "databyte",
2462
- validityDays: "perioddays",
2463
- dataLimit: "databyte"
2464
- },
2465
- createLocationZone: { name: "locationZoneName" }
2434
+ var PACKAGE_TEMPLATE_CREATE_FIELD_SET = new Set(PACKAGE_TEMPLATE_CREATE_FIELDS);
2435
+ var PACKAGE_TEMPLATE_CREATE_ALIASES = {
2436
+ name: "prepaidpackagetemplatename",
2437
+ volume: "databyte",
2438
+ dataLimit: "databyte",
2439
+ dataBytes: "databyte",
2440
+ validityDays: "perioddays",
2441
+ periodDays: "perioddays",
2442
+ locationZoneId: "locationzoneid",
2443
+ destinationZoneId: "destinationzoneid",
2444
+ resellerId: "resellerid"
2466
2445
  };
2467
- function applyParamRenames(method, params) {
2468
- const renames = OCS_PARAM_RENAMES[method];
2469
- if (!renames || !params || typeof params !== "object" || Array.isArray(params)) {
2470
- return params;
2446
+ function templateRecord(input) {
2447
+ if (typeof input !== "string") {
2448
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
2449
+ return { ok: false, error: "template must be a JSON object" };
2450
+ }
2451
+ return { ok: true, body: input };
2471
2452
  }
2472
- const record = params;
2473
- let out;
2474
- for (const [from, to] of Object.entries(renames)) {
2475
- if (!Object.prototype.hasOwnProperty.call(record, from)) continue;
2476
- out ??= { ...record };
2477
- if (!Object.prototype.hasOwnProperty.call(record, to)) out[to] = record[from];
2478
- delete out[from];
2453
+ let parsed;
2454
+ try {
2455
+ parsed = JSON.parse(input);
2456
+ } catch {
2457
+ return { ok: false, error: "template must be a JSON object" };
2479
2458
  }
2480
- return out ?? params;
2481
- }
2482
- function isBareIntResellerMethod(method) {
2483
- return OCS_BARE_INT_RESELLER_METHODS.includes(method);
2484
- }
2485
- function isBareIntMethod(method) {
2486
- return isBareIntResellerMethod(method) || Object.prototype.hasOwnProperty.call(OCS_BARE_INT_OTHER_METHODS, method);
2459
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2460
+ return { ok: false, error: "template must be a JSON object" };
2461
+ }
2462
+ return { ok: true, body: parsed };
2487
2463
  }
2488
- function bareIntIdName(method) {
2489
- if (isBareIntResellerMethod(method)) return "resellerId";
2490
- return OCS_BARE_INT_OTHER_METHODS[method] ?? "id";
2464
+ function templateName(raw) {
2465
+ if (typeof raw !== "string") return { error: "prepaidpackagetemplatename must be a string" };
2466
+ const name = raw.trim();
2467
+ if (!name) return { error: "prepaidpackagetemplatename is required" };
2468
+ return name;
2491
2469
  }
2492
- function assertBareIntParam(method, params) {
2493
- if (!isBareIntMethod(method)) return;
2494
- if (typeof params === "number") return;
2495
- const shape = params === null ? "null" : Array.isArray(params) ? "array" : typeof params === "object" ? "object" : typeof params;
2496
- throw new TypeError(
2497
- `OCS method "${method}" takes a BARE INTEGER ${bareIntIdName(method)}, but received ${shape}. Sending an object makes OCS reject the whole request with "Cannot deserialize value of type java.lang.Integer from Object value", which surfaces in the UI as an empty panel. Pass the id itself, e.g. call("${method}", 1170).`
2498
- );
2470
+ function packageTemplateCreateParams(input) {
2471
+ const parsed = templateRecord(input);
2472
+ if (!parsed.ok) return { error: parsed.error };
2473
+ const body = parsed.body;
2474
+ if ("price" in body) return { error: "createPrepaidPackageTemplate has no price field" };
2475
+ if ("description" in body) return { error: "createPrepaidPackageTemplate has no description field" };
2476
+ const out = {};
2477
+ for (const [key, value] of Object.entries(body)) {
2478
+ if (PACKAGE_TEMPLATE_CREATE_FIELD_SET.has(key)) out[key] = value;
2479
+ }
2480
+ for (const [key, value] of Object.entries(body)) {
2481
+ const target = PACKAGE_TEMPLATE_CREATE_ALIASES[key];
2482
+ if (!target) {
2483
+ if (!PACKAGE_TEMPLATE_CREATE_FIELD_SET.has(key)) {
2484
+ return { error: `createPrepaidPackageTemplate does not accept ${key}` };
2485
+ }
2486
+ continue;
2487
+ }
2488
+ if (target in out) continue;
2489
+ if (target === "prepaidpackagetemplatename") {
2490
+ const name = templateName(value);
2491
+ if (typeof name !== "string") return name;
2492
+ out[target] = name;
2493
+ continue;
2494
+ }
2495
+ out[target] = value;
2496
+ }
2497
+ if ("prepaidpackagetemplatename" in out) {
2498
+ const name = templateName(out["prepaidpackagetemplatename"]);
2499
+ if (typeof name !== "string") return name;
2500
+ out["prepaidpackagetemplatename"] = name;
2501
+ }
2502
+ return out;
2499
2503
  }
2500
- var OcsApiError = class extends Error {
2501
- constructor(code, message, method, details = {}) {
2502
- super(`[${method}] OCS error ${code}: ${message}`);
2503
- this.code = code;
2504
- this.method = method;
2505
- this.name = "OcsApiError";
2506
- this.details = details;
2504
+ function packageTemplateCreateFromToolJson(template) {
2505
+ let parsed;
2506
+ try {
2507
+ parsed = JSON.parse(template);
2508
+ } catch {
2509
+ return { error: "template must be a JSON object" };
2507
2510
  }
2508
- code;
2509
- method;
2510
- details;
2511
- };
2512
- var OcsUnknownMethodError = class extends Error {
2513
- constructor(method) {
2514
- super(
2515
- `[${method}] is not a known OCS v1 method. It is not in the verified set of ${OCS_V1_METHODS.size} methods (createAccount/createSubscriber do not exist on the OCS). If this is a genuinely new method, add it to OCS_V1_METHODS or construct the client without { strictMethods: true }.`
2516
- );
2517
- this.method = method;
2518
- this.name = "OcsUnknownMethodError";
2511
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2512
+ return { error: "template must be a JSON object" };
2519
2513
  }
2520
- method;
2514
+ let normalized;
2515
+ try {
2516
+ normalized = normalizePackageTemplate(parsed);
2517
+ } catch (err) {
2518
+ const message = err instanceof Error ? err.message : "template could not be normalized";
2519
+ return { error: message };
2520
+ }
2521
+ return packageTemplateCreateParams(normalized);
2522
+ }
2523
+ var PACKAGE_TEMPLATE_MODIFY_EXTRA = {
2524
+ modifyPPTCore: ["changeSponsor"],
2525
+ modifyPPTRecurring: [],
2526
+ modifyPPTThrottling: []
2521
2527
  };
2522
- var OcsContractError = class _OcsContractError extends Error {
2523
- constructor(method, issues, payload) {
2524
- super(
2525
- `[${method}] OCS response failed contract validation: ${_OcsContractError.summarize(issues)}`
2526
- );
2527
- this.method = method;
2528
- this.issues = issues;
2529
- this.payload = payload;
2530
- this.name = "OcsContractError";
2528
+ var PACKAGE_TEMPLATE_MODIFY_IDENTITY = /* @__PURE__ */ new Set(["templateId", "prepaidpackagetemplateid"]);
2529
+ var PACKAGE_TEMPLATE_MODIFY_ALIASES = {
2530
+ ...PACKAGE_TEMPLATE_CREATE_ALIASES,
2531
+ periodicity: "recurringPeriodicityType",
2532
+ occurrences: "nbOccurrence",
2533
+ firstThresholdPercent: "throttlingThreshold1Perc",
2534
+ firstThresholdLimitKbps: "throttlingThreshold1Limit",
2535
+ errorAction: "throttlingErrorAction"
2536
+ };
2537
+ function changesRecord(input) {
2538
+ if (typeof input !== "string") {
2539
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
2540
+ return { ok: false, error: "changes must be a JSON object" };
2541
+ }
2542
+ return { ok: true, body: input };
2531
2543
  }
2532
- method;
2533
- issues;
2534
- payload;
2535
- /** One-line, human-readable join of the issues — for logs and the error message. */
2536
- get summary() {
2537
- return _OcsContractError.summarize(this.issues);
2544
+ let parsed;
2545
+ try {
2546
+ parsed = JSON.parse(input);
2547
+ } catch {
2548
+ return { ok: false, error: "changes must be a JSON object" };
2538
2549
  }
2539
- static summarize(issues) {
2540
- return issues.map((i) => `${i.path}: ${i.message}`).join("; ");
2550
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2551
+ return { ok: false, error: "changes must be a JSON object" };
2541
2552
  }
2542
- };
2543
- function ocsErrorMessage(code, rawMsg, rawBody) {
2544
- switch (code) {
2545
- case 12:
2546
- return {
2547
- message: "OCS resource is read-only (code 12). The package/subscriber/account state does not allow this mutation. Often returned for end-of-life subscribers or finalized invoices.",
2548
- details: {}
2549
- };
2550
- case 17:
2551
- return {
2552
- message: "Subscriber is end-of-life (code 17). It can no longer be mutated. Use modify_subscriber_status to revive before further changes.",
2553
- details: {}
2554
- };
2555
- default:
2556
- if (code >= 10001 && code <= 10004) {
2557
- const details = {};
2558
- if (rawBody["invalidTadigs"] !== void 0) {
2559
- details.invalidTadigs = rawBody["invalidTadigs"];
2560
- }
2561
- if (rawBody["existingLZ"] !== void 0) {
2562
- details.existingLZ = rawBody["existingLZ"];
2563
- }
2564
- return { message: rawMsg || "Location zone validation failed", details };
2565
- }
2566
- return { message: rawMsg || "Unknown error", details: {} };
2553
+ return { ok: true, body: parsed };
2554
+ }
2555
+ function periodicityValue(raw) {
2556
+ if (typeof raw === "number" && OCS_PERIODICITY_VALUES.includes(raw)) return raw;
2557
+ if (typeof raw === "string") {
2558
+ const key = raw.trim().toLowerCase();
2559
+ if (key === "daily" || key === "0") return OCS_PERIODICITY.DAILY;
2560
+ if (key === "weekly" || key === "1") return OCS_PERIODICITY.WEEKLY;
2561
+ if (key === "monthly" || key === "2") return OCS_PERIODICITY.MONTHLY;
2567
2562
  }
2563
+ return { error: "recurringPeriodicityType must be 0 (daily), 1 (weekly), or 2 (monthly)" };
2568
2564
  }
2569
- var BACKOFF_BASE_MS2 = 500;
2570
- var BACKOFF_MAX_MS = 4e3;
2571
- var BACKOFF_MAX_RETRIES = 3;
2572
- var TRAFFIC_CONTROL_BACKOFF_BASE_MS = 1e3;
2573
- var TRAFFIC_CONTROL_BACKOFF_MAX_MS = 16e3;
2574
- var TRAFFIC_CONTROL_MAX_RETRIES = 6;
2575
- var BACKOFF_JITTER = 0.25;
2576
- async function retryWithBackoff(fn) {
2577
- let lastError;
2578
- let maxRetries = BACKOFF_MAX_RETRIES;
2579
- let backoffBaseMs = BACKOFF_BASE_MS2;
2580
- let backoffMaxMs = BACKOFF_MAX_MS;
2581
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
2582
- if (attempt > 0) {
2583
- const base = Math.min(backoffBaseMs * Math.pow(2, attempt - 1), backoffMaxMs);
2584
- const jitter = base * BACKOFF_JITTER * (2 * Math.random() - 1);
2585
- const delay = Math.max(0, Math.round(base + jitter));
2586
- await new Promise((r) => setTimeout(r, delay));
2587
- }
2588
- try {
2589
- return await fn();
2590
- } catch (err) {
2591
- lastError = err;
2592
- if (err instanceof OcsApiError) {
2593
- if (err.code !== 100) throw err;
2594
- maxRetries = TRAFFIC_CONTROL_MAX_RETRIES;
2595
- backoffBaseMs = TRAFFIC_CONTROL_BACKOFF_BASE_MS;
2596
- backoffMaxMs = TRAFFIC_CONTROL_BACKOFF_MAX_MS;
2597
- }
2598
- if (err instanceof OcsContractError) {
2599
- throw err;
2600
- }
2601
- if (err instanceof OcsTimeoutError) {
2602
- throw err;
2603
- }
2604
- }
2565
+ function throttlingErrorActionValue(raw) {
2566
+ if (typeof raw === "number" && Number.isInteger(raw)) return raw;
2567
+ if (typeof raw === "string" && /^-?\d+$/.test(raw.trim())) return Number(raw.trim());
2568
+ if (typeof raw === "string" && raw.trim().toLowerCase() === "continue_unthrottled") {
2569
+ return {
2570
+ error: "throttlingErrorAction is an integer. continue_unthrottled is not a modifyPPT value"
2571
+ };
2572
+ }
2573
+ return { error: "throttlingErrorAction is an integer" };
2574
+ }
2575
+ function applyTemplateField(out, target, value) {
2576
+ if (target === "prepaidpackagetemplatename") {
2577
+ const name = templateName(value);
2578
+ if (typeof name !== "string") return name;
2579
+ out[target] = name;
2580
+ return void 0;
2605
2581
  }
2606
- throw lastError;
2582
+ if (target === "recurringPeriodicityType") {
2583
+ const period = periodicityValue(value);
2584
+ if (typeof period !== "number") return period;
2585
+ out[target] = period;
2586
+ return void 0;
2587
+ }
2588
+ if (target === "throttlingErrorAction") {
2589
+ const action = throttlingErrorActionValue(value);
2590
+ if (typeof action !== "number") return action;
2591
+ out[target] = action;
2592
+ return void 0;
2593
+ }
2594
+ out[target] = value;
2595
+ return void 0;
2607
2596
  }
2608
- function isAbortError(err) {
2609
- return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError" || /aborted|timed? ?out/i.test(err.message));
2597
+ function packageTemplateModifyParams(input, method) {
2598
+ const parsed = changesRecord(input);
2599
+ if (!parsed.ok) return { error: parsed.error };
2600
+ const body = parsed.body;
2601
+ if ("price" in body) return { error: `${method} has no price field` };
2602
+ if ("description" in body) return { error: `${method} has no description field` };
2603
+ const accepted = /* @__PURE__ */ new Set([
2604
+ ...PACKAGE_TEMPLATE_CREATE_FIELDS,
2605
+ ...PACKAGE_TEMPLATE_MODIFY_EXTRA[method]
2606
+ ]);
2607
+ const out = {};
2608
+ for (const [key, value] of Object.entries(body)) {
2609
+ if (PACKAGE_TEMPLATE_MODIFY_IDENTITY.has(key) || !accepted.has(key)) continue;
2610
+ const applied = applyTemplateField(out, key, value);
2611
+ if (applied) return applied;
2612
+ }
2613
+ for (const [key, value] of Object.entries(body)) {
2614
+ if (PACKAGE_TEMPLATE_MODIFY_IDENTITY.has(key)) continue;
2615
+ const target = PACKAGE_TEMPLATE_MODIFY_ALIASES[key];
2616
+ if (!target) {
2617
+ if (!accepted.has(key)) return { error: `${method} does not accept ${key}` };
2618
+ continue;
2619
+ }
2620
+ if (target in out) continue;
2621
+ const applied = applyTemplateField(out, target, value);
2622
+ if (applied) return applied;
2623
+ }
2624
+ if (Object.keys(out).length === 0) {
2625
+ return { error: "at least one template field required" };
2626
+ }
2627
+ return out;
2610
2628
  }
2611
- var OcsClient = class {
2612
- /**
2613
- * @param baseUrl OCS base URL without trailing slash (e.g. https://ocs.esimvault.cloud).
2614
- * @param _defaultToken Default API token; identifies the reseller for rate-floor keying.
2615
- * @param _priority Call priority: "interactive" (MCP tool calls, 80% capacity) or
2616
- * "batch" (cron/poll callers, 20% capacity). Default: "interactive".
2617
- * @param options Contract-enforcement opt-outs (see `OcsClientOptions`).
2618
- */
2619
- constructor(baseUrl, _defaultToken, _priority = "interactive", options = {}) {
2620
- this._defaultToken = _defaultToken;
2621
- this._priority = _priority;
2622
- let end = baseUrl.length;
2623
- while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
2624
- this.baseUrl = baseUrl.slice(0, end);
2625
- this._strictMethods = options.strictMethods ?? false;
2626
- this._validateResponses = options.validateResponses ?? true;
2627
- this._strictValidation = options.strictValidation ?? false;
2629
+ function packageTemplateModifyFromToolJson(changes, method) {
2630
+ let parsed;
2631
+ try {
2632
+ parsed = JSON.parse(changes);
2633
+ } catch {
2634
+ return { error: "changes must be a JSON object" };
2628
2635
  }
2629
- _defaultToken;
2630
- _priority;
2631
- baseUrl;
2632
- _strictMethods;
2633
- _validateResponses;
2634
- _strictValidation;
2635
- // Per-instance (NOT static): a static set would dedup across requests in a
2636
- // reused Worker isolate and silently swallow recurring contract-drift warnings.
2637
- _warnedMethods = /* @__PURE__ */ new Set();
2638
- _warnedValidation = /* @__PURE__ */ new Set();
2639
- async call(method, params = {}, token) {
2640
- if (!OCS_V1_METHODS.has(method)) {
2641
- if (this._strictMethods) throw new OcsUnknownMethodError(method);
2642
- if (!this._warnedMethods.has(method)) {
2643
- this._warnedMethods.add(method);
2644
- console.warn(
2645
- `[ocs-client] method "${method}" is not in the verified OCS v1 set \u2014 it may not exist on this OCS instance (see OCS_V1_METHODS).`
2646
- );
2647
- }
2648
- }
2649
- const tok = token ?? this._defaultToken;
2650
- if (!tok) {
2651
- throw new OcsApiError(-1, "No OCS API token provided", method);
2636
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2637
+ return { error: "changes must be a JSON object" };
2638
+ }
2639
+ let body = parsed;
2640
+ if (method === "modifyPPTCore") {
2641
+ try {
2642
+ body = normalizePackageTemplateChanges(body);
2643
+ } catch (err) {
2644
+ const message = err instanceof Error ? err.message : "changes could not be normalized";
2645
+ return { error: message };
2652
2646
  }
2653
- const url = `${this.baseUrl}/v1?token=${tok}`;
2654
- assertBareIntParam(method, params);
2655
- const wireParams = applyParamRenames(method, params);
2656
- const body = JSON.stringify({ [method]: wireParams });
2657
- return retryWithBackoff(async () => {
2658
- await acquireToken(tok);
2659
- await acquireEndpointSlot(tok, method, this._priority);
2660
- return runWithBudget(method, async (signal, attemptMs) => {
2661
- let res;
2662
- try {
2663
- res = await fetch(url, {
2664
- method: "POST",
2665
- headers: { "Content-Type": "application/json" },
2666
- body,
2667
- signal
2668
- });
2669
- } catch (err) {
2670
- if (isAbortError(err)) {
2671
- throw new OcsTimeoutError(method, attemptMs);
2672
- }
2673
- throw err;
2674
- }
2675
- if (res.status >= 500) {
2676
- throw new Error(`HTTP ${res.status} ${res.statusText}`);
2677
- }
2678
- if (!res.ok) {
2679
- throw new OcsApiError(
2680
- res.status,
2681
- `HTTP ${res.status} ${res.statusText}`,
2682
- method
2683
- );
2684
- }
2685
- let json;
2686
- try {
2687
- json = await res.json();
2688
- } catch (err) {
2689
- if (isAbortError(err)) {
2690
- throw new OcsTimeoutError(method, attemptMs);
2691
- }
2692
- throw err;
2693
- }
2694
- if (json.status?.code !== 0) {
2695
- const ocsCode = json.status?.code ?? -1;
2696
- const rawMsg = json.status?.msg ?? "Unknown error";
2697
- const rawBody = json;
2698
- const { message, details } = ocsErrorMessage(ocsCode, rawMsg, rawBody);
2699
- throw new OcsApiError(ocsCode, message, method, details);
2700
- }
2701
- let payload;
2702
- if (method === "getCustomerTariff" && json["listTariffRule"] !== void 0) {
2703
- payload = json["listTariffRule"];
2704
- } else if (method === "getSubscriberLocationByCellId") {
2705
- const byMethod = json[method];
2706
- if (byMethod !== void 0) {
2707
- payload = byMethod;
2708
- } else if (json["subscriberLocation"] !== void 0) {
2709
- payload = json["subscriberLocation"];
2710
- } else {
2711
- payload = json[method] ?? json;
2712
- }
2713
- } else if (json[method] !== void 0) {
2714
- payload = json[method];
2715
- } else if (Object.keys(json).some((k) => k !== "status")) {
2716
- payload = json;
2717
- } else {
2718
- payload = null;
2719
- }
2720
- if (this._validateResponses) {
2721
- const schema = RESPONSE_SCHEMAS[method];
2722
- if (schema) {
2723
- const parsed = schema.safeParse(payload);
2724
- if (!parsed.success) {
2725
- const issues = parsed.error.issues.map((i) => ({
2726
- path: i.path.join(".") || "<root>",
2727
- message: i.message
2728
- }));
2729
- if (this._strictValidation) {
2730
- throw new OcsContractError(method, issues, payload);
2731
- }
2732
- if (!this._warnedValidation.has(method)) {
2733
- this._warnedValidation.add(method);
2734
- console.warn(`[ocs-client] ${new OcsContractError(method, issues, payload).message}`);
2735
- }
2736
- }
2737
- }
2738
- }
2739
- return payload;
2740
- });
2741
- });
2742
2647
  }
2648
+ return packageTemplateModifyParams(body, method);
2649
+ }
2650
+
2651
+ // ../../packages/ocs-client/dist/chunk-SFPWPYGO.js
2652
+ import { z } from "zod";
2653
+ var OCS_V1_METHODS = /* @__PURE__ */ new Set([
2654
+ "getResellerInfo",
2655
+ "listResellerAccount",
2656
+ "listPrepaidPackageTemplate",
2657
+ "listSubscriberPrepaidPackages",
2658
+ "listSubscriber",
2659
+ "getSingleSubscriber",
2660
+ "getSimProviderStatus",
2661
+ "getSubscriberLocation",
2662
+ "getSubscriberLocationByCellId",
2663
+ "esimStatusPerAccount",
2664
+ "subscriberUsageOverPeriod",
2665
+ "affectRecurringPackageToSubscriber",
2666
+ "deleteSubscriberPackage",
2667
+ "modifyAccountBalance",
2668
+ "modifySubscriberBalance",
2669
+ "modifySubscriberContactInfo",
2670
+ "modifySubscriberMobilePlan",
2671
+ "modifySubscriberSteeringList",
2672
+ "modifySubscriberVoipPlan",
2673
+ "modifySubscriberPrepaidPackageActivePeriod",
2674
+ "modifySubscriberPrepaidPackageLimits",
2675
+ "modifySubscriberPrepaidPackageStatus",
2676
+ "modifyPPTCore",
2677
+ "modifyPPTRecurring",
2678
+ "createPrepaidPackageTemplate",
2679
+ "createLocationZone",
2680
+ "changeNetworkProfileOfLocationZone",
2681
+ "hlrGetBitrate",
2682
+ "hlrSetBitrate",
2683
+ "listSponsor",
2684
+ "listDestinationListPrefix",
2685
+ "listDetailedLocationZone",
2686
+ "listLocationZoneElement",
2687
+ "listSubscriberVoipTariff",
2688
+ "listVoipTariffRule",
2689
+ "affectSubscriberFakePhoneNumber",
2690
+ "affectSubscriberRealPhoneNumber",
2691
+ "getCustomerTariff",
2692
+ "resetSubsGzCounter",
2693
+ "sendMtSms"
2694
+ ]);
2695
+ var OcsResponseEnvelope = z.object({
2696
+ status: z.object({
2697
+ code: z.number(),
2698
+ // Required to match the `OcsStatus` interface in client.ts ({ code, msg }).
2699
+ msg: z.string()
2700
+ }).passthrough()
2701
+ }).passthrough();
2702
+ var GetResellerInfoResponse = z.object({
2703
+ id: z.number(),
2704
+ name: z.string(),
2705
+ parentId: z.number(),
2706
+ parentName: z.string(),
2707
+ // Production eSIMVault returns decimal money as a JSON string ("495.52").
2708
+ // Staging fixtures used a number, so accept both exact provider variants.
2709
+ balance: z.union([z.number(), z.string().regex(/^-?\d+(?:\.\d+)?$/)]),
2710
+ cdrFolder: z.string().optional(),
2711
+ trafficInfo: z.object({}).passthrough().optional(),
2712
+ chargingInfo: z.object({}).passthrough().optional(),
2713
+ contactInfo: z.object({}).passthrough().optional()
2714
+ }).passthrough();
2715
+ var ListResellerAccountResponse = z.object({
2716
+ reseller: z.array(
2717
+ z.object({
2718
+ id: z.number(),
2719
+ name: z.string(),
2720
+ account: z.array(z.object({}).passthrough())
2721
+ }).passthrough()
2722
+ )
2723
+ }).passthrough();
2724
+ var ListPrepaidPackageTemplateResponse = z.object({
2725
+ template: z.array(
2726
+ z.object({
2727
+ prepaidpackagetemplateid: z.number(),
2728
+ prepaidpackagetemplatename: z.string(),
2729
+ resellerid: z.number(),
2730
+ locationzoneid: z.number()
2731
+ }).passthrough()
2732
+ )
2733
+ }).passthrough();
2734
+ var SubscriberStatusName = z.string().regex(/^[A-Za-z]/, "subscriber status must be a non-empty alpha-leading name");
2735
+ var GetSingleSubscriberResponse = z.object({
2736
+ status: z.array(
2737
+ z.object({
2738
+ status: SubscriberStatusName
2739
+ }).passthrough()
2740
+ ).min(1, "getSingleSubscriber.status must be a non-empty array")
2741
+ }).passthrough();
2742
+ var RESPONSE_SCHEMAS = {
2743
+ getResellerInfo: GetResellerInfoResponse,
2744
+ listResellerAccount: ListResellerAccountResponse,
2745
+ listPrepaidPackageTemplate: ListPrepaidPackageTemplateResponse,
2746
+ getSingleSubscriber: GetSingleSubscriberResponse
2743
2747
  };
2748
+ var IccidParam = z.object({ iccid: z.string().min(1) });
2749
+ var AccountIdParam = z.object({ accountId: z.coerce.number().int().positive() });
2750
+ var PackageIdParam = z.object({ packageId: z.coerce.number().int().positive() });
2751
+ var TemplateIdParam = z.object({ templateId: z.coerce.number().int().positive() });
2752
+ var ResellerIdParam = z.object({ resellerId: z.coerce.number().int().positive() });
2753
+ var DateRangeBody = z.object({
2754
+ start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD"),
2755
+ end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD")
2756
+ });
2757
+ var ErrorResponse = z.object({
2758
+ error: z.string(),
2759
+ code: z.number().optional(),
2760
+ retryAfter: z.number().optional()
2761
+ });
2762
+ var OkResponse = z.object({
2763
+ ok: z.boolean(),
2764
+ data: z.unknown().optional()
2765
+ });
2766
+ var ModifySubscriberStatusBody = z.object({
2767
+ newStatus: z.enum(["active", "suspended", "deactivated"])
2768
+ });
2769
+ var ModifySubscriberBalanceBody = z.object({
2770
+ amount: z.number().optional(),
2771
+ setBalance: z.number().optional()
2772
+ }).refine((v) => v.amount !== void 0 || v.setBalance !== void 0, {
2773
+ message: "Provide either amount (delta) or setBalance (absolute)"
2774
+ });
2775
+ var ModifySubscriberContactInfoBody = z.object({
2776
+ name: z.string().optional(),
2777
+ mail: z.string().email().optional(),
2778
+ phone: z.string().optional()
2779
+ });
2780
+ var SetTrafficRestrictionsBody = z.object({
2781
+ blockData: z.boolean().optional(),
2782
+ blockVoice: z.boolean().optional(),
2783
+ blockSms: z.boolean().optional(),
2784
+ blockRoaming: z.boolean().optional()
2785
+ });
2786
+ var ModifySteeringListBody = z.object({
2787
+ steeringListId: z.number().int().positive()
2788
+ });
2789
+ var HlrSetBitrateBody = z.object({
2790
+ limit: z.enum(OCS_HLR_BITRATE_LIMITS)
2791
+ });
2792
+ var AssignPackageBody = z.object({
2793
+ packageTemplateId: z.number().int().positive(),
2794
+ startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
2795
+ });
2796
+ var AssignRecurringPackageBody = z.object({
2797
+ packageTemplateId: z.number().int().positive()
2798
+ });
2799
+ var ModifyPackageLimitsBody = z.object({
2800
+ dataLimitMb: z.number().positive().optional(),
2801
+ voiceLimitMin: z.number().nonnegative().optional(),
2802
+ smsLimit: z.number().nonnegative().optional()
2803
+ });
2804
+ var ModifyPackageExpiryBody = z.object({
2805
+ expiryDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
2806
+ });
2807
+ var ModifyPackageStatusBody = z.object({
2808
+ status: z.enum(["active", "paused"])
2809
+ });
2810
+ var StopResumeRecurringBody = z.object({
2811
+ action: z.enum(["stop", "resume"])
2812
+ });
2813
+ var CreatePackageTemplateBody = z.object({
2814
+ name: z.string().min(1),
2815
+ dataLimitMb: z.number().positive(),
2816
+ validityDays: z.number().int().positive(),
2817
+ price: z.number().nonnegative().optional(),
2818
+ recurring: z.boolean().optional()
2819
+ });
2820
+ var ModifyTemplateCoreBody = z.object({
2821
+ name: z.string().optional(),
2822
+ dataLimitMb: z.number().positive().optional(),
2823
+ validityDays: z.number().int().positive().optional()
2824
+ });
2825
+ var ModifyTemplateRecurringBody = z.object({
2826
+ billingCycleDays: z.number().int().positive().optional(),
2827
+ autoRenew: z.boolean().optional()
2828
+ });
2829
+ var ModifyTemplateThrottlingBody = z.object({
2830
+ throttleSpeedKbps: z.number().int().nonnegative()
2831
+ });
2832
+ var ModifyAccountBalanceBody = z.object({
2833
+ amount: z.number(),
2834
+ // adapt and add are a signed delta. set replaces the balance. subtract
2835
+ // negates a positive amount. The API drops mode before the OCS call.
2836
+ mode: z.enum(["adapt", "set", "add", "subtract"]).default("adapt")
2837
+ });
2838
+ var MoveSubscribersBody = z.object({
2839
+ fromAccountId: z.number().int().positive(),
2840
+ iccidStart: z.string().min(1),
2841
+ iccidEnd: z.string().min(1)
2842
+ });
2843
+ var CreateLocationZoneBody = z.object({
2844
+ name: z.string().min(1),
2845
+ countries: z.array(z.string().length(2)).min(1)
2846
+ });
2847
+ var SendSmsBody = z.object({
2848
+ text: z.string().min(1).max(160),
2849
+ senderId: z.string().optional()
2850
+ });
2851
+ var ChangeSimStatusBody = z.object({
2852
+ newStatus: z.enum(["Enable", "Disable"])
2853
+ });
2854
+ var DiagnoseSubscriberBody = z.object({
2855
+ iccid: z.string().min(1)
2856
+ });
2857
+ var FleetHealthQuery = z.object({
2858
+ accountId: z.coerce.number().int().positive().optional()
2859
+ });
2860
+ var ChurnRiskQuery = z.object({
2861
+ daysSinceUsage: z.coerce.number().int().positive().default(14),
2862
+ limit: z.coerce.number().int().positive().max(500).default(50)
2863
+ });
2864
+ var DetectUsageAnomaliesBody = z.object({
2865
+ start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
2866
+ end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
2867
+ thresholdMultiplier: z.number().positive().default(2.5)
2868
+ });
2869
+ var OptimizePackageBody = z.object({
2870
+ iccid: z.string().min(1)
2871
+ });
2872
+ var AuditNetworkCoverageBody = z.object({
2873
+ locationZoneId: z.number().int().positive().optional()
2874
+ });
2875
+ var MarketingIntelligenceQuery = z.object({
2876
+ segment: z.enum(["low_usage", "high_usage", "expiring_soon", "churned"]).optional()
2877
+ });
2878
+ var HighCostSubscribersQuery = z.object({
2879
+ limit: z.coerce.number().int().positive().max(200).default(20),
2880
+ start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
2881
+ end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
2882
+ });
2883
+ var WebhookEventBody = z.record(z.string(), z.unknown());
2884
+ var HealthResponse = z.object({
2885
+ ok: z.literal(true),
2886
+ version: z.string(),
2887
+ build_sha: z.string()
2888
+ });
2744
2889
 
2745
- // ../../packages/ocs-client/dist/chunk-AL4HRKV6.js
2746
- var OCS_MAX_USAGE_WINDOW_DAYS = 7;
2747
- function clampUsagePeriod(start, end, maxDays = OCS_MAX_USAGE_WINDOW_DAYS) {
2748
- const endMs = Date.parse(`${end}T00:00:00Z`);
2749
- const startMs = Date.parse(`${start}T00:00:00Z`);
2750
- if (Number.isNaN(endMs) || Number.isNaN(startMs) || startMs > endMs) {
2751
- return { start, end };
2752
- }
2753
- const minStartMs = endMs - (maxDays - 1) * 864e5;
2754
- if (startMs < minStartMs) {
2755
- return { start: new Date(minStartMs).toISOString().slice(0, 10), end };
2890
+ // ../../packages/ocs-client/dist/chunk-CGMK4FTD.js
2891
+ var DEFAULT_OCS_BUDGET = {
2892
+ attemptMs: 3e4,
2893
+ maxAttempts: 3,
2894
+ totalMs: 75e3
2895
+ };
2896
+ var OCS_METHOD_BUDGETS = {
2897
+ // 3.4 MB, 88-128 s measured. 180 s is 1.4x the slowest observation. One
2898
+ // attempt: payload size does not vary, so a retry only doubles the wait.
2899
+ getCustomerTariff: { attemptMs: 18e4, maxAttempts: 1, totalMs: 18e4 }
2900
+ };
2901
+ var MIN_ATTEMPT_BUDGET_MS = 1e3;
2902
+ var BACKOFF_BASE_MS = 500;
2903
+ var OcsTimeoutError = class extends Error {
2904
+ constructor(method, timeoutMs, budgetMs = timeoutMs, attempts = 1) {
2905
+ super(
2906
+ `[${method}] OCS request timed out after ${timeoutMs}ms` + (attempts > 1 ? ` on each of ${attempts} attempts` : "") + (budgetMs > timeoutMs ? ` (budget ${budgetMs}ms)` : "") + `. The upstream did not deliver a complete response in time \u2014 usually an oversized payload or a wedged upstream. Narrow the request, or raise OCS_TIMEOUT_MS / OCS_TOTAL_TIMEOUT_MS if this method is legitimately slower here.`
2907
+ );
2908
+ this.method = method;
2909
+ this.timeoutMs = timeoutMs;
2910
+ this.budgetMs = budgetMs;
2911
+ this.attempts = attempts;
2912
+ this.name = "OcsTimeoutError";
2756
2913
  }
2757
- return { start, end };
2758
- }
2759
- function lastNDaysPeriod(days = OCS_MAX_USAGE_WINDOW_DAYS, now = /* @__PURE__ */ new Date()) {
2760
- const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
2761
- const start = new Date(end);
2762
- start.setUTCDate(end.getUTCDate() - (days - 1));
2763
- return {
2764
- start: start.toISOString().slice(0, 10),
2765
- end: end.toISOString().slice(0, 10)
2766
- };
2914
+ method;
2915
+ timeoutMs;
2916
+ budgetMs;
2917
+ attempts;
2918
+ };
2919
+ function envInt(name) {
2920
+ const proc = globalThis["process"];
2921
+ const parsed = Number(proc?.env?.[name]);
2922
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
2767
2923
  }
2768
- function locationParams(iccid) {
2769
- return { iccid };
2924
+ function ocsBudgetFor(method) {
2925
+ const base = OCS_METHOD_BUDGETS[method] ?? DEFAULT_OCS_BUDGET;
2926
+ const attemptMs = envInt("OCS_TIMEOUT_MS") ?? base.attemptMs;
2927
+ const totalMs = Math.max(envInt("OCS_TOTAL_TIMEOUT_MS") ?? base.totalMs, attemptMs);
2928
+ return { attemptMs, maxAttempts: base.maxAttempts, totalMs };
2770
2929
  }
2771
- function subscriberIdParams(iccid) {
2772
- return { iccid };
2930
+ function sleep2(ms, signal) {
2931
+ return new Promise((resolve) => {
2932
+ const timer = setTimeout(finish, ms);
2933
+ function finish() {
2934
+ clearTimeout(timer);
2935
+ signal.removeEventListener("abort", finish);
2936
+ resolve();
2937
+ }
2938
+ signal.addEventListener("abort", finish, { once: true });
2939
+ });
2773
2940
  }
2774
- function recurringPackageParams(iccid, packageTemplateId, options = {}) {
2775
- if (options.activationAtFirstUse !== void 0 && options.startTimeUTC !== void 0) {
2776
- throw new Error("activationAtFirstUse cannot be combined with startTimeUTC");
2941
+ async function withDeadline(work, deadline, ms) {
2942
+ let timer;
2943
+ try {
2944
+ return await Promise.race([
2945
+ work,
2946
+ new Promise((_resolve, reject) => {
2947
+ timer = setTimeout(() => reject(deadline), ms);
2948
+ })
2949
+ ]);
2950
+ } finally {
2951
+ if (timer !== void 0) clearTimeout(timer);
2777
2952
  }
2778
- return {
2779
- subscriber: { iccid },
2780
- packageTemplateId,
2781
- ...options.startTimeUTC !== void 0 ? { startTimeUTC: options.startTimeUTC } : { activationAtFirstUse: options.activationAtFirstUse ?? true }
2782
- };
2783
2953
  }
2784
- function networkEventsOverPeriodParams(iccid, start, end) {
2785
- return usageOverPeriodParams(iccid, start, end);
2954
+ async function runWithBudget(method, attempt, budget = ocsBudgetFor(method)) {
2955
+ const deadlineAt = Date.now() + budget.totalMs;
2956
+ const budgetController = new AbortController();
2957
+ const budgetTimer = setTimeout(() => budgetController.abort(), budget.totalMs);
2958
+ let attempts = 0;
2959
+ let lastTimeout;
2960
+ try {
2961
+ for (let i = 0; i < budget.maxAttempts; i++) {
2962
+ const remaining = deadlineAt - Date.now();
2963
+ if (remaining < MIN_ATTEMPT_BUDGET_MS) break;
2964
+ const attemptMs = Math.min(budget.attemptMs, remaining);
2965
+ attempts++;
2966
+ const attemptController = new AbortController();
2967
+ const onBudgetAbort = () => attemptController.abort();
2968
+ budgetController.signal.addEventListener("abort", onBudgetAbort, { once: true });
2969
+ const attemptTimer = setTimeout(() => attemptController.abort(), attemptMs);
2970
+ const breach = new OcsTimeoutError(method, attemptMs, budget.totalMs, attempts);
2971
+ try {
2972
+ return await withDeadline(
2973
+ attempt(attemptController.signal, attemptMs),
2974
+ breach,
2975
+ attemptMs
2976
+ );
2977
+ } catch (err) {
2978
+ if (!(err instanceof OcsTimeoutError)) throw err;
2979
+ lastTimeout = new OcsTimeoutError(method, attemptMs, budget.totalMs, attempts);
2980
+ } finally {
2981
+ clearTimeout(attemptTimer);
2982
+ budgetController.signal.removeEventListener("abort", onBudgetAbort);
2983
+ attemptController.abort();
2984
+ }
2985
+ if (i === budget.maxAttempts - 1) break;
2986
+ const backoff = Math.random() * BACKOFF_BASE_MS * 2 ** i;
2987
+ if (Date.now() + backoff + MIN_ATTEMPT_BUDGET_MS > deadlineAt) break;
2988
+ await sleep2(backoff, budgetController.signal);
2989
+ }
2990
+ } finally {
2991
+ clearTimeout(budgetTimer);
2992
+ }
2993
+ throw lastTimeout ?? new OcsTimeoutError(method, budget.attemptMs, budget.totalMs, Math.max(attempts, 1));
2786
2994
  }
2787
- function usageOverPeriodParams(iccid, start, end) {
2788
- const period = clampUsagePeriod(start, end);
2789
- return { subscriber: { iccid }, period };
2995
+
2996
+ // ../../packages/ocs-client/dist/chunk-WYFBYNKX.js
2997
+ var RATE_LIMIT = 10;
2998
+ var WINDOW_MS = 1e3;
2999
+ var windows = /* @__PURE__ */ new Map();
3000
+ function getWindowState(key) {
3001
+ let s = windows.get(key);
3002
+ if (!s) {
3003
+ s = { log: [], gate: Promise.resolve() };
3004
+ windows.set(key, s);
3005
+ }
3006
+ return s;
2790
3007
  }
2791
- function imsiFromSubscriberRecord(record) {
2792
- if (!record || typeof record !== "object") return void 0;
2793
- const rec = record;
2794
- const inner = rec.getSingleSubscriber && typeof rec.getSingleSubscriber === "object" ? rec.getSingleSubscriber : rec;
2795
- const direct = inner.imsi;
2796
- if (typeof direct === "string" && direct.length > 0) return direct;
2797
- for (const key of ["imsiList", "multiImsi"]) {
2798
- const list = inner[key];
2799
- if (!Array.isArray(list)) continue;
2800
- for (const entry of list) {
2801
- if (!entry || typeof entry !== "object") continue;
2802
- const value = entry.imsi;
2803
- if (typeof value === "string" && value.length > 0) return value;
2804
- }
3008
+ async function acquireToken(resellerId) {
3009
+ const proc = globalThis["process"];
3010
+ if (proc?.env?.["RATE_FLOOR_DISABLED"] === "true" || globalThis["RATE_FLOOR_DISABLED"] === "true") {
3011
+ return;
2805
3012
  }
2806
- return void 0;
3013
+ const state = getWindowState(resellerId);
3014
+ const ticket = state.gate.then(async () => {
3015
+ while (true) {
3016
+ const now = Date.now();
3017
+ if (state.log.length < RATE_LIMIT) {
3018
+ state.log.push(now);
3019
+ return;
3020
+ }
3021
+ const oldest = state.log[0];
3022
+ const age = now - oldest;
3023
+ if (age >= WINDOW_MS) {
3024
+ state.log.shift();
3025
+ state.log.push(now);
3026
+ return;
3027
+ }
3028
+ const waitMs = WINDOW_MS - age + 1;
3029
+ await new Promise((r) => setTimeout(r, waitMs));
3030
+ }
3031
+ });
3032
+ state.gate = ticket;
3033
+ return ticket;
2807
3034
  }
2808
- var OCS_PREPAID_PACKAGE_LIMIT_FIELDS = [
2809
- "dataByte",
2810
- "mocSecond",
2811
- "mtcSecond",
2812
- "moSms",
2813
- "mtSms"
2814
- ];
2815
- var LIMIT_ALIASES = {
2816
- dataByte: "dataByte",
2817
- dataBytes: "dataByte",
2818
- dataLimit: "dataByte",
2819
- databyte: "dataByte",
2820
- mocSecond: "mocSecond",
2821
- mocSeconds: "mocSecond",
2822
- voiceOutLimit: "mocSecond",
2823
- mtcSecond: "mtcSecond",
2824
- mtcSeconds: "mtcSecond",
2825
- voiceInLimit: "mtcSecond",
2826
- moSms: "moSms",
2827
- moSmsNumber: "moSms",
2828
- smsOutLimit: "moSms",
2829
- mtSms: "mtSms",
2830
- mtSmsNumber: "mtSms",
2831
- smsInLimit: "mtSms"
3035
+ var ENDPOINT_LIMITS_PER_MIN = {
3036
+ // Global sentinel (keyed as "__global__")
3037
+ __global__: 600,
3038
+ // 30/min — tightest endpoint
3039
+ subscriberNetworkEventsOverPeriod: 30,
3040
+ // 100/min
3041
+ subscriberUsageOverPeriod: 100,
3042
+ // 150/min
3043
+ listLocationZoneElement: 150,
3044
+ sendMtSms: 150,
3045
+ // 300/min (explicit; also the fallback default)
3046
+ modifySubscriberMobilePlan: 300,
3047
+ affectRecurringPackageToSubscriber: 300,
3048
+ affectSubscriberFakePhoneNumber: 300,
3049
+ affectSubscriberRealPhoneNumber: 300,
3050
+ esimStatusPerAccount: 300,
3051
+ getSubscriberActivePeriod: 300,
3052
+ hlrGetBitrate: 300,
3053
+ hlrSetBitrate: 300,
3054
+ listSubscriber: 300,
3055
+ listSubscriberPrepaidPackages: 300,
3056
+ moveSubscriberRangeToAccount: 300,
3057
+ pushSteeringToSubs: 300,
3058
+ setSubscriberTrafficRestrictions: 300,
3059
+ getSingleSubscriber: 300,
3060
+ getSubscriberLocationByCellId: 300
2832
3061
  };
2833
- function prepaidPackageLimits(limits) {
2834
- const out = {};
2835
- const unknown = [];
2836
- for (const [key, value] of Object.entries(limits)) {
2837
- const target = LIMIT_ALIASES[key];
2838
- if (!target) {
2839
- unknown.push(key);
2840
- continue;
2841
- }
2842
- out[target] = value;
2843
- }
2844
- if (unknown.length > 0) {
2845
- throw new Error(
2846
- `Unknown package limit field(s): ${unknown.join(", ")}. OCS accepts only ${OCS_PREPAID_PACKAGE_LIMIT_FIELDS.join(", ")} inside newLimits (aliases: ${Object.keys(LIMIT_ALIASES).join(", ")}).`
2847
- );
3062
+ var DEFAULT_LIMIT_PER_MIN = 300;
3063
+ var WINDOW_MS2 = 6e4;
3064
+ var METRIC_EMIT_MIN_INTERVAL_MS = 1e4;
3065
+ var BATCH_FRACTION = 0.2;
3066
+ function resellerKeyHash(resellerKey) {
3067
+ if (!resellerKey) return "00000000";
3068
+ let h = 5381;
3069
+ for (let i = 0; i < resellerKey.length; i++) {
3070
+ h = ((h << 5) + h ^ resellerKey.charCodeAt(i)) >>> 0;
2848
3071
  }
2849
- if (Object.keys(out).length === 0) {
2850
- throw new Error(
2851
- `No package limits to change. Provide at least one of ${OCS_PREPAID_PACKAGE_LIMIT_FIELDS.join(", ")}.`
3072
+ return h.toString(16).padStart(8, "0");
3073
+ }
3074
+ function emitMetric(endpoint, resellerKey, callsInWindow, limitPerMin) {
3075
+ const pct2 = Math.round(callsInWindow / limitPerMin * 100);
3076
+ const reseller_hash = resellerKeyHash(resellerKey);
3077
+ console.error(
3078
+ JSON.stringify({
3079
+ metric: "carrier_ocs_calls_per_min",
3080
+ endpoint,
3081
+ reseller_hash,
3082
+ calls_in_window: callsInWindow,
3083
+ limit_per_min: limitPerMin,
3084
+ utilisation_pct: pct2,
3085
+ alert: pct2 >= 80,
3086
+ ts: Date.now()
3087
+ })
3088
+ );
3089
+ if (pct2 >= 80) {
3090
+ console.warn(
3091
+ `[rate-governor] ALERT: ${endpoint} at ${pct2}% capacity (${callsInWindow}/${limitPerMin} per min) reseller=${reseller_hash}`
2852
3092
  );
2853
3093
  }
2854
- return out;
2855
3094
  }
2856
- function ocsLocalDateTime(value, bareDate = "end") {
2857
- const trimmed = value.trim();
2858
- if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
2859
- return bareDate === "start" ? `${trimmed}T00:00:00` : `${trimmed}T23:59:59`;
2860
- }
2861
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(trimmed)) return `${trimmed}:00`;
2862
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(trimmed)) {
2863
- return trimmed.replace(/(?:Z|[+-]\d{2}:?\d{2})$/, "").slice(0, 19);
3095
+ var _endpointWindows = /* @__PURE__ */ new Map();
3096
+ function _getEndpointState(key) {
3097
+ let s = _endpointWindows.get(key);
3098
+ if (!s) {
3099
+ s = {
3100
+ log: [],
3101
+ batchLog: [],
3102
+ gate: Promise.resolve(),
3103
+ lastMetricEmitAt: 0,
3104
+ metricBelowHighUtil: true
3105
+ };
3106
+ _endpointWindows.set(key, s);
2864
3107
  }
2865
- throw new Error(
2866
- `"${value}" is not a date OCS can read. Use YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss \u2014 this field is a java.time.LocalDateTime and a zone offset is rejected.`
2867
- );
3108
+ return s;
2868
3109
  }
2869
- function activePeriodFromPackages(listResponse, packageId) {
2870
- const root = listResponse ?? {};
2871
- const payload = root.listSubscriberPrepaidPackages ?? root.data ?? root;
2872
- const packages = Array.isArray(payload?.packages) ? payload.packages : [];
2873
- for (const entry of packages) {
2874
- if (!entry || typeof entry !== "object") continue;
2875
- const rec = entry;
2876
- const id = rec.subscriberprepaidpackageid ?? rec.subscriberPrepaidPackageId;
2877
- if (Number(id) !== packageId) continue;
2878
- const activation = rec.tsactivationutc ?? rec.tsActivationUtc;
2879
- const expiration = rec.tsexpirationutc ?? rec.tsExpirationUtc;
2880
- const window = {};
2881
- if (typeof activation === "string" && activation) window.activation = activation;
2882
- if (typeof expiration === "string" && expiration) window.expiration = expiration;
2883
- return window;
2884
- }
2885
- return void 0;
3110
+ function _prune(log, windowStart) {
3111
+ let i = 0;
3112
+ while (i < log.length && log[i] <= windowStart) i++;
3113
+ return i > 0 ? log.slice(i) : log;
2886
3114
  }
2887
- var OCS_ACTIVE_PERIOD_DEFAULT_COMMENT = "Active period changed via Carrier";
2888
- function packageActivePeriodParams(packageId, change) {
2889
- if (change.startDate === void 0 && change.endDate === void 0) {
2890
- throw new Error("Provide startDate and/or endDate to change a package's active period.");
2891
- }
2892
- const activation = change.startDate !== void 0 ? ocsLocalDateTime(change.startDate, "start") : change.current?.activation;
2893
- const expiration = change.endDate !== void 0 ? ocsLocalDateTime(change.endDate, "end") : change.current?.expiration;
2894
- for (const [field, value, omitted] of [
2895
- ["newActivationDateUtc", activation, "startDate"],
2896
- ["newExpirationDateUtc", expiration, "endDate"]
2897
- ]) {
2898
- if (value !== void 0) continue;
2899
- throw new Error(
2900
- `OCS requires both bounds on an active-period change and rejects the whole request with "Missing '${field}'" when one is absent. Supply ${omitted}, or read the package's current window first (activePeriodFromPackages) and pass it as \`current\`.`
2901
- );
3115
+ async function acquireEndpointSlot(resellerId, endpoint, priority = "interactive") {
3116
+ const proc = globalThis["process"];
3117
+ if (proc?.env?.["RATE_FLOOR_DISABLED"] === "true" || globalThis["RATE_FLOOR_DISABLED"] === "true") {
3118
+ return;
2902
3119
  }
2903
- return {
2904
- packageId,
2905
- // Already LocalDateTime-shaped when it came from `current`; normalising
2906
- // again is a no-op and keeps a hand-passed value honest.
2907
- newActivationDateUtc: ocsLocalDateTime(activation, "start"),
2908
- newExpirationDateUtc: ocsLocalDateTime(expiration, "end"),
2909
- comment: change.comment?.trim() || OCS_ACTIVE_PERIOD_DEFAULT_COMMENT
2910
- };
3120
+ await _acquireOneSlot(resellerId, "__global__", priority);
3121
+ await _acquireOneSlot(resellerId, endpoint, priority);
2911
3122
  }
2912
- function recurringIdForPackage(listResponse, packageId) {
2913
- const root = listResponse ?? {};
2914
- const payload = root.listSubscriberPrepaidPackages ?? root.data ?? root;
2915
- const packages = Array.isArray(payload?.packages) ? payload.packages : [];
2916
- for (const entry of packages) {
2917
- if (!entry || typeof entry !== "object") continue;
2918
- const rec = entry;
2919
- const id = rec.subscriberprepaidpackageid ?? rec.subscriberPrepaidPackageId;
2920
- if (Number(id) !== packageId) continue;
2921
- const recurring = rec.recurringPackage ?? rec.recurringId;
2922
- const value = Number(recurring);
2923
- return Number.isFinite(value) && value > 0 ? value : void 0;
2924
- }
2925
- return void 0;
3123
+ async function _acquireOneSlot(resellerId, endpoint, priority) {
3124
+ const bucketKey = `${resellerId}:${endpoint}`;
3125
+ const limitPerMin = ENDPOINT_LIMITS_PER_MIN[endpoint] ?? DEFAULT_LIMIT_PER_MIN;
3126
+ const batchCap = Math.floor(limitPerMin * BATCH_FRACTION);
3127
+ const state = _getEndpointState(bucketKey);
3128
+ const ticket = state.gate.then(async () => {
3129
+ while (true) {
3130
+ const now = Date.now();
3131
+ const windowStart = now - WINDOW_MS2;
3132
+ state.log = _prune(state.log, windowStart);
3133
+ state.batchLog = _prune(state.batchLog, windowStart);
3134
+ const totalInWindow = state.log.length;
3135
+ const batchInWindow = state.batchLog.length;
3136
+ if (totalInWindow > 0 && endpoint !== "__global__") {
3137
+ const utilPct = Math.round(totalInWindow / limitPerMin * 100);
3138
+ const intervalOk = now - state.lastMetricEmitAt >= METRIC_EMIT_MIN_INTERVAL_MS;
3139
+ const enteredHighUtil = utilPct >= 80 && state.metricBelowHighUtil;
3140
+ if (intervalOk || enteredHighUtil) {
3141
+ emitMetric(endpoint, resellerId, totalInWindow, limitPerMin);
3142
+ state.lastMetricEmitAt = now;
3143
+ if (utilPct >= 80) {
3144
+ state.metricBelowHighUtil = false;
3145
+ }
3146
+ }
3147
+ if (utilPct < 80) {
3148
+ state.metricBelowHighUtil = true;
3149
+ }
3150
+ }
3151
+ const globalFull = totalInWindow >= limitPerMin;
3152
+ const batchFull = priority === "batch" && batchInWindow >= batchCap;
3153
+ if (!globalFull && !batchFull) {
3154
+ state.log.push(now);
3155
+ if (priority === "batch") {
3156
+ state.batchLog.push(now);
3157
+ }
3158
+ return;
3159
+ }
3160
+ let waitMs;
3161
+ if (globalFull) {
3162
+ const oldest = state.log[0];
3163
+ waitMs = WINDOW_MS2 - (now - oldest) + 1;
3164
+ } else {
3165
+ const oldestBatch = state.batchLog[0];
3166
+ waitMs = WINDOW_MS2 - (now - oldestBatch) + 1;
3167
+ }
3168
+ await new Promise((r) => setTimeout(r, waitMs));
3169
+ }
3170
+ });
3171
+ state.gate = ticket;
3172
+ return ticket;
2926
3173
  }
2927
- function emptyEsimStatusCounts() {
2928
- return { active: 0, suspended: 0, inventory: 0, other: 0, total: 0 };
3174
+ function getLimitForEndpoint(endpoint) {
3175
+ return ENDPOINT_LIMITS_PER_MIN[endpoint] ?? DEFAULT_LIMIT_PER_MIN;
2929
3176
  }
2930
- function addEsimStatusRow(acc, rec) {
2931
- const count = Number(rec.count ?? 0);
2932
- if (!Number.isFinite(count) || count === 0) return;
2933
- const str2 = String(rec.statusStr ?? rec.name ?? rec.status ?? "").toUpperCase();
2934
- if (str2 === "ACTIVATED" || str2 === "ACTIVE" || str2 === "AFFECTED") {
2935
- acc.active += count;
2936
- return;
2937
- }
2938
- if (str2 === "SUSPENDED") {
2939
- acc.suspended += count;
2940
- return;
3177
+ function getRateLimitWindowCounts(resellerKey, endpoint) {
3178
+ const bucketKey = `${resellerKey}:${endpoint}`;
3179
+ const state = _endpointWindows.get(bucketKey);
3180
+ if (!state) {
3181
+ return { calls_in_window: 0, batch_calls_in_window: 0 };
2941
3182
  }
2942
- if (str2 === "FREE" || str2 === "INVENTORY" || str2 === "NOT_ACTIVATED" || str2 === "AVAILABLE") {
2943
- acc.inventory += count;
2944
- return;
3183
+ const windowStart = Date.now() - WINDOW_MS2;
3184
+ let calls_in_window = 0;
3185
+ for (const ts of state.log) {
3186
+ if (ts > windowStart) calls_in_window++;
2945
3187
  }
2946
- const num = rec.statusNum;
2947
- if (typeof num === "number") {
2948
- if (num === 1) acc.active += count;
2949
- else if (num === 0) acc.inventory += count;
2950
- else if (num === 2) acc.suspended += count;
2951
- else acc.other += count;
2952
- return;
3188
+ let batch_calls_in_window = 0;
3189
+ for (const ts of state.batchLog) {
3190
+ if (ts > windowStart) batch_calls_in_window++;
2953
3191
  }
2954
- acc.other += count;
3192
+ return { calls_in_window, batch_calls_in_window };
2955
3193
  }
2956
- function extractEsimStatusCounts(json) {
2957
- const acc = emptyEsimStatusCounts();
2958
- const root = json ?? {};
2959
- const payload = root.data ?? root.esimStatusPerAccount ?? root;
2960
- const accounts = Array.isArray(payload) ? payload : Array.isArray(payload.account) ? payload.account : [];
2961
- for (const account of accounts) {
2962
- const rec = account ?? {};
2963
- const sponsors = Array.isArray(rec.sponsor) ? rec.sponsor : [];
2964
- if (sponsors.length > 0) {
2965
- for (const sp of sponsors) {
2966
- const esim = sp?.esim ?? {};
2967
- const statuses = Array.isArray(esim.status) ? esim.status : [];
2968
- for (const row of statuses) addEsimStatusRow(acc, row ?? {});
2969
- }
2970
- continue;
2971
- }
2972
- acc.active += Number(rec.active ?? 0);
2973
- acc.suspended += Number(rec.suspended ?? 0);
2974
- acc.inventory += Number(rec.inventory ?? rec.notActivated ?? rec.free ?? 0);
2975
- acc.other += Number(rec.other ?? rec.terminated ?? 0);
3194
+ var OCS_BARE_INT_RESELLER_METHODS = [
3195
+ "listSponsor",
3196
+ "listSteeringList",
3197
+ "listDetailedDestinationList",
3198
+ "listDetailedLocationZone",
3199
+ "getCustomerTariff"
3200
+ ];
3201
+ var OCS_BARE_INT_OTHER_METHODS = {
3202
+ listLocationZoneElement: "locationZoneId",
3203
+ listDestinationListPrefix: "destinationListId",
3204
+ listVoipTariffRule: "voipPlanId",
3205
+ getSimProviderStatus: "simId",
3206
+ deleteSubscriberPackage: "packageId"
3207
+ };
3208
+ var OCS_PARAM_RENAMES = {
3209
+ getResellerInfo: { resellerId: "id" },
3210
+ modifyPPTCore: {
3211
+ templateId: "prepaidpackagetemplateid",
3212
+ name: "prepaidpackagetemplatename",
3213
+ volume: "databyte",
3214
+ validityDays: "perioddays"
3215
+ },
3216
+ modifyPPTRecurring: { templateId: "prepaidpackagetemplateid" },
3217
+ modifyPPTThrottling: { templateId: "prepaidpackagetemplateid" },
3218
+ // Console NL-create and the MCP tool both advertise `name` / `volume` /
3219
+ // `validityDays`. OCS createPrepaidPackageTemplate rejects unknown keys
3220
+ // (Jackson code 2) — live failure was Unrecognized field "name".
3221
+ createPrepaidPackageTemplate: {
3222
+ name: "prepaidpackagetemplatename",
3223
+ volume: "databyte",
3224
+ validityDays: "perioddays",
3225
+ dataLimit: "databyte"
3226
+ },
3227
+ createLocationZone: { name: "locationZoneName" }
3228
+ };
3229
+ function applyParamRenames(method, params) {
3230
+ const renames = OCS_PARAM_RENAMES[method];
3231
+ if (!renames || !params || typeof params !== "object" || Array.isArray(params)) {
3232
+ return params;
2976
3233
  }
2977
- acc.total = acc.active + acc.suspended + acc.inventory + acc.other;
2978
- return acc;
3234
+ const record = params;
3235
+ let out;
3236
+ for (const [from, to] of Object.entries(renames)) {
3237
+ if (!Object.prototype.hasOwnProperty.call(record, from)) continue;
3238
+ out ??= { ...record };
3239
+ if (!Object.prototype.hasOwnProperty.call(record, to)) out[to] = record[from];
3240
+ delete out[from];
3241
+ }
3242
+ return out ?? params;
2979
3243
  }
2980
- function esimStatusPerAccountParams(args) {
2981
- if (typeof args === "number") return { accountId: args };
2982
- if (args.accountId !== void 0) return { accountId: args.accountId };
2983
- if (args.resellerId !== void 0) return { resellerId: args.resellerId };
2984
- throw new Error("esimStatusPerAccountParams needs an accountId or a resellerId");
3244
+ function isBareIntResellerMethod(method) {
3245
+ return OCS_BARE_INT_RESELLER_METHODS.includes(method);
2985
3246
  }
2986
- var UNLIMITED_DATA_BYTES = 100 * 1024 * 1024 * 1024;
2987
- var BYTES_PER_MB = 1048576;
2988
- var TEMPLATE_DATA_KEYS = ["databyte", "dataBytes", "dataLimit"];
2989
- function readTemplateDataBytes(t) {
2990
- for (const key of TEMPLATE_DATA_KEYS) {
2991
- const v = t[key];
2992
- if (typeof v === "number" && Number.isFinite(v)) return v;
2993
- }
2994
- return void 0;
3247
+ function isBareIntMethod(method) {
3248
+ return isBareIntResellerMethod(method) || Object.prototype.hasOwnProperty.call(OCS_BARE_INT_OTHER_METHODS, method);
2995
3249
  }
2996
- function throttlePercentFromMb(mb, totalBytes = UNLIMITED_DATA_BYTES) {
2997
- if (!Number.isFinite(mb) || mb <= 0 || totalBytes <= 0) return 100;
2998
- const pct2 = Math.round(mb * BYTES_PER_MB / totalBytes * 100);
2999
- return Math.min(100, Math.max(1, pct2));
3250
+ function bareIntIdName(method) {
3251
+ if (isBareIntResellerMethod(method)) return "resellerId";
3252
+ return OCS_BARE_INT_OTHER_METHODS[method] ?? "id";
3000
3253
  }
3001
- var OCS_THROTTLE_KBPS = [
3002
- 128,
3003
- 256,
3004
- 384,
3005
- 512,
3006
- 1024,
3007
- 3072,
3008
- 5120,
3009
- 7680,
3010
- 10240,
3011
- 20480,
3012
- 51200,
3013
- 102400
3014
- ];
3015
- var OCS_PERIODICITY = { DAILY: 0, WEEKLY: 1, MONTHLY: 2 };
3016
- var OCS_PERIODICITY_VALUES = [0, 1, 2];
3017
- var CEILING_MULTIPLIERS = [20, 25, 50, 100];
3018
- var SECONDS_PER_DAY = 86400;
3019
- function throttledBytesPerPeriod(kbps, periodDays = 1) {
3020
- if (!Number.isFinite(kbps) || kbps <= 0) return 0;
3021
- return kbps * 1e3 * SECONDS_PER_DAY * Math.max(1, periodDays) / 8;
3254
+ function assertBareIntParam(method, params) {
3255
+ if (!isBareIntMethod(method)) return;
3256
+ if (typeof params === "number") return;
3257
+ const shape = params === null ? "null" : Array.isArray(params) ? "array" : typeof params === "object" ? "object" : typeof params;
3258
+ throw new TypeError(
3259
+ `OCS method "${method}" takes a BARE INTEGER ${bareIntIdName(method)}, but received ${shape}. Sending an object makes OCS reject the whole request with "Cannot deserialize value of type java.lang.Integer from Object value", which surfaces in the UI as an empty panel. Pass the id itself, e.g. call("${method}", 1170).`
3260
+ );
3022
3261
  }
3023
- function unlimitedCeilingFor(windowMb, throttleKbps, periodDays = 1) {
3024
- const windowBytes = windowMb * BYTES_PER_MB;
3025
- const unreachableAbove = windowBytes + throttledBytesPerPeriod(throttleKbps ?? 0, periodDays);
3026
- for (const multiplier of CEILING_MULTIPLIERS) {
3027
- const ceilingBytes = windowBytes * multiplier;
3028
- if (ceilingBytes > unreachableAbove) {
3029
- return { ceilingBytes, percent: Math.round(100 / multiplier) };
3030
- }
3262
+ var OcsApiError = class extends Error {
3263
+ constructor(code, message, method, details = {}) {
3264
+ super(`[${method}] OCS error ${code}: ${message}`);
3265
+ this.code = code;
3266
+ this.method = method;
3267
+ this.name = "OcsApiError";
3268
+ this.details = details;
3031
3269
  }
3032
- const last = CEILING_MULTIPLIERS[CEILING_MULTIPLIERS.length - 1];
3033
- return { ceilingBytes: windowBytes * last, percent: Math.round(100 / last) };
3034
- }
3035
- function normalizePackageTemplate(template) {
3036
- const out = { ...template };
3037
- const unlimitedFlag = out.unlimited === true;
3038
- const current = readTemplateDataBytes(out);
3039
- const isUnlimited = unlimitedFlag || current === void 0 || current === 0;
3040
- const throttleAfterMb = typeof out.throttleAfterMb === "number" ? out.throttleAfterMb : void 0;
3041
- const throttleKbps = typeof out.throttleKbps === "number" ? out.throttleKbps : void 0;
3042
- const periodDays = typeof out.perioddays === "number" ? out.perioddays : 1;
3043
- if (throttleKbps !== void 0 && !OCS_THROTTLE_KBPS.includes(throttleKbps)) {
3044
- throw new Error(
3045
- `throttleKbps ${throttleKbps} is not an OCS bitrate. Allowed: ${OCS_THROTTLE_KBPS.join(", ")}. This field is a SPEED in Kbps, not a data allowance.`
3270
+ code;
3271
+ method;
3272
+ details;
3273
+ };
3274
+ var OcsUnknownMethodError = class extends Error {
3275
+ constructor(method) {
3276
+ super(
3277
+ `[${method}] is not a known OCS v1 method. It is not in the verified set of ${OCS_V1_METHODS.size} methods (createAccount/createSubscriber do not exist on the OCS). If this is a genuinely new method, add it to OCS_V1_METHODS or construct the client without { strictMethods: true }.`
3046
3278
  );
3279
+ this.method = method;
3280
+ this.name = "OcsUnknownMethodError";
3047
3281
  }
3048
- const periodicity = out.recurringPeriodicityType;
3049
- if (periodicity !== void 0 && !OCS_PERIODICITY_VALUES.includes(periodicity)) {
3050
- throw new Error(
3051
- `recurringPeriodicityType ${periodicity} is out of range. Allowed: 0=Daily, 1=Weekly, 2=Monthly. (The live v2 Mango templates use 3, which is not a valid value.)`
3282
+ method;
3283
+ };
3284
+ var OcsContractError = class _OcsContractError extends Error {
3285
+ constructor(method, issues, payload) {
3286
+ super(
3287
+ `[${method}] OCS response failed contract validation: ${_OcsContractError.summarize(issues)}`
3052
3288
  );
3289
+ this.method = method;
3290
+ this.issues = issues;
3291
+ this.payload = payload;
3292
+ this.name = "OcsContractError";
3293
+ }
3294
+ method;
3295
+ issues;
3296
+ payload;
3297
+ /** One-line, human-readable join of the issues — for logs and the error message. */
3298
+ get summary() {
3299
+ return _OcsContractError.summarize(this.issues);
3053
3300
  }
3054
- if (out.recurring === true && (periodicity === OCS_PERIODICITY.DAILY || periodicity === OCS_PERIODICITY.WEEKLY) && out.nbOccurrence === void 0) {
3055
- throw new Error(
3056
- "nbOccurrence is required for a daily or weekly recurring template; OCS only treats it as optional for monthly templates."
3057
- );
3301
+ static summarize(issues) {
3302
+ return issues.map((i) => `${i.path}: ${i.message}`).join("; ");
3058
3303
  }
3059
- if (throttleKbps !== void 0) out.throttlingThreshold1Limit = throttleKbps;
3060
- delete out.throttleKbps;
3061
- if (isUnlimited) {
3062
- let ceiling = UNLIMITED_DATA_BYTES;
3063
- if (throttleAfterMb !== void 0 && throttleAfterMb > 0) {
3064
- const sized = unlimitedCeilingFor(throttleAfterMb, throttleKbps, periodDays);
3065
- ceiling = sized.ceilingBytes;
3066
- out.throttlingThreshold1Perc = sized.percent;
3067
- }
3068
- for (const key of TEMPLATE_DATA_KEYS) {
3069
- if (key in out) out[key] = ceiling;
3070
- }
3071
- if (!TEMPLATE_DATA_KEYS.some((k) => k in out)) out.databyte = ceiling;
3304
+ };
3305
+ function ocsErrorMessage(code, rawMsg, rawBody) {
3306
+ switch (code) {
3307
+ case 12:
3308
+ return {
3309
+ message: "OCS resource is read-only (code 12). The package/subscriber/account state does not allow this mutation. Often returned for end-of-life subscribers or finalized invoices.",
3310
+ details: {}
3311
+ };
3312
+ case 17:
3313
+ return {
3314
+ message: "Subscriber is end-of-life (code 17). It can no longer be mutated. Use modify_subscriber_status to revive before further changes.",
3315
+ details: {}
3316
+ };
3317
+ default:
3318
+ if (code >= 10001 && code <= 10004) {
3319
+ const details = {};
3320
+ if (rawBody["invalidTadigs"] !== void 0) {
3321
+ details.invalidTadigs = rawBody["invalidTadigs"];
3322
+ }
3323
+ if (rawBody["existingLZ"] !== void 0) {
3324
+ details.existingLZ = rawBody["existingLZ"];
3325
+ }
3326
+ return { message: rawMsg || "Location zone validation failed", details };
3327
+ }
3328
+ return { message: rawMsg || "Unknown error", details: {} };
3072
3329
  }
3073
- delete out.unlimited;
3074
- if (throttleAfterMb !== void 0) {
3075
- if (!isUnlimited) {
3076
- const total = readTemplateDataBytes(out) ?? UNLIMITED_DATA_BYTES;
3077
- out.throttlingThreshold1Perc = throttlePercentFromMb(throttleAfterMb, total);
3330
+ }
3331
+ var BACKOFF_BASE_MS2 = 500;
3332
+ var BACKOFF_MAX_MS = 4e3;
3333
+ var BACKOFF_MAX_RETRIES = 3;
3334
+ var TRAFFIC_CONTROL_BACKOFF_BASE_MS = 1e3;
3335
+ var TRAFFIC_CONTROL_BACKOFF_MAX_MS = 16e3;
3336
+ var TRAFFIC_CONTROL_MAX_RETRIES = 6;
3337
+ var BACKOFF_JITTER = 0.25;
3338
+ async function retryWithBackoff(fn) {
3339
+ let lastError;
3340
+ let maxRetries = BACKOFF_MAX_RETRIES;
3341
+ let backoffBaseMs = BACKOFF_BASE_MS2;
3342
+ let backoffMaxMs = BACKOFF_MAX_MS;
3343
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
3344
+ if (attempt > 0) {
3345
+ const base = Math.min(backoffBaseMs * Math.pow(2, attempt - 1), backoffMaxMs);
3346
+ const jitter = base * BACKOFF_JITTER * (2 * Math.random() - 1);
3347
+ const delay = Math.max(0, Math.round(base + jitter));
3348
+ await new Promise((r) => setTimeout(r, delay));
3349
+ }
3350
+ try {
3351
+ return await fn();
3352
+ } catch (err) {
3353
+ lastError = err;
3354
+ if (err instanceof OcsApiError) {
3355
+ if (err.code !== 100) throw err;
3356
+ maxRetries = TRAFFIC_CONTROL_MAX_RETRIES;
3357
+ backoffBaseMs = TRAFFIC_CONTROL_BACKOFF_BASE_MS;
3358
+ backoffMaxMs = TRAFFIC_CONTROL_BACKOFF_MAX_MS;
3359
+ }
3360
+ if (err instanceof OcsContractError) {
3361
+ throw err;
3362
+ }
3363
+ if (err instanceof OcsTimeoutError) {
3364
+ throw err;
3365
+ }
3078
3366
  }
3079
- out.throttlingActive = out.throttlingActive ?? true;
3080
3367
  }
3081
- delete out.throttleAfterMb;
3082
- return out;
3368
+ throw lastError;
3083
3369
  }
3084
- function normalizePackageTemplateChanges(changes) {
3085
- const out = { ...changes };
3086
- const explicitlyUnlimited = out.unlimited === true;
3087
- for (const key of TEMPLATE_DATA_KEYS) {
3088
- if (!(key in out)) continue;
3089
- if (out[key] === 0 || explicitlyUnlimited) out[key] = UNLIMITED_DATA_BYTES;
3090
- }
3091
- if (explicitlyUnlimited && !TEMPLATE_DATA_KEYS.some((k) => k in out)) {
3092
- out.databyte = UNLIMITED_DATA_BYTES;
3370
+ function isAbortError(err) {
3371
+ return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError" || /aborted|timed? ?out/i.test(err.message));
3372
+ }
3373
+ var OcsClient = class {
3374
+ /**
3375
+ * @param baseUrl OCS base URL without trailing slash (e.g. https://ocs.esimvault.cloud).
3376
+ * @param _defaultToken Default API token; identifies the reseller for rate-floor keying.
3377
+ * @param _priority Call priority: "interactive" (MCP tool calls, 80% capacity) or
3378
+ * "batch" (cron/poll callers, 20% capacity). Default: "interactive".
3379
+ * @param options Contract-enforcement opt-outs (see `OcsClientOptions`).
3380
+ */
3381
+ constructor(baseUrl, _defaultToken, _priority = "interactive", options = {}) {
3382
+ this._defaultToken = _defaultToken;
3383
+ this._priority = _priority;
3384
+ let end = baseUrl.length;
3385
+ while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
3386
+ this.baseUrl = baseUrl.slice(0, end);
3387
+ this._strictMethods = options.strictMethods ?? false;
3388
+ this._validateResponses = options.validateResponses ?? true;
3389
+ this._strictValidation = options.strictValidation ?? false;
3093
3390
  }
3094
- delete out.unlimited;
3095
- const throttleAfterMb = out.throttleAfterMb;
3096
- if (typeof throttleAfterMb === "number") {
3097
- const total = readTemplateDataBytes(out) ?? UNLIMITED_DATA_BYTES;
3098
- out.throttlingThreshold1Perc = throttlePercentFromMb(throttleAfterMb, total);
3391
+ _defaultToken;
3392
+ _priority;
3393
+ baseUrl;
3394
+ _strictMethods;
3395
+ _validateResponses;
3396
+ _strictValidation;
3397
+ // Per-instance (NOT static): a static set would dedup across requests in a
3398
+ // reused Worker isolate and silently swallow recurring contract-drift warnings.
3399
+ _warnedMethods = /* @__PURE__ */ new Set();
3400
+ _warnedValidation = /* @__PURE__ */ new Set();
3401
+ async call(method, params = {}, token) {
3402
+ if (!OCS_V1_METHODS.has(method)) {
3403
+ if (this._strictMethods) throw new OcsUnknownMethodError(method);
3404
+ if (!this._warnedMethods.has(method)) {
3405
+ this._warnedMethods.add(method);
3406
+ console.warn(
3407
+ `[ocs-client] method "${method}" is not in the verified OCS v1 set \u2014 it may not exist on this OCS instance (see OCS_V1_METHODS).`
3408
+ );
3409
+ }
3410
+ }
3411
+ const tok = token ?? this._defaultToken;
3412
+ if (!tok) {
3413
+ throw new OcsApiError(-1, "No OCS API token provided", method);
3414
+ }
3415
+ const url = `${this.baseUrl}/v1?token=${tok}`;
3416
+ assertBareIntParam(method, params);
3417
+ const wireParams = applyParamRenames(method, params);
3418
+ const body = JSON.stringify({ [method]: wireParams });
3419
+ return retryWithBackoff(async () => {
3420
+ await acquireToken(tok);
3421
+ await acquireEndpointSlot(tok, method, this._priority);
3422
+ return runWithBudget(method, async (signal, attemptMs) => {
3423
+ let res;
3424
+ try {
3425
+ res = await fetch(url, {
3426
+ method: "POST",
3427
+ headers: { "Content-Type": "application/json" },
3428
+ body,
3429
+ signal
3430
+ });
3431
+ } catch (err) {
3432
+ if (isAbortError(err)) {
3433
+ throw new OcsTimeoutError(method, attemptMs);
3434
+ }
3435
+ throw err;
3436
+ }
3437
+ if (res.status >= 500) {
3438
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
3439
+ }
3440
+ if (!res.ok) {
3441
+ throw new OcsApiError(
3442
+ res.status,
3443
+ `HTTP ${res.status} ${res.statusText}`,
3444
+ method
3445
+ );
3446
+ }
3447
+ let json;
3448
+ try {
3449
+ json = await res.json();
3450
+ } catch (err) {
3451
+ if (isAbortError(err)) {
3452
+ throw new OcsTimeoutError(method, attemptMs);
3453
+ }
3454
+ throw err;
3455
+ }
3456
+ if (json.status?.code !== 0) {
3457
+ const ocsCode = json.status?.code ?? -1;
3458
+ const rawMsg = json.status?.msg ?? "Unknown error";
3459
+ const rawBody = json;
3460
+ const { message, details } = ocsErrorMessage(ocsCode, rawMsg, rawBody);
3461
+ throw new OcsApiError(ocsCode, message, method, details);
3462
+ }
3463
+ let payload;
3464
+ if (method === "getCustomerTariff" && json["listTariffRule"] !== void 0) {
3465
+ payload = json["listTariffRule"];
3466
+ } else if (method === "getSubscriberLocationByCellId") {
3467
+ const byMethod = json[method];
3468
+ if (byMethod !== void 0) {
3469
+ payload = byMethod;
3470
+ } else if (json["subscriberLocation"] !== void 0) {
3471
+ payload = json["subscriberLocation"];
3472
+ } else {
3473
+ payload = json[method] ?? json;
3474
+ }
3475
+ } else if (json[method] !== void 0) {
3476
+ payload = json[method];
3477
+ } else if (Object.keys(json).some((k) => k !== "status")) {
3478
+ payload = json;
3479
+ } else {
3480
+ payload = null;
3481
+ }
3482
+ if (this._validateResponses) {
3483
+ const schema = RESPONSE_SCHEMAS[method];
3484
+ if (schema) {
3485
+ const parsed = schema.safeParse(payload);
3486
+ if (!parsed.success) {
3487
+ const issues = parsed.error.issues.map((i) => ({
3488
+ path: i.path.join(".") || "<root>",
3489
+ message: i.message
3490
+ }));
3491
+ if (this._strictValidation) {
3492
+ throw new OcsContractError(method, issues, payload);
3493
+ }
3494
+ if (!this._warnedValidation.has(method)) {
3495
+ this._warnedValidation.add(method);
3496
+ console.warn(`[ocs-client] ${new OcsContractError(method, issues, payload).message}`);
3497
+ }
3498
+ }
3499
+ }
3500
+ }
3501
+ return payload;
3502
+ });
3503
+ });
3099
3504
  }
3100
- delete out.throttleAfterMb;
3101
- return out;
3102
- }
3505
+ };
3103
3506
 
3104
3507
  // ../../packages/ocs-spec/src/mcc-iso.ts
3105
3508
  var MCC_TO_ISO = {
@@ -3918,20 +4321,23 @@ var NETWORK_EVENT_KEYS = [
3918
4321
  "mocAnswer",
3919
4322
  "mtcAnswer"
3920
4323
  ];
4324
+ function positiveSimId(raw) {
4325
+ const n = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() !== "" ? Number(raw) : NaN;
4326
+ return Number.isSafeInteger(n) && n > 0 ? n : void 0;
4327
+ }
3921
4328
  function subscriberSimId(record) {
3922
4329
  if (!record || typeof record !== "object") return void 0;
3923
4330
  const rec = record;
3924
4331
  const sim = rec.sim;
3925
4332
  if (sim && typeof sim === "object") {
3926
- const id = sim.id;
3927
- if (typeof id === "number" && Number.isFinite(id)) return id;
3928
- if (typeof id === "string" && id.trim() !== "" && Number.isFinite(Number(id))) return Number(id);
3929
- }
3930
- for (const key of ["simId", "sim_id"]) {
3931
- const v = rec[key];
3932
- if (typeof v === "number" && Number.isFinite(v)) return v;
3933
- if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
3934
- }
4333
+ const simRec = sim;
4334
+ const fromSim = positiveSimId(simRec.id) ?? positiveSimId(simRec.simId) ?? positiveSimId(simRec.sim_id);
4335
+ if (fromSim !== void 0) return fromSim;
4336
+ }
4337
+ const flat = positiveSimId(rec.simId) ?? positiveSimId(rec.sim_id);
4338
+ if (flat !== void 0) return flat;
4339
+ const nested = rec.subscriber;
4340
+ if (nested && typeof nested === "object" && nested !== record) return subscriberSimId(nested);
3935
4341
  return void 0;
3936
4342
  }
3937
4343
  function prepaidPackageRows(data) {
@@ -4263,10 +4669,6 @@ async function generateStorefrontLogo(input) {
4263
4669
  }
4264
4670
 
4265
4671
  export {
4266
- getLimitForEndpoint,
4267
- getRateLimitWindowCounts,
4268
- OcsApiError,
4269
- OcsClient,
4270
4672
  OCS_MAX_USAGE_WINDOW_DAYS,
4271
4673
  clampUsagePeriod,
4272
4674
  lastNDaysPeriod,
@@ -4283,8 +4685,16 @@ export {
4283
4685
  recurringIdForPackage,
4284
4686
  extractEsimStatusCounts,
4285
4687
  esimStatusPerAccountParams,
4286
- normalizePackageTemplate,
4287
- normalizePackageTemplateChanges,
4688
+ ocsHlrBitrate,
4689
+ ocsHlrBitrateFromBps,
4690
+ ocsSimStatus,
4691
+ locationZoneCreateParams,
4692
+ packageTemplateCreateFromToolJson,
4693
+ packageTemplateModifyFromToolJson,
4694
+ getLimitForEndpoint,
4695
+ getRateLimitWindowCounts,
4696
+ OcsApiError,
4697
+ OcsClient,
4288
4698
  mccToIso,
4289
4699
  buildListSubscriberParams,
4290
4700
  extractSubscriberStatus,
@@ -4348,4 +4758,4 @@ export {
4348
4758
  storefrontScreen,
4349
4759
  CARRIER_VERSION
4350
4760
  };
4351
- //# sourceMappingURL=chunk-J52VP7E7.js.map
4761
+ //# sourceMappingURL=chunk-2TVU2GRC.js.map