@jpkme/diff-review 0.1.12 → 0.1.13
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 +1 -1
- package/lib/acp-policy.mjs +99 -12
- package/lib/acp-policy.test.mjs +56 -3
- package/lib/acp-shell.mjs +38 -8
- package/lib/acp-shell.test.mjs +32 -1
- package/lib/agent-login.mjs +105 -105
- package/lib/check-report.mjs +1 -0
- package/lib/check-report.test.mjs +1 -0
- package/lib/extract-cache.test.mjs +162 -162
- package/lib/model-label.mjs +263 -263
- package/lib/model-label.test.mjs +77 -77
- package/lib/models-cache.mjs +75 -75
- package/lib/models-cache.test.mjs +50 -50
- package/lib/review-prep.mjs +63 -63
- package/package.json +1 -1
- package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css → 0.BKRG1f36.css} +1 -1
- package/web/build/client/_app/immutable/assets/0.BKRG1f36.css.br +0 -0
- package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css.gz → 0.BKRG1f36.css.gz} +0 -0
- package/web/build/client/_app/immutable/assets/2.tqrMRTM0.css +1 -0
- package/web/build/client/_app/immutable/assets/2.tqrMRTM0.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.tqrMRTM0.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css +1 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.DqYH0w6R.css +1 -0
- package/web/build/client/_app/immutable/assets/storage.DqYH0w6R.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.DqYH0w6R.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/BXUWMim4.js +213 -0
- package/web/build/client/_app/immutable/chunks/BXUWMim4.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/BXUWMim4.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{CPyWH418.js → Bu1mm6Oz.js} +1 -1
- package/web/build/client/_app/immutable/chunks/Bu1mm6Oz.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/Bu1mm6Oz.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{B5ByWcPE.js → DPfXzQnm.js} +2 -2
- package/web/build/client/_app/immutable/chunks/DPfXzQnm.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/DPfXzQnm.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/{app.BiBohpyO.js → app.CTgiuiU1.js} +2 -2
- package/web/build/client/_app/immutable/entry/app.CTgiuiU1.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.CTgiuiU1.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CFSl0kht.js +1 -0
- package/web/build/client/_app/immutable/entry/start.CFSl0kht.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.CFSl0kht.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/{1.BzPXaJJj.js → 1.0PQZpSbJ.js} +1 -1
- package/web/build/client/_app/immutable/nodes/1.0PQZpSbJ.js.br +2 -0
- package/web/build/client/_app/immutable/nodes/1.0PQZpSbJ.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.DQaoQoy4.js +10 -0
- package/web/build/client/_app/immutable/nodes/2.DQaoQoy4.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.DQaoQoy4.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.AciugOtL.js +171 -0
- package/web/build/client/_app/immutable/nodes/3.AciugOtL.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.AciugOtL.js.gz +0 -0
- package/web/build/client/_app/version.json +1 -1
- package/web/build/client/_app/version.json.br +0 -0
- package/web/build/client/_app/version.json.gz +0 -0
- package/web/build/handler.js +3 -3
- package/web/build/index.js +3 -3
- package/web/build/server/chunks/{0-BYok4D0n.js → 0-DUStDOsr.js} +3 -3
- package/web/build/server/chunks/{0-BYok4D0n.js.map → 0-DUStDOsr.js.map} +1 -1
- package/web/build/server/chunks/1-C2qlInYk.js +7 -0
- package/web/build/server/chunks/{1-C7zcB0q0.js.map → 1-C2qlInYk.js.map} +1 -1
- package/web/build/server/chunks/2-DyE177MK.js +7 -0
- package/web/build/server/chunks/2-DyE177MK.js.map +1 -0
- package/web/build/server/chunks/3-SO3atuWO.js +7 -0
- package/web/build/server/chunks/{3-DAAmG6VX.js.map → 3-SO3atuWO.js.map} +1 -1
- package/web/build/server/chunks/{_server.ts-qYVrMYPB.js → _server.ts-CRNhzL2a.js} +3 -3
- package/web/build/server/chunks/{_server.ts-qYVrMYPB.js.map → _server.ts-CRNhzL2a.js.map} +1 -1
- package/web/build/server/chunks/{agent-sessions-DSqGavSn.js → agent-sessions-kcUaLhK5.js} +171 -40
- package/web/build/server/chunks/agent-sessions-kcUaLhK5.js.map +1 -0
- package/web/build/server/chunks/{git-review-CpO_G8z8.js → git-review-CXKcjYgZ.js} +2 -2
- package/web/build/server/chunks/{git-review-CpO_G8z8.js.map → git-review-CXKcjYgZ.js.map} +1 -1
- package/web/build/server/chunks/{handler-Di1wJQuU.js → handler-_E0NucKN.js} +3 -3
- package/web/build/server/chunks/{handler-Di1wJQuU.js.map → handler-_E0NucKN.js.map} +1 -1
- package/web/build/server/chunks/{hooks.server-BLOHobEK.js → hooks.server-D7yPODoc.js} +2 -2
- package/web/build/server/chunks/{hooks.server-BLOHobEK.js.map → hooks.server-D7yPODoc.js.map} +1 -1
- package/web/build/server/chunks/{index.js-yLYmrAWy.js → index.js-lMtDM8ZQ.js} +3 -3
- package/web/build/server/chunks/{index.js-yLYmrAWy.js.map → index.js-lMtDM8ZQ.js.map} +1 -1
- package/web/build/server/chunks/{manifest.js-CXj8IIo3.js → manifest.js-BIm6jot5.js} +9 -9
- package/web/build/server/chunks/{manifest.js-CXj8IIo3.js.map → manifest.js-BIm6jot5.js.map} +1 -1
- package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js → remote-1g4ayag-DmqxwUIz.js} +3 -3
- package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js.map → remote-1g4ayag-DmqxwUIz.js.map} +1 -1
- package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js → remote-wh6t6j-DZk0W9Pp.js} +4 -4
- package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js.map → remote-wh6t6j-DZk0W9Pp.js.map} +1 -1
- package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css +0 -1
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css +0 -1
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css +0 -1
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CPyWH418.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CPyWH418.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js +0 -210
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js +0 -1
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.br +0 -2
- package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js +0 -10
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js +0 -171
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.gz +0 -0
- package/web/build/server/chunks/1-C7zcB0q0.js +0 -7
- package/web/build/server/chunks/2-v6ejI_0-.js +0 -7
- package/web/build/server/chunks/2-v6ejI_0-.js.map +0 -1
- package/web/build/server/chunks/3-DAAmG6VX.js +0 -7
- package/web/build/server/chunks/agent-sessions-DSqGavSn.js.map +0 -1
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js → 0.Dj97JW46.js} +0 -0
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.br → 0.Dj97JW46.js.br} +0 -0
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.gz → 0.Dj97JW46.js.gz} +0 -0
package/lib/model-label.mjs
CHANGED
|
@@ -1,263 +1,263 @@
|
|
|
1
|
-
/** Compara nombres de modelo ignorando mayúsculas, espacios y puntuación. */
|
|
2
|
-
export function modelSlug(s) {
|
|
3
|
-
return String(s || "")
|
|
4
|
-
.toLowerCase()
|
|
5
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
6
|
-
.replace(/^-+|-+$/g, "");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Parece un id técnico (claude-opus-5-5) más que un nombre legible. */
|
|
10
|
-
function isSlugLike(s) {
|
|
11
|
-
const t = String(s || "").trim();
|
|
12
|
-
if (!t) return true;
|
|
13
|
-
if (/\s/.test(t)) return false;
|
|
14
|
-
if (/[A-Z]/.test(t) && /[a-z]/.test(t) && !t.includes("-")) return false; // "Sonnet", "Auto"
|
|
15
|
-
return /^[a-z0-9][a-z0-9._|-]*$/i.test(t) && (t.includes("-") || t.includes(".") || t.includes("["));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* claude-opus-5-5 → Claude Opus 5.5; gpt-5.6-sol → GPT-5.6 Sol; gemini-3.8-flash → Gemini 3.8 Flash
|
|
20
|
-
* @param {string} id
|
|
21
|
-
*/
|
|
22
|
-
export function humanizeModelId(id) {
|
|
23
|
-
const base = String(id || "")
|
|
24
|
-
.trim()
|
|
25
|
-
.replace(/\[.*\]$/, "");
|
|
26
|
-
if (!base) return "";
|
|
27
|
-
const parts = base.split(/[-_]/).filter(Boolean);
|
|
28
|
-
const out = [];
|
|
29
|
-
for (let i = 0; i < parts.length; i++) {
|
|
30
|
-
const p = parts[i];
|
|
31
|
-
const lower = p.toLowerCase();
|
|
32
|
-
if (lower === "gpt" || lower === "gpt5") {
|
|
33
|
-
out.push(lower === "gpt5" ? "GPT-5" : "GPT");
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (lower === "claude" || lower === "gemini" || lower === "grok" || lower === "composer" || lower === "muse") {
|
|
37
|
-
out.push(lower.charAt(0).toUpperCase() + lower.slice(1));
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (lower === "sonnet" || lower === "opus" || lower === "haiku" || lower === "flash" || lower === "codex") {
|
|
41
|
-
out.push(lower.charAt(0).toUpperCase() + lower.slice(1));
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
// 5-5 → 5.5 (versión mayor-menor)
|
|
45
|
-
if (/^\d+$/.test(p) && i + 1 < parts.length && /^\d+$/.test(parts[i + 1])) {
|
|
46
|
-
out.push(`${p}.${parts[i + 1]}`);
|
|
47
|
-
i++;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (/^\d+(\.\d+)?$/.test(p)) {
|
|
51
|
-
out.push(p);
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
out.push(p.charAt(0).toUpperCase() + p.slice(1).toLowerCase());
|
|
55
|
-
}
|
|
56
|
-
return out.join(" ");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* context=300k,effort=medium,fast=false → ["300k", "medium"]
|
|
61
|
-
* @param {string} raw
|
|
62
|
-
* @returns {string[]}
|
|
63
|
-
*/
|
|
64
|
-
export function compactModelMeta(raw) {
|
|
65
|
-
const s = String(raw || "").trim();
|
|
66
|
-
if (!s) return [];
|
|
67
|
-
/** @type {string[]} */
|
|
68
|
-
const badges = [];
|
|
69
|
-
for (const piece of s.split(",")) {
|
|
70
|
-
const part = piece.trim();
|
|
71
|
-
if (!part) continue;
|
|
72
|
-
const eq = part.indexOf("=");
|
|
73
|
-
if (eq < 0) {
|
|
74
|
-
// bare token: "1m", "1M"
|
|
75
|
-
badges.push(part);
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
const key = part.slice(0, eq).trim().toLowerCase();
|
|
79
|
-
const val = part.slice(eq + 1).trim();
|
|
80
|
-
if (!val || val === "false" || val === "0") continue;
|
|
81
|
-
if (key === "fast" || key === "thinking") {
|
|
82
|
-
if (val === "true" || val === "1") badges.push(key);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (key === "context" || key === "ctx") {
|
|
86
|
-
badges.push(val);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (key === "effort" || key === "reasoning_effort" || key === "reasoning") {
|
|
90
|
-
badges.push(val);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
badges.push(val);
|
|
94
|
-
}
|
|
95
|
-
return badges;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @param {string} name
|
|
100
|
-
* @param {string} value
|
|
101
|
-
* @returns {{ title: string, badges: string[], label: string, hint?: string }}
|
|
102
|
-
*/
|
|
103
|
-
export function formatModelOption(name, value) {
|
|
104
|
-
const n = String(name ?? "").trim();
|
|
105
|
-
const v = String(value ?? "").trim();
|
|
106
|
-
if (!v && !n) return { title: "", badges: [], label: "" };
|
|
107
|
-
if (/^default(\[\])?$/i.test(v) && n) {
|
|
108
|
-
return { title: n, badges: [], label: n };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const bracket = /^(.+?)\[(.*)\]$/.exec(v);
|
|
112
|
-
const base = bracket ? bracket[1].trim() : v;
|
|
113
|
-
const metaRaw = bracket ? bracket[2].trim() : "";
|
|
114
|
-
let badges = compactModelMeta(metaRaw);
|
|
115
|
-
|
|
116
|
-
let title;
|
|
117
|
-
if (n && !isSlugLike(n)) {
|
|
118
|
-
title = n;
|
|
119
|
-
} else if (n && modelSlug(n) === modelSlug(base)) {
|
|
120
|
-
title = isSlugLike(n) ? humanizeModelId(base || n) : n;
|
|
121
|
-
} else if (n && isSlugLike(n)) {
|
|
122
|
-
title = humanizeModelId(base || n);
|
|
123
|
-
} else if (n) {
|
|
124
|
-
title = n;
|
|
125
|
-
} else {
|
|
126
|
-
title = humanizeModelId(base) || base || v;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Si el título ya menciona el contexto (p. ej. "Sonnet (1M context)"), no repetir el badge "1m".
|
|
130
|
-
if (badges.length && /\(\s*\d+\s*m\b/i.test(title)) {
|
|
131
|
-
badges = badges.filter((b) => !/^\d+m$/i.test(b));
|
|
132
|
-
}
|
|
133
|
-
const titleSlug = modelSlug(title);
|
|
134
|
-
if (badges.length && titleSlug.includes(modelSlug(badges.join("")))) {
|
|
135
|
-
/* keep badges anyway for effort/fast */
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const hint = badges.length ? `[ ${badges.join(", ")} ]` : undefined;
|
|
139
|
-
const label = hint ? `${title} · ${hint}` : title;
|
|
140
|
-
return { title, badges, label, ...(hint ? { hint } : {}) };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Etiqueta compacta (compat / un solo string).
|
|
145
|
-
* @param {string} name
|
|
146
|
-
* @param {string} value
|
|
147
|
-
*/
|
|
148
|
-
export function modelOptionLabel(name, value) {
|
|
149
|
-
return formatModelOption(name, value).label || String(name || value || "").trim();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Etiqueta del ítem "usar el default del agente".
|
|
154
|
-
* @param {{ value: string, name: string } | undefined} current
|
|
155
|
-
* @param {string} [configuredOptionLabel]
|
|
156
|
-
*/
|
|
157
|
-
export function defaultModelOptionLabel(current, configuredOptionLabel) {
|
|
158
|
-
const name = String(current?.name ?? "").trim();
|
|
159
|
-
const value = String(current?.value ?? "").trim();
|
|
160
|
-
const generic =
|
|
161
|
-
!name ||
|
|
162
|
-
value === "default" ||
|
|
163
|
-
value === "auto" ||
|
|
164
|
-
/^default(\b|\s|\()/i.test(name) ||
|
|
165
|
-
/^auto(\b|\s|\()/i.test(name) ||
|
|
166
|
-
/\(recommended\)/i.test(name);
|
|
167
|
-
|
|
168
|
-
if (generic) {
|
|
169
|
-
const configured = String(configuredOptionLabel ?? "").trim();
|
|
170
|
-
if (configured && configured !== "Por defecto del agente") return configured;
|
|
171
|
-
return "Por defecto del agente";
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const { title, hint } = formatModelOption(name, value);
|
|
175
|
-
return hint ? `Por defecto · ${title} · ${hint}` : `Por defecto · ${title}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Orden de la lista: default (`""`) y `auto` primero, resto A→Z por label.
|
|
180
|
-
* @template {{ value: string, label?: string, title?: string }} T
|
|
181
|
-
* @param {T[]} options
|
|
182
|
-
* @returns {T[]}
|
|
183
|
-
*/
|
|
184
|
-
export function sortModelSelectOptions(options) {
|
|
185
|
-
const list = Array.isArray(options) ? [...options] : [];
|
|
186
|
-
|
|
187
|
-
/** @param {{ value: string, label?: string, title?: string }} o */
|
|
188
|
-
const isAuto = (o) => {
|
|
189
|
-
const v = String(o.value ?? "").toLowerCase();
|
|
190
|
-
if (v === "auto" || v.startsWith("auto[")) return true;
|
|
191
|
-
const t = String(o.title || o.label || "").trim();
|
|
192
|
-
return /^auto\b/i.test(t);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const defaults = list.filter((o) => o.value === "");
|
|
196
|
-
const autos = list.filter((o) => o.value !== "" && isAuto(o));
|
|
197
|
-
const middle = list.filter((o) => o.value !== "" && !isAuto(o));
|
|
198
|
-
|
|
199
|
-
/** @param {{ label?: string, title?: string, value: string }} a */
|
|
200
|
-
/** @param {{ label?: string, title?: string, value: string }} b */
|
|
201
|
-
const byLabel = (a, b) =>
|
|
202
|
-
String(a.label || a.title || a.value).localeCompare(String(b.label || b.title || b.value), "es", {
|
|
203
|
-
sensitivity: "base",
|
|
204
|
-
numeric: true,
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
autos.sort(byLabel);
|
|
208
|
-
middle.sort(byLabel);
|
|
209
|
-
return [...defaults, ...autos, ...middle];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Misma lista que la UI: default + aliases fijos + modelos ACP.
|
|
214
|
-
* Clack: `label` = título amigable, `hint` = `[ 300k, medium ]`.
|
|
215
|
-
* UI: también `title` + `badges` para chips.
|
|
216
|
-
*
|
|
217
|
-
* @param {{ models: { value: string, name: string }[], currentModel: string }} live
|
|
218
|
-
* @param {{ options: { value: string, label: string, hint?: string }[], initialValue?: string }} fixed
|
|
219
|
-
* @returns {{ options: { value: string, label: string, hint?: string, title?: string, badges?: string[] }[], initialValue: string }}
|
|
220
|
-
*/
|
|
221
|
-
export function buildAcpModelSelectOptions(live, fixed) {
|
|
222
|
-
const fixedOptions = fixed?.options ?? [];
|
|
223
|
-
const fallback = fixedOptions.filter((m) => m.value !== "");
|
|
224
|
-
const configuredLabel = fixedOptions.find((m) => m.value === "")?.label;
|
|
225
|
-
const current = live.models.find((m) => m.value === live.currentModel);
|
|
226
|
-
const seen = new Set(["", "default"]);
|
|
227
|
-
/** @type {{ value: string, label: string, hint?: string, title?: string, badges?: string[] }[]} */
|
|
228
|
-
const options = [
|
|
229
|
-
{
|
|
230
|
-
value: "",
|
|
231
|
-
label: defaultModelOptionLabel(current, configuredLabel),
|
|
232
|
-
title: defaultModelOptionLabel(current, configuredLabel),
|
|
233
|
-
badges: [],
|
|
234
|
-
},
|
|
235
|
-
];
|
|
236
|
-
|
|
237
|
-
for (const m of fallback) {
|
|
238
|
-
if (seen.has(m.value)) continue;
|
|
239
|
-
seen.add(m.value);
|
|
240
|
-
options.push({
|
|
241
|
-
value: m.value,
|
|
242
|
-
label: m.label,
|
|
243
|
-
title: m.label,
|
|
244
|
-
badges: [],
|
|
245
|
-
...(m.hint ? { hint: m.hint } : {}),
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
for (const m of live.models) {
|
|
250
|
-
if (seen.has(m.value)) continue;
|
|
251
|
-
seen.add(m.value);
|
|
252
|
-
const formatted = formatModelOption(m.name, m.value);
|
|
253
|
-
options.push({
|
|
254
|
-
value: m.value,
|
|
255
|
-
label: formatted.title,
|
|
256
|
-
title: formatted.title,
|
|
257
|
-
badges: formatted.badges,
|
|
258
|
-
...(formatted.hint ? { hint: formatted.hint } : {}),
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return { options: sortModelSelectOptions(options), initialValue: "" };
|
|
263
|
-
}
|
|
1
|
+
/** Compara nombres de modelo ignorando mayúsculas, espacios y puntuación. */
|
|
2
|
+
export function modelSlug(s) {
|
|
3
|
+
return String(s || "")
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
6
|
+
.replace(/^-+|-+$/g, "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Parece un id técnico (claude-opus-5-5) más que un nombre legible. */
|
|
10
|
+
function isSlugLike(s) {
|
|
11
|
+
const t = String(s || "").trim();
|
|
12
|
+
if (!t) return true;
|
|
13
|
+
if (/\s/.test(t)) return false;
|
|
14
|
+
if (/[A-Z]/.test(t) && /[a-z]/.test(t) && !t.includes("-")) return false; // "Sonnet", "Auto"
|
|
15
|
+
return /^[a-z0-9][a-z0-9._|-]*$/i.test(t) && (t.includes("-") || t.includes(".") || t.includes("["));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* claude-opus-5-5 → Claude Opus 5.5; gpt-5.6-sol → GPT-5.6 Sol; gemini-3.8-flash → Gemini 3.8 Flash
|
|
20
|
+
* @param {string} id
|
|
21
|
+
*/
|
|
22
|
+
export function humanizeModelId(id) {
|
|
23
|
+
const base = String(id || "")
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/\[.*\]$/, "");
|
|
26
|
+
if (!base) return "";
|
|
27
|
+
const parts = base.split(/[-_]/).filter(Boolean);
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 0; i < parts.length; i++) {
|
|
30
|
+
const p = parts[i];
|
|
31
|
+
const lower = p.toLowerCase();
|
|
32
|
+
if (lower === "gpt" || lower === "gpt5") {
|
|
33
|
+
out.push(lower === "gpt5" ? "GPT-5" : "GPT");
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (lower === "claude" || lower === "gemini" || lower === "grok" || lower === "composer" || lower === "muse") {
|
|
37
|
+
out.push(lower.charAt(0).toUpperCase() + lower.slice(1));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (lower === "sonnet" || lower === "opus" || lower === "haiku" || lower === "flash" || lower === "codex") {
|
|
41
|
+
out.push(lower.charAt(0).toUpperCase() + lower.slice(1));
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// 5-5 → 5.5 (versión mayor-menor)
|
|
45
|
+
if (/^\d+$/.test(p) && i + 1 < parts.length && /^\d+$/.test(parts[i + 1])) {
|
|
46
|
+
out.push(`${p}.${parts[i + 1]}`);
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (/^\d+(\.\d+)?$/.test(p)) {
|
|
51
|
+
out.push(p);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
out.push(p.charAt(0).toUpperCase() + p.slice(1).toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
return out.join(" ");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* context=300k,effort=medium,fast=false → ["300k", "medium"]
|
|
61
|
+
* @param {string} raw
|
|
62
|
+
* @returns {string[]}
|
|
63
|
+
*/
|
|
64
|
+
export function compactModelMeta(raw) {
|
|
65
|
+
const s = String(raw || "").trim();
|
|
66
|
+
if (!s) return [];
|
|
67
|
+
/** @type {string[]} */
|
|
68
|
+
const badges = [];
|
|
69
|
+
for (const piece of s.split(",")) {
|
|
70
|
+
const part = piece.trim();
|
|
71
|
+
if (!part) continue;
|
|
72
|
+
const eq = part.indexOf("=");
|
|
73
|
+
if (eq < 0) {
|
|
74
|
+
// bare token: "1m", "1M"
|
|
75
|
+
badges.push(part);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const key = part.slice(0, eq).trim().toLowerCase();
|
|
79
|
+
const val = part.slice(eq + 1).trim();
|
|
80
|
+
if (!val || val === "false" || val === "0") continue;
|
|
81
|
+
if (key === "fast" || key === "thinking") {
|
|
82
|
+
if (val === "true" || val === "1") badges.push(key);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (key === "context" || key === "ctx") {
|
|
86
|
+
badges.push(val);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (key === "effort" || key === "reasoning_effort" || key === "reasoning") {
|
|
90
|
+
badges.push(val);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
badges.push(val);
|
|
94
|
+
}
|
|
95
|
+
return badges;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string} name
|
|
100
|
+
* @param {string} value
|
|
101
|
+
* @returns {{ title: string, badges: string[], label: string, hint?: string }}
|
|
102
|
+
*/
|
|
103
|
+
export function formatModelOption(name, value) {
|
|
104
|
+
const n = String(name ?? "").trim();
|
|
105
|
+
const v = String(value ?? "").trim();
|
|
106
|
+
if (!v && !n) return { title: "", badges: [], label: "" };
|
|
107
|
+
if (/^default(\[\])?$/i.test(v) && n) {
|
|
108
|
+
return { title: n, badges: [], label: n };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const bracket = /^(.+?)\[(.*)\]$/.exec(v);
|
|
112
|
+
const base = bracket ? bracket[1].trim() : v;
|
|
113
|
+
const metaRaw = bracket ? bracket[2].trim() : "";
|
|
114
|
+
let badges = compactModelMeta(metaRaw);
|
|
115
|
+
|
|
116
|
+
let title;
|
|
117
|
+
if (n && !isSlugLike(n)) {
|
|
118
|
+
title = n;
|
|
119
|
+
} else if (n && modelSlug(n) === modelSlug(base)) {
|
|
120
|
+
title = isSlugLike(n) ? humanizeModelId(base || n) : n;
|
|
121
|
+
} else if (n && isSlugLike(n)) {
|
|
122
|
+
title = humanizeModelId(base || n);
|
|
123
|
+
} else if (n) {
|
|
124
|
+
title = n;
|
|
125
|
+
} else {
|
|
126
|
+
title = humanizeModelId(base) || base || v;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Si el título ya menciona el contexto (p. ej. "Sonnet (1M context)"), no repetir el badge "1m".
|
|
130
|
+
if (badges.length && /\(\s*\d+\s*m\b/i.test(title)) {
|
|
131
|
+
badges = badges.filter((b) => !/^\d+m$/i.test(b));
|
|
132
|
+
}
|
|
133
|
+
const titleSlug = modelSlug(title);
|
|
134
|
+
if (badges.length && titleSlug.includes(modelSlug(badges.join("")))) {
|
|
135
|
+
/* keep badges anyway for effort/fast */
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const hint = badges.length ? `[ ${badges.join(", ")} ]` : undefined;
|
|
139
|
+
const label = hint ? `${title} · ${hint}` : title;
|
|
140
|
+
return { title, badges, label, ...(hint ? { hint } : {}) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Etiqueta compacta (compat / un solo string).
|
|
145
|
+
* @param {string} name
|
|
146
|
+
* @param {string} value
|
|
147
|
+
*/
|
|
148
|
+
export function modelOptionLabel(name, value) {
|
|
149
|
+
return formatModelOption(name, value).label || String(name || value || "").trim();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Etiqueta del ítem "usar el default del agente".
|
|
154
|
+
* @param {{ value: string, name: string } | undefined} current
|
|
155
|
+
* @param {string} [configuredOptionLabel]
|
|
156
|
+
*/
|
|
157
|
+
export function defaultModelOptionLabel(current, configuredOptionLabel) {
|
|
158
|
+
const name = String(current?.name ?? "").trim();
|
|
159
|
+
const value = String(current?.value ?? "").trim();
|
|
160
|
+
const generic =
|
|
161
|
+
!name ||
|
|
162
|
+
value === "default" ||
|
|
163
|
+
value === "auto" ||
|
|
164
|
+
/^default(\b|\s|\()/i.test(name) ||
|
|
165
|
+
/^auto(\b|\s|\()/i.test(name) ||
|
|
166
|
+
/\(recommended\)/i.test(name);
|
|
167
|
+
|
|
168
|
+
if (generic) {
|
|
169
|
+
const configured = String(configuredOptionLabel ?? "").trim();
|
|
170
|
+
if (configured && configured !== "Por defecto del agente") return configured;
|
|
171
|
+
return "Por defecto del agente";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { title, hint } = formatModelOption(name, value);
|
|
175
|
+
return hint ? `Por defecto · ${title} · ${hint}` : `Por defecto · ${title}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Orden de la lista: default (`""`) y `auto` primero, resto A→Z por label.
|
|
180
|
+
* @template {{ value: string, label?: string, title?: string }} T
|
|
181
|
+
* @param {T[]} options
|
|
182
|
+
* @returns {T[]}
|
|
183
|
+
*/
|
|
184
|
+
export function sortModelSelectOptions(options) {
|
|
185
|
+
const list = Array.isArray(options) ? [...options] : [];
|
|
186
|
+
|
|
187
|
+
/** @param {{ value: string, label?: string, title?: string }} o */
|
|
188
|
+
const isAuto = (o) => {
|
|
189
|
+
const v = String(o.value ?? "").toLowerCase();
|
|
190
|
+
if (v === "auto" || v.startsWith("auto[")) return true;
|
|
191
|
+
const t = String(o.title || o.label || "").trim();
|
|
192
|
+
return /^auto\b/i.test(t);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const defaults = list.filter((o) => o.value === "");
|
|
196
|
+
const autos = list.filter((o) => o.value !== "" && isAuto(o));
|
|
197
|
+
const middle = list.filter((o) => o.value !== "" && !isAuto(o));
|
|
198
|
+
|
|
199
|
+
/** @param {{ label?: string, title?: string, value: string }} a */
|
|
200
|
+
/** @param {{ label?: string, title?: string, value: string }} b */
|
|
201
|
+
const byLabel = (a, b) =>
|
|
202
|
+
String(a.label || a.title || a.value).localeCompare(String(b.label || b.title || b.value), "es", {
|
|
203
|
+
sensitivity: "base",
|
|
204
|
+
numeric: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
autos.sort(byLabel);
|
|
208
|
+
middle.sort(byLabel);
|
|
209
|
+
return [...defaults, ...autos, ...middle];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Misma lista que la UI: default + aliases fijos + modelos ACP.
|
|
214
|
+
* Clack: `label` = título amigable, `hint` = `[ 300k, medium ]`.
|
|
215
|
+
* UI: también `title` + `badges` para chips.
|
|
216
|
+
*
|
|
217
|
+
* @param {{ models: { value: string, name: string }[], currentModel: string }} live
|
|
218
|
+
* @param {{ options: { value: string, label: string, hint?: string }[], initialValue?: string }} fixed
|
|
219
|
+
* @returns {{ options: { value: string, label: string, hint?: string, title?: string, badges?: string[] }[], initialValue: string }}
|
|
220
|
+
*/
|
|
221
|
+
export function buildAcpModelSelectOptions(live, fixed) {
|
|
222
|
+
const fixedOptions = fixed?.options ?? [];
|
|
223
|
+
const fallback = fixedOptions.filter((m) => m.value !== "");
|
|
224
|
+
const configuredLabel = fixedOptions.find((m) => m.value === "")?.label;
|
|
225
|
+
const current = live.models.find((m) => m.value === live.currentModel);
|
|
226
|
+
const seen = new Set(["", "default"]);
|
|
227
|
+
/** @type {{ value: string, label: string, hint?: string, title?: string, badges?: string[] }[]} */
|
|
228
|
+
const options = [
|
|
229
|
+
{
|
|
230
|
+
value: "",
|
|
231
|
+
label: defaultModelOptionLabel(current, configuredLabel),
|
|
232
|
+
title: defaultModelOptionLabel(current, configuredLabel),
|
|
233
|
+
badges: [],
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
for (const m of fallback) {
|
|
238
|
+
if (seen.has(m.value)) continue;
|
|
239
|
+
seen.add(m.value);
|
|
240
|
+
options.push({
|
|
241
|
+
value: m.value,
|
|
242
|
+
label: m.label,
|
|
243
|
+
title: m.label,
|
|
244
|
+
badges: [],
|
|
245
|
+
...(m.hint ? { hint: m.hint } : {}),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const m of live.models) {
|
|
250
|
+
if (seen.has(m.value)) continue;
|
|
251
|
+
seen.add(m.value);
|
|
252
|
+
const formatted = formatModelOption(m.name, m.value);
|
|
253
|
+
options.push({
|
|
254
|
+
value: m.value,
|
|
255
|
+
label: formatted.title,
|
|
256
|
+
title: formatted.title,
|
|
257
|
+
badges: formatted.badges,
|
|
258
|
+
...(formatted.hint ? { hint: formatted.hint } : {}),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { options: sortModelSelectOptions(options), initialValue: "" };
|
|
263
|
+
}
|