terminal-smart-cli 0.97.12 → 0.97.14
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/bin/ts.js +141 -10
- package/lib/agent.js +625 -38
- package/lib/api.js +9 -2
- package/lib/audit-packs.js +56 -0
- package/lib/evolution-telemetry.js +45 -0
- package/lib/i18n.js +2 -0
- package/lib/image-job.js +31 -0
- package/lib/intelligence-core.js +50 -13
- package/lib/meta.js +314 -14
- package/lib/office-editors.js +88 -14
- package/lib/office-readers.js +14 -1
- package/lib/tools.js +93 -19
- package/lib/xlsx-compat-editor.js +78 -2
- package/package.json +3 -3
package/lib/api.js
CHANGED
|
@@ -9,7 +9,10 @@ class ApiError extends Error {
|
|
|
9
9
|
|
|
10
10
|
// ── RESILIÊNCIA a queda de gateway (backend + CDC de IA) ─────────────────────
|
|
11
11
|
// Status HTTP transitórios (vale re-tentar). 402/401/4xx-de-lógica NÃO entram.
|
|
12
|
-
function isRetryableStatus(s) {
|
|
12
|
+
function isRetryableStatus(s) {
|
|
13
|
+
return s === 408 || s === 425 || s === 429 || s === 500 || s === 502 || s === 503 || s === 504
|
|
14
|
+
|| (s >= 520 && s <= 527); // Cloudflare/origin transitórios, incluindo timeout 524
|
|
15
|
+
}
|
|
13
16
|
// Re-tenta fn com backoff exponencial + jitter. NUNCA re-tenta erros de lógica
|
|
14
17
|
// (sem crédito, auth, contexto, 4xx não-transitório). onRetry avisa a UI.
|
|
15
18
|
async function withRetry(fn, { tries = 3, baseMs = 500, onRetry = null, connOnly = false } = {}) {
|
|
@@ -26,7 +29,11 @@ async function withRetry(fn, { tries = 3, baseMs = 500, onRetry = null, connOnly
|
|
|
26
29
|
const fatal = code === 'no_credits' || code === 'auth' || code === 'context'
|
|
27
30
|
|| st === 401 || st === 402 || st === 403 || st === 404 || st === 409 || st === 422
|
|
28
31
|
|| (st >= 400 && st < 500 && !isRetryableStatus(st));
|
|
29
|
-
|
|
32
|
+
// A 524 already consumed the proxy's long origin timeout. One retry is
|
|
33
|
+
// enough; repeated 524s should return control so the model router can
|
|
34
|
+
// switch provider instead of freezing a mission for many minutes.
|
|
35
|
+
const slowGatewayTimeoutExhausted = st === 524 && attempt >= 2;
|
|
36
|
+
if (fatal || !retryable || attempt === tries || slowGatewayTimeoutExhausted) throw e;
|
|
30
37
|
const wait = Math.round(baseMs * Math.pow(2.5, attempt - 1) * (0.75 + Math.random() * 0.5));
|
|
31
38
|
if (onRetry) { try { onRetry({ attempt, tries, waitMs: wait, reason: code || ('http_' + st) }); } catch (_) {} }
|
|
32
39
|
await new Promise(r => setTimeout(r, wait));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const PACKS = Object.freeze({
|
|
6
|
+
web: Object.freeze({
|
|
7
|
+
id: 'web', label: 'Web/App',
|
|
8
|
+
prompt: 'Se a interface for criada ou alterada, prove em navegador real: desktop e mobile sem overflow, fluxo principal completo, validacao de entrada, persistencia apos reload, console/rede sem erros e screenshots. Prefira navegador ja instalado com playwright-core, sem baixar binario. Mantenha um script reproduzivel e AUTOCONTIDO chamado audit:browser (ou audit:web/audit:e2e): ele deve iniciar o servidor quando necessario, aguardar a URL, testar e encerrar somente o processo que iniciou em bloco finally. Rode-o antes de concluir.',
|
|
9
|
+
}),
|
|
10
|
+
office: Object.freeze({
|
|
11
|
+
id: 'office', label: 'Office/Arquivos',
|
|
12
|
+
prompt: 'Edite o arquivo original por padrao; crie copia somente se o usuario pedir. Preserve backup e formato. Em XLSX, agrupe celulas e formulas do mesmo arquivo em UMA chamada editar_planilha. Em DOCX, agrupe todas as mudancas em UMA chamada atomica editar_documento: use substituicoes para texto e alteracoes_tabela com rotulo/valor para celulas de tabela; nunca envie varias chamadas paralelas para o mesmo DOCX. Reabra com o leitor nativo do formato e prove conteudo, estrutura, formulas/tabelas/slides relevantes e caminho final. Hash ou existencia do arquivo nao provam conteudo. Nunca declare uma alteracao bloqueada ou falha como concluida.',
|
|
13
|
+
}),
|
|
14
|
+
productivity: Object.freeze({
|
|
15
|
+
id: 'productivity', label: 'E-mail/Drive',
|
|
16
|
+
prompt: 'Cheque o status da integracao antes de agir. Para leitura, registre ids e filtros usados. Para mutacao, confirme alvo e estado final pela API. Nunca infira destinatario ausente. Crie rascunho por padrao quando o pedido nao autorizar explicitamente envio; envio, exclusao e movimentacao exigem os gates normais.',
|
|
17
|
+
}),
|
|
18
|
+
vps: Object.freeze({
|
|
19
|
+
id: 'vps', label: 'VPS/Infra',
|
|
20
|
+
prompt: 'Antes de mudar, registre status, configuracao e porta afetados; preserve snapshot/backup. Aplique mudanca minima. Depois prove config valida, servico ativo, porta/HTTP esperado e logs sem regressao. Nao use exclusao ampla nem reinicio indiscriminado.',
|
|
21
|
+
}),
|
|
22
|
+
generic: Object.freeze({
|
|
23
|
+
id: 'generic', label: 'Geral',
|
|
24
|
+
prompt: 'Defina criterios executaveis antes de concluir. Prove o fluxo principal e pelo menos uma falha relevante; registre comandos, codigos de saida e artefatos. Nao aceite narrativa como substituto de evidencia.',
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function _has(cwd, name) { try { return fs.existsSync(path.join(cwd || process.cwd(), name)); } catch (_) { return false; } }
|
|
29
|
+
|
|
30
|
+
function selectAuditPack(taskText, cwd) {
|
|
31
|
+
const t = String(taskText || '').toLocaleLowerCase();
|
|
32
|
+
if (/\b(?:gmail|outlook|hotmail|e-?mail|drive|google docs|google sheets|workspace|onedrive|calend[aá]rio|agenda)\b/i.test(t)) return PACKS.productivity;
|
|
33
|
+
if (/\b(?:vps|servidor|ssh|deploy|docker|nginx|traefik|systemd|firewall|porta|produ[cç][aã]o)\b/i.test(t)) return PACKS.vps;
|
|
34
|
+
if (/\b(?:excel|xlsx|planilha|word|docx|powerpoint|pptx|apresenta[cç][aã]o|pdf)\b/i.test(t)) return PACKS.office;
|
|
35
|
+
if (/\b(?:site|p[aá]gina|web|frontend|front-end|interface|navegador|dashboard|painel|html|css|jogo)\b/i.test(t)
|
|
36
|
+
|| _has(cwd, 'index.html') || _has(cwd, 'vite.config.js') || _has(cwd, 'next.config.js')) return PACKS.web;
|
|
37
|
+
return PACKS.generic;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function promptBlock(pack, lang = 'pt') {
|
|
41
|
+
if (!pack) return '';
|
|
42
|
+
const title = lang === 'en' ? 'MANDATORY AUDIT PACK' : 'PACOTE DE AUDITORIA OBRIGATORIO';
|
|
43
|
+
return `\n\n${title} [${pack.id} — ${pack.label}]: ${pack.prompt}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function deriveCriteria(cwd, pack) {
|
|
47
|
+
if (!pack || pack.id !== 'web') return [];
|
|
48
|
+
let pkg = null;
|
|
49
|
+
try { pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')); } catch (_) {}
|
|
50
|
+
const scripts = (pkg && pkg.scripts) || {};
|
|
51
|
+
const names = ['audit:browser', 'audit:web', 'audit:e2e'];
|
|
52
|
+
const picked = names.find(n => scripts[n]);
|
|
53
|
+
return picked ? [{ type: 'command', cmd: `npm run ${picked}`, exit: 0, timeout_s: 300, label: `auditoria real (${picked})` }] : [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { PACKS, selectAuditPack, promptBlock, deriveCriteria };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const core = require('./core');
|
|
5
|
+
|
|
6
|
+
function sanitizePattern(value) {
|
|
7
|
+
return String(core.redactSecrets(value || ''))
|
|
8
|
+
.replace(/[\u0000-\u001f\u007f]/g, ' ')
|
|
9
|
+
.replace(/\b(?:nvapi-|AIza)[A-Za-z0-9._-]{12,}/gi, '<secret>')
|
|
10
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '<email>')
|
|
11
|
+
.replace(/https?:\/\/[^\s"']+/gi, '<url>')
|
|
12
|
+
.replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '<ip>')
|
|
13
|
+
.replace(/[A-Za-z]:\\[^\s"']+/g, '<path>')
|
|
14
|
+
.replace(/\/(?:home|root|opt|var|tmp|Users|mnt)\/[^\s"']+/g, '<path>')
|
|
15
|
+
.replace(/\b[0-9a-f]{32,}\b/gi, '<id>')
|
|
16
|
+
.replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi, '<id>')
|
|
17
|
+
.replace(/\b\d{5,}\b/g, '#')
|
|
18
|
+
.replace(/\s+/g, ' ').trim().slice(0, 300);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildEvents(toolErrors = [], actions = [], context = {}) {
|
|
22
|
+
const recoveredTools = new Set((actions || []).map(action => action && action.name).filter(Boolean));
|
|
23
|
+
const seen = new Set(); const events = [];
|
|
24
|
+
for (const error of (Array.isArray(toolErrors) ? toolErrors : []).slice(-50)) {
|
|
25
|
+
const tool = String(error.tool || 'unknown').slice(0, 80);
|
|
26
|
+
const errorClass = String(error.errorClass || error.class || 'unknown').slice(0, 80);
|
|
27
|
+
const pattern = sanitizePattern(error.evidence || error.message || '');
|
|
28
|
+
const stage = String(error.stage || 'execution').slice(0, 40);
|
|
29
|
+
const fingerprint = crypto.createHash('sha256').update([tool, errorClass, stage, pattern].join('|')).digest('hex');
|
|
30
|
+
if (seen.has(fingerprint)) continue;
|
|
31
|
+
seen.add(fingerprint);
|
|
32
|
+
events.push({ tool, errorClass, pattern, stage, fingerprint,
|
|
33
|
+
retryable: error.retryable === true, recovered: recoveredTools.has(tool),
|
|
34
|
+
surface: context.surface || 'cli', appVersion: context.appVersion || 'unknown', model: context.model || 'unknown' });
|
|
35
|
+
if (events.length >= 20) break;
|
|
36
|
+
}
|
|
37
|
+
return events;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function enabled(config = {}, env = process.env) {
|
|
41
|
+
if (String(env.TS_DIAGNOSTICS || '').toLowerCase() === '0' || String(env.TS_DIAGNOSTICS || '').toLowerCase() === 'off') return false;
|
|
42
|
+
return config.diagnosticsEnabled !== false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { sanitizePattern, buildEvents, enabled };
|
package/lib/i18n.js
CHANGED
|
@@ -78,6 +78,7 @@ const STR = {
|
|
|
78
78
|
['ts quem', 'conta conectada'],
|
|
79
79
|
['ts conta exportar', 'baixa seus dados sem senhas, tokens ou chaves privadas'],
|
|
80
80
|
['ts conta abrir', 'abre Minha Conta para privacidade e exclusão segura'],
|
|
81
|
+
['ts diagnosticos status', 'controla telemetria sanitizada de erros (ativar/desativar)'],
|
|
81
82
|
['ts doctor', 'diagnóstico do ambiente (Node, login, gateway, versão, deps opcionais)'],
|
|
82
83
|
['ts privacidade', 'mostra o endereço da Política de Privacidade'],
|
|
83
84
|
['ts tema', 'paleta do terminal (7 temas; a cor indica o ESTADO)'],
|
|
@@ -247,6 +248,7 @@ const STR = {
|
|
|
247
248
|
['ts quem', 'connected account'],
|
|
248
249
|
['ts account export', 'download your data without passwords, tokens or private keys'],
|
|
249
250
|
['ts account open', 'open Account for privacy controls and safe deletion'],
|
|
251
|
+
['ts diagnostics status', 'controls sanitized error telemetry (on/off)'],
|
|
250
252
|
['ts privacy', 'shows the Privacy Policy address'],
|
|
251
253
|
['ts tema', 'terminal palette (7 themes; color means STATE)'],
|
|
252
254
|
['ts idioma pt|en', 'language (default pt-BR)'],
|
package/lib/image-job.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function imageJobError(message, code) {
|
|
4
|
+
const error = new Error(message);
|
|
5
|
+
error.code = code;
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function waitForImageJob(jobId, options = {}) {
|
|
10
|
+
const request = options.request;
|
|
11
|
+
if (typeof request !== 'function') throw new TypeError('request é obrigatório');
|
|
12
|
+
const sleep = options.sleep || (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
|
13
|
+
const intervalMs = Math.max(0, Number(options.intervalMs) || 2000);
|
|
14
|
+
const maxAttempts = Math.max(1, Number(options.maxAttempts) || 180);
|
|
15
|
+
const id = String(jobId || '').trim();
|
|
16
|
+
if (!id) throw imageJobError('Job de imagem inválido.', 'image_job_invalid');
|
|
17
|
+
|
|
18
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
19
|
+
if (attempt > 1) await sleep(intervalMs);
|
|
20
|
+
const job = await request('/api/ia/image/jobs/' + encodeURIComponent(id));
|
|
21
|
+
if (job && job.status === 'completed' && job.url) return job;
|
|
22
|
+
if (job && job.status === 'failed') throw imageJobError(job.message || 'Falha ao gerar imagem Premium.', 'image_job_failed');
|
|
23
|
+
if (job && !['processing', 'queued'].includes(job.status)) {
|
|
24
|
+
throw imageJobError(job.message || `Estado inesperado da geração: ${job.status || 'vazio'}.`, 'image_job_protocol');
|
|
25
|
+
}
|
|
26
|
+
if (typeof options.onProgress === 'function') options.onProgress({ attempt, maxAttempts, job });
|
|
27
|
+
}
|
|
28
|
+
throw imageJobError('A imagem continua sendo processada. Consulte a conversa no Terminal Smart para acompanhar.', 'image_job_timeout');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { waitForImageJob, imageJobError };
|
package/lib/intelligence-core.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
|
|
11
11
|
const INTELLIGENCE_CONTRACT = 1;
|
|
12
|
-
|
|
12
|
+
// Keep this machine-readable. Human/provider notes belong in commit history;
|
|
13
|
+
// catalogFreshness appends an ISO time before parsing this value.
|
|
14
|
+
const PRICE_REVISION = '2026-08-11';
|
|
13
15
|
|
|
14
16
|
const MODEL_CATALOG = Object.freeze({
|
|
15
17
|
smart: {
|
|
@@ -28,7 +30,7 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
28
30
|
},
|
|
29
31
|
'deepseek-v4-pro': {
|
|
30
32
|
upstreamId: 'deepseek/deepseek-v4-pro',
|
|
31
|
-
provider: 'deepseek', contextWindow: 1048576, price: { input: 0.
|
|
33
|
+
provider: 'deepseek', contextWindow: 1048576, price: { input: 0.63168, output: 1.26336, cachedInput: 0.063168 },
|
|
32
34
|
capabilities: ['tools', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.95, quality: 0.93,
|
|
33
35
|
},
|
|
34
36
|
'gemini-2.5-flash-lite': {
|
|
@@ -63,7 +65,7 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
63
65
|
},
|
|
64
66
|
'glm-5.2': {
|
|
65
67
|
upstreamId: 'z-ai/glm-5.2',
|
|
66
|
-
provider: 'zai', contextWindow:
|
|
68
|
+
provider: 'zai', contextWindow: 1048576, price: { input: 0.76, output: 2.42, cachedInput: 0.152 },
|
|
67
69
|
capabilities: ['tools', 'planning', 'code', 'long-context'], toolReliability: 0.88, quality: 0.89,
|
|
68
70
|
},
|
|
69
71
|
'mimo-v2.5': {
|
|
@@ -83,19 +85,24 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
83
85
|
},
|
|
84
86
|
'gpt-5.6-terra': {
|
|
85
87
|
upstreamId: 'openai/gpt-5.6-terra',
|
|
86
|
-
provider: 'openai', contextWindow:
|
|
88
|
+
provider: 'openai', contextWindow: 1050000, price: { input: 1.00, output: 6.00, cachedInput: 0.10 },
|
|
87
89
|
capabilities: ['tools', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.96, quality: 0.95,
|
|
88
90
|
},
|
|
89
91
|
'gpt-5.6-luna': {
|
|
90
92
|
upstreamId: 'openai/gpt-5.6-luna',
|
|
91
|
-
provider: 'openai', contextWindow:
|
|
92
|
-
capabilities: ['tools', 'code', 'planning', 'execution', 'summarization', 'review', 'long-context'], toolReliability: 0.93, quality: 0.89,
|
|
93
|
+
provider: 'openai', contextWindow: 1050000, price: { input: 0.10, output: 0.60, cachedInput: 0.01 },
|
|
94
|
+
capabilities: ['tools', 'code', 'planning', 'execution', 'summarization', 'review', 'vision', 'long-context'], toolReliability: 0.93, quality: 0.89,
|
|
93
95
|
},
|
|
94
96
|
'claude-sonnet-5': {
|
|
95
97
|
upstreamId: 'anthropic/claude-sonnet-5',
|
|
96
98
|
provider: 'anthropic', contextWindow: 1000000, price: { input: 2.00, output: 10.00, cachedInput: 0.20 },
|
|
97
99
|
capabilities: ['tools', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.97, quality: 0.96,
|
|
98
100
|
},
|
|
101
|
+
'claude-opus-5': {
|
|
102
|
+
upstreamId: 'anthropic/claude-opus-5',
|
|
103
|
+
provider: 'anthropic', contextWindow: 1000000, price: { input: 5.00, output: 25.00, cachedInput: 0.50 },
|
|
104
|
+
capabilities: ['tools', 'vision', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.98, quality: 0.99,
|
|
105
|
+
},
|
|
99
106
|
'claude-sonnet-4-6': {
|
|
100
107
|
upstreamId: 'anthropic/claude-sonnet-4.6',
|
|
101
108
|
provider: 'anthropic', contextWindow: 1000000, price: { input: 3.00, output: 15.00, cachedInput: 0.30 },
|
|
@@ -126,8 +133,33 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
126
133
|
},
|
|
127
134
|
'laguna-s-2.1': {
|
|
128
135
|
upstreamId: 'poolside/laguna-s-2.1',
|
|
129
|
-
provider: 'openrouter', contextWindow: 1048576, price: { input: 0.
|
|
130
|
-
capabilities: ['code', 'review', 'long-context'], toolReliability: 0.
|
|
136
|
+
provider: 'openrouter', contextWindow: 1048576, price: { input: 0.09, output: 0.18, cachedInput: 0.009 },
|
|
137
|
+
capabilities: ['tools', 'code', 'review', 'long-context'], toolReliability: 0.80, quality: 0.78,
|
|
138
|
+
},
|
|
139
|
+
'qwen3.8-max': {
|
|
140
|
+
upstreamId: 'qwen/qwen3.8-max',
|
|
141
|
+
provider: 'openrouter', contextWindow: 1000000, price: { input: 2.00, output: 6.00, cachedInput: 0.20 },
|
|
142
|
+
capabilities: ['tools', 'vision', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.91, quality: 0.93,
|
|
143
|
+
},
|
|
144
|
+
'gemini-3.6-flash': {
|
|
145
|
+
upstreamId: 'google/gemini-3.6-flash',
|
|
146
|
+
provider: 'openrouter', contextWindow: 1048576, price: { input: 1.50, output: 7.50, cachedInput: 0.15 },
|
|
147
|
+
capabilities: ['tools', 'vision', 'classification', 'planning', 'review', 'long-context'], toolReliability: 0.92, quality: 0.92,
|
|
148
|
+
},
|
|
149
|
+
'muse-spark-1.2': {
|
|
150
|
+
upstreamId: 'meta/muse-spark-1.2',
|
|
151
|
+
provider: 'meta', contextWindow: 1048576, price: { input: 1.25, output: 4.25, cachedInput: 0.125 },
|
|
152
|
+
capabilities: ['tools', 'vision', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.95, quality: 0.95,
|
|
153
|
+
},
|
|
154
|
+
'muse-glimmer-30b': {
|
|
155
|
+
upstreamId: 'meta/muse-glimmer-30b',
|
|
156
|
+
provider: 'openrouter', contextWindow: 131072, price: { input: 0.35, output: 1.50, cachedInput: 0.035 },
|
|
157
|
+
capabilities: ['tools', 'vision', 'planning', 'review'], toolReliability: 0.88, quality: 0.86,
|
|
158
|
+
},
|
|
159
|
+
'solar-pro4': {
|
|
160
|
+
upstreamId: 'upstage/solar-pro4',
|
|
161
|
+
provider: 'openrouter', contextWindow: 524288, price: { input: 0.03, output: 0.12, cachedInput: 0.003 },
|
|
162
|
+
capabilities: ['tools', 'code', 'review', 'long-context'], toolReliability: 0.82, quality: 0.78,
|
|
131
163
|
},
|
|
132
164
|
'gpt-oss-120b': {
|
|
133
165
|
upstreamId: 'openai/gpt-oss-120b',
|
|
@@ -165,11 +197,11 @@ const AGENT_ROLES = Object.freeze({
|
|
|
165
197
|
}),
|
|
166
198
|
executor: Object.freeze({
|
|
167
199
|
prompt: 'Execute somente a etapa recebida com as ferramentas autorizadas. Não declare sucesso sem resultado verificável.',
|
|
168
|
-
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-flash', 'deepseek-v4-pro'], pro: ['deepseek-v4-flash', 'deepseek-v4-pro', 'claude-haiku-4-5'] }),
|
|
200
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-flash', 'deepseek-v4-pro'], pro: ['deepseek-v4-flash', 'deepseek-v4-pro', 'glm-5.2', 'claude-haiku-4-5'] }),
|
|
169
201
|
}),
|
|
170
202
|
inspector: Object.freeze({
|
|
171
203
|
prompt: 'Inspecione em modo somente leitura e compare evidências com os critérios de aceite. Não altere nada e não elogie por cortesia.',
|
|
172
|
-
models: Object.freeze({ free: ['gemini-2.5-flash-lite'], basic: ['gemini-2.5-flash', 'gpt-4o-mini'], pro: ['claude-haiku-4-5', 'gemini-2.5-flash'
|
|
204
|
+
models: Object.freeze({ free: ['gemini-2.5-flash-lite'], basic: ['gemini-2.5-flash', 'gpt-4o-mini'], pro: ['gpt-5.6-luna', 'claude-haiku-4-5', 'gemini-2.5-flash'] }),
|
|
173
205
|
}),
|
|
174
206
|
corrector: Object.freeze({
|
|
175
207
|
prompt: 'Corrija apenas falhas confirmadas pelo inspetor, preserve o que funciona e repita as provas afetadas. Não amplie o escopo.',
|
|
@@ -177,7 +209,7 @@ const AGENT_ROLES = Object.freeze({
|
|
|
177
209
|
}),
|
|
178
210
|
vision: Object.freeze({
|
|
179
211
|
prompt: 'Extraia fatos observáveis da imagem e separe leitura, inferência e incerteza. Não invente texto ilegível.',
|
|
180
|
-
models: Object.freeze({ free: ['smart'], basic: ['gemini-2.5-flash'], pro: ['gemini-2.5-flash', 'claude-haiku-4-5'] }),
|
|
212
|
+
models: Object.freeze({ free: ['smart'], basic: ['gemini-2.5-flash'], pro: ['gpt-5.6-luna', 'gemini-2.5-flash', 'claude-haiku-4-5'] }),
|
|
181
213
|
}),
|
|
182
214
|
document: Object.freeze({
|
|
183
215
|
prompt: 'Produza conteúdo estruturado e use o motor nativo do formato solicitado. Preserve editabilidade e valide o arquivo gerado.',
|
|
@@ -205,13 +237,18 @@ function clamp(n, min, max) {
|
|
|
205
237
|
|
|
206
238
|
function normalizeModelId(model) {
|
|
207
239
|
const raw = String(model || 'smart').trim();
|
|
208
|
-
|
|
240
|
+
// O prefixo e uma instrucao de transporte do CDC, nao parte da identidade/preco.
|
|
241
|
+
// Sem remove-lo, todo modelo OpenRouter caia no preco `_default`: Luna era
|
|
242
|
+
// sobrecobrado e Opus subcobrado, com risco direto de margem negativa.
|
|
243
|
+
const transportFree = raw.replace(/^openrouter:/i, '');
|
|
244
|
+
const lower = transportFree.toLowerCase();
|
|
209
245
|
if (MODEL_CATALOG[raw]) return raw;
|
|
246
|
+
if (MODEL_CATALOG[transportFree]) return transportFree;
|
|
210
247
|
if (MODEL_CATALOG[lower]) return lower;
|
|
211
248
|
if (MODEL_ALIASES[lower]) return MODEL_ALIASES[lower];
|
|
212
249
|
const upstream = Object.entries(MODEL_CATALOG)
|
|
213
250
|
.find(([, info]) => String(info.upstreamId || '').toLowerCase() === lower);
|
|
214
|
-
return upstream ? upstream[0] :
|
|
251
|
+
return upstream ? upstream[0] : transportFree;
|
|
215
252
|
}
|
|
216
253
|
|
|
217
254
|
function modelInfo(model, now) {
|