@fruggr/zendesk-mcp-server 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -7583,6 +7583,14 @@ const Transport = _enum(["stdio", "http"]);
|
|
|
7583
7583
|
const ConfigSchema = object({
|
|
7584
7584
|
subdomain: string$2().min(1, "ZENDESK_SUBDOMAIN is required"),
|
|
7585
7585
|
oauthClientId: string$2().min(1),
|
|
7586
|
+
/**
|
|
7587
|
+
* Help Center brand allow-list (`--brand-ids`): brand ids or subdomains, or
|
|
7588
|
+
* 'all'. Unset: account default brand, surface unchanged. One entry: hard
|
|
7589
|
+
* lock, no `brand_id` field. Several or 'all': `list_brands` exposed and
|
|
7590
|
+
* `brand_id` required on brand-scoped tools. Entries resolve lazily, on first
|
|
7591
|
+
* use. Modes and rationale: docs/configuration.md.
|
|
7592
|
+
*/
|
|
7593
|
+
brandIds: array(string$2().min(1)).min(1).optional(),
|
|
7586
7594
|
logLevel: LogLevel,
|
|
7587
7595
|
mode: ToolMode,
|
|
7588
7596
|
readOnly: boolean$1(),
|
|
@@ -7610,14 +7618,12 @@ const ConfigSchema = object({
|
|
|
7610
7618
|
topology: boolean$1().default(true),
|
|
7611
7619
|
/**
|
|
7612
7620
|
* 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.
|
|
7621
|
+
* article resource's `list` callback, which scans `/articles` to enumerate the
|
|
7622
|
+
* promoted set for `resources/list`. On by default; `--no-promoted-articles`
|
|
7623
|
+
* turns it off (e.g. on a very large or multi-brand Help Center) so the server
|
|
7624
|
+
* issues zero preloading requests. Reading a known article by id and the
|
|
7625
|
+
* on-demand `list_promoted_articles` tool stay available. Only ever active
|
|
7626
|
+
* when the `help_center` namespace itself is active.
|
|
7621
7627
|
*/
|
|
7622
7628
|
promotedArticles: boolean$1().default(true),
|
|
7623
7629
|
/**
|
|
@@ -7696,8 +7702,25 @@ const portEnv = (name) => {
|
|
|
7696
7702
|
const env = readNonEmptyEnv(name);
|
|
7697
7703
|
return env.value === void 0 ? void 0 : parsePort(env.value, env.name);
|
|
7698
7704
|
};
|
|
7705
|
+
/**
|
|
7706
|
+
* Split a raw `--brand-ids` / `ZENDESK_BRAND_IDS` value into its entries.
|
|
7707
|
+
* Comma-separated, entries trimmed; an empty entry (from `a,,b`, a leading/
|
|
7708
|
+
* trailing comma, or a whitespace-only value) is a misconfiguration, not an
|
|
7709
|
+
* ignored blank: it fails at startup naming the knob. Format validation only —
|
|
7710
|
+
* whether an id/subdomain exists is decided lazily, on first use (no token is
|
|
7711
|
+
* available at startup on either transport).
|
|
7712
|
+
*/
|
|
7713
|
+
const parseBrandIds = (raw) => {
|
|
7714
|
+
if (raw === void 0) return void 0;
|
|
7715
|
+
const entries = raw.split(",").map((entry) => entry.trim());
|
|
7716
|
+
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);
|
|
7717
|
+
const normalized = entries.map((entry) => entry.toLowerCase());
|
|
7718
|
+
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);
|
|
7719
|
+
return [...new Set(normalized)];
|
|
7720
|
+
};
|
|
7699
7721
|
const CLI_OPTIONS = {
|
|
7700
7722
|
mode: { type: "string" },
|
|
7723
|
+
"brand-ids": { type: "string" },
|
|
7701
7724
|
namespace: {
|
|
7702
7725
|
type: "string",
|
|
7703
7726
|
multiple: true
|
|
@@ -7785,6 +7808,7 @@ const parseCliArgs = (args) => {
|
|
|
7785
7808
|
}
|
|
7786
7809
|
if (values.port !== void 0) result.port = parsePort(values.port, "--port");
|
|
7787
7810
|
if (values["callback-port"] !== void 0) result.callbackPort = parsePort(values["callback-port"], "--callback-port");
|
|
7811
|
+
if (values["brand-ids"] !== void 0) result.brandIds = values["brand-ids"];
|
|
7788
7812
|
return result;
|
|
7789
7813
|
};
|
|
7790
7814
|
const splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -7812,10 +7836,12 @@ const loadConfig = (argv = process.argv.slice(2)) => {
|
|
|
7812
7836
|
const mode = cli.tools?.length ? "all" : cli.mode ?? "namespace";
|
|
7813
7837
|
const namespaces = cli.namespaces ?? (cli.tools?.length ? [...Namespace.options] : void 0);
|
|
7814
7838
|
const callbackPort = cli.callbackPort ?? portEnv("OAUTH_CALLBACK_PORT");
|
|
7839
|
+
const brandIds = cli.brandIds ?? requireNonEmptyEnv("ZENDESK_BRAND_IDS");
|
|
7815
7840
|
const hcResourceScheme = cli.hcResourceScheme ?? requireNonEmptyEnv("HC_RESOURCE_SCHEME");
|
|
7816
7841
|
return validated({
|
|
7817
7842
|
subdomain,
|
|
7818
7843
|
oauthClientId,
|
|
7844
|
+
brandIds: parseBrandIds(brandIds),
|
|
7819
7845
|
logLevel: cli.logLevel ?? requireNonEmptyEnv("LOG_LEVEL") ?? "info",
|
|
7820
7846
|
mode,
|
|
7821
7847
|
readOnly: cli.readOnly ?? false,
|
|
@@ -7832,6 +7858,388 @@ const loadConfig = (argv = process.argv.slice(2)) => {
|
|
|
7832
7858
|
});
|
|
7833
7859
|
};
|
|
7834
7860
|
//#endregion
|
|
7861
|
+
//#region src/constants.ts
|
|
7862
|
+
const positiveIntEnv = (name, fallback) => {
|
|
7863
|
+
const raw = readEnv(name).value;
|
|
7864
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
7865
|
+
const parsed = Number(raw);
|
|
7866
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
7867
|
+
};
|
|
7868
|
+
const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
|
|
7869
|
+
const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
|
|
7870
|
+
const DEFAULT_CALLBACK_PORT = 27439;
|
|
7871
|
+
const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
|
|
7872
|
+
const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
|
|
7873
|
+
const STDIO_MAX_MESSAGE_BYTES = 10485760;
|
|
7874
|
+
const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
|
|
7875
|
+
const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
|
|
7876
|
+
const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
|
|
7877
|
+
const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
|
|
7878
|
+
const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
|
|
7879
|
+
const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
|
|
7880
|
+
const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
|
|
7881
|
+
const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
|
|
7882
|
+
const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
|
|
7883
|
+
const getOAuthUrls = (subdomain) => ({
|
|
7884
|
+
authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
|
|
7885
|
+
tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
|
|
7886
|
+
});
|
|
7887
|
+
//#endregion
|
|
7888
|
+
//#region src/client/retry.ts
|
|
7889
|
+
const MAX_ATTEMPTS = 3;
|
|
7890
|
+
const BASE_DELAY_MS = 250;
|
|
7891
|
+
const MAX_DELAY_MS = 4e3;
|
|
7892
|
+
const REQUEST_TIMEOUT_MS = 3e4;
|
|
7893
|
+
const TRANSFER_TIMEOUT_MS = 12e4;
|
|
7894
|
+
/**
|
|
7895
|
+
* A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
|
|
7896
|
+
* is numeric, not a syscall string, so it classifies as `unknown`: a GET is
|
|
7897
|
+
* retried, a write is not — the request may have arrived before the deadline.
|
|
7898
|
+
*/
|
|
7899
|
+
const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
|
|
7900
|
+
const MAX_RETRY_AFTER_MS = 5e3;
|
|
7901
|
+
const POLICIES = {
|
|
7902
|
+
GET: {
|
|
7903
|
+
network: "any",
|
|
7904
|
+
serverErrors: true
|
|
7905
|
+
},
|
|
7906
|
+
DELETE: {
|
|
7907
|
+
network: "pre-send",
|
|
7908
|
+
serverErrors: false
|
|
7909
|
+
},
|
|
7910
|
+
POST: {
|
|
7911
|
+
network: "pre-send",
|
|
7912
|
+
serverErrors: false
|
|
7913
|
+
},
|
|
7914
|
+
PUT: {
|
|
7915
|
+
network: "pre-send",
|
|
7916
|
+
serverErrors: false
|
|
7917
|
+
}
|
|
7918
|
+
};
|
|
7919
|
+
const DELAY_SECONDS = /^\d+$/;
|
|
7920
|
+
const HTTP_DATE_START = /^[A-Za-z]/;
|
|
7921
|
+
const NON_ASCII = /[^ -~]/g;
|
|
7922
|
+
const TOKEN_SEGMENT = /\/token\/[^/]+/;
|
|
7923
|
+
const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
|
|
7924
|
+
const PRE_SEND_CODES = /* @__PURE__ */ new Set([
|
|
7925
|
+
"ENOTFOUND",
|
|
7926
|
+
"EAI_AGAIN",
|
|
7927
|
+
"ECONNREFUSED",
|
|
7928
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
7929
|
+
]);
|
|
7930
|
+
const defaultRetryDeps = {
|
|
7931
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
7932
|
+
random: Math.random
|
|
7933
|
+
};
|
|
7934
|
+
/** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
|
|
7935
|
+
const toAscii = (text) => text.replace(NON_ASCII, "?");
|
|
7936
|
+
/**
|
|
7937
|
+
* Identifies the request without leaking a credential: uploads carry an upload
|
|
7938
|
+
* token in the query string, and an attachment `content_url` carries a download
|
|
7939
|
+
* token as a path segment (`/attachments/token/<token>/`).
|
|
7940
|
+
*/
|
|
7941
|
+
const describeTarget = (url) => {
|
|
7942
|
+
const { origin, pathname } = new URL(url);
|
|
7943
|
+
return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
|
|
7944
|
+
};
|
|
7945
|
+
/** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
|
|
7946
|
+
const errorCode = (err) => {
|
|
7947
|
+
let current = err;
|
|
7948
|
+
for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
|
|
7949
|
+
const { code, cause } = current;
|
|
7950
|
+
if (typeof code === "string") return code;
|
|
7951
|
+
current = cause;
|
|
7952
|
+
}
|
|
7953
|
+
};
|
|
7954
|
+
const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
|
|
7955
|
+
const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
|
|
7956
|
+
/**
|
|
7957
|
+
* The client refuses to replay a write that may have landed — but the caller is
|
|
7958
|
+
* an LLM whose reflex on a failed tool call is to try again, which would undo
|
|
7959
|
+
* that. So a failed write says whether it may have taken effect. ASCII only,
|
|
7960
|
+
* same rule as the rest of the message.
|
|
7961
|
+
*/
|
|
7962
|
+
const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
|
|
7963
|
+
const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
|
|
7964
|
+
const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
|
|
7965
|
+
/** Only a write can be duplicated by a replay, so only a write carries a note. */
|
|
7966
|
+
const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
|
|
7967
|
+
/**
|
|
7968
|
+
* A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
|
|
7969
|
+
* carrying credentials by printing the whole thing, query string included — which
|
|
7970
|
+
* would smuggle back exactly what `describeTarget` drops. Same treatment for both,
|
|
7971
|
+
* so one function owns what a URL may look like in our output.
|
|
7972
|
+
*/
|
|
7973
|
+
const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
|
|
7974
|
+
try {
|
|
7975
|
+
return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
|
|
7976
|
+
} catch {
|
|
7977
|
+
return "[url]";
|
|
7978
|
+
}
|
|
7979
|
+
});
|
|
7980
|
+
const failureDetail = (cause) => {
|
|
7981
|
+
if (!(cause instanceof Error)) return redactUrls(String(cause));
|
|
7982
|
+
const message = redactUrls(cause.message);
|
|
7983
|
+
const code = errorCode(cause);
|
|
7984
|
+
if (code !== void 0) return `${code}: ${message}`;
|
|
7985
|
+
return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
|
|
7986
|
+
};
|
|
7987
|
+
const networkErrorMessage = (method, target, attempts, cause) => {
|
|
7988
|
+
const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
|
|
7989
|
+
const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
|
|
7990
|
+
return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
|
|
7991
|
+
};
|
|
7992
|
+
const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
|
|
7993
|
+
name: "ZendeskNetworkError",
|
|
7994
|
+
method,
|
|
7995
|
+
target,
|
|
7996
|
+
attempts
|
|
7997
|
+
});
|
|
7998
|
+
/** Exponential backoff with equal jitter: half the window fixed, half random. */
|
|
7999
|
+
const computeBackoffMs = (attempt, random) => {
|
|
8000
|
+
const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
|
|
8001
|
+
return Math.round(window / 2 + window * random() / 2);
|
|
8002
|
+
};
|
|
8003
|
+
/** `Retry-After` in ms — delay-seconds or HTTP-date. */
|
|
8004
|
+
const parseRetryAfter = (header, now = Date.now()) => {
|
|
8005
|
+
if (header === null) return void 0;
|
|
8006
|
+
const value = header.trim();
|
|
8007
|
+
if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
|
|
8008
|
+
if (!HTTP_DATE_START.test(value)) return void 0;
|
|
8009
|
+
const date = Date.parse(value);
|
|
8010
|
+
if (Number.isNaN(date)) return void 0;
|
|
8011
|
+
return Math.max(0, date - now);
|
|
8012
|
+
};
|
|
8013
|
+
/**
|
|
8014
|
+
* How long to wait before replaying this response, or undefined to accept it.
|
|
8015
|
+
* `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
|
|
8016
|
+
* during maintenance as well as on a 429 — and a value past the cap means the
|
|
8017
|
+
* response is surfaced rather than parking the call for that long.
|
|
8018
|
+
*/
|
|
8019
|
+
const retryDelayFor = (response, policy, attempt, random) => {
|
|
8020
|
+
if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
|
|
8021
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
8022
|
+
if (retryAfter === void 0) return computeBackoffMs(attempt, random);
|
|
8023
|
+
return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
|
|
8024
|
+
};
|
|
8025
|
+
/**
|
|
8026
|
+
* Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
|
|
8027
|
+
* attempt budget. Returns the last response for the caller to inspect (a
|
|
8028
|
+
* non-ok status is still the caller's to turn into a `ZendeskApiError`), and
|
|
8029
|
+
* throws `ZendeskNetworkError` when no response was ever obtained.
|
|
8030
|
+
*/
|
|
8031
|
+
const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
|
|
8032
|
+
const policy = POLICIES[method];
|
|
8033
|
+
for (let tries = 1;; tries += 1) {
|
|
8034
|
+
const last = tries >= MAX_ATTEMPTS;
|
|
8035
|
+
let response;
|
|
8036
|
+
try {
|
|
8037
|
+
response = await attempt();
|
|
8038
|
+
} catch (err) {
|
|
8039
|
+
const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
|
|
8040
|
+
if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
|
|
8041
|
+
await deps.sleep(computeBackoffMs(tries, deps.random));
|
|
8042
|
+
continue;
|
|
8043
|
+
}
|
|
8044
|
+
if (last) return response;
|
|
8045
|
+
const delayMs = retryDelayFor(response, policy, tries, deps.random);
|
|
8046
|
+
if (delayMs === void 0) return response;
|
|
8047
|
+
await response.text().catch(() => void 0);
|
|
8048
|
+
await deps.sleep(delayMs);
|
|
8049
|
+
}
|
|
8050
|
+
};
|
|
8051
|
+
//#endregion
|
|
8052
|
+
//#region src/client/zendesk-api.ts
|
|
8053
|
+
var ZendeskApiError = class ZendeskApiError extends Error {
|
|
8054
|
+
status;
|
|
8055
|
+
statusText;
|
|
8056
|
+
body;
|
|
8057
|
+
method;
|
|
8058
|
+
constructor(status, statusText, body, method) {
|
|
8059
|
+
super(ZendeskApiError.buildMessage(status, statusText, body, method));
|
|
8060
|
+
this.status = status;
|
|
8061
|
+
this.statusText = statusText;
|
|
8062
|
+
this.body = body;
|
|
8063
|
+
this.method = method;
|
|
8064
|
+
this.name = "ZendeskApiError";
|
|
8065
|
+
}
|
|
8066
|
+
static buildMessage(status, statusText, body, method) {
|
|
8067
|
+
switch (status) {
|
|
8068
|
+
case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
|
|
8069
|
+
case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
|
|
8070
|
+
case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
|
|
8071
|
+
case 422: return `Validation error: ${body}`;
|
|
8072
|
+
case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
|
|
8073
|
+
default: {
|
|
8074
|
+
const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
|
|
8075
|
+
return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
};
|
|
8080
|
+
const buildAuthHeader = (token) => `Bearer ${token}`;
|
|
8081
|
+
const buildUrl = (base, path, params) => {
|
|
8082
|
+
const url = new URL(`${base}${path}`);
|
|
8083
|
+
if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
|
|
8084
|
+
return url.toString();
|
|
8085
|
+
};
|
|
8086
|
+
const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
|
|
8087
|
+
...init,
|
|
8088
|
+
method,
|
|
8089
|
+
signal: deadlineSignal(timeoutMs)
|
|
8090
|
+
}), method, describeTarget(url));
|
|
8091
|
+
const executeRequest = async (url, token, options = {}) => {
|
|
8092
|
+
const { method = "GET", body } = options;
|
|
8093
|
+
const headers = {
|
|
8094
|
+
Authorization: buildAuthHeader(token),
|
|
8095
|
+
Accept: "application/json"
|
|
8096
|
+
};
|
|
8097
|
+
if (body) headers["Content-Type"] = "application/json";
|
|
8098
|
+
const init = { headers };
|
|
8099
|
+
if (body) init.body = JSON.stringify(body);
|
|
8100
|
+
const response = await performFetch(method, url, init);
|
|
8101
|
+
if (!response.ok) {
|
|
8102
|
+
const responseBody = await response.text();
|
|
8103
|
+
throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
|
|
8104
|
+
}
|
|
8105
|
+
if (response.status === 204) return {};
|
|
8106
|
+
return response.json();
|
|
8107
|
+
};
|
|
8108
|
+
const zendeskGet = (subdomain, token, path, params) => {
|
|
8109
|
+
const url = buildUrl(getBaseUrl(subdomain), path, params);
|
|
8110
|
+
return executeRequest(url, token);
|
|
8111
|
+
};
|
|
8112
|
+
const zendeskPost = (subdomain, token, path, body) => {
|
|
8113
|
+
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
8114
|
+
return executeRequest(url, token, {
|
|
8115
|
+
method: "POST",
|
|
8116
|
+
body
|
|
8117
|
+
});
|
|
8118
|
+
};
|
|
8119
|
+
const zendeskPut = (subdomain, token, path, body) => {
|
|
8120
|
+
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
8121
|
+
return executeRequest(url, token, {
|
|
8122
|
+
method: "PUT",
|
|
8123
|
+
body
|
|
8124
|
+
});
|
|
8125
|
+
};
|
|
8126
|
+
const helpCenterGet = (subdomain, token, path, params) => {
|
|
8127
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
|
|
8128
|
+
return executeRequest(url, token);
|
|
8129
|
+
};
|
|
8130
|
+
const helpCenterPost = (subdomain, token, path, body) => {
|
|
8131
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8132
|
+
return executeRequest(url, token, {
|
|
8133
|
+
method: "POST",
|
|
8134
|
+
body
|
|
8135
|
+
});
|
|
8136
|
+
};
|
|
8137
|
+
const helpCenterPut = (subdomain, token, path, body) => {
|
|
8138
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8139
|
+
return executeRequest(url, token, {
|
|
8140
|
+
method: "PUT",
|
|
8141
|
+
body
|
|
8142
|
+
});
|
|
8143
|
+
};
|
|
8144
|
+
const helpCenterDelete = (subdomain, token, path) => {
|
|
8145
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8146
|
+
return executeRequest(url, token, { method: "DELETE" });
|
|
8147
|
+
};
|
|
8148
|
+
const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
|
|
8149
|
+
const expectedHost = `${subdomain}.zendesk.com`;
|
|
8150
|
+
const headers = {};
|
|
8151
|
+
if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
|
|
8152
|
+
const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
|
|
8153
|
+
if (!response.ok) {
|
|
8154
|
+
const body = await response.text();
|
|
8155
|
+
throw new ZendeskApiError(response.status, response.statusText, body, "GET");
|
|
8156
|
+
}
|
|
8157
|
+
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
8158
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
8159
|
+
return {
|
|
8160
|
+
data: Buffer.from(arrayBuffer),
|
|
8161
|
+
contentType
|
|
8162
|
+
};
|
|
8163
|
+
};
|
|
8164
|
+
const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
|
|
8165
|
+
const params = { filename };
|
|
8166
|
+
if (uploadToken) params["token"] = uploadToken;
|
|
8167
|
+
const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
|
|
8168
|
+
const response = await performFetch("POST", url, {
|
|
8169
|
+
headers: {
|
|
8170
|
+
Authorization: buildAuthHeader(token),
|
|
8171
|
+
"Content-Type": contentType
|
|
8172
|
+
},
|
|
8173
|
+
body: data
|
|
8174
|
+
}, TRANSFER_TIMEOUT_MS);
|
|
8175
|
+
if (!response.ok) {
|
|
8176
|
+
const responseBody = await response.text();
|
|
8177
|
+
throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
|
|
8178
|
+
}
|
|
8179
|
+
return response.json();
|
|
8180
|
+
};
|
|
8181
|
+
const helpCenterUpload = async (subdomain, token, path, formData) => {
|
|
8182
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
8183
|
+
const response = await performFetch("POST", url, {
|
|
8184
|
+
headers: { Authorization: buildAuthHeader(token) },
|
|
8185
|
+
body: formData
|
|
8186
|
+
}, TRANSFER_TIMEOUT_MS);
|
|
8187
|
+
if (!response.ok) {
|
|
8188
|
+
const responseBody = await response.text();
|
|
8189
|
+
throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
|
|
8190
|
+
}
|
|
8191
|
+
return response.json();
|
|
8192
|
+
};
|
|
8193
|
+
//#endregion
|
|
8194
|
+
//#region src/client/brands.ts
|
|
8195
|
+
const BRAND_SUBDOMAIN = /^[a-z0-9][a-z0-9-]*$/;
|
|
8196
|
+
/**
|
|
8197
|
+
* Fetch every brand of the account (cursor-paginated). `page[size]` is sent on
|
|
8198
|
+
* the FIRST request too: without it Zendesk falls back to OFFSET pagination,
|
|
8199
|
+
* which answers without `meta.after_cursor`, and the loop below would stop
|
|
8200
|
+
* after page 1 — a brand past the first 100 would resolve as Unknown.
|
|
8201
|
+
*/
|
|
8202
|
+
const fetchAllBrands = async (subdomain, token) => {
|
|
8203
|
+
const brands = [];
|
|
8204
|
+
let cursor;
|
|
8205
|
+
do {
|
|
8206
|
+
const response = await zendeskGet(subdomain, token, "/brands", {
|
|
8207
|
+
"page[size]": String(100),
|
|
8208
|
+
...cursor ? { "page[after]": cursor } : {}
|
|
8209
|
+
});
|
|
8210
|
+
brands.push(...response.brands ?? []);
|
|
8211
|
+
const meta = response.meta;
|
|
8212
|
+
if (meta?.has_more) {
|
|
8213
|
+
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.");
|
|
8214
|
+
cursor = meta.after_cursor;
|
|
8215
|
+
} else cursor = void 0;
|
|
8216
|
+
} while (cursor);
|
|
8217
|
+
return brands;
|
|
8218
|
+
};
|
|
8219
|
+
const createBrandSubdomainResolver = (subdomain, getToken) => {
|
|
8220
|
+
let cached;
|
|
8221
|
+
const brands = () => {
|
|
8222
|
+
if (!cached || Date.now() - cached.at >= 3e5) {
|
|
8223
|
+
const promise = (async () => fetchAllBrands(subdomain, await getToken()))();
|
|
8224
|
+
cached = {
|
|
8225
|
+
at: Date.now(),
|
|
8226
|
+
promise
|
|
8227
|
+
};
|
|
8228
|
+
promise.catch(() => {
|
|
8229
|
+
cached = void 0;
|
|
8230
|
+
});
|
|
8231
|
+
}
|
|
8232
|
+
return cached.promise;
|
|
8233
|
+
};
|
|
8234
|
+
return async (idOrSubdomain) => {
|
|
8235
|
+
const all = await brands();
|
|
8236
|
+
const brand = all.find((b) => String(b.id) === idOrSubdomain) ?? all.find((b) => b.subdomain.toLowerCase() === idOrSubdomain.toLowerCase());
|
|
8237
|
+
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.`);
|
|
8238
|
+
if (!BRAND_SUBDOMAIN.test(brand.subdomain)) throw new Error(`Brand ${brand.id} has an unexpected subdomain; refusing to send a request to it.`);
|
|
8239
|
+
return brand.subdomain;
|
|
8240
|
+
};
|
|
8241
|
+
};
|
|
8242
|
+
//#endregion
|
|
7835
8243
|
//#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
7836
8244
|
var __create = Object.create;
|
|
7837
8245
|
var __defProp = Object.defineProperty;
|
|
@@ -25342,339 +25750,6 @@ var WebStandardStreamableHTTPServerTransport = class {
|
|
|
25342
25750
|
}
|
|
25343
25751
|
};
|
|
25344
25752
|
//#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
25753
|
//#region node_modules/.pnpm/cheerio@1.2.0/node_modules/cheerio/dist/esm/options.js
|
|
25679
25754
|
const defaultOpts$2 = { _useHtmlParser2: false };
|
|
25680
25755
|
/**
|
|
@@ -84056,7 +84131,7 @@ function toAlignment(value) {
|
|
|
84056
84131
|
return code === 67 || code === 99 ? 99 : code === 76 || code === 108 ? 108 : code === 82 || code === 114 ? 114 : 0;
|
|
84057
84132
|
}
|
|
84058
84133
|
//#endregion
|
|
84059
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84134
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/configure.js
|
|
84060
84135
|
/**
|
|
84061
84136
|
* @import {Options, State} from './types.js'
|
|
84062
84137
|
*/
|
|
@@ -84123,7 +84198,7 @@ function map$2(left, right) {
|
|
|
84123
84198
|
if (right) Object.assign(left, right);
|
|
84124
84199
|
}
|
|
84125
84200
|
//#endregion
|
|
84126
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84201
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js
|
|
84127
84202
|
/**
|
|
84128
84203
|
* @import {Blockquote, Parents} from 'mdast'
|
|
84129
84204
|
* @import {Info, Map, State} from 'mdast-util-to-markdown'
|
|
@@ -84160,7 +84235,7 @@ function map$1(line, _, blank) {
|
|
|
84160
84235
|
return ">" + (blank ? "" : " ") + line;
|
|
84161
84236
|
}
|
|
84162
84237
|
//#endregion
|
|
84163
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84238
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js
|
|
84164
84239
|
/**
|
|
84165
84240
|
* @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
|
|
84166
84241
|
*/
|
|
@@ -84197,7 +84272,7 @@ function listInScope(stack, list, none) {
|
|
|
84197
84272
|
return false;
|
|
84198
84273
|
}
|
|
84199
84274
|
//#endregion
|
|
84200
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84275
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/break.js
|
|
84201
84276
|
/**
|
|
84202
84277
|
* @import {Break, Parents} from 'mdast'
|
|
84203
84278
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
@@ -84253,7 +84328,7 @@ function longestStreak(value, substring) {
|
|
|
84253
84328
|
return max;
|
|
84254
84329
|
}
|
|
84255
84330
|
//#endregion
|
|
84256
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84331
|
+
//#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
84332
|
/**
|
|
84258
84333
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84259
84334
|
* @import {Code} from 'mdast'
|
|
@@ -84272,7 +84347,7 @@ function formatCodeAsIndented(node, state) {
|
|
|
84272
84347
|
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
84348
|
}
|
|
84274
84349
|
//#endregion
|
|
84275
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84350
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-fence.js
|
|
84276
84351
|
/**
|
|
84277
84352
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
84278
84353
|
*/
|
|
@@ -84290,7 +84365,7 @@ function checkFence(state) {
|
|
|
84290
84365
|
return marker;
|
|
84291
84366
|
}
|
|
84292
84367
|
//#endregion
|
|
84293
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84368
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/code.js
|
|
84294
84369
|
/**
|
|
84295
84370
|
* @import {Info, Map, State} from 'mdast-util-to-markdown'
|
|
84296
84371
|
* @import {Code, Parents} from 'mdast'
|
|
@@ -84359,7 +84434,7 @@ function map(line, _, blank) {
|
|
|
84359
84434
|
return (blank ? "" : " ") + line;
|
|
84360
84435
|
}
|
|
84361
84436
|
//#endregion
|
|
84362
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84437
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-quote.js
|
|
84363
84438
|
/**
|
|
84364
84439
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
84365
84440
|
*/
|
|
@@ -84377,7 +84452,7 @@ function checkQuote(state) {
|
|
|
84377
84452
|
return marker;
|
|
84378
84453
|
}
|
|
84379
84454
|
//#endregion
|
|
84380
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84455
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/definition.js
|
|
84381
84456
|
/**
|
|
84382
84457
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84383
84458
|
* @import {Definition, Parents} from 'mdast'
|
|
@@ -84443,7 +84518,7 @@ function definition$1(node, _, state, info) {
|
|
|
84443
84518
|
return value;
|
|
84444
84519
|
}
|
|
84445
84520
|
//#endregion
|
|
84446
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84521
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js
|
|
84447
84522
|
/**
|
|
84448
84523
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
84449
84524
|
*/
|
|
@@ -84461,7 +84536,7 @@ function checkEmphasis(state) {
|
|
|
84461
84536
|
return marker;
|
|
84462
84537
|
}
|
|
84463
84538
|
//#endregion
|
|
84464
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84539
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js
|
|
84465
84540
|
/**
|
|
84466
84541
|
* @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
|
|
84467
84542
|
* @import {Emphasis, Parents} from 'mdast'
|
|
@@ -84526,7 +84601,7 @@ function peek$1(_, _1, state) {
|
|
|
84526
84601
|
return state.options.emphasis || "*";
|
|
84527
84602
|
}
|
|
84528
84603
|
//#endregion
|
|
84529
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84604
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js
|
|
84530
84605
|
/**
|
|
84531
84606
|
* Encode a code point as a character reference.
|
|
84532
84607
|
*
|
|
@@ -84539,7 +84614,7 @@ function encodeCharacterReference(code) {
|
|
|
84539
84614
|
return "&#x" + code.toString(16).toUpperCase() + ";";
|
|
84540
84615
|
}
|
|
84541
84616
|
//#endregion
|
|
84542
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84617
|
+
//#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
84618
|
/**
|
|
84544
84619
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84545
84620
|
* @import {Heading} from 'mdast'
|
|
@@ -84565,7 +84640,7 @@ function formatHeadingAsSetext(node, state) {
|
|
|
84565
84640
|
return Boolean((!node.depth || node.depth < 3) && toString(node) && (state.options.setext || literalWithBreak));
|
|
84566
84641
|
}
|
|
84567
84642
|
//#endregion
|
|
84568
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84643
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/heading.js
|
|
84569
84644
|
/**
|
|
84570
84645
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84571
84646
|
* @import {Heading, Parents} from 'mdast'
|
|
@@ -84617,7 +84692,7 @@ function heading$1(node, _, state, info) {
|
|
|
84617
84692
|
return value;
|
|
84618
84693
|
}
|
|
84619
84694
|
//#endregion
|
|
84620
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84695
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/html.js
|
|
84621
84696
|
/**
|
|
84622
84697
|
* @import {Html} from 'mdast'
|
|
84623
84698
|
*/
|
|
@@ -84643,7 +84718,7 @@ function htmlPeek() {
|
|
|
84643
84718
|
return "<";
|
|
84644
84719
|
}
|
|
84645
84720
|
//#endregion
|
|
84646
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84721
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image.js
|
|
84647
84722
|
/**
|
|
84648
84723
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84649
84724
|
* @import {Image, Parents} from 'mdast'
|
|
@@ -84720,7 +84795,7 @@ function imagePeek() {
|
|
|
84720
84795
|
return "!";
|
|
84721
84796
|
}
|
|
84722
84797
|
//#endregion
|
|
84723
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84798
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js
|
|
84724
84799
|
/**
|
|
84725
84800
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84726
84801
|
* @import {ImageReference, Parents} from 'mdast'
|
|
@@ -84781,7 +84856,7 @@ function imageReferencePeek() {
|
|
|
84781
84856
|
return "!";
|
|
84782
84857
|
}
|
|
84783
84858
|
//#endregion
|
|
84784
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84859
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js
|
|
84785
84860
|
/**
|
|
84786
84861
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84787
84862
|
* @import {InlineCode, Parents} from 'mdast'
|
|
@@ -84829,7 +84904,7 @@ function inlineCodePeek() {
|
|
|
84829
84904
|
return "`";
|
|
84830
84905
|
}
|
|
84831
84906
|
//#endregion
|
|
84832
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84907
|
+
//#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
84908
|
/**
|
|
84834
84909
|
* @import {State} from 'mdast-util-to-markdown'
|
|
84835
84910
|
* @import {Link} from 'mdast'
|
|
@@ -84849,7 +84924,7 @@ function formatLinkAsAutolink(node, state) {
|
|
|
84849
84924
|
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
84925
|
}
|
|
84851
84926
|
//#endregion
|
|
84852
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
84927
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link.js
|
|
84853
84928
|
/**
|
|
84854
84929
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84855
84930
|
* @import {Link, Parents} from 'mdast'
|
|
@@ -84950,7 +85025,7 @@ function linkPeek(node, _, state) {
|
|
|
84950
85025
|
return formatLinkAsAutolink(node, state) ? "<" : "[";
|
|
84951
85026
|
}
|
|
84952
85027
|
//#endregion
|
|
84953
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85028
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js
|
|
84954
85029
|
/**
|
|
84955
85030
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
84956
85031
|
* @import {LinkReference, Parents} from 'mdast'
|
|
@@ -85011,7 +85086,7 @@ function linkReferencePeek() {
|
|
|
85011
85086
|
return "[";
|
|
85012
85087
|
}
|
|
85013
85088
|
//#endregion
|
|
85014
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85089
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js
|
|
85015
85090
|
/**
|
|
85016
85091
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85017
85092
|
*/
|
|
@@ -85029,7 +85104,7 @@ function checkBullet(state) {
|
|
|
85029
85104
|
return marker;
|
|
85030
85105
|
}
|
|
85031
85106
|
//#endregion
|
|
85032
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85107
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js
|
|
85033
85108
|
/**
|
|
85034
85109
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85035
85110
|
*/
|
|
@@ -85050,7 +85125,7 @@ function checkBulletOther(state) {
|
|
|
85050
85125
|
return bulletOther;
|
|
85051
85126
|
}
|
|
85052
85127
|
//#endregion
|
|
85053
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85128
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js
|
|
85054
85129
|
/**
|
|
85055
85130
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85056
85131
|
*/
|
|
@@ -85068,7 +85143,7 @@ function checkBulletOrdered(state) {
|
|
|
85068
85143
|
return marker;
|
|
85069
85144
|
}
|
|
85070
85145
|
//#endregion
|
|
85071
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85146
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule.js
|
|
85072
85147
|
/**
|
|
85073
85148
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85074
85149
|
*/
|
|
@@ -85086,7 +85161,7 @@ function checkRule(state) {
|
|
|
85086
85161
|
return marker;
|
|
85087
85162
|
}
|
|
85088
85163
|
//#endregion
|
|
85089
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85164
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list.js
|
|
85090
85165
|
/**
|
|
85091
85166
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85092
85167
|
* @import {List, Parents} from 'mdast'
|
|
@@ -85136,7 +85211,7 @@ function list$2(node, parent, state, info) {
|
|
|
85136
85211
|
return value;
|
|
85137
85212
|
}
|
|
85138
85213
|
//#endregion
|
|
85139
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85214
|
+
//#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
85215
|
/**
|
|
85141
85216
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85142
85217
|
*/
|
|
@@ -85154,7 +85229,7 @@ function checkListItemIndent(state) {
|
|
|
85154
85229
|
return style;
|
|
85155
85230
|
}
|
|
85156
85231
|
//#endregion
|
|
85157
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85232
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list-item.js
|
|
85158
85233
|
/**
|
|
85159
85234
|
* @import {Info, Map, State} from 'mdast-util-to-markdown'
|
|
85160
85235
|
* @import {ListItem, Parents} from 'mdast'
|
|
@@ -85197,7 +85272,7 @@ function listItem$1(node, parent, state, info) {
|
|
|
85197
85272
|
}
|
|
85198
85273
|
}
|
|
85199
85274
|
//#endregion
|
|
85200
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85275
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js
|
|
85201
85276
|
/**
|
|
85202
85277
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85203
85278
|
* @import {Paragraph, Parents} from 'mdast'
|
|
@@ -85225,7 +85300,7 @@ function paragraph$1(node, _, state, info) {
|
|
|
85225
85300
|
return value;
|
|
85226
85301
|
}
|
|
85227
85302
|
//#endregion
|
|
85228
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85303
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/root.js
|
|
85229
85304
|
/**
|
|
85230
85305
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85231
85306
|
* @import {Parents, Root} from 'mdast'
|
|
@@ -85254,7 +85329,7 @@ function root$1(node, _, state, info) {
|
|
|
85254
85329
|
return value;
|
|
85255
85330
|
}
|
|
85256
85331
|
//#endregion
|
|
85257
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85332
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-strong.js
|
|
85258
85333
|
/**
|
|
85259
85334
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85260
85335
|
*/
|
|
@@ -85272,7 +85347,7 @@ function checkStrong(state) {
|
|
|
85272
85347
|
return marker;
|
|
85273
85348
|
}
|
|
85274
85349
|
//#endregion
|
|
85275
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85350
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/strong.js
|
|
85276
85351
|
/**
|
|
85277
85352
|
* @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
|
|
85278
85353
|
* @import {Parents, Strong} from 'mdast'
|
|
@@ -85337,7 +85412,7 @@ function peek(_, _1, state) {
|
|
|
85337
85412
|
return state.options.strong || "*";
|
|
85338
85413
|
}
|
|
85339
85414
|
//#endregion
|
|
85340
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85415
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/text.js
|
|
85341
85416
|
/**
|
|
85342
85417
|
* @import {Info, State} from 'mdast-util-to-markdown'
|
|
85343
85418
|
* @import {Parents, Text} from 'mdast'
|
|
@@ -85347,7 +85422,7 @@ function peek(_, _1, state) {
|
|
|
85347
85422
|
*
|
|
85348
85423
|
* @param {Text} node
|
|
85349
85424
|
* Node to serialize.
|
|
85350
|
-
* @param {Parents | undefined}
|
|
85425
|
+
* @param {Parents | undefined} parent
|
|
85351
85426
|
* Parent node.
|
|
85352
85427
|
* @param {State} state
|
|
85353
85428
|
* Info passed around.
|
|
@@ -85356,11 +85431,18 @@ function peek(_, _1, state) {
|
|
|
85356
85431
|
* @returns {string}
|
|
85357
85432
|
* Serialized markdown.
|
|
85358
85433
|
*/
|
|
85359
|
-
function text$4(node,
|
|
85360
|
-
|
|
85434
|
+
function text$4(node, parent, state, info) {
|
|
85435
|
+
/* c8 ignore next -- parent always passed */
|
|
85436
|
+
const siblings = parent ? parent.children : [];
|
|
85437
|
+
const index = siblings.indexOf(node);
|
|
85438
|
+
return state.safe(node.value, {
|
|
85439
|
+
...info,
|
|
85440
|
+
afterNode: siblings[index + 1],
|
|
85441
|
+
beforeNode: siblings[index - 1]
|
|
85442
|
+
});
|
|
85361
85443
|
}
|
|
85362
85444
|
//#endregion
|
|
85363
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85445
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js
|
|
85364
85446
|
/**
|
|
85365
85447
|
* @import {Options, State} from 'mdast-util-to-markdown'
|
|
85366
85448
|
*/
|
|
@@ -85378,7 +85460,7 @@ function checkRuleRepetition(state) {
|
|
|
85378
85460
|
return repetition;
|
|
85379
85461
|
}
|
|
85380
85462
|
//#endregion
|
|
85381
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85463
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js
|
|
85382
85464
|
/**
|
|
85383
85465
|
* @import {State} from 'mdast-util-to-markdown'
|
|
85384
85466
|
* @import {Parents, ThematicBreak} from 'mdast'
|
|
@@ -85400,7 +85482,7 @@ function thematicBreak$2(_, _1, state) {
|
|
|
85400
85482
|
return state.options.ruleSpaces ? value.slice(0, -1) : value;
|
|
85401
85483
|
}
|
|
85402
85484
|
//#endregion
|
|
85403
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85485
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/index.js
|
|
85404
85486
|
/**
|
|
85405
85487
|
* Default (CommonMark) handlers.
|
|
85406
85488
|
*/
|
|
@@ -85526,7 +85608,7 @@ const htmlRawNames = [
|
|
|
85526
85608
|
"textarea"
|
|
85527
85609
|
];
|
|
85528
85610
|
//#endregion
|
|
85529
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85611
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/html-kind.js
|
|
85530
85612
|
/**
|
|
85531
85613
|
* @typedef {1 | 2 | 3 | 4 | 5 | 6 | 7} Kind
|
|
85532
85614
|
* Kind of HTML (flow) element.
|
|
@@ -85574,7 +85656,7 @@ function htmlKind(value) {
|
|
|
85574
85656
|
return htmlBlockNames.includes(name) ? 6 : 7;
|
|
85575
85657
|
}
|
|
85576
85658
|
//#endregion
|
|
85577
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85659
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/join.js
|
|
85578
85660
|
/**
|
|
85579
85661
|
* @import {Join} from 'mdast-util-to-markdown'
|
|
85580
85662
|
*/
|
|
@@ -85601,7 +85683,7 @@ function joinDefaults(left, right, parent, state) {
|
|
|
85601
85683
|
}
|
|
85602
85684
|
}
|
|
85603
85685
|
//#endregion
|
|
85604
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
85686
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/unsafe.js
|
|
85605
85687
|
/**
|
|
85606
85688
|
* @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
|
|
85607
85689
|
*/
|
|
@@ -88069,7 +88151,7 @@ function decode($0, $1, $2) {
|
|
|
88069
88151
|
return decodeNamedCharacterReference($2) || $0;
|
|
88070
88152
|
}
|
|
88071
88153
|
//#endregion
|
|
88072
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88154
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/association.js
|
|
88073
88155
|
/**
|
|
88074
88156
|
* @import {AssociationId} from '../types.js'
|
|
88075
88157
|
*/
|
|
@@ -88098,7 +88180,7 @@ function association(node) {
|
|
|
88098
88180
|
return decodeString(node.identifier);
|
|
88099
88181
|
}
|
|
88100
88182
|
//#endregion
|
|
88101
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88183
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js
|
|
88102
88184
|
/**
|
|
88103
88185
|
* @import {CompilePattern} from '../types.js'
|
|
88104
88186
|
*/
|
|
@@ -88110,7 +88192,7 @@ function association(node) {
|
|
|
88110
88192
|
function compilePattern(pattern) {
|
|
88111
88193
|
if (!pattern._compiled) {
|
|
88112
88194
|
const before = (pattern.atBreak ? "[\\r\\n][\\t ]*" : "") + (pattern.before ? "(?:" + pattern.before + ")" : "");
|
|
88113
|
-
pattern._compiled = new RegExp((before ? "(" + before + ")" : "") + (/[$()
|
|
88195
|
+
pattern._compiled = new RegExp((pattern.beforeNode ? "^" : before ? "(" + before + ")" : "") + (/[$()*+.?[\\\]^{|}]/.test(pattern.character) ? "\\" : "") + pattern.character + (pattern.afterNode ? "$" : pattern.after ? "(?:" + pattern.after + ")" : ""), pattern.unicode ? "gu" : "g");
|
|
88114
88196
|
}
|
|
88115
88197
|
return pattern._compiled;
|
|
88116
88198
|
}
|
|
@@ -88138,7 +88220,7 @@ function classifyCharacter(code) {
|
|
|
88138
88220
|
if (unicodePunctuation(code)) return 2;
|
|
88139
88221
|
}
|
|
88140
88222
|
//#endregion
|
|
88141
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88223
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/character.js
|
|
88142
88224
|
/**
|
|
88143
88225
|
* Get the first character of a value.
|
|
88144
88226
|
*
|
|
@@ -88168,7 +88250,7 @@ function lastCharacter(value) {
|
|
|
88168
88250
|
return code !== void 0 && code > 65535 ? value.slice(-2) : value.slice(-1);
|
|
88169
88251
|
}
|
|
88170
88252
|
//#endregion
|
|
88171
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88253
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-info.js
|
|
88172
88254
|
/**
|
|
88173
88255
|
* @import {EncodeSides} from '../types.js'
|
|
88174
88256
|
*/
|
|
@@ -88240,7 +88322,7 @@ function encodeInfo(outside, inside, marker) {
|
|
|
88240
88322
|
};
|
|
88241
88323
|
}
|
|
88242
88324
|
//#endregion
|
|
88243
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88325
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js
|
|
88244
88326
|
/**
|
|
88245
88327
|
* @import {Attention, Handle, Info, State} from 'mdast-util-to-markdown'
|
|
88246
88328
|
* @import {PhrasingParents} from '../types.js'
|
|
@@ -88733,7 +88815,7 @@ function serialize(items, before, after) {
|
|
|
88733
88815
|
return result;
|
|
88734
88816
|
}
|
|
88735
88817
|
//#endregion
|
|
88736
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88818
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-flow.js
|
|
88737
88819
|
/**
|
|
88738
88820
|
* @import {State} from 'mdast-util-to-markdown'
|
|
88739
88821
|
* @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
|
|
@@ -88797,7 +88879,7 @@ function between(left, right, parent, state) {
|
|
|
88797
88879
|
return "\n\n";
|
|
88798
88880
|
}
|
|
88799
88881
|
//#endregion
|
|
88800
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88882
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js
|
|
88801
88883
|
/**
|
|
88802
88884
|
* @import {IndentLines} from '../types.js'
|
|
88803
88885
|
*/
|
|
@@ -88833,9 +88915,10 @@ function indentLines(value, map) {
|
|
|
88833
88915
|
}
|
|
88834
88916
|
}
|
|
88835
88917
|
//#endregion
|
|
88836
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
88918
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/safe.js
|
|
88837
88919
|
/**
|
|
88838
|
-
* @import {
|
|
88920
|
+
* @import {Nodes} from 'mdast'
|
|
88921
|
+
* @import {SafeConfig, State, Unsafe} from 'mdast-util-to-markdown'
|
|
88839
88922
|
*/
|
|
88840
88923
|
const own$3 = {}.hasOwnProperty;
|
|
88841
88924
|
/**
|
|
@@ -88872,17 +88955,21 @@ function safe(state, input, config) {
|
|
|
88872
88955
|
/** @type {Record<number, {before: boolean, after: boolean}>} */
|
|
88873
88956
|
const infos = {};
|
|
88874
88957
|
const percentEncode = state.stack.includes("autolink");
|
|
88958
|
+
const offset = config.before ? config.before.length : 0;
|
|
88959
|
+
const end = value.length - (config.after ? config.after.length : 0);
|
|
88875
88960
|
let index = -1;
|
|
88876
88961
|
while (++index < state.unsafe.length) {
|
|
88877
88962
|
const pattern = state.unsafe[index];
|
|
88878
|
-
if (!patternInScope(state.stack, pattern)) continue;
|
|
88963
|
+
if (!patternInScope(state.stack, pattern) || !nodeInScope(pattern.beforeNode, config.beforeNode) || !nodeInScope(pattern.afterNode, config.afterNode)) continue;
|
|
88879
88964
|
const expression = state.compilePattern(pattern);
|
|
88965
|
+
const from = pattern.beforeNode ? offset : 0;
|
|
88966
|
+
const subject = value.slice(from, pattern.afterNode ? end : void 0);
|
|
88880
88967
|
/** @type {RegExpExecArray | null} */
|
|
88881
88968
|
let match;
|
|
88882
|
-
while (match = expression.exec(
|
|
88969
|
+
while (match = expression.exec(subject)) {
|
|
88883
88970
|
const before = "before" in pattern || Boolean(pattern.atBreak);
|
|
88884
88971
|
const after = "after" in pattern;
|
|
88885
|
-
const position = match.index + (before ? match[1].length : 0);
|
|
88972
|
+
const position = from + match.index + (before ? match[1].length : 0);
|
|
88886
88973
|
if (own$3.call(infos, position)) {
|
|
88887
88974
|
if (infos[position].before && !before) infos[position].before = false;
|
|
88888
88975
|
if (infos[position].after && !after) infos[position].after = false;
|
|
@@ -88896,8 +88983,6 @@ function safe(state, input, config) {
|
|
|
88896
88983
|
}
|
|
88897
88984
|
}
|
|
88898
88985
|
positions.sort(numerical);
|
|
88899
|
-
const offset = config.before ? config.before.length : 0;
|
|
88900
|
-
const end = value.length - (config.after ? config.after.length : 0);
|
|
88901
88986
|
let start = offset;
|
|
88902
88987
|
index = -1;
|
|
88903
88988
|
while (++index < positions.length) {
|
|
@@ -88932,6 +89017,21 @@ function safe(state, input, config) {
|
|
|
88932
89017
|
return result.join("");
|
|
88933
89018
|
}
|
|
88934
89019
|
/**
|
|
89020
|
+
* Check if a node matches the node types of a pattern.
|
|
89021
|
+
*
|
|
89022
|
+
* @param {Array<Nodes['type']> | Nodes['type'] | null | undefined} types
|
|
89023
|
+
* Node types.
|
|
89024
|
+
* @param {Nodes | undefined} node
|
|
89025
|
+
* Node.
|
|
89026
|
+
* @returns {boolean}
|
|
89027
|
+
* Whether `node` matches `types`.
|
|
89028
|
+
*/
|
|
89029
|
+
function nodeInScope(types, node) {
|
|
89030
|
+
if (!types) return true;
|
|
89031
|
+
if (!node) return false;
|
|
89032
|
+
return typeof types === "string" ? types === node.type : types.includes(node.type);
|
|
89033
|
+
}
|
|
89034
|
+
/**
|
|
88935
89035
|
* Compare two numbers.
|
|
88936
89036
|
*
|
|
88937
89037
|
* @param {number} a
|
|
@@ -88975,7 +89075,7 @@ function escapeBackslashes(value, after) {
|
|
|
88975
89075
|
return results.join("");
|
|
88976
89076
|
}
|
|
88977
89077
|
//#endregion
|
|
88978
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89078
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/track.js
|
|
88979
89079
|
/**
|
|
88980
89080
|
* @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
|
|
88981
89081
|
*/
|
|
@@ -89033,7 +89133,7 @@ function track(config) {
|
|
|
89033
89133
|
}
|
|
89034
89134
|
}
|
|
89035
89135
|
//#endregion
|
|
89036
|
-
//#region node_modules/.pnpm/mdast-util-to-markdown@2.
|
|
89136
|
+
//#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/index.js
|
|
89037
89137
|
/**
|
|
89038
89138
|
* @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
|
|
89039
89139
|
* @import {Nodes} from 'mdast'
|
|
@@ -99040,7 +99140,7 @@ function preprocess() {
|
|
|
99040
99140
|
}
|
|
99041
99141
|
}
|
|
99042
99142
|
//#endregion
|
|
99043
|
-
//#region node_modules/.pnpm/mdast-util-from-markdown@2.
|
|
99143
|
+
//#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
99144
|
/**
|
|
99045
99145
|
* @import {
|
|
99046
99146
|
* Break,
|
|
@@ -99120,6 +99220,8 @@ function fromMarkdown(value, encoding, options) {
|
|
|
99120
99220
|
function compiler(options) {
|
|
99121
99221
|
/** @type {Config} */
|
|
99122
99222
|
const config = {
|
|
99223
|
+
afterExit: [],
|
|
99224
|
+
beforeEnter: [],
|
|
99123
99225
|
transforms: [],
|
|
99124
99226
|
canContainEols: [
|
|
99125
99227
|
"emphasis",
|
|
@@ -99239,7 +99341,8 @@ function compiler(options) {
|
|
|
99239
99341
|
/** @type {Root} */
|
|
99240
99342
|
let tree = {
|
|
99241
99343
|
type: "root",
|
|
99242
|
-
children: []
|
|
99344
|
+
children: [],
|
|
99345
|
+
position: void 0
|
|
99243
99346
|
};
|
|
99244
99347
|
/** @type {Omit<CompileContext, 'sliceSerialize'>} */
|
|
99245
99348
|
const context = {
|
|
@@ -99254,15 +99357,32 @@ function compiler(options) {
|
|
|
99254
99357
|
};
|
|
99255
99358
|
/** @type {Array<number>} */
|
|
99256
99359
|
const listStack = [];
|
|
99360
|
+
/** @type {Array<Event>} */
|
|
99361
|
+
const prepared = [];
|
|
99257
99362
|
let index = -1;
|
|
99258
|
-
while (++index < events.length)
|
|
99259
|
-
|
|
99260
|
-
|
|
99363
|
+
while (++index < events.length) {
|
|
99364
|
+
prepared.push(events[index]);
|
|
99365
|
+
if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
|
|
99366
|
+
if (events[index][0] === "enter") listStack.push(prepared.length - 1);
|
|
99367
|
+
else prepareList(prepared, listStack.pop());
|
|
99368
|
+
}
|
|
99261
99369
|
}
|
|
99370
|
+
events = prepared;
|
|
99262
99371
|
index = -1;
|
|
99263
99372
|
while (++index < events.length) {
|
|
99264
99373
|
const handler = config[events[index][0]];
|
|
99265
|
-
if (
|
|
99374
|
+
if (events[index][0] === "enter" && config.beforeEnter.length > 0) callListeners(config.beforeEnter, {
|
|
99375
|
+
...context,
|
|
99376
|
+
sliceSerialize: events[index][2].sliceSerialize
|
|
99377
|
+
}, events[index][1]);
|
|
99378
|
+
if (own$2.call(handler, events[index][1].type)) handler[events[index][1].type].call({
|
|
99379
|
+
...context,
|
|
99380
|
+
sliceSerialize: events[index][2].sliceSerialize
|
|
99381
|
+
}, events[index][1]);
|
|
99382
|
+
if (events[index][0] === "exit" && config.afterExit.length > 0) callListeners(config.afterExit, {
|
|
99383
|
+
...context,
|
|
99384
|
+
sliceSerialize: events[index][2].sliceSerialize
|
|
99385
|
+
}, events[index][1]);
|
|
99266
99386
|
}
|
|
99267
99387
|
if (context.tokenStack.length > 0) {
|
|
99268
99388
|
const tail = context.tokenStack[context.tokenStack.length - 1];
|
|
@@ -99285,12 +99405,28 @@ function compiler(options) {
|
|
|
99285
99405
|
return tree;
|
|
99286
99406
|
}
|
|
99287
99407
|
/**
|
|
99408
|
+
* @param {Array<Handle>} listeners
|
|
99409
|
+
* Listeners.
|
|
99410
|
+
* @param {CompileContext} context
|
|
99411
|
+
* Context.
|
|
99412
|
+
* @param {Token} token
|
|
99413
|
+
* Token.
|
|
99414
|
+
* @returns {undefined}
|
|
99415
|
+
* Nothing.
|
|
99416
|
+
*/
|
|
99417
|
+
function callListeners(listeners, context, token) {
|
|
99418
|
+
let index = -1;
|
|
99419
|
+
while (++index < listeners.length) listeners[index].call(context, token);
|
|
99420
|
+
}
|
|
99421
|
+
/**
|
|
99288
99422
|
* @param {Array<Event>} events
|
|
99423
|
+
* Events, ending with the list to prepare.
|
|
99289
99424
|
* @param {number} start
|
|
99290
|
-
*
|
|
99291
|
-
* @returns {
|
|
99425
|
+
* Index of the list enter.
|
|
99426
|
+
* @returns {undefined}
|
|
99292
99427
|
*/
|
|
99293
|
-
function prepareList(events, start
|
|
99428
|
+
function prepareList(events, start) {
|
|
99429
|
+
const end = events.length - 1;
|
|
99294
99430
|
let index = start - 1;
|
|
99295
99431
|
let containerBalance = -1;
|
|
99296
99432
|
let listSpread = false;
|
|
@@ -99302,7 +99438,14 @@ function compiler(options) {
|
|
|
99302
99438
|
let firstBlankLineIndex;
|
|
99303
99439
|
/** @type {boolean | undefined} */
|
|
99304
99440
|
let atMarker;
|
|
99305
|
-
|
|
99441
|
+
/**
|
|
99442
|
+
* `listItem` events to insert before the event at `at`, in walk order.
|
|
99443
|
+
* Merged in after the walk, as inserting while walking would shift the
|
|
99444
|
+
* rest of the list once per item.
|
|
99445
|
+
* @type {Array<{at: number, event: Event}>}
|
|
99446
|
+
*/
|
|
99447
|
+
const insertions = [];
|
|
99448
|
+
while (++index <= end) {
|
|
99306
99449
|
const event = events[index];
|
|
99307
99450
|
switch (event[1].type) {
|
|
99308
99451
|
case "listUnordered":
|
|
@@ -99343,13 +99486,14 @@ function compiler(options) {
|
|
|
99343
99486
|
}
|
|
99344
99487
|
if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) listItem._spread = true;
|
|
99345
99488
|
listItem.end = Object.assign({}, lineIndex ? events[lineIndex][1].start : event[1].end);
|
|
99346
|
-
|
|
99347
|
-
|
|
99348
|
-
|
|
99349
|
-
|
|
99350
|
-
|
|
99351
|
-
|
|
99352
|
-
|
|
99489
|
+
insertions.push({
|
|
99490
|
+
at: lineIndex || index,
|
|
99491
|
+
event: [
|
|
99492
|
+
"exit",
|
|
99493
|
+
listItem,
|
|
99494
|
+
event[2]
|
|
99495
|
+
]
|
|
99496
|
+
});
|
|
99353
99497
|
}
|
|
99354
99498
|
if (event[1].type === "listItemPrefix") {
|
|
99355
99499
|
/** @type {Token} */
|
|
@@ -99360,20 +99504,27 @@ function compiler(options) {
|
|
|
99360
99504
|
end: void 0
|
|
99361
99505
|
};
|
|
99362
99506
|
listItem = item;
|
|
99363
|
-
|
|
99364
|
-
|
|
99365
|
-
|
|
99366
|
-
|
|
99367
|
-
|
|
99368
|
-
|
|
99369
|
-
|
|
99507
|
+
insertions.push({
|
|
99508
|
+
at: index,
|
|
99509
|
+
event: [
|
|
99510
|
+
"enter",
|
|
99511
|
+
item,
|
|
99512
|
+
event[2]
|
|
99513
|
+
]
|
|
99514
|
+
});
|
|
99370
99515
|
firstBlankLineIndex = void 0;
|
|
99371
99516
|
atMarker = true;
|
|
99372
99517
|
}
|
|
99373
99518
|
}
|
|
99374
99519
|
}
|
|
99520
|
+
const listEvents = events.splice(start);
|
|
99521
|
+
let insertion = 0;
|
|
99522
|
+
index = -1;
|
|
99523
|
+
while (++index < listEvents.length) {
|
|
99524
|
+
while (insertion < insertions.length && insertions[insertion].at === start + index) events.push(insertions[insertion++].event);
|
|
99525
|
+
events.push(listEvents[index]);
|
|
99526
|
+
}
|
|
99375
99527
|
events[start][1]._spread = listSpread;
|
|
99376
|
-
return length;
|
|
99377
99528
|
}
|
|
99378
99529
|
/**
|
|
99379
99530
|
* Create an opener handle.
|
|
@@ -99448,7 +99599,7 @@ function compiler(options) {
|
|
|
99448
99599
|
start: token.start,
|
|
99449
99600
|
end: token.end
|
|
99450
99601
|
}) + "): it’s not open");
|
|
99451
|
-
|
|
99602
|
+
if (open[0].type !== token.type) {
|
|
99452
99603
|
if (onExitError) onExitError.call(this, token, open[0]);
|
|
99453
99604
|
else (open[1] || defaultOnError).call(this, token, open[0]);
|
|
99454
99605
|
}
|
|
@@ -99816,7 +99967,8 @@ function compiler(options) {
|
|
|
99816
99967
|
function blockQuote() {
|
|
99817
99968
|
return {
|
|
99818
99969
|
type: "blockquote",
|
|
99819
|
-
children: []
|
|
99970
|
+
children: [],
|
|
99971
|
+
position: void 0
|
|
99820
99972
|
};
|
|
99821
99973
|
}
|
|
99822
99974
|
/** @returns {Code} */
|
|
@@ -99825,14 +99977,16 @@ function compiler(options) {
|
|
|
99825
99977
|
type: "code",
|
|
99826
99978
|
lang: null,
|
|
99827
99979
|
meta: null,
|
|
99828
|
-
value: ""
|
|
99980
|
+
value: "",
|
|
99981
|
+
position: void 0
|
|
99829
99982
|
};
|
|
99830
99983
|
}
|
|
99831
99984
|
/** @returns {InlineCode} */
|
|
99832
99985
|
function codeText() {
|
|
99833
99986
|
return {
|
|
99834
99987
|
type: "inlineCode",
|
|
99835
|
-
value: ""
|
|
99988
|
+
value: "",
|
|
99989
|
+
position: void 0
|
|
99836
99990
|
};
|
|
99837
99991
|
}
|
|
99838
99992
|
/** @returns {Definition} */
|
|
@@ -99842,14 +99996,16 @@ function compiler(options) {
|
|
|
99842
99996
|
identifier: "",
|
|
99843
99997
|
label: null,
|
|
99844
99998
|
title: null,
|
|
99845
|
-
url: ""
|
|
99999
|
+
url: "",
|
|
100000
|
+
position: void 0
|
|
99846
100001
|
};
|
|
99847
100002
|
}
|
|
99848
100003
|
/** @returns {Emphasis} */
|
|
99849
100004
|
function emphasis() {
|
|
99850
100005
|
return {
|
|
99851
100006
|
type: "emphasis",
|
|
99852
|
-
children: []
|
|
100007
|
+
children: [],
|
|
100008
|
+
position: void 0
|
|
99853
100009
|
};
|
|
99854
100010
|
}
|
|
99855
100011
|
/** @returns {Heading} */
|
|
@@ -99857,18 +100013,23 @@ function compiler(options) {
|
|
|
99857
100013
|
return {
|
|
99858
100014
|
type: "heading",
|
|
99859
100015
|
depth: 0,
|
|
99860
|
-
children: []
|
|
100016
|
+
children: [],
|
|
100017
|
+
position: void 0
|
|
99861
100018
|
};
|
|
99862
100019
|
}
|
|
99863
100020
|
/** @returns {Break} */
|
|
99864
100021
|
function hardBreak() {
|
|
99865
|
-
return {
|
|
100022
|
+
return {
|
|
100023
|
+
type: "break",
|
|
100024
|
+
position: void 0
|
|
100025
|
+
};
|
|
99866
100026
|
}
|
|
99867
100027
|
/** @returns {Html} */
|
|
99868
100028
|
function html() {
|
|
99869
100029
|
return {
|
|
99870
100030
|
type: "html",
|
|
99871
|
-
value: ""
|
|
100031
|
+
value: "",
|
|
100032
|
+
position: void 0
|
|
99872
100033
|
};
|
|
99873
100034
|
}
|
|
99874
100035
|
/** @returns {Image} */
|
|
@@ -99877,7 +100038,8 @@ function compiler(options) {
|
|
|
99877
100038
|
type: "image",
|
|
99878
100039
|
title: null,
|
|
99879
100040
|
url: "",
|
|
99880
|
-
alt: null
|
|
100041
|
+
alt: null,
|
|
100042
|
+
position: void 0
|
|
99881
100043
|
};
|
|
99882
100044
|
}
|
|
99883
100045
|
/** @returns {Link} */
|
|
@@ -99886,7 +100048,8 @@ function compiler(options) {
|
|
|
99886
100048
|
type: "link",
|
|
99887
100049
|
title: null,
|
|
99888
100050
|
url: "",
|
|
99889
|
-
children: []
|
|
100051
|
+
children: [],
|
|
100052
|
+
position: void 0
|
|
99890
100053
|
};
|
|
99891
100054
|
}
|
|
99892
100055
|
/**
|
|
@@ -99899,7 +100062,8 @@ function compiler(options) {
|
|
|
99899
100062
|
ordered: token.type === "listOrdered",
|
|
99900
100063
|
start: null,
|
|
99901
100064
|
spread: token._spread,
|
|
99902
|
-
children: []
|
|
100065
|
+
children: [],
|
|
100066
|
+
position: void 0
|
|
99903
100067
|
};
|
|
99904
100068
|
}
|
|
99905
100069
|
/**
|
|
@@ -99911,33 +100075,40 @@ function compiler(options) {
|
|
|
99911
100075
|
type: "listItem",
|
|
99912
100076
|
spread: token._spread,
|
|
99913
100077
|
checked: null,
|
|
99914
|
-
children: []
|
|
100078
|
+
children: [],
|
|
100079
|
+
position: void 0
|
|
99915
100080
|
};
|
|
99916
100081
|
}
|
|
99917
100082
|
/** @returns {Paragraph} */
|
|
99918
100083
|
function paragraph() {
|
|
99919
100084
|
return {
|
|
99920
100085
|
type: "paragraph",
|
|
99921
|
-
children: []
|
|
100086
|
+
children: [],
|
|
100087
|
+
position: void 0
|
|
99922
100088
|
};
|
|
99923
100089
|
}
|
|
99924
100090
|
/** @returns {Strong} */
|
|
99925
100091
|
function strong() {
|
|
99926
100092
|
return {
|
|
99927
100093
|
type: "strong",
|
|
99928
|
-
children: []
|
|
100094
|
+
children: [],
|
|
100095
|
+
position: void 0
|
|
99929
100096
|
};
|
|
99930
100097
|
}
|
|
99931
100098
|
/** @returns {Text} */
|
|
99932
100099
|
function text() {
|
|
99933
100100
|
return {
|
|
99934
100101
|
type: "text",
|
|
99935
|
-
value: ""
|
|
100102
|
+
value: "",
|
|
100103
|
+
position: void 0
|
|
99936
100104
|
};
|
|
99937
100105
|
}
|
|
99938
100106
|
/** @returns {ThematicBreak} */
|
|
99939
100107
|
function thematicBreak() {
|
|
99940
|
-
return {
|
|
100108
|
+
return {
|
|
100109
|
+
type: "thematicBreak",
|
|
100110
|
+
position: void 0
|
|
100111
|
+
};
|
|
99941
100112
|
}
|
|
99942
100113
|
}
|
|
99943
100114
|
/**
|
|
@@ -99987,6 +100158,12 @@ function extension(combined, extension) {
|
|
|
99987
100158
|
if (right) combined[key].push(...right);
|
|
99988
100159
|
break;
|
|
99989
100160
|
}
|
|
100161
|
+
case "afterExit":
|
|
100162
|
+
case "beforeEnter": {
|
|
100163
|
+
const right = extension[key];
|
|
100164
|
+
if (right) combined[key].push(right);
|
|
100165
|
+
break;
|
|
100166
|
+
}
|
|
99990
100167
|
case "enter":
|
|
99991
100168
|
case "exit": {
|
|
99992
100169
|
const right = extension[key];
|
|
@@ -100004,7 +100181,7 @@ function defaultOnError(left, right) {
|
|
|
100004
100181
|
start: right.start,
|
|
100005
100182
|
end: right.end
|
|
100006
100183
|
}) + ") is open");
|
|
100007
|
-
|
|
100184
|
+
throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
|
|
100008
100185
|
start: right.start,
|
|
100009
100186
|
end: right.end
|
|
100010
100187
|
}) + ") is still open");
|
|
@@ -103736,6 +103913,7 @@ const formatNodeTranslationSummary = (translation) => [
|
|
|
103736
103913
|
`- **Updated**: ${translation.updated_at}`
|
|
103737
103914
|
].join("\n");
|
|
103738
103915
|
const formatCategory = (category) => `- **${category.name}** (${category.id}) — ${category.description || "No description"}`;
|
|
103916
|
+
const formatBrand = (brand) => `- **${brand.name}** (${brand.id}) — ${brand.brand_url}${brand.default ? " — default" : ""}${brand.active ? "" : " — inactive"}`;
|
|
103739
103917
|
const formatSection = (section) => `- **${section.name}** (${section.id}) — Category: ${section.category_id} — ${section.description || "No description"}`;
|
|
103740
103918
|
const formatView = (view, count) => {
|
|
103741
103919
|
const countText = count ? ` — ${count.pretty} ticket(s)${count.fresh ? "" : " (count updating)"}` : "";
|
|
@@ -103786,13 +103964,7 @@ const extractSearchPaginationMeta = (response, perPage, page) => {
|
|
|
103786
103964
|
};
|
|
103787
103965
|
//#endregion
|
|
103788
103966
|
//#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
|
-
*/
|
|
103967
|
+
/** Name of the companion tool that lists promoted articles (`help-center.ts`). */
|
|
103796
103968
|
const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
|
|
103797
103969
|
/**
|
|
103798
103970
|
* Scan the Help Center for promoted ("featured") articles with the CALLER'S
|
|
@@ -103801,9 +103973,11 @@ const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
|
|
|
103801
103973
|
* `/articles` and filter `promoted` client-side, bounded by `maxPages` to keep
|
|
103802
103974
|
* the scan tractable on a large Help Center. `truncated` signals the cap was hit.
|
|
103803
103975
|
*
|
|
103804
|
-
*
|
|
103805
|
-
*
|
|
103806
|
-
*
|
|
103976
|
+
* `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own
|
|
103977
|
+
* (a brand is only ever <subdomain>.zendesk.com). Returns the FULL promoted
|
|
103978
|
+
* articles so callers that need rich metadata (the `list_promoted_articles`
|
|
103979
|
+
* tool) get everything; the resource provider maps these down to lean refs
|
|
103980
|
+
* before caching so the per-session cache doesn't retain bodies.
|
|
103807
103981
|
*/
|
|
103808
103982
|
const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOURCES_SCAN_MAX_PAGES) => {
|
|
103809
103983
|
const promoted = [];
|
|
@@ -103833,6 +104007,7 @@ const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOUR
|
|
|
103833
104007
|
* caller's token and render it as Markdown: the shared metadata summary plus the
|
|
103834
104008
|
* body converted from HTML (rather than a raw HTML dump), capped by the response
|
|
103835
104009
|
* character limit. Reuses the same formatting as the `get_article` tool.
|
|
104010
|
+
* `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own.
|
|
103836
104011
|
*/
|
|
103837
104012
|
const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
|
|
103838
104013
|
const path = locale ? `/${locale}/articles/${id}` : `/articles/${id}`;
|
|
@@ -103852,24 +104027,69 @@ const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
|
|
|
103852
104027
|
* module scope — in HTTP mode a shared one would leak a caller's data to
|
|
103853
104028
|
* another. `getToken` resolves lazily, so connecting never triggers the OAuth
|
|
103854
104029
|
* flow, and a 401 notifies `onUnauthorized` to drop the stale token.
|
|
104030
|
+
*
|
|
104031
|
+
* Branding is resolved through the SHARED brand subdomain resolver, never a
|
|
104032
|
+
* second /brands walk: `brandIds` decides which brands `listPromoted` scans
|
|
104033
|
+
* (all of them in multi mode, so every allowed brand's promoted articles are
|
|
104034
|
+
* listed with their own URIs; 'all' scans every brand of the account), and
|
|
104035
|
+
* `readArticle` resolves the URI's {brand} through the same allow-list +
|
|
104036
|
+
* resolver the tools use.
|
|
103855
104037
|
*/
|
|
103856
|
-
const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) => {
|
|
104038
|
+
const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized, resolveBrandSubdomain = () => Promise.resolve(subdomain), brandIds) => {
|
|
103857
104039
|
let cached;
|
|
103858
104040
|
const notifyIfUnauthorized = (err) => {
|
|
103859
104041
|
if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
|
|
103860
104042
|
};
|
|
104043
|
+
const isSingleLock = brandIds !== void 0 && brandIds.length === 1 && brandIds[0] !== "all";
|
|
104044
|
+
const listBrands = async () => {
|
|
104045
|
+
if (brandIds === void 0 || isSingleLock) return [void 0];
|
|
104046
|
+
if (brandIds[0] === "all") return (await fetchAllBrands(subdomain, await getToken())).filter((b) => b.active && b.has_help_center).map((b) => b.subdomain);
|
|
104047
|
+
return brandIds;
|
|
104048
|
+
};
|
|
104049
|
+
const effectiveSubdomain = async (brand) => {
|
|
104050
|
+
if (brand !== void 0) return resolveBrandSubdomain(brand);
|
|
104051
|
+
if (isSingleLock) return resolveBrandSubdomain(brandIds[0]);
|
|
104052
|
+
return subdomain;
|
|
104053
|
+
};
|
|
104054
|
+
const scanBrand = async (brand, token) => {
|
|
104055
|
+
try {
|
|
104056
|
+
const hcSubdomain = await effectiveSubdomain(brand);
|
|
104057
|
+
const scan = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
|
|
104058
|
+
return {
|
|
104059
|
+
refs: scan.articles.map((a) => brand === void 0 ? {
|
|
104060
|
+
id: a.id,
|
|
104061
|
+
title: a.title
|
|
104062
|
+
} : {
|
|
104063
|
+
id: a.id,
|
|
104064
|
+
title: a.title,
|
|
104065
|
+
brand
|
|
104066
|
+
}),
|
|
104067
|
+
truncated: scan.truncated
|
|
104068
|
+
};
|
|
104069
|
+
} catch (err) {
|
|
104070
|
+
if (err instanceof ZendeskApiError && err.status === 401) throw err;
|
|
104071
|
+
return {
|
|
104072
|
+
refs: [],
|
|
104073
|
+
truncated: true
|
|
104074
|
+
};
|
|
104075
|
+
}
|
|
104076
|
+
};
|
|
103861
104077
|
return {
|
|
103862
104078
|
listPromoted() {
|
|
103863
104079
|
const now = Date.now();
|
|
103864
104080
|
if (cached && now - cached.at < 3e5) return cached.promise;
|
|
103865
104081
|
const promise = (async () => {
|
|
103866
104082
|
const token = await getToken();
|
|
103867
|
-
const
|
|
104083
|
+
const brands = await listBrands();
|
|
104084
|
+
const refs = [];
|
|
104085
|
+
let truncated = false;
|
|
104086
|
+
for (const brand of brands) {
|
|
104087
|
+
const scan = await scanBrand(brand, token);
|
|
104088
|
+
truncated = truncated || scan.truncated;
|
|
104089
|
+
refs.push(...scan.refs);
|
|
104090
|
+
}
|
|
103868
104091
|
return {
|
|
103869
|
-
refs
|
|
103870
|
-
id: a.id,
|
|
103871
|
-
title: a.title
|
|
103872
|
-
})),
|
|
104092
|
+
refs,
|
|
103873
104093
|
truncated
|
|
103874
104094
|
};
|
|
103875
104095
|
})().catch((err) => {
|
|
@@ -103883,10 +104103,10 @@ const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) =>
|
|
|
103883
104103
|
};
|
|
103884
104104
|
return promise;
|
|
103885
104105
|
},
|
|
103886
|
-
async readArticle(id) {
|
|
104106
|
+
async readArticle(id, brand) {
|
|
103887
104107
|
try {
|
|
103888
|
-
const token = await getToken();
|
|
103889
|
-
return await fetchArticleMarkdown(
|
|
104108
|
+
const [token, hcSubdomain] = await Promise.all([getToken(), effectiveSubdomain(brand)]);
|
|
104109
|
+
return await fetchArticleMarkdown(hcSubdomain, token, id, void 0);
|
|
103890
104110
|
} catch (err) {
|
|
103891
104111
|
notifyIfUnauthorized(err);
|
|
103892
104112
|
throw err;
|
|
@@ -103907,17 +104127,33 @@ const topologyResourceUri = (config) => `${config.hcResourceScheme}://topology`;
|
|
|
103907
104127
|
/**
|
|
103908
104128
|
* URI template of the pull-only Help Center article resources, built from the
|
|
103909
104129
|
* configured scheme exactly like `topologyResourceUri` (`--hc-resource-scheme`,
|
|
103910
|
-
* default `zendesk-hc`
|
|
103911
|
-
*
|
|
103912
|
-
*
|
|
104130
|
+
* default `zendesk-hc`). In multi/all brand mode the URI carries a brand
|
|
104131
|
+
* dimension — `<scheme>://brands/{brand}/articles/{id}` — because an article id
|
|
104132
|
+
* alone does not say which brand's Help Center holds it; the brand is named by
|
|
104133
|
+
* id or subdomain, the same idiom the tools' `brand_id` accepts. Unset and
|
|
104134
|
+
* single-brand modes keep `<scheme>://article/{id}`: one Help Center is
|
|
104135
|
+
* addressed, so a brand segment would be noise. The template's `list` callback
|
|
104136
|
+
* enumerates the promoted ("featured") articles (so clients can surface them
|
|
104137
|
+
* for pinning), while any article id can be read on demand.
|
|
103913
104138
|
*/
|
|
103914
|
-
const articleResourceUriTemplate = (config) => `${config.hcResourceScheme}://article/{id}`;
|
|
104139
|
+
const articleResourceUriTemplate = (config) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/{brand}/articles/{id}` : `${config.hcResourceScheme}://article/{id}`;
|
|
103915
104140
|
/**
|
|
103916
104141
|
* Build the concrete resource URI for a single article id, under the configured
|
|
103917
104142
|
* scheme. Shares the scheme with the template above so the listed URIs always
|
|
103918
|
-
* match the template the read callback is registered under.
|
|
104143
|
+
* match the template the read callback is registered under. In multi/all mode
|
|
104144
|
+
* the brand argument (id or subdomain) is required and fills the {brand} slot.
|
|
104145
|
+
*/
|
|
104146
|
+
const articleResourceUri = (config, id, brand) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/${brand}/articles/${id}` : `${config.hcResourceScheme}://article/${id}`;
|
|
104147
|
+
/**
|
|
104148
|
+
* Whether the article resources address one Help Center among several: the
|
|
104149
|
+
* server was started with several brands, or with `all`. Only then does the URI
|
|
104150
|
+
* carry the brand dimension; unset and single-lock modes name exactly one Help
|
|
104151
|
+
* Center implicitly.
|
|
103919
104152
|
*/
|
|
103920
|
-
const
|
|
104153
|
+
const brandScopedArticles = (config) => {
|
|
104154
|
+
const brandIds = config.brandIds;
|
|
104155
|
+
return brandIds !== void 0 && (brandIds.length > 1 || brandIds[0] === "all");
|
|
104156
|
+
};
|
|
103921
104157
|
/**
|
|
103922
104158
|
* Whether the `help_center` namespace is active: no `--namespace` filter, or one
|
|
103923
104159
|
* that includes it. The shared second half of the two Help Center feature gates
|
|
@@ -103943,13 +104179,11 @@ const helpCenterContextEnabled = (config) => config.topology && helpCenterNamesp
|
|
|
103943
104179
|
const articleResourceEnabled = (config) => helpCenterNamespaceActive(config);
|
|
103944
104180
|
/**
|
|
103945
104181
|
* 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`.
|
|
104182
|
+
* callback's scan, which enumerates the promoted articles for `resources/list`.
|
|
104183
|
+
* A client may trigger it unprompted, and it fans out (a capped scan of
|
|
104184
|
+
* `/articles`, per brand in scope), so `--no-promoted-articles` turns it off.
|
|
104185
|
+
* Read-by-id and the explicit `list_promoted_articles` tool stay available.
|
|
104186
|
+
* `!== false` so an omitted flag on a hand-built Config stays default-on.
|
|
103953
104187
|
*/
|
|
103954
104188
|
const promotedArticlesEnabled = (config) => config.promotedArticles !== false && helpCenterNamespaceActive(config);
|
|
103955
104189
|
/**
|
|
@@ -103960,13 +104194,20 @@ const promotedArticlesEnabled = (config) => config.promotedArticles !== false &&
|
|
|
103960
104194
|
*/
|
|
103961
104195
|
const buildInstructions = (config) => {
|
|
103962
104196
|
if (!helpCenterContextEnabled(config)) return void 0;
|
|
104197
|
+
let scope;
|
|
104198
|
+
if (config.brandIds === void 0) scope = `the Zendesk Help Center of "${config.subdomain}"`;
|
|
104199
|
+
else if (config.brandIds.length === 1 && config.brandIds[0] === "all") scope = `all brands of the Zendesk Help Center of "${config.subdomain}"`;
|
|
104200
|
+
else if (config.brandIds.length === 1) scope = `brand ${config.brandIds[0]} of the Zendesk Help Center of "${config.subdomain}"`;
|
|
104201
|
+
else scope = `brands ${config.brandIds.join(", ")} of the Zendesk Help Center of "${config.subdomain}"`;
|
|
104202
|
+
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
104203
|
return [
|
|
103964
|
-
`This MCP server is connected to
|
|
104204
|
+
`This MCP server is connected to ${scope}.`,
|
|
103965
104205
|
"",
|
|
103966
104206
|
`When creating or editing Help Center content, the resource ${topologyResourceUri(config)} is useful context:`,
|
|
103967
104207
|
"it lists the active locales (and the default one), the category → section tree with IDs,",
|
|
103968
104208
|
"the visibility user segments, the permission groups, and your current role.",
|
|
103969
104209
|
"Prefer its IDs (section_id, permission_group_id, user_segment_id, locale) over guessing from names.",
|
|
104210
|
+
...topologyBrandNote,
|
|
103970
104211
|
"",
|
|
103971
104212
|
"It degrades gracefully: without Guide-admin / Help Center manager rights the permission-groups and",
|
|
103972
104213
|
"user-segments sections are marked unavailable (not empty). In that case reuse a permission_group_id",
|
|
@@ -104003,12 +104244,13 @@ const tolerate403 = async (promise, fallback) => {
|
|
|
104003
104244
|
* and sections are each capped at one max-size page; `sectionsHasMore` /
|
|
104004
104245
|
* `categoriesHasMore` signal a Help Center too large to enumerate inline.
|
|
104005
104246
|
*/
|
|
104006
|
-
const fetchTopology = async (subdomain, token) => {
|
|
104247
|
+
const fetchTopology = async (subdomain, token, brandIds, brandSubdomain) => {
|
|
104007
104248
|
const pageParams = { "page[size]": String(100) };
|
|
104249
|
+
const hcSubdomain = brandSubdomain ?? subdomain;
|
|
104008
104250
|
const [locales, categoriesRes, sectionsRes, segments, perms, meRes] = await Promise.all([
|
|
104009
|
-
helpCenterGet(
|
|
104010
|
-
helpCenterGet(
|
|
104011
|
-
helpCenterGet(
|
|
104251
|
+
helpCenterGet(hcSubdomain, token, "/locales"),
|
|
104252
|
+
helpCenterGet(hcSubdomain, token, "/categories", pageParams),
|
|
104253
|
+
helpCenterGet(hcSubdomain, token, "/sections", pageParams),
|
|
104012
104254
|
tolerate403(helpCenterGet(subdomain, token, "/user_segments"), { user_segments: [] }),
|
|
104013
104255
|
tolerate403(zendeskGet(subdomain, token, "/guide/permission_groups"), { permission_groups: [] }),
|
|
104014
104256
|
zendeskGet(subdomain, token, "/users/me")
|
|
@@ -104017,6 +104259,7 @@ const fetchTopology = async (subdomain, token) => {
|
|
|
104017
104259
|
const sections = sectionsRes.sections ?? [];
|
|
104018
104260
|
return {
|
|
104019
104261
|
subdomain,
|
|
104262
|
+
...brandIds === void 0 ? {} : { brandIds },
|
|
104020
104263
|
locales,
|
|
104021
104264
|
categories,
|
|
104022
104265
|
sections,
|
|
@@ -104068,10 +104311,20 @@ const renderAdminSection = (items, denied, deniedNote) => {
|
|
|
104068
104311
|
};
|
|
104069
104312
|
/** Render the topology as a compact Markdown document for the LLM context. */
|
|
104070
104313
|
const formatTopology = (data) => {
|
|
104314
|
+
let brandNote;
|
|
104315
|
+
let allowedBrandsLine = [];
|
|
104316
|
+
if (data.brandIds === void 0) brandNote = "";
|
|
104317
|
+
else if (data.brandIds.length === 1 && data.brandIds[0] === "all") brandNote = " (all brands; tree below: account default)";
|
|
104318
|
+
else if (data.brandIds.length === 1) brandNote = ` (brand ${data.brandIds[0]})`;
|
|
104319
|
+
else {
|
|
104320
|
+
brandNote = ` (brand ${data.brandIds[0]})`;
|
|
104321
|
+
allowedBrandsLine = ["", `**Allowed brands**: ${data.brandIds.join(", ")}`];
|
|
104322
|
+
}
|
|
104071
104323
|
const text = [
|
|
104072
|
-
`# Zendesk Help Center topology — ${data.subdomain}`,
|
|
104324
|
+
`# Zendesk Help Center topology — ${data.subdomain}${brandNote}`,
|
|
104073
104325
|
"",
|
|
104074
104326
|
`**Your access**: ${data.currentUser.name} (id ${data.currentUser.id}), role "${data.currentUser.role}".`,
|
|
104327
|
+
...allowedBrandsLine,
|
|
104075
104328
|
"",
|
|
104076
104329
|
"## Locales",
|
|
104077
104330
|
`- Default: ${data.locales.default_locale}`,
|
|
@@ -104098,14 +104351,14 @@ const formatTopology = (data) => {
|
|
|
104098
104351
|
* token. A 401 notifies `onUnauthorized` (stdio OAuth only) to invalidate the
|
|
104099
104352
|
* stale token, mirroring the tool dispatch path in `server.ts`.
|
|
104100
104353
|
*/
|
|
104101
|
-
const createTopologyProvider = (getToken, subdomain, onUnauthorized) => {
|
|
104354
|
+
const createTopologyProvider = (getToken, subdomain, onUnauthorized, brandIds, resolveBrandSubdomain = () => Promise.resolve(void 0)) => {
|
|
104102
104355
|
let cached;
|
|
104103
104356
|
return { read() {
|
|
104104
104357
|
const now = Date.now();
|
|
104105
104358
|
if (cached && now - cached.at < 3e5) return cached.promise;
|
|
104106
104359
|
const promise = (async () => {
|
|
104107
|
-
const token = await getToken();
|
|
104108
|
-
return formatTopology(await fetchTopology(subdomain, token));
|
|
104360
|
+
const [token, brandSubdomain] = await Promise.all([getToken(), resolveBrandSubdomain()]);
|
|
104361
|
+
return formatTopology(await fetchTopology(subdomain, token, brandIds, brandSubdomain));
|
|
104109
104362
|
})().catch((err) => {
|
|
104110
104363
|
cached = void 0;
|
|
104111
104364
|
if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
|
|
@@ -104149,7 +104402,6 @@ const filterTools = (allTools, options) => allTools.filter((tool) => {
|
|
|
104149
104402
|
if (options.readOnly && !tool.readOnly) return false;
|
|
104150
104403
|
if (options.namespaces?.length && !options.namespaces.includes(tool.namespace)) return false;
|
|
104151
104404
|
if (options.tools?.length && !options.tools.includes(tool.name)) return false;
|
|
104152
|
-
if (options.promotedArticles === false && tool.name === "list_promoted_articles") return false;
|
|
104153
104405
|
return true;
|
|
104154
104406
|
});
|
|
104155
104407
|
const groupByNamespace = (tools) => {
|
|
@@ -104267,6 +104519,50 @@ const isPlacedAsRequested = (effectiveAfter, movedId, target, referenceId) => {
|
|
|
104267
104519
|
//#endregion
|
|
104268
104520
|
//#region src/tools/help-center.ts
|
|
104269
104521
|
const ARTICLE_ID_DESC = "Article ID — the numeric id of the Help Center article. Obtain it from list_articles or search_articles.";
|
|
104522
|
+
const brandIdField = (brandIds) => {
|
|
104523
|
+
if (!brandIds || brandIds.length === 0) return void 0;
|
|
104524
|
+
if (brandIds.length === 1 && brandIds[0] !== "all") return void 0;
|
|
104525
|
+
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(", ")}.`;
|
|
104526
|
+
return union([number$2().int(), string$2().min(1)]).describe(description);
|
|
104527
|
+
};
|
|
104528
|
+
/**
|
|
104529
|
+
* Resolve which brand a call targets, enforcing the deploy allow-list.
|
|
104530
|
+
*
|
|
104531
|
+
* - unset: no brand override; a per-call brand_id is rejected (schema has no
|
|
104532
|
+
* field for it, so this is a backstop for hand-crafted calls).
|
|
104533
|
+
* - single: the configured entry is the only brand; ANY per-call brand_id is
|
|
104534
|
+
* rejected (hard lock, no escape — the schema has no field for it).
|
|
104535
|
+
* - multi: brand_id is REQUIRED; the per-call id is resolved and must match an
|
|
104536
|
+
* allowed entry by id or resolved subdomain.
|
|
104537
|
+
* - all: brand_id is REQUIRED; any existing brand passes (validated by the
|
|
104538
|
+
* resolver).
|
|
104539
|
+
*
|
|
104540
|
+
* Returns the brand's subdomain, or undefined for the account default.
|
|
104541
|
+
*/
|
|
104542
|
+
const resolveBrand = async (perCall, brandIds, resolveSubdomain) => {
|
|
104543
|
+
if (!brandIds || brandIds.length === 0) {
|
|
104544
|
+
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.");
|
|
104545
|
+
return;
|
|
104546
|
+
}
|
|
104547
|
+
const isAll = brandIds.length === 1 && brandIds[0] === "all";
|
|
104548
|
+
if (brandIds.length === 1 && !isAll) {
|
|
104549
|
+
const locked = brandIds[0];
|
|
104550
|
+
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.`);
|
|
104551
|
+
return resolveSubdomain(locked);
|
|
104552
|
+
}
|
|
104553
|
+
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.`);
|
|
104554
|
+
const resolved = await resolveSubdomain(String(perCall));
|
|
104555
|
+
if (isAll) return resolved;
|
|
104556
|
+
await assertAllowed(perCall, resolved, brandIds, resolveSubdomain);
|
|
104557
|
+
return resolved;
|
|
104558
|
+
};
|
|
104559
|
+
const assertAllowed = async (perCall, resolved, brandIds, resolveSubdomain) => {
|
|
104560
|
+
if (brandIds.includes(String(perCall))) return;
|
|
104561
|
+
const settled = await Promise.allSettled(brandIds.map((entry) => resolveSubdomain(entry)));
|
|
104562
|
+
const allowed = new Set(brandIds);
|
|
104563
|
+
for (const r of settled) if (r.status === "fulfilled") allowed.add(r.value);
|
|
104564
|
+
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(", ")}.`);
|
|
104565
|
+
};
|
|
104270
104566
|
const listTranslations = (subdomain, token, articleId) => helpCenterGet(subdomain, token, `/articles/${articleId}/translations`).then((res) => res.translations);
|
|
104271
104567
|
const NODE_LABEL = {
|
|
104272
104568
|
sections: "section",
|
|
@@ -104467,13 +104763,54 @@ const structureWarning = (r) => {
|
|
|
104467
104763
|
return [];
|
|
104468
104764
|
};
|
|
104469
104765
|
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)];
|
|
104766
|
+
/**
|
|
104767
|
+
* Build the `list_brands` tool when the server runs in multi/all mode; an empty
|
|
104768
|
+
* array otherwise. It is the discovery source for the required per-call
|
|
104769
|
+
* `brand_id`: in single-lock or unset mode the brand is implicit, the schemas
|
|
104770
|
+
* carry no `brand_id` field, and the tool stays hidden. In multi mode (not
|
|
104771
|
+
* 'all') the listing is filtered to the allowed brands.
|
|
104772
|
+
*/
|
|
104773
|
+
const createListBrandsTool = (ctx) => {
|
|
104774
|
+
const { subdomain, brandIds, getToken } = ctx;
|
|
104775
|
+
if (brandIds === void 0) return [];
|
|
104776
|
+
const isAll = brandIds.length === 1 && brandIds[0] === "all";
|
|
104777
|
+
if (brandIds.length === 1 && !isAll) return [];
|
|
104778
|
+
return [{
|
|
104779
|
+
name: "list_brands",
|
|
104780
|
+
namespace: "help_center",
|
|
104781
|
+
readOnly: true,
|
|
104782
|
+
title: "List Zendesk Brands",
|
|
104783
|
+
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.",
|
|
104784
|
+
inputSchema: object({}),
|
|
104785
|
+
annotations: {
|
|
104786
|
+
readOnlyHint: true,
|
|
104787
|
+
destructiveHint: false,
|
|
104788
|
+
idempotentHint: true,
|
|
104789
|
+
openWorldHint: true
|
|
104790
|
+
},
|
|
104791
|
+
handler: async () => {
|
|
104792
|
+
const token = await getToken();
|
|
104793
|
+
const brands = await fetchAllBrands(subdomain, token);
|
|
104794
|
+
const visible = isAll ? brands : brands.filter((b) => brandIds.includes(String(b.id)) || brandIds.includes(b.subdomain));
|
|
104795
|
+
return { content: [{
|
|
104796
|
+
type: "text",
|
|
104797
|
+
text: formatList(visible, formatBrand, void 0, "Pass a brand id as brand_id to any Help Center tool to operate on that brand.")
|
|
104798
|
+
}] };
|
|
104799
|
+
}
|
|
104800
|
+
}];
|
|
104801
|
+
};
|
|
104470
104802
|
const createHelpCenterTools = (ctx) => {
|
|
104471
|
-
const { subdomain, getToken } = ctx;
|
|
104472
|
-
const
|
|
104803
|
+
const { subdomain, brandIds, resolveBrandSubdomain, getToken } = ctx;
|
|
104804
|
+
const brandIdSchema = brandIdField(brandIds);
|
|
104805
|
+
const brandIdPart = brandIdSchema ? { brand_id: brandIdSchema } : {};
|
|
104806
|
+
const resolveHcSubdomain = async (brand_id) => {
|
|
104807
|
+
return await resolveBrand(brand_id, brandIds, resolveBrandSubdomain) ?? subdomain;
|
|
104808
|
+
};
|
|
104809
|
+
const fetchSectionOrder = async (sectionId, locale, token, hcSubdomain) => {
|
|
104473
104810
|
const order = [];
|
|
104474
104811
|
let cursor;
|
|
104475
104812
|
do {
|
|
104476
|
-
const response = await helpCenterGet(
|
|
104813
|
+
const response = await helpCenterGet(hcSubdomain, token, `/${locale}/sections/${sectionId}/articles`, buildCursorParams(100, cursor));
|
|
104477
104814
|
const articles = response.articles ?? [];
|
|
104478
104815
|
for (const article of articles) order.push({
|
|
104479
104816
|
id: article.id,
|
|
@@ -104484,19 +104821,19 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104484
104821
|
} while (cursor);
|
|
104485
104822
|
return order;
|
|
104486
104823
|
};
|
|
104487
|
-
const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token) => {
|
|
104824
|
+
const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token, hcSubdomain) => {
|
|
104488
104825
|
if (effective.some((a) => a.id === referenceArticleId)) return;
|
|
104489
104826
|
let detail = "was not found";
|
|
104490
104827
|
try {
|
|
104491
|
-
const { article: ref } = await helpCenterGet(
|
|
104828
|
+
const { article: ref } = await helpCenterGet(hcSubdomain, token, `/articles/${referenceArticleId}`);
|
|
104492
104829
|
detail = `is in section #${ref.section_id}, not section #${sectionId}`;
|
|
104493
104830
|
} catch {}
|
|
104494
104831
|
throw new Error(`Reference article #${referenceArticleId} ${detail}. It must be in the same section (#${sectionId}) as article #${articleId}.`);
|
|
104495
104832
|
};
|
|
104496
|
-
const applyPositionWrites = async (writes, articleId, token) => {
|
|
104833
|
+
const applyPositionWrites = async (writes, articleId, token, hcSubdomain) => {
|
|
104497
104834
|
let applied = 0;
|
|
104498
104835
|
for (const write of writes) try {
|
|
104499
|
-
await helpCenterPut(
|
|
104836
|
+
await helpCenterPut(hcSubdomain, token, `/articles/${write.id}`, { article: { position: write.position } });
|
|
104500
104837
|
applied += 1;
|
|
104501
104838
|
} catch (error) {
|
|
104502
104839
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -104505,6 +104842,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104505
104842
|
return applied;
|
|
104506
104843
|
};
|
|
104507
104844
|
return [
|
|
104845
|
+
...createListBrandsTool(ctx),
|
|
104508
104846
|
{
|
|
104509
104847
|
name: "search_articles",
|
|
104510
104848
|
namespace: "help_center",
|
|
@@ -104515,7 +104853,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104515
104853
|
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
104854
|
locale: string$2().optional().describe("Filter by locale (e.g., \"en-us\", \"fr\")"),
|
|
104517
104855
|
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)
|
|
104856
|
+
page: number$2().int().min(1).default(1).describe(PAGE_DESC),
|
|
104857
|
+
...brandIdPart
|
|
104519
104858
|
}),
|
|
104520
104859
|
annotations: {
|
|
104521
104860
|
readOnlyHint: true,
|
|
@@ -104524,14 +104863,15 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104524
104863
|
openWorldHint: true
|
|
104525
104864
|
},
|
|
104526
104865
|
handler: async (params) => {
|
|
104527
|
-
const { query, locale, per_page, page } = params;
|
|
104866
|
+
const { query, locale, per_page, page, brand_id } = params;
|
|
104528
104867
|
const token = await getToken();
|
|
104868
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104529
104869
|
const p = {
|
|
104530
104870
|
query,
|
|
104531
104871
|
...buildOffsetParams(per_page, page)
|
|
104532
104872
|
};
|
|
104533
104873
|
if (locale) p["locale"] = locale;
|
|
104534
|
-
const response = await helpCenterGet(
|
|
104874
|
+
const response = await helpCenterGet(hcSubdomain, token, "/articles/search", p);
|
|
104535
104875
|
return { content: [{
|
|
104536
104876
|
type: "text",
|
|
104537
104877
|
text: formatList(response.results ?? [], formatArticleSummary, extractSearchPaginationMeta(response, per_page, page))
|
|
@@ -104546,7 +104886,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104546
104886
|
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
104887
|
inputSchema: object({
|
|
104548
104888
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
104549
|
-
locale: string$2().optional().describe("Locale for translated version")
|
|
104889
|
+
locale: string$2().optional().describe("Locale for translated version"),
|
|
104890
|
+
...brandIdPart
|
|
104550
104891
|
}),
|
|
104551
104892
|
annotations: {
|
|
104552
104893
|
readOnlyHint: true,
|
|
@@ -104555,11 +104896,12 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104555
104896
|
openWorldHint: true
|
|
104556
104897
|
},
|
|
104557
104898
|
handler: async (params) => {
|
|
104558
|
-
const { article_id, locale } = params;
|
|
104899
|
+
const { article_id, locale, brand_id } = params;
|
|
104559
104900
|
const token = await getToken();
|
|
104901
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104560
104902
|
const path = locale ? `/${locale}/articles/${article_id}` : `/articles/${article_id}`;
|
|
104561
|
-
const { article } = await helpCenterGet(
|
|
104562
|
-
const translations = await listTranslations(
|
|
104903
|
+
const { article } = await helpCenterGet(hcSubdomain, token, path, void 0);
|
|
104904
|
+
const translations = await listTranslations(hcSubdomain, token, article_id);
|
|
104563
104905
|
const text = (largeArticleHint(article.body, parseSections(article.body).length) ?? "") + formatArticle(article) + `\n\n**Available translations**: ${translations.map((t) => t.locale).join(", ")}`;
|
|
104564
104906
|
return { content: [{
|
|
104565
104907
|
type: "text",
|
|
@@ -104576,7 +104918,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104576
104918
|
inputSchema: object({
|
|
104577
104919
|
locale: string$2().optional().describe("Locale for category names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
|
|
104578
104920
|
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.")
|
|
104921
|
+
cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
|
|
104922
|
+
...brandIdPart
|
|
104580
104923
|
}),
|
|
104581
104924
|
annotations: {
|
|
104582
104925
|
readOnlyHint: true,
|
|
@@ -104585,10 +104928,11 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104585
104928
|
openWorldHint: true
|
|
104586
104929
|
},
|
|
104587
104930
|
handler: async (params) => {
|
|
104588
|
-
const { locale, page_size, cursor } = params;
|
|
104931
|
+
const { locale, page_size, cursor, brand_id } = params;
|
|
104589
104932
|
const token = await getToken();
|
|
104933
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104590
104934
|
const path = `${localePrefix(locale)}/categories`;
|
|
104591
|
-
const response = await helpCenterGet(
|
|
104935
|
+
const response = await helpCenterGet(hcSubdomain, token, path, buildCursorParams(page_size, cursor));
|
|
104592
104936
|
const categories = response.categories ?? [];
|
|
104593
104937
|
return { content: [{
|
|
104594
104938
|
type: "text",
|
|
@@ -104606,7 +104950,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104606
104950
|
category_id: number$2().int().optional().describe("Restrict to sections of this category (id from list_categories). Omit to list every section."),
|
|
104607
104951
|
locale: string$2().optional().describe("Locale for section names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
|
|
104608
104952
|
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.")
|
|
104953
|
+
cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
|
|
104954
|
+
...brandIdPart
|
|
104610
104955
|
}),
|
|
104611
104956
|
annotations: {
|
|
104612
104957
|
readOnlyHint: true,
|
|
@@ -104615,9 +104960,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104615
104960
|
openWorldHint: true
|
|
104616
104961
|
},
|
|
104617
104962
|
handler: async (params) => {
|
|
104618
|
-
const { category_id, locale, page_size, cursor } = params;
|
|
104963
|
+
const { category_id, locale, page_size, cursor, brand_id } = params;
|
|
104619
104964
|
const token = await getToken();
|
|
104620
|
-
const
|
|
104965
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104966
|
+
const response = await helpCenterGet(hcSubdomain, token, sectionListPath(category_id, locale), buildCursorParams(page_size, cursor));
|
|
104621
104967
|
const sections = response.sections ?? [];
|
|
104622
104968
|
return { content: [{
|
|
104623
104969
|
type: "text",
|
|
@@ -104643,7 +104989,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104643
104989
|
"title"
|
|
104644
104990
|
]).default("position").describe("Field to sort by; \"position\" (the default) is the manual order set in Guide."),
|
|
104645
104991
|
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)")
|
|
104992
|
+
include_translations: boolean$1().default(false).describe("Include available translation locales per article (causes 1 extra API call per article)"),
|
|
104993
|
+
...brandIdPart
|
|
104647
104994
|
}),
|
|
104648
104995
|
annotations: {
|
|
104649
104996
|
readOnlyHint: true,
|
|
@@ -104652,9 +104999,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104652
104999
|
openWorldHint: true
|
|
104653
105000
|
},
|
|
104654
105001
|
handler: async (params) => {
|
|
104655
|
-
const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations } = params;
|
|
105002
|
+
const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations, brand_id } = params;
|
|
104656
105003
|
const token = await getToken();
|
|
104657
|
-
const
|
|
105004
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105005
|
+
const response = await helpCenterGet(hcSubdomain, token, articleListPath(section_id, locale), {
|
|
104658
105006
|
...buildCursorParams(page_size, cursor),
|
|
104659
105007
|
sort_by,
|
|
104660
105008
|
sort_order
|
|
@@ -104665,7 +105013,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104665
105013
|
text: formatList(articles, formatArticleSummary, extractPaginationMeta(response, articles.length))
|
|
104666
105014
|
}] };
|
|
104667
105015
|
const formatted = await Promise.all(articles.map(async (article) => {
|
|
104668
|
-
const locales = (await listTranslations(
|
|
105016
|
+
const locales = (await listTranslations(hcSubdomain, token, article.id)).map((t) => t.locale).join(", ");
|
|
104669
105017
|
return `${formatArticleSummary(article)}\n- **Translations**: ${locales}`;
|
|
104670
105018
|
}));
|
|
104671
105019
|
const meta = extractPaginationMeta(response, articles.length);
|
|
@@ -104682,23 +105030,25 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104682
105030
|
readOnly: true,
|
|
104683
105031
|
title: "List Promoted Help Center Articles",
|
|
104684
105032
|
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({}),
|
|
105033
|
+
inputSchema: object({ ...brandIdPart }),
|
|
104686
105034
|
annotations: {
|
|
104687
105035
|
readOnlyHint: true,
|
|
104688
105036
|
destructiveHint: false,
|
|
104689
105037
|
idempotentHint: true,
|
|
104690
105038
|
openWorldHint: true
|
|
104691
105039
|
},
|
|
104692
|
-
handler: async () => {
|
|
105040
|
+
handler: async (params) => {
|
|
105041
|
+
const { brand_id } = params;
|
|
104693
105042
|
const token = await getToken();
|
|
104694
|
-
const
|
|
105043
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105044
|
+
const { articles, truncated, pagesScanned } = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
|
|
104695
105045
|
const header = `Promoted (featured) articles: ${articles.length}`;
|
|
104696
105046
|
const body = articles.length ? articles.map(formatArticleSummary).join("\n\n") : "_No promoted articles found._";
|
|
104697
105047
|
const cost = `${pagesScanned} Zendesk API request${pagesScanned === 1 ? "" : "s"}`;
|
|
104698
105048
|
const note = scanCostNote(truncated, pagesScanned, cost);
|
|
104699
105049
|
return { content: [{
|
|
104700
105050
|
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.")
|
|
105051
|
+
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
105052
|
}] };
|
|
104703
105053
|
}
|
|
104704
105054
|
},
|
|
@@ -104708,7 +105058,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104708
105058
|
readOnly: true,
|
|
104709
105059
|
title: "List Article Translations",
|
|
104710
105060
|
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({
|
|
105061
|
+
inputSchema: object({
|
|
105062
|
+
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105063
|
+
...brandIdPart
|
|
105064
|
+
}),
|
|
104712
105065
|
annotations: {
|
|
104713
105066
|
readOnlyHint: true,
|
|
104714
105067
|
destructiveHint: false,
|
|
@@ -104716,9 +105069,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104716
105069
|
openWorldHint: true
|
|
104717
105070
|
},
|
|
104718
105071
|
handler: async (params) => {
|
|
104719
|
-
const { article_id } = params;
|
|
105072
|
+
const { article_id, brand_id } = params;
|
|
104720
105073
|
const token = await getToken();
|
|
104721
|
-
const
|
|
105074
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105075
|
+
const translations = await listTranslations(hcSubdomain, token, article_id);
|
|
104722
105076
|
return { content: [{
|
|
104723
105077
|
type: "text",
|
|
104724
105078
|
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 +105090,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104736
105090
|
locale: string$2().describe("Target locale (e.g., \"fr\", \"de\")"),
|
|
104737
105091
|
title: string$2().min(1).describe("Translated article title."),
|
|
104738
105092
|
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).")
|
|
105093
|
+
draft: boolean$1().default(false).describe("Create the translation as a draft (not visible to end users). Defaults to false (published)."),
|
|
105094
|
+
...brandIdPart
|
|
104740
105095
|
}),
|
|
104741
105096
|
annotations: {
|
|
104742
105097
|
readOnlyHint: false,
|
|
@@ -104745,9 +105100,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104745
105100
|
openWorldHint: true
|
|
104746
105101
|
},
|
|
104747
105102
|
handler: async (params) => {
|
|
104748
|
-
const { article_id, locale, title, body, draft } = params;
|
|
105103
|
+
const { article_id, locale, title, body, draft, brand_id } = params;
|
|
104749
105104
|
const token = await getToken();
|
|
104750
|
-
const
|
|
105105
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105106
|
+
const { translation } = await helpCenterPost(hcSubdomain, token, `/articles/${article_id}/translations`, { translation: {
|
|
104751
105107
|
locale,
|
|
104752
105108
|
title,
|
|
104753
105109
|
body,
|
|
@@ -104770,7 +105126,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104770
105126
|
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
105127
|
title: string$2().optional().describe("New title for this locale. Omit to leave the current title unchanged."),
|
|
104772
105128
|
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.")
|
|
105129
|
+
draft: boolean$1().optional().describe("When true, keeps this translation as a draft; when false, publishes it."),
|
|
105130
|
+
...brandIdPart
|
|
104774
105131
|
}),
|
|
104775
105132
|
annotations: {
|
|
104776
105133
|
readOnlyHint: false,
|
|
@@ -104779,9 +105136,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104779
105136
|
openWorldHint: true
|
|
104780
105137
|
},
|
|
104781
105138
|
handler: async (params) => {
|
|
104782
|
-
const { article_id, locale, ...updates } = params;
|
|
105139
|
+
const { article_id, locale, brand_id, ...updates } = params;
|
|
104783
105140
|
const token = await getToken();
|
|
104784
|
-
const
|
|
105141
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105142
|
+
const { translation } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
|
|
104785
105143
|
return { content: [{
|
|
104786
105144
|
type: "text",
|
|
104787
105145
|
text: `Translation updated for article #${article_id} in "${locale}".\n\n${formatTranslation(translation)}`
|
|
@@ -104794,7 +105152,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104794
105152
|
readOnly: true,
|
|
104795
105153
|
title: "List Section Translations",
|
|
104796
105154
|
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({
|
|
105155
|
+
inputSchema: object({
|
|
105156
|
+
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."),
|
|
105157
|
+
...brandIdPart
|
|
105158
|
+
}),
|
|
104798
105159
|
annotations: {
|
|
104799
105160
|
readOnlyHint: true,
|
|
104800
105161
|
destructiveHint: false,
|
|
@@ -104802,9 +105163,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104802
105163
|
openWorldHint: true
|
|
104803
105164
|
},
|
|
104804
105165
|
handler: async (params) => {
|
|
104805
|
-
const { section_id } = params;
|
|
105166
|
+
const { section_id, brand_id } = params;
|
|
104806
105167
|
const token = await getToken();
|
|
104807
|
-
const
|
|
105168
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105169
|
+
const translations = await listNodeTranslations(hcSubdomain, token, "sections", section_id, void 0);
|
|
104808
105170
|
return { content: [{
|
|
104809
105171
|
type: "text",
|
|
104810
105172
|
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 +105179,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104817
105179
|
readOnly: true,
|
|
104818
105180
|
title: "List Category Translations",
|
|
104819
105181
|
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({
|
|
105182
|
+
inputSchema: object({
|
|
105183
|
+
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."),
|
|
105184
|
+
...brandIdPart
|
|
105185
|
+
}),
|
|
104821
105186
|
annotations: {
|
|
104822
105187
|
readOnlyHint: true,
|
|
104823
105188
|
destructiveHint: false,
|
|
@@ -104825,9 +105190,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104825
105190
|
openWorldHint: true
|
|
104826
105191
|
},
|
|
104827
105192
|
handler: async (params) => {
|
|
104828
|
-
const { category_id } = params;
|
|
105193
|
+
const { category_id, brand_id } = params;
|
|
104829
105194
|
const token = await getToken();
|
|
104830
|
-
const
|
|
105195
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105196
|
+
const translations = await listNodeTranslations(hcSubdomain, token, "categories", category_id, void 0);
|
|
104831
105197
|
return { content: [{
|
|
104832
105198
|
type: "text",
|
|
104833
105199
|
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 +105208,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104842
105208
|
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
105209
|
inputSchema: object({
|
|
104844
105210
|
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.")
|
|
105211
|
+
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."),
|
|
105212
|
+
...brandIdPart
|
|
104846
105213
|
}),
|
|
104847
105214
|
annotations: {
|
|
104848
105215
|
readOnlyHint: true,
|
|
@@ -104851,12 +105218,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104851
105218
|
openWorldHint: true
|
|
104852
105219
|
},
|
|
104853
105220
|
handler: async (params) => {
|
|
104854
|
-
const { locale, category_id } = params;
|
|
105221
|
+
const { locale, category_id, brand_id } = params;
|
|
104855
105222
|
const token = await getToken();
|
|
105223
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
104856
105224
|
const [locales, categoryScope, sectionsRes] = await Promise.all([
|
|
104857
|
-
helpCenterGet(
|
|
104858
|
-
fetchGapCategories(
|
|
104859
|
-
helpCenterGet(
|
|
105225
|
+
helpCenterGet(hcSubdomain, token, "/locales", void 0),
|
|
105226
|
+
fetchGapCategories(hcSubdomain, token, category_id),
|
|
105227
|
+
helpCenterGet(hcSubdomain, token, sectionListPath(category_id, void 0), {
|
|
104860
105228
|
...buildCursorParams(100, void 0),
|
|
104861
105229
|
include: TRANSLATIONS_SIDELOAD
|
|
104862
105230
|
})
|
|
@@ -104899,7 +105267,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104899
105267
|
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
105268
|
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
105269
|
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.")
|
|
105270
|
+
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."),
|
|
105271
|
+
...brandIdPart
|
|
104903
105272
|
}),
|
|
104904
105273
|
annotations: {
|
|
104905
105274
|
readOnlyHint: false,
|
|
@@ -104908,9 +105277,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104908
105277
|
openWorldHint: true
|
|
104909
105278
|
},
|
|
104910
105279
|
handler: async (params) => {
|
|
104911
|
-
const { section_id, ...input } = params;
|
|
105280
|
+
const { section_id, brand_id, ...input } = params;
|
|
104912
105281
|
const token = await getToken();
|
|
104913
|
-
const
|
|
105282
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105283
|
+
const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "sections", section_id, input);
|
|
104914
105284
|
return { content: [{
|
|
104915
105285
|
type: "text",
|
|
104916
105286
|
text: nodeTranslationWriteText("sections", section_id, translation, created)
|
|
@@ -104928,7 +105298,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104928
105298
|
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
105299
|
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
105300
|
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.")
|
|
105301
|
+
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."),
|
|
105302
|
+
...brandIdPart
|
|
104932
105303
|
}),
|
|
104933
105304
|
annotations: {
|
|
104934
105305
|
readOnlyHint: false,
|
|
@@ -104937,9 +105308,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104937
105308
|
openWorldHint: true
|
|
104938
105309
|
},
|
|
104939
105310
|
handler: async (params) => {
|
|
104940
|
-
const { category_id, ...input } = params;
|
|
105311
|
+
const { category_id, brand_id, ...input } = params;
|
|
104941
105312
|
const token = await getToken();
|
|
104942
|
-
const
|
|
105313
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105314
|
+
const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "categories", category_id, input);
|
|
104943
105315
|
return { content: [{
|
|
104944
105316
|
type: "text",
|
|
104945
105317
|
text: nodeTranslationWriteText("categories", category_id, translation, created)
|
|
@@ -104991,7 +105363,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
104991
105363
|
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
105364
|
draft: boolean$1().default(true).describe("When true (default), the article is created unpublished; set false to publish immediately."),
|
|
104993
105365
|
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)")
|
|
105366
|
+
label_names: array(string$2()).optional().describe("Label names for search ranking (use list_labels to see existing labels)"),
|
|
105367
|
+
...brandIdPart
|
|
104995
105368
|
}),
|
|
104996
105369
|
annotations: {
|
|
104997
105370
|
readOnlyHint: false,
|
|
@@ -105000,9 +105373,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105000
105373
|
openWorldHint: true
|
|
105001
105374
|
},
|
|
105002
105375
|
handler: async (params) => {
|
|
105003
|
-
const { section_id, ...articleData } = params;
|
|
105376
|
+
const { section_id, brand_id, ...articleData } = params;
|
|
105004
105377
|
const token = await getToken();
|
|
105005
|
-
const
|
|
105378
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105379
|
+
const { article } = await helpCenterPost(hcSubdomain, token, `/sections/${section_id}/articles`, { article: articleData });
|
|
105006
105380
|
return { content: [{
|
|
105007
105381
|
type: "text",
|
|
105008
105382
|
text: `Article #${article.id} created.\n\n${formatArticle(article)}`
|
|
@@ -105025,7 +105399,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105025
105399
|
author_id: number$2().int().optional().describe("User id of the article author (from search_users)."),
|
|
105026
105400
|
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
105401
|
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.")
|
|
105402
|
+
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."),
|
|
105403
|
+
...brandIdPart
|
|
105029
105404
|
}),
|
|
105030
105405
|
annotations: {
|
|
105031
105406
|
readOnlyHint: false,
|
|
@@ -105034,9 +105409,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105034
105409
|
openWorldHint: true
|
|
105035
105410
|
},
|
|
105036
105411
|
handler: async (params) => {
|
|
105037
|
-
const { article_id, ...updates } = params;
|
|
105412
|
+
const { article_id, brand_id, ...updates } = params;
|
|
105038
105413
|
const token = await getToken();
|
|
105039
|
-
const
|
|
105414
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105415
|
+
const { article } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}`, { article: updates });
|
|
105040
105416
|
return { content: [{
|
|
105041
105417
|
type: "text",
|
|
105042
105418
|
text: `Article #${article.id} updated.\n\n${formatArticle(article)}`
|
|
@@ -105059,7 +105435,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105059
105435
|
]).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
105436
|
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
105437
|
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.")
|
|
105438
|
+
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."),
|
|
105439
|
+
...brandIdPart
|
|
105063
105440
|
}),
|
|
105064
105441
|
annotations: {
|
|
105065
105442
|
readOnlyHint: false,
|
|
@@ -105068,15 +105445,16 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105068
105445
|
openWorldHint: true
|
|
105069
105446
|
},
|
|
105070
105447
|
handler: async (params) => {
|
|
105071
|
-
const { article_id, target, reference_article_id, normalize = false, confirm = false } = params;
|
|
105448
|
+
const { article_id, target, reference_article_id, normalize = false, confirm = false, brand_id } = params;
|
|
105072
105449
|
assertReorderParamsCoherent(article_id, target, reference_article_id);
|
|
105073
105450
|
const needsReference = needsReferenceArticle(target);
|
|
105074
105451
|
const token = await getToken();
|
|
105075
|
-
const
|
|
105452
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105453
|
+
const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
|
|
105076
105454
|
const sectionId = article.section_id;
|
|
105077
105455
|
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);
|
|
105456
|
+
const effective = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
|
|
105457
|
+
if (reference_article_id !== void 0) await assertReferenceInSection(effective, reference_article_id, article_id, sectionId, token, hcSubdomain);
|
|
105080
105458
|
const targetLabel = needsReference ? `${target} article #${reference_article_id}` : target;
|
|
105081
105459
|
const desired = arrangeDesiredOrder(effective, article_id, target, reference_article_id);
|
|
105082
105460
|
const writes = computePositionWrites(desired, article_id, normalize);
|
|
@@ -105092,8 +105470,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105092
105470
|
type: "text",
|
|
105093
105471
|
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
105472
|
}] };
|
|
105095
|
-
const applied = await applyPositionWrites(writes, article_id, token);
|
|
105096
|
-
const after = await fetchSectionOrder(sectionId, locale, token);
|
|
105473
|
+
const applied = await applyPositionWrites(writes, article_id, token, hcSubdomain);
|
|
105474
|
+
const after = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
|
|
105097
105475
|
if (!isPlacedAsRequested(after, article_id, target, reference_article_id)) return { content: [{
|
|
105098
105476
|
type: "text",
|
|
105099
105477
|
text: autoSortNotice(sectionId, applied)
|
|
@@ -105112,7 +105490,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105112
105490
|
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
105491
|
inputSchema: object({
|
|
105114
105492
|
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.")
|
|
105493
|
+
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."),
|
|
105494
|
+
...brandIdPart
|
|
105116
105495
|
}),
|
|
105117
105496
|
annotations: {
|
|
105118
105497
|
readOnlyHint: false,
|
|
@@ -105121,10 +105500,11 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105121
105500
|
openWorldHint: true
|
|
105122
105501
|
},
|
|
105123
105502
|
handler: async (params) => {
|
|
105124
|
-
const { article_id, confirm } = params;
|
|
105503
|
+
const { article_id, confirm, brand_id } = params;
|
|
105125
105504
|
if (confirm !== true) throw new Error("Archiving is guarded: pass confirm: true to archive (soft-delete) this article. No changes were made.");
|
|
105126
105505
|
const token = await getToken();
|
|
105127
|
-
await
|
|
105506
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105507
|
+
await helpCenterDelete(hcSubdomain, token, `/articles/${article_id}`);
|
|
105128
105508
|
return { content: [{
|
|
105129
105509
|
type: "text",
|
|
105130
105510
|
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 +105576,21 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105196
105576
|
readOnly: true,
|
|
105197
105577
|
title: "List Article Labels",
|
|
105198
105578
|
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({}),
|
|
105579
|
+
inputSchema: object({ ...brandIdPart }),
|
|
105200
105580
|
annotations: {
|
|
105201
105581
|
readOnlyHint: true,
|
|
105202
105582
|
destructiveHint: false,
|
|
105203
105583
|
idempotentHint: true,
|
|
105204
105584
|
openWorldHint: true
|
|
105205
105585
|
},
|
|
105206
|
-
handler: async () => {
|
|
105586
|
+
handler: async (params) => {
|
|
105587
|
+
const { brand_id } = params;
|
|
105207
105588
|
const token = await getToken();
|
|
105208
|
-
const
|
|
105589
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105590
|
+
const response = await helpCenterGet(hcSubdomain, token, "/articles/labels", void 0);
|
|
105209
105591
|
return { content: [{
|
|
105210
105592
|
type: "text",
|
|
105211
|
-
text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no parameters, so this listing cannot be narrowed from the call.")
|
|
105593
|
+
text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no filter parameters, so this listing cannot be narrowed from the call.")
|
|
105212
105594
|
}] };
|
|
105213
105595
|
}
|
|
105214
105596
|
},
|
|
@@ -105229,7 +105611,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105229
105611
|
const token = await getToken();
|
|
105230
105612
|
let response;
|
|
105231
105613
|
try {
|
|
105232
|
-
response = await helpCenterGet(subdomain, token, "/user_segments");
|
|
105614
|
+
response = await helpCenterGet(subdomain, token, "/user_segments", void 0);
|
|
105233
105615
|
} catch (error) {
|
|
105234
105616
|
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
105617
|
throw error;
|
|
@@ -105246,7 +105628,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105246
105628
|
readOnly: true,
|
|
105247
105629
|
title: "List Article Attachments",
|
|
105248
105630
|
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({
|
|
105631
|
+
inputSchema: object({
|
|
105632
|
+
article_id: number$2().int().describe("ID of the Help Center article whose attachments to list."),
|
|
105633
|
+
...brandIdPart
|
|
105634
|
+
}),
|
|
105250
105635
|
annotations: {
|
|
105251
105636
|
readOnlyHint: true,
|
|
105252
105637
|
destructiveHint: false,
|
|
@@ -105254,9 +105639,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105254
105639
|
openWorldHint: true
|
|
105255
105640
|
},
|
|
105256
105641
|
handler: async (params) => {
|
|
105257
|
-
const { article_id } = params;
|
|
105642
|
+
const { article_id, brand_id } = params;
|
|
105258
105643
|
const token = await getToken();
|
|
105259
|
-
const
|
|
105644
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105645
|
+
const attachments = (await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
|
|
105260
105646
|
if (attachments.length === 0) return { content: [{
|
|
105261
105647
|
type: "text",
|
|
105262
105648
|
text: `No attachments found on article #${article_id}.`
|
|
@@ -105275,7 +105661,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105275
105661
|
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
105662
|
inputSchema: object({
|
|
105277
105663
|
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)")
|
|
105664
|
+
locale: string$2().optional().describe("Locale of the body to outline (defaults to article source_locale)"),
|
|
105665
|
+
...brandIdPart
|
|
105279
105666
|
}),
|
|
105280
105667
|
annotations: {
|
|
105281
105668
|
readOnlyHint: true,
|
|
@@ -105284,12 +105671,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105284
105671
|
openWorldHint: true
|
|
105285
105672
|
},
|
|
105286
105673
|
handler: async (params) => {
|
|
105287
|
-
const { article_id, locale } = params;
|
|
105674
|
+
const { article_id, locale, brand_id } = params;
|
|
105288
105675
|
const token = await getToken();
|
|
105289
|
-
const
|
|
105676
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105677
|
+
const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
|
|
105290
105678
|
const effectiveLocale = locale ?? article.source_locale;
|
|
105291
|
-
const { translation } = await helpCenterGet(
|
|
105292
|
-
const translations = await listTranslations(
|
|
105679
|
+
const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${effectiveLocale}`, void 0);
|
|
105680
|
+
const translations = await listTranslations(hcSubdomain, token, article_id);
|
|
105293
105681
|
const sections = parseSections(translation.body);
|
|
105294
105682
|
const outlineLines = sections.length ? sections.map((s) => `- [${s.index}] ${s.headingTag ? `${s.headingTag}: ` : ""}${s.heading} (${s.wordCount} words)`).join("\n") : "_(no sections detected)_";
|
|
105295
105683
|
const translationsList = translations.map((t) => `- ${t.locale}${t.outdated ? " (outdated)" : ""}`).join("\n");
|
|
@@ -105318,7 +105706,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105318
105706
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105319
105707
|
locale: string$2().describe("Locale of the body (e.g., \"en-us\", \"fr\")"),
|
|
105320
105708
|
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.")
|
|
105709
|
+
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."),
|
|
105710
|
+
...brandIdPart
|
|
105322
105711
|
}),
|
|
105323
105712
|
annotations: {
|
|
105324
105713
|
readOnlyHint: true,
|
|
@@ -105327,9 +105716,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105327
105716
|
openWorldHint: true
|
|
105328
105717
|
},
|
|
105329
105718
|
handler: async (params) => {
|
|
105330
|
-
const { article_id, locale, section_index, format } = params;
|
|
105719
|
+
const { article_id, locale, section_index, format, brand_id } = params;
|
|
105331
105720
|
const token = await getToken();
|
|
105332
|
-
const
|
|
105721
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105722
|
+
const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
|
|
105333
105723
|
const sections = parseSections(translation.body);
|
|
105334
105724
|
const section = sections[section_index];
|
|
105335
105725
|
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 +105747,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105357
105747
|
locale: string$2().describe("Locale of the translation to update"),
|
|
105358
105748
|
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
105749
|
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.")
|
|
105750
|
+
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."),
|
|
105751
|
+
...brandIdPart
|
|
105361
105752
|
}),
|
|
105362
105753
|
annotations: {
|
|
105363
105754
|
readOnlyHint: false,
|
|
@@ -105366,12 +105757,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105366
105757
|
openWorldHint: true
|
|
105367
105758
|
},
|
|
105368
105759
|
handler: async (params) => {
|
|
105369
|
-
const { article_id, locale, section_index, content, format } = params;
|
|
105760
|
+
const { article_id, locale, section_index, content, format, brand_id } = params;
|
|
105370
105761
|
const token = await getToken();
|
|
105371
|
-
const
|
|
105762
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105763
|
+
const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
|
|
105372
105764
|
const newSectionHtml = format === "markdown" ? markdownToHtml(content) : content;
|
|
105373
105765
|
const { body: newBody, stripped, contentHeadings, introLost, sectionsBefore } = applySectionUpdate(translation.body, section_index, newSectionHtml);
|
|
105374
|
-
const { translation: updated } = await helpCenterPut(
|
|
105766
|
+
const { translation: updated } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: { body: newBody } });
|
|
105375
105767
|
const updatedSections = parseSections(updated.body);
|
|
105376
105768
|
const updatedSection = updatedSections[section_index];
|
|
105377
105769
|
const emptied = introLost === "empty";
|
|
@@ -105402,7 +105794,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105402
105794
|
inputSchema: object({
|
|
105403
105795
|
article_id: number$2().int().describe(ARTICLE_ID_DESC),
|
|
105404
105796
|
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")
|
|
105797
|
+
target_locale: string$2().describe("Target locale to compare against source"),
|
|
105798
|
+
...brandIdPart
|
|
105406
105799
|
}),
|
|
105407
105800
|
annotations: {
|
|
105408
105801
|
readOnlyHint: true,
|
|
@@ -105411,12 +105804,13 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105411
105804
|
openWorldHint: true
|
|
105412
105805
|
},
|
|
105413
105806
|
handler: async (params) => {
|
|
105414
|
-
const { article_id, source_locale, target_locale } = params;
|
|
105807
|
+
const { article_id, source_locale, target_locale, brand_id } = params;
|
|
105415
105808
|
const token = await getToken();
|
|
105809
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105416
105810
|
const [sourceRes, targetRes, translations] = await Promise.all([
|
|
105417
|
-
helpCenterGet(
|
|
105418
|
-
helpCenterGet(
|
|
105419
|
-
listTranslations(
|
|
105811
|
+
helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${source_locale}`, void 0),
|
|
105812
|
+
helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${target_locale}`, void 0),
|
|
105813
|
+
listTranslations(hcSubdomain, token, article_id)
|
|
105420
105814
|
]);
|
|
105421
105815
|
const sourceSections = parseSections(sourceRes.translation.body);
|
|
105422
105816
|
const targetSections = parseSections(targetRes.translation.body);
|
|
@@ -105455,7 +105849,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105455
105849
|
abort: true,
|
|
105456
105850
|
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
105851
|
}).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.")
|
|
105852
|
+
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."),
|
|
105853
|
+
...brandIdPart
|
|
105459
105854
|
}),
|
|
105460
105855
|
annotations: {
|
|
105461
105856
|
readOnlyHint: false,
|
|
@@ -105464,13 +105859,14 @@ const createHelpCenterTools = (ctx) => {
|
|
|
105464
105859
|
openWorldHint: true
|
|
105465
105860
|
},
|
|
105466
105861
|
handler: async (params) => {
|
|
105467
|
-
const { article_id, file_name, file_base64, content_type } = params;
|
|
105862
|
+
const { article_id, file_name, file_base64, content_type, brand_id } = params;
|
|
105468
105863
|
const token = await getToken();
|
|
105864
|
+
const hcSubdomain = await resolveHcSubdomain(brand_id);
|
|
105469
105865
|
const buffer = Buffer.from(file_base64, "base64");
|
|
105470
105866
|
const blob = new Blob([buffer], { type: content_type });
|
|
105471
105867
|
const formData = new FormData();
|
|
105472
105868
|
formData.append("file", blob, file_name);
|
|
105473
|
-
const { article_attachment } = await helpCenterUpload(
|
|
105869
|
+
const { article_attachment } = await helpCenterUpload(hcSubdomain, token, `/articles/${article_id}/attachments`, formData);
|
|
105474
105870
|
return { content: [{
|
|
105475
105871
|
type: "text",
|
|
105476
105872
|
text: `Attachment created for article #${article_id}.\n\n${formatAttachment(article_attachment)}`
|
|
@@ -107445,6 +107841,31 @@ const createServerShell = (config, logger = silentLogger) => {
|
|
|
107445
107841
|
return server;
|
|
107446
107842
|
};
|
|
107447
107843
|
/**
|
|
107844
|
+
* Lazily resolve the Help Center subdomain the topology resource pins to: the
|
|
107845
|
+
* FIRST allowed brand when a list is set (named in the header), else undefined
|
|
107846
|
+
* (the account default). Uses the SHARED resolver so the topology and article
|
|
107847
|
+
* resources hit the same cached brand list the tools use; it only fires when a
|
|
107848
|
+
* brand list is set. Providers await it per read (cheap after the first), since
|
|
107849
|
+
* registerToolset is synchronous and cannot resolve up front.
|
|
107850
|
+
*/
|
|
107851
|
+
const firstBrandSubdomainResolver = (config, resolveBrandSubdomain) => {
|
|
107852
|
+
const first = config.brandIds?.[0];
|
|
107853
|
+
if (first === void 0 || first === "all") return () => Promise.resolve(void 0);
|
|
107854
|
+
return () => resolveBrandSubdomain(first);
|
|
107855
|
+
};
|
|
107856
|
+
/**
|
|
107857
|
+
* Per-call brand guard for the article resource URIs in multi/all mode: the
|
|
107858
|
+
* {brand} slot of `<scheme>://brands/{brand}/articles/{id}` is resolved and
|
|
107859
|
+
* allow-list-checked EXACTLY like a tool call's `brand_id` (shared resolver,
|
|
107860
|
+
* same resolveBrand rules), returning the brand's subdomain for the Help
|
|
107861
|
+
* Center fetch. Unset/single modes never reach here — their URIs carry no
|
|
107862
|
+
* brand segment.
|
|
107863
|
+
*/
|
|
107864
|
+
const resourceBrandResolver = (config, resolveBrandSubdomain) => {
|
|
107865
|
+
const singleLock = config.brandIds?.length === 1 && config.brandIds[0] !== "all";
|
|
107866
|
+
return (idOrSubdomain) => singleLock ? resolveBrandSubdomain(idOrSubdomain) : resolveBrand(idOrSubdomain, config.brandIds, resolveBrandSubdomain).then((sub) => sub ?? config.subdomain);
|
|
107867
|
+
};
|
|
107868
|
+
/**
|
|
107448
107869
|
* Registers one generation of the toolset (mode/filters applied) plus the
|
|
107449
107870
|
* optional topology resource onto an existing server, and returns a handle
|
|
107450
107871
|
* whose `dispose()` removes exactly what this call added. When the SDK server
|
|
@@ -107454,7 +107875,8 @@ const createServerShell = (config, logger = silentLogger) => {
|
|
|
107454
107875
|
* without dropping the transport. `tools` is passed in (not built here) so the
|
|
107455
107876
|
* reload path can hand over freshly re-imported definitions.
|
|
107456
107877
|
*/
|
|
107457
|
-
const registerToolset = (server, { config, getToken, onUnauthorized, logger = silentLogger }, tools) => {
|
|
107878
|
+
const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUnauthorized, logger = silentLogger }, tools) => {
|
|
107879
|
+
const sharedResolver = resolveBrandSubdomain ?? createBrandSubdomainResolver(config.subdomain, getToken);
|
|
107458
107880
|
const registered = [];
|
|
107459
107881
|
const dispose = () => {
|
|
107460
107882
|
for (const handle of registered) handle.remove();
|
|
@@ -107462,8 +107884,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107462
107884
|
const filteredTools = filterTools(tools, {
|
|
107463
107885
|
readOnly: config.readOnly,
|
|
107464
107886
|
namespaces: config.namespaces,
|
|
107465
|
-
tools: config.tools
|
|
107466
|
-
promotedArticles: config.promotedArticles
|
|
107887
|
+
tools: config.tools
|
|
107467
107888
|
});
|
|
107468
107889
|
try {
|
|
107469
107890
|
switch (config.mode) {
|
|
@@ -107492,7 +107913,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107492
107913
|
}
|
|
107493
107914
|
}
|
|
107494
107915
|
if (helpCenterContextEnabled(config)) {
|
|
107495
|
-
const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized);
|
|
107916
|
+
const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized, config.brandIds, firstBrandSubdomainResolver(config, sharedResolver));
|
|
107496
107917
|
registered.push(server.registerResource("help-center-topology", topologyResourceUri(config), {
|
|
107497
107918
|
title: "Zendesk Help Center topology",
|
|
107498
107919
|
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 +107925,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107504
107925
|
}] })));
|
|
107505
107926
|
}
|
|
107506
107927
|
if (articleResourceEnabled(config)) {
|
|
107507
|
-
const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized);
|
|
107928
|
+
const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized, resourceBrandResolver(config, sharedResolver), config.brandIds);
|
|
107508
107929
|
const listPromotedEnabled = promotedArticlesEnabled(config);
|
|
107509
107930
|
const template = new ResourceTemplate(articleResourceUriTemplate(config), { list: async () => {
|
|
107510
107931
|
if (!listPromotedEnabled) return { resources: [] };
|
|
@@ -107515,10 +107936,10 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107515
107936
|
listed: refs.length
|
|
107516
107937
|
});
|
|
107517
107938
|
return { resources: refs.map((ref) => ({
|
|
107518
|
-
uri: articleResourceUri(config, ref.id),
|
|
107519
|
-
name: ref.title
|
|
107939
|
+
uri: articleResourceUri(config, ref.id, ref.brand),
|
|
107940
|
+
name: ref.brand === void 0 ? ref.title : `${ref.title} (${ref.brand})`,
|
|
107520
107941
|
title: ref.title,
|
|
107521
|
-
description: `"${ref.title}" (article ${ref.id}) — promoted Help Center article, as Markdown.`,
|
|
107942
|
+
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
107943
|
mimeType: "text/markdown"
|
|
107523
107944
|
})) };
|
|
107524
107945
|
} catch (err) {
|
|
@@ -107528,16 +107949,18 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107528
107949
|
} });
|
|
107529
107950
|
registered.push(server.registerResource("help-center-article", template, {
|
|
107530
107951
|
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.",
|
|
107952
|
+
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
107953
|
mimeType: "text/markdown"
|
|
107533
107954
|
}, async (uri, variables) => {
|
|
107534
|
-
const
|
|
107535
|
-
const id = Number(
|
|
107955
|
+
const rawId = Array.isArray(variables["id"]) ? variables["id"][0] : variables["id"];
|
|
107956
|
+
const id = Number(rawId);
|
|
107536
107957
|
if (!Number.isSafeInteger(id) || id <= 0) throw new Error(`Invalid article id in resource URI: ${uri.toString()}`);
|
|
107958
|
+
const rawBrand = variables["brand"];
|
|
107959
|
+
const brand = Array.isArray(rawBrand) ? rawBrand[0] : rawBrand;
|
|
107537
107960
|
return { contents: [{
|
|
107538
107961
|
uri: uri.toString(),
|
|
107539
107962
|
mimeType: "text/markdown",
|
|
107540
|
-
text: await articles.readArticle(id)
|
|
107963
|
+
text: await articles.readArticle(id, brand)
|
|
107541
107964
|
}] };
|
|
107542
107965
|
}));
|
|
107543
107966
|
}
|
|
@@ -107556,13 +107979,17 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
|
|
|
107556
107979
|
};
|
|
107557
107980
|
const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized) => {
|
|
107558
107981
|
const server = createServerShell(config, logger);
|
|
107982
|
+
const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
|
|
107559
107983
|
const tools = createAllTools({
|
|
107560
107984
|
subdomain: config.subdomain,
|
|
107985
|
+
brandIds: config.brandIds,
|
|
107986
|
+
resolveBrandSubdomain,
|
|
107561
107987
|
getToken
|
|
107562
107988
|
});
|
|
107563
107989
|
registerToolset(server, {
|
|
107564
107990
|
config,
|
|
107565
107991
|
getToken,
|
|
107992
|
+
resolveBrandSubdomain,
|
|
107566
107993
|
onUnauthorized,
|
|
107567
107994
|
logger
|
|
107568
107995
|
}, tools);
|
|
@@ -107752,13 +108179,17 @@ const loadFreshTools = async (ctx) => {
|
|
|
107752
108179
|
*/
|
|
107753
108180
|
const createReloadableServer = (config, getToken, logger = silentLogger, onUnauthorized, loadTools = loadFreshTools) => {
|
|
107754
108181
|
const server = createServerShell(config, logger);
|
|
108182
|
+
const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
|
|
107755
108183
|
const ctx = {
|
|
107756
108184
|
subdomain: config.subdomain,
|
|
108185
|
+
brandIds: config.brandIds,
|
|
108186
|
+
resolveBrandSubdomain,
|
|
107757
108187
|
getToken
|
|
107758
108188
|
};
|
|
107759
108189
|
const params = {
|
|
107760
108190
|
config,
|
|
107761
108191
|
getToken,
|
|
108192
|
+
resolveBrandSubdomain,
|
|
107762
108193
|
onUnauthorized,
|
|
107763
108194
|
logger
|
|
107764
108195
|
};
|
|
@@ -107897,8 +108328,7 @@ const renderToolSurface = (config, tools) => {
|
|
|
107897
108328
|
const filtered = filterTools(tools, {
|
|
107898
108329
|
readOnly: config.readOnly,
|
|
107899
108330
|
namespaces: config.namespaces,
|
|
107900
|
-
tools: config.tools
|
|
107901
|
-
promotedArticles: config.promotedArticles
|
|
108331
|
+
tools: config.tools
|
|
107902
108332
|
});
|
|
107903
108333
|
const header = renderHeader(config);
|
|
107904
108334
|
if (filtered.length === 0) return `${header}\n\nNo tools exposed. Check --namespace / --tool / --read-only.`;
|
|
@@ -107994,7 +108424,7 @@ const installShutdown = (options) => {
|
|
|
107994
108424
|
//#endregion
|
|
107995
108425
|
//#region src/index.ts
|
|
107996
108426
|
const buildStdioTokenStore = async (config, logger) => {
|
|
107997
|
-
const { createTokenStore } = await import("./token-store-
|
|
108427
|
+
const { createTokenStore } = await import("./token-store-Aq7gP39o.js");
|
|
107998
108428
|
return createTokenStore({
|
|
107999
108429
|
subdomain: config.subdomain,
|
|
108000
108430
|
oauthClientId: config.oauthClientId,
|
|
@@ -108013,6 +108443,8 @@ const main = async () => {
|
|
|
108013
108443
|
if (config.printTools) {
|
|
108014
108444
|
const tools = createAllTools({
|
|
108015
108445
|
subdomain: config.subdomain,
|
|
108446
|
+
brandIds: config.brandIds,
|
|
108447
|
+
resolveBrandSubdomain: () => Promise.resolve(""),
|
|
108016
108448
|
getToken: () => ""
|
|
108017
108449
|
});
|
|
108018
108450
|
console.log(renderToolSurface(config, tools));
|
|
@@ -108033,7 +108465,7 @@ const main = async () => {
|
|
|
108033
108465
|
return;
|
|
108034
108466
|
}
|
|
108035
108467
|
if (config.dev) logger.warn("dev_mode_ignored_http");
|
|
108036
|
-
const { startHttpTransport } = await import("./http-
|
|
108468
|
+
const { startHttpTransport } = await import("./http-GIKJdf05.js");
|
|
108037
108469
|
const http = await startHttpTransport(config, logger);
|
|
108038
108470
|
installShutdown({
|
|
108039
108471
|
watchStdin: false,
|
|
@@ -108047,4 +108479,4 @@ main().catch((error) => {
|
|
|
108047
108479
|
process.exit(1);
|
|
108048
108480
|
});
|
|
108049
108481
|
//#endregion
|
|
108050
|
-
export { __toESM$1 as _,
|
|
108482
|
+
export { __toESM$1 as _, zendeskGet as a, DEFAULT_CALLBACK_PORT as c, createStartupError as d, errnoCode as f, __require as g, __exportAll$1 as h, WebStandardStreamableHTTPServerTransport as i, getOAuthUrls as l, __commonJSMin$1 as m, readPackageInfo as n, REQUEST_TIMEOUT_MS as o, readEnv as p, silentLogger as r, deadlineSignal as s, createMcpServer as t, STARTUP_DOCS as u };
|