@costgate/momentum-mcp 0.1.34 → 0.1.35

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/README.md CHANGED
@@ -9,7 +9,7 @@ MOMENTUM_TOKEN=mom_agent_xxx \
9
9
  ```
10
10
 
11
11
  整条命令不用自己拼 —— 在 Momentum 的「设置 › Agent」里签发令牌时会直接给出,
12
- 默认用 `@latest`,发新版不用改配置;想固定某一版时把 latest 换成版本号(当前是 0.1.34)。
12
+ 默认用 `@latest`,发新版不用改配置;想固定某一版时把 latest 换成版本号(当前是 0.1.35)。
13
13
 
14
14
  | 环境变量 | 必填 | 说明 |
15
15
  |---|---|---|
@@ -18,4 +18,4 @@ MOMENTUM_TOKEN=mom_agent_xxx \
18
18
  | `MOMENTUM_CF_ACCESS_CLIENT_ID` | 否 | 部署在 Cloudflare Access 后面时才需要 |
19
19
  | `MOMENTUM_CF_ACCESS_CLIENT_SECRET` | 否 | 同上 |
20
20
 
21
- 版本 0.1.34。客户端和服务端版本不一致时,启动日志(stderr)会明确警告。
21
+ 版本 0.1.35。客户端和服务端版本不一致时,启动日志(stderr)会明确警告。
package/momentum-mcp.mjs CHANGED
@@ -15339,7 +15339,7 @@ var allowErrorProps = SuperJSON.allowErrorProps;
15339
15339
 
15340
15340
  // src/version.ts
15341
15341
  var UNKNOWN = "unknown";
15342
- var MCP_VERSION = true ? "0.1.34" : versionFromRepo();
15342
+ var MCP_VERSION = true ? "0.1.35" : versionFromRepo();
15343
15343
  var CLIENT_HEADER = `momentum-mcp/${MCP_VERSION}`;
15344
15344
  function compareVersions(a, b) {
15345
15345
  const parse4 = (v) => {
@@ -23621,6 +23621,12 @@ function hostUsageReader(env = process.env, opts = {}) {
23621
23621
  const home = opts.home ?? homedir();
23622
23622
  return async (runId, since) => {
23623
23623
  try {
23624
+ const records = opts.reported?.since(since) ?? [];
23625
+ if (env.MOMENTUM_USAGE_FILE) {
23626
+ records.push(...parseHostReportFile(await readOptional(env.MOMENTUM_USAGE_FILE), since, opts.log));
23627
+ }
23628
+ const reported = sumHostReported(records);
23629
+ if (reported) return reported;
23624
23630
  if (env.CLAUDE_CODE_SESSION_ID) {
23625
23631
  const dir = env.CLAUDE_CONFIG_DIR ?? join(home, ".claude");
23626
23632
  return await claudeCodeUsage(dir, env.CLAUDE_CODE_SESSION_ID, runId, since);
@@ -23734,6 +23740,74 @@ function sumCodex(bodies, threadId, since) {
23734
23740
  total.modelRequests = items.length;
23735
23741
  return total;
23736
23742
  }
23743
+ var HOST_USAGE_META_KEY = "ai.costgate.momentum/usage";
23744
+ var HostReports = class {
23745
+ constructor(log2) {
23746
+ this.log = log2;
23747
+ }
23748
+ items = [];
23749
+ /** 只吃 `_meta`。工具参数是模型写的,哪怕里面也叫 `_meta`,也不会走到这里 */
23750
+ record(meta, at = /* @__PURE__ */ new Date()) {
23751
+ if (!meta || typeof meta !== "object") return;
23752
+ const value = meta[HOST_USAGE_META_KEY];
23753
+ if (value === void 0) return;
23754
+ for (const item of Array.isArray(value) ? value : [value]) {
23755
+ const report = toHostReport(item);
23756
+ if (report) this.items.push({ ...report, at: at.getTime() });
23757
+ else this.log?.(` \u5FFD\u7565\u4E00\u6761\u683C\u5F0F\u4E0D\u5BF9\u7684\u5BBF\u4E3B\u7528\u91CF\uFF1A${JSON.stringify(item).slice(0, 200)}`);
23758
+ }
23759
+ }
23760
+ since(since) {
23761
+ return this.items.filter((r) => r.at >= since.getTime());
23762
+ }
23763
+ };
23764
+ function parseHostReportFile(body, since, log2) {
23765
+ const out = [];
23766
+ for (const line of body.split("\n")) {
23767
+ if (!line.trim()) continue;
23768
+ const entry = parseLine(line);
23769
+ if (!entry || !inWindow(entry.timestamp, since)) continue;
23770
+ const report = toHostReport(entry);
23771
+ if (report) out.push(report);
23772
+ else log2?.(` \u5FFD\u7565\u4E00\u6761\u683C\u5F0F\u4E0D\u5BF9\u7684\u5BBF\u4E3B\u7528\u91CF\uFF1A${line.slice(0, 200)}`);
23773
+ }
23774
+ return out;
23775
+ }
23776
+ function sumHostReported(reports) {
23777
+ const seen = /* @__PURE__ */ new Set();
23778
+ const total = emptyUsage("host_reported");
23779
+ for (const r of reports) {
23780
+ if (r.requestId !== void 0) {
23781
+ if (seen.has(r.requestId)) continue;
23782
+ seen.add(r.requestId);
23783
+ }
23784
+ total.modelRequests += 1;
23785
+ total.inputTokens += r.inputTokens;
23786
+ total.cachedInputTokens += r.cachedInputTokens;
23787
+ total.outputTokens += r.outputTokens;
23788
+ total.reasoningTokens += r.reasoningTokens;
23789
+ }
23790
+ return total.modelRequests > 0 ? total : null;
23791
+ }
23792
+ function toHostReport(value) {
23793
+ if (!value || typeof value !== "object") return null;
23794
+ const v = value;
23795
+ const count = (x, fallback) => x === void 0 && fallback !== void 0 ? fallback : Number.isSafeInteger(x) && x >= 0 ? x : null;
23796
+ const inputTokens = count(v.inputTokens);
23797
+ const outputTokens = count(v.outputTokens);
23798
+ const cachedInputTokens = count(v.cachedInputTokens, 0);
23799
+ const reasoningTokens = count(v.reasoningTokens, 0);
23800
+ if (inputTokens === null || outputTokens === null || cachedInputTokens === null || reasoningTokens === null) return null;
23801
+ if (cachedInputTokens > inputTokens || reasoningTokens > outputTokens) return null;
23802
+ if (v.requestId !== void 0 && (typeof v.requestId !== "string" || v.requestId.length > 200)) return null;
23803
+ return {
23804
+ ...typeof v.requestId === "string" ? { requestId: v.requestId } : {},
23805
+ inputTokens,
23806
+ cachedInputTokens,
23807
+ outputTokens,
23808
+ reasoningTokens
23809
+ };
23810
+ }
23737
23811
  var RunMeters = class {
23738
23812
  runs = /* @__PURE__ */ new Map();
23739
23813
  open(runId, since = /* @__PURE__ */ new Date()) {
@@ -23780,6 +23854,14 @@ function inWindow(timestamp, since) {
23780
23854
  const at = timestamp ? Date.parse(timestamp) : Number.NaN;
23781
23855
  return Number.isFinite(at) && at >= since.getTime();
23782
23856
  }
23857
+ async function readOptional(path) {
23858
+ try {
23859
+ return await readFile(path, "utf8");
23860
+ } catch (err) {
23861
+ if (err.code === "ENOENT") return "";
23862
+ throw err;
23863
+ }
23864
+ }
23783
23865
  async function isFile(path) {
23784
23866
  try {
23785
23867
  return (await stat(path)).isFile();
@@ -23884,7 +23966,8 @@ function createTools(client, profile = "full", options = {}) {
23884
23966
  { log: options.log }
23885
23967
  );
23886
23968
  const meters = new RunMeters();
23887
- const readUsage = options.readUsage ?? hostUsageReader(process.env, { log: options.log });
23969
+ const reported = options.hostReports ?? new HostReports(options.log);
23970
+ const readUsage = options.readUsage ?? hostUsageReader(process.env, { log: options.log, reported });
23888
23971
  async function measured(runId) {
23889
23972
  const meter = meters.get(runId);
23890
23973
  if (!meter) return {};
@@ -24863,7 +24946,8 @@ ${JSON.stringify(receipt)}`);
24863
24946
  const selected = profile === "work" ? tools.filter((tool) => WORK_TOOL_NAMES.has(tool.name)) : tools;
