@kairyou/agent-tools 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,6 @@ import {
8
8
  AGENT_TOOLS_HOME,
9
9
  agentConfig,
10
10
  usagePreset,
11
- panelUserHeaders,
12
11
  newApiQuotaScale,
13
12
  providerUsageDays,
14
13
  debugLog,
@@ -22,15 +21,12 @@ import {
22
21
  } from "./urls.mjs";
23
22
  import {
24
23
  pickNumber,
25
- formatMoney,
26
24
  usageRoot,
27
25
  hasV1UsageFields,
28
26
  formatQuota,
29
27
  formatNewApiTokenLine,
28
+ formatOneApiBillingLine,
30
29
  formatOpenRouterLine,
31
- formatPanelUserSelfLine,
32
- panelQuotaScale,
33
- panelQuotaLooksRemaining,
34
30
  } from "./format.mjs";
35
31
  import { readRouteCache } from "./cache.mjs";
36
32
 
@@ -63,13 +59,12 @@ async function fetchV1Usage(context) {
63
59
  return usageResult(context, "v1-usage", await formatQuota(context.label, json), json);
64
60
  }
65
61
 
66
- // NewAPI / OneAPI family panels: use the current API key as Bearer auth and
67
- // query token usage from the service root rather than the /v1 OpenAI-compatible
68
- // path.
62
+ // New API exposes a read-only usage endpoint authenticated by the same relay
63
+ // API key used for model requests.
69
64
  async function fetchNewApiTokenUsage(context) {
70
65
  const json = await requestJson(joinUrl(serviceRoot(context.baseUrl), "/api/usage/token/"), {
71
66
  key: context.key,
72
- name: "NewAPI token usage",
67
+ name: "New API token usage",
73
68
  });
74
69
  const root = usageRoot(json);
75
70
  const quota = pickNumber(root, ["quota", "limit", "total_quota", "totalQuota"]);
@@ -96,84 +91,46 @@ async function fetchNewApiTokenUsage(context) {
96
91
  return usageResult(context, "newapi-token", await formatNewApiTokenLine(context.label, json), normalized);
97
92
  }
98
93
 
99
- // NewAPI / OneAPI / OneHub / DoneHub / Veloera panel session endpoint, based on
100
- // Metapi's platform handling. This works when PROVIDER_USAGE_API_KEY is a panel
101
- // access/session token, or when the site accepts the API key for /api/user/self.
102
- async function fetchPanelUserSelfUsage(context) {
103
- const preset = await usagePreset();
104
- const kind = preset === "auto" ? "new-api" : preset;
105
- const json = await requestJson(joinUrl(serviceRoot(context.baseUrl), "/api/user/self"), {
94
+ // One API's legacy OpenAI billing endpoints use the same relay API key as
95
+ // model requests. Subscription reports the total quota; usage reports cents.
96
+ async function fetchOneApiBillingUsage(context) {
97
+ const base = serviceRoot(context.baseUrl);
98
+ const subscription = await requestJson(joinUrl(base, "/v1/dashboard/billing/subscription"), {
106
99
  key: context.key,
107
- name: "panel /api/user/self",
108
- headers: await panelUserHeaders(),
100
+ name: "One API billing subscription",
109
101
  });
110
- const root = usageRoot(json);
111
- if (pickNumber(root, ["quota"]) === undefined && pickNumber(root, ["used_quota", "usedQuota"]) === undefined) {
112
- await debugLog({
113
- source: "panel /api/user/self",
114
- payloadKeys: Object.keys(root || {}).slice(0, 20),
115
- success: root?.success,
116
- message: root?.message || root?.error?.message || "",
117
- });
102
+ const usage = await requestJson(joinUrl(base, "/v1/dashboard/billing/usage"), {
103
+ key: context.key,
104
+ name: "One API billing usage",
105
+ });
106
+ const limit = pickNumber(subscription, ["hard_limit_usd", "hardLimitUsd"]);
107
+ const usageCents = pickNumber(usage, ["total_usage", "totalUsage"]);
108
+ if (limit === undefined || usageCents === undefined) {
109
+ throw new Error("One API billing payload has no quota fields");
118
110
  }
119
- const scale = panelQuotaScale(kind);
120
- const quota = pickNumber(root, ["quota"]);
121
- const used = pickNumber(root, ["used_quota", "usedQuota"]);
122
- const remaining = panelQuotaLooksRemaining(kind)
123
- ? quota
124
- : (quota === undefined || used === undefined ? quota : Math.max(0, quota - used));
125
- const total = panelQuotaLooksRemaining(kind)
126
- ? (quota === undefined || used === undefined ? quota : quota + used)
127
- : quota;
111
+ const used = usageCents / 100;
128
112
  const normalized = {
129
113
  mode: "quota_limited",
130
114
  quota: {
131
- limit: total === undefined ? undefined : total / scale,
132
- used: used === undefined ? undefined : used / scale,
133
- remaining: remaining === undefined ? undefined : remaining / scale,
115
+ limit,
116
+ used,
117
+ remaining: Math.max(0, limit - used),
134
118
  },
135
119
  unit: "USD",
136
- source: "panel-user-self",
137
- raw: json,
120
+ source: "oneapi-billing",
121
+ raw: { subscription, usage },
138
122
  };
139
123
  return usageResult(
140
124
  context,
141
- "panel-user-self",
142
- await formatPanelUserSelfLine(context.label, json, kind),
143
- normalized
144
- );
145
- }
146
-
147
- // Sub2API exposes user balance as USD at /api/v1/auth/me. Newer deployments may
148
- // also expose richer subscription summaries through /v1/usage, so this route is
149
- // a fallback for deployments where /v1/usage is unavailable.
150
- async function fetchSub2ApiAuthMeUsage(context) {
151
- const json = await requestJson(joinUrl(serviceRoot(context.baseUrl), "/api/v1/auth/me"), {
152
- key: context.key,
153
- name: "Sub2API auth/me",
154
- });
155
- const root = usageRoot(json);
156
- const balance = pickNumber(root, ["balance"]);
157
- if (balance === undefined) throw new Error("Sub2API auth/me payload has no balance field");
158
- const normalized = {
159
- mode: "unrestricted",
160
- planName: root?.username || root?.email || context.label || "Sub2API",
161
- balance,
162
- unit: "USD",
163
- source: "sub2api-auth-me",
164
- raw: json,
165
- };
166
- return usageResult(
167
- context,
168
- "sub2api-auth-me",
169
- `API | balance ${formatMoney(balance)}`,
125
+ "oneapi-billing",
126
+ formatOneApiBillingLine(limit, used),
170
127
  normalized
171
128
  );
172
129
  }
173
130
 
174
131
  // OpenRouter exposes normal API-key usage at /api/v1/key. Some accounts also
175
132
  // expose credits at /api/v1/credits; keep this route isolated because
176
- // OpenRouter's base URL already includes /api/v1, unlike NewAPI/OneAPI.
133
+ // OpenRouter's base URL already includes /api/v1, unlike New API.
177
134
  async function fetchOpenRouterUsage(context) {
178
135
  const base = cleanBaseUrl(context.baseUrl).includes("/api/v1")
179
136
  ? cleanBaseUrl(context.baseUrl)
@@ -201,20 +158,15 @@ const USAGE_ROUTES = {
201
158
  path: "/v1/usage",
202
159
  run: fetchV1Usage,
203
160
  },
204
- "sub2api-auth-me": {
205
- id: "sub2api-auth-me",
206
- path: "/api/v1/auth/me",
207
- run: fetchSub2ApiAuthMeUsage,
208
- },
209
161
  "newapi-token": {
210
162
  id: "newapi-token",
211
163
  path: "/api/usage/token/",
212
164
  run: fetchNewApiTokenUsage,
213
165
  },
214
- "panel-user-self": {
215
- id: "panel-user-self",
216
- path: "/api/user/self",
217
- run: fetchPanelUserSelfUsage,
166
+ "oneapi-billing": {
167
+ id: "oneapi-billing",
168
+ path: "/v1/dashboard/billing/subscription",
169
+ run: fetchOneApiBillingUsage,
218
170
  },
219
171
  "openrouter": {
220
172
  id: "openrouter",
@@ -224,7 +176,7 @@ const USAGE_ROUTES = {
224
176
  };
225
177
 
226
178
  // User-authored gateway routes, declared in config.jsonc:
227
- // "providerUsage": { "routes": ["custom/anyrouter.mjs"] }
179
+ // "providerUsage": { "routes": ["custom/my-gateway.mjs"] }
228
180
  // Paths resolve against ~/.agent-tools. Each module exports
229
181
  // `export async function run(context, helpers)` plus an optional
230
182
  // `export const meta = { id }` (id defaults to the file name). Broken modules
@@ -304,25 +256,14 @@ async function routeRegistry() {
304
256
  return registry;
305
257
  }
306
258
 
307
- // Presets are probe-order aliases over the routes above, not separate
308
- // protocols (e.g. anyrouter/agentrouter just try the NewAPI panel endpoints
309
- // and /v1/usage in a different order).
310
- // They do not provide panel session-cookie authentication; only endpoints that
311
- // accept the configured Bearer key can succeed.
259
+ // Presets select API-key usage protocols, not hosted gateway brands.
312
260
  async function usageRouteIds(context) {
313
261
  const preset = await usagePreset();
314
262
  const routes = {
315
- "sub2api": ["v1-usage", "sub2api-auth-me"],
263
+ "sub2api": ["v1-usage"],
316
264
  "openai-compatible": ["v1-usage"],
317
- "new-api": ["newapi-token", "panel-user-self"],
318
- "one-api": ["newapi-token", "panel-user-self"],
319
- "onehub": ["newapi-token", "panel-user-self"],
320
- "one-hub": ["newapi-token", "panel-user-self"],
321
- "donehub": ["newapi-token", "panel-user-self"],
322
- "done-hub": ["newapi-token", "panel-user-self"],
323
- "veloera": ["panel-user-self", "newapi-token"],
324
- "anyrouter": ["newapi-token", "panel-user-self", "v1-usage"],
325
- "agentrouter": ["newapi-token", "panel-user-self", "v1-usage"],
265
+ "new-api": ["newapi-token"],
266
+ "one-api": ["oneapi-billing"],
326
267
  "openrouter": ["openrouter"],
327
268
  };
328
269
  if (routes[preset]) return routes[preset];
@@ -334,7 +275,7 @@ async function usageRouteIds(context) {
334
275
  const customIds = (await customRoutes()).map((route) => route.id);
335
276
  const builtinIds = hostIncludes(context.baseUrl, "openrouter.ai")
336
277
  ? ["openrouter"]
337
- : ["v1-usage", "sub2api-auth-me", "newapi-token", "panel-user-self"];
278
+ : ["v1-usage", "newapi-token", "oneapi-billing"];
338
279
  return [...new Set([...customIds, ...builtinIds])];
339
280
  }
340
281
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kairyou/agent-tools",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Reusable Agent Skills and installable integrations (statusline, provider usage, vision) for Codex, Claude Code, and opencode.",
5
5
  "license": "MIT",
6
6
  "engines": {