@fruggr/zendesk-mcp-server 3.2.1 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD-PARTY-NOTICES.md +9 -9
- package/dist/{http-7NVoxfEA.js → http-BYYYoQaK.js} +45 -17
- package/dist/index.js +1280 -672
- package/dist/sbom.cdx.json +25 -25
- package/dist/{token-persistence-BeetFqPF.js → token-persistence-BUHh23BM.js} +1 -1
- package/dist/{token-store-SYK0DN9M.js → token-store-BLAkLLQO.js} +2 -2
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -7546,6 +7546,92 @@ const isStartupError = (err) => err instanceof Error && err.name === "StartupErr
|
|
|
7546
7546
|
/** The errno code of a failed filesystem call, for the message. */
|
|
7547
7547
|
const errnoCode = (err) => err?.code ?? "unknown error";
|
|
7548
7548
|
//#endregion
|
|
7549
|
+
//#region src/utils/validation.ts
|
|
7550
|
+
const formatPath = (path) => path.map(String).join(".");
|
|
7551
|
+
const formatIssue$1 = (issue) => issue.path.length > 0 ? `${formatPath(issue.path)}: ${issue.message}` : issue.message;
|
|
7552
|
+
/**
|
|
7553
|
+
* `path: message` per issue, joined by `; ` since zod messages carry commas.
|
|
7554
|
+
* Ours rather than the SDK's (not exported): one wording for every mode and for
|
|
7555
|
+
* startup config errors, stable across SDK bumps.
|
|
7556
|
+
*/
|
|
7557
|
+
const formatIssues = (issues) => issues.map(formatIssue$1).join("; ");
|
|
7558
|
+
const unwrap$1 = (node) => node._zod.def.innerType ? unwrap$1(node._zod.def.innerType) : node;
|
|
7559
|
+
/**
|
|
7560
|
+
* Keys declared by the object at `path`, or undefined when no plain object sits
|
|
7561
|
+
* there (a record, say, whose keys zod checks against an enum).
|
|
7562
|
+
*/
|
|
7563
|
+
const keysAt = (node, path) => {
|
|
7564
|
+
const { def } = unwrap$1(node)._zod;
|
|
7565
|
+
if (path.length === 0) return def.shape && Object.keys(def.shape);
|
|
7566
|
+
const [head, ...rest] = path;
|
|
7567
|
+
const child = typeof head === "number" ? def.element : def.shape?.[String(head)];
|
|
7568
|
+
return child && keysAt(child, rest);
|
|
7569
|
+
};
|
|
7570
|
+
const keyList = (keys) => [...keys].sort().join(", ") || "(none)";
|
|
7571
|
+
/** One sentence per object holding unknown keys, the top level first. */
|
|
7572
|
+
const unknownKeysMessage = (schema, issues, topLevelKeys) => [...issues].sort((a, b) => a.path.length - b.path.length).map(({ path, keys }) => {
|
|
7573
|
+
if (path.length === 0) return `Unknown parameter(s): ${keys.join(", ")}. Valid parameters: ${topLevelKeys}.`;
|
|
7574
|
+
const parent = formatPath(path);
|
|
7575
|
+
const unknown = `Unknown parameter(s): ${keys.map((key) => `${parent}.${key}`).join(", ")}.`;
|
|
7576
|
+
const valid = keysAt(schema, path);
|
|
7577
|
+
return valid ? `${unknown} Valid parameters for ${parent}: ${keyList(valid)}.` : unknown;
|
|
7578
|
+
}).join(" ");
|
|
7579
|
+
/**
|
|
7580
|
+
* Build a strict validator for a tool's input schema, computing the strict
|
|
7581
|
+
* schema and the valid-key list once at construction rather than per call.
|
|
7582
|
+
*
|
|
7583
|
+
* Zod objects default to `strip`, which silently drops unknown keys. That hid
|
|
7584
|
+
* #100: a caller passing `per_page` to list_tickets (whose parameter is
|
|
7585
|
+
* `page_size`) had the key dropped, so a large unpaginated page came back. The
|
|
7586
|
+
* validator rejects unknown keys and names both the offending ones and the
|
|
7587
|
+
* valid parameters, so a misremembered name fails loudly. Any other failure is
|
|
7588
|
+
* one `path: message` per issue (#206). When both occur only the unknown keys
|
|
7589
|
+
* are reported: a guessed name is the one error a caller cannot spot by
|
|
7590
|
+
* re-reading its own arguments.
|
|
7591
|
+
*/
|
|
7592
|
+
const createStrictValidator = (schema, strict = schema.strict()) => {
|
|
7593
|
+
const topLevelKeys = keyList(Object.keys(schema.shape));
|
|
7594
|
+
return (params) => {
|
|
7595
|
+
const result = strict.safeParse(params);
|
|
7596
|
+
if (result.success) return {
|
|
7597
|
+
success: true,
|
|
7598
|
+
data: result.data
|
|
7599
|
+
};
|
|
7600
|
+
const unknownKeyIssues = result.error.issues.filter((issue) => issue.code === "unrecognized_keys");
|
|
7601
|
+
return {
|
|
7602
|
+
success: false,
|
|
7603
|
+
message: unknownKeyIssues.length > 0 ? unknownKeysMessage(schema, unknownKeyIssues, topLevelKeys) : `Invalid parameter(s): ${formatIssues(result.error.issues)}`
|
|
7604
|
+
};
|
|
7605
|
+
};
|
|
7606
|
+
};
|
|
7607
|
+
/** The proxy dispatch path (namespace/single modes): throws the validator's message. */
|
|
7608
|
+
const createStrictParamsParser = (schema) => {
|
|
7609
|
+
const validate = createStrictValidator(schema);
|
|
7610
|
+
return (params) => {
|
|
7611
|
+
const result = validate(params);
|
|
7612
|
+
if (!result.success) throw new Error(result.message);
|
|
7613
|
+
return result.data;
|
|
7614
|
+
};
|
|
7615
|
+
};
|
|
7616
|
+
/**
|
|
7617
|
+
* The same validator as a Standard Schema, for `all` mode: the SDK calls this
|
|
7618
|
+
* `validate` and prints our pathless issue after its own prefix, so all three
|
|
7619
|
+
* modes word an error alike. The JSON Schema is zod's own, untouched.
|
|
7620
|
+
*/
|
|
7621
|
+
const toStrictStandardSchema = (schema) => {
|
|
7622
|
+
const strict = schema.strict();
|
|
7623
|
+
const validate = createStrictValidator(schema, strict);
|
|
7624
|
+
return { "~standard": {
|
|
7625
|
+
version: 1,
|
|
7626
|
+
vendor: "zendesk-mcp-server",
|
|
7627
|
+
validate: (value) => {
|
|
7628
|
+
const result = validate(value);
|
|
7629
|
+
return result.success ? { value: result.data } : { issues: [{ message: result.message }] };
|
|
7630
|
+
},
|
|
7631
|
+
jsonSchema: strict["~standard"].jsonSchema
|
|
7632
|
+
} };
|
|
7633
|
+
};
|
|
7634
|
+
//#endregion
|
|
7549
7635
|
//#region src/config.ts
|
|
7550
7636
|
const ToolMode = _enum([
|
|
7551
7637
|
"single",
|
|
@@ -7583,6 +7669,14 @@ const Transport = _enum(["stdio", "http"]);
|
|
|
7583
7669
|
const ConfigSchema = object({
|
|
7584
7670
|
subdomain: string$2().min(1, "ZENDESK_SUBDOMAIN is required"),
|
|
7585
7671
|
oauthClientId: string$2().min(1),
|
|
7672
|
+
/**
|
|
7673
|
+
* Help Center brand allow-list (`--brand-ids`): brand ids or subdomains, or
|
|
7674
|
+
* 'all'. Unset: account default brand, surface unchanged. One entry: hard
|
|
7675
|
+
* lock, no `brand_id` field. Several or 'all': `list_brands` exposed and
|
|
7676
|
+
* `brand_id` required on brand-scoped tools. Entries resolve lazily, on first
|
|
7677
|
+
* use. Modes and rationale: docs/configuration.md.
|
|
7678
|
+
*/
|
|
7679
|
+
brandIds: array(string$2().min(1)).min(1).optional(),
|
|
7586
7680
|
logLevel: LogLevel,
|
|
7587
7681
|
mode: ToolMode,
|
|
7588
7682
|
readOnly: boolean$1(),
|
|
@@ -7610,14 +7704,12 @@ const ConfigSchema = object({
|
|
|
7610
7704
|
topology: boolean$1().default(true),
|
|
7611
7705
|
/**
|
|
7612
7706
|
* Whether to PRE-LIST the promoted ("featured") Help Center articles: the
|
|
7613
|
-
*
|
|
7614
|
-
*
|
|
7615
|
-
*
|
|
7616
|
-
*
|
|
7617
|
-
*
|
|
7618
|
-
*
|
|
7619
|
-
* registered) — that is cheap and on-demand. Only ever active when the
|
|
7620
|
-
* `help_center` namespace itself is active.
|
|
7707
|
+
* article resource's `list` callback, which scans `/articles` to enumerate the
|
|
7708
|
+
* promoted set for `resources/list`. On by default; `--no-promoted-articles`
|
|
7709
|
+
* turns it off (e.g. on a very large or multi-brand Help Center) so the server
|
|
7710
|
+
* issues zero preloading requests. Reading a known article by id and the
|
|
7711
|
+
* on-demand `list_promoted_articles` tool stay available. Only ever active
|
|
7712
|
+
* when the `help_center` namespace itself is active.
|
|
7621
7713
|
*/
|
|
7622
7714
|
promotedArticles: boolean$1().default(true),
|
|
7623
7715
|
/**
|
|
@@ -7696,8 +7788,25 @@ const portEnv = (name) => {
|
|
|
7696
7788
|
const env = readNonEmptyEnv(name);
|
|
7697
7789
|
return env.value === void 0 ? void 0 : parsePort(env.value, env.name);
|
|
7698
7790
|
};
|
|
7791
|
+
/**
|
|
7792
|
+
* Split a raw `--brand-ids` / `ZENDESK_BRAND_IDS` value into its entries.
|
|
7793
|
+
* Comma-separated, entries trimmed; an empty entry (from `a,,b`, a leading/
|
|
7794
|
+
* trailing comma, or a whitespace-only value) is a misconfiguration, not an
|
|
7795
|
+
* ignored blank: it fails at startup naming the knob. Format validation only —
|
|
7796
|
+
* whether an id/subdomain exists is decided lazily, on first use (no token is
|
|
7797
|
+
* available at startup on either transport).
|
|
7798
|
+
*/
|
|
7799
|
+
const parseBrandIds = (raw) => {
|
|
7800
|
+
if (raw === void 0) return void 0;
|
|
7801
|
+
const entries = raw.split(",").map((entry) => entry.trim());
|
|
7802
|
+
if (entries.some((entry) => entry.length === 0)) throw createStartupError("Invalid --brand-ids / ZENDESK_BRAND_IDS value: empty entry. Expected a comma-separated list of brand ids or subdomains, or \"all\".", STARTUP_DOCS.environment);
|
|
7803
|
+
const normalized = entries.map((entry) => entry.toLowerCase());
|
|
7804
|
+
if (normalized.length > 1 && normalized.includes("all")) throw createStartupError("Invalid --brand-ids / ZENDESK_BRAND_IDS value: \"all\" cannot be combined with brand ids or subdomains.", STARTUP_DOCS.environment);
|
|
7805
|
+
return [...new Set(normalized)];
|
|
7806
|
+
};
|
|
7699
7807
|
const CLI_OPTIONS = {
|
|
7700
7808
|
mode: { type: "string" },
|
|
7809
|
+
"brand-ids": { type: "string" },
|
|
7701
7810
|
namespace: {
|
|
7702
7811
|
type: "string",
|
|
7703
7812
|
multiple: true
|
|
@@ -7767,7 +7876,7 @@ const parseStrict = (args) => {
|
|
|
7767
7876
|
const validated = (input) => {
|
|
7768
7877
|
const result = ConfigSchema.safeParse(input);
|
|
7769
7878
|
if (result.success) return result.data;
|
|
7770
|
-
throw createStartupError(`${result.error.issues
|
|
7879
|
+
throw createStartupError(`${formatIssues(result.error.issues)}.`, STARTUP_DOCS.cliReference, result.error);
|
|
7771
7880
|
};
|
|
7772
7881
|
const parseCliArgs = (args) => {
|
|
7773
7882
|
const { values, positionals } = parseStrict(args);
|
|
@@ -7785,6 +7894,7 @@ const parseCliArgs = (args) => {
|
|
|
7785
7894
|
}
|
|
7786
7895
|
if (values.port !== void 0) result.port = parsePort(values.port, "--port");
|
|
7787
7896
|
if (values["callback-port"] !== void 0) result.callbackPort = parsePort(values["callback-port"], "--callback-port");
|
|
7897
|
+
if (values["brand-ids"] !== void 0) result.brandIds = values["brand-ids"];
|
|
7788
7898
|
return result;
|
|
7789
7899
|
};
|
|
7790
7900
|
const splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -7812,10 +7922,12 @@ const loadConfig = (argv = process.argv.slice(2)) => {
|
|
|
7812
7922
|
const mode = cli.tools?.length ? "all" : cli.mode ?? "namespace";
|
|
7813
7923
|
const namespaces = cli.namespaces ?? (cli.tools?.length ? [...Namespace.options] : void 0);
|
|
7814
7924
|
const callbackPort = cli.callbackPort ?? portEnv("OAUTH_CALLBACK_PORT");
|
|
7925
|
+
const brandIds = cli.brandIds ?? requireNonEmptyEnv("ZENDESK_BRAND_IDS");
|
|
7815
7926
|
const hcResourceScheme = cli.hcResourceScheme ?? requireNonEmptyEnv("HC_RESOURCE_SCHEME");
|
|
7816
7927
|
return validated({
|
|
7817
7928
|
subdomain,
|
|
7818
7929
|
oauthClientId,
|
|
7930
|
+
brandIds: parseBrandIds(brandIds),
|
|
7819
7931
|
logLevel: cli.logLevel ?? requireNonEmptyEnv("LOG_LEVEL") ?? "info",
|
|
7820
7932
|
mode,
|
|
7821
7933
|
readOnly: cli.readOnly ?? false,
|
|
@@ -7832,7 +7944,389 @@ const loadConfig = (argv = process.argv.slice(2)) => {
|
|
|
7832
7944
|
});
|
|
7833
7945
|
};
|
|
7834
7946
|
//#endregion
|
|
7835
|
-
//#region
|
|
7947
|
+
//#region src/constants.ts
|
|
7948
|
+
const positiveIntEnv = (name, fallback) => {
|
|
7949
|
+
const raw = readEnv(name).value;
|
|
7950
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
7951
|
+
const parsed = Number(raw);
|
|
7952
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
7953
|
+
};
|
|
7954
|
+
const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
|
|
7955
|
+
const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
|
|
7956
|
+
const DEFAULT_CALLBACK_PORT = 27439;
|
|
7957
|
+
const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
|
|
7958
|
+
const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
|
|
7959
|
+
const STDIO_MAX_MESSAGE_BYTES = 10485760;
|
|
7960
|
+
const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
|
|
7961
|
+
const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
|
|
7962
|
+
const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
|
|
7963
|
+
const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
|
|
7964
|
+
const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
|
|
7965
|
+
const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
|
|
7966
|
+
const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
|
|
7967
|
+
const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
|
|
7968
|
+
const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
|
|
7969
|
+
const getOAuthUrls = (subdomain) => ({
|
|
7970
|
+
authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
|
|
7971
|
+
tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
|
|
7972
|
+
});
|
|
7973
|
+
//#endregion
|
|
7974
|
+
//#region src/client/retry.ts
|
|
7975
|
+
const MAX_ATTEMPTS = 3;
|
|
7976
|
+
const BASE_DELAY_MS = 250;
|
|
7977
|
+
const MAX_DELAY_MS = 4e3;
|
|
7978
|
+
const REQUEST_TIMEOUT_MS = 3e4;
|
|
7979
|
+
const TRANSFER_TIMEOUT_MS = 12e4;
|
|
7980
|
+
/**
|
|
7981
|
+
* A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
|
|
7982
|
+
* is numeric, not a syscall string, so it classifies as `unknown`: a GET is
|
|
7983
|
+
* retried, a write is not — the request may have arrived before the deadline.
|
|
7984
|
+
*/
|
|
7985
|
+
const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
|
|
7986
|
+
const MAX_RETRY_AFTER_MS = 5e3;
|
|
7987
|
+
const POLICIES = {
|
|
7988
|
+
GET: {
|
|
7989
|
+
network: "any",
|
|
7990
|
+
serverErrors: true
|
|
7991
|
+
},
|
|
7992
|
+
DELETE: {
|
|
7993
|
+
network: "pre-send",
|
|
7994
|
+
serverErrors: false
|
|
7995
|
+
},
|
|
7996
|
+
POST: {
|
|
7997
|
+
network: "pre-send",
|
|
7998
|
+
serverErrors: false
|
|
7999
|
+
},
|
|
8000
|
+
PUT: {
|
|
8001
|
+
network: "pre-send",
|
|
8002
|
+
serverErrors: false
|
|
8003
|
+
}
|
|
8004
|
+
};
|
|
8005
|
+
const DELAY_SECONDS = /^\d+$/;
|
|
8006
|
+
const HTTP_DATE_START = /^[A-Za-z]/;
|
|
8007
|
+
const NON_ASCII = /[^ -~]/g;
|
|
8008
|
+
const TOKEN_SEGMENT = /\/token\/[^/]+/;
|
|
8009
|
+
const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
|
|
8010
|
+
const PRE_SEND_CODES = /* @__PURE__ */ new Set([
|
|
8011
|
+
"ENOTFOUND",
|
|
8012
|
+
"EAI_AGAIN",
|
|
8013
|
+
"ECONNREFUSED",
|
|
8014
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
8015
|
+
]);
|
|
8016
|
+
const defaultRetryDeps = {
|
|
8017
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
8018
|
+
random: Math.random
|
|
8019
|
+
};
|
|
8020
|
+
/** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
|
|
8021
|
+
const toAscii = (text) => text.replace(NON_ASCII, "?");
|
|
8022
|
+
/**
|
|
8023
|
+
* Identifies the request without leaking a credential: uploads carry an upload
|
|
8024
|
+
* token in the query string, and an attachment `content_url` carries a download
|
|
8025
|
+
* token as a path segment (`/attachments/token/<token>/`).
|
|
8026
|
+
*/
|
|
8027
|
+
const describeTarget = (url) => {
|
|
8028
|
+
const { origin, pathname } = new URL(url);
|
|
8029
|
+
return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
|
|
8030
|
+
};
|
|
8031
|
+
/** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
|
|
8032
|
+
const errorCode = (err) => {
|
|
8033
|
+
let current = err;
|
|
8034
|
+
for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
|
|
8035
|
+
const { code, cause } = current;
|
|
8036
|
+
if (typeof code === "string") return code;
|
|
8037
|
+
current = cause;
|
|
8038
|
+
}
|
|
8039
|
+
};
|
|
8040
|
+
const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
|
|
8041
|
+
const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
|
|
8042
|
+
/**
|
|
8043
|
+
* The client refuses to replay a write that may have landed — but the caller is
|
|
8044
|
+
* an LLM whose reflex on a failed tool call is to try again, which would undo
|
|
8045
|
+
* that. So a failed write says whether it may have taken effect. ASCII only,
|
|
8046
|
+
* same rule as the rest of the message.
|
|
8047
|
+
*/
|
|
8048
|
+
const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
|
|
8049
|
+
const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
|
|
8050
|
+
const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
|
|
8051
|
+
/** Only a write can be duplicated by a replay, so only a write carries a note. */
|
|
8052
|
+
const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
|
|
8053
|
+
/**
|
|
8054
|
+
* A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
|
|
8055
|
+
* carrying credentials by printing the whole thing, query string included — which
|
|
8056
|
+
* would smuggle back exactly what `describeTarget` drops. Same treatment for both,
|
|
8057
|
+
* so one function owns what a URL may look like in our output.
|
|
8058
|
+
*/
|
|
8059
|
+
const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
|
|
8060
|
+
try {
|
|
8061
|
+
return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
|
|
8062
|
+
} catch {
|
|
8063
|
+
return "[url]";
|
|
8064
|
+
}
|
|
8065
|
+
});
|
|
8066
|
+
const failureDetail = (cause) => {
|
|
8067
|
+
if (!(cause instanceof Error)) return redactUrls(String(cause));
|
|
8068
|
+
const message = redactUrls(cause.message);
|
|
8069
|
+
const code = errorCode(cause);
|
|
8070
|
+
if (code !== void 0) return `${code}: ${message}`;
|
|
8071
|
+
return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
|
|
8072
|
+
};
|
|
8073
|
+
const networkErrorMessage = (method, target, attempts, cause) => {
|
|
8074
|
+
const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
|
|
8075
|
+
const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
|
|
8076
|
+
return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
|
|
8077
|
+
};
|
|
8078
|
+
const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
|
|
8079
|
+
name: "ZendeskNetworkError",
|
|
8080
|
+
method,
|
|
8081
|
+
target,
|
|
8082
|
+
attempts
|
|
8083
|
+
});
|
|
8084
|
+
/** Exponential backoff with equal jitter: half the window fixed, half random. */
|
|
8085
|
+
const computeBackoffMs = (attempt, random) => {
|
|
8086
|
+
const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
|
|
8087
|
+
return Math.round(window / 2 + window * random() / 2);
|
|
8088
|
+
};
|
|
8089
|
+
/** `Retry-After` in ms — delay-seconds or HTTP-date. */
|
|
8090
|
+
const parseRetryAfter = (header, now = Date.now()) => {
|
|
8091
|
+
if (header === null) return void 0;
|
|
8092
|
+
const value = header.trim();
|
|
8093
|
+
if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
|
|
8094
|
+
if (!HTTP_DATE_START.test(value)) return void 0;
|
|
8095
|
+
const date = Date.parse(value);
|
|
8096
|
+
if (Number.isNaN(date)) return void 0;
|
|
8097
|
+
return Math.max(0, date - now);
|
|
8098
|
+
};
|
|
8099
|
+
/**
|
|
8100
|
+
* How long to wait before replaying this response, or undefined to accept it.
|
|
8101
|
+
* `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
|
|
8102
|
+
* during maintenance as well as on a 429 — and a value past the cap means the
|
|
8103
|
+
* response is surfaced rather than parking the call for that long.
|
|
8104
|
+
*/
|
|
8105
|
+
const retryDelayFor = (response, policy, attempt, random) => {
|
|
8106
|
+
if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
|
|
8107
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
8108
|
+
if (retryAfter === void 0) return computeBackoffMs(attempt, random);
|
|
8109
|
+
return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
|
|
8110
|
+
};
|
|
8111
|
+
/**
|
|
8112
|
+
* Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
|
|
8113
|
+
* attempt budget. Returns the last response for the caller to inspect (a
|
|
8114
|
+
* non-ok status is still the caller's to turn into a `ZendeskApiError`), and
|
|
8115
|
+
* throws `ZendeskNetworkError` when no response was ever obtained.
|
|
8116
|
+
*/
|
|
8117
|
+
const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
|
|
8118
|
+
const policy = POLICIES[method];
|
|
8119
|
+
for (let tries = 1;; tries += 1) {
|
|
8120
|
+
const last = tries >= MAX_ATTEMPTS;
|
|
8121
|
+
let response;
|
|
8122
|
+
try {
|
|
8123
|
+
response = await attempt();
|
|
8124
|
+
} catch (err) {
|
|
8125
|
+
const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
|
|
8126
|
+
if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
|
|
8127
|
+
await deps.sleep(computeBackoffMs(tries, deps.random));
|
|
8128
|
+
continue;
|
|
8129
|
+
}
|
|
8130
|
+
if (last) return response;
|
|
8131
|
+
const delayMs = retryDelayFor(response, policy, tries, deps.random);
|
|
8132
|
+
if (delayMs === void 0) return response;
|
|
8133
|
+
await response.text().catch(() => void 0);
|
|
8134
|
+
await deps.sleep(delayMs);
|
|
8135
|
+
}
|
|
8136
|
+
};
|
|
8137
|
+
//#endregion
|
|
8138
|
+
//#region src/client/zendesk-api.ts
|
|
8139
|
+
var ZendeskApiError = class ZendeskApiError extends Error {
|
|
8140
|
+
status;
|
|
8141
|
+
statusText;
|
|
8142
|
+
body;
|
|
8143
|
+
method;
|
|
8144
|
+
constructor(status, statusText, body, method) {
|
|
8145
|
+
super(ZendeskApiError.buildMessage(status, statusText, body, method));
|
|
8146
|
+
this.status = status;
|
|
8147
|
+
this.statusText = statusText;
|
|
8148
|
+
this.body = body;
|
|
8149
|
+
this.method = method;
|
|
8150
|
+
this.name = "ZendeskApiError";
|
|
8151
|
+
}
|
|
8152
|
+
static buildMessage(status, statusText, body, method) {
|
|
8153
|
+
switch (status) {
|
|
8154
|
+
case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
|
|
8155
|
+
case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
|
|
8156
|
+
case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
|
|
8157
|
+
case 422: return `Validation error: ${body}`;
|
|
8158
|
+
case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
|
|
8159
|
+
default: {
|
|
8160
|
+
const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
|
|
8161
|
+
return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
|
|
8162
|
+
}
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8165
|
+
};
|
|
8166
|
+
const buildAuthHeader = (token) => `Bearer ${token}`;
|
|
8167
|
+
const buildUrl = (base, path, params) => {
|
|
8168
|
+
const url = new URL(`${base}${path}`);
|
|
8169
|
+
if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
|
|
8170
|
+
return url.toString();
|
|
8171
|
+
};
|
|
8172
|
+
const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
|
|
8173
|
+
...init,
|
|
8174
|
+
method,
|
|
8175
|
+
signal: deadlineSignal(timeoutMs)
|
|
8176
|
+
}), method, describeTarget(url));
|
|
8177
|
+
const executeRequest = async (url, token, options = {}) => {
|
|
8178
|
+
const { method = "GET", body } = options;
|
|
8179
|
+
const headers = {
|
|
8180
|
+
Authorization: buildAuthHeader(token),
|
|
8181
|
+
Accept: "application/json"
|
|
8182
|
+
};
|
|
8183
|
+
if (body) headers["Content-Type"] = "application/json";
|
|
8184
|
+
const init = { headers };
|
|
8185
|
+
if (body) init.body = JSON.stringify(body);
|
|
8186
|
+
const response = await performFetch(method, url, init);
|
|
8187
|
+
if (!response.ok) {
|
|
8188
|
+
const responseBody = await response.text();
|
|
8189
|
+
throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
|
|
8190
|
+
}
|
|
8191
|
+
if (response.status === 204) return {};
|
|
8192
|
+
return response.json();
|
|
8193
|
+
};
|
|
8194
|
+
const zendeskGet = (subdomain, token, path, params) => {
|
|
8195
|
+
const url = buildUrl(getBaseUrl(subdomain), path, params);
|
|
8196
|
+
return executeRequest(url, token);
|
|
8197
|
+
};
|
|
8198
|
+
const zendeskPost = (subdomain, token, path, body) => {
|
|
8199
|
+
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
8200
|
+
return executeRequest(url, token, {
|
|
8201
|
+
method: "POST",
|
|
8202
|
+
body
|
|
8203
|
+
});
|
|
8204
|
+
};
|
|
8205
|
+
const zendeskPut = (subdomain, token, path, body) => {
|
|
8206
|
+
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
8207
|
+
return executeRequest(url, token, {
|
|
8208
|
+
method: "PUT",
|
|
8209
|
+
body
|
|
8210
|
+
});
|
|
8211
|
+
};
|
|
8212
|
+
const helpCenterGet = (subdomain, token, path, params) => {
|
|
8213
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
|
|
8214
|
+
return executeRequest(url, token);
|
|
8215
|
+
};
|
|
8216
|
+
const helpCenterPost = (subdomain, token, path, body) => {
|
|
8217
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8218
|
+
return executeRequest(url, token, {
|
|
8219
|
+
method: "POST",
|
|
8220
|
+
body
|
|
8221
|
+
});
|
|
8222
|
+
};
|
|
8223
|
+
const helpCenterPut = (subdomain, token, path, body) => {
|
|
8224
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8225
|
+
return executeRequest(url, token, {
|
|
8226
|
+
method: "PUT",
|
|
8227
|
+
body
|
|
8228
|
+
});
|
|
8229
|
+
};
|
|
8230
|
+
const helpCenterDelete = (subdomain, token, path) => {
|
|
8231
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8232
|
+
return executeRequest(url, token, { method: "DELETE" });
|
|
8233
|
+
};
|
|
8234
|
+
const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
|
|
8235
|
+
const expectedHost = `${subdomain}.zendesk.com`;
|
|
8236
|
+
const headers = {};
|
|
8237
|
+
if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
|
|
8238
|
+
const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
|
|
8239
|
+
if (!response.ok) {
|
|
8240
|
+
const body = await response.text();
|
|
8241
|
+
throw new ZendeskApiError(response.status, response.statusText, body, "GET");
|
|
8242
|
+
}
|
|
8243
|
+
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
8244
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
8245
|
+
return {
|
|
8246
|
+
data: Buffer.from(arrayBuffer),
|
|
8247
|
+
contentType
|
|
8248
|
+
};
|
|
8249
|
+
};
|
|
8250
|
+
const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
|
|
8251
|
+
const params = { filename };
|
|
8252
|
+
if (uploadToken) params["token"] = uploadToken;
|
|
8253
|
+
const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
|
|
8254
|
+
const response = await performFetch("POST", url, {
|
|
8255
|
+
headers: {
|
|
8256
|
+
Authorization: buildAuthHeader(token),
|
|
8257
|
+
"Content-Type": contentType
|
|
8258
|
+
},
|
|
8259
|
+
body: data
|
|
8260
|
+
}, TRANSFER_TIMEOUT_MS);
|
|
8261
|
+
if (!response.ok) {
|
|
8262
|
+
const responseBody = await response.text();
|
|
8263
|
+
throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
|
|
8264
|
+
}
|
|
8265
|
+
return response.json();
|
|
8266
|
+
};
|
|
8267
|
+
const helpCenterUpload = async (subdomain, token, path, formData) => {
|
|
8268
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8269
|
+
const response = await performFetch("POST", url, {
|
|
8270
|
+
headers: { Authorization: buildAuthHeader(token) },
|
|
8271
|
+
body: formData
|
|
8272
|
+
}, TRANSFER_TIMEOUT_MS);
|
|
8273
|
+
if (!response.ok) {
|
|
8274
|
+
const responseBody = await response.text();
|
|
8275
|
+
throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
|
|
8276
|
+
}
|
|
8277
|
+
return response.json();
|
|
8278
|
+
};
|
|
8279
|
+
//#endregion
|
|
8280
|
+
//#region src/client/brands.ts
|
|
8281
|
+
const BRAND_SUBDOMAIN = /^[a-z0-9][a-z0-9-]*$/;
|
|
8282
|
+
/**
|
|
8283
|
+
* Fetch every brand of the account (cursor-paginated). `page[size]` is sent on
|
|
8284
|
+
* the FIRST request too: without it Zendesk falls back to OFFSET pagination,
|
|
8285
|
+
* which answers without `meta.after_cursor`, and the loop below would stop
|
|
8286
|
+
* after page 1 — a brand past the first 100 would resolve as Unknown.
|
|
8287
|
+
*/
|
|
8288
|
+
const fetchAllBrands = async (subdomain, token) => {
|
|
8289
|
+
const brands = [];
|
|
8290
|
+
let cursor;
|
|
8291
|
+
do {
|
|
8292
|
+
const response = await zendeskGet(subdomain, token, "/brands", {
|
|
8293
|
+
"page[size]": String(100),
|
|
8294
|
+
...cursor ? { "page[after]": cursor } : {}
|
|
8295
|
+
});
|
|
8296
|
+
brands.push(...response.brands ?? []);
|
|
8297
|
+
const meta = response.meta;
|
|
8298
|
+
if (meta?.has_more) {
|
|
8299
|
+
if (!meta.after_cursor || meta.after_cursor === cursor) throw new Error("GET /brands reported more pages but returned no usable after_cursor (missing or identical to the cursor just sent). Cannot continue pagination safely.");
|
|
8300
|
+
cursor = meta.after_cursor;
|
|
8301
|
+
} else cursor = void 0;
|
|
8302
|
+
} while (cursor);
|
|
8303
|
+
return brands;
|
|
8304
|
+
};
|
|
8305
|
+
const createBrandSubdomainResolver = (subdomain, getToken) => {
|
|
8306
|
+
let cached;
|
|
8307
|
+
const brands = () => {
|
|
8308
|
+
if (!cached || Date.now() - cached.at >= 3e5) {
|
|
8309
|
+
const promise = (async () => fetchAllBrands(subdomain, await getToken()))();
|
|
8310
|
+
cached = {
|
|
8311
|
+
at: Date.now(),
|
|
8312
|
+
promise
|
|
8313
|
+
};
|
|
8314
|
+
promise.catch(() => {
|
|
8315
|
+
cached = void 0;
|
|
8316
|
+
});
|
|
8317
|
+
}
|
|
8318
|
+
return cached.promise;
|
|
8319
|
+
};
|
|
8320
|
+
return async (idOrSubdomain) => {
|
|
8321
|
+
const all = await brands();
|
|
8322
|
+
const brand = all.find((b) => String(b.id) === idOrSubdomain) ?? all.find((b) => b.subdomain.toLowerCase() === idOrSubdomain.toLowerCase());
|
|
8323
|
+
if (!brand) throw new Error(`Unknown brand "${idOrSubdomain}": no brand with that id or subdomain exists on this account; call list_brands to see the available brands.`);
|
|
8324
|
+
if (!BRAND_SUBDOMAIN.test(brand.subdomain)) throw new Error(`Brand ${brand.id} has an unexpected subdomain; refusing to send a request to it.`);
|
|
8325
|
+
return brand.subdomain;
|
|
8326
|
+
};
|
|
8327
|
+
};
|
|
8328
|
+
//#endregion
|
|
8329
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
7836
8330
|
var __create = Object.create;
|
|
7837
8331
|
var __defProp = Object.defineProperty;
|
|
7838
8332
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -7864,7 +8358,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
7864
8358
|
enumerable: true
|
|
7865
8359
|
}) : target, mod));
|
|
7866
8360
|
//#endregion
|
|
7867
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.
|
|
8361
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/dialects-DoSzNhcb.mjs
|
|
7868
8362
|
/**
|
|
7869
8363
|
* Canonical `$schema` URIs per supported dialect (http + https variants, trailing-`#` stripped).
|
|
7870
8364
|
*/
|
|
@@ -9527,7 +10021,7 @@ const OAuthTokenRevocationRequestSchema = object({
|
|
|
9527
10021
|
token_type_hint: string$2().optional()
|
|
9528
10022
|
}).strip();
|
|
9529
10023
|
//#endregion
|
|
9530
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.
|
|
10024
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs
|
|
9531
10025
|
/**
|
|
9532
10026
|
* Cross-bundle `instanceof` support for the SDK error classes.
|
|
9533
10027
|
*
|
|
@@ -12582,7 +13076,7 @@ function build() {
|
|
|
12582
13076
|
const subscriptionsListenParamsShape = { notifications: SubscriptionFilterSchema$1 };
|
|
12583
13077
|
const SubscriptionsListenRequestSchema$1 = wireRequest("subscriptions/listen", subscriptionsListenParamsShape);
|
|
12584
13078
|
/**
|
|
12585
|
-
* Anchor
|
|
13079
|
+
* Anchor SubscriptionsListenResultMetaObject — required subscriptionId stamp on
|
|
12586
13080
|
* the graceful-close result. Extends `ResultMetaObject` since spec PR
|
|
12587
13081
|
* #3002 (composed, so the serverInfo key and its leniency stay single-sourced).
|
|
12588
13082
|
*/
|
|
@@ -12855,7 +13349,8 @@ function build() {
|
|
|
12855
13349
|
ListResourceTemplatesResultResponseSchema: wireResultResponse(ListResourceTemplatesResultSchema$1),
|
|
12856
13350
|
ReadResourceResultResponseSchema: wireResultResponse(union([ReadResourceResultSchema$1, InputRequiredResultSchema])),
|
|
12857
13351
|
CompleteResultResponseSchema: wireResultResponse(CompleteResultSchema$1),
|
|
12858
|
-
DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1)
|
|
13352
|
+
DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1),
|
|
13353
|
+
SubscriptionsListenResultResponseSchema: wireResultResponse(SubscriptionsListenResultSchema$1)
|
|
12859
13354
|
};
|
|
12860
13355
|
}
|
|
12861
13356
|
let memo;
|
|
@@ -13599,6 +14094,10 @@ function isSpecRequestMethod(method) {
|
|
|
13599
14094
|
function isSpecNotificationMethod(method) {
|
|
13600
14095
|
return ALL_CODECS.some((codec) => codec.hasNotificationMethod(method));
|
|
13601
14096
|
}
|
|
14097
|
+
/** The names SEP-2663 re-defines for the Tasks extension on the era that removed them from core. */
|
|
14098
|
+
function isExtensionReusedRequestMethod(method) {
|
|
14099
|
+
return method === "tasks/get" || method === "tasks/cancel";
|
|
14100
|
+
}
|
|
13602
14101
|
const ALL_CODECS = [rev2025Codec, rev2026Codec];
|
|
13603
14102
|
var schemas_exports = /* @__PURE__ */ __exportAll({
|
|
13604
14103
|
AnnotationsSchema: () => AnnotationsSchema,
|
|
@@ -14631,6 +15130,8 @@ var Protocol = class {
|
|
|
14631
15130
|
_transport;
|
|
14632
15131
|
_requestMessageId = 0;
|
|
14633
15132
|
_requestHandlers = /* @__PURE__ */ new Map();
|
|
15133
|
+
/** Methods registered with an explicit schema; the era gate in `_onrequest` reads it for the Tasks extension names. */
|
|
15134
|
+
_customSchemaRequestMethods = /* @__PURE__ */ new Set();
|
|
14634
15135
|
_requestHandlerAbortControllers = /* @__PURE__ */ new Map();
|
|
14635
15136
|
_notificationHandlers = /* @__PURE__ */ new Map();
|
|
14636
15137
|
_responseHandlers = /* @__PURE__ */ new Map();
|
|
@@ -14908,7 +15409,7 @@ var Protocol = class {
|
|
|
14908
15409
|
return;
|
|
14909
15410
|
}
|
|
14910
15411
|
}
|
|
14911
|
-
if (isSpecRequestMethod(request.method) && !codec.hasRequestMethod(request.method)) {
|
|
15412
|
+
if (isSpecRequestMethod(request.method) && !codec.hasRequestMethod(request.method) && !(isExtensionReusedRequestMethod(request.method) && this._customSchemaRequestMethods.has(request.method))) {
|
|
14912
15413
|
sendErrorResponse(ProtocolErrorCode.MethodNotFound, "Method not found");
|
|
14913
15414
|
return;
|
|
14914
15415
|
}
|
|
@@ -15041,8 +15542,11 @@ var Protocol = class {
|
|
|
15041
15542
|
}
|
|
15042
15543
|
request(request, schemaOrOptions, maybeOptions) {
|
|
15043
15544
|
const codec = this._resolveOutboundCodec(request.method);
|
|
15545
|
+
if (isStandardSchema(schemaOrOptions)) {
|
|
15546
|
+
if (!isExtensionReusedRequestMethod(request.method)) this._assertOutboundRequestInEra(codec, request.method);
|
|
15547
|
+
return this._requestWithSchemaViaCodec(codec, request, schemaOrOptions, maybeOptions);
|
|
15548
|
+
}
|
|
15044
15549
|
this._assertOutboundRequestInEra(codec, request.method);
|
|
15045
|
-
if (isStandardSchema(schemaOrOptions)) return this._requestWithSchemaViaCodec(codec, request, schemaOrOptions, maybeOptions);
|
|
15046
15550
|
const validate = codecResultValidator(codec, request.method);
|
|
15047
15551
|
if (validate === void 0) throw new TypeError(`'${request.method}' is not a spec method; pass a result schema as the second argument to request().`);
|
|
15048
15552
|
return this._requestWithSchemaViaCodec(codec, request, validate, schemaOrOptions);
|
|
@@ -15286,6 +15790,8 @@ var Protocol = class {
|
|
|
15286
15790
|
return maybeHandler(parsed.data, ctx);
|
|
15287
15791
|
};
|
|
15288
15792
|
else throw new TypeError("setRequestHandler: handler is required");
|
|
15793
|
+
if (typeof schemasOrHandler === "function") this._customSchemaRequestMethods.delete(method);
|
|
15794
|
+
else this._customSchemaRequestMethods.add(method);
|
|
15289
15795
|
this._requestHandlers.set(method, this._wrapHandler(method, stored));
|
|
15290
15796
|
}
|
|
15291
15797
|
/**
|
|
@@ -15314,6 +15820,7 @@ var Protocol = class {
|
|
|
15314
15820
|
*/
|
|
15315
15821
|
removeRequestHandler(method) {
|
|
15316
15822
|
this._requestHandlers.delete(method);
|
|
15823
|
+
this._customSchemaRequestMethods.delete(method);
|
|
15317
15824
|
}
|
|
15318
15825
|
/**
|
|
15319
15826
|
* Asserts that a request handler has not already been set for the given method, in preparation for a new one being automatically installed.
|
|
@@ -15895,7 +16402,7 @@ function normalizeRawShapeSchema(schema) {
|
|
|
15895
16402
|
return schema;
|
|
15896
16403
|
}
|
|
15897
16404
|
//#endregion
|
|
15898
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.
|
|
16405
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/ajvProvider-CEoC__sr.mjs
|
|
15899
16406
|
var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
15900
16407
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15901
16408
|
exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = void 0;
|
|
@@ -23030,7 +23537,7 @@ var AjvJsonSchemaValidator = class {
|
|
|
23030
23537
|
};
|
|
23031
23538
|
import_ajv.Ajv;
|
|
23032
23539
|
//#endregion
|
|
23033
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.
|
|
23540
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs
|
|
23034
23541
|
const COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
|
23035
23542
|
/**
|
|
23036
23543
|
* Checks if a schema is completable (has completion metadata).
|
|
@@ -23052,6 +23559,15 @@ function armSseKeepAlive(intervalMs, onTick) {
|
|
|
23052
23559
|
timer.unref?.();
|
|
23053
23560
|
return timer;
|
|
23054
23561
|
}
|
|
23562
|
+
function comparableResource(value) {
|
|
23563
|
+
const text = String(value);
|
|
23564
|
+
const hash = text.indexOf("#");
|
|
23565
|
+
return (hash === -1 ? text : text.slice(0, hash)).replace(/\/$/, "");
|
|
23566
|
+
}
|
|
23567
|
+
function sameResource(reported, expected) {
|
|
23568
|
+
if (!reported) return false;
|
|
23569
|
+
return comparableResource(reported) === comparableResource(expected);
|
|
23570
|
+
}
|
|
23055
23571
|
function headerQuotedValue(value) {
|
|
23056
23572
|
return value.replaceAll(/[\\"]/g, String.raw`\$&`).replaceAll(/[^\u0020-\u007E]/g, " ");
|
|
23057
23573
|
}
|
|
@@ -23071,6 +23587,38 @@ function buildWwwAuthenticateHeader(errorCode, description, requiredScopes, reso
|
|
|
23071
23587
|
return header;
|
|
23072
23588
|
}
|
|
23073
23589
|
/**
|
|
23590
|
+
* Validate a raw `Authorization` header value as a Bearer token and return
|
|
23591
|
+
* the verified {@link AuthInfo}.
|
|
23592
|
+
*
|
|
23593
|
+
* The runtime-neutral core of Bearer authentication: it parses the header,
|
|
23594
|
+
* runs the verifier, compares the token's resource with `expectedResource`,
|
|
23595
|
+
* enforces `requiredScopes`, and rejects tokens without an
|
|
23596
|
+
* expiration or past it. On any failure it throws an {@link OAuthError} —
|
|
23597
|
+
* pass that to {@link bearerAuthChallengeResponse} for the matching HTTP
|
|
23598
|
+
* answer, or use {@link requireBearerAuth} to get both steps as one call.
|
|
23599
|
+
*
|
|
23600
|
+
* Framework adapters build on this: `requireBearerAuth` from
|
|
23601
|
+
* `@modelcontextprotocol/express` feeds it `req.headers.authorization`.
|
|
23602
|
+
*/
|
|
23603
|
+
async function verifyBearerToken(authorizationHeader, options) {
|
|
23604
|
+
const { verifier, requiredScopes = [], expectedResource } = options;
|
|
23605
|
+
if (!authorizationHeader) throw new OAuthError(OAuthErrorCode.InvalidToken, "Missing Authorization header");
|
|
23606
|
+
const [type, token] = authorizationHeader.split(" ");
|
|
23607
|
+
if (type?.toLowerCase() !== "bearer" || !token) throw new OAuthError(OAuthErrorCode.InvalidToken, "Invalid Authorization header format, expected 'Bearer TOKEN'");
|
|
23608
|
+
const authInfo = await verifier.verifyAccessToken(token);
|
|
23609
|
+
if (expectedResource !== void 0 && !sameResource(authInfo.resource, expectedResource)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token was not issued for this resource");
|
|
23610
|
+
if (requiredScopes.length > 0) {
|
|
23611
|
+
if (!requiredScopes.every((scope) => authInfo.scopes.includes(scope))) throw new OAuthError(OAuthErrorCode.InsufficientScope, "Insufficient scope");
|
|
23612
|
+
}
|
|
23613
|
+
if (typeof authInfo.expiresAt !== "number" || Number.isNaN(authInfo.expiresAt)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has no expiration time");
|
|
23614
|
+
else if (authInfo.expiresAt < Date.now() / 1e3) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has expired");
|
|
23615
|
+
if (options.resourceMetadataUrl !== void 0 && authInfo.resourceMetadataUrl === void 0) return {
|
|
23616
|
+
...authInfo,
|
|
23617
|
+
resourceMetadataUrl: options.resourceMetadataUrl
|
|
23618
|
+
};
|
|
23619
|
+
return authInfo;
|
|
23620
|
+
}
|
|
23621
|
+
/**
|
|
23074
23622
|
* Build the HTTP answer for a Bearer authentication failure.
|
|
23075
23623
|
*
|
|
23076
23624
|
* Maps an {@link OAuthError} to its status — `401` for `invalid_token` and
|
|
@@ -23383,6 +23931,11 @@ var Server = class extends Protocol {
|
|
|
23383
23931
|
if (modernProtocolVersions(this._supportedProtocolVersions).length > 0) this.setRequestHandler("server/discover", () => this._ondiscover());
|
|
23384
23932
|
if (this._capabilities.logging) this._registerLoggingHandler();
|
|
23385
23933
|
}
|
|
23934
|
+
/** Attaches to the given transport; rejects while this instance is connected to another one. */
|
|
23935
|
+
async connect(transport) {
|
|
23936
|
+
if (this.transport !== void 0) throw new SdkError(SdkErrorCode.AlreadyConnected, "Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.");
|
|
23937
|
+
await super.connect(transport);
|
|
23938
|
+
}
|
|
23386
23939
|
/**
|
|
23387
23940
|
* Registers the built-in `logging/setLevel` request handler.
|
|
23388
23941
|
*
|
|
@@ -23928,6 +24481,30 @@ var Server = class extends Protocol {
|
|
|
23928
24481
|
function discoverAdvertisedCapabilities(capabilities) {
|
|
23929
24482
|
return { ...capabilities };
|
|
23930
24483
|
}
|
|
24484
|
+
function toolInputElementCount(value, max) {
|
|
24485
|
+
let count = 0;
|
|
24486
|
+
const stack = [value];
|
|
24487
|
+
while (stack.length > 0) {
|
|
24488
|
+
const node = stack.pop();
|
|
24489
|
+
if (node === null || typeof node !== "object") continue;
|
|
24490
|
+
if (Array.isArray(node)) for (const child of node) {
|
|
24491
|
+
if (++count > max) return count;
|
|
24492
|
+
if (child !== null && typeof child === "object") stack.push(child);
|
|
24493
|
+
}
|
|
24494
|
+
else for (const key in node) {
|
|
24495
|
+
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
|
|
24496
|
+
if (++count > max) return count;
|
|
24497
|
+
const child = node[key];
|
|
24498
|
+
if (child !== null && typeof child === "object") stack.push(child);
|
|
24499
|
+
}
|
|
24500
|
+
}
|
|
24501
|
+
return count;
|
|
24502
|
+
}
|
|
24503
|
+
function resolveMaxToolInputElements(value) {
|
|
24504
|
+
if (value === void 0 || value === Infinity) return void 0;
|
|
24505
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 1) throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
|
|
24506
|
+
return value;
|
|
24507
|
+
}
|
|
23931
24508
|
/**
|
|
23932
24509
|
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
|
|
23933
24510
|
* For advanced usage (like sending notifications or setting custom request handlers), use the underlying
|
|
@@ -23946,16 +24523,12 @@ var McpServer = class {
|
|
|
23946
24523
|
* The underlying {@linkcode Server} instance, useful for advanced operations like sending notifications.
|
|
23947
24524
|
*/
|
|
23948
24525
|
server;
|
|
24526
|
+
_maxToolInputElements;
|
|
23949
24527
|
_registeredResources = {};
|
|
23950
24528
|
_registeredResourceTemplates = {};
|
|
23951
24529
|
_registeredTools = {};
|
|
23952
24530
|
_registeredPrompts = {};
|
|
23953
|
-
/**
|
|
23954
|
-
* Per-tool JSON-converted `inputSchema`, memoized so the SEP-2243
|
|
23955
|
-
* registration-time scan and the pre-dispatch validation step share one
|
|
23956
|
-
* conversion instead of paying it twice per request under the
|
|
23957
|
-
* per-request-factory `createMcpHandler` model.
|
|
23958
|
-
*/
|
|
24531
|
+
/** Per-tool JSON-converted `inputSchema`, filled on first use by `toolInputSchemaJson()`. */
|
|
23959
24532
|
_toolInputSchemaJson = {};
|
|
23960
24533
|
/**
|
|
23961
24534
|
* The JSON-serialized `inputSchema` of a registered tool, or `undefined`
|
|
@@ -23980,6 +24553,7 @@ var McpServer = class {
|
|
|
23980
24553
|
}
|
|
23981
24554
|
constructor(serverInfo, options) {
|
|
23982
24555
|
this.server = new Server(serverInfo, options);
|
|
24556
|
+
this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
|
|
23983
24557
|
if (options?.capabilities?.tools) this.setToolRequestHandlers();
|
|
23984
24558
|
if (options?.capabilities?.resources) this.setResourceRequestHandlers();
|
|
23985
24559
|
if (options?.capabilities?.prompts) this.setPromptRequestHandlers();
|
|
@@ -24051,7 +24625,7 @@ var McpServer = class {
|
|
|
24051
24625
|
name,
|
|
24052
24626
|
title: tool.title,
|
|
24053
24627
|
description: tool.description,
|
|
24054
|
-
inputSchema: tool.inputSchema ?
|
|
24628
|
+
inputSchema: tool.inputSchema ? convertListedInputSchema(name, tool.inputSchema) : EMPTY_OBJECT_JSON_SCHEMA,
|
|
24055
24629
|
annotations: tool.annotations,
|
|
24056
24630
|
icons: tool.icons,
|
|
24057
24631
|
execution: tool.execution,
|
|
@@ -24096,6 +24670,7 @@ var McpServer = class {
|
|
|
24096
24670
|
* Validates tool input arguments against the tool's input schema.
|
|
24097
24671
|
*/
|
|
24098
24672
|
async validateToolInput(tool, args, toolName) {
|
|
24673
|
+
if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
|
|
24099
24674
|
if (!tool.inputSchema) return;
|
|
24100
24675
|
const parseResult = await validateStandardSchema(tool.inputSchema, args ?? {});
|
|
24101
24676
|
if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`);
|
|
@@ -24369,19 +24944,19 @@ var McpServer = class {
|
|
|
24369
24944
|
}
|
|
24370
24945
|
_createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
|
|
24371
24946
|
validateAndWarnToolName(name);
|
|
24372
|
-
if (inputSchema !== void 0) try {
|
|
24373
|
-
const json = standardSchemaToJsonSchema(inputSchema, "input");
|
|
24374
|
-
this._toolInputSchemaJson[name] = json;
|
|
24375
|
-
const scan = scanXMcpHeaderDeclarations(json);
|
|
24376
|
-
if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
|
|
24377
|
-
} catch {}
|
|
24378
24947
|
let currentHandler = handler;
|
|
24948
|
+
let outputSchemaJson;
|
|
24379
24949
|
const registeredTool = {
|
|
24380
24950
|
title,
|
|
24381
24951
|
description,
|
|
24382
24952
|
inputSchema,
|
|
24383
24953
|
outputSchema,
|
|
24384
|
-
outputSchemaJson
|
|
24954
|
+
get outputSchemaJson() {
|
|
24955
|
+
return outputSchemaJson ??= convertOutputSchemaJson(registeredTool.outputSchema);
|
|
24956
|
+
},
|
|
24957
|
+
set outputSchemaJson(value) {
|
|
24958
|
+
outputSchemaJson = value;
|
|
24959
|
+
},
|
|
24385
24960
|
annotations,
|
|
24386
24961
|
icons,
|
|
24387
24962
|
execution,
|
|
@@ -24542,6 +25117,13 @@ const EMPTY_OBJECT_JSON_SCHEMA = {
|
|
|
24542
25117
|
type: "object",
|
|
24543
25118
|
properties: {}
|
|
24544
25119
|
};
|
|
25120
|
+
/** Converts a tool's `inputSchema` for `tools/list` and warns on an invalid SEP-2243 `x-mcp-header` declaration. */
|
|
25121
|
+
function convertListedInputSchema(name, inputSchema) {
|
|
25122
|
+
const json = standardSchemaToJsonSchema(inputSchema, "input");
|
|
25123
|
+
const scan = scanXMcpHeaderDeclarations(json);
|
|
25124
|
+
if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
|
|
25125
|
+
return json;
|
|
25126
|
+
}
|
|
24545
25127
|
/**
|
|
24546
25128
|
* Convert a registered `outputSchema` to JSON Schema, memoised on {@link RegisteredTool.outputSchemaJson}
|
|
24547
25129
|
* so `tools/call` passes the SAME advertised schema to the wire codec's `projectCallToolResult` that
|
|
@@ -24564,7 +25146,7 @@ function createPromptHandler(name, argsSchema, callback) {
|
|
|
24564
25146
|
if (argsSchema) {
|
|
24565
25147
|
const typedCallback = callback;
|
|
24566
25148
|
return async (args, ctx) => {
|
|
24567
|
-
const parseResult = await validateStandardSchema(argsSchema, args);
|
|
25149
|
+
const parseResult = await validateStandardSchema(argsSchema, args ?? {});
|
|
24568
25150
|
if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
|
|
24569
25151
|
return typedCallback(parseResult.data, ctx);
|
|
24570
25152
|
};
|
|
@@ -24601,7 +25183,7 @@ function unwrapOptionalSchema(schema) {
|
|
|
24601
25183
|
return schema.def?.innerType ?? schema;
|
|
24602
25184
|
}
|
|
24603
25185
|
//#endregion
|
|
24604
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.
|
|
25186
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/index.mjs
|
|
24605
25187
|
/** Default upper bound, in bytes, on a request body read by the HTTP entry points (4 MiB). */
|
|
24606
25188
|
const DEFAULT_MAX_REQUEST_BODY_SIZE = 4194304;
|
|
24607
25189
|
/** Upper bound on the number of messages accepted in one JSON-RPC batch array. */
|
|
@@ -24690,6 +25272,10 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
|
|
|
24690
25272
|
* @example Hono.js
|
|
24691
25273
|
* ```ts source="./streamableHttp.examples.ts#WebStandardStreamableHTTPServerTransport_hono"
|
|
24692
25274
|
* app.all('/mcp', async c => {
|
|
25275
|
+
* // Stateless example: create a server and a transport per request.
|
|
25276
|
+
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
25277
|
+
* const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
25278
|
+
* await server.connect(transport);
|
|
24693
25279
|
* return transport.handleRequest(c.req.raw);
|
|
24694
25280
|
* });
|
|
24695
25281
|
* ```
|
|
@@ -24698,6 +25284,10 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
|
|
|
24698
25284
|
* ```ts source="./streamableHttp.examples.ts#WebStandardStreamableHTTPServerTransport_workers"
|
|
24699
25285
|
* const worker = {
|
|
24700
25286
|
* async fetch(request: Request): Promise<Response> {
|
|
25287
|
+
* // Stateless example: create a server and a transport per request.
|
|
25288
|
+
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
25289
|
+
* const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
25290
|
+
* await server.connect(transport);
|
|
24701
25291
|
* return transport.handleRequest(request);
|
|
24702
25292
|
* }
|
|
24703
25293
|
* };
|
|
@@ -24706,6 +25296,7 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
|
|
|
24706
25296
|
var WebStandardStreamableHTTPServerTransport = class {
|
|
24707
25297
|
sessionIdGenerator;
|
|
24708
25298
|
_started = false;
|
|
25299
|
+
_hasHandledRequest = false;
|
|
24709
25300
|
_closed = false;
|
|
24710
25301
|
_streamMapping = /* @__PURE__ */ new Map();
|
|
24711
25302
|
_requestToStreamMapping = /* @__PURE__ */ new Map();
|
|
@@ -24827,6 +25418,8 @@ var WebStandardStreamableHTTPServerTransport = class {
|
|
|
24827
25418
|
*/
|
|
24828
25419
|
async handleRequest(req, options) {
|
|
24829
25420
|
if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
25421
|
+
if (!this.sessionIdGenerator && this._hasHandledRequest) throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
25422
|
+
this._hasHandledRequest = true;
|
|
24830
25423
|
const validationError = this.validateRequestHeaders(req);
|
|
24831
25424
|
if (validationError) return validationError;
|
|
24832
25425
|
switch (req.method) {
|
|
@@ -25342,339 +25935,6 @@ var WebStandardStreamableHTTPServerTransport = class {
|
|
|
25342
25935
|
}
|
|
25343
25936
|
};
|
|
25344
25937
|
//#endregion
|
|
25345
|
-
//#region src/constants.ts
|
|
25346
|
-
const positiveIntEnv = (name, fallback) => {
|
|
25347
|
-
const raw = readEnv(name).value;
|
|
25348
|
-
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
25349
|
-
const parsed = Number(raw);
|
|
25350
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
25351
|
-
};
|
|
25352
|
-
const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
|
|
25353
|
-
const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
|
|
25354
|
-
const DEFAULT_CALLBACK_PORT = 27439;
|
|
25355
|
-
const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
|
|
25356
|
-
const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
|
|
25357
|
-
const STDIO_MAX_MESSAGE_BYTES = 10485760;
|
|
25358
|
-
const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
|
|
25359
|
-
const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
|
|
25360
|
-
const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
|
|
25361
|
-
const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
|
|
25362
|
-
const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
|
|
25363
|
-
const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
|
|
25364
|
-
const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
|
|
25365
|
-
const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
|
|
25366
|
-
const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
|
|
25367
|
-
const getOAuthUrls = (subdomain) => ({
|
|
25368
|
-
authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
|
|
25369
|
-
tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
|
|
25370
|
-
});
|
|
25371
|
-
//#endregion
|
|
25372
|
-
//#region src/client/retry.ts
|
|
25373
|
-
const MAX_ATTEMPTS = 3;
|
|
25374
|
-
const BASE_DELAY_MS = 250;
|
|
25375
|
-
const MAX_DELAY_MS = 4e3;
|
|
25376
|
-
const REQUEST_TIMEOUT_MS = 3e4;
|
|
25377
|
-
const TRANSFER_TIMEOUT_MS = 12e4;
|
|
25378
|
-
/**
|
|
25379
|
-
* A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
|
|
25380
|
-
* is numeric, not a syscall string, so it classifies as `unknown`: a GET is
|
|
25381
|
-
* retried, a write is not — the request may have arrived before the deadline.
|
|
25382
|
-
*/
|
|
25383
|
-
const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
|
|
25384
|
-
const MAX_RETRY_AFTER_MS = 5e3;
|
|
25385
|
-
const POLICIES = {
|
|
25386
|
-
GET: {
|
|
25387
|
-
network: "any",
|
|
25388
|
-
serverErrors: true
|
|
25389
|
-
},
|
|
25390
|
-
DELETE: {
|
|
25391
|
-
network: "pre-send",
|
|
25392
|
-
serverErrors: false
|
|
25393
|
-
},
|
|
25394
|
-
POST: {
|
|
25395
|
-
network: "pre-send",
|
|
25396
|
-
serverErrors: false
|
|
25397
|
-
},
|
|
25398
|
-
PUT: {
|
|
25399
|
-
network: "pre-send",
|
|
25400
|
-
serverErrors: false
|
|
25401
|
-
}
|
|
25402
|
-
};
|
|
25403
|
-
const DELAY_SECONDS = /^\d+$/;
|
|
25404
|
-
const HTTP_DATE_START = /^[A-Za-z]/;
|
|
25405
|
-
const NON_ASCII = /[^ -~]/g;
|
|
25406
|
-
const TOKEN_SEGMENT = /\/token\/[^/]+/;
|
|
25407
|
-
const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
|
|
25408
|
-
const PRE_SEND_CODES = /* @__PURE__ */ new Set([
|
|
25409
|
-
"ENOTFOUND",
|
|
25410
|
-
"EAI_AGAIN",
|
|
25411
|
-
"ECONNREFUSED",
|
|
25412
|
-
"UND_ERR_CONNECT_TIMEOUT"
|
|
25413
|
-
]);
|
|
25414
|
-
const defaultRetryDeps = {
|
|
25415
|
-
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
25416
|
-
random: Math.random
|
|
25417
|
-
};
|
|
25418
|
-
/** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
|
|
25419
|
-
const toAscii = (text) => text.replace(NON_ASCII, "?");
|
|
25420
|
-
/**
|
|
25421
|
-
* Identifies the request without leaking a credential: uploads carry an upload
|
|
25422
|
-
* token in the query string, and an attachment `content_url` carries a download
|
|
25423
|
-
* token as a path segment (`/attachments/token/<token>/`).
|
|
25424
|
-
*/
|
|
25425
|
-
const describeTarget = (url) => {
|
|
25426
|
-
const { origin, pathname } = new URL(url);
|
|
25427
|
-
return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
|
|
25428
|
-
};
|
|
25429
|
-
/** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
|
|
25430
|
-
const errorCode = (err) => {
|
|
25431
|
-
let current = err;
|
|
25432
|
-
for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
|
|
25433
|
-
const { code, cause } = current;
|
|
25434
|
-
if (typeof code === "string") return code;
|
|
25435
|
-
current = cause;
|
|
25436
|
-
}
|
|
25437
|
-
};
|
|
25438
|
-
const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
|
|
25439
|
-
const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
|
|
25440
|
-
/**
|
|
25441
|
-
* The client refuses to replay a write that may have landed — but the caller is
|
|
25442
|
-
* an LLM whose reflex on a failed tool call is to try again, which would undo
|
|
25443
|
-
* that. So a failed write says whether it may have taken effect. ASCII only,
|
|
25444
|
-
* same rule as the rest of the message.
|
|
25445
|
-
*/
|
|
25446
|
-
const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
|
|
25447
|
-
const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
|
|
25448
|
-
const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
|
|
25449
|
-
/** Only a write can be duplicated by a replay, so only a write carries a note. */
|
|
25450
|
-
const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
|
|
25451
|
-
/**
|
|
25452
|
-
* A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
|
|
25453
|
-
* carrying credentials by printing the whole thing, query string included — which
|
|
25454
|
-
* would smuggle back exactly what `describeTarget` drops. Same treatment for both,
|
|
25455
|
-
* so one function owns what a URL may look like in our output.
|
|
25456
|
-
*/
|
|
25457
|
-
const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
|
|
25458
|
-
try {
|
|
25459
|
-
return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
|
|
25460
|
-
} catch {
|
|
25461
|
-
return "[url]";
|
|
25462
|
-
}
|
|
25463
|
-
});
|
|
25464
|
-
const failureDetail = (cause) => {
|
|
25465
|
-
if (!(cause instanceof Error)) return redactUrls(String(cause));
|
|
25466
|
-
const message = redactUrls(cause.message);
|
|
25467
|
-
const code = errorCode(cause);
|
|
25468
|
-
if (code !== void 0) return `${code}: ${message}`;
|
|
25469
|
-
return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
|
|
25470
|
-
};
|
|
25471
|
-
const networkErrorMessage = (method, target, attempts, cause) => {
|
|
25472
|
-
const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
|
|
25473
|
-
const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
|
|
25474
|
-
return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
|
|
25475
|
-
};
|
|
25476
|
-
const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
|
|
25477
|
-
name: "ZendeskNetworkError",
|
|
25478
|
-
method,
|
|
25479
|
-
target,
|
|
25480
|
-
attempts
|
|
25481
|
-
});
|
|
25482
|
-
/** Exponential backoff with equal jitter: half the window fixed, half random. */
|
|
25483
|
-
const computeBackoffMs = (attempt, random) => {
|
|
25484
|
-
const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
|
|
25485
|
-
return Math.round(window / 2 + window * random() / 2);
|
|
25486
|
-
};
|
|
25487
|
-
/** `Retry-After` in ms — delay-seconds or HTTP-date. */
|
|
25488
|
-
const parseRetryAfter = (header, now = Date.now()) => {
|
|
25489
|
-
if (header === null) return void 0;
|
|
25490
|
-
const value = header.trim();
|
|
25491
|
-
if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
|
|
25492
|
-
if (!HTTP_DATE_START.test(value)) return void 0;
|
|
25493
|
-
const date = Date.parse(value);
|
|
25494
|
-
if (Number.isNaN(date)) return void 0;
|
|
25495
|
-
return Math.max(0, date - now);
|
|
25496
|
-
};
|
|
25497
|
-
/**
|
|
25498
|
-
* How long to wait before replaying this response, or undefined to accept it.
|
|
25499
|
-
* `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
|
|
25500
|
-
* during maintenance as well as on a 429 — and a value past the cap means the
|
|
25501
|
-
* response is surfaced rather than parking the call for that long.
|
|
25502
|
-
*/
|
|
25503
|
-
const retryDelayFor = (response, policy, attempt, random) => {
|
|
25504
|
-
if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
|
|
25505
|
-
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
25506
|
-
if (retryAfter === void 0) return computeBackoffMs(attempt, random);
|
|
25507
|
-
return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
|
|
25508
|
-
};
|
|
25509
|
-
/**
|
|
25510
|
-
* Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
|
|
25511
|
-
* attempt budget. Returns the last response for the caller to inspect (a
|
|
25512
|
-
* non-ok status is still the caller's to turn into a `ZendeskApiError`), and
|
|
25513
|
-
* throws `ZendeskNetworkError` when no response was ever obtained.
|
|
25514
|
-
*/
|
|
25515
|
-
const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
|
|
25516
|
-
const policy = POLICIES[method];
|
|
25517
|
-
for (let tries = 1;; tries += 1) {
|
|
25518
|
-
const last = tries >= MAX_ATTEMPTS;
|
|
25519
|
-
let response;
|
|
25520
|
-
try {
|
|
25521
|
-
response = await attempt();
|
|
25522
|
-
} catch (err) {
|
|
25523
|
-
const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
|
|
25524
|
-
if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
|
|
25525
|
-
await deps.sleep(computeBackoffMs(tries, deps.random));
|
|
25526
|
-
continue;
|
|
25527
|
-
}
|
|
25528
|
-
if (last) return response;
|
|
25529
|
-
const delayMs = retryDelayFor(response, policy, tries, deps.random);
|
|
25530
|
-
if (delayMs === void 0) return response;
|
|
25531
|
-
await response.text().catch(() => void 0);
|
|
25532
|
-
await deps.sleep(delayMs);
|
|
25533
|
-
}
|
|
25534
|
-
};
|
|
25535
|
-
//#endregion
|
|
25536
|
-
//#region src/client/zendesk-api.ts
|
|
25537
|
-
var ZendeskApiError = class ZendeskApiError extends Error {
|
|
25538
|
-
status;
|
|
25539
|
-
statusText;
|
|
25540
|
-
body;
|
|
25541
|
-
method;
|
|
25542
|
-
constructor(status, statusText, body, method) {
|
|
25543
|
-
super(ZendeskApiError.buildMessage(status, statusText, body, method));
|
|
25544
|
-
this.status = status;
|
|
25545
|
-
this.statusText = statusText;
|
|
25546
|
-
this.body = body;
|
|
25547
|
-
this.method = method;
|
|
25548
|
-
this.name = "ZendeskApiError";
|
|
25549
|
-
}
|
|
25550
|
-
static buildMessage(status, statusText, body, method) {
|
|
25551
|
-
switch (status) {
|
|
25552
|
-
case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
|
|
25553
|
-
case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
|
|
25554
|
-
case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
|
|
25555
|
-
case 422: return `Validation error: ${body}`;
|
|
25556
|
-
case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
|
|
25557
|
-
default: {
|
|
25558
|
-
const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
|
|
25559
|
-
return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
|
|
25560
|
-
}
|
|
25561
|
-
}
|
|
25562
|
-
}
|
|
25563
|
-
};
|
|
25564
|
-
const buildAuthHeader = (token) => `Bearer ${token}`;
|
|
25565
|
-
const buildUrl = (base, path, params) => {
|
|
25566
|
-
const url = new URL(`${base}${path}`);
|
|
25567
|
-
if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
|
|
25568
|
-
return url.toString();
|
|
25569
|
-
};
|
|
25570
|
-
const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
|
|
25571
|
-
...init,
|
|
25572
|
-
method,
|
|
25573
|
-
signal: deadlineSignal(timeoutMs)
|
|
25574
|
-
}), method, describeTarget(url));
|
|
25575
|
-
const executeRequest = async (url, token, options = {}) => {
|
|
25576
|
-
const { method = "GET", body } = options;
|
|
25577
|
-
const headers = {
|
|
25578
|
-
Authorization: buildAuthHeader(token),
|
|
25579
|
-
Accept: "application/json"
|
|
25580
|
-
};
|
|
25581
|
-
if (body) headers["Content-Type"] = "application/json";
|
|
25582
|
-
const init = { headers };
|
|
25583
|
-
if (body) init.body = JSON.stringify(body);
|
|
25584
|
-
const response = await performFetch(method, url, init);
|
|
25585
|
-
if (!response.ok) {
|
|
25586
|
-
const responseBody = await response.text();
|
|
25587
|
-
throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
|
|
25588
|
-
}
|
|
25589
|
-
if (response.status === 204) return {};
|
|
25590
|
-
return response.json();
|
|
25591
|
-
};
|
|
25592
|
-
const zendeskGet = (subdomain, token, path, params) => {
|
|
25593
|
-
const url = buildUrl(getBaseUrl(subdomain), path, params);
|
|
25594
|
-
return executeRequest(url, token);
|
|
25595
|
-
};
|
|
25596
|
-
const zendeskPost = (subdomain, token, path, body) => {
|
|
25597
|
-
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
25598
|
-
return executeRequest(url, token, {
|
|
25599
|
-
method: "POST",
|
|
25600
|
-
body
|
|
25601
|
-
});
|
|
25602
|
-
};
|
|
25603
|
-
const zendeskPut = (subdomain, token, path, body) => {
|
|
25604
|
-
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
25605
|
-
return executeRequest(url, token, {
|
|
25606
|
-
method: "PUT",
|
|
25607
|
-
body
|
|
25608
|
-
});
|
|
25609
|
-
};
|
|
25610
|
-
const helpCenterGet = (subdomain, token, path, params) => {
|
|
25611
|
-
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
|
|
25612
|
-
return executeRequest(url, token);
|
|
25613
|
-
};
|
|
25614
|
-
const helpCenterPost = (subdomain, token, path, body) => {
|
|
25615
|
-
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
25616
|
-
return executeRequest(url, token, {
|
|
25617
|
-
method: "POST",
|
|
25618
|
-
body
|
|
25619
|
-
});
|
|
25620
|
-
};
|
|
25621
|
-
const helpCenterPut = (subdomain, token, path, body) => {
|
|
25622
|
-
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
25623
|
-
return executeRequest(url, token, {
|
|
25624
|
-
method: "PUT",
|
|
25625
|
-
body
|
|
25626
|
-
});
|
|
25627
|
-
};
|
|
25628
|
-
const helpCenterDelete = (subdomain, token, path) => {
|
|
25629
|
-
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
25630
|
-
return executeRequest(url, token, { method: "DELETE" });
|
|
25631
|
-
};
|
|
25632
|
-
const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
|
|
25633
|
-
const expectedHost = `${subdomain}.zendesk.com`;
|
|
25634
|
-
const headers = {};
|
|
25635
|
-
if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
|
|
25636
|
-
const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
|
|
25637
|
-
if (!response.ok) {
|
|
25638
|
-
const body = await response.text();
|
|
25639
|
-
throw new ZendeskApiError(response.status, response.statusText, body, "GET");
|
|
25640
|
-
}
|
|
25641
|
-
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
25642
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
25643
|
-
return {
|
|
25644
|
-
data: Buffer.from(arrayBuffer),
|
|
25645
|
-
contentType
|
|
25646
|
-
};
|
|
25647
|
-
};
|
|
25648
|
-
const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
|
|
25649
|
-
const params = { filename };
|
|
25650
|
-
if (uploadToken) params["token"] = uploadToken;
|
|
25651
|
-
const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
|
|
25652
|
-
const response = await performFetch("POST", url, {
|
|
25653
|
-
headers: {
|
|
25654
|
-
Authorization: buildAuthHeader(token),
|
|
25655
|
-
"Content-Type": contentType
|
|
25656
|
-
},
|
|
25657
|
-
body: data
|
|
25658
|
-
}, TRANSFER_TIMEOUT_MS);
|
|
25659
|
-
if (!response.ok) {
|
|
25660
|
-
const responseBody = await response.text();
|
|
25661
|
-
throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
|
|
25662
|
-
}
|
|
25663
|
-
return response.json();
|
|
25664
|
-
};
|
|
25665
|
-
const helpCenterUpload = async (subdomain, token, path, formData) => {
|
|
25666
|
-
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
25667
|
-
const response = await performFetch("POST", url, {
|
|
25668
|
-
headers: { Authorization: buildAuthHeader(token) },
|
|
25669
|
-
body: formData
|
|
25670
|
-
}, TRANSFER_TIMEOUT_MS);
|
|
25671
|
-
if (!response.ok) {
|
|
25672
|
-
const responseBody = await response.text();
|
|
25673
|
-
throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
|
|
25674
|
-
}
|
|
25675
|
-
return response.json();
|
|
25676
|
-
};
|
|
25677
|
-
//#endregion
|
|
25678
25938
|
//#region node_modules/.pnpm/cheerio@1.2.0/node_modules/cheerio/dist/esm/options.js
|
|
25679
25939
|
const defaultOpts$2 = { _useHtmlParser2: false };
|
|
25680
25940
|
/**
|
|
@@ -83005,7 +83265,7 @@ function escapeStringRegexp(string) {
|
|
|
83005
83265
|
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
|
|
83006
83266
|
}
|
|
83007
83267
|
//#endregion
|
|
83008
|
-
//#region node_modules/.pnpm/mdast-util-find-and-replace@3.0.
|
|
83268
|
+
//#region node_modules/.pnpm/mdast-util-find-and-replace@3.0.3/node_modules/mdast-util-find-and-replace/lib/index.js
|
|
83009
83269
|
/**
|
|
83010
83270
|
* @import {Nodes, Parents, PhrasingContent, Root, Text} from 'mdast'
|
|
83011
83271
|
* @import {BuildVisitor, Test, VisitorResult} from 'unist-util-visit-parents'
|
|
@@ -83082,7 +83342,11 @@ function findAndReplace(tree, list, options) {
|
|
|
83082
83342
|
const pairs = toPairs(list);
|
|
83083
83343
|
let pairIndex = -1;
|
|
83084
83344
|
while (++pairIndex < pairs.length) visitParents(tree, "text", visitor);
|
|
83085
|
-
/**
|
|
83345
|
+
/**
|
|
83346
|
+
* Visit a text node, and handle it if it is not in an ignored parent.
|
|
83347
|
+
*
|
|
83348
|
+
* @type {BuildVisitor<Root, 'text'>}
|
|
83349
|
+
*/
|
|
83086
83350
|
function visitor(node, parents) {
|
|
83087
83351
|
let index = -1;
|
|
83088
83352
|
/** @type {Parents | undefined} */
|
|
@@ -83100,11 +83364,11 @@ function findAndReplace(tree, list, options) {
|
|
|
83100
83364
|
* Handle a text node which is not in an ignored parent.
|
|
83101
83365
|
*
|
|
83102
83366
|
* @param {Text} node
|
|
83103
|
-
* Text node.
|
|
83367
|
+
* Text node to search in.
|
|
83104
83368
|
* @param {Array<Parents>} parents
|
|
83105
|
-
*
|
|
83369
|
+
* Ancestors of `node`.
|
|
83106
83370
|
* @returns {VisitorResult}
|
|
83107
|
-
*
|
|
83371
|
+
* Where to continue visiting.
|
|
83108
83372
|
*/
|
|
83109
83373
|
function handler(node, parents) {
|
|
83110
83374
|
const parent = parents[parents.length - 1];
|
|
@@ -83132,12 +83396,12 @@ function findAndReplace(tree, list, options) {
|
|
|
83132
83396
|
} : void 0;
|
|
83133
83397
|
if (value === false) find.lastIndex = position + 1;
|
|
83134
83398
|
else {
|
|
83135
|
-
if (start !== position)
|
|
83399
|
+
if (start !== position) add({
|
|
83136
83400
|
type: "text",
|
|
83137
83401
|
value: node.value.slice(start, position)
|
|
83138
83402
|
});
|
|
83139
|
-
if (Array.isArray(value))
|
|
83140
|
-
else if (value)
|
|
83403
|
+
if (Array.isArray(value)) for (const child of value) add(child);
|
|
83404
|
+
else if (value) add(value);
|
|
83141
83405
|
start = position + match[0].length;
|
|
83142
83406
|
change = true;
|
|
83143
83407
|
}
|
|
@@ -83145,13 +83409,26 @@ function findAndReplace(tree, list, options) {
|
|
|
83145
83409
|
match = find.exec(node.value);
|
|
83146
83410
|
}
|
|
83147
83411
|
if (change) {
|
|
83148
|
-
if (start < node.value.length)
|
|
83412
|
+
if (start < node.value.length) add({
|
|
83149
83413
|
type: "text",
|
|
83150
83414
|
value: node.value.slice(start)
|
|
83151
83415
|
});
|
|
83152
83416
|
parent.children.splice(index, 1, ...nodes);
|
|
83153
83417
|
} else nodes = [node];
|
|
83154
83418
|
return index + nodes.length;
|
|
83419
|
+
/**
|
|
83420
|
+
* Add a node.
|
|
83421
|
+
*
|
|
83422
|
+
* @param {PhrasingContent} child
|
|
83423
|
+
* Node to add.
|
|
83424
|
+
* @returns {undefined}
|
|
83425
|
+
* Nothing.
|
|
83426
|
+
*/
|
|
83427
|
+
function add(child) {
|
|
83428
|
+
const previous = nodes[nodes.length - 1];
|
|
83429
|
+
if (previous && previous.type === "text" && child.type === "text") previous.value += child.value;
|
|
83430
|
+
else nodes.push(child);
|
|
83431
|
+
}
|
|
83155
83432
|
}
|
|
83156
83433
|
}
|
|
83157
83434
|
/**
|
|
@@ -83163,9 +83440,9 @@ function findAndReplace(tree, list, options) {
|
|
|
83163
83440
|
* Clean pairs.
|
|
83164
83441
|
*/
|
|
83165
83442
|
function toPairs(tupleOrList) {
|
|
83443
|
+
if (!Array.isArray(tupleOrList)) throw new TypeError("Expected find and replace tuple or list of tuples");
|
|
83166
83444
|
/** @type {Pairs} */
|
|
83167
83445
|
const result = [];
|
|
83168
|
-
if (!Array.isArray(tupleOrList)) throw new TypeError("Expected find and replace tuple or list of tuples");
|
|
83169
83446
|
/** @type {FindAndReplaceList} */
|
|
83170
83447
|
const list = !tupleOrList[0] || Array.isArray(tupleOrList[0]) ? tupleOrList : [tupleOrList];
|
|
83171
83448
|
let index = -1;
|
|
@@ -83179,9 +83456,9 @@ function toPairs(tupleOrList) {
|
|
|
83179
83456
|
* Turn a find into an expression.
|
|
83180
83457
|
*
|
|
83181
83458
|
* @param {Find} find
|
|
83182
|
-
*
|
|
83459
|
+
* Pattern to find.
|
|
83183
83460
|
* @returns {RegExp}
|
|
83184
|
-
*
|
|
83461
|
+
* Global expression.
|
|
83185
83462
|
*/
|
|
83186
83463
|
function toExpression(find) {
|
|
83187
83464
|
return typeof find === "string" ? new RegExp(escapeStringRegexp(find), "g") : find;
|
|
@@ -83190,9 +83467,9 @@ function toExpression(find) {
|
|
|
83190
83467
|
* Turn a replace into a function.
|
|
83191
83468
|
*
|
|
83192
83469
|
* @param {Replace} replace
|
|
83193
|
-
*
|
|
83470
|
+
* Thing to replace with.
|
|
83194
83471
|
* @returns {ReplaceFunction}
|
|
83195
|
-
* Function.
|
|
83472
|
+
* Function that returns that thing.
|
|
83196
83473
|
*/
|
|
83197
83474
|
function toFunction(replace) {
|
|
83198
83475
|
return typeof replace === "function" ? replace : function() {
|
|
@@ -84056,7 +84333,7 @@ function toAlignment(value) {
|
|
|
84056
84333
|
return code === 67 || code === 99 ? 99 : code === 76 || code === 108 ? 108 : code === 82 || code === 114 ? 114 : 0;
|
|
84057
84334
|
}
|
|
84058
84335
|
//#endregion
|
|
84059
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84336
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/configure.js
|
|
84060
84337
|
/**
|
|
84061
84338
|
* @import {Options, State} from './types.js'
|
|
84062
84339
|
*/
|
|
@@ -84123,7 +84400,7 @@ function map$2(left, right) {
|
|
|
84123
84400
|
if (right) Object.assign(left, right);
|
|
84124
84401
|
}
|
|
84125
84402
|
//#endregion
|
|
84126
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84403
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js
|
|
84127
84404
|
/**
|
|
84128
84405
|
* @import {Blockquote, Parents} from 'mdast'
|
|
84129
84406
|
* @import {Info, Map, State} from 'mdast-util-to-markdown'
|
|
@@ -84160,7 +84437,7 @@ function map$1(line, _, blank) {
|
|
|
84160
84437
|
return ">" + (blank ? "" : " ") + line;
|
|
84161
84438
|
}
|
|
84162
84439
|
//#endregion
|
|
84163
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84440
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js
|
|
84164
84441
|
/**
|
|
84165
84442
|
* @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
|
|
84166
84443
|
*/
|
|
@@ -84197,7 +84474,7 @@ function listInScope(stack, list, none) {
|
|
|
84197
84474
|
return false;
|
|
84198
84475
|
}
|
|
84199
84476
|
//#endregion
|
|
84200
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84477
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/break.js
|
|
84201
84478
|
/**
|
|
84202
84479
|
* @import {Break, Parents} from 'mdast'
|
|
84203
84480
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
@@ -84253,7 +84530,7 @@ function longestStreak(value, substring) {
|
|
|
84253
84530
|
return max;
|
|
84254
84531
|
}
|
|
84255
84532
|
//#endregion
|
|
84256
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84533
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js
|
|
84257
84534
|
/**
|
|
84258
84535
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84259
84536
|
* @import {Code} from 'mdast'
|
|
@@ -84272,7 +84549,7 @@ function formatCodeAsIndented(node, state) {
|
|
|
84272
84549
|
return Boolean(state.options.fences === false && node.value && !node.lang && /[^\n\r ]/.test(node.value) && !/^[\t ]*(?:[\n\r]|$)|(?:^|[\n\r])[\t ]*$/.test(node.value));
|
|
84273
84550
|
}
|
|
84274
84551
|
//#endregion
|
|
84275
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84552
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-fence.js
|
|
84276
84553
|
/**
|
|
84277
84554
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
84278
84555
|
*/
|
|
@@ -84290,7 +84567,7 @@ function checkFence(state) {
|
|
|
84290
84567
|
return marker;
|
|
84291
84568
|
}
|
|
84292
84569
|
//#endregion
|
|
84293
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84570
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/code.js
|
|
84294
84571
|
/**
|
|
84295
84572
|
* @import {Info, Map, State} from 'mdast-util-to-markdown'
|
|
84296
84573
|
* @import {Code, Parents} from 'mdast'
|
|
@@ -84359,7 +84636,7 @@ function map(line, _, blank) {
|
|
|
84359
84636
|
return (blank ? "" : " ") + line;
|
|
84360
84637
|
}
|
|
84361
84638
|
//#endregion
|
|
84362
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84639
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-quote.js
|
|
84363
84640
|
/**
|
|
84364
84641
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
84365
84642
|
*/
|
|
@@ -84377,7 +84654,7 @@ function checkQuote(state) {
|
|
|
84377
84654
|
return marker;
|
|
84378
84655
|
}
|
|
84379
84656
|
//#endregion
|
|
84380
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84657
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/definition.js
|
|
84381
84658
|
/**
|
|
84382
84659
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84383
84660
|
* @import {Definition, Parents} from 'mdast'
|
|
@@ -84443,7 +84720,7 @@ function definition$1(node, _, state, info) {
|
|
|
84443
84720
|
return value;
|
|
84444
84721
|
}
|
|
84445
84722
|
//#endregion
|
|
84446
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84723
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js
|
|
84447
84724
|
/**
|
|
84448
84725
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
84449
84726
|
*/
|
|
@@ -84461,7 +84738,7 @@ function checkEmphasis(state) {
|
|
|
84461
84738
|
return marker;
|
|
84462
84739
|
}
|
|
84463
84740
|
//#endregion
|
|
84464
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84741
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js
|
|
84465
84742
|
/**
|
|
84466
84743
|
* @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
|
|
84467
84744
|
* @import {Emphasis, Parents} from 'mdast'
|
|
@@ -84526,7 +84803,7 @@ function peek$1(_, _1, state) {
|
|
|
84526
84803
|
return state.options.emphasis || "*";
|
|
84527
84804
|
}
|
|
84528
84805
|
//#endregion
|
|
84529
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84806
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js
|
|
84530
84807
|
/**
|
|
84531
84808
|
* Encode a code point as a character reference.
|
|
84532
84809
|
*
|
|
@@ -84539,7 +84816,7 @@ function encodeCharacterReference(code) {
|
|
|
84539
84816
|
return "&#x" + code.toString(16).toUpperCase() + ";";
|
|
84540
84817
|
}
|
|
84541
84818
|
//#endregion
|
|
84542
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84819
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js
|
|
84543
84820
|
/**
|
|
84544
84821
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84545
84822
|
* @import {Heading} from 'mdast'
|
|
@@ -84565,7 +84842,7 @@ function formatHeadingAsSetext(node, state) {
|
|
|
84565
84842
|
return Boolean((!node.depth || node.depth < 3) && toString(node) && (state.options.setext || literalWithBreak));
|
|
84566
84843
|
}
|
|
84567
84844
|
//#endregion
|
|
84568
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84845
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/heading.js
|
|
84569
84846
|
/**
|
|
84570
84847
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84571
84848
|
* @import {Heading, Parents} from 'mdast'
|
|
@@ -84617,7 +84894,7 @@ function heading$1(node, _, state, info) {
|
|
|
84617
84894
|
return value;
|
|
84618
84895
|
}
|
|
84619
84896
|
//#endregion
|
|
84620
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84897
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/html.js
|
|
84621
84898
|
/**
|
|
84622
84899
|
* @import {Html} from 'mdast'
|
|
84623
84900
|
*/
|
|
@@ -84643,7 +84920,7 @@ function htmlPeek() {
|
|
|
84643
84920
|
return "<";
|
|
84644
84921
|
}
|
|
84645
84922
|
//#endregion
|
|
84646
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84923
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image.js
|
|
84647
84924
|
/**
|
|
84648
84925
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84649
84926
|
* @import {Image, Parents} from 'mdast'
|
|
@@ -84720,7 +84997,7 @@ function imagePeek() {
|
|
|
84720
84997
|
return "!";
|
|
84721
84998
|
}
|
|
84722
84999
|
//#endregion
|
|
84723
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85000
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js
|
|
84724
85001
|
/**
|
|
84725
85002
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84726
85003
|
* @import {ImageReference, Parents} from 'mdast'
|
|
@@ -84781,7 +85058,7 @@ function imageReferencePeek() {
|
|
|
84781
85058
|
return "!";
|
|
84782
85059
|
}
|
|
84783
85060
|
//#endregion
|
|
84784
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85061
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js
|
|
84785
85062
|
/**
|
|
84786
85063
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84787
85064
|
* @import {InlineCode, Parents} from 'mdast'
|
|
@@ -84829,7 +85106,7 @@ function inlineCodePeek() {
|
|
|
84829
85106
|
return "`";
|
|
84830
85107
|
}
|
|
84831
85108
|
//#endregion
|
|
84832
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85109
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js
|
|
84833
85110
|
/**
|
|
84834
85111
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84835
85112
|
* @import {Link} from 'mdast'
|
|
@@ -84849,7 +85126,7 @@ function formatLinkAsAutolink(node, state) {
|
|
|
84849
85126
|
return Boolean(!state.options.resourceLink && node.url && !node.title && node.children && node.children.length === 1 && node.children[0].type === "text" && (raw === node.url || "mailto:" + raw === node.url) && /^[a-z][+\-.a-z]+:/i.test(node.url) && !/[\0- <>\u007F]/.test(node.url));
|
|
84850
85127
|
}
|
|
84851
85128
|
//#endregion
|
|
84852
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85129
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link.js
|
|
84853
85130
|
/**
|
|
84854
85131
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84855
85132
|
* @import {Link, Parents} from 'mdast'
|
|
@@ -84950,7 +85227,7 @@ function linkPeek(node, _, state) {
|
|
|
84950
85227
|
return formatLinkAsAutolink(node, state) ? "<" : "[";
|
|
84951
85228
|
}
|
|
84952
85229
|
//#endregion
|
|
84953
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85230
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js
|
|
84954
85231
|
/**
|
|
84955
85232
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84956
85233
|
* @import {LinkReference, Parents} from 'mdast'
|
|
@@ -85011,7 +85288,7 @@ function linkReferencePeek() {
|
|
|
85011
85288
|
return "[";
|
|
85012
85289
|
}
|
|
85013
85290
|
//#endregion
|
|
85014
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85291
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js
|
|
85015
85292
|
/**
|
|
85016
85293
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85017
85294
|
*/
|
|
@@ -85029,7 +85306,7 @@ function checkBullet(state) {
|
|
|
85029
85306
|
return marker;
|
|
85030
85307
|
}
|
|
85031
85308
|
//#endregion
|
|
85032
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85309
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js
|
|
85033
85310
|
/**
|
|
85034
85311
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85035
85312
|
*/
|
|
@@ -85050,7 +85327,7 @@ function checkBulletOther(state) {
|
|
|
85050
85327
|
return bulletOther;
|
|
85051
85328
|
}
|
|
85052
85329
|
//#endregion
|
|
85053
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85330
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js
|
|
85054
85331
|
/**
|
|
85055
85332
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85056
85333
|
*/
|
|
@@ -85068,7 +85345,7 @@ function checkBulletOrdered(state) {
|
|
|
85068
85345
|
return marker;
|
|
85069
85346
|
}
|
|
85070
85347
|
//#endregion
|
|
85071
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85348
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule.js
|
|
85072
85349
|
/**
|
|
85073
85350
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85074
85351
|
*/
|
|
@@ -85086,7 +85363,7 @@ function checkRule(state) {
|
|
|
85086
85363
|
return marker;
|
|
85087
85364
|
}
|
|
85088
85365
|
//#endregion
|
|
85089
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85366
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list.js
|
|
85090
85367
|
/**
|
|
85091
85368
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85092
85369
|
* @import {List, Parents} from 'mdast'
|
|
@@ -85136,7 +85413,7 @@ function list$2(node, parent, state, info) {
|
|
|
85136
85413
|
return value;
|
|
85137
85414
|
}
|
|
85138
85415
|
//#endregion
|
|
85139
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85416
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js
|
|
85140
85417
|
/**
|
|
85141
85418
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85142
85419
|
*/
|
|
@@ -85154,7 +85431,7 @@ function checkListItemIndent(state) {
|
|
|
85154
85431
|
return style;
|
|
85155
85432
|
}
|
|
85156
85433
|
//#endregion
|
|
85157
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85434
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list-item.js
|
|
85158
85435
|
/**
|
|
85159
85436
|
* @import {Info, Map, State} from 'mdast-util-to-markdown'
|
|
85160
85437
|
* @import {ListItem, Parents} from 'mdast'
|
|
@@ -85197,7 +85474,7 @@ function listItem$1(node, parent, state, info) {
|
|
|
85197
85474
|
}
|
|
85198
85475
|
}
|
|
85199
85476
|
//#endregion
|
|
85200
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85477
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js
|
|
85201
85478
|
/**
|
|
85202
85479
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85203
85480
|
* @import {Paragraph, Parents} from 'mdast'
|
|
@@ -85225,7 +85502,7 @@ function paragraph$1(node, _, state, info) {
|
|
|
85225
85502
|
return value;
|
|
85226
85503
|
}
|
|
85227
85504
|
//#endregion
|
|
85228
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85505
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/root.js
|
|
85229
85506
|
/**
|
|
85230
85507
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85231
85508
|
* @import {Parents, Root} from 'mdast'
|
|
@@ -85254,7 +85531,7 @@ function root$1(node, _, state, info) {
|
|
|
85254
85531
|
return value;
|
|
85255
85532
|
}
|
|
85256
85533
|
//#endregion
|
|
85257
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85534
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-strong.js
|
|
85258
85535
|
/**
|
|
85259
85536
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85260
85537
|
*/
|
|
@@ -85272,7 +85549,7 @@ function checkStrong(state) {
|
|
|
85272
85549
|
return marker;
|
|
85273
85550
|
}
|
|
85274
85551
|
//#endregion
|
|
85275
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85552
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/strong.js
|
|
85276
85553
|
/**
|
|
85277
85554
|
* @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
|
|
85278
85555
|
* @import {Parents, Strong} from 'mdast'
|
|
@@ -85337,7 +85614,7 @@ function peek(_, _1, state) {
|
|
|
85337
85614
|
return state.options.strong || "*";
|
|
85338
85615
|
}
|
|
85339
85616
|
//#endregion
|
|
85340
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85617
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/text.js
|
|
85341
85618
|
/**
|
|
85342
85619
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85343
85620
|
* @import {Parents, Text} from 'mdast'
|
|
@@ -85347,7 +85624,7 @@ function peek(_, _1, state) {
|
|
|
85347
85624
|
*
|
|
85348
85625
|
* @param {Text} node
|
|
85349
85626
|
* Node to serialize.
|
|
85350
|
-
* @param {Parents | undefined}
|
|
85627
|
+
* @param {Parents | undefined} parent
|
|
85351
85628
|
* Parent node.
|
|
85352
85629
|
* @param {State} state
|
|
85353
85630
|
* Info passed around.
|
|
@@ -85356,11 +85633,18 @@ function peek(_, _1, state) {
|
|
|
85356
85633
|
* @returns {string}
|
|
85357
85634
|
* Serialized markdown.
|
|
85358
85635
|
*/
|
|
85359
|
-
function text$4(node,
|
|
85360
|
-
|
|
85636
|
+
function text$4(node, parent, state, info) {
|
|
85637
|
+
/* c8 ignore next -- parent always passed */
|
|
85638
|
+
const siblings = parent ? parent.children : [];
|
|
85639
|
+
const index = siblings.indexOf(node);
|
|
85640
|
+
return state.safe(node.value, {
|
|
85641
|
+
...info,
|
|
85642
|
+
afterNode: siblings[index + 1],
|
|
85643
|
+
beforeNode: siblings[index - 1]
|
|
85644
|
+
});
|
|
85361
85645
|
}
|
|
85362
85646
|
//#endregion
|
|
85363
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85647
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js
|
|
85364
85648
|
/**
|
|
85365
85649
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85366
85650
|
*/
|
|
@@ -85378,7 +85662,7 @@ function checkRuleRepetition(state) {
|
|
|
85378
85662
|
return repetition;
|
|
85379
85663
|
}
|
|
85380
85664
|
//#endregion
|
|
85381
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85665
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js
|
|
85382
85666
|
/**
|
|
85383
85667
|
* @import {State} from 'mdast-util-to-markdown'
|
|
85384
85668
|
* @import {Parents, ThematicBreak} from 'mdast'
|
|
@@ -85400,7 +85684,7 @@ function thematicBreak$2(_, _1, state) {
|
|
|
85400
85684
|
return state.options.ruleSpaces ? value.slice(0, -1) : value;
|
|
85401
85685
|
}
|
|
85402
85686
|
//#endregion
|
|
85403
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85687
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/index.js
|
|
85404
85688
|
/**
|
|
85405
85689
|
* Default (CommonMark) handlers.
|
|
85406
85690
|
*/
|
|
@@ -85526,7 +85810,7 @@ const htmlRawNames = [
|
|
|
85526
85810
|
"textarea"
|
|
85527
85811
|
];
|
|
85528
85812
|
//#endregion
|
|
85529
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85813
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/html-kind.js
|
|
85530
85814
|
/**
|
|
85531
85815
|
* @typedef {1 | 2 | 3 | 4 | 5 | 6 | 7} Kind
|
|
85532
85816
|
* Kind of HTML (flow) element.
|
|
@@ -85574,7 +85858,7 @@ function htmlKind(value) {
|
|
|
85574
85858
|
return htmlBlockNames.includes(name) ? 6 : 7;
|
|
85575
85859
|
}
|
|
85576
85860
|
//#endregion
|
|
85577
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85861
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/join.js
|
|
85578
85862
|
/**
|
|
85579
85863
|
* @import {Join} from 'mdast-util-to-markdown'
|
|
85580
85864
|
*/
|
|
@@ -85601,7 +85885,7 @@ function joinDefaults(left, right, parent, state) {
|
|
|
85601
85885
|
}
|
|
85602
85886
|
}
|
|
85603
85887
|
//#endregion
|
|
85604
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85888
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/unsafe.js
|
|
85605
85889
|
/**
|
|
85606
85890
|
* @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
|
|
85607
85891
|
*/
|
|
@@ -88069,7 +88353,7 @@ function decode($0, $1, $2) {
|
|
|
88069
88353
|
return decodeNamedCharacterReference($2) || $0;
|
|
88070
88354
|
}
|
|
88071
88355
|
//#endregion
|
|
88072
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88356
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/association.js
|
|
88073
88357
|
/**
|
|
88074
88358
|
* @import {AssociationId} from '../types.js'
|
|
88075
88359
|
*/
|
|
@@ -88098,7 +88382,7 @@ function association(node) {
|
|
|
88098
88382
|
return decodeString(node.identifier);
|
|
88099
88383
|
}
|
|
88100
88384
|
//#endregion
|
|
88101
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88385
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js
|
|
88102
88386
|
/**
|
|
88103
88387
|
* @import {CompilePattern} from '../types.js'
|
|
88104
88388
|
*/
|
|
@@ -88110,7 +88394,7 @@ function association(node) {
|
|
|
88110
88394
|
function compilePattern(pattern) {
|
|
88111
88395
|
if (!pattern._compiled) {
|
|
88112
88396
|
const before = (pattern.atBreak ? "[\\r\\n][\\t ]*" : "") + (pattern.before ? "(?:" + pattern.before + ")" : "");
|
|
88113
|
-
pattern._compiled = new RegExp((before ? "(" + before + ")" : "") + (/[$()
|
|
88397
|
+
pattern._compiled = new RegExp((pattern.beforeNode ? "^" : before ? "(" + before + ")" : "") + (/[$()*+.?[\\\]^{|}]/.test(pattern.character) ? "\\" : "") + pattern.character + (pattern.afterNode ? "$" : pattern.after ? "(?:" + pattern.after + ")" : ""), pattern.unicode ? "gu" : "g");
|
|
88114
88398
|
}
|
|
88115
88399
|
return pattern._compiled;
|
|
88116
88400
|
}
|
|
@@ -88138,7 +88422,7 @@ function classifyCharacter(code) {
|
|
|
88138
88422
|
if (unicodePunctuation(code)) return 2;
|
|
88139
88423
|
}
|
|
88140
88424
|
//#endregion
|
|
88141
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88425
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/character.js
|
|
88142
88426
|
/**
|
|
88143
88427
|
* Get the first character of a value.
|
|
88144
88428
|
*
|
|
@@ -88168,7 +88452,7 @@ function lastCharacter(value) {
|
|
|
88168
88452
|
return code !== void 0 && code > 65535 ? value.slice(-2) : value.slice(-1);
|
|
88169
88453
|
}
|
|
88170
88454
|
//#endregion
|
|
88171
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88455
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-info.js
|
|
88172
88456
|
/**
|
|
88173
88457
|
* @import {EncodeSides} from '../types.js'
|
|
88174
88458
|
*/
|
|
@@ -88240,7 +88524,7 @@ function encodeInfo(outside, inside, marker) {
|
|
|
88240
88524
|
};
|
|
88241
88525
|
}
|
|
88242
88526
|
//#endregion
|
|
88243
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88527
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js
|
|
88244
88528
|
/**
|
|
88245
88529
|
* @import {Attention, Handle, Info, State} from 'mdast-util-to-markdown'
|
|
88246
88530
|
* @import {PhrasingParents} from '../types.js'
|
|
@@ -88733,7 +89017,7 @@ function serialize(items, before, after) {
|
|
|
88733
89017
|
return result;
|
|
88734
89018
|
}
|
|
88735
89019
|
//#endregion
|
|
88736
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89020
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-flow.js
|
|
88737
89021
|
/**
|
|
88738
89022
|
* @import {State} from 'mdast-util-to-markdown'
|
|
88739
89023
|
* @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
|
|
@@ -88797,7 +89081,7 @@ function between(left, right, parent, state) {
|
|
|
88797
89081
|
return "\n\n";
|
|
88798
89082
|
}
|
|
88799
89083
|
//#endregion
|
|
88800
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89084
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js
|
|
88801
89085
|
/**
|
|
88802
89086
|
* @import {IndentLines} from '../types.js'
|
|
88803
89087
|
*/
|
|
@@ -88833,9 +89117,10 @@ function indentLines(value, map) {
|
|
|
88833
89117
|
}
|
|
88834
89118
|
}
|
|
88835
89119
|
//#endregion
|
|
88836
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89120
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/safe.js
|
|
88837
89121
|
/**
|
|
88838
|
-
* @import {
|
|
89122
|
+
* @import {Nodes} from 'mdast'
|
|
89123
|
+
* @import {SafeConfig, State, Unsafe} from 'mdast-util-to-markdown'
|
|
88839
89124
|
*/
|
|
88840
89125
|
const own$3 = {}.hasOwnProperty;
|
|
88841
89126
|
/**
|
|
@@ -88872,17 +89157,21 @@ function safe(state, input, config) {
|
|
|
88872
89157
|
/** @type {Record<number, {before: boolean, after: boolean}>} */
|
|
88873
89158
|
const infos = {};
|
|
88874
89159
|
const percentEncode = state.stack.includes("autolink");
|
|
89160
|
+
const offset = config.before ? config.before.length : 0;
|
|
89161
|
+
const end = value.length - (config.after ? config.after.length : 0);
|
|
88875
89162
|
let index = -1;
|
|
88876
89163
|
while (++index < state.unsafe.length) {
|
|
88877
89164
|
const pattern = state.unsafe[index];
|
|
88878
|
-
if (!patternInScope(state.stack, pattern)) continue;
|
|
89165
|
+
if (!patternInScope(state.stack, pattern) || !nodeInScope(pattern.beforeNode, config.beforeNode) || !nodeInScope(pattern.afterNode, config.afterNode)) continue;
|
|
88879
89166
|
const expression = state.compilePattern(pattern);
|
|
89167
|
+
const from = pattern.beforeNode ? offset : 0;
|
|
89168
|
+
const subject = value.slice(from, pattern.afterNode ? end : void 0);
|
|
88880
89169
|
/** @type {RegExpExecArray | null} */
|
|
88881
89170
|
let match;
|
|
88882
|
-
while (match = expression.exec(
|
|
89171
|
+
while (match = expression.exec(subject)) {
|
|
88883
89172
|
const before = "before" in pattern || Boolean(pattern.atBreak);
|
|
88884
89173
|
const after = "after" in pattern;
|
|
88885
|
-
const position = match.index + (before ? match[1].length : 0);
|
|
89174
|
+
const position = from + match.index + (before ? match[1].length : 0);
|
|
88886
89175
|
if (own$3.call(infos, position)) {
|
|
88887
89176
|
if (infos[position].before && !before) infos[position].before = false;
|
|
88888
89177
|
if (infos[position].after && !after) infos[position].after = false;
|
|
@@ -88896,8 +89185,6 @@ function safe(state, input, config) {
|
|
|
88896
89185
|
}
|
|
88897
89186
|
}
|
|
88898
89187
|
positions.sort(numerical);
|
|
88899
|
-
const offset = config.before ? config.before.length : 0;
|
|
88900
|
-
const end = value.length - (config.after ? config.after.length : 0);
|
|
88901
89188
|
let start = offset;
|
|
88902
89189
|
index = -1;
|
|
88903
89190
|
while (++index < positions.length) {
|
|
@@ -88932,6 +89219,21 @@ function safe(state, input, config) {
|
|
|
88932
89219
|
return result.join("");
|
|
88933
89220
|
}
|
|
88934
89221
|
/**
|
|
89222
|
+
* Check if a node matches the node types of a pattern.
|
|
89223
|
+
*
|
|
89224
|
+
* @param {Array<Nodes['type']> | Nodes['type'] | null | undefined} types
|
|
89225
|
+
* Node types.
|
|
89226
|
+
* @param {Nodes | undefined} node
|
|
89227
|
+
* Node.
|
|
89228
|
+
* @returns {boolean}
|
|
89229
|
+
* Whether `node` matches `types`.
|
|
89230
|
+
*/
|
|
89231
|
+
function nodeInScope(types, node) {
|
|
89232
|
+
if (!types) return true;
|
|
89233
|
+
if (!node) return false;
|
|
89234
|
+
return typeof types === "string" ? types === node.type : types.includes(node.type);
|
|
89235
|
+
}
|
|
89236
|
+
/**
|
|
88935
89237
|
* Compare two numbers.
|
|
88936
89238
|
*
|
|
88937
89239
|
* @param {number} a
|
|
@@ -88975,7 +89277,7 @@ function escapeBackslashes(value, after) {
|
|
|
88975
89277
|
return results.join("");
|
|
88976
89278
|
}
|
|
88977
89279
|
//#endregion
|
|
88978
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89280
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/track.js
|
|
88979
89281
|
/**
|
|
88980
89282
|
* @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
|
|
88981
89283
|
*/
|
|
@@ -89033,7 +89335,7 @@ function track(config) {
|
|
|
89033
89335
|
}
|
|
89034
89336
|
}
|
|
89035
89337
|
//#endregion
|
|
89036
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89338
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/index.js
|
|
89037
89339
|
/**
|
|
89038
89340
|
* @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
|
|
89039
89341
|
* @import {Nodes} from 'mdast'
|
|
@@ -99040,7 +99342,7 @@ function preprocess() {
|
|
|
99040
99342
|
}
|
|
99041
99343
|
}
|
|
99042
99344
|
//#endregion
|
|
99043
|
-
//#region node_modules/.pnpm/mdast-util-from-markdown@2.
|
|
99345
|
+
//#region node_modules/.pnpm/mdast-util-from-markdown@2.1.0_supports-color@7.2.0/node_modules/mdast-util-from-markdown/lib/index.js
|
|
99044
99346
|
/**
|
|
99045
99347
|
* @import {
|
|
99046
99348
|
* Break,
|
|
@@ -99120,6 +99422,8 @@ function fromMarkdown(value, encoding, options) {
|
|
|
99120
99422
|
function compiler(options) {
|
|
99121
99423
|
/** @type {Config} */
|
|
99122
99424
|
const config = {
|
|
99425
|
+
afterExit: [],
|
|
99426
|
+
beforeEnter: [],
|
|
99123
99427
|
transforms: [],
|
|
99124
99428
|
canContainEols: [
|
|
99125
99429
|
"emphasis",
|
|
@@ -99239,7 +99543,8 @@ function compiler(options) {
|
|
|
99239
99543
|
/** @type {Root} */
|
|
99240
99544
|
let tree = {
|
|
99241
99545
|
type: "root",
|
|
99242
|
-
children: []
|
|
99546
|
+
children: [],
|
|
99547
|
+
position: void 0
|
|
99243
99548
|
};
|
|
99244
99549
|
/** @type {Omit<CompileContext, 'sliceSerialize'>} */
|
|
99245
99550
|
const context = {
|
|
@@ -99254,15 +99559,32 @@ function compiler(options) {
|
|
|
99254
99559
|
};
|
|
99255
99560
|
/** @type {Array<number>} */
|
|
99256
99561
|
const listStack = [];
|
|
99562
|
+
/** @type {Array<Event>} */
|
|
99563
|
+
const prepared = [];
|
|
99257
99564
|
let index = -1;
|
|
99258
|
-
while (++index < events.length)
|
|
99259
|
-
|
|
99260
|
-
|
|
99565
|
+
while (++index < events.length) {
|
|
99566
|
+
prepared.push(events[index]);
|
|
99567
|
+
if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
|
|
99568
|
+
if (events[index][0] === "enter") listStack.push(prepared.length - 1);
|
|
99569
|
+
else prepareList(prepared, listStack.pop());
|
|
99570
|
+
}
|
|
99261
99571
|
}
|
|
99572
|
+
events = prepared;
|
|
99262
99573
|
index = -1;
|
|
99263
99574
|
while (++index < events.length) {
|
|
99264
99575
|
const handler = config[events[index][0]];
|
|
99265
|
-
if (
|
|
99576
|
+
if (events[index][0] === "enter" && config.beforeEnter.length > 0) callListeners(config.beforeEnter, {
|
|
99577
|
+
...context,
|
|
99578
|
+
sliceSerialize: events[index][2].sliceSerialize
|
|
99579
|
+
}, events[index][1]);
|
|
99580
|
+
if (own$2.call(handler, events[index][1].type)) handler[events[index][1].type].call({
|
|
99581
|
+
...context,
|
|
99582
|
+
sliceSerialize: events[index][2].sliceSerialize
|
|
99583
|
+
}, events[index][1]);
|
|
99584
|
+
if (events[index][0] === "exit" && config.afterExit.length > 0) callListeners(config.afterExit, {
|
|
99585
|
+
...context,
|
|
99586
|
+
sliceSerialize: events[index][2].sliceSerialize
|
|
99587
|
+
}, events[index][1]);
|
|
99266
99588
|
}
|
|
99267
99589
|
if (context.tokenStack.length > 0) {
|
|
99268
99590
|
const tail = context.tokenStack[context.tokenStack.length - 1];
|
|
@@ -99285,12 +99607,28 @@ function compiler(options) {
|
|
|
99285
99607
|
return tree;
|
|
99286
99608
|
}
|
|
99287
99609
|
/**
|
|
99610
|
+
* @param {Array<Handle>} listeners
|
|
99611
|
+
* Listeners.
|
|
99612
|
+
* @param {CompileContext} context
|
|
99613
|
+
* Context.
|
|
99614
|
+
* @param {Token} token
|
|
99615
|
+
* Token.
|
|
99616
|
+
* @returns {undefined}
|
|
99617
|
+
* Nothing.
|
|
99618
|
+
*/
|
|
99619
|
+
function callListeners(listeners, context, token) {
|
|
99620
|
+
let index = -1;
|
|
99621
|
+
while (++index < listeners.length) listeners[index].call(context, token);
|
|
99622
|
+
}
|
|
99623
|
+
/**
|
|
99288
99624
|
* @param {Array<Event>} events
|
|
99625
|
+
* Events, ending with the list to prepare.
|
|
99289
99626
|
* @param {number} start
|
|
99290
|
-
*
|
|
99291
|
-
* @returns {
|
|
99627
|
+
* Index of the list enter.
|
|
99628
|
+
* @returns {undefined}
|
|
99292
99629
|
*/
|
|
99293
|
-
function prepareList(events, start
|
|
99630
|
+
function prepareList(events, start) {
|
|
99631
|
+
const end = events.length - 1;
|
|
99294
99632
|
let index = start - 1;
|
|
99295
99633
|
let containerBalance = -1;
|
|
99296
99634
|
let listSpread = false;
|
|
@@ -99302,7 +99640,14 @@ function compiler(options) {
|
|
|
99302
99640
|
let firstBlankLineIndex;
|
|
99303
99641
|
/** @type {boolean | undefined} */
|
|
99304
99642
|
let atMarker;
|
|
99305
|
-
|
|
99643
|
+
/**
|
|
99644
|
+
* `listItem` events to insert before the event at `at`, in walk order.
|
|
99645
|
+
* Merged in after the walk, as inserting while walking would shift the
|
|
99646
|
+
* rest of the list once per item.
|
|
99647
|
+
* @type {Array<{at: number, event: Event}>}
|
|
99648
|
+
*/
|
|
99649
|
+
const insertions = [];
|
|
99650
|
+
while (++index <= end) {
|
|
99306
99651
|
const event = events[index];
|
|
99307
99652
|
switch (event[1].type) {
|
|
99308
99653
|
case "listUnordered":
|
|
@@ -99343,13 +99688,14 @@ function compiler(options) {
|
|
|
99343
99688
|
}
|
|
99344
99689
|
if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) listItem._spread = true;
|
|
99345
99690
|
listItem.end = Object.assign({}, lineIndex ? events[lineIndex][1].start : event[1].end);
|
|
99346
|
-
|
|
99347
|
-
|
|
99348
|
-
|
|
99349
|
-
|
|
99350
|
-
|
|
99351
|
-
|
|
99352
|
-
|
|
99691
|
+
insertions.push({
|
|
99692
|
+
at: lineIndex || index,
|
|
99693
|
+
event: [
|
|
99694
|
+
"exit",
|
|
99695
|
+
listItem,
|
|
99696
|
+
event[2]
|
|
99697
|
+
]
|
|
99698
|
+
});
|
|
99353
99699
|
}
|
|
99354
99700
|
if (event[1].type === "listItemPrefix") {
|
|
99355
99701
|
/** @type {Token} */
|
|
@@ -99360,20 +99706,27 @@ function compiler(options) {
|
|
|
99360
99706
|
end: void 0
|
|
99361
99707
|
};
|
|
99362
99708
|
listItem = item;
|
|
99363
|
-
|
|
99364
|
-
|
|
99365
|
-
|
|
99366
|
-
|
|
99367
|
-
|
|
99368
|
-
|
|
99369
|
-
|
|
99709
|
+
insertions.push({
|
|
99710
|
+
at: index,
|
|
99711
|
+
event: [
|
|
99712
|
+
"enter",
|
|
99713
|
+
item,
|
|
99714
|
+
event[2]
|
|
99715
|
+
]
|
|
99716
|
+
});
|
|
99370
99717
|
firstBlankLineIndex = void 0;
|
|
99371
99718
|
atMarker = true;
|
|
99372
99719
|
}
|
|
99373
99720
|
}
|
|
99374
99721
|
}
|
|
99722
|
+
const listEvents = events.splice(start);
|
|
99723
|
+
let insertion = 0;
|
|
99724
|
+
index = -1;
|
|
99725
|
+
while (++index < listEvents.length) {
|
|
99726
|
+
while (insertion < insertions.length && insertions[insertion].at === start + index) events.push(insertions[insertion++].event);
|
|
99727
|
+
events.push(listEvents[index]);
|
|
99728
|
+
}
|
|
99375
99729
|
events[start][1]._spread = listSpread;
|
|
99376
|
-
return length;
|
|
99377
99730
|
}
|
|
99378
99731
|
/**
|
|
99379
99732
|
* Create an opener handle.
|
|
@@ -99448,7 +99801,7 @@ function compiler(options) {
|
|
|
99448
99801
|
start: token.start,
|
|
99449
99802
|
end: token.end
|
|
99450
99803
|
}) + "): it’s not open");
|
|
99451
|
-
|
|
99804
|
+
if (open[0].type !== token.type) {
|
|
99452
99805
|
if (onExitError) onExitError.call(this, token, open[0]);
|
|
99453
99806
|
else (open[1] || defaultOnError).call(this, token, open[0]);
|
|
99454
99807
|
}
|
|
@@ -99816,7 +100169,8 @@ function compiler(options) {
|
|
|
99816
100169
|
function blockQuote() {
|
|
99817
100170
|
return {
|
|
99818
100171
|
type: "blockquote",
|
|
99819
|
-
children: []
|
|
100172
|
+
children: [],
|
|
100173
|
+
position: void 0
|
|
99820
100174
|
};
|
|
99821
100175
|
}
|
|
99822
100176
|
/** @returns {Code} */
|
|
@@ -99825,14 +100179,16 @@ function compiler(options) {
|
|
|
99825
100179
|
type: "code",
|
|
99826
100180
|
lang: null,
|
|
99827
100181
|
meta: null,
|
|
99828
|
-
value: ""
|
|
100182
|
+
value: "",
|
|
100183
|
+
position: void 0
|
|
99829
100184
|
};
|
|
99830
100185
|
}
|
|
99831
100186
|
/** @returns {InlineCode} */
|
|
99832
100187
|
function codeText() {
|
|
99833
100188
|
return {
|
|
99834
100189
|
type: "inlineCode",
|
|
99835
|
-
value: ""
|
|
100190
|
+
value: "",
|
|
100191
|
+
position: void 0
|
|
99836
100192
|
};
|
|
99837
100193
|
}
|
|
99838
100194
|
/** @returns {Definition} */
|
|
@@ -99842,14 +100198,16 @@ function compiler(options) {
|
|
|
99842
100198
|
identifier: "",
|
|
99843
100199
|
label: null,
|
|
99844
100200
|
title: null,
|
|
99845
|
-
url: ""
|
|
100201
|
+
url: "",
|
|
100202
|
+
position: void 0
|
|
99846
100203
|
};
|
|
99847
100204
|
}
|
|
99848
100205
|
/** @returns {Emphasis} */
|
|
99849
100206
|
function emphasis() {
|
|
99850
100207
|
return {
|
|
99851
100208
|
type: "emphasis",
|
|
99852
|
-
children: []
|
|
100209
|
+
children: [],
|
|
100210
|
+
position: void 0
|
|
99853
100211
|
};
|
|
99854
100212
|
}
|
|
99855
100213
|
/** @returns {Heading} */
|
|
@@ -99857,18 +100215,23 @@ function compiler(options) {
|
|
|
99857
100215
|
return {
|
|
99858
100216
|
type: "heading",
|
|
99859
100217
|
depth: 0,
|
|
99860
|
-
children: []
|
|
100218
|
+
children: [],
|
|
100219
|
+
position: void 0
|
|
99861
100220
|
};
|
|
99862
100221
|
}
|
|
99863
100222
|
/** @returns {Break} */
|
|
99864
100223
|
function hardBreak() {
|
|
99865
|
-
return {
|
|
100224
|
+
return {
|
|
100225
|
+
type: "break",
|
|
100226
|
+
position: void 0
|
|
100227
|
+
};
|
|
99866
100228
|
}
|
|
99867
100229
|
/** @returns {Html} */
|
|
99868
100230
|
function html() {
|
|
99869
100231
|
return {
|
|
99870
100232
|
type: "html",
|
|
99871
|
-
value: ""
|
|
100233
|
+
value: "",
|
|
100234
|
+
position: void 0
|
|
99872
100235
|
};
|
|
99873
100236
|
}
|
|
99874
100237
|
/** @returns {Image} */
|
|
@@ -99877,7 +100240,8 @@ function compiler(options) {
|
|
|
99877
100240
|
type: "image",
|
|
99878
100241
|
title: null,
|
|
99879
100242
|
url: "",
|
|
99880
|
-
alt: null
|
|
100243
|
+
alt: null,
|
|
100244
|
+
position: void 0
|
|
99881
100245
|
};
|
|
99882
100246
|
}
|
|
99883
100247
|
/** @returns {Link} */
|
|
@@ -99886,7 +100250,8 @@ function compiler(options) {
|
|
|
99886
100250
|
type: "link",
|
|
99887
100251
|
title: null,
|
|
99888
100252
|
url: "",
|
|
99889
|
-
children: []
|
|
100253
|
+
children: [],
|
|
100254
|
+
position: void 0
|
|
99890
100255
|
};
|
|
99891
100256
|
}
|
|
99892
100257
|
/**
|
|
@@ -99899,7 +100264,8 @@ function compiler(options) {
|
|
|
99899
100264
|
ordered: token.type === "listOrdered",
|
|
99900
100265
|
start: null,
|
|
99901
100266
|
spread: token._spread,
|
|
99902
|
-
children: []
|
|
100267
|
+
children: [],
|
|
100268
|
+
position: void 0
|
|
99903
100269
|
};
|
|
99904
100270
|
}
|
|
99905
100271
|
/**
|
|
@@ -99911,33 +100277,40 @@ function compiler(options) {
|
|
|
99911
100277
|
type: "listItem",
|
|
99912
100278
|
spread: token._spread,
|
|
99913
100279
|
checked: null,
|
|
99914
|
-
children: []
|
|
100280
|
+
children: [],
|
|
100281
|
+
position: void 0
|
|
99915
100282
|
};
|
|
99916
100283
|
}
|
|
99917
100284
|
/** @returns {Paragraph} */
|
|
99918
100285
|
function paragraph() {
|
|
99919
100286
|
return {
|
|
99920
100287
|
type: "paragraph",
|
|
99921
|
-
children: []
|
|
100288
|
+
children: [],
|
|
100289
|
+
position: void 0
|
|
99922
100290
|
};
|
|
99923
100291
|
}
|
|
99924
100292
|
/** @returns {Strong} */
|
|
99925
100293
|
function strong() {
|
|
99926
100294
|
return {
|
|
99927
100295
|
type: "strong",
|
|
99928
|
-
children: []
|
|
100296
|
+
children: [],
|
|
100297
|
+
position: void 0
|
|
99929
100298
|
};
|
|
99930
100299
|
}
|
|
99931
100300
|
/** @returns {Text} */
|
|
99932
100301
|
function text() {
|
|
99933
100302
|
return {
|
|
99934
100303
|
type: "text",
|
|
99935
|
-
value: ""
|
|
100304
|
+
value: "",
|
|
100305
|
+
position: void 0
|
|
99936
100306
|
};
|
|
99937
100307
|
}
|
|
99938
100308
|
/** @returns {ThematicBreak} */
|
|
99939
100309
|
function thematicBreak() {
|
|
99940
|
-
return {
|
|
100310
|
+
return {
|
|
100311
|
+
type: "thematicBreak",
|
|
100312
|
+
position: void 0
|
|
100313
|
+
};
|
|
99941
100314
|
}
|
|
99942
100315
|
}
|
|
99943
100316
|
/**
|
|
@@ -99987,6 +100360,12 @@ function extension(combined, extension) {
|
|
|
99987
100360
|
if (right) combined[key].push(...right);
|
|
99988
100361
|
break;
|
|
99989
100362
|
}
|
|
100363
|
+
case "afterExit":
|
|
100364
|
+
case "beforeEnter": {
|
|
100365
|
+
const right = extension[key];
|
|
100366
|
+
if (right) combined[key].push(right);
|
|
100367
|
+
break;
|
|
100368
|
+
}
|
|
99990
100369
|
case "enter":
|
|
99991
100370
|
case "exit": {
|
|
99992
100371
|
const right = extension[key];
|
|
@@ -100004,7 +100383,7 @@ function defaultOnError(left, right) {
|
|
|
100004
100383
|
start: right.start,
|
|
100005
100384
|
end: right.end
|
|
100006
100385
|
}) + ") is open");
|
|
100007
|
-
|
|
100386
|
+
throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
|
|
100008
100387
|
start: right.start,
|
|
100009
100388
|
end: right.end
|
|
100010
100389
|
}) + ") is still open");
|
|
@@ -103736,6 +104115,7 @@ const formatNodeTranslationSummary = (translation) => [
|
|
|
103736
104115
|
`- **Updated**: ${translation.updated_at}`
|
|
103737
104116
|
].join("\n");
|
|
103738
104117
|
const formatCategory = (category) => `- **${category.name}** (${category.id}) — ${category.description || "No description"}`;
|
|
104118
|
+
const formatBrand = (brand) => `- **${brand.name}** (${brand.id}) — ${brand.brand_url}${brand.default ? " — default" : ""}${brand.active ? "" : " — inactive"}`;
|
|
103739
104119
|
const formatSection = (section) => `- **${section.name}** (${section.id}) — Category: ${section.category_id} — ${section.description || "No description"}`;
|
|
103740
104120
|
const formatView = (view, count) => {
|
|
103741
104121
|
const countText = count ? ` — ${count.pretty} ticket(s)${count.fresh ? "" : " (count updating)"}` : "";
|
|
@@ -103786,13 +104166,7 @@ const extractSearchPaginationMeta = (response, perPage, page) => {
|
|
|
103786
104166
|
};
|
|
103787
104167
|
//#endregion
|
|
103788
104168
|
//#region src/guidance/article-resources.ts
|
|
103789
|
-
/**
|
|
103790
|
-
* Name of the companion tool that lists promoted articles. Shared between the
|
|
103791
|
-
* tool definition (`help-center.ts`) and the `--no-promoted-articles` opt-out
|
|
103792
|
-
* filter (`server.ts`) so the two can never drift: renaming the tool here keeps
|
|
103793
|
-
* the filter dropping it, preserving the "zero preloading requests when off"
|
|
103794
|
-
* invariant.
|
|
103795
|
-
*/
|
|
104169
|
+
/** Name of the companion tool that lists promoted articles (`help-center.ts`). */
|
|
103796
104170
|
const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
|
|
103797
104171
|
/**
|
|
103798
104172
|
* Scan the Help Center for promoted ("featured") articles with the CALLER'S
|
|
@@ -103801,9 +104175,11 @@ const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
|
|
|
103801
104175
|
* `/articles` and filter `promoted` client-side, bounded by `maxPages` to keep
|
|
103802
104176
|
* the scan tractable on a large Help Center. `truncated` signals the cap was hit.
|
|
103803
104177
|
*
|
|
103804
|
-
*
|
|
103805
|
-
*
|
|
103806
|
-
*
|
|
104178
|
+
* `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own
|
|
104179
|
+
* (a brand is only ever <subdomain>.zendesk.com). Returns the FULL promoted
|
|
104180
|
+
* articles so callers that need rich metadata (the `list_promoted_articles`
|
|
104181
|
+
* tool) get everything; the resource provider maps these down to lean refs
|
|
104182
|
+
* before caching so the per-session cache doesn't retain bodies.
|
|
103807
104183
|
*/
|
|
103808
104184
|
const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOURCES_SCAN_MAX_PAGES) => {
|
|
103809
104185
|
const promoted = [];
|
|
@@ -103833,6 +104209,7 @@ const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOUR
|
|
|
103833
104209
|
* caller's token and render it as Markdown: the shared metadata summary plus the
|
|
103834
104210
|
* body converted from HTML (rather than a raw HTML dump), capped by the response
|
|
103835
104211
|
* character limit. Reuses the same formatting as the `get_article` tool.
|
|
104212
|
+
* `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own.
|
|
103836
104213
|
*/
|
|
103837
104214
|
const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
|
|
103838
104215
|
const path = locale ? `/${locale}/articles/${id}` : `/articles/${id}`;
|
|
@@ -103852,24 +104229,69 @@ const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
|
|
|
103852
104229
|
* module scope — in HTTP mode a shared one would leak a caller's data to
|
|
103853
104230
|
* another. `getToken` resolves lazily, so connecting never triggers the OAuth
|
|
103854
104231
|
* flow, and a 401 notifies `onUnauthorized` to drop the stale token.
|
|
104232
|
+
*
|
|
104233
|
+
* Branding is resolved through the SHARED brand subdomain resolver, never a
|
|
104234
|
+
* second /brands walk: `brandIds` decides which brands `listPromoted` scans
|
|
104235
|
+
* (all of them in multi mode, so every allowed brand's promoted articles are
|
|
104236
|
+
* listed with their own URIs; 'all' scans every brand of the account), and
|
|
104237
|
+
* `readArticle` resolves the URI's {brand} through the same allow-list +
|
|
104238
|
+
* resolver the tools use.
|
|
103855
104239
|
*/
|
|
103856
|
-
const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) => {
|
|
104240
|
+
const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized, resolveBrandSubdomain = () => Promise.resolve(subdomain), brandIds) => {
|
|
103857
104241
|
let cached;
|
|
103858
104242
|
const notifyIfUnauthorized = (err) => {
|
|
103859
104243
|
if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
|
|
103860
104244
|
};
|
|
104245
|
+
const isSingleLock = brandIds !== void 0 && brandIds.length === 1 && brandIds[0] !== "all";
|
|
104246
|
+
const listBrands = async () => {
|
|
104247
|
+
if (brandIds === void 0 || isSingleLock) return [void 0];
|
|
104248
|
+
if (brandIds[0] === "all") return (await fetchAllBrands(subdomain, await getToken())).filter((b) => b.active && b.has_help_center).map((b) => b.subdomain);
|
|
104249
|
+
return brandIds;
|
|
104250
|
+
};
|
|
104251
|
+
const effectiveSubdomain = async (brand) => {
|
|
104252
|
+
if (brand !== void 0) return resolveBrandSubdomain(brand);
|
|
104253
|
+
if (isSingleLock) return resolveBrandSubdomain(brandIds[0]);
|
|
104254
|
+
return subdomain;
|
|
104255
|
+
};
|
|
104256
|
+
const scanBrand = async (brand, token) => {
|
|
104257
|
+
try {
|
|
104258
|
+
const hcSubdomain = await effectiveSubdomain(brand);
|
|
104259
|
+
const scan = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
|
|
104260
|
+
return {
|
|
104261
|
+
refs: scan.articles.map((a) => brand === void 0 ? {
|
|
104262
|
+
id: a.id,
|
|
104263
|
+
title: a.title
|
|
104264
|
+
} : {
|
|
104265
|
+
id: a.id,
|
|
104266
|
+
title: a.title,
|
|
104267
|
+
brand
|
|
104268
|
+
}),
|
|
104269
|
+
truncated: scan.truncated
|
|
104270
|
+
};
|
|
104271
|
+
} catch (err) {
|
|
104272
|
+
if (err instanceof ZendeskApiError && err.status === 401) throw err;
|
|
104273
|
+
return {
|
|
104274
|
+
refs: [],
|
|
104275
|
+
truncated: true
|
|
104276
|
+
};
|
|
104277
|
+
}
|
|
104278
|
+
};
|
|
103861
104279
|
return {
|
|
103862
104280
|
listPromoted() {
|
|
103863
104281
|
const now = Date.now();
|
|
103864
104282
|
if (cached && now - cached.at < 3e5) return cached.promise;
|
|
103865
104283
|
const promise = (async () => {
|
|
103866
104284
|
const token = await getToken();
|
|
103867
|
-
const
|
|
104285
|
+
const brands = await listBrands();
|
|
104286
|
+
const refs = [];
|
|
104287
|
+
let truncated = false;
|
|
104288
|
+
for (const brand of brands) {
|
|
104289
|
+
const scan = await scanBrand(brand, token);
|
|
104290
|
+
truncated = truncated || scan.truncated;
|
|
104291
|
+
refs.push(...scan.refs);
|
|
104292
|
+
}
|
|
103868
104293
|
return {
|
|
103869
|
-
refs
|
|
103870
|
-
id: a.id,
|
|
103871
|
-
title: a.title
|
|
103872
|
-
})),
|
|
104294
|
+
refs,
|
|
103873
104295
|
truncated
|
|
103874
104296
|
};
|
|
103875
104297
|
})().catch((err) => {
|
|
@@ -103883,10 +104305,10 @@ const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) =>
|
|
|
103883
104305
|
};
|
|
103884
104306
|
return promise;
|
|
103885
104307
|
},
|
|
103886
|
-
async readArticle(id) {
|
|
104308
|
+
async readArticle(id, brand) {
|
|
103887
104309
|
try {
|
|
103888
|
-
const token = await getToken();
|
|
103889
|
-
return await fetchArticleMarkdown(
|
|
104310
|
+
const [token, hcSubdomain] = await Promise.all([getToken(), effectiveSubdomain(brand)]);
|
|
104311
|
+
return await fetchArticleMarkdown(hcSubdomain, token, id, void 0);
|
|
103890
104312
|
} catch (err) {
|
|
103891
104313
|
notifyIfUnauthorized(err);
|
|
103892
104314
|
throw err;
|
|
@@ -103907,17 +104329,33 @@ const topologyResourceUri = (config) => `${config.hcResourceScheme}://topology`;
|
|
|
103907
104329
|
/**
|
|
103908
104330
|
* URI template of the pull-only Help Center article resources, built from the
|
|
103909
104331
|
* configured scheme exactly like `topologyResourceUri` (`--hc-resource-scheme`,
|
|
103910
|
-
* default `zendesk-hc`
|
|
103911
|
-
*
|
|
103912
|
-
*
|
|
104332
|
+
* default `zendesk-hc`). In multi/all brand mode the URI carries a brand
|
|
104333
|
+
* dimension — `<scheme>://brands/{brand}/articles/{id}` — because an article id
|
|
104334
|
+
* alone does not say which brand's Help Center holds it; the brand is named by
|
|
104335
|
+
* id or subdomain, the same idiom the tools' `brand_id` accepts. Unset and
|
|
104336
|
+
* single-brand modes keep `<scheme>://article/{id}`: one Help Center is
|
|
104337
|
+
* addressed, so a brand segment would be noise. The template's `list` callback
|
|
104338
|
+
* enumerates the promoted ("featured") articles (so clients can surface them
|
|
104339
|
+
* for pinning), while any article id can be read on demand.
|
|
103913
104340
|
*/
|
|
103914
|
-
const articleResourceUriTemplate = (config) => `${config.hcResourceScheme}://article/{id}`;
|
|
104341
|
+
const articleResourceUriTemplate = (config) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/{brand}/articles/{id}` : `${config.hcResourceScheme}://article/{id}`;
|
|
103915
104342
|
/**
|
|
103916
104343
|
* Build the concrete resource URI for a single article id, under the configured
|
|
103917
104344
|
* scheme. Shares the scheme with the template above so the listed URIs always
|
|
103918
|
-
* match the template the read callback is registered under.
|
|
104345
|
+
* match the template the read callback is registered under. In multi/all mode
|
|
104346
|
+
* the brand argument (id or subdomain) is required and fills the {brand} slot.
|
|
104347
|
+
*/
|
|
104348
|
+
const articleResourceUri = (config, id, brand) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/${brand}/articles/${id}` : `${config.hcResourceScheme}://article/${id}`;
|
|
104349
|
+
/**
|
|
104350
|
+
* Whether the article resources address one Help Center among several: the
|
|
104351
|
+
* server was started with several brands, or with `all`. Only then does the URI
|
|
104352
|
+
* carry the brand dimension; unset and single-lock modes name exactly one Help
|
|
104353
|
+
* Center implicitly.
|
|
103919
104354
|
*/
|
|
103920
|
-
const
|
|
104355
|
+
const brandScopedArticles = (config) => {
|
|
104356
|
+
const brandIds = config.brandIds;
|
|
104357
|
+
return brandIds !== void 0 && (brandIds.length > 1 || brandIds[0] === "all");
|
|
104358
|
+
};
|
|
103921
104359
|
/**
|
|
103922
104360
|
* Whether the `help_center` namespace is active: no `--namespace` filter, or one
|
|
103923
104361
|
* that includes it. The shared second half of the two Help Center feature gates
|
|
@@ -103943,13 +104381,11 @@ const helpCenterContextEnabled = (config) => config.topology && helpCenterNamesp
|
|
|
103943
104381
|
const articleResourceEnabled = (config) => helpCenterNamespaceActive(config);
|
|
103944
104382
|
/**
|
|
103945
104383
|
* Whether the promoted-article PRE-LISTING is exposed: the resource `list`
|
|
103946
|
-
* callback's scan
|
|
103947
|
-
*
|
|
103948
|
-
*
|
|
103949
|
-
*
|
|
103950
|
-
*
|
|
103951
|
-
* so an omitted flag on a hand-built Config stays default-on, matching the tool
|
|
103952
|
-
* filter in `server.ts`.
|
|
104384
|
+
* callback's scan, which enumerates the promoted articles for `resources/list`.
|
|
104385
|
+
* A client may trigger it unprompted, and it fans out (a capped scan of
|
|
104386
|
+
* `/articles`, per brand in scope), so `--no-promoted-articles` turns it off.
|
|
104387
|
+
* Read-by-id and the explicit `list_promoted_articles` tool stay available.
|
|
104388
|
+
* `!== false` so an omitted flag on a hand-built Config stays default-on.
|
|
103953
104389
|
*/
|
|
103954
104390
|
const promotedArticlesEnabled = (config) => config.promotedArticles !== false && helpCenterNamespaceActive(config);
|
|
103955
104391
|
/**
|
|
@@ -103960,13 +104396,20 @@ const promotedArticlesEnabled = (config) => config.promotedArticles !== false &&
|
|
|
103960
104396
|
*/
|
|
103961
104397
|
const buildInstructions = (config) => {
|
|
103962
104398
|
if (!helpCenterContextEnabled(config)) return void 0;
|
|
104399
|
+
let scope;
|
|
104400
|
+
if (config.brandIds === void 0) scope = `the Zendesk Help Center of "${config.subdomain}"`;
|
|
104401
|
+
else if (config.brandIds.length === 1 && config.brandIds[0] === "all") scope = `all brands of the Zendesk Help Center of "${config.subdomain}"`;
|
|
104402
|
+
else if (config.brandIds.length === 1) scope = `brand ${config.brandIds[0]} of the Zendesk Help Center of "${config.subdomain}"`;
|
|
104403
|
+
else scope = `brands ${config.brandIds.join(", ")} of the Zendesk Help Center of "${config.subdomain}"`;
|
|
104404
|
+
const topologyBrandNote = config.brandIds !== void 0 && (config.brandIds.length > 1 || config.brandIds[0] === "all") ? [`The topology describes ${config.brandIds[0] === "all" ? "the account default brand" : `brand ${config.brandIds[0]}`} only; for another brand, use list_categories / list_sections with its brand_id.`] : [];
|
|
103963
104405
|
return [
|
|
103964
|
-
`This MCP server is connected to
|
|
104406
|
+
`This MCP server is connected to ${scope}.`,
|
|
103965
104407
|
"",
|
|
103966
104408
|
`When creating or editing Help Center content, the resource ${topologyResourceUri(config)} is useful context:`,
|
|
103967
104409
|
"it lists the active locales (and the default one), the category → section tree with IDs,",
|
|
103968
104410
|
"the visibility user segments, the permission groups, and your current role.",
|
|
103969
104411
|
"Prefer its IDs (section_id, permission_group_id, user_segment_id, locale) over guessing from names.",
|
|
104412
|
+
...topologyBrandNote,
|
|
103970
104413
|
"",
|
|
103971
104414
|
"It degrades gracefully: without Guide-admin / Help Center manager rights the permission-groups and",
|
|
103972
104415
|
"user-segments sections are marked unavailable (not empty). In that case reuse a permission_group_id",
|
|
@@ -104003,12 +104446,13 @@ const tolerate403 = async (promise, fallback) => {
|
|
|
104003
104446
|
* and sections are each capped at one max-size page; `sectionsHasMore` /
|
|
104004
104447
|
* `categoriesHasMore` signal a Help Center too large to enumerate inline.
|
|
104005
104448
|
*/
|
|
104006
|
-
const fetchTopology = async (subdomain, token) => {
|
|
104449
|
+
const fetchTopology = async (subdomain, token, brandIds, brandSubdomain) => {
|
|
104007
104450
|
const pageParams = { "page[size]": String(100) };
|
|
104451
|
+
const hcSubdomain = brandSubdomain ?? subdomain;
|
|
104008
104452
|
const [locales, categoriesRes, sectionsRes, segments, perms, meRes] = await Promise.all([
|
|
104009
|
-
helpCenterGet(
|
|
104010
|
-
helpCenterGet(
|
|
104011
|
-
helpCenterGet(
|
|
104453
|
+
helpCenterGet(hcSubdomain, token, "/locales"),
|
|
104454
|
+
helpCenterGet(hcSubdomain, token, "/categories", pageParams),
|
|
104455
|
+
helpCenterGet(hcSubdomain, token, "/sections", pageParams),
|
|
104012
104456
|
tolerate403(helpCenterGet(subdomain, token, "/user_segments"), { user_segments: [] }),
|
|
104013
104457
|
tolerate403(zendeskGet(subdomain, token, "/guide/permission_groups"), { permission_groups: [] }),
|
|
104014
104458
|
zendeskGet(subdomain, token, "/users/me")
|
|
@@ -104017,6 +104461,7 @@ const fetchTopology = async (subdomain, token) => {
|
|
|
104017
104461
|
const sections = sectionsRes.sections ?? [];
|
|
104018
104462
|
return {
|
|
104019
104463
|
subdomain,
|
|
104464
|
+
...brandIds === void 0 ? {} : { brandIds },
|
|
104020
104465
|
locales,
|
|
104021
104466
|
categories,
|
|
104022
104467
|
sections,
|
|
@@ -104068,10 +104513,20 @@ const renderAdminSection = (items, denied, deniedNote) => {
|
|
|
104068
104513
|
};
|
|
104069
104514
|
/** Render the topology as a compact Markdown document for the LLM context. */
|
|
104070
104515
|
const formatTopology = (data) => {
|
|
104516
|
+
let brandNote;
|
|
104517
|
+
let allowedBrandsLine = [];
|
|
104518
|
+
if (data.brandIds === void 0) brandNote = "";
|
|
104519
|
+
else if (data.brandIds.length === 1 && data.brandIds[0] === "all") brandNote = " (all brands; tree below: account default)";
|
|
104520
|
+
else if (data.brandIds.length === 1) brandNote = ` (brand ${data.brandIds[0]})`;
|
|
104521
|
+
else {
|
|
104522
|
+
brandNote = ` (brand ${data.brandIds[0]})`;
|
|
104523
|
+
allowedBrandsLine = ["", `**Allowed brands**: ${data.brandIds.join(", ")}`];
|
|
104524
|
+
}
|
|
104071
104525
|
const text = [
|
|
104072
|
-
`# Zendesk Help Center topology — ${data.subdomain}`,
|
|
104526
|
+
`# Zendesk Help Center topology — ${data.subdomain}${brandNote}`,
|
|
104073
104527
|
"",
|
|
104074
104528
|
`**Your access**: ${data.currentUser.name} (id ${data.currentUser.id}), role "${data.currentUser.role}".`,
|
|
104529
|
+
...allowedBrandsLine,
|
|
104075
104530
|
"",
|
|
104076
104531
|
"## Locales",
|
|
104077
104532
|
`- Default: ${data.locales.default_locale}`,
|
|
@@ -104098,14 +104553,14 @@ const formatTopology = (data) => {
|
|
|
104098
104553
|
* token. A 401 notifies `onUnauthorized` (stdio OAuth only) to invalidate the
|
|
104099
104554
|
* stale token, mirroring the tool dispatch path in `server.ts`.
|
|
104100
104555
|
*/
|
|
104101
|
-
const createTopologyProvider = (getToken, subdomain, onUnauthorized) => {
|
|
104556
|
+
const createTopologyProvider = (getToken, subdomain, onUnauthorized, brandIds, resolveBrandSubdomain = () => Promise.resolve(void 0)) => {
|
|
104102
104557
|
let cached;
|
|
104103
104558
|
return { read() {
|
|
104104
104559
|
const now = Date.now();
|
|
104105
104560
|
if (cached && now - cached.at < 3e5) return cached.promise;
|
|
104106
104561
|
const promise = (async () => {
|
|
104107
|
-
const token = await getToken();
|
|
104108
|
-
return formatTopology(await fetchTopology(subdomain, token));
|
|
104562
|
+
const [token, brandSubdomain] = await Promise.all([getToken(), resolveBrandSubdomain()]);
|
|
104563
|
+
return formatTopology(await fetchTopology(subdomain, token, brandIds, brandSubdomain));
|
|
104109
104564
|
})().catch((err) => {
|
|
104110
104565
|
cached = void 0;
|
|
104111
104566
|
if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
|
|
@@ -104149,7 +104604,6 @@ const filterTools = (allTools, options) => allTools.filter((tool) => {
|
|
|
104149
104604
|
if (options.readOnly && !tool.readOnly) return false;
|
|
104150
104605
|
if (options.namespaces?.length && !options.namespaces.includes(tool.namespace)) return false;
|
|
104151
104606
|
if (options.tools?.length && !options.tools.includes(tool.name)) return false;
|
|
104152
|
-
if (options.promotedArticles === false && tool.name === "list_promoted_articles") return false;
|
|
104153
104607
|
return true;
|
|
104154
104608
|
});
|
|
104155
104609
|
const groupByNamespace = (tools) => {
|
|
@@ -104267,6 +104721,50 @@ const isPlacedAsRequested = (effectiveAfter, movedId, target, referenceId) => {
|
|
|
104267
104721
|
//#endregion
|
|
104268
104722
|
//#region src/tools/help-center.ts
|
|
104269
104723
|
const ARTICLE_ID_DESC = "Article ID — the numeric id of the Help Center article. Obtain it from list_articles or search_articles.";
|
|
104724
|
+
const brandIdField = (brandIds) => {
|
|
104725
|
+
if (!brandIds || brandIds.length === 0) return void 0;
|
|
104726
|
+
if (brandIds.length === 1 && brandIds[0] !== "all") return void 0;
|
|
104727
|
+
const description = brandIds[0] === "all" ? "Brand of the Help Center to operate on, by id or subdomain. Required: any brand from list_brands." : `Brand of the Help Center to operate on, by id or subdomain. Required: one of ${brandIds.join(", ")}.`;
|
|
104728
|
+
return union([number$2().int(), string$2().min(1)]).describe(description);
|
|
104729
|
+
};
|
|
104730
|
+
/**
|
|
104731
|
+
* Resolve which brand a call targets, enforcing the deploy allow-list.
|
|
104732
|
+
*
|
|
104733
|
+
* - unset: no brand override; a per-call brand_id is rejected (schema has no
|
|
104734
|
+
* field for it, so this is a backstop for hand-crafted calls).
|
|
104735
|
+
* - single: the configured entry is the only brand; ANY per-call brand_id is
|
|
104736
|
+
* rejected (hard lock, no escape — the schema has no field for it).
|
|
104737
|
+
* - multi: brand_id is REQUIRED; the per-call id is resolved and must match an
|
|
104738
|
+
* allowed entry by id or resolved subdomain.
|
|
104739
|
+
* - all: brand_id is REQUIRED; any existing brand passes (validated by the
|
|
104740
|
+
* resolver).
|
|
104741
|
+
*
|
|
104742
|
+
* Returns the brand's subdomain, or undefined for the account default.
|
|
104743
|
+
*/
|
|
104744
|
+
const resolveBrand = async (perCall, brandIds, resolveSubdomain) => {
|
|
104745
|
+
if (!brandIds || brandIds.length === 0) {
|
|
104746
|
+
if (perCall !== void 0) throw new Error("brand_id is not accepted: this server was started without --brand-ids, so it targets the account default brand.");
|
|
104747
|
+
return;
|
|
104748
|
+
}
|
|
104749
|
+
const isAll = brandIds.length === 1 && brandIds[0] === "all";
|
|
104750
|
+
if (brandIds.length === 1 && !isAll) {
|
|
104751
|
+
const locked = brandIds[0];
|
|
104752
|
+
if (perCall !== void 0) throw new Error(`brand_id is not accepted: this server was started with --brand-ids ${locked}, which locks every Help Center operation to that brand. Omit brand_id.`);
|
|
104753
|
+
return resolveSubdomain(locked);
|
|
104754
|
+
}
|
|
104755
|
+
if (perCall === void 0) throw new Error(`brand_id is required: this server was started with --brand-ids ${brandIds.join(", ")}, so every brand-scoped Help Center call must name one.`);
|
|
104756
|
+
const resolved = await resolveSubdomain(String(perCall));
|
|
104757
|
+
if (isAll) return resolved;
|
|
104758
|
+
await assertAllowed(perCall, resolved, brandIds, resolveSubdomain);
|
|
104759
|
+
return resolved;
|
|
104760
|
+
};
|
|
104761
|
+
const assertAllowed = async (perCall, resolved, brandIds, resolveSubdomain) => {
|
|
104762
|
+
if (brandIds.includes(String(perCall))) return;
|
|
104763
|
+
const settled = await Promise.allSettled(brandIds.map((entry) => resolveSubdomain(entry)));
|
|
104764
|
+
const allowed = new Set(brandIds);
|
|
104765
|
+
for (const r of settled) if (r.status === "fulfilled") allowed.add(r.value);
|
|
104766
|
+
if (!allowed.has(resolved)) throw new Error(`brand_id ${perCall} is not allowed: this server was started with --brand-ids ${brandIds.join(", ")}. Allowed: ${[...allowed].join(", ")}.`);
|
|
104767
|
+
};
|
|
104270
104768
|
const listTranslations = (subdomain, token, articleId) => helpCenterGet(subdomain, token, `/articles/${articleId}/translations`).then((res) => res.translations);
|
|
104271
104769
|
const NODE_LABEL = {
|
|
104272
104770
|
sections: "section",
|
|
@@ -104467,13 +104965,54 @@ const structureWarning = (r) => {
|
|
|
104467
104965
|
return [];
|
|
104468
104966
|
};
|
|
104469
104967
|
const sectionUpdateNotes = (r) => [...r.stripped ? [`Note: removed a leading <${r.stripped.tag}> "${r.stripped.text}" from content; the section heading is kept automatically and is not part of content.`] : [], ...structureWarning(r)];
|
|
104968
|
+
/**
|
|
104969
|
+
* Build the `list_brands` tool when the server runs in multi/all mode; an empty
|
|
104970
|
+
* array otherwise. It is the discovery source for the required per-call
|
|
104971
|
+
* `brand_id`: in single-lock or unset mode the brand is implicit, the schemas
|
|
104972
|
+
* carry no `brand_id` field, and the tool stays hidden. In multi mode (not
|
|
104973
|
+
* 'all') the listing is filtered to the allowed brands.
|
|
104974
|
+
*/
|
|
104975
|
+
const createListBrandsTool = (ctx) => {
|
|
104976
|
+
const { subdomain, brandIds, getToken } = ctx;
|
|
104977
|
+
if (brandIds === void 0) return [];
|
|
104978
|
+
const isAll = brandIds.length === 1 && brandIds[0] === "all";
|
|
104979
|
+
if (brandIds.length === 1 && !isAll) return [];
|
|
104980
|
+
return [{
|
|
104981
|
+
name: "list_brands",
|
|
104982
|
+
namespace: "help_center",
|
|
104983
|
+
readOnly: true,
|
|
104984
|
+
title: "List Zendesk Brands",
|
|
104985
|
+
description: "List the Zendesk account's brands (id, name, URL, default/active flags) from the Support API. Multi-brand Guide accounts have one Help Center per brand: find the brand id here, then pass it as brand_id (id or subdomain) — REQUIRED on every brand-scoped Help Center tool in multi/all mode.",
|
|
104986
|
+
inputSchema: object({}),
|
|
104987
|
+
annotations: {
|
|
104988
|
+
readOnlyHint: true,
|
|
104989
|
+
destructiveHint: false,
|
|
104990
|
+
idempotentHint: true,
|
|
104991
|
+
openWorldHint: true
|
|
104992
|
+
},
|
|
104993
|
+
handler: async () => {
|
|
104994
|
+
const token = await getToken();
|
|
104995
|
+
const brands = await fetchAllBrands(subdomain, token);
|
|
104996
|
+
const visible = isAll ? brands : brands.filter((b) => brandIds.includes(String(b.id)) || brandIds.includes(b.subdomain));
|
|
104997
|
+
return { content: [{
|
|
104998
|
+
type: "text",
|
|
104999
|
+
text: formatList(visible, formatBrand, void 0, "Pass a brand id as brand_id to any Help Center tool to operate on that brand.")
|
|
105000
|
+
}] };
|
|
105001
|
+
}
|
|
105002
|
+
}];
|
|
105003
|
+
};
|
|
104470
105004
|
const createHelpCenterTools = (ctx) => {
|
|
104471
|
-
const { subdomain, getToken } = ctx;
|
|
104472
|
-
const
|
|
105005
|
+
const { subdomain, brandIds, resolveBrandSubdomain, getToken } = ctx;
|
|
105006
|
+
const brandIdSchema = brandIdField(brandIds);
|
|
105007
|
+
const brandIdPart = brandIdSchema ? { brand_id: brandIdSchema } : {};
|
|
105008
|
+
const resolveHcSubdomain = async (brand_id) => {
|
|
105009
|
+
return await resolveBrand(brand_id, brandIds, resolveBrandSubdomain) ?? subdomain;
|
|
105010
|
+
};
|
|
105011
|
+
const fetchSectionOrder = async (sectionId, locale, token, hcSubdomain) => {
|
|
104473
105012
|
const order = [];
|
|
104474
105013
|
let cursor;
|
|
104475
105014
|
do {
|
|
104476
|
-
const response = await helpCenterGet(
|
|
105015
|
+
const response = await helpCenterGet(hcSubdomain, token, `/${locale}/sections/${sectionId}/articles`, buildCursorParams(100, cursor));
|
|
104477
105016
|
const articles = response.articles ?? [];
|
|
104478
105017
|
for (const article of articles) order.push({
|
|
104479
105018
|
id: article.id,
|
|
@@ -104484,19 +105023,19 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104484
105023
|
} while (cursor);
|
|
104485
105024
|
return order;
|
|
104486
105025
|
};
|
|
104487
|
-
const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token) => {
|
|
105026
|
+
const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token, hcSubdomain) => {
|
|
104488
105027
|
if (effective.some((a) => a.id === referenceArticleId)) return;
|
|
104489
105028
|
let detail = "was not found";
|
|
104490
105029
|
try {
|
|
104491
|
-
const { article: ref } = await helpCenterGet(
|
|
105030
|
+
const { article: ref } = await helpCenterGet(hcSubdomain, token, `/articles/${referenceArticleId}`);
|
|
104492
105031
|
detail = `is in section #${ref.section_id}, not section #${sectionId}`;
|
|
104493
105032
|
} catch {}
|
|
104494
105033
|
throw new Error(`Reference article #${referenceArticleId} ${detail}. It must be in the same section (#${sectionId}) as article #${articleId}.`);
|
|
104495
105034
|
};
|
|
104496
|
-
const applyPositionWrites = async (writes, articleId, token) => {
|
|
105035
|
+
const applyPositionWrites = async (writes, articleId, token, hcSubdomain) => {
|
|
104497
105036
|
let applied = 0;
|
|
104498
105037
|
for (const write of writes) try {
|
|
104499
|
-
await helpCenterPut(
|
|
105038
|
+
await helpCenterPut(hcSubdomain, token, `/articles/${write.id}`, { article: { position: write.position } });
|
|
104500
105039
|
applied += 1;
|
|
104501
105040
|
} catch (error) {
|
|
104502
105041
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -104505,6 +105044,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104505
105044
|
return applied;
|
|
104506
105045
|
};
|
|
104507
105046
|
return [
|
|
105047
|
+
...createListBrandsTool(ctx),
|
|
104508
105048
|
{
|
|
104509
105049
|
name: "search_articles",
|
|
104510
105050
|
namespace: "help_center",
|
|
@@ -104515,7 +105055,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104515
105055
|
query: string$2().min(1).describe("Full-text query matched against article titles and body. Plain keywords; combine with the locale filter to scope to one language."),
|
|
104516
105056
|
locale: string$2().optional().describe("Filter by locale (e.g., \"en-us\", \"fr\")"),
|
|
104517
105057
|
per_page: number$2().int().min(1).max(100).default(100).describe(PER_PAGE_DESC),
|
|
104518
|
-
page: number$2().int().min(1).default(1).describe(PAGE_DESC)
|
|
105058
|
+
page: number$2().int().min(1).default(1).describe(PAGE_DESC),
|
|
105059
|
+
...brandIdPart
|
|
104519
105060
|
}),
|
|
104520
105061
|
annotations: {
|
|
104521
105062
|
readOnlyHint: true,
|
|
@@ -104524,14 +105065,15 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104524
105065
|
openWorldHint: true
|
|
104525
105066
|
},
|
|
104526
105067
|
handler: async (params) => {
|
|
104527
|
-
const { query, locale, per_page, page } = params;
|
|
105068
|
+
const { query, locale, per_page, page, brand_id } = params;
|
|
104528
105069
|
const token = await getToken();
|
|
105070
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104529
105071
|
const p = {
|
|
104530
105072
|
query,
|
|
104531
105073
|
...buildOffsetParams(per_page, page)
|
|
104532
105074
|
};
|
|
104533
105075
|
if (locale) p["locale"] = locale;
|
|
104534
|
-
const response = await helpCenterGet(
|
|
105076
|
+
const response = await helpCenterGet(hcSubdomain, token, "/articles/search", p);
|
|
104535
105077
|
return { content: [{
|
|
104536
105078
|
type: "text",
|
|
104537
105079
|
text: formatList(response.results ?? [], formatArticleSummary, extractSearchPaginationMeta(response, per_page, page))
|
|
@@ -104546,7 +105088,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104546
105088
|
description: "Retrieve an article by ID with full body content. For large articles, prefer get_article_outline + get_article_section to save tokens. Optionally specify locale for a translated version. Returns body (HTML), metadata, source_locale, and list of available translations.",
|
|
104547
105089
|
inputSchema: object({
|
|
104548
105090
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
104549
|
-
locale: string$2().optional().describe("Locale for translated version")
|
|
105091
|
+
locale: string$2().optional().describe("Locale for translated version"),
|
|
105092
|
+
...brandIdPart
|
|
104550
105093
|
}),
|
|
104551
105094
|
annotations: {
|
|
104552
105095
|
readOnlyHint: true,
|
|
@@ -104555,11 +105098,12 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104555
105098
|
openWorldHint: true
|
|
104556
105099
|
},
|
|
104557
105100
|
handler: async (params) => {
|
|
104558
|
-
const { article_id, locale } = params;
|
|
105101
|
+
const { article_id, locale, brand_id } = params;
|
|
104559
105102
|
const token = await getToken();
|
|
105103
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104560
105104
|
const path = locale ? `/${locale}/articles/${article_id}` : `/articles/${article_id}`;
|
|
104561
|
-
const { article } = await helpCenterGet(
|
|
104562
|
-
const translations = await listTranslations(
|
|
105105
|
+
const { article } = await helpCenterGet(hcSubdomain, token, path, void 0);
|
|
105106
|
+
const translations = await listTranslations(hcSubdomain, token, article_id);
|
|
104563
105107
|
const text = (largeArticleHint(article.body, parseSections(article.body).length) ?? "") + formatArticle(article) + `\n\n**Available translations**: ${translations.map((t) => t.locale).join(", ")}`;
|
|
104564
105108
|
return { content: [{
|
|
104565
105109
|
type: "text",
|
|
@@ -104576,7 +105120,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104576
105120
|
inputSchema: object({
|
|
104577
105121
|
locale: string$2().optional().describe("Locale for category names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
|
|
104578
105122
|
page_size: number$2().int().min(1).max(100).default(100).describe("Categories per page (1-100, default 100)."),
|
|
104579
|
-
cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page.")
|
|
105123
|
+
cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
|
|
105124
|
+
...brandIdPart
|
|
104580
105125
|
}),
|
|
104581
105126
|
annotations: {
|
|
104582
105127
|
readOnlyHint: true,
|
|
@@ -104585,10 +105130,11 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104585
105130
|
openWorldHint: true
|
|
104586
105131
|
},
|
|
104587
105132
|
handler: async (params) => {
|
|
104588
|
-
const { locale, page_size, cursor } = params;
|
|
105133
|
+
const { locale, page_size, cursor, brand_id } = params;
|
|
104589
105134
|
const token = await getToken();
|
|
105135
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104590
105136
|
const path = `${localePrefix(locale)}/categories`;
|
|
104591
|
-
const response = await helpCenterGet(
|
|
105137
|
+
const response = await helpCenterGet(hcSubdomain, token, path, buildCursorParams(page_size, cursor));
|
|
104592
105138
|
const categories = response.categories ?? [];
|
|
104593
105139
|
return { content: [{
|
|
104594
105140
|
type: "text",
|
|
@@ -104606,7 +105152,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104606
105152
|
category_id: number$2().int().optional().describe("Restrict to sections of this category (id from list_categories). Omit to list every section."),
|
|
104607
105153
|
locale: string$2().optional().describe("Locale for section names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
|
|
104608
105154
|
page_size: number$2().int().min(1).max(100).default(100).describe("Sections per page (1-100, default 100)."),
|
|
104609
|
-
cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page.")
|
|
105155
|
+
cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
|
|
105156
|
+
...brandIdPart
|
|
104610
105157
|
}),
|
|
104611
105158
|
annotations: {
|
|
104612
105159
|
readOnlyHint: true,
|
|
@@ -104615,9 +105162,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104615
105162
|
openWorldHint: true
|
|
104616
105163
|
},
|
|
104617
105164
|
handler: async (params) => {
|
|
104618
|
-
const { category_id, locale, page_size, cursor } = params;
|
|
105165
|
+
const { category_id, locale, page_size, cursor, brand_id } = params;
|
|
104619
105166
|
const token = await getToken();
|
|
104620
|
-
const
|
|
105167
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105168
|
+
const response = await helpCenterGet(hcSubdomain, token, sectionListPath(category_id, locale), buildCursorParams(page_size, cursor));
|
|
104621
105169
|
const sections = response.sections ?? [];
|
|
104622
105170
|
return { content: [{
|
|
104623
105171
|
type: "text",
|
|
@@ -104643,7 +105191,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104643
105191
|
"title"
|
|
104644
105192
|
]).default("position").describe("Field to sort by; \"position\" (the default) is the manual order set in Guide."),
|
|
104645
105193
|
sort_order: _enum(["asc", "desc"]).default("asc").describe("Sort direction: ascending or descending."),
|
|
104646
|
-
include_translations: boolean$1().default(false).describe("Include available translation locales per article (causes 1 extra API call per article)")
|
|
105194
|
+
include_translations: boolean$1().default(false).describe("Include available translation locales per article (causes 1 extra API call per article)"),
|
|
105195
|
+
...brandIdPart
|
|
104647
105196
|
}),
|
|
104648
105197
|
annotations: {
|
|
104649
105198
|
readOnlyHint: true,
|
|
@@ -104652,9 +105201,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104652
105201
|
openWorldHint: true
|
|
104653
105202
|
},
|
|
104654
105203
|
handler: async (params) => {
|
|
104655
|
-
const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations } = params;
|
|
105204
|
+
const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations, brand_id } = params;
|
|
104656
105205
|
const token = await getToken();
|
|
104657
|
-
const
|
|
105206
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105207
|
+
const response = await helpCenterGet(hcSubdomain, token, articleListPath(section_id, locale), {
|
|
104658
105208
|
...buildCursorParams(page_size, cursor),
|
|
104659
105209
|
sort_by,
|
|
104660
105210
|
sort_order
|
|
@@ -104665,7 +105215,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104665
105215
|
text: formatList(articles, formatArticleSummary, extractPaginationMeta(response, articles.length))
|
|
104666
105216
|
}] };
|
|
104667
105217
|
const formatted = await Promise.all(articles.map(async (article) => {
|
|
104668
|
-
const locales = (await listTranslations(
|
|
105218
|
+
const locales = (await listTranslations(hcSubdomain, token, article.id)).map((t) => t.locale).join(", ");
|
|
104669
105219
|
return `${formatArticleSummary(article)}\n- **Translations**: ${locales}`;
|
|
104670
105220
|
}));
|
|
104671
105221
|
const meta = extractPaginationMeta(response, articles.length);
|
|
@@ -104682,23 +105232,25 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104682
105232
|
readOnly: true,
|
|
104683
105233
|
title: "List Promoted Help Center Articles",
|
|
104684
105234
|
description: "List the promoted (\"featured\") Help Center articles — the small, editorially-curated set surfaced at the top of their sections. Returns metadata only (no body); use get_article for full content. COST: the Help Center API has no server-side promoted filter, so this scans article pages (one Zendesk API request per page, up to ARTICLE_RESOURCES_SCAN_MAX_PAGES, default 20) and filters client-side — potentially costly on a large Help Center. Each call performs a fresh, uncached scan, so avoid calling it repeatedly. On a very large Help Center some promoted articles may be omitted, and both the omission and the number of pages scanned are flagged in the output. Lists the default locale. To promote or unpromote an article, use update_article with `promoted` (requires Help Center admin / Guide admin rights).",
|
|
104685
|
-
inputSchema: object({}),
|
|
105235
|
+
inputSchema: object({ ...brandIdPart }),
|
|
104686
105236
|
annotations: {
|
|
104687
105237
|
readOnlyHint: true,
|
|
104688
105238
|
destructiveHint: false,
|
|
104689
105239
|
idempotentHint: true,
|
|
104690
105240
|
openWorldHint: true
|
|
104691
105241
|
},
|
|
104692
|
-
handler: async () => {
|
|
105242
|
+
handler: async (params) => {
|
|
105243
|
+
const { brand_id } = params;
|
|
104693
105244
|
const token = await getToken();
|
|
104694
|
-
const
|
|
105245
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105246
|
+
const { articles, truncated, pagesScanned } = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
|
|
104695
105247
|
const header = `Promoted (featured) articles: ${articles.length}`;
|
|
104696
105248
|
const body = articles.length ? articles.map(formatArticleSummary).join("\n\n") : "_No promoted articles found._";
|
|
104697
105249
|
const cost = `${pagesScanned} Zendesk API request${pagesScanned === 1 ? "" : "s"}`;
|
|
104698
105250
|
const note = scanCostNote(truncated, pagesScanned, cost);
|
|
104699
105251
|
return { content: [{
|
|
104700
105252
|
type: "text",
|
|
104701
|
-
text: truncateIfNeeded(`${header}\n\n${body}${note}`, "list_promoted_articles takes no parameters, so this listing cannot be narrowed from the call; read a single article with get_article.")
|
|
105253
|
+
text: truncateIfNeeded(`${header}\n\n${body}${note}`, "list_promoted_articles takes no filter parameters, so this listing cannot be narrowed from the call; read a single article with get_article.")
|
|
104702
105254
|
}] };
|
|
104703
105255
|
}
|
|
104704
105256
|
},
|
|
@@ -104708,7 +105260,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104708
105260
|
readOnly: true,
|
|
104709
105261
|
title: "List Article Translations",
|
|
104710
105262
|
description: "List all available translations for an article (metadata only, no body: locale, title, draft, updated_at). Use get_article with locale for full translated content.",
|
|
104711
|
-
inputSchema: object({
|
|
105263
|
+
inputSchema: object({
|
|
105264
|
+
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105265
|
+
...brandIdPart
|
|
105266
|
+
}),
|
|
104712
105267
|
annotations: {
|
|
104713
105268
|
readOnlyHint: true,
|
|
104714
105269
|
destructiveHint: false,
|
|
@@ -104716,9 +105271,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104716
105271
|
openWorldHint: true
|
|
104717
105272
|
},
|
|
104718
105273
|
handler: async (params) => {
|
|
104719
|
-
const { article_id } = params;
|
|
105274
|
+
const { article_id, brand_id } = params;
|
|
104720
105275
|
const token = await getToken();
|
|
104721
|
-
const
|
|
105276
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105277
|
+
const translations = await listTranslations(hcSubdomain, token, article_id);
|
|
104722
105278
|
return { content: [{
|
|
104723
105279
|
type: "text",
|
|
104724
105280
|
text: formatList(translations, formatTranslationSummary, void 0, "list_article_translations takes only article_id, so this listing cannot be narrowed from the call; read one locale in full with get_article.")
|
|
@@ -104736,7 +105292,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104736
105292
|
locale: string$2().describe("Target locale (e.g., \"fr\", \"de\")"),
|
|
104737
105293
|
title: string$2().min(1).describe("Translated article title."),
|
|
104738
105294
|
body: string$2().min(1).describe("Translated body (HTML)"),
|
|
104739
|
-
draft: boolean$1().default(false).describe("Create the translation as a draft (not visible to end users). Defaults to false (published).")
|
|
105295
|
+
draft: boolean$1().default(false).describe("Create the translation as a draft (not visible to end users). Defaults to false (published)."),
|
|
105296
|
+
...brandIdPart
|
|
104740
105297
|
}),
|
|
104741
105298
|
annotations: {
|
|
104742
105299
|
readOnlyHint: false,
|
|
@@ -104745,9 +105302,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104745
105302
|
openWorldHint: true
|
|
104746
105303
|
},
|
|
104747
105304
|
handler: async (params) => {
|
|
104748
|
-
const { article_id, locale, title, body, draft } = params;
|
|
105305
|
+
const { article_id, locale, title, body, draft, brand_id } = params;
|
|
104749
105306
|
const token = await getToken();
|
|
104750
|
-
const
|
|
105307
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105308
|
+
const { translation } = await helpCenterPost(hcSubdomain, token, `/articles/${article_id}/translations`, { translation: {
|
|
104751
105309
|
locale,
|
|
104752
105310
|
title,
|
|
104753
105311
|
body,
|
|
@@ -104770,7 +105328,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104770
105328
|
locale: string$2().describe("Locale of the translation to update, e.g. \"en-us\" or \"fr\". Use the source_locale (from get_article) to edit the default language."),
|
|
104771
105329
|
title: string$2().optional().describe("New title for this locale. Omit to leave the current title unchanged."),
|
|
104772
105330
|
body: string$2().optional().describe("New full body (HTML) for this locale. Replaces the entire body — for a single-section edit prefer update_article_section. Omit to leave the body unchanged."),
|
|
104773
|
-
draft: boolean$1().optional().describe("When true, keeps this translation as a draft; when false, publishes it.")
|
|
105331
|
+
draft: boolean$1().optional().describe("When true, keeps this translation as a draft; when false, publishes it."),
|
|
105332
|
+
...brandIdPart
|
|
104774
105333
|
}),
|
|
104775
105334
|
annotations: {
|
|
104776
105335
|
readOnlyHint: false,
|
|
@@ -104779,9 +105338,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104779
105338
|
openWorldHint: true
|
|
104780
105339
|
},
|
|
104781
105340
|
handler: async (params) => {
|
|
104782
|
-
const { article_id, locale, ...updates } = params;
|
|
105341
|
+
const { article_id, locale, brand_id, ...updates } = params;
|
|
104783
105342
|
const token = await getToken();
|
|
104784
|
-
const
|
|
105343
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105344
|
+
const { translation } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
|
|
104785
105345
|
return { content: [{
|
|
104786
105346
|
type: "text",
|
|
104787
105347
|
text: `Translation updated for article #${article_id} in "${locale}".\n\n${formatTranslation(translation)}`
|
|
@@ -104794,7 +105354,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104794
105354
|
readOnly: true,
|
|
104795
105355
|
title: "List Section Translations",
|
|
104796
105356
|
description: "List the translations of a Help Center section: for each locale, the localized name, whether a description is set, and whether the translation is published or still a draft. Reach for this when a section looks wrong in a locale, because list_sections with that locale cannot settle it: a section with no translation is omitted from it, while a section whose translation is an unpublished draft may still be listed there under the draft name — so appearing in that listing does not mean published, and the draft flag here is what decides. Fix either case with set_section_translation; to sweep every category and section at once, use find_translation_gaps.",
|
|
104797
|
-
inputSchema: object({
|
|
105357
|
+
inputSchema: object({
|
|
105358
|
+
section_id: number$2().int().describe("Section ID — the numeric id of the Help Center section. Obtain it from list_sections or the zendesk-hc://topology resource."),
|
|
105359
|
+
...brandIdPart
|
|
105360
|
+
}),
|
|
104798
105361
|
annotations: {
|
|
104799
105362
|
readOnlyHint: true,
|
|
104800
105363
|
destructiveHint: false,
|
|
@@ -104802,9 +105365,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104802
105365
|
openWorldHint: true
|
|
104803
105366
|
},
|
|
104804
105367
|
handler: async (params) => {
|
|
104805
|
-
const { section_id } = params;
|
|
105368
|
+
const { section_id, brand_id } = params;
|
|
104806
105369
|
const token = await getToken();
|
|
104807
|
-
const
|
|
105370
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105371
|
+
const translations = await listNodeTranslations(hcSubdomain, token, "sections", section_id, void 0);
|
|
104808
105372
|
return { content: [{
|
|
104809
105373
|
type: "text",
|
|
104810
105374
|
text: formatList(translations, formatNodeTranslationSummary, void 0, "list_section_translations takes only section_id, so this listing cannot be narrowed from the call; write one locale with set_section_translation.")
|
|
@@ -104817,7 +105381,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104817
105381
|
readOnly: true,
|
|
104818
105382
|
title: "List Category Translations",
|
|
104819
105383
|
description: "List the translations of a Help Center category: for each locale, the localized name, whether a description is set, and whether the translation is published or still a draft. Reach for this when a category looks wrong in a locale, because list_categories with that locale cannot settle it: a category with no translation is omitted from it, while a category whose translation is an unpublished draft may still be listed there under the draft name — so appearing in that listing does not mean published, and the draft flag here is what decides. Fix either case with set_category_translation; to sweep every category and section at once, use find_translation_gaps.",
|
|
104820
|
-
inputSchema: object({
|
|
105384
|
+
inputSchema: object({
|
|
105385
|
+
category_id: number$2().int().describe("Category ID — the numeric id of the Help Center category. Obtain it from list_categories or the zendesk-hc://topology resource."),
|
|
105386
|
+
...brandIdPart
|
|
105387
|
+
}),
|
|
104821
105388
|
annotations: {
|
|
104822
105389
|
readOnlyHint: true,
|
|
104823
105390
|
destructiveHint: false,
|
|
@@ -104825,9 +105392,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104825
105392
|
openWorldHint: true
|
|
104826
105393
|
},
|
|
104827
105394
|
handler: async (params) => {
|
|
104828
|
-
const { category_id } = params;
|
|
105395
|
+
const { category_id, brand_id } = params;
|
|
104829
105396
|
const token = await getToken();
|
|
104830
|
-
const
|
|
105397
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105398
|
+
const translations = await listNodeTranslations(hcSubdomain, token, "categories", category_id, void 0);
|
|
104831
105399
|
return { content: [{
|
|
104832
105400
|
type: "text",
|
|
104833
105401
|
text: formatList(translations, formatNodeTranslationSummary, void 0, "list_category_translations takes only category_id, so this listing cannot be narrowed from the call; write one locale with set_category_translation.")
|
|
@@ -104842,7 +105410,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104842
105410
|
description: "Audit the Help Center tree for a target locale and report every category and section that has no translation, or one that is still an unpublished draft. Use it before or after translating articles: an article published in a second locale is unreachable while its parent section only exists in the source locale. Listing sections in that locale cannot answer this — a node with no translation is simply absent, without saying why, and a node whose translation is an unpublished draft may still be listed under its draft name — so this audit reads the draft flag on each node instead of trusting that listing. Costs two listings and covers up to 100 categories and 100 sections; past that only the first page of each level is audited, and the report says so — pass category_id to narrow it. Fix what it reports with set_section_translation / set_category_translation.",
|
|
104843
105411
|
inputSchema: object({
|
|
104844
105412
|
locale: string$2().describe("Locale to audit, e.g. \"fr\" or \"de\" — usually a non-default active locale of the Help Center (zendesk-hc://topology lists them). A locale that is not active is reported as a warning, since every node would then look untranslated."),
|
|
104845
|
-
category_id: number$2().int().optional().describe("Restrict the audit to this category and the sections it contains (id from list_categories). Omit to sweep the whole tree, which costs two listings and covers up to 100 categories and 100 sections.")
|
|
105413
|
+
category_id: number$2().int().optional().describe("Restrict the audit to this category and the sections it contains (id from list_categories). Omit to sweep the whole tree, which costs two listings and covers up to 100 categories and 100 sections."),
|
|
105414
|
+
...brandIdPart
|
|
104846
105415
|
}),
|
|
104847
105416
|
annotations: {
|
|
104848
105417
|
readOnlyHint: true,
|
|
@@ -104851,12 +105420,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104851
105420
|
openWorldHint: true
|
|
104852
105421
|
},
|
|
104853
105422
|
handler: async (params) => {
|
|
104854
|
-
const { locale, category_id } = params;
|
|
105423
|
+
const { locale, category_id, brand_id } = params;
|
|
104855
105424
|
const token = await getToken();
|
|
105425
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104856
105426
|
const [locales, categoryScope, sectionsRes] = await Promise.all([
|
|
104857
|
-
helpCenterGet(
|
|
104858
|
-
fetchGapCategories(
|
|
104859
|
-
helpCenterGet(
|
|
105427
|
+
helpCenterGet(hcSubdomain, token, "/locales", void 0),
|
|
105428
|
+
fetchGapCategories(hcSubdomain, token, category_id),
|
|
105429
|
+
helpCenterGet(hcSubdomain, token, sectionListPath(category_id, void 0), {
|
|
104860
105430
|
...buildCursorParams(100, void 0),
|
|
104861
105431
|
include: TRANSLATIONS_SIDELOAD
|
|
104862
105432
|
})
|
|
@@ -104899,7 +105469,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104899
105469
|
locale: string$2().describe("Locale to write, e.g. \"fr\" or \"de\". Must be an active locale of the Help Center (zendesk-hc://topology lists them); list_section_translations shows which ones the section already has."),
|
|
104900
105470
|
name: string$2().min(1).optional().describe("Localized section name for this locale (sent as the API's translation `title`). Required when the locale has no translation yet; omit on an existing one to leave its name untouched, for instance when only publishing a draft."),
|
|
104901
105471
|
description: string$2().optional().describe("Localized section description for this locale (sent as the API's translation `body`). Omit to leave an existing description untouched; pass an empty string to clear it."),
|
|
104902
|
-
draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the section visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged.")
|
|
105472
|
+
draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the section visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged."),
|
|
105473
|
+
...brandIdPart
|
|
104903
105474
|
}),
|
|
104904
105475
|
annotations: {
|
|
104905
105476
|
readOnlyHint: false,
|
|
@@ -104908,9 +105479,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104908
105479
|
openWorldHint: true
|
|
104909
105480
|
},
|
|
104910
105481
|
handler: async (params) => {
|
|
104911
|
-
const { section_id, ...input } = params;
|
|
105482
|
+
const { section_id, brand_id, ...input } = params;
|
|
104912
105483
|
const token = await getToken();
|
|
104913
|
-
const
|
|
105484
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105485
|
+
const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "sections", section_id, input);
|
|
104914
105486
|
return { content: [{
|
|
104915
105487
|
type: "text",
|
|
104916
105488
|
text: nodeTranslationWriteText("sections", section_id, translation, created)
|
|
@@ -104928,7 +105500,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104928
105500
|
locale: string$2().describe("Locale to write, e.g. \"fr\" or \"de\". Must be an active locale of the Help Center (zendesk-hc://topology lists them); list_category_translations shows which ones the category already has."),
|
|
104929
105501
|
name: string$2().min(1).optional().describe("Localized category name for this locale (sent as the API's translation `title`). Required when the locale has no translation yet; omit on an existing one to leave its name untouched, for instance when only publishing a draft."),
|
|
104930
105502
|
description: string$2().optional().describe("Localized category description for this locale (sent as the API's translation `body`). Omit to leave an existing description untouched; pass an empty string to clear it."),
|
|
104931
|
-
draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the category visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged.")
|
|
105503
|
+
draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the category visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged."),
|
|
105504
|
+
...brandIdPart
|
|
104932
105505
|
}),
|
|
104933
105506
|
annotations: {
|
|
104934
105507
|
readOnlyHint: false,
|
|
@@ -104937,9 +105510,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104937
105510
|
openWorldHint: true
|
|
104938
105511
|
},
|
|
104939
105512
|
handler: async (params) => {
|
|
104940
|
-
const { category_id, ...input } = params;
|
|
105513
|
+
const { category_id, brand_id, ...input } = params;
|
|
104941
105514
|
const token = await getToken();
|
|
104942
|
-
const
|
|
105515
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105516
|
+
const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "categories", category_id, input);
|
|
104943
105517
|
return { content: [{
|
|
104944
105518
|
type: "text",
|
|
104945
105519
|
text: nodeTranslationWriteText("categories", category_id, translation, created)
|
|
@@ -104991,7 +105565,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104991
105565
|
locale: string$2().optional().describe("Source locale for the article, e.g. \"en-us\" or \"fr\". Defaults to the Help Center's default locale; becomes the article's source_locale."),
|
|
104992
105566
|
draft: boolean$1().default(true).describe("When true (default), the article is created unpublished; set false to publish immediately."),
|
|
104993
105567
|
promoted: boolean$1().default(false).describe("When true, marks the article as promoted (featured) in its section. Defaults to false."),
|
|
104994
|
-
label_names: array(string$2()).optional().describe("Label names for search ranking (use list_labels to see existing labels)")
|
|
105568
|
+
label_names: array(string$2()).optional().describe("Label names for search ranking (use list_labels to see existing labels)"),
|
|
105569
|
+
...brandIdPart
|
|
104995
105570
|
}),
|
|
104996
105571
|
annotations: {
|
|
104997
105572
|
readOnlyHint: false,
|
|
@@ -105000,9 +105575,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105000
105575
|
openWorldHint: true
|
|
105001
105576
|
},
|
|
105002
105577
|
handler: async (params) => {
|
|
105003
|
-
const { section_id, ...articleData } = params;
|
|
105578
|
+
const { section_id, brand_id, ...articleData } = params;
|
|
105004
105579
|
const token = await getToken();
|
|
105005
|
-
const
|
|
105580
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105581
|
+
const { article } = await helpCenterPost(hcSubdomain, token, `/sections/${section_id}/articles`, { article: articleData });
|
|
105006
105582
|
return { content: [{
|
|
105007
105583
|
type: "text",
|
|
105008
105584
|
text: `Article #${article.id} created.\n\n${formatArticle(article)}`
|
|
@@ -105025,7 +105601,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105025
105601
|
author_id: number$2().int().optional().describe("User id of the article author (from search_users)."),
|
|
105026
105602
|
permission_group_id: number$2().int().optional().describe("Guide permission group controlling who can edit (id from list_permission_groups; if that is forbidden because the token is not a Guide admin, reuse the permission_group_id of an existing article from get_article)."),
|
|
105027
105603
|
section_id: number$2().int().optional().describe("Move the article to this section (numeric id from list_sections)."),
|
|
105028
|
-
position: number$2().int().min(0).optional().describe("Sort position within the section (manual ordering only; 0 = first/top). New articles default to position 0. To move an article to the END of its section, set this to one more than the highest current position: read the highest position P from list_articles with sort_by=\"position\", sort_order=\"desc\", then set position = P + 1.")
|
|
105604
|
+
position: number$2().int().min(0).optional().describe("Sort position within the section (manual ordering only; 0 = first/top). New articles default to position 0. To move an article to the END of its section, set this to one more than the highest current position: read the highest position P from list_articles with sort_by=\"position\", sort_order=\"desc\", then set position = P + 1."),
|
|
105605
|
+
...brandIdPart
|
|
105029
105606
|
}),
|
|
105030
105607
|
annotations: {
|
|
105031
105608
|
readOnlyHint: false,
|
|
@@ -105034,9 +105611,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105034
105611
|
openWorldHint: true
|
|
105035
105612
|
},
|
|
105036
105613
|
handler: async (params) => {
|
|
105037
|
-
const { article_id, ...updates } = params;
|
|
105614
|
+
const { article_id, brand_id, ...updates } = params;
|
|
105038
105615
|
const token = await getToken();
|
|
105039
|
-
const
|
|
105616
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105617
|
+
const { article } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}`, { article: updates });
|
|
105040
105618
|
return { content: [{
|
|
105041
105619
|
type: "text",
|
|
105042
105620
|
text: `Article #${article.id} updated.\n\n${formatArticle(article)}`
|
|
@@ -105059,7 +105637,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105059
105637
|
]).describe("Where to move the article relative to its section siblings: \"top\" (becomes first), \"bottom\" (becomes last), or \"before\"/\"after\" a specific reference article. \"before\" and \"after\" require reference_article_id."),
|
|
105060
105638
|
reference_article_id: number$2().int().optional().describe("The sibling article to position next to when target is \"before\" or \"after\" (numeric id from list_articles). Must belong to the same section and differ from article_id; leave it unset for \"top\" or \"bottom\"."),
|
|
105061
105639
|
normalize: boolean$1().default(false).describe("When true, also renumber every article in the section to contiguous positions (0, 1, 2, …) so the stored positions stay tidy. Defaults to false, which writes the fewest positions possible and lets gaps remain. Either way the confirmation threshold still applies."),
|
|
105062
|
-
confirm: boolean$1().default(false).describe("Safety guard for large reorders. When the move would rewrite more article positions than the configured threshold (REORDER_CONFIRM_THRESHOLD, default 20), the tool refuses and reports the count until you pass true here. Has no effect on small reorders.")
|
|
105640
|
+
confirm: boolean$1().default(false).describe("Safety guard for large reorders. When the move would rewrite more article positions than the configured threshold (REORDER_CONFIRM_THRESHOLD, default 20), the tool refuses and reports the count until you pass true here. Has no effect on small reorders."),
|
|
105641
|
+
...brandIdPart
|
|
105063
105642
|
}),
|
|
105064
105643
|
annotations: {
|
|
105065
105644
|
readOnlyHint: false,
|
|
@@ -105068,15 +105647,16 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105068
105647
|
openWorldHint: true
|
|
105069
105648
|
},
|
|
105070
105649
|
handler: async (params) => {
|
|
105071
|
-
const { article_id, target, reference_article_id, normalize = false, confirm = false } = params;
|
|
105650
|
+
const { article_id, target, reference_article_id, normalize = false, confirm = false, brand_id } = params;
|
|
105072
105651
|
assertReorderParamsCoherent(article_id, target, reference_article_id);
|
|
105073
105652
|
const needsReference = needsReferenceArticle(target);
|
|
105074
105653
|
const token = await getToken();
|
|
105075
|
-
const
|
|
105654
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105655
|
+
const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
|
|
105076
105656
|
const sectionId = article.section_id;
|
|
105077
105657
|
const locale = article.source_locale;
|
|
105078
|
-
const effective = await fetchSectionOrder(sectionId, locale, token);
|
|
105079
|
-
if (reference_article_id !== void 0) await assertReferenceInSection(effective, reference_article_id, article_id, sectionId, token);
|
|
105658
|
+
const effective = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
|
|
105659
|
+
if (reference_article_id !== void 0) await assertReferenceInSection(effective, reference_article_id, article_id, sectionId, token, hcSubdomain);
|
|
105080
105660
|
const targetLabel = needsReference ? `${target} article #${reference_article_id}` : target;
|
|
105081
105661
|
const desired = arrangeDesiredOrder(effective, article_id, target, reference_article_id);
|
|
105082
105662
|
const writes = computePositionWrites(desired, article_id, normalize);
|
|
@@ -105092,8 +105672,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105092
105672
|
type: "text",
|
|
105093
105673
|
text: `Reordering article #${article_id} to ${targetLabel} would reposition ${writes.length} articles in section #${sectionId}, above the safety threshold of ${REORDER_CONFIRM_THRESHOLD}. Re-run with confirm: true to proceed.`
|
|
105094
105674
|
}] };
|
|
105095
|
-
const applied = await applyPositionWrites(writes, article_id, token);
|
|
105096
|
-
const after = await fetchSectionOrder(sectionId, locale, token);
|
|
105675
|
+
const applied = await applyPositionWrites(writes, article_id, token, hcSubdomain);
|
|
105676
|
+
const after = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
|
|
105097
105677
|
if (!isPlacedAsRequested(after, article_id, target, reference_article_id)) return { content: [{
|
|
105098
105678
|
type: "text",
|
|
105099
105679
|
text: autoSortNotice(sectionId, applied)
|
|
@@ -105112,7 +105692,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105112
105692
|
description: "Archive (soft-delete) a Help Center article: it is removed from the Help Center but can be restored from the Guide admin UI. Returns a confirmation message; the article and all its translations become invisible to end users. This is the only removal the Zendesk API offers — permanent deletion is not available via the API (do it from the Guide admin UI). To only hide an article temporarily while keeping it in the knowledge base, use update_article with draft: true (unpublish) instead. Guarded by a required confirm flag.",
|
|
105113
105693
|
inputSchema: object({
|
|
105114
105694
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105115
|
-
confirm: boolean$1().describe("Explicit safety guard: must be set to true to archive the article. Any other value refuses the operation without calling Zendesk.")
|
|
105695
|
+
confirm: boolean$1().describe("Explicit safety guard: must be set to true to archive the article. Any other value refuses the operation without calling Zendesk."),
|
|
105696
|
+
...brandIdPart
|
|
105116
105697
|
}),
|
|
105117
105698
|
annotations: {
|
|
105118
105699
|
readOnlyHint: false,
|
|
@@ -105121,10 +105702,11 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105121
105702
|
openWorldHint: true
|
|
105122
105703
|
},
|
|
105123
105704
|
handler: async (params) => {
|
|
105124
|
-
const { article_id, confirm } = params;
|
|
105705
|
+
const { article_id, confirm, brand_id } = params;
|
|
105125
105706
|
if (confirm !== true) throw new Error("Archiving is guarded: pass confirm: true to archive (soft-delete) this article. No changes were made.");
|
|
105126
105707
|
const token = await getToken();
|
|
105127
|
-
await
|
|
105708
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105709
|
+
await helpCenterDelete(hcSubdomain, token, `/articles/${article_id}`);
|
|
105128
105710
|
return { content: [{
|
|
105129
105711
|
type: "text",
|
|
105130
105712
|
text: `Article #${article_id} archived (soft-deleted). It is removed from the Help Center; restore it from the Guide admin UI if needed.`
|
|
@@ -105196,19 +105778,21 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105196
105778
|
readOnly: true,
|
|
105197
105779
|
title: "List Article Labels",
|
|
105198
105780
|
description: "List all article labels. Labels improve Help Center search ranking and are not visible to end users. Attach them to an article through the label_names parameter of create_article or update_article.",
|
|
105199
|
-
inputSchema: object({}),
|
|
105781
|
+
inputSchema: object({ ...brandIdPart }),
|
|
105200
105782
|
annotations: {
|
|
105201
105783
|
readOnlyHint: true,
|
|
105202
105784
|
destructiveHint: false,
|
|
105203
105785
|
idempotentHint: true,
|
|
105204
105786
|
openWorldHint: true
|
|
105205
105787
|
},
|
|
105206
|
-
handler: async () => {
|
|
105788
|
+
handler: async (params) => {
|
|
105789
|
+
const { brand_id } = params;
|
|
105207
105790
|
const token = await getToken();
|
|
105208
|
-
const
|
|
105791
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105792
|
+
const response = await helpCenterGet(hcSubdomain, token, "/articles/labels", void 0);
|
|
105209
105793
|
return { content: [{
|
|
105210
105794
|
type: "text",
|
|
105211
|
-
text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no parameters, so this listing cannot be narrowed from the call.")
|
|
105795
|
+
text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no filter parameters, so this listing cannot be narrowed from the call.")
|
|
105212
105796
|
}] };
|
|
105213
105797
|
}
|
|
105214
105798
|
},
|
|
@@ -105229,7 +105813,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105229
105813
|
const token = await getToken();
|
|
105230
105814
|
let response;
|
|
105231
105815
|
try {
|
|
105232
|
-
response = await helpCenterGet(subdomain, token, "/user_segments");
|
|
105816
|
+
response = await helpCenterGet(subdomain, token, "/user_segments", void 0);
|
|
105233
105817
|
} catch (error) {
|
|
105234
105818
|
if (error instanceof ZendeskApiError && error.status === 403) throw new Error("list_user_segments reads Help Center user segments (GET /help_center/user_segments), which Zendesk restricts to Guide admins / Help Center managers. The current token lacks that role (HTTP 403). To set an article's visibility without it, reuse the user_segment_id of an existing article (get_article), or omit user_segment_id when creating/updating to default to everyone.", { cause: error });
|
|
105235
105819
|
throw error;
|
|
@@ -105246,7 +105830,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105246
105830
|
readOnly: true,
|
|
105247
105831
|
title: "List Article Attachments",
|
|
105248
105832
|
description: "List all attachments for an article. Returns attachment metadata only (id, file name, content type, size, URL), not the file bytes; both inline and block attachments are included. This is for Help Center articles — for attachments on support tickets use get_ticket_attachments instead. Upload new files with create_article_attachment.",
|
|
105249
|
-
inputSchema: object({
|
|
105833
|
+
inputSchema: object({
|
|
105834
|
+
article_id: number$2().int().describe("ID of the Help Center article whose attachments to list."),
|
|
105835
|
+
...brandIdPart
|
|
105836
|
+
}),
|
|
105250
105837
|
annotations: {
|
|
105251
105838
|
readOnlyHint: true,
|
|
105252
105839
|
destructiveHint: false,
|
|
@@ -105254,9 +105841,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105254
105841
|
openWorldHint: true
|
|
105255
105842
|
},
|
|
105256
105843
|
handler: async (params) => {
|
|
105257
|
-
const { article_id } = params;
|
|
105844
|
+
const { article_id, brand_id } = params;
|
|
105258
105845
|
const token = await getToken();
|
|
105259
|
-
const
|
|
105846
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105847
|
+
const attachments = (await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
|
|
105260
105848
|
if (attachments.length === 0) return { content: [{
|
|
105261
105849
|
type: "text",
|
|
105262
105850
|
text: `No attachments found on article #${article_id}.`
|
|
@@ -105275,7 +105863,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105275
105863
|
description: "Return a compact outline of an article (list of sections delimited by h1/h2/h3, with word counts) for the given locale (defaults to source_locale). Includes available translations with their outdated status. Use get_article_section to fetch a specific section.",
|
|
105276
105864
|
inputSchema: object({
|
|
105277
105865
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105278
|
-
locale: string$2().optional().describe("Locale of the body to outline (defaults to article source_locale)")
|
|
105866
|
+
locale: string$2().optional().describe("Locale of the body to outline (defaults to article source_locale)"),
|
|
105867
|
+
...brandIdPart
|
|
105279
105868
|
}),
|
|
105280
105869
|
annotations: {
|
|
105281
105870
|
readOnlyHint: true,
|
|
@@ -105284,12 +105873,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105284
105873
|
openWorldHint: true
|
|
105285
105874
|
},
|
|
105286
105875
|
handler: async (params) => {
|
|
105287
|
-
const { article_id, locale } = params;
|
|
105876
|
+
const { article_id, locale, brand_id } = params;
|
|
105288
105877
|
const token = await getToken();
|
|
105289
|
-
const
|
|
105878
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105879
|
+
const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
|
|
105290
105880
|
const effectiveLocale = locale ?? article.source_locale;
|
|
105291
|
-
const { translation } = await helpCenterGet(
|
|
105292
|
-
const translations = await listTranslations(
|
|
105881
|
+
const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${effectiveLocale}`, void 0);
|
|
105882
|
+
const translations = await listTranslations(hcSubdomain, token, article_id);
|
|
105293
105883
|
const sections = parseSections(translation.body);
|
|
105294
105884
|
const outlineLines = sections.length ? sections.map((s) => `- [${s.index}] ${s.headingTag ? `${s.headingTag}: ` : ""}${s.heading} (${s.wordCount} words)`).join("\n") : "_(no sections detected)_";
|
|
105295
105885
|
const translationsList = translations.map((t) => `- ${t.locale}${t.outdated ? " (outdated)" : ""}`).join("\n");
|
|
@@ -105318,7 +105908,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105318
105908
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105319
105909
|
locale: string$2().describe("Locale of the body (e.g., \"en-us\", \"fr\")"),
|
|
105320
105910
|
section_index: number$2().int().min(0).describe("0-based index of the section (see get_article_outline)"),
|
|
105321
|
-
format: _enum(["html", "markdown"]).default("html").describe("Output format. \"html\" (default) is round-trip safe. \"markdown\" is lossy on some HTML structures — use only for human review, not before update_article_section.")
|
|
105911
|
+
format: _enum(["html", "markdown"]).default("html").describe("Output format. \"html\" (default) is round-trip safe. \"markdown\" is lossy on some HTML structures — use only for human review, not before update_article_section."),
|
|
105912
|
+
...brandIdPart
|
|
105322
105913
|
}),
|
|
105323
105914
|
annotations: {
|
|
105324
105915
|
readOnlyHint: true,
|
|
@@ -105327,9 +105918,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105327
105918
|
openWorldHint: true
|
|
105328
105919
|
},
|
|
105329
105920
|
handler: async (params) => {
|
|
105330
|
-
const { article_id, locale, section_index, format } = params;
|
|
105921
|
+
const { article_id, locale, section_index, format, brand_id } = params;
|
|
105331
105922
|
const token = await getToken();
|
|
105332
|
-
const
|
|
105923
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105924
|
+
const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
|
|
105333
105925
|
const sections = parseSections(translation.body);
|
|
105334
105926
|
const section = sections[section_index];
|
|
105335
105927
|
if (!section) throw new Error(`Section index ${section_index} not found. Article has ${sections.length} section(s) (0-${Math.max(0, sections.length - 1)}).`);
|
|
@@ -105357,7 +105949,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105357
105949
|
locale: string$2().describe("Locale of the translation to update"),
|
|
105358
105950
|
section_index: number$2().int().min(0).describe("0-based index of the section to replace (see get_article_outline). 0 is the heading-less intro when the body has text before its first heading, otherwise the first heading."),
|
|
105359
105951
|
content: string$2().describe("New content for the section (heading excluded: send neither the heading nor the \"## [n] hN: ...\" label line that get_article_section prints). HTML by default, Markdown if format=\"markdown\"."),
|
|
105360
|
-
format: _enum(["html", "markdown"]).default("html").describe("Input format. \"html\" (default) is the safe path. \"markdown\" is converted to HTML server-side but may introduce artifacts on complex content.")
|
|
105952
|
+
format: _enum(["html", "markdown"]).default("html").describe("Input format. \"html\" (default) is the safe path. \"markdown\" is converted to HTML server-side but may introduce artifacts on complex content."),
|
|
105953
|
+
...brandIdPart
|
|
105361
105954
|
}),
|
|
105362
105955
|
annotations: {
|
|
105363
105956
|
readOnlyHint: false,
|
|
@@ -105366,12 +105959,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105366
105959
|
openWorldHint: true
|
|
105367
105960
|
},
|
|
105368
105961
|
handler: async (params) => {
|
|
105369
|
-
const { article_id, locale, section_index, content, format } = params;
|
|
105962
|
+
const { article_id, locale, section_index, content, format, brand_id } = params;
|
|
105370
105963
|
const token = await getToken();
|
|
105371
|
-
const
|
|
105964
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105965
|
+
const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
|
|
105372
105966
|
const newSectionHtml = format === "markdown" ? markdownToHtml(content) : content;
|
|
105373
105967
|
const { body: newBody, stripped, contentHeadings, introLost, sectionsBefore } = applySectionUpdate(translation.body, section_index, newSectionHtml);
|
|
105374
|
-
const { translation: updated } = await helpCenterPut(
|
|
105968
|
+
const { translation: updated } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: { body: newBody } });
|
|
105375
105969
|
const updatedSections = parseSections(updated.body);
|
|
105376
105970
|
const updatedSection = updatedSections[section_index];
|
|
105377
105971
|
const emptied = introLost === "empty";
|
|
@@ -105402,7 +105996,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105402
105996
|
inputSchema: object({
|
|
105403
105997
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105404
105998
|
source_locale: string$2().describe("Reference locale to diff against, e.g. \"en-us\". Usually the article source_locale (from get_article)."),
|
|
105405
|
-
target_locale: string$2().describe("Target locale to compare against source")
|
|
105999
|
+
target_locale: string$2().describe("Target locale to compare against source"),
|
|
106000
|
+
...brandIdPart
|
|
105406
106001
|
}),
|
|
105407
106002
|
annotations: {
|
|
105408
106003
|
readOnlyHint: true,
|
|
@@ -105411,12 +106006,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105411
106006
|
openWorldHint: true
|
|
105412
106007
|
},
|
|
105413
106008
|
handler: async (params) => {
|
|
105414
|
-
const { article_id, source_locale, target_locale } = params;
|
|
106009
|
+
const { article_id, source_locale, target_locale, brand_id } = params;
|
|
105415
106010
|
const token = await getToken();
|
|
106011
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105416
106012
|
const [sourceRes, targetRes, translations] = await Promise.all([
|
|
105417
|
-
helpCenterGet(
|
|
105418
|
-
helpCenterGet(
|
|
105419
|
-
listTranslations(
|
|
106013
|
+
helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${source_locale}`, void 0),
|
|
106014
|
+
helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${target_locale}`, void 0),
|
|
106015
|
+
listTranslations(hcSubdomain, token, article_id)
|
|
105420
106016
|
]);
|
|
105421
106017
|
const sourceSections = parseSections(sourceRes.translation.body);
|
|
105422
106018
|
const targetSections = parseSections(targetRes.translation.body);
|
|
@@ -105455,7 +106051,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105455
106051
|
abort: true,
|
|
105456
106052
|
error: (issue) => `File too large: ${issue.input.length} base64 characters, limit ${MAX_BASE64_INPUT_CHARS}. Downscale the file, split it, or host it elsewhere and link to it.`
|
|
105457
106053
|
}).describe(`The file's raw bytes as a base64-encoded string; the server decodes them before upload. At most ${MAX_BASE64_INPUT_CHARS} characters (about ${MAX_BASE64_INPUT_MB} MB of file); the HTTP transport additionally caps request bodies at 4 MB.`),
|
|
105458
|
-
content_type: string$2().default("application/octet-stream").describe("MIME type of the file, e.g. \"image/png\" or \"application/pdf\". Defaults to application/octet-stream when omitted.")
|
|
106054
|
+
content_type: string$2().default("application/octet-stream").describe("MIME type of the file, e.g. \"image/png\" or \"application/pdf\". Defaults to application/octet-stream when omitted."),
|
|
106055
|
+
...brandIdPart
|
|
105459
106056
|
}),
|
|
105460
106057
|
annotations: {
|
|
105461
106058
|
readOnlyHint: false,
|
|
@@ -105464,13 +106061,14 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105464
106061
|
openWorldHint: true
|
|
105465
106062
|
},
|
|
105466
106063
|
handler: async (params) => {
|
|
105467
|
-
const { article_id, file_name, file_base64, content_type } = params;
|
|
106064
|
+
const { article_id, file_name, file_base64, content_type, brand_id } = params;
|
|
105468
106065
|
const token = await getToken();
|
|
106066
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105469
106067
|
const buffer = Buffer.from(file_base64, "base64");
|
|
105470
106068
|
const blob = new Blob([buffer], { type: content_type });
|
|
105471
106069
|
const formData = new FormData();
|
|
105472
106070
|
formData.append("file", blob, file_name);
|
|
105473
|
-
const { article_attachment } = await helpCenterUpload(
|
|
106071
|
+
const { article_attachment } = await helpCenterUpload(hcSubdomain, token, `/articles/${article_id}/attachments`, formData);
|
|
105474
106072
|
return { content: [{
|
|
105475
106073
|
type: "text",
|
|
105476
106074
|
text: `Attachment created for article #${article_id}.\n\n${formatAttachment(article_attachment)}`
|
|
@@ -105496,7 +106094,7 @@ const attachmentSchema = object({
|
|
|
105496
106094
|
error: (issue) => `Attachment too large: ${issue.input.length} base64 characters, limit ${MAX_BASE64_INPUT_CHARS}. Downscale the file, split the upload, or link to it instead of uploading.`
|
|
105497
106095
|
}).base64().describe(`File content encoded as base64. At most ${MAX_BASE64_INPUT_CHARS} characters (about ${MAX_BASE64_INPUT_MB} MB of file), and the attachments of one call must stay under that total; the HTTP transport additionally caps request bodies at 4 MB.`),
|
|
105498
106096
|
content_type: string$2().min(1).default("application/octet-stream").describe("MIME type, e.g. \"text/plain\", \"image/png\", \"application/pdf\".")
|
|
105499
|
-
});
|
|
106097
|
+
}).strict();
|
|
105500
106098
|
/**
|
|
105501
106099
|
* The optional `attachments` array parameter, capped as a whole.
|
|
105502
106100
|
*
|
|
@@ -105826,7 +106424,7 @@ const createRequestTools = (ctx) => {
|
|
|
105826
106424
|
custom_fields: array(object({
|
|
105827
106425
|
id: number$2().int(),
|
|
105828
106426
|
value: unknown$3()
|
|
105829
|
-
})).optional().describe("Answers to the form's own questions, as { id, value } pairs. Take both the ids and the accepted values from get_request_form; an id this form does not carry, or a value a dropdown does not offer, is refused here because Zendesk would drop it without an error. Option values are the tag strings, never numbers."),
|
|
106427
|
+
}).strict()).optional().describe("Answers to the form's own questions, as { id, value } pairs. Take both the ids and the accepted values from get_request_form; an id this form does not carry, or a value a dropdown does not offer, is refused here because Zendesk would drop it without an error. Option values are the tag strings, never numbers."),
|
|
105830
106428
|
attachments: attachmentsParam("Files to attach to the request, such as a screenshot or a log, with their content base64-encoded.")
|
|
105831
106429
|
}),
|
|
105832
106430
|
annotations: {
|
|
@@ -106616,7 +107214,7 @@ const createTicketTools = (ctx) => {
|
|
|
106616
107214
|
custom_fields: array(object({
|
|
106617
107215
|
id: number$2().int(),
|
|
106618
107216
|
value: unknown$3()
|
|
106619
|
-
})).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts.")
|
|
107217
|
+
}).strict()).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts.")
|
|
106620
107218
|
}),
|
|
106621
107219
|
annotations: {
|
|
106622
107220
|
readOnlyHint: false,
|
|
@@ -106673,7 +107271,7 @@ const createTicketTools = (ctx) => {
|
|
|
106673
107271
|
custom_fields: array(object({
|
|
106674
107272
|
id: number$2().int(),
|
|
106675
107273
|
value: unknown$3()
|
|
106676
|
-
})).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts."),
|
|
107274
|
+
}).strict()).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts."),
|
|
106677
107275
|
followers: object({
|
|
106678
107276
|
add: subscriberIdList("User ids to start following the ticket. Omit to only remove."),
|
|
106679
107277
|
remove: subscriberIdList("User ids to stop following the ticket. Omit to only add.")
|
|
@@ -107337,32 +107935,6 @@ const readPackageInfo = () => {
|
|
|
107337
107935
|
return cached;
|
|
107338
107936
|
};
|
|
107339
107937
|
//#endregion
|
|
107340
|
-
//#region src/utils/validation.ts
|
|
107341
|
-
/**
|
|
107342
|
-
* Build a strict params parser for a tool's input schema, computing the strict
|
|
107343
|
-
* schema and the valid-key list once at construction rather than per call.
|
|
107344
|
-
*
|
|
107345
|
-
* Zod objects default to `strip`, which silently drops unknown keys. That hid
|
|
107346
|
-
* #100: a caller passing `per_page` to list_tickets (whose parameter is
|
|
107347
|
-
* `page_size`) had the key dropped, so a large unpaginated page came back. The
|
|
107348
|
-
* parser rejects unknown keys and names both the offending ones and the valid
|
|
107349
|
-
* parameters, so a misremembered name fails loudly.
|
|
107350
|
-
*
|
|
107351
|
-
* Used on the proxy dispatch path (namespace/single modes); in `all` mode the
|
|
107352
|
-
* SDK validates against the strict schema we register.
|
|
107353
|
-
*/
|
|
107354
|
-
const createStrictParamsParser = (schema) => {
|
|
107355
|
-
const strict = schema.strict();
|
|
107356
|
-
const validKeys = Object.keys(schema.shape).sort().join(", ");
|
|
107357
|
-
return (params) => {
|
|
107358
|
-
const result = strict.safeParse(params);
|
|
107359
|
-
if (result.success) return result.data;
|
|
107360
|
-
const unknownKeys = result.error.issues.flatMap((issue) => issue.code === "unrecognized_keys" ? issue.keys : []);
|
|
107361
|
-
if (unknownKeys.length > 0) throw new Error(`Unknown parameter(s): ${unknownKeys.join(", ")}. Valid parameters: ${validKeys || "(none)"}.`);
|
|
107362
|
-
throw result.error;
|
|
107363
|
-
};
|
|
107364
|
-
};
|
|
107365
|
-
//#endregion
|
|
107366
107938
|
//#region src/server.ts
|
|
107367
107939
|
/**
|
|
107368
107940
|
* Invoke a tool handler, notifying `onUnauthorized` when Zendesk rejects the
|
|
@@ -107445,6 +108017,31 @@ const createServerShell = (config, logger = silentLogger) => {
|
|
|
107445
108017
|
return server;
|
|
107446
108018
|
};
|
|
107447
108019
|
/**
|
|
108020
|
+
* Lazily resolve the Help Center subdomain the topology resource pins to: the
|
|
108021
|
+
* FIRST allowed brand when a list is set (named in the header), else undefined
|
|
108022
|
+
* (the account default). Uses the SHARED resolver so the topology and article
|
|
108023
|
+
* resources hit the same cached brand list the tools use; it only fires when a
|
|
108024
|
+
* brand list is set. Providers await it per read (cheap after the first), since
|
|
108025
|
+
* registerToolset is synchronous and cannot resolve up front.
|
|
108026
|
+
*/
|
|
108027
|
+
const firstBrandSubdomainResolver = (config, resolveBrandSubdomain) => {
|
|
108028
|
+
const first = config.brandIds?.[0];
|
|
108029
|
+
if (first === void 0 || first === "all") return () => Promise.resolve(void 0);
|
|
108030
|
+
return () => resolveBrandSubdomain(first);
|
|
108031
|
+
};
|
|
108032
|
+
/**
|
|
108033
|
+
* Per-call brand guard for the article resource URIs in multi/all mode: the
|
|
108034
|
+
* {brand} slot of `<scheme>://brands/{brand}/articles/{id}` is resolved and
|
|
108035
|
+
* allow-list-checked EXACTLY like a tool call's `brand_id` (shared resolver,
|
|
108036
|
+
* same resolveBrand rules), returning the brand's subdomain for the Help
|
|
108037
|
+
* Center fetch. Unset/single modes never reach here — their URIs carry no
|
|
108038
|
+
* brand segment.
|
|
108039
|
+
*/
|
|
108040
|
+
const resourceBrandResolver = (config, resolveBrandSubdomain) => {
|
|
108041
|
+
const singleLock = config.brandIds?.length === 1 && config.brandIds[0] !== "all";
|
|
108042
|
+
return (idOrSubdomain) => singleLock ? resolveBrandSubdomain(idOrSubdomain) : resolveBrand(idOrSubdomain, config.brandIds, resolveBrandSubdomain).then((sub) => sub ?? config.subdomain);
|
|
108043
|
+
};
|
|
108044
|
+
/**
|
|
107448
108045
|
* Registers one generation of the toolset (mode/filters applied) plus the
|
|
107449
108046
|
* optional topology resource onto an existing server, and returns a handle
|
|
107450
108047
|
* whose `dispose()` removes exactly what this call added. When the SDK server
|
|
@@ -107454,7 +108051,8 @@ const createServerShell = (config, logger = silentLogger) => {
|
|
|
107454
108051
|
* without dropping the transport. `tools` is passed in (not built here) so the
|
|
107455
108052
|
* reload path can hand over freshly re-imported definitions.
|
|
107456
108053
|
*/
|
|
107457
|
-
const registerToolset = (server, { config, getToken, onUnauthorized, logger = silentLogger }, tools) => {
|
|
108054
|
+
const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUnauthorized, logger = silentLogger }, tools) => {
|
|
108055
|
+
const sharedResolver = resolveBrandSubdomain ?? createBrandSubdomainResolver(config.subdomain, getToken);
|
|
107458
108056
|
const registered = [];
|
|
107459
108057
|
const dispose = () => {
|
|
107460
108058
|
for (const handle of registered) handle.remove();
|
|
@@ -107462,8 +108060,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107462
108060
|
const filteredTools = filterTools(tools, {
|
|
107463
108061
|
readOnly: config.readOnly,
|
|
107464
108062
|
namespaces: config.namespaces,
|
|
107465
|
-
tools: config.tools
|
|
107466
|
-
promotedArticles: config.promotedArticles
|
|
108063
|
+
tools: config.tools
|
|
107467
108064
|
});
|
|
107468
108065
|
try {
|
|
107469
108066
|
switch (config.mode) {
|
|
@@ -107471,7 +108068,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107471
108068
|
for (const tool of filteredTools) registered.push(server.registerTool(tool.name, {
|
|
107472
108069
|
title: tool.title,
|
|
107473
108070
|
description: tool.description,
|
|
107474
|
-
inputSchema: tool.inputSchema
|
|
108071
|
+
inputSchema: toStrictStandardSchema(tool.inputSchema),
|
|
107475
108072
|
annotations: tool.annotations
|
|
107476
108073
|
}, async (params) => runHandler(tool, params, onUnauthorized)));
|
|
107477
108074
|
break;
|
|
@@ -107492,7 +108089,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107492
108089
|
}
|
|
107493
108090
|
}
|
|
107494
108091
|
if (helpCenterContextEnabled(config)) {
|
|
107495
|
-
const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized);
|
|
108092
|
+
const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized, config.brandIds, firstBrandSubdomainResolver(config, sharedResolver));
|
|
107496
108093
|
registered.push(server.registerResource("help-center-topology", topologyResourceUri(config), {
|
|
107497
108094
|
title: "Zendesk Help Center topology",
|
|
107498
108095
|
description: "Active locales, category → section tree, visibility segments, permission groups, and your role. Useful context when creating or editing content; admin-only sections (permission groups, user segments) are marked unavailable rather than empty when your role lacks Guide-admin rights.",
|
|
@@ -107504,7 +108101,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107504
108101
|
}] })));
|
|
107505
108102
|
}
|
|
107506
108103
|
if (articleResourceEnabled(config)) {
|
|
107507
|
-
const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized);
|
|
108104
|
+
const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized, resourceBrandResolver(config, sharedResolver), config.brandIds);
|
|
107508
108105
|
const listPromotedEnabled = promotedArticlesEnabled(config);
|
|
107509
108106
|
const template = new ResourceTemplate(articleResourceUriTemplate(config), { list: async () => {
|
|
107510
108107
|
if (!listPromotedEnabled) return { resources: [] };
|
|
@@ -107515,10 +108112,10 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107515
108112
|
listed: refs.length
|
|
107516
108113
|
});
|
|
107517
108114
|
return { resources: refs.map((ref) => ({
|
|
107518
|
-
uri: articleResourceUri(config, ref.id),
|
|
107519
|
-
name: ref.title
|
|
108115
|
+
uri: articleResourceUri(config, ref.id, ref.brand),
|
|
108116
|
+
name: ref.brand === void 0 ? ref.title : `${ref.title} (${ref.brand})`,
|
|
107520
108117
|
title: ref.title,
|
|
107521
|
-
description: `"${ref.title}" (article ${ref.id}) — promoted Help Center article, as Markdown.`,
|
|
108118
|
+
description: ref.brand === void 0 ? `"${ref.title}" (article ${ref.id}) — promoted Help Center article, as Markdown.` : `"${ref.title}" (article ${ref.id}, brand ${ref.brand}) — promoted Help Center article, as Markdown.`,
|
|
107522
108119
|
mimeType: "text/markdown"
|
|
107523
108120
|
})) };
|
|
107524
108121
|
} catch (err) {
|
|
@@ -107528,16 +108125,18 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107528
108125
|
} });
|
|
107529
108126
|
registered.push(server.registerResource("help-center-article", template, {
|
|
107530
108127
|
title: "Zendesk Help Center article",
|
|
107531
|
-
description: "A Help Center article rendered as Markdown, addressed by id. The list surfaces the promoted (featured) articles so one can be pinned as context; any article id can be read, subject to your Zendesk read permissions.",
|
|
108128
|
+
description: "A Help Center article rendered as Markdown, addressed by id (by brand and id when the server runs multi-brand). The list surfaces the promoted (featured) articles so one can be pinned as context; any article id can be read, subject to your Zendesk read permissions.",
|
|
107532
108129
|
mimeType: "text/markdown"
|
|
107533
108130
|
}, async (uri, variables) => {
|
|
107534
|
-
const
|
|
107535
|
-
const id = Number(
|
|
108131
|
+
const rawId = Array.isArray(variables["id"]) ? variables["id"][0] : variables["id"];
|
|
108132
|
+
const id = Number(rawId);
|
|
107536
108133
|
if (!Number.isSafeInteger(id) || id <= 0) throw new Error(`Invalid article id in resource URI: ${uri.toString()}`);
|
|
108134
|
+
const rawBrand = variables["brand"];
|
|
108135
|
+
const brand = Array.isArray(rawBrand) ? rawBrand[0] : rawBrand;
|
|
107537
108136
|
return { contents: [{
|
|
107538
108137
|
uri: uri.toString(),
|
|
107539
108138
|
mimeType: "text/markdown",
|
|
107540
|
-
text: await articles.readArticle(id)
|
|
108139
|
+
text: await articles.readArticle(id, brand)
|
|
107541
108140
|
}] };
|
|
107542
108141
|
}));
|
|
107543
108142
|
}
|
|
@@ -107556,20 +108155,24 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107556
108155
|
};
|
|
107557
108156
|
const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized) => {
|
|
107558
108157
|
const server = createServerShell(config, logger);
|
|
108158
|
+
const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
|
|
107559
108159
|
const tools = createAllTools({
|
|
107560
108160
|
subdomain: config.subdomain,
|
|
108161
|
+
brandIds: config.brandIds,
|
|
108162
|
+
resolveBrandSubdomain,
|
|
107561
108163
|
getToken
|
|
107562
108164
|
});
|
|
107563
108165
|
registerToolset(server, {
|
|
107564
108166
|
config,
|
|
107565
108167
|
getToken,
|
|
108168
|
+
resolveBrandSubdomain,
|
|
107566
108169
|
onUnauthorized,
|
|
107567
108170
|
logger
|
|
107568
108171
|
}, tools);
|
|
107569
108172
|
return server;
|
|
107570
108173
|
};
|
|
107571
108174
|
//#endregion
|
|
107572
|
-
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.
|
|
108175
|
+
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
|
|
107573
108176
|
/**
|
|
107574
108177
|
* Marks a stdout `'error'` listener that a closed transport left attached purely to
|
|
107575
108178
|
* swallow late write failures (see {@link StdioServerTransport.close}). A fresh
|
|
@@ -107752,13 +108355,17 @@ const loadFreshTools = async (ctx) => {
|
|
|
107752
108355
|
*/
|
|
107753
108356
|
const createReloadableServer = (config, getToken, logger = silentLogger, onUnauthorized, loadTools = loadFreshTools) => {
|
|
107754
108357
|
const server = createServerShell(config, logger);
|
|
108358
|
+
const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
|
|
107755
108359
|
const ctx = {
|
|
107756
108360
|
subdomain: config.subdomain,
|
|
108361
|
+
brandIds: config.brandIds,
|
|
108362
|
+
resolveBrandSubdomain,
|
|
107757
108363
|
getToken
|
|
107758
108364
|
};
|
|
107759
108365
|
const params = {
|
|
107760
108366
|
config,
|
|
107761
108367
|
getToken,
|
|
108368
|
+
resolveBrandSubdomain,
|
|
107762
108369
|
onUnauthorized,
|
|
107763
108370
|
logger
|
|
107764
108371
|
};
|
|
@@ -107897,8 +108504,7 @@ const renderToolSurface = (config, tools) => {
|
|
|
107897
108504
|
const filtered = filterTools(tools, {
|
|
107898
108505
|
readOnly: config.readOnly,
|
|
107899
108506
|
namespaces: config.namespaces,
|
|
107900
|
-
tools: config.tools
|
|
107901
|
-
promotedArticles: config.promotedArticles
|
|
108507
|
+
tools: config.tools
|
|
107902
108508
|
});
|
|
107903
108509
|
const header = renderHeader(config);
|
|
107904
108510
|
if (filtered.length === 0) return `${header}\n\nNo tools exposed. Check --namespace / --tool / --read-only.`;
|
|
@@ -107994,7 +108600,7 @@ const installShutdown = (options) => {
|
|
|
107994
108600
|
//#endregion
|
|
107995
108601
|
//#region src/index.ts
|
|
107996
108602
|
const buildStdioTokenStore = async (config, logger) => {
|
|
107997
|
-
const { createTokenStore } = await import("./token-store-
|
|
108603
|
+
const { createTokenStore } = await import("./token-store-BLAkLLQO.js");
|
|
107998
108604
|
return createTokenStore({
|
|
107999
108605
|
subdomain: config.subdomain,
|
|
108000
108606
|
oauthClientId: config.oauthClientId,
|
|
@@ -108013,6 +108619,8 @@ const main = async () => {
|
|
|
108013
108619
|
if (config.printTools) {
|
|
108014
108620
|
const tools = createAllTools({
|
|
108015
108621
|
subdomain: config.subdomain,
|
|
108622
|
+
brandIds: config.brandIds,
|
|
108623
|
+
resolveBrandSubdomain: () => Promise.resolve(""),
|
|
108016
108624
|
getToken: () => ""
|
|
108017
108625
|
});
|
|
108018
108626
|
console.log(renderToolSurface(config, tools));
|
|
@@ -108033,7 +108641,7 @@ const main = async () => {
|
|
|
108033
108641
|
return;
|
|
108034
108642
|
}
|
|
108035
108643
|
if (config.dev) logger.warn("dev_mode_ignored_http");
|
|
108036
|
-
const { startHttpTransport } = await import("./http-
|
|
108644
|
+
const { startHttpTransport } = await import("./http-BYYYoQaK.js");
|
|
108037
108645
|
const http = await startHttpTransport(config, logger);
|
|
108038
108646
|
installShutdown({
|
|
108039
108647
|
watchStdin: false,
|
|
@@ -108047,4 +108655,4 @@ main().catch((error) => {
|
|
|
108047
108655
|
process.exit(1);
|
|
108048
108656
|
});
|
|
108049
108657
|
//#endregion
|
|
108050
|
-
export {
|
|
108658
|
+
export { __commonJSMin$1 as _, verifyBearerToken as a, __toESM$1 as b, zendeskGet as c, DEFAULT_CALLBACK_PORT as d, getOAuthUrls as f, readEnv as g, errnoCode as h, WebStandardStreamableHTTPServerTransport as i, REQUEST_TIMEOUT_MS as l, createStartupError as m, readPackageInfo as n, OAuthError as o, STARTUP_DOCS as p, silentLogger as r, OAuthErrorCode as s, createMcpServer as t, deadlineSignal as u, __exportAll$1 as v, __require as y };
|