antitone 1.1.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/SKILL.md +114 -0
  4. package/dist/cli.d.ts +4 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +371 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/client.d.ts +108 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +412 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/config.d.ts +42 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +117 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/errors.d.ts +53 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +224 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/index.d.ts +17 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +36 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/output.d.ts +74 -0
  25. package/dist/output.d.ts.map +1 -0
  26. package/dist/output.js +283 -0
  27. package/dist/output.js.map +1 -0
  28. package/dist/tools/astock.d.ts +5 -0
  29. package/dist/tools/astock.d.ts.map +1 -0
  30. package/dist/tools/astock.js +30 -0
  31. package/dist/tools/astock.js.map +1 -0
  32. package/dist/tools/backtest.d.ts +5 -0
  33. package/dist/tools/backtest.d.ts.map +1 -0
  34. package/dist/tools/backtest.js +36 -0
  35. package/dist/tools/backtest.js.map +1 -0
  36. package/dist/tools/briefing.d.ts +5 -0
  37. package/dist/tools/briefing.d.ts.map +1 -0
  38. package/dist/tools/briefing.js +28 -0
  39. package/dist/tools/briefing.js.map +1 -0
  40. package/dist/tools/calendar.d.ts +5 -0
  41. package/dist/tools/calendar.d.ts.map +1 -0
  42. package/dist/tools/calendar.js +35 -0
  43. package/dist/tools/calendar.js.map +1 -0
  44. package/dist/tools/entity.d.ts +5 -0
  45. package/dist/tools/entity.d.ts.map +1 -0
  46. package/dist/tools/entity.js +41 -0
  47. package/dist/tools/entity.js.map +1 -0
  48. package/dist/tools/index.d.ts +28 -0
  49. package/dist/tools/index.d.ts.map +1 -0
  50. package/dist/tools/index.js +58 -0
  51. package/dist/tools/index.js.map +1 -0
  52. package/dist/tools/kit.d.ts +82 -0
  53. package/dist/tools/kit.d.ts.map +1 -0
  54. package/dist/tools/kit.js +146 -0
  55. package/dist/tools/kit.js.map +1 -0
  56. package/dist/tools/market.d.ts +5 -0
  57. package/dist/tools/market.d.ts.map +1 -0
  58. package/dist/tools/market.js +194 -0
  59. package/dist/tools/market.js.map +1 -0
  60. package/dist/tools/news.d.ts +5 -0
  61. package/dist/tools/news.d.ts.map +1 -0
  62. package/dist/tools/news.js +101 -0
  63. package/dist/tools/news.js.map +1 -0
  64. package/dist/tools/usage.d.ts +13 -0
  65. package/dist/tools/usage.d.ts.map +1 -0
  66. package/dist/tools/usage.js +50 -0
  67. package/dist/tools/usage.js.map +1 -0
  68. package/package.json +66 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,aAAa,GACb,sBAAsB,GACtB,eAAe,GACf,cAAc,GACd,aAAa,GACb,WAAW,GACX,SAAS,GACT,SAAS,GACT,cAAc,GACd,cAAc,GACd,WAAW,GACX,SAAS,CAAC;AAEd,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO5D;AAUD,sCAAsC;AACtC,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,SAAS,CAS5G;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMjE;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;gBAEf,UAAU,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,OAAO;IAO1D,IAAI,IAAI,IAAI,SAAS,CAEpB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,GAAG,EAAE,oBAAoB,GAAG,aAAa,CA8IrE;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,kBAAuB,GAAG,aAAa,CAsC3F;AAED,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,kBAAuB,GAAG,aAAa,CAE9F"}
