@trazum/core 1.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.
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/advisories.d.ts +23 -0
- package/dist/advisories.d.ts.map +1 -0
- package/dist/advisories.js +376 -0
- package/dist/advisories.js.map +1 -0
- package/dist/aws-sigv4.d.ts +88 -0
- package/dist/aws-sigv4.d.ts.map +1 -0
- package/dist/aws-sigv4.js +117 -0
- package/dist/aws-sigv4.js.map +1 -0
- package/dist/baseline.d.ts +171 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +273 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +28 -0
- package/dist/cache.js.map +1 -0
- package/dist/changes.d.ts +29 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +142 -0
- package/dist/changes.js.map +1 -0
- package/dist/compare.d.ts +65 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +58 -0
- package/dist/compare.js.map +1 -0
- package/dist/config-schema.d.ts +118 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +315 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +112 -0
- package/dist/config.js.map +1 -0
- package/dist/detect.d.ts +70 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +228 -0
- package/dist/detect.js.map +1 -0
- package/dist/evaluate.d.ts +98 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +110 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/extract.d.ts +81 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +280 -0
- package/dist/extract.js.map +1 -0
- package/dist/gcp-auth.d.ts +58 -0
- package/dist/gcp-auth.d.ts.map +1 -0
- package/dist/gcp-auth.js +113 -0
- package/dist/gcp-auth.js.map +1 -0
- package/dist/glob.d.ts +49 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +154 -0
- package/dist/glob.js.map +1 -0
- package/dist/host.d.ts +30 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +69 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n/en.d.ts +4 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +168 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/es.d.ts +4 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +168 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/index.d.ts +36 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +50 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/types.d.ts +180 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +11 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +226 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +485 -0
- package/dist/llm.js.map +1 -0
- package/dist/nearest.d.ts +20 -0
- package/dist/nearest.d.ts.map +1 -0
- package/dist/nearest.js +54 -0
- package/dist/nearest.js.map +1 -0
- package/dist/net.d.ts +90 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/net.js +203 -0
- package/dist/net.js.map +1 -0
- package/dist/node.d.ts +32 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +33 -0
- package/dist/node.js.map +1 -0
- package/dist/openrouter.d.ts +25 -0
- package/dist/openrouter.d.ts.map +1 -0
- package/dist/openrouter.js +72 -0
- package/dist/openrouter.js.map +1 -0
- package/dist/optimize.d.ts +38 -0
- package/dist/optimize.d.ts.map +1 -0
- package/dist/optimize.js +183 -0
- package/dist/optimize.js.map +1 -0
- package/dist/otlp.d.ts +91 -0
- package/dist/otlp.d.ts.map +1 -0
- package/dist/otlp.js +102 -0
- package/dist/otlp.js.map +1 -0
- package/dist/phrases.d.ts +169 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +939 -0
- package/dist/phrases.js.map +1 -0
- package/dist/pricing-overlay.d.ts +55 -0
- package/dist/pricing-overlay.d.ts.map +1 -0
- package/dist/pricing-overlay.js +241 -0
- package/dist/pricing-overlay.js.map +1 -0
- package/dist/pricing.d.ts +115 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +400 -0
- package/dist/pricing.js.map +1 -0
- package/dist/profile.d.ts +71 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +55 -0
- package/dist/profile.js.map +1 -0
- package/dist/promptfoo.d.ts +58 -0
- package/dist/promptfoo.d.ts.map +1 -0
- package/dist/promptfoo.js +149 -0
- package/dist/promptfoo.js.map +1 -0
- package/dist/prune.d.ts +91 -0
- package/dist/prune.d.ts.map +1 -0
- package/dist/prune.js +110 -0
- package/dist/prune.js.map +1 -0
- package/dist/reorder.d.ts +82 -0
- package/dist/reorder.d.ts.map +1 -0
- package/dist/reorder.js +215 -0
- package/dist/reorder.js.map +1 -0
- package/dist/review.d.ts +54 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +131 -0
- package/dist/review.js.map +1 -0
- package/dist/rules.d.ts +5 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +279 -0
- package/dist/rules.js.map +1 -0
- package/dist/savings.d.ts +36 -0
- package/dist/savings.d.ts.map +1 -0
- package/dist/savings.js +83 -0
- package/dist/savings.js.map +1 -0
- package/dist/segment.d.ts +8 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/segment.js +74 -0
- package/dist/segment.js.map +1 -0
- package/dist/shared-prefix.d.ts +63 -0
- package/dist/shared-prefix.d.ts.map +1 -0
- package/dist/shared-prefix.js +151 -0
- package/dist/shared-prefix.js.map +1 -0
- package/dist/similarity.d.ts +13 -0
- package/dist/similarity.d.ts.map +1 -0
- package/dist/similarity.js +30 -0
- package/dist/similarity.js.map +1 -0
- package/dist/structure.d.ts +144 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +455 -0
- package/dist/structure.js.map +1 -0
- package/dist/suggest.d.ts +100 -0
- package/dist/suggest.d.ts.map +1 -0
- package/dist/suggest.js +151 -0
- package/dist/suggest.js.map +1 -0
- package/dist/tokenizer.d.ts +57 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/tokenizer.js +157 -0
- package/dist/tokenizer.js.map +1 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/walk.d.ts +40 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +95 -0
- package/dist/walk.js.map +1 -0
- package/package.json +56 -0
- package/src/advisories.ts +431 -0
- package/src/aws-sigv4.ts +174 -0
- package/src/baseline.ts +390 -0
- package/src/cache.ts +54 -0
- package/src/changes.ts +158 -0
- package/src/compare.ts +131 -0
- package/src/config-schema.ts +451 -0
- package/src/config.ts +161 -0
- package/src/detect.ts +312 -0
- package/src/evaluate.ts +188 -0
- package/src/extract.ts +336 -0
- package/src/gcp-auth.ts +166 -0
- package/src/glob.ts +160 -0
- package/src/host.ts +90 -0
- package/src/i18n/en.ts +236 -0
- package/src/i18n/es.ts +236 -0
- package/src/i18n/index.ts +68 -0
- package/src/i18n/types.ts +230 -0
- package/src/index.ts +228 -0
- package/src/llm.ts +708 -0
- package/src/nearest.ts +61 -0
- package/src/net.ts +233 -0
- package/src/node.ts +63 -0
- package/src/openrouter.ts +125 -0
- package/src/optimize.ts +228 -0
- package/src/otlp.ts +179 -0
- package/src/phrases.ts +1047 -0
- package/src/pricing-overlay.ts +319 -0
- package/src/pricing.ts +468 -0
- package/src/profile.ts +124 -0
- package/src/promptfoo.ts +213 -0
- package/src/prune.ts +211 -0
- package/src/reorder.ts +307 -0
- package/src/review.ts +180 -0
- package/src/rules.ts +324 -0
- package/src/savings.ts +121 -0
- package/src/segment.ts +106 -0
- package/src/shared-prefix.ts +198 -0
- package/src/similarity.ts +28 -0
- package/src/structure.ts +652 -0
- package/src/suggest.ts +254 -0
- package/src/tokenizer.ts +190 -0
- package/src/types.ts +323 -0
- package/src/walk.ts +117 -0
package/src/host.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tool Trazum is running inside.
|
|
3
|
+
*
|
|
4
|
+
* On the Node entry point rather than the browser-safe one, because it reads the
|
|
5
|
+
* process environment. Nothing about the optimisation changes with the answer —
|
|
6
|
+
* the same prompt optimises the same way everywhere, as it must, and that is a
|
|
7
|
+
* standing constraint rather than a coincidence.
|
|
8
|
+
*
|
|
9
|
+
* What the answer earns is the right to stop quoting a monthly dollar saving to
|
|
10
|
+
* somebody on a flat-rate plan. Inside Cursor or a Claude Code subscription the
|
|
11
|
+
* per-call saving is real arithmetic about tokens and **not money anybody gets
|
|
12
|
+
* back**, and "$184/month" is wrong in the direction that matters most.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface HostEnvironment {
|
|
16
|
+
id: string;
|
|
17
|
+
displayName: string;
|
|
18
|
+
/**
|
|
19
|
+
* How the host bills.
|
|
20
|
+
*
|
|
21
|
+
* `unknown` is honest rather than lazy: a VS Code terminal says nothing about
|
|
22
|
+
* whether the prompts written in it go to a metered API or a subscription, and
|
|
23
|
+
* guessing either way would be worse than saying so.
|
|
24
|
+
*/
|
|
25
|
+
billing: 'per-token' | 'subscription' | 'unknown';
|
|
26
|
+
/** The variable that gave it away, so the answer is checkable rather than magic. */
|
|
27
|
+
evidence: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hosts recognised by an environment variable they set.
|
|
32
|
+
*
|
|
33
|
+
* A miss is harmless: it falls through to `terminal` and nothing changes. That
|
|
34
|
+
* asymmetry is why the list can include entries verified only by documentation
|
|
35
|
+
* rather than by running under them — the cost of a wrong row is a detection
|
|
36
|
+
* that never fires, not a wrong report.
|
|
37
|
+
*
|
|
38
|
+
* Ordered most-specific first. Cursor is a VS Code fork and sets
|
|
39
|
+
* `TERM_PROGRAM=vscode` as well, so a VS Code row above it would swallow every
|
|
40
|
+
* Cursor session.
|
|
41
|
+
*/
|
|
42
|
+
const HOSTS: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
displayName: string;
|
|
45
|
+
billing: HostEnvironment['billing'];
|
|
46
|
+
test: (env: NodeJS.ProcessEnv) => string | null;
|
|
47
|
+
}> = [
|
|
48
|
+
{
|
|
49
|
+
id: 'claude-code',
|
|
50
|
+
displayName: 'Claude Code',
|
|
51
|
+
billing: 'subscription',
|
|
52
|
+
test: (e) => (e.CLAUDECODE ? 'CLAUDECODE' : null),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'codex',
|
|
56
|
+
displayName: 'Codex',
|
|
57
|
+
billing: 'subscription',
|
|
58
|
+
test: (e) => (e.CODEX_SANDBOX ? 'CODEX_SANDBOX' : e.CODEX_HOME ? 'CODEX_HOME' : null),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'cursor',
|
|
62
|
+
displayName: 'Cursor',
|
|
63
|
+
billing: 'subscription',
|
|
64
|
+
test: (e) => (e.CURSOR_TRACE_ID ? 'CURSOR_TRACE_ID' : null),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'github-actions',
|
|
68
|
+
displayName: 'GitHub Actions',
|
|
69
|
+
billing: 'per-token',
|
|
70
|
+
test: (e) => (e.GITHUB_ACTIONS ? 'GITHUB_ACTIONS' : null),
|
|
71
|
+
},
|
|
72
|
+
{ id: 'ci', displayName: 'CI', billing: 'per-token', test: (e) => (e.CI ? 'CI' : null) },
|
|
73
|
+
{
|
|
74
|
+
id: 'vscode',
|
|
75
|
+
displayName: 'VS Code',
|
|
76
|
+
billing: 'unknown',
|
|
77
|
+
test: (e) => (e.TERM_PROGRAM === 'vscode' ? 'TERM_PROGRAM' : null),
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/** The host, or a plain terminal when nothing identifies itself. */
|
|
82
|
+
export function detectHost(env: NodeJS.ProcessEnv = process.env): HostEnvironment {
|
|
83
|
+
for (const host of HOSTS) {
|
|
84
|
+
const evidence = host.test(env);
|
|
85
|
+
if (evidence !== null) {
|
|
86
|
+
return { id: host.id, displayName: host.displayName, billing: host.billing, evidence };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { id: 'terminal', displayName: 'terminal', billing: 'unknown', evidence: null };
|
|
90
|
+
}
|
package/src/i18n/en.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { CoreMessages } from './types.js';
|
|
2
|
+
|
|
3
|
+
const n = (value: number): string => value.toLocaleString('en-US');
|
|
4
|
+
|
|
5
|
+
/** English catalogue. This is the source of truth every other locale mirrors. */
|
|
6
|
+
export const en: CoreMessages = {
|
|
7
|
+
locale: 'en',
|
|
8
|
+
numberLocale: 'en-US',
|
|
9
|
+
|
|
10
|
+
rules: {
|
|
11
|
+
'duplicate-blocks': {
|
|
12
|
+
title: 'Repeated paragraphs',
|
|
13
|
+
rationale:
|
|
14
|
+
'Removes whole paragraphs that already appear earlier in the prompt. Common when prompts are assembled by concatenating templates: the same block of instructions goes in twice and is paid for twice.',
|
|
15
|
+
},
|
|
16
|
+
'near-duplicate-blocks': {
|
|
17
|
+
title: 'Near-identical paragraphs',
|
|
18
|
+
rationale:
|
|
19
|
+
'Removes paragraphs sharing 92% or more of their words with an earlier one, catching instructions reworded to say the same thing twice. Aggressive level: read the diff, because the 8% that differs may be exactly the nuance you cared about.',
|
|
20
|
+
},
|
|
21
|
+
'duplicate-lines': {
|
|
22
|
+
title: 'Repeated lines',
|
|
23
|
+
rationale:
|
|
24
|
+
'Removes lines identical to one that appeared earlier, ignoring case, accents and punctuation. Only touches lines of 25 characters or more, so bullets and legitimate separators survive.',
|
|
25
|
+
},
|
|
26
|
+
'verbose-phrases': {
|
|
27
|
+
title: 'Wordy phrasing',
|
|
28
|
+
rationale:
|
|
29
|
+
'Replaces long constructions with their short equivalent ("in order to" → "to", "con el fin de" → "para"). The meaning is identical; only the token count changes.',
|
|
30
|
+
},
|
|
31
|
+
politeness: {
|
|
32
|
+
title: 'Politeness formulas',
|
|
33
|
+
rationale:
|
|
34
|
+
'Strips "please", "thank you", "kindly", "por favor"... The model does not answer better for being asked nicely, and every formula is paid for on every call.',
|
|
35
|
+
},
|
|
36
|
+
filler: {
|
|
37
|
+
title: 'Filler and throat-clearing',
|
|
38
|
+
rationale:
|
|
39
|
+
'Removes empty openers such as "basically", "it is important to note that" or "cabe destacar que", which carry no instruction.',
|
|
40
|
+
},
|
|
41
|
+
hedges: {
|
|
42
|
+
title: 'Hedging',
|
|
43
|
+
rationale:
|
|
44
|
+
'Removes "I think", "in my opinion", "creo que". Inside an instruction they weaken the request without adding information.',
|
|
45
|
+
},
|
|
46
|
+
intensifiers: {
|
|
47
|
+
title: 'Intensifiers',
|
|
48
|
+
rationale:
|
|
49
|
+
'Strips "very", "really", "extremely", "muy"... They rarely change the task. Aggressive level because in a specific prompt the nuance can matter.',
|
|
50
|
+
},
|
|
51
|
+
'self-check': {
|
|
52
|
+
title: 'Self-verification instructions',
|
|
53
|
+
rationale:
|
|
54
|
+
'Removes "double-check your work", "verifica tu respuesta". Current models already verify their own output; asking explicitly triggers extra steps paid for in output tokens. Disable this rule if your workflow depends on that verification.',
|
|
55
|
+
},
|
|
56
|
+
emphasis: {
|
|
57
|
+
title: 'Shouted emphasis',
|
|
58
|
+
rationale:
|
|
59
|
+
'Lowercases shouted words (MUST, NEVER, CRITICAL) and drops prefixes like "IMPORTANT:". Uppercase splits into more tokens than lowercase, and on current models excessive emphasis makes the instruction over-trigger.',
|
|
60
|
+
},
|
|
61
|
+
decoration: {
|
|
62
|
+
title: 'Decorative separators',
|
|
63
|
+
rationale:
|
|
64
|
+
'Removes lines made only of repeated characters (====, ----, ****) and runs of exclamation marks. They add no structure the model uses and cost tokens on every call.',
|
|
65
|
+
},
|
|
66
|
+
whitespace: {
|
|
67
|
+
title: 'Stray whitespace',
|
|
68
|
+
rationale:
|
|
69
|
+
'Trims trailing spaces, collapses repeated spaces inside a line and reduces consecutive blank lines to one. Leading indentation is preserved so lists and nested markdown keep working.',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
llm: {
|
|
74
|
+
emptyResponse: () => 'The model returned an empty response.',
|
|
75
|
+
protectedContentAltered: (count) =>
|
|
76
|
+
`The model altered ${count} protected fragment(s) (code, URL or template placeholder). Discarded so the prompt is not broken.`,
|
|
77
|
+
notShorter: (after, before) =>
|
|
78
|
+
`The result is not shorter (${after} vs ${before} tokens). Keeping the deterministic version.`,
|
|
79
|
+
suspiciousShrink: (retainedPct) =>
|
|
80
|
+
`The result keeps only ${retainedPct}% of the tokens. That looks like a summary rather than compression: review it by hand before using it.`,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
suggest: {
|
|
84
|
+
'not-found': () =>
|
|
85
|
+
'the quoted phrase is not in the prompt — the model paraphrased what it was copying',
|
|
86
|
+
'touches-protected': () =>
|
|
87
|
+
'it would edit code, a URL, a placeholder or a tag, which are copied verbatim',
|
|
88
|
+
'introduces-protected': () => 'the replacement adds a placeholder or URL that was not there',
|
|
89
|
+
'no-saving': () => 'it is no shorter than what it replaces',
|
|
90
|
+
overlaps: () => 'it shares text with a suggestion already accepted',
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
advisories: {
|
|
94
|
+
contextOverflow: ({ tokens, modelName, contextWindow }) => ({
|
|
95
|
+
title: 'The prompt does not fit in the context window',
|
|
96
|
+
detail: `The optimised prompt is ~${n(tokens)} tokens and ${modelName} accepts ${n(contextWindow)}. The call will fail: split the content or move to a model with a larger window.`,
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
promptCaching: ({
|
|
100
|
+
placeholder,
|
|
101
|
+
prefixTokens,
|
|
102
|
+
totalTokens,
|
|
103
|
+
minTokens,
|
|
104
|
+
modelName,
|
|
105
|
+
hitRatePct,
|
|
106
|
+
readPct,
|
|
107
|
+
writePct,
|
|
108
|
+
explicit,
|
|
109
|
+
}) => {
|
|
110
|
+
const scope = placeholder
|
|
111
|
+
? `The stable prefix — everything before the first placeholder ${placeholder} — is ~${n(prefixTokens)} of the prompt's ${n(totalTokens)} tokens, and clears ${modelName}'s ${n(minTokens)}-token cacheable minimum.`
|
|
112
|
+
: `The prompt has no variable placeholders, so the whole thing is a cacheable prefix and it clears ${modelName}'s ${n(minTokens)}-token minimum.`;
|
|
113
|
+
const how = explicit
|
|
114
|
+
? 'Put the cache marker at the end of the stable prefix: any byte that changes before the cut invalidates everything after it.'
|
|
115
|
+
: `${modelName} caches automatically above its minimum, so there is nothing to set — but the same rule applies: any byte that changes before the cut invalidates everything after it.`;
|
|
116
|
+
return {
|
|
117
|
+
title: 'Turn on prompt caching for the stable prefix',
|
|
118
|
+
detail: `${scope} At a ${hitRatePct}% hit rate, a cache read costs ${readPct}% of the input price and a write costs ${writePct}%. ${how}`,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
promptCachingNotWorthIt: () => ({
|
|
123
|
+
title: 'At that hit rate, caching does not pay off',
|
|
124
|
+
detail:
|
|
125
|
+
'A cache write costs 125% of the input price and a read costs 10%. Below roughly a 28% hit rate you pay more than you save. Either raise prefix reuse or leave caching off.',
|
|
126
|
+
}),
|
|
127
|
+
|
|
128
|
+
belowCacheMinimum: ({
|
|
129
|
+
modelName,
|
|
130
|
+
minTokens,
|
|
131
|
+
placeholder,
|
|
132
|
+
prefixTokens,
|
|
133
|
+
totalTokens,
|
|
134
|
+
mentionLowerMinimum,
|
|
135
|
+
}) => {
|
|
136
|
+
const reason = placeholder
|
|
137
|
+
? `here the first variable placeholder (${placeholder}) shows up at ~${n(prefixTokens)} tokens and only what precedes it can be cached`
|
|
138
|
+
: `this prompt has ~${n(totalTokens)}`;
|
|
139
|
+
return {
|
|
140
|
+
title: 'Below the cacheable minimum',
|
|
141
|
+
detail:
|
|
142
|
+
`${modelName} needs at least ${n(minTokens)} prefix tokens to cache; ${reason}. Setting cache_control will not error, it simply will not cache.` +
|
|
143
|
+
(mentionLowerMinimum
|
|
144
|
+
? ' Claude Opus 5 lowers that minimum to 512 tokens, so short prompts that miss here would cache there.'
|
|
145
|
+
: ''),
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
cachePrefixReorder: ({ staticTokensAfter, sharePct, placeholder }) => ({
|
|
150
|
+
title: 'Move the stable instructions ahead of the first placeholder',
|
|
151
|
+
detail: `About ~${n(staticTokensAfter)} tokens of stable content (${sharePct}% of the prompt) sit after the first variable placeholder ${placeholder}, so today they never get cached. Reorder the template — fixed instructions and context first, placeholders last — and that content starts being read from cache at 10% of the price. Check that reordering does not change what the prompt asks for.`,
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
batchApi: () => ({
|
|
155
|
+
title: 'If the work tolerates latency, use the Batch API',
|
|
156
|
+
detail:
|
|
157
|
+
'The Batch API applies a 50% discount on both input and output. Most batches finish in under an hour, with a 24-hour maximum. It fits bulk classification, data enrichment or evaluations: anything that is not answering a user in real time.',
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
modelDowngrade: ({ modelName, tier, candidateName, currentUsd, candidateUsd }) => ({
|
|
161
|
+
title: `This task may not need ${modelName}`,
|
|
162
|
+
detail: `By length and vocabulary the prompt looks like "${tier}" complexity. On ${candidateName} you would go from ${currentUsd} to ${candidateUsd} per month. This is a keyword heuristic, not a judgement about answer quality: measure the difference with your own evaluations before switching in production.`,
|
|
163
|
+
}),
|
|
164
|
+
|
|
165
|
+
outputDominated: ({ outputUsd, inputUsd }) => ({
|
|
166
|
+
title: 'Your cost is in the output, not the prompt',
|
|
167
|
+
detail: `Output accounts for ${outputUsd} per month against ${inputUsd} of input. Shortening the prompt has a low ceiling here. The two controls that move the needle are the effort parameter (lower it when the task is not reasoning-heavy) and explicitly asking for concise answers.`,
|
|
168
|
+
}),
|
|
169
|
+
|
|
170
|
+
promoPricing: ({ modelName, promoInput, promoOutput, until, listInput, listOutput }) => ({
|
|
171
|
+
title: 'You are costing this with promotional pricing',
|
|
172
|
+
detail: `${modelName} has introductory pricing of ${promoInput}/${promoOutput} per million tokens until ${until}. After that date it moves to ${listInput}/${listOutput}: your bill goes up even if you change nothing.`,
|
|
173
|
+
}),
|
|
174
|
+
|
|
175
|
+
contradictoryInstructions: ({
|
|
176
|
+
axis,
|
|
177
|
+
firstValue,
|
|
178
|
+
firstSnippet,
|
|
179
|
+
secondValue,
|
|
180
|
+
secondSnippet,
|
|
181
|
+
otherCount,
|
|
182
|
+
}) => ({
|
|
183
|
+
title: `Two instructions disagree about ${axis}`,
|
|
184
|
+
detail:
|
|
185
|
+
`One says ${firstValue} ("${firstSnippet}") and another says ${secondValue} ("${secondSnippet}"). ` +
|
|
186
|
+
'The model has to pick one, and which one it picks can change between calls — so this is a correctness problem before it is a cost one. ' +
|
|
187
|
+
'Deleting the instruction you did not mean also shortens the prompt.' +
|
|
188
|
+
(otherCount > 0
|
|
189
|
+
? ` ${otherCount} other ${otherCount === 1 ? 'pair' : 'pairs'} of instructions disagree too.`
|
|
190
|
+
: ''),
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
redundantExamples: ({ redundantCount, totalCount, redundantTokens, topSimilarityPct }) => ({
|
|
194
|
+
title: `${redundantCount} of ${totalCount} examples repeat an earlier one`,
|
|
195
|
+
detail:
|
|
196
|
+
`They share ${topSimilarityPct}% or more of their wording with an example that comes before them, and account for about ${n(redundantTokens)} tokens paid for on every call. ` +
|
|
197
|
+
'Few-shot examples earn their cost by teaching something new; two that demonstrate the same pattern teach it once. ' +
|
|
198
|
+
'Read them before deleting: an example that looks redundant may be showing a boundary case on purpose.',
|
|
199
|
+
}),
|
|
200
|
+
|
|
201
|
+
movableSchema: ({ blocks, tokens, keyList, cue }) => ({
|
|
202
|
+
title: 'The output schema could travel in the request instead of the prompt',
|
|
203
|
+
detail:
|
|
204
|
+
`${blocks === 1 ? 'A schema block' : `${blocks} schema blocks`} introduced by "${cue}" defines ${keyList}, costing about ${n(tokens)} tokens on every call. ` +
|
|
205
|
+
'Every major API now takes a response schema as a request parameter — output_config.format, response_format, responseSchema — and moving it there is the rare change that is both cheaper and stricter: prose asks the model to comply, a parameter makes the decoder comply. ' +
|
|
206
|
+
'Trazum cannot make this edit, because it changes the call rather than the prompt, and it does not check whether your provider offers the parameter — if it does not, this is not available to you.',
|
|
207
|
+
}),
|
|
208
|
+
|
|
209
|
+
restatedOutputFormat: ({ restatedCount, totalCount, restatedTokens, keyList }) => ({
|
|
210
|
+
title: 'The output format is specified twice',
|
|
211
|
+
detail:
|
|
212
|
+
`The prose describes ${restatedCount} of the ${totalCount} fields your schema already defines (${keyList}), costing about ${n(restatedTokens)} tokens on every call. ` +
|
|
213
|
+
'The code block is the version worth keeping: it is unambiguous, and Trazum never edits it. ' +
|
|
214
|
+
'Check the two agree before deleting either — when a prompt says one thing in prose and another in its schema, the prose is usually the one that went stale.',
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
contradictionAxes: {
|
|
219
|
+
'response-language': 'the language of the answer',
|
|
220
|
+
'output-format': 'the output format',
|
|
221
|
+
'response-length': 'how long the answer should be',
|
|
222
|
+
'reasoning-visibility': 'whether to show the reasoning',
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
contradictionValues: {
|
|
226
|
+
'fixed-language': 'always the same language',
|
|
227
|
+
'mirror-language': "the reader's own language",
|
|
228
|
+
'format-json': 'JSON',
|
|
229
|
+
'format-markdown': 'Markdown',
|
|
230
|
+
'format-plain-text': 'plain text',
|
|
231
|
+
'length-brief': 'keep it brief',
|
|
232
|
+
'length-detailed': 'go into detail',
|
|
233
|
+
'reasoning-shown': 'show the reasoning',
|
|
234
|
+
'reasoning-hidden': 'hide the reasoning',
|
|
235
|
+
},
|
|
236
|
+
};
|
package/src/i18n/es.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { CoreMessages } from './types.js';
|
|
2
|
+
|
|
3
|
+
const n = (value: number): string => value.toLocaleString('es-ES');
|
|
4
|
+
|
|
5
|
+
/** Spanish catalogue. Mirrors `en.ts`, which is the source of truth. */
|
|
6
|
+
export const es: CoreMessages = {
|
|
7
|
+
locale: 'es',
|
|
8
|
+
numberLocale: 'es-ES',
|
|
9
|
+
|
|
10
|
+
rules: {
|
|
11
|
+
'duplicate-blocks': {
|
|
12
|
+
title: 'Párrafos repetidos',
|
|
13
|
+
rationale:
|
|
14
|
+
'Elimina párrafos enteros que ya aparecen antes en el prompt. Es habitual al montar prompts por concatenación de plantillas: el mismo bloque de instrucciones entra dos veces y se paga dos veces.',
|
|
15
|
+
},
|
|
16
|
+
'near-duplicate-blocks': {
|
|
17
|
+
title: 'Párrafos casi idénticos',
|
|
18
|
+
rationale:
|
|
19
|
+
'Elimina párrafos con un 92% o más de palabras en común con otro anterior. Detecta instrucciones reformuladas que dicen lo mismo dos veces. Nivel agresivo: revisa el diff, porque el 8% que difiere puede ser justo el matiz que te importa.',
|
|
20
|
+
},
|
|
21
|
+
'duplicate-lines': {
|
|
22
|
+
title: 'Líneas repetidas',
|
|
23
|
+
rationale:
|
|
24
|
+
'Elimina líneas que ya aparecen idénticas antes en el prompt, ignorando mayúsculas, acentos y puntuación. Solo actúa sobre líneas de 25 caracteres o más, para no tocar viñetas ni separadores legítimos.',
|
|
25
|
+
},
|
|
26
|
+
'verbose-phrases': {
|
|
27
|
+
title: 'Perífrasis largas',
|
|
28
|
+
rationale:
|
|
29
|
+
'Sustituye construcciones largas por su equivalente corto ("con el fin de" → "para", "in order to" → "to"). El significado es idéntico; solo cambia el número de tokens.',
|
|
30
|
+
},
|
|
31
|
+
politeness: {
|
|
32
|
+
title: 'Fórmulas de cortesía',
|
|
33
|
+
rationale:
|
|
34
|
+
'Quita "por favor", "gracias", "please", "kindly"... El modelo no responde mejor por pedírselo con cortesía, y cada fórmula se paga en todas las llamadas.',
|
|
35
|
+
},
|
|
36
|
+
filler: {
|
|
37
|
+
title: 'Muletillas y rodeos',
|
|
38
|
+
rationale:
|
|
39
|
+
'Elimina arranques vacíos como "básicamente", "cabe destacar que" o "it is important to note that", que no aportan instrucción.',
|
|
40
|
+
},
|
|
41
|
+
hedges: {
|
|
42
|
+
title: 'Coletillas de duda',
|
|
43
|
+
rationale:
|
|
44
|
+
'Elimina "creo que", "I think", "en mi opinión". En una instrucción debilitan la orden sin añadir información.',
|
|
45
|
+
},
|
|
46
|
+
intensifiers: {
|
|
47
|
+
title: 'Intensificadores',
|
|
48
|
+
rationale:
|
|
49
|
+
'Quita "muy", "realmente", "extremely"... Rara vez cambian la tarea. Nivel agresivo porque en algún prompt concreto el matiz sí importa.',
|
|
50
|
+
},
|
|
51
|
+
'self-check': {
|
|
52
|
+
title: 'Instrucciones de auto-verificación',
|
|
53
|
+
rationale:
|
|
54
|
+
'Quita "verifica tu respuesta", "double-check your work". Los modelos actuales ya verifican su trabajo; pedirlo explícitamente dispara pasos extra que se pagan en tokens de salida. Desactívala si tu flujo depende de esa verificación.',
|
|
55
|
+
},
|
|
56
|
+
emphasis: {
|
|
57
|
+
title: 'Énfasis en mayúsculas',
|
|
58
|
+
rationale:
|
|
59
|
+
'Pasa a minúscula palabras gritadas (MUST, NUNCA, CRITICAL) y quita prefijos tipo "IMPORTANTE:". Las mayúsculas se parten en más tokens que las minúsculas, y en los modelos actuales el énfasis excesivo hace que la instrucción se dispare de más.',
|
|
60
|
+
},
|
|
61
|
+
decoration: {
|
|
62
|
+
title: 'Separadores decorativos',
|
|
63
|
+
rationale:
|
|
64
|
+
'Elimina líneas hechas solo de caracteres repetidos (====, ----, ****) y las secuencias de signos de exclamación. No aportan estructura que el modelo aproveche y cuestan tokens en cada llamada.',
|
|
65
|
+
},
|
|
66
|
+
whitespace: {
|
|
67
|
+
title: 'Espacios y líneas en blanco sobrantes',
|
|
68
|
+
rationale:
|
|
69
|
+
'Quita espacios al final de línea, colapsa espacios repetidos dentro de la línea y reduce las líneas en blanco consecutivas a una. Respeta la sangría inicial para no romper listas ni markdown anidado.',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
llm: {
|
|
74
|
+
emptyResponse: () => 'El modelo devolvió una respuesta vacía.',
|
|
75
|
+
protectedContentAltered: (count) =>
|
|
76
|
+
`El modelo alteró ${count} fragmento(s) protegido(s) (código, URL o marcador de plantilla). Descartado para no romper el prompt.`,
|
|
77
|
+
notShorter: (after, before) =>
|
|
78
|
+
`El resultado no es más corto (${after} vs ${before} tokens). Se mantiene la versión determinista.`,
|
|
79
|
+
suspiciousShrink: (retainedPct) =>
|
|
80
|
+
`El resultado conserva solo el ${retainedPct}% de los tokens. Eso parece un resumen, no una compresión: revísalo a mano antes de usarlo.`,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
suggest: {
|
|
84
|
+
'not-found': () =>
|
|
85
|
+
'la frase citada no está en el prompt — el modelo parafraseó lo que estaba copiando',
|
|
86
|
+
'touches-protected': () =>
|
|
87
|
+
'editaría código, una URL, un marcador o una etiqueta, que se copian literalmente',
|
|
88
|
+
'introduces-protected': () => 'el reemplazo añade un marcador o una URL que no estaba',
|
|
89
|
+
'no-saving': () => 'no es más corto que lo que reemplaza',
|
|
90
|
+
overlaps: () => 'comparte texto con una sugerencia ya aceptada',
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
advisories: {
|
|
94
|
+
contextOverflow: ({ tokens, modelName, contextWindow }) => ({
|
|
95
|
+
title: 'El prompt no cabe en la ventana de contexto',
|
|
96
|
+
detail: `El prompt optimizado ocupa ~${n(tokens)} tokens y ${modelName} admite ${n(contextWindow)}. La llamada fallará: divide el contenido o cambia a un modelo con ventana mayor.`,
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
promptCaching: ({
|
|
100
|
+
placeholder,
|
|
101
|
+
prefixTokens,
|
|
102
|
+
totalTokens,
|
|
103
|
+
minTokens,
|
|
104
|
+
modelName,
|
|
105
|
+
hitRatePct,
|
|
106
|
+
readPct,
|
|
107
|
+
writePct,
|
|
108
|
+
explicit,
|
|
109
|
+
}) => {
|
|
110
|
+
const scope = placeholder
|
|
111
|
+
? `El prefijo estable —lo anterior al primer marcador ${placeholder}— son ~${n(prefixTokens)} de los ${n(totalTokens)} tokens del prompt, y supera el mínimo cacheable de ${n(minTokens)} de ${modelName}.`
|
|
112
|
+
: `El prompt no tiene marcadores variables, así que el prefijo cacheable es entero y supera el mínimo de ${n(minTokens)} tokens de ${modelName}.`;
|
|
113
|
+
const how = explicit
|
|
114
|
+
? 'Coloca el marcador de caché al final del prefijo estable: cualquier byte que cambie antes del corte invalida todo lo que va detrás.'
|
|
115
|
+
: `${modelName} cachea automáticamente por encima de su mínimo, así que no hay nada que activar; pero la regla es la misma: cualquier byte que cambie antes del corte invalida todo lo que va detrás.`;
|
|
116
|
+
return {
|
|
117
|
+
title: 'Activa prompt caching en el prefijo estable',
|
|
118
|
+
detail: `${scope} Con una tasa de acierto del ${hitRatePct}%, la lectura de caché cuesta un ${readPct}% del precio de entrada y la escritura un ${writePct}%. ${how}`,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
promptCachingNotWorthIt: () => ({
|
|
123
|
+
title: 'Con esa tasa de acierto, la caché no compensa',
|
|
124
|
+
detail:
|
|
125
|
+
'Escribir en caché cuesta un 125% del precio de entrada y leer un 10%. Por debajo de un ~28% de aciertos pagas más de lo que ahorras. Sube la reutilización del prefijo o deja la caché desactivada.',
|
|
126
|
+
}),
|
|
127
|
+
|
|
128
|
+
belowCacheMinimum: ({
|
|
129
|
+
modelName,
|
|
130
|
+
minTokens,
|
|
131
|
+
placeholder,
|
|
132
|
+
prefixTokens,
|
|
133
|
+
totalTokens,
|
|
134
|
+
mentionLowerMinimum,
|
|
135
|
+
}) => {
|
|
136
|
+
const reason = placeholder
|
|
137
|
+
? `aquí el primer marcador variable (${placeholder}) aparece a los ~${n(prefixTokens)} tokens y solo lo anterior puede cachearse`
|
|
138
|
+
: `este prompt tiene ~${n(totalTokens)}`;
|
|
139
|
+
return {
|
|
140
|
+
title: 'Por debajo del mínimo cacheable',
|
|
141
|
+
detail:
|
|
142
|
+
`${modelName} necesita al menos ${n(minTokens)} tokens de prefijo para cachear; ${reason}. Marcar cache_control no dará error, simplemente no cacheará.` +
|
|
143
|
+
(mentionLowerMinimum
|
|
144
|
+
? ' Claude Opus 5 baja ese mínimo a 512 tokens, así que prompts cortos que aquí no cachean, allí sí.'
|
|
145
|
+
: ''),
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
cachePrefixReorder: ({ staticTokensAfter, sharePct, placeholder }) => ({
|
|
150
|
+
title: 'Mueve las instrucciones estables antes del primer marcador',
|
|
151
|
+
detail: `Unos ~${n(staticTokensAfter)} tokens de contenido estable (el ${sharePct}% del prompt) están después del primer marcador variable ${placeholder}, así que hoy no se cachean nunca. Reordena la plantilla —instrucciones y contexto fijos primero, marcadores al final— y ese contenido pasa a leerse de caché al 10% del precio. Revisa que la reordenación no cambie el sentido del prompt.`,
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
batchApi: () => ({
|
|
155
|
+
title: 'Si el trabajo tolera latencia, usa la Batch API',
|
|
156
|
+
detail:
|
|
157
|
+
'La Batch API aplica un 50% de descuento sobre entrada y salida. La mayoría de lotes terminan en menos de una hora, con un máximo de 24. Sirve para clasificación masiva, enriquecimiento de datos o evaluaciones: cualquier cosa que no responda a un usuario en tiempo real.',
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
modelDowngrade: ({ modelName, tier, candidateName, currentUsd, candidateUsd }) => ({
|
|
161
|
+
title: `Esta tarea quizá no necesite ${modelName}`,
|
|
162
|
+
detail: `Por longitud y vocabulario, el prompt parece de complejidad "${tier}". Con ${candidateName} pasarías de ${currentUsd} a ${candidateUsd} al mes. Es una heurística por palabras clave, no un juicio de calidad: mide la diferencia con tus propias evaluaciones antes de cambiar en producción.`,
|
|
163
|
+
}),
|
|
164
|
+
|
|
165
|
+
outputDominated: ({ outputUsd, inputUsd }) => ({
|
|
166
|
+
title: 'Tu coste está en la salida, no en el prompt',
|
|
167
|
+
detail: `La salida supone ${outputUsd} al mes frente a ${inputUsd} de entrada. Acortar el prompt tiene un techo bajo aquí. Los dos controles que mueven la aguja son el parámetro effort (bájalo si la tarea no es intensiva en razonamiento) y pedir respuestas concisas de forma explícita.`,
|
|
168
|
+
}),
|
|
169
|
+
|
|
170
|
+
promoPricing: ({ modelName, promoInput, promoOutput, until, listInput, listOutput }) => ({
|
|
171
|
+
title: 'Estás calculando con precio promocional',
|
|
172
|
+
detail: `${modelName} tiene precio de lanzamiento ${promoInput}/${promoOutput} por millón de tokens hasta el ${until}. A partir de esa fecha pasa a ${listInput}/${listOutput}: tu factura subirá aunque no cambies nada.`,
|
|
173
|
+
}),
|
|
174
|
+
|
|
175
|
+
contradictoryInstructions: ({
|
|
176
|
+
axis,
|
|
177
|
+
firstValue,
|
|
178
|
+
firstSnippet,
|
|
179
|
+
secondValue,
|
|
180
|
+
secondSnippet,
|
|
181
|
+
otherCount,
|
|
182
|
+
}) => ({
|
|
183
|
+
title: `Dos instrucciones se contradicen sobre ${axis}`,
|
|
184
|
+
detail:
|
|
185
|
+
`Una dice ${firstValue} ("${firstSnippet}") y otra dice ${secondValue} ("${secondSnippet}"). ` +
|
|
186
|
+
'El modelo tiene que elegir una, y cuál elige puede cambiar entre llamadas: esto es un problema de corrección antes que de coste. ' +
|
|
187
|
+
'Borrar la instrucción que no querías además acorta el prompt.' +
|
|
188
|
+
(otherCount > 0
|
|
189
|
+
? ` Hay ${otherCount} ${otherCount === 1 ? 'pareja más' : 'parejas más'} de instrucciones que se contradicen.`
|
|
190
|
+
: ''),
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
redundantExamples: ({ redundantCount, totalCount, redundantTokens, topSimilarityPct }) => ({
|
|
194
|
+
title: `${redundantCount} de ${totalCount} ejemplos repiten uno anterior`,
|
|
195
|
+
detail:
|
|
196
|
+
`Comparten el ${topSimilarityPct}% o más de su redacción con un ejemplo previo y suponen unos ${n(redundantTokens)} tokens que pagas en cada llamada. ` +
|
|
197
|
+
'Un ejemplo few-shot se paga a sí mismo enseñando algo nuevo; dos que demuestran el mismo patrón lo enseñan una vez. ' +
|
|
198
|
+
'Léelos antes de borrar: un ejemplo que parece redundante puede estar mostrando un caso límite a propósito.',
|
|
199
|
+
}),
|
|
200
|
+
|
|
201
|
+
movableSchema: ({ blocks, tokens, keyList, cue }) => ({
|
|
202
|
+
title: 'El esquema de salida podría ir en la petición y no en el prompt',
|
|
203
|
+
detail:
|
|
204
|
+
`${blocks === 1 ? 'Un bloque de esquema' : `${blocks} bloques de esquema`} introducido por "${cue}" define ${keyList}, y cuesta unos ${n(tokens)} tokens en cada llamada. ` +
|
|
205
|
+
'Toda API relevante acepta ya un esquema de respuesta como parámetro de la petición — output_config.format, response_format, responseSchema — y moverlo allí es el cambio raro que sale más barato y además más estricto: la prosa le pide al modelo que cumpla, un parámetro obliga al decodificador. ' +
|
|
206
|
+
'Trazum no puede hacer esta edición, porque cambia la llamada y no el prompt, y no comprueba si tu proveedor ofrece el parámetro — si no lo ofrece, esto no está disponible para ti.',
|
|
207
|
+
}),
|
|
208
|
+
|
|
209
|
+
restatedOutputFormat: ({ restatedCount, totalCount, restatedTokens, keyList }) => ({
|
|
210
|
+
title: 'El formato de salida está especificado dos veces',
|
|
211
|
+
detail:
|
|
212
|
+
`La prosa describe ${restatedCount} de los ${totalCount} campos que tu esquema ya define (${keyList}), y cuesta unos ${n(restatedTokens)} tokens en cada llamada. ` +
|
|
213
|
+
'El bloque de código es la versión que merece la pena conservar: no es ambiguo y Trazum nunca lo toca. ' +
|
|
214
|
+
'Comprueba que ambos dicen lo mismo antes de borrar ninguno: cuando un prompt dice una cosa en prosa y otra en el esquema, lo que suele haberse quedado obsoleto es la prosa.',
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
contradictionAxes: {
|
|
219
|
+
'response-language': 'el idioma de la respuesta',
|
|
220
|
+
'output-format': 'el formato de salida',
|
|
221
|
+
'response-length': 'la longitud de la respuesta',
|
|
222
|
+
'reasoning-visibility': 'si mostrar el razonamiento',
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
contradictionValues: {
|
|
226
|
+
'fixed-language': 'siempre el mismo idioma',
|
|
227
|
+
'mirror-language': 'el idioma de quien escribe',
|
|
228
|
+
'format-json': 'JSON',
|
|
229
|
+
'format-markdown': 'Markdown',
|
|
230
|
+
'format-plain-text': 'texto plano',
|
|
231
|
+
'length-brief': 'que sea breve',
|
|
232
|
+
'length-detailed': 'que entre en detalle',
|
|
233
|
+
'reasoning-shown': 'mostrar el razonamiento',
|
|
234
|
+
'reasoning-hidden': 'ocultar el razonamiento',
|
|
235
|
+
},
|
|
236
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { en } from './en.js';
|
|
2
|
+
import { es } from './es.js';
|
|
3
|
+
import { DEFAULT_LOCALE, LOCALES } from './types.js';
|
|
4
|
+
import type { CoreMessages, Locale } from './types.js';
|
|
5
|
+
|
|
6
|
+
const CATALOGUES: Record<Locale, CoreMessages> = { en, es };
|
|
7
|
+
|
|
8
|
+
/** Message catalogue for a locale. */
|
|
9
|
+
export function getMessages(locale: Locale = DEFAULT_LOCALE): CoreMessages {
|
|
10
|
+
return CATALOGUES[locale] ?? CATALOGUES[DEFAULT_LOCALE];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Whether a string is a locale Trazum ships. */
|
|
14
|
+
export function isLocale(value: unknown): value is Locale {
|
|
15
|
+
return typeof value === 'string' && (LOCALES as readonly string[]).includes(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Highest-priority locale in `input` that Trazum actually ships, or `null`
|
|
20
|
+
* when it lists none.
|
|
21
|
+
*
|
|
22
|
+
* Accepts a bare tag (`es`), a regional tag (`es-ES`, `en_GB`), a POSIX locale
|
|
23
|
+
* string (`es_ES.UTF-8`) or an Accept-Language list, so the same helper works
|
|
24
|
+
* for an HTTP header, a CLI flag and the LANG environment variable.
|
|
25
|
+
*
|
|
26
|
+
* The whole list is walked rather than just the first entry: a browser sending
|
|
27
|
+
* `fr-FR,es;q=0.9` prefers French, which we do not have, but it does read
|
|
28
|
+
* Spanish, which we do — falling straight back to English would ignore a
|
|
29
|
+
* preference the user actually expressed. Quality values are assumed to be in
|
|
30
|
+
* descending order, as every real client sends them.
|
|
31
|
+
*
|
|
32
|
+
* Returning `null` instead of a default is what lets callers tell "asked for
|
|
33
|
+
* something we don't have" from "asked for English", and so fall through to
|
|
34
|
+
* the next configuration source.
|
|
35
|
+
*/
|
|
36
|
+
export function matchLocale(input: string | null | undefined): Locale | null {
|
|
37
|
+
if (!input) return null;
|
|
38
|
+
for (const tag of input.split(',')) {
|
|
39
|
+
const base = tag.trim().split(/[-_.;]/)[0]?.toLowerCase() ?? '';
|
|
40
|
+
if (isLocale(base)) return base;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Same as `matchLocale`, but falls back to the default locale rather than
|
|
47
|
+
* returning `null`: a bad locale should never be the reason an optimisation
|
|
48
|
+
* fails.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveLocale(input: string | null | undefined): Locale {
|
|
51
|
+
return matchLocale(input) ?? DEFAULT_LOCALE;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { DEFAULT_LOCALE, LOCALES, en, es };
|
|
55
|
+
export type {
|
|
56
|
+
BelowCacheMinimumParams,
|
|
57
|
+
CachePrefixReorderParams,
|
|
58
|
+
ContextOverflowParams,
|
|
59
|
+
CoreMessages,
|
|
60
|
+
Locale,
|
|
61
|
+
LocalizedMessage,
|
|
62
|
+
ModelDowngradeParams,
|
|
63
|
+
OutputDominatedParams,
|
|
64
|
+
PromoPricingParams,
|
|
65
|
+
PromptCachingParams,
|
|
66
|
+
RuleCopy,
|
|
67
|
+
RuleId,
|
|
68
|
+
} from './types.js';
|