@kairyou/agent-tools 0.3.0 → 0.5.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/README.md +57 -39
- package/README.zh-CN.md +54 -40
- package/dist/statusline/claude-statusline.mjs +1160 -0
- package/dist/usage/cli.mjs +25 -0
- package/dist/usage/codex-hook.mjs +144 -0
- package/dist/usage/core.mjs +1876 -0
- package/dist/usage/opencode-plugin.mjs +80 -0
- package/dist/usage/opencode-tui.mjs +46 -0
- package/integrations/statusline/claude-statusline.mjs +8 -38
- package/integrations/usage/core.mjs +13 -1074
- package/integrations/usage/lib/cache.mjs +110 -0
- package/integrations/usage/lib/config.mjs +99 -0
- package/integrations/usage/lib/context.mjs +138 -0
- package/integrations/usage/lib/format.mjs +265 -0
- package/integrations/usage/lib/http.mjs +186 -0
- package/integrations/usage/lib/routes.mjs +265 -0
- package/integrations/usage/lib/urls.mjs +48 -0
- package/package.json +5 -5
- package/scripts/build.mjs +65 -0
- package/scripts/install.mjs +26 -15
- package/scripts/build-vision.mjs +0 -35
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// integrations/usage/opencode-plugin.mjs
|
|
2
|
+
import { queryProviderUsage } from "./core.mjs";
|
|
3
|
+
var DEFAULT_REFRESH_MS = 6e4;
|
|
4
|
+
function toastMessage(text) {
|
|
5
|
+
return text.replace(/^API \| /, "");
|
|
6
|
+
}
|
|
7
|
+
function firstString(value, keys) {
|
|
8
|
+
for (const key of keys) {
|
|
9
|
+
if (typeof value?.[key] === "string" && value[key].trim()) return value[key].trim();
|
|
10
|
+
}
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
function providerContext(input) {
|
|
14
|
+
const info = input?.provider?.info || {};
|
|
15
|
+
const options = input?.provider?.options || {};
|
|
16
|
+
const providerName = String(input?.model?.providerID || info.id || "opencode");
|
|
17
|
+
const label = String(info.name || providerName);
|
|
18
|
+
return {
|
|
19
|
+
providerName,
|
|
20
|
+
provider: { name: label },
|
|
21
|
+
label,
|
|
22
|
+
baseUrl: process.env.PROVIDER_USAGE_BASE_URL || firstString(options, ["baseURL", "baseUrl", "base_url"]),
|
|
23
|
+
key: process.env.PROVIDER_USAGE_API_KEY || process.env.SUB2API_API_KEY || firstString(options, ["apiKey", "api_key"])
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function refreshInterval(options) {
|
|
27
|
+
const configured = Number(options?.refreshMs || process.env.PROVIDER_USAGE_REFRESH_MS);
|
|
28
|
+
return Number.isFinite(configured) && configured >= 0 ? configured : DEFAULT_REFRESH_MS;
|
|
29
|
+
}
|
|
30
|
+
var AgentToolsUsage = async ({ client }, options = {}) => {
|
|
31
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
32
|
+
const refreshedAt = /* @__PURE__ */ new Map();
|
|
33
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
34
|
+
const refreshMs = refreshInterval(options);
|
|
35
|
+
async function showUsage(text) {
|
|
36
|
+
if (!text) return;
|
|
37
|
+
try {
|
|
38
|
+
await client.tui.showToast({
|
|
39
|
+
body: {
|
|
40
|
+
title: "Provider usage",
|
|
41
|
+
message: toastMessage(text),
|
|
42
|
+
variant: "info",
|
|
43
|
+
duration: 8e3
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function refreshSession(sessionID) {
|
|
50
|
+
const context = contexts.get(sessionID);
|
|
51
|
+
if (!context?.baseUrl || !context?.key) return;
|
|
52
|
+
const cacheKey = context.baseUrl.replace(/\/+$/, "");
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
if (now - (refreshedAt.get(cacheKey) || 0) < refreshMs) return;
|
|
55
|
+
if (inFlight.has(cacheKey)) return await inFlight.get(cacheKey);
|
|
56
|
+
refreshedAt.set(cacheKey, now);
|
|
57
|
+
const task = queryProviderUsage(context, {
|
|
58
|
+
agent: "opencode",
|
|
59
|
+
rememberSnapshot: true
|
|
60
|
+
}).then((result) => showUsage(result?.text || "")).catch(() => {
|
|
61
|
+
}).finally(() => inFlight.delete(cacheKey));
|
|
62
|
+
inFlight.set(cacheKey, task);
|
|
63
|
+
return await task;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
"chat.params": async (input) => {
|
|
67
|
+
contexts.set(input.sessionID, providerContext(input));
|
|
68
|
+
},
|
|
69
|
+
event: async ({ event }) => {
|
|
70
|
+
if (event.type === "session.idle") {
|
|
71
|
+
void refreshSession(event.properties.sessionID);
|
|
72
|
+
} else if (event.type === "session.deleted") {
|
|
73
|
+
contexts.delete(event.properties.sessionID);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export {
|
|
79
|
+
AgentToolsUsage
|
|
80
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// integrations/usage/opencode-tui.mjs
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
var AGENT_TOOLS_HOME = process.env.AGENT_TOOLS_HOME || join(homedir(), ".agent-tools");
|
|
6
|
+
var SNAPSHOT_PATH = join(AGENT_TOOLS_HOME, "cache", "usage-snapshot.json");
|
|
7
|
+
function toastMessage(text) {
|
|
8
|
+
return text.replace(/^API \| /, "");
|
|
9
|
+
}
|
|
10
|
+
function latestUsage() {
|
|
11
|
+
try {
|
|
12
|
+
const data = JSON.parse(readFileSync(SNAPSHOT_PATH, "utf8"));
|
|
13
|
+
return Object.values(data?.items || {}).filter((item) => typeof item?.text === "string" && item.text).sort((a, b) => String(b.updatedAt || "").localeCompare(String(a.updatedAt || "")))[0]?.text || "";
|
|
14
|
+
} catch {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
var tui = async (api) => {
|
|
19
|
+
api.keymap.registerLayer({
|
|
20
|
+
commands: [
|
|
21
|
+
{
|
|
22
|
+
name: "agent-tools.usage",
|
|
23
|
+
title: "Provider usage",
|
|
24
|
+
category: "Agent Tools",
|
|
25
|
+
namespace: "palette",
|
|
26
|
+
slashName: "at-usage",
|
|
27
|
+
run() {
|
|
28
|
+
const message = latestUsage();
|
|
29
|
+
api.ui.toast({
|
|
30
|
+
title: "Provider usage",
|
|
31
|
+
message: message ? toastMessage(message) : "Provider usage is not available yet",
|
|
32
|
+
variant: message ? "info" : "warning",
|
|
33
|
+
duration: 8e3
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
var opencode_tui_default = {
|
|
41
|
+
id: "agent-tools-usage",
|
|
42
|
+
tui
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
opencode_tui_default as default
|
|
46
|
+
};
|
|
@@ -14,6 +14,7 @@ import { execFileSync, spawn } from "node:child_process";
|
|
|
14
14
|
import fs from "node:fs";
|
|
15
15
|
import { basename, dirname, join } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
17
18
|
|
|
18
19
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const AGENT_TOOLS_HOME = process.env.AGENT_TOOLS_HOME || join(SCRIPT_DIR, "..", "..");
|
|
@@ -21,8 +22,8 @@ const DEFAULT_CONFIG_FILE = join(AGENT_TOOLS_HOME, "config.jsonc");
|
|
|
21
22
|
const SNAPSHOT_FILE = join(AGENT_TOOLS_HOME, "cache", "usage-snapshot.json");
|
|
22
23
|
const REFRESH_STATE_FILE = join(AGENT_TOOLS_HOME, "cache", "usage-refresh-state.json");
|
|
23
24
|
// Cross-integration dependency: statusline renders provider usage via the
|
|
24
|
-
// usage integration's query engine.
|
|
25
|
-
const USAGE_RUNTIME = join(AGENT_TOOLS_HOME, "
|
|
25
|
+
// usage integration's query engine (built artifact; see scripts/build.mjs).
|
|
26
|
+
const USAGE_RUNTIME = join(AGENT_TOOLS_HOME, "dist", "usage", "core.mjs");
|
|
26
27
|
const DEFAULT_SNAPSHOT_TTL_MS = 60_000;
|
|
27
28
|
const DEFAULT_REFRESH_COOLDOWN_MS = 30_000;
|
|
28
29
|
const DEFAULT_FAILURE_BACKOFF_MS = 120_000;
|
|
@@ -62,47 +63,16 @@ async function readStdin() {
|
|
|
62
63
|
function readJsonFile(file) {
|
|
63
64
|
try {
|
|
64
65
|
if (!fs.existsSync(file)) return {};
|
|
65
|
-
const raw =
|
|
66
|
-
|
|
66
|
+
const raw = fs.readFileSync(file, "utf8").replace(/^\uFEFF/, "");
|
|
67
|
+
if (!raw.trim()) return {};
|
|
68
|
+
const errors = [];
|
|
69
|
+
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
70
|
+
return errors.length === 0 && parsed && typeof parsed === "object" ? parsed : {};
|
|
67
71
|
} catch {
|
|
68
72
|
return {};
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
function stripJsonComments(input) {
|
|
73
|
-
let out = "";
|
|
74
|
-
let inString = false;
|
|
75
|
-
let escaped = false;
|
|
76
|
-
for (let i = 0; i < input.length; i++) {
|
|
77
|
-
const ch = input[i];
|
|
78
|
-
const next = input[i + 1];
|
|
79
|
-
if (inString) {
|
|
80
|
-
out += ch;
|
|
81
|
-
escaped = ch === "\\" ? !escaped : false;
|
|
82
|
-
if (ch === "\"" && !escaped) inString = false;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (ch === "\"") {
|
|
86
|
-
inString = true;
|
|
87
|
-
out += ch;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (ch === "/" && next === "/") {
|
|
91
|
-
while (i < input.length && input[i] !== "\n") i++;
|
|
92
|
-
out += "\n";
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (ch === "/" && next === "*") {
|
|
96
|
-
i += 2;
|
|
97
|
-
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
98
|
-
i++;
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
out += ch;
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
76
|
function parseArgs(argv) {
|
|
107
77
|
const opts = {};
|
|
108
78
|
for (let i = 0; i < argv.length; i++) {
|