micro-models-agent 2.4.0 → 2.8.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.
@@ -0,0 +1,114 @@
1
+ // Pure formatting helpers + tool taxonomy for the `mma web` frontend.
2
+
3
+ import { t } from "./i18n.js";
4
+ import { S } from "./store.js";
5
+
6
+ export const CATEGORIES = {
7
+ recon: [
8
+ "read_file", "list_dir", "glob", "grep", "file_info", "project_map",
9
+ "chunk_query", "web_search", "web_fetch", "history", "recall",
10
+ "process_list", "process_log",
11
+ ],
12
+ mutate: [
13
+ "write_file", "edit_file", "create_dir", "delete_file", "move_file",
14
+ "attach_image",
15
+ ],
16
+ execute: [
17
+ "bash", "process_kill", "pipeline_run", "browser", "download_file",
18
+ "subagent", "mcp_call",
19
+ ],
20
+ meta: [
21
+ "plan", "todo", "verify", "lsp_check", "enable_tools", "set_thinking",
22
+ "load_skill", "remember",
23
+ ],
24
+ };
25
+
26
+ export const FILE_TOOLS = ["read_file", "write_file", "edit_file", "list_dir", "delete_file", "move_file", "file_info"];
27
+ // Tools whose `path` argument points at a readable file the viewer can open.
28
+ // `list_dir` is a directory, `delete_file`/`move_file` leave the file gone.
29
+ export const OPENABLE_TOOLS = ["read_file", "write_file", "edit_file", "file_info"];
30
+ export const IMAGE_TOOLS = ["attach_image", "browser"];
31
+ export const LINK_TOOLS = ["web_fetch", "download_file"];
32
+
33
+ export function categoryOf(name) {
34
+ for (const cat of Object.keys(CATEGORIES)) {
35
+ if (CATEGORIES[cat].indexOf(name) !== -1) return cat;
36
+ }
37
+ return "meta";
38
+ }
39
+
40
+ // Friendly, localized tool label from `tool.friendly.*`; humanize unknown names.
41
+ export function labelOf(name) {
42
+ const key = "tool.friendly." + name;
43
+ const s = t(key);
44
+ if (s !== key) return s;
45
+ const human = String(name).replace(/_/g, " ");
46
+ return human.charAt(0).toUpperCase() + human.slice(1);
47
+ }
48
+
49
+ export function argText(args) {
50
+ if (!args || typeof args !== "object") return "";
51
+ const keys = ["path", "file", "dir", "command", "query", "url", "pattern", "name", "id", "text", "prompt"];
52
+ for (const key of keys) {
53
+ if (typeof args[key] === "string" && args[key]) return args[key];
54
+ }
55
+ const keys2 = Object.keys(args);
56
+ if (!keys2.length) return "";
57
+ const val = args[keys2[0]];
58
+ if (typeof val === "string") return val;
59
+ if (typeof val === "number" || typeof val === "boolean") return String(val);
60
+ try {
61
+ const json = JSON.stringify(args);
62
+ return json === "{}" ? "" : json.slice(0, 120);
63
+ } catch (err) {
64
+ return "";
65
+ }
66
+ }
67
+
68
+ export function formatMs(ms) {
69
+ if (typeof ms !== "number" || !isFinite(ms)) return "";
70
+ return ms >= 1000 ? (ms / 1000).toFixed(1) + "s" : Math.round(ms) + "ms";
71
+ }
72
+
73
+ // Effective chat verbosity, mirroring `resolveVerbosity` in src/ui/theme.ts:
74
+ // explicit `ui.verbosity` wins; the legacy boolean maps to verbose/quiet. A
75
+ // fresh config defaults to `quiet`, so the web feed matches the terminal.
76
+ export function verbosity() {
77
+ const ui = (S.state.config && S.state.config.ui) || {};
78
+ if (ui.verbosity) return ui.verbosity;
79
+ return ui.verbose ? "verbose" : "quiet";
80
+ }
81
+
82
+ // First non-empty line of a tool result — the calm one-line cause shown in
83
+ // quiet mode instead of the whole model-facing body.
84
+ export function firstOutputLine(text) {
85
+ if (typeof text !== "string" || !text) return "";
86
+ for (const raw of text.split("\n")) {
87
+ const line = raw.trim();
88
+ if (line) return line.length > 200 ? line.slice(0, 197) + "…" : line;
89
+ }
90
+ return "";
91
+ }
92
+
93
+ export function relTime(ts) {
94
+ if (!ts) return "";
95
+ const diff = Date.now() - new Date(ts).getTime();
96
+ if (!isFinite(diff) || diff < 0) return "";
97
+ const min = Math.floor(diff / 60000);
98
+ if (min < 1) return "now";
99
+ if (min < 60) return min + "m";
100
+ const hrs = Math.floor(min / 60);
101
+ if (hrs < 24) return hrs + "h";
102
+ const days = Math.floor(hrs / 24);
103
+ return days + "d";
104
+ }
105
+
106
+ export function fmtTokens(n) {
107
+ return n >= 1000 ? (n / 1000).toFixed(1) + "K" : String(n);
108
+ }
109
+ // Compact context-window label for selectors and the model panel.
110
+ export function contextLabel() {
111
+ const n = (S.state.config && S.state.config.contextWindow) || 0;
112
+ return n ? Math.round(n / 1024) + "K" : "—";
113
+ }
114
+
@@ -0,0 +1,46 @@
1
+ // Localisation for the `mma web` frontend. Dictionary comes from the server
2
+ // (`GET /api/i18n`) so the chrome follows `config.locale` without a reload.
3
+
4
+ import { api } from "./api.js";
5
+
6
+ export const I18N = { locale: "en", dicts: {} };
7
+
8
+ export function t(key, params) {
9
+ const dict = I18N.dicts[I18N.locale] || I18N.dicts.en || {};
10
+ const en = I18N.dicts.en || {};
11
+ let template = dict[key];
12
+ if (template === undefined) template = en[key];
13
+ if (template === undefined) return key;
14
+ if (params) {
15
+ Object.keys(params).forEach((k) => {
16
+ template = template.split("{" + k + "}").join(String(params[k]));
17
+ });
18
+ }
19
+ return template;
20
+ }
21
+
22
+ // Plural form for a `{count}` template: uses `baseKey_one|_few|_many`
23
+ // (RU one/few/many; EN one/many) and falls back to `baseKey` if absent.
24
+ export function pluralCount(baseKey, n) {
25
+ let form;
26
+ if (I18N.locale === "ru") {
27
+ const m10 = n % 10;
28
+ const m100 = n % 100;
29
+ if (m10 === 1 && m100 !== 11) form = "one";
30
+ else if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) form = "few";
31
+ else form = "many";
32
+ } else {
33
+ form = n === 1 ? "one" : "many";
34
+ }
35
+ const key = baseKey + "_" + form;
36
+ const s = t(key, { count: n });
37
+ return s === key ? t(baseKey, { count: n }) : s;
38
+ }
39
+
40
+ export async function loadI18n() {
41
+ const r = await api("/api/i18n");
42
+ if (r.ok && r.data) {
43
+ I18N.locale = r.data.locale || "en";
44
+ I18N.dicts = r.data.dicts || {};
45
+ }
46
+ }