24864
24947
  return selected.map((tool) => ({
24865
24948
  ...tool,
24866
- handler: async (args) => {
24949
+ handler: async (args, meta) => {
24950
+ reported.record(meta);
24867
24951
  const argBytes = byteLength(JSON.stringify(args ?? {}));
24868
24952
  const charged = meters.beginCall(argBytes);
24869
24953
  let result;
@@ -24947,8 +25031,9 @@ function createMcpServer(client, profile = "work", options = {}) {
24947
25031
  server.registerTool(
24948
25032
  tool.name,
24949
25033
  { description: tool.description, inputSchema: tool.inputSchema },
24950
- // SDK 会先按 inputSchema 校验再调进来,这里的形状转换是安全的
24951
- (args) => tool.handler(args)
25034
+ // SDK 会先按 inputSchema 校验再调进来,这里的形状转换是安全的。
25035
+ // extra._meta 是请求 params._meta,宿主可以在里面上报用量(IMMO-215)
25036
+ (args, extra) => tool.handler(args, extra._meta)
24952
25037
  );
24953
25038
  }
24954
25039
  return server;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@costgate/momentum-mcp",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "Momentum MCP Server —— 把 Momentum 的任务、领取与交付接进任意 MCP 客户端",
5
5
  "type": "module",
6
6
  "bin": {