package/dist/errors.js ADDED
@@ -0,0 +1,224 @@
1
+ /**
2
+ * 错误规范化:把 HTTP 状态码 / 网络异常翻译成 agent 可读的中文语义,
3
+ * 并保留 402 / 403 / 429 的结构化字段(need_credits / required_scope / Retry-After)。
4
+ *
5
+ * 依据:docs/api-platform-master-plan.md §7.4 错误语义、§7.5 计费回执。
6
+ */
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ export function toInteger(value) {
11
+ if (typeof value === "number" && Number.isFinite(value))
12
+ return Math.trunc(value);
13
+ if (typeof value === "string" && value.trim() !== "") {
14
+ const n = Number(value);
15
+ if (Number.isFinite(n))
16
+ return Math.trunc(n);
17
+ }
18
+ return undefined;
19
+ }
20
+ function firstString(source, keys) {
21
+ for (const key of keys) {
22
+ const value = source[key];
23
+ if (typeof value === "string" && value.trim() !== "")
24
+ return value.trim();
25
+ }
26
+ return undefined;
27
+ }
28
+ /** 解析 Retry-After:支持秒数或 HTTP-date。 */
29
+ export function parseRetryAfter(raw, now = Date.now()) {
30
+ if (!raw)
31
+ return undefined;
32
+ const trimmed = raw.trim();
33
+ if (trimmed === "")
34
+ return undefined;
35
+ const seconds = Number(trimmed);
36
+ if (Number.isFinite(seconds) && seconds >= 0)
37
+ return Math.round(seconds * 1000);
38
+ const date = Date.parse(trimmed);
39
+ if (Number.isFinite(date))
40
+ return Math.max(0, date - now);
41
+ return undefined;
42
+ }
43
+ export function humanizeRetryAfter(ms) {
44
+ if (ms === undefined)
45
+ return "稍后";
46
+ const s = Math.ceil(ms / 1000);
47
+ if (s < 60)
48
+ return `${s} 秒后`;
49
+ const m = Math.ceil(s / 60);
50
+ return `${m} 分钟后`;
51
+ }
52
+ export class AntitoneError extends Error {
53
+ normalized;
54
+ billing;
55
+ constructor(normalized, billing) {
56
+ super(normalized.message);
57
+ this.name = "AntitoneError";
58
+ this.normalized = normalized;
59
+ if (billing)
60
+ this.billing = billing;
61
+ }
62
+ get kind() {
63
+ return this.normalized.kind;
64
+ }
65
+ get retryable() {
66
+ return this.normalized.retryable;
67
+ }
68
+ }
69
+ /** 把一次失败的 HTTP 响应规范化。体里若带 error/limit/required_scope 等字段则直通。 */
70
+ export function fromResponse(ctx) {
71
+ const { status } = ctx;
72
+ const body = isRecord(ctx.body) ? ctx.body : {};
73
+ const code = firstString(body, ["error", "code", "detail_code"]);
74
+ const detailMessage = firstString(body, ["message", "detail", "msg"]);
75
+ const balance = toInteger(body["balance"]);
76
+ const needCredits = toInteger(body["need_credits"]);
77
+ const topupUrl = firstString(body, ["topup_url", "topup_hint"]);
78
+ const requiredScope = firstString(body, ["required_scope", "scope"]);
79
+ const retryAfterMs = parseRetryAfter(ctx.retryAfterHeader);
80
+ if (status === 401) {
81
+ const paused = code === "key_paused" || typeof body["until"] === "string";
82
+ const reason = firstString(body, ["reason"]);
83
+ const until = firstString(body, ["until"]);
84
+ return new AntitoneError({
85
+ kind: "invalid_key",
86
+ status,
87
+ message: paused
88
+ ? `API Key 已被暂停${reason ? `(原因:${reason})` : ""}${until ? `,恢复时间 ${until}` : ""}。`
89
+ : "API Key 无效或已吊销",
90
+ hint: paused
91
+ ? "登录个人中心 → API Keys 查看该 Key 状态;被暂停的 Key 可手动恢复或改用其他 Key。"
92
+ : "检查 ANTITONE_API_KEY 是否为完整值 nhk_xxx.yyy(含 . 后的 secret,创建时只显示一次),以及 Key 是否已被吊销。",
93
+ retryable: false,
94
+ ...(balance !== undefined ? { balance } : {}),
95
+ ...(detailMessage ? { detail: detailMessage } : {}),
96
+ }, ctx.billing);
97
+ }
98
+ if (status === 402) {
99
+ const need = needCredits ?? 0;
100
+ const have = balance ?? 0;
101
+ return new AntitoneError({
102
+ kind: "insufficient_credits",
103
+ status,
104
+ message: `余额不足:本次调用至少需要 ${need} 积分,当前余额 ${have} 积分。`,
105
+ hint: "到个人中心充值(1 积分 = ¥0.005);每日有 100 积分免费额度,北京时间 0 点重置。可先用免费端点 get_usage 查看余额与今日消耗。",
106
+ retryable: false,
107
+ ...(needCredits !== undefined ? { need_credits: needCredits } : {}),
108
+ ...(balance !== undefined ? { balance } : {}),
109
+ ...(topupUrl ? { topup_url: topupUrl } : {}),
110
+ }, ctx.billing);
111
+ }
112
+ if (status === 403) {
113
+ return new AntitoneError({
114
+ kind: "missing_scope",
115
+ status,
116
+ message: `缺少 scope / 权益:${requiredScope ?? code ?? "服务端未说明"}`,
117
+ hint: requiredScope
118
+ ? `在个人中心编辑该 API Key,勾选 ${requiredScope} 作用域后重试(修改即时生效)。`
119
+ : "在个人中心编辑该 API Key 的作用域;若确认已勾选,请检查 Key 是否被降权。",
120
+ retryable: false,
121
+ ...(requiredScope ? { required_scope: requiredScope } : {}),
122
+ ...(topupUrl ? { topup_url: topupUrl } : {}),
123
+ }, ctx.billing);
124
+ }
125
+ if (status === 429) {
126
+ const limit = firstString(body, ["limit"]);
127
+ const resetAt = firstString(body, ["reset_at"]);
128
+ const wait = retryAfterMs !== undefined ? humanizeRetryAfter(retryAfterMs) : "稍后";
129
+ return new AntitoneError({
130
+ kind: "rate_limited",
131
+ status,
132
+ message: `速率或配额超限${limit ? `(${limit === "daily" ? "日配额" : "每分钟速率"})` : ""},请 ${wait}重试。`,
133
+ hint: [
134
+ limit === "daily"
135
+ ? "日配额按北京时间 0 点重置;也可用更少的调用次数完成同样的事(合并批量参数)。"
136
+ : "降低并发/频率,或把多次小请求合并成一次批量请求(如 get_quotes 一次传多只)。",
137
+ resetAt ? `服务端建议的重置时间:${resetAt}` : "",
138
+ ]
139
+ .filter(Boolean)
140
+ .join(" "),
141
+ retryable: true,
142
+ ...(retryAfterMs !== undefined ? { retry_after_ms: retryAfterMs } : {}),
143
+ ...(retryAfterMs !== undefined ? { retry_after_s: Math.ceil(retryAfterMs / 1000) } : {}),
144
+ }, ctx.billing);
145
+ }
146
+ if (status === 404) {
147
+ return new AntitoneError({
148
+ kind: "not_found",
149
+ status,
150
+ message: `资源不存在(HTTP 404)${detailMessage ? `:${detailMessage}` : ",请检查 ID / 代码是否拼写正确"}`,
151
+ hint: "新闻用 search_news 拿到 public_id;题材/板块代码先调 list_themes / list_sectors 取;股票代码用 6 位数字(如 600519)。",
152
+ retryable: false,
153
+ }, ctx.billing);
154
+ }
155
+ if (status >= 400 && status < 500) {
156
+ return new AntitoneError({
157
+ kind: "bad_request",
158
+ status,
159
+ message: `请求被服务端拒绝(HTTP ${status})${detailMessage ? `:${detailMessage}` : ""}`,
160
+ hint: "检查参数取值范围(limit / days / symbols 数量)与参数名拼写后重试。",
161
+ retryable: false,
162
+ ...(detailMessage ? { detail: detailMessage } : {}),
163
+ }, ctx.billing);
164
+ }
165
+ if (status >= 500) {
166
+ return new AntitoneError({
167
+ kind: "server_error",
168
+ status,
169
+ message: `Antitone 服务端错误(HTTP ${status})${detailMessage ? `:${detailMessage}` : ""}`,
170
+ hint: "这是服务端问题,可原样重试 1–2 次;持续失败请在个人中心查看状态页或联系支持。",
171
+ retryable: true,
172
+ }, ctx.billing);
173
+ }
174
+ return new AntitoneError({
175
+ kind: "unknown",
176
+ status,
177
+ message: `请求失败(HTTP ${status})${detailMessage ? `:${detailMessage}` : ""}`,
178
+ retryable: false,
179
+ }, ctx.billing);
180
+ }
181
+ /** 把 fetch 抛出的异常(超时 / DNS / 连接重置 / 用户取消)规范化。 */
182
+ export function normalizeThrown(error, ctx = {}) {
183
+ if (error instanceof AntitoneError)
184
+ return error;
185
+ const name = error instanceof Error ? error.name : "";
186
+ const rawMessage = error instanceof Error ? error.message : String(error);
187
+ if (ctx.signalAborted && !ctx.timedOut) {
188
+ return new AntitoneError({
189
+ kind: "cancelled",
190
+ message: "请求已被取消(客户端中断)。",
191
+ hint: "重试即可;若频繁发生请检查 MCP 客户端的超时设置。",
192
+ retryable: true,
193
+ });
194
+ }
195
+ if (ctx.timedOut || name === "AbortError" || name === "TimeoutError") {
196
+ return new AntitoneError({
197
+ kind: "timeout",
198
+ message: `请求超时(${ctx.timeoutMs ?? "?"} ms 未收到响应)。`,
199
+ hint: "调大环境变量 ANTITONE_TIMEOUT_MS(如 60000),或收窄参数(更小的 limit / days、更短的时间窗)后重试。",
200
+ retryable: true,
201
+ });
202
+ }
203
+ if (error instanceof TypeError || /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|EAI_AGAIN|socket hang up|network/i.test(rawMessage)) {
204
+ return new AntitoneError({
205
+ kind: "network",
206
+ message: `网络错误:无法连接 Antitone API${ctx.url ? `(${ctx.url})` : ""}。`,
207
+ hint: "检查本机网络/代理/DNS,以及 ANTITONE_BASE_URL 是否正确(默认 https://api.desicterminal.com:8443,端口 8443 不能省)。原始错误:" + rawMessage,
208
+ retryable: true,
209
+ detail: rawMessage,
210
+ });
211
+ }
212
+ return new AntitoneError({
213
+ kind: "unknown",
214
+ message: `未预期的错误:${rawMessage}`,
215
+ hint: "可重试一次;持续失败请带上 X-Request-Id 联系支持。",
216
+ retryable: true,
217
+ detail: rawMessage,
218
+ });
219
+ }
220
+ /** 统一出口:把任意异常转成 AntitoneError(工具层绝不让异常冒泡打断 MCP 连接)。 */
221
+ export function normalizeAnyThrown(error, ctx = {}) {
222
+ return normalizeThrown(error, ctx);
223
+ }
224
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * antitone-mcp 主入口 = CLI(2026-09-17 去 MCP)。
4
+ *
5
+ * 产品决策:MCP 协议适配层已移除,全面转向「CLI + Skill」——
6
+ * 所有主流 Agent(DSH / Claude Code / Codex / Cursor / Kimi / Trae / Gemini CLI …)
7
+ * 都有 shell,且 Skill 的祈使式触发远好于 MCP 的被动能力清单。
8
+ *
9
+ * 用法:antitone-mcp <命令>,例如
10
+ * antitone-mcp news --q "半导体 利空" --limit 10
11
+ * antitone-mcp skill --install
12
+ * antitone-mcp tools
13
+ */
14
+ export { AntitoneClient } from "./client.js";
15
+ export { loadConfig, describeConfig } from "./config.js";
16
+ export { TOOL_NAMES, TOOL_SCOPES } from "./tools/index.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * antitone-mcp 主入口 = CLI(2026-09-17 去 MCP)。
4
+ *
5
+ * 产品决策:MCP 协议适配层已移除,全面转向「CLI + Skill」——
6
+ * 所有主流 Agent(DSH / Claude Code / Codex / Cursor / Kimi / Trae / Gemini CLI …)
7
+ * 都有 shell,且 Skill 的祈使式触发远好于 MCP 的被动能力清单。
8
+ *
9
+ * 用法:antitone-mcp <命令>,例如
10
+ * antitone-mcp news --q "半导体 利空" --limit 10
11
+ * antitone-mcp skill --install
12
+ * antitone-mcp tools
13
+ */
14
+ import { realpathSync } from "node:fs";
15
+ import { pathToFileURL } from "node:url";
16
+ export { AntitoneClient } from "./client.js";
17
+ export { loadConfig, describeConfig } from "./config.js";
18
+ export { TOOL_NAMES, TOOL_SCOPES } from "./tools/index.js";
19
+ const invokedDirectly = (() => {
20
+ const entry = process.argv[1];
21
+ if (!entry)
22
+ return false;
23
+ try {
24
+ // npx / npm 的 bin 是 node_modules/.bin 下的符号链接,必须先 realpath 再与 import.meta.url 比对,
25
+ // 否则 `npx -y antitone-mcp` 会静默什么都不做。
26
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
27
+ }
28
+ catch {
29
+ return true;
30
+ }
31
+ })();
32
+ if (invokedDirectly) {
33
+ const { runCli } = await import("./cli.js");
34
+ process.exitCode = await runCli(process.argv.slice(2));
35
+ }
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,iFAAiF;QACjF,qCAAqC;QACrC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC,EAAE,CAAC;AAEL,IAAI,eAAe,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 输出规范(文档 §9 要求,MCP 最容易翻车的地方):
3
+ * 1. 单次返回序列化后 ≤ 32 KB,超出则截断并标 `truncated: true` + 给出收窄参数提示;
4
+ * 2. 每条结果附 `as_of`(数据时刻)与 `stale`(是否过期)布尔;
5
+ * 3. 列表默认 limit=20(在工具层实现);
6
+ * 4. `_billing` 明细透出为 `billing: {credits, balance, ...}`(body 与响应头合并);
7
+ * 5. 错误一律 isError: true 返回,绝不抛异常打断 MCP 连接。
8
+ */
9
+ /**
10
+ * 工具调用结果的统一形状(本地定义,2026-09-17 起不再依赖 MCP SDK)。
11
+ * 与 MCP 的 CallToolResult 结构兼容:content 为块数组,isError 标记失败。
12
+ */
13
+ export interface ContentBlock {
14
+ type: string;
15
+ text?: string;
16
+ [key: string]: unknown;
17
+ }
18
+ export interface CallToolResult {
19
+ content: ContentBlock[];
20
+ isError?: boolean;
21
+ structuredContent?: unknown;
22
+ [key: string]: unknown;
23
+ }
24
+ import type { Billing } from "./client.js";
25
+ import { AntitoneError } from "./errors.js";
26
+ export interface SuccessInput {
27
+ tool: string;
28
+ data: unknown;
29
+ endpoints: string[];
30
+ asOf: string;
31
+ asOfSource: string;
32
+ stale: boolean;
33
+ staleAfterMs: number;
34
+ billing?: Billing;
35
+ requestId?: string;
36
+ httpStatus?: number;
37
+ durationMs?: number;
38
+ /** 列表条数(截断后实际返回的条数)。 */
39
+ count?: number;
40
+ /** 截断时给 LLM 的收窄建议。 */
41
+ narrowHints?: string[];
42
+ maxBytes?: number;
43
+ meta?: Record<string, unknown>;
44
+ }
45
+ export interface DropInfo {
46
+ path: string;
47
+ kept: number;
48
+ total: number;
49
+ reason: string;
50
+ }
51
+ export interface FitResult {
52
+ text: string;
53
+ truncated: boolean;
54
+ originalBytes: number;
55
+ finalBytes: number;
56
+ dropped: DropInfo[];
57
+ }
58
+ export declare function byteLength(text: string): number;
59
+ /**
60
+ * 把信封压进字节预算:先缩数组 → 再截超长字符串 → 最后退化为预览对象。
61
+ * 返回的 text 一定是合法 JSON,且一定 ≤ budget。
62
+ */
63
+ export declare function fitToBudget(envelope: Record<string, unknown>, budget: number, narrowHints: string[]): FitResult;
64
+ export declare function buildSuccessResult(input: SuccessInput): CallToolResult;
65
+ export interface ErrorInput {
66
+ tool: string;
67
+ error: AntitoneError;
68
+ requestId?: string;
69
+ endpoints?: string[];
70
+ durationMs?: number;
71
+ }
72
+ /** 失败也走正常 content 返回,仅把 isError 置 true —— 这样 MCP 连接不会断。 */
73
+ export declare function buildErrorResult(input: ErrorInput): CallToolResult;
74
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AACD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB;AAMD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AA4ED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CA6D/G;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CA0FtE;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoCD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAOlE"}
package/dist/output.js ADDED
@@ -0,0 +1,283 @@
1
+ /**
2
+ * 输出规范(文档 §9 要求,MCP 最容易翻车的地方):
3
+ * 1. 单次返回序列化后 ≤ 32 KB,超出则截断并标 `truncated: true` + 给出收窄参数提示;
4
+ * 2. 每条结果附 `as_of`(数据时刻)与 `stale`(是否过期)布尔;
5
+ * 3. 列表默认 limit=20(在工具层实现);
6
+ * 4. `_billing` 明细透出为 `billing: {credits, balance, ...}`(body 与响应头合并);
7
+ * 5. 错误一律 isError: true 返回,绝不抛异常打断 MCP 连接。
8
+ */
9
+ import { MAX_RESULT_BYTES } from "./config.js";
10
+ function isRecord(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ export function byteLength(text) {
14
+ return Buffer.byteLength(text, "utf8");
15
+ }
16
+ function serialize(value) {
17
+ try {
18
+ const json = JSON.stringify(value, null, 2);
19
+ return json === undefined ? "null" : json;
20
+ }
21
+ catch {
22
+ return JSON.stringify({ ok: false, note: "结果无法序列化为 JSON(可能包含循环引用)" }, null, 2);
23
+ }
24
+ }
25
+ /** 只收集「不在其他数组内部」的数组(外层数组一缩小,内层自然跟着缩)。 */
26
+ function collectTopLevelArrays(node, path, out, insideArray, depth = 0) {
27
+ if (depth > 10 || node === null || typeof node !== "object")
28
+ return;
29
+ if (Array.isArray(node)) {
30
+ if (!insideArray && node.length > 0) {
31
+ const size = byteLength(JSON.stringify(node) ?? "");
32
+ if (size >= 256)
33
+ out.push({ path, arr: node, size });
34
+ }
35
+ for (const child of node)
36
+ collectTopLevelArrays(child, path, out, true, depth + 1);
37
+ return;
38
+ }
39
+ for (const [key, value] of Object.entries(node)) {
40
+ collectTopLevelArrays(value, path ? `${path}.${key}` : key, out, insideArray, depth + 1);
41
+ }
42
+ }
43
+ /** 逐级缩小的候选保留条数(从大到小)。 */
44
+ function keepSteps(length) {
45
+ const steps = new Set();
46
+ for (const factor of [0.75, 0.5, 0.35, 0.25, 0.15]) {
47
+ const value = Math.max(1, Math.floor(length * factor));
48
+ if (value < length)
49
+ steps.add(value);
50
+ }
51
+ for (const fixed of [200, 100, 50, 40, 20, 10, 5, 3, 2, 1, 0]) {
52
+ if (fixed < length)
53
+ steps.add(fixed);
54
+ }
55
+ return [...steps].sort((a, b) => b - a);
56
+ }
57
+ const STRING_LIMIT = 2048;
58
+ function truncateLongStrings(node, path, dropped, depth = 0) {
59
+ if (depth > 12 || node === null || typeof node !== "object")
60
+ return;
61
+ if (Array.isArray(node)) {
62
+ node.forEach((child, index) => truncateLongStrings(child, `${path}[${index}]`, dropped, depth + 1));
63
+ return;
64
+ }
65
+ const record = node;
66
+ for (const [key, value] of Object.entries(record)) {
67
+ const childPath = path ? `${path}.${key}` : key;
68
+ if (typeof value === "string" && value.length > STRING_LIMIT) {
69
+ record[key] = `${value.slice(0, STRING_LIMIT / 2)}…(超长字段已截断,原长度 ${value.length} 字符)`;
70
+ dropped.push({ path: childPath, kept: STRING_LIMIT / 2, total: value.length, reason: "long_string_truncated" });
71
+ continue;
72
+ }
73
+ truncateLongStrings(value, childPath, dropped, depth + 1);
74
+ }
75
+ }
76
+ function buildTruncationHint(originalBytes, finalBytes, budget, hints) {
77
+ const parts = [
78
+ `结果超过单次 ${budget} B 上限已自动截断(原始 ${originalBytes} B → 实际 ${finalBytes} B)。`,
79
+ ];
80
+ if (hints.length > 0) {
81
+ parts.push(`收窄建议:${hints.map((hint, index) => `${index + 1}) ${hint}`).join(";")}。`);
82
+ }
83
+ return parts.join(" ");
84
+ }
85
+ /**
86
+ * 把信封压进字节预算:先缩数组 → 再截超长字符串 → 最后退化为预览对象。
87
+ * 返回的 text 一定是合法 JSON,且一定 ≤ budget。
88
+ */
89
+ export function fitToBudget(envelope, budget, narrowHints) {
90
+ let text = serialize(envelope);
91
+ let bytes = byteLength(text);
92
+ if (bytes <= budget) {
93
+ return { text, truncated: false, originalBytes: bytes, finalBytes: bytes, dropped: [] };
94
+ }
95
+ const originalBytes = bytes;
96
+ const dropped = [];
97
+ const targets = [];
98
+ collectTopLevelArrays(envelope["data"], "data", targets, false);
99
+ targets.sort((a, b) => b.size - a.size);
100
+ for (const target of targets) {
101
+ if (byteLength(serialize(envelope)) <= budget)
102
+ break;
103
+ const total = target.arr.length;
104
+ if (total === 0)
105
+ continue;
106
+ let kept = total;
107
+ for (const candidate of keepSteps(total)) {
108
+ target.arr.length = candidate;
109
+ kept = candidate;
110
+ if (byteLength(serialize(envelope)) <= budget)
111
+ break;
112
+ }
113
+ dropped.push({ path: target.path, kept, total, reason: "list_trimmed" });
114
+ }
115
+ if (byteLength(serialize(envelope)) > budget) {
116
+ truncateLongStrings(envelope["data"], "data", dropped);
117
+ }
118
+ if (byteLength(serialize(envelope)) > budget) {
119
+ const previewSource = serialize(envelope["data"]);
120
+ let preview = previewSource.slice(0, 4000);
121
+ envelope["data"] = {
122
+ _truncated_preview: true,
123
+ note: "原始结果过大,已截断为预览;请用更窄的参数重新调用。",
124
+ original_bytes: byteLength(previewSource),
125
+ preview,
126
+ };
127
+ for (const limit of [2000, 800, 200, 0]) {
128
+ if (byteLength(serialize(envelope)) <= budget)
129
+ break;
130
+ preview = previewSource.slice(0, limit);
131
+ envelope["data"] = {
132
+ _truncated_preview: true,
133
+ note: "原始结果过大,已截断为预览;请用更窄的参数重新调用。",
134
+ original_bytes: byteLength(previewSource),
135
+ preview,
136
+ };
137
+ }
138
+ }
139
+ text = serialize(envelope);
140
+ bytes = byteLength(text);
141
+ // 兜底:极端情况下(巨大的 meta)直接把 preview 清空为骨架。
142
+ if (bytes > budget) {
143
+ envelope["data"] = { _truncated: true, note: "结果过大且无法安全裁剪,请收窄参数后重试。" };
144
+ text = serialize(envelope);
145
+ bytes = byteLength(text);
146
+ }
147
+ return { text, truncated: true, originalBytes, finalBytes: bytes, dropped };
148
+ }
149
+ export function buildSuccessResult(input) {
150
+ const budget = input.maxBytes ?? MAX_RESULT_BYTES;
151
+ const hints = input.narrowHints ?? [];
152
+ const meta = {
153
+ endpoints: input.endpoints,
154
+ data_as_of_source: input.asOfSource,
155
+ stale_after_ms: input.staleAfterMs,
156
+ ...(input.requestId ? { request_id: input.requestId } : {}),
157
+ ...(input.httpStatus !== undefined ? { http_status: input.httpStatus } : {}),
158
+ ...(input.durationMs !== undefined ? { duration_ms: input.durationMs } : {}),
159
+ ...(input.count !== undefined ? { count: input.count } : {}),
160
+ ...(input.meta ?? {}),
161
+ };
162
+ const makeEnvelope = (truncation) => ({
163
+ ok: true,
164
+ tool: input.tool,
165
+ as_of: input.asOf,
166
+ stale: input.stale,
167
+ truncated: truncation !== undefined,
168
+ ...(input.billing ? { billing: input.billing } : {}),
169
+ ...(truncation ? { truncation } : {}),
170
+ data: input.data ?? null,
171
+ meta,
172
+ });
173
+ const first = fitToBudget(makeEnvelope(), budget, hints);
174
+ if (!first.truncated) {
175
+ return { content: [{ type: "text", text: first.text }] };
176
+ }
177
+ let dropped = [...first.dropped];
178
+ const originalBytes = first.originalBytes;
179
+ let truncation = {
180
+ budget_bytes: budget,
181
+ original_bytes: originalBytes,
182
+ final_bytes: first.finalBytes,
183
+ dropped,
184
+ hint: buildTruncationHint(originalBytes, first.finalBytes, budget, hints),
185
+ };
186
+ // 固定点迭代:把 truncation 元信息本身占用的字节也纳入预算,最多 4 轮。
187
+ for (let round = 0; round < 4; round += 1) {
188
+ const envelope = makeEnvelope(truncation);
189
+ const text = serialize(envelope);
190
+ const bytes = byteLength(text);
191
+ if (bytes <= budget) {
192
+ truncation["final_bytes"] = bytes;
193
+ const settled = serialize(makeEnvelope(truncation));
194
+ if (byteLength(settled) <= budget) {
195
+ return { content: [{ type: "text", text: settled }] };
196
+ }
197
+ continue;
198
+ }
199
+ const shrunk = fitToBudget(envelope, budget, hints);
200
+ dropped = [
201
+ ...dropped,
202
+ ...shrunk.dropped.filter((item) => !dropped.some((seen) => seen.path === item.path && seen.kept === item.kept)),
203
+ ];
204
+ truncation = {
205
+ budget_bytes: budget,
206
+ original_bytes: originalBytes,
207
+ final_bytes: shrunk.finalBytes,
208
+ dropped,
209
+ hint: buildTruncationHint(originalBytes, shrunk.finalBytes, budget, hints),
210
+ };
211
+ }
212
+ // 极端兜底:只保留元信息骨架(仍是合法 JSON)。
213
+ const minimal = {
214
+ ok: true,
215
+ tool: input.tool,
216
+ as_of: input.asOf,
217
+ stale: input.stale,
218
+ truncated: true,
219
+ truncation: {
220
+ budget_bytes: budget,
221
+ original_bytes: originalBytes,
222
+ dropped,
223
+ hint: buildTruncationHint(originalBytes, 0, budget, hints),
224
+ },
225
+ data: { _truncated: true, note: "结果过大,仅保留元信息;请收窄参数后重试。" },
226
+ };
227
+ let text = serialize(minimal);
228
+ if (byteLength(text) > budget) {
229
+ delete minimal["truncation"];
230
+ minimal["truncation_note"] = `原始 ${originalBytes} B 超预算,已仅保留骨架`;
231
+ text = serialize(minimal);
232
+ }
233
+ return { content: [{ type: "text", text }] };
234
+ }
235
+ function errorPayload(error, tool, requestId, endpoints, durationMs) {
236
+ const normalized = error.normalized;
237
+ const detail = {
238
+ kind: normalized.kind,
239
+ message: normalized.message,
240
+ retryable: normalized.retryable,
241
+ };
242
+ if (normalized.status !== undefined)
243
+ detail["status"] = normalized.status;
244
+ if (normalized.hint)
245
+ detail["hint"] = normalized.hint;
246
+ if (normalized.need_credits !== undefined)
247
+ detail["need_credits"] = normalized.need_credits;
248
+ if (normalized.balance !== undefined)
249
+ detail["balance"] = normalized.balance;
250
+ if (normalized.required_scope !== undefined)
251
+ detail["required_scope"] = normalized.required_scope;
252
+ if (normalized.topup_url !== undefined)
253
+ detail["topup_url"] = normalized.topup_url;
254
+ if (normalized.retry_after_ms !== undefined) {
255
+ detail["retry_after"] = normalized.retry_after_s ?? Math.ceil(normalized.retry_after_ms / 1000);
256
+ detail["retry_after_ms"] = normalized.retry_after_ms;
257
+ }
258
+ if (normalized.detail !== undefined)
259
+ detail["detail"] = normalized.detail;
260
+ return {
261
+ ok: false,
262
+ tool,
263
+ as_of: new Date().toISOString(),
264
+ stale: false,
265
+ error: detail,
266
+ ...(error.billing ? { billing: error.billing } : {}),
267
+ meta: {
268
+ ...(endpoints && endpoints.length > 0 ? { endpoints } : {}),
269
+ ...(requestId ? { request_id: requestId } : {}),
270
+ ...(durationMs !== undefined ? { duration_ms: durationMs } : {}),
271
+ },
272
+ };
273
+ }
274
+ /** 失败也走正常 content 返回,仅把 isError 置 true —— 这样 MCP 连接不会断。 */
275
+ export function buildErrorResult(input) {
276
+ const payload = errorPayload(input.error, input.tool, input.requestId, input.endpoints, input.durationMs);
277
+ const text = serialize(payload);
278
+ return {
279
+ content: [{ type: "text", text }],
280
+ isError: true,
281
+ };
282
+ }
283
+ //# sourceMappingURL=output.js.map