@poco-ai/agentero-dsh-host 0.0.0-stage → 0.1.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.
Files changed (67) hide show
  1. package/lib/bookmarks-write.d.ts +24 -0
  2. package/lib/bookmarks-write.js +181 -0
  3. package/lib/bookmarks-write.js.map +1 -0
  4. package/lib/catalog.d.ts +28 -0
  5. package/lib/catalog.js +58 -0
  6. package/lib/catalog.js.map +1 -0
  7. package/lib/cli-run.d.ts +15 -0
  8. package/lib/cli-run.js +102 -0
  9. package/lib/cli-run.js.map +1 -0
  10. package/lib/client/index.d.ts +71 -0
  11. package/lib/client/settings-model.d.ts +28 -0
  12. package/lib/client/settings.d.ts +27 -0
  13. package/lib/client/ui/button.d.ts +10 -0
  14. package/lib/client/ui/input.d.ts +2 -0
  15. package/lib/client/utils.d.ts +2 -0
  16. package/lib/client.js +4071 -0
  17. package/lib/index.d.ts +55 -0
  18. package/lib/index.js +1103 -0
  19. package/lib/index.js.map +1 -0
  20. package/lib/marks-store.d.ts +80 -0
  21. package/lib/marks-store.js +393 -0
  22. package/lib/marks-store.js.map +1 -0
  23. package/lib/notes-files.d.ts +47 -0
  24. package/lib/notes-files.js +427 -0
  25. package/lib/notes-files.js.map +1 -0
  26. package/lib/pdf-file.d.ts +23 -0
  27. package/lib/pdf-file.js +84 -0
  28. package/lib/pdf-file.js.map +1 -0
  29. package/lib/recommend.d.ts +61 -0
  30. package/lib/recommend.js +864 -0
  31. package/lib/recommend.js.map +1 -0
  32. package/lib/tools.d.ts +8 -0
  33. package/lib/tools.js +204 -0
  34. package/lib/tools.js.map +1 -0
  35. package/lib/translate.d.ts +70 -0
  36. package/lib/translate.js +80 -0
  37. package/lib/translate.js.map +1 -0
  38. package/lib/translation/batches.d.ts +21 -0
  39. package/lib/translation/batches.js +88 -0
  40. package/lib/translation/batches.js.map +1 -0
  41. package/lib/translation/chunks.d.ts +2 -0
  42. package/lib/translation/chunks.js +36 -0
  43. package/lib/translation/chunks.js.map +1 -0
  44. package/lib/translation/mask.d.ts +22 -0
  45. package/lib/translation/mask.js +69 -0
  46. package/lib/translation/mask.js.map +1 -0
  47. package/lib/translation/pipeline.d.ts +3 -0
  48. package/lib/translation/pipeline.js +119 -0
  49. package/lib/translation/pipeline.js.map +1 -0
  50. package/lib/translation/preserved-links.d.ts +26 -0
  51. package/lib/translation/preserved-links.js +127 -0
  52. package/lib/translation/preserved-links.js.map +1 -0
  53. package/lib/translation/prompt.d.ts +99 -0
  54. package/lib/translation/prompt.js +224 -0
  55. package/lib/translation/prompt.js.map +1 -0
  56. package/lib/translation/retry.d.ts +3 -0
  57. package/lib/translation/retry.js +30 -0
  58. package/lib/translation/retry.js.map +1 -0
  59. package/lib/translation/sentences.d.ts +5 -0
  60. package/lib/translation/sentences.js +77 -0
  61. package/lib/translation/sentences.js.map +1 -0
  62. package/lib/vault.d.ts +21 -0
  63. package/lib/vault.js +69 -0
  64. package/lib/vault.js.map +1 -0
  65. package/package.json +44 -3
  66. package/src/translation/MOTIF-LICENSE.txt +21 -0
  67. package/README.md +0 -3
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Protect tokens that MT engines mangle (inline math, LaTeX commands, URLs,
3
+ * DOIs, citation markers, cross-references, and Markdown links) by swapping
4
+ * them for opaque placeholders before the request and restoring them afterwards.
5
+ */
6
+ export type MaskedToken = {
7
+ placeholder: string;
8
+ original: string;
9
+ };
10
+ export type MaskedText = {
11
+ text: string;
12
+ tokens: MaskedToken[];
13
+ };
14
+ export type RestoredText = {
15
+ text: string;
16
+ /** Placeholders the engine dropped or rewrote; caller decides retry policy. */
17
+ missing: number;
18
+ /** Duplicated or invented placeholders invalidate structural restoration. */
19
+ invalid?: boolean;
20
+ };
21
+ export declare function maskInlineTokens(text: string): MaskedText;
22
+ export declare function restoreInlineTokens(text: string, tokens: readonly MaskedToken[]): RestoredText;
@@ -0,0 +1,69 @@
1
+ // Ported from motif (Copyright (c) 2026 poco-ai, MIT). See MOTIF-LICENSE.txt.
2
+ /**
3
+ * Protect tokens that MT engines mangle (inline math, LaTeX commands, URLs,
4
+ * DOIs, citation markers, cross-references, and Markdown links) by swapping
5
+ * them for opaque placeholders before the request and restoring them afterwards.
6
+ */
7
+ import { findPreservedLinkSpans } from "./preserved-links.js";
8
+ /**
9
+ * Mathematical white square brackets: absent from paper prose, and left alone
10
+ * by MT engines far more reliably than ASCII brackets, which collide with
11
+ * citation markers and with the `[[n]]` batch numbering.
12
+ */
13
+ function placeholderFor(index) {
14
+ return `⟦${index}⟧`;
15
+ }
16
+ const MASK_PATTERNS = [
17
+ /⟪(?:F|\/?S)\d+⟫/gu,
18
+ // Display / inline TeX math (present when the source came from LaTeX).
19
+ /\$\$[^$]{1,400}\$\$/g,
20
+ /\$[^$\n]{1,200}\$/g,
21
+ /\\\((?:[^\\]|\\[^)]){1,200}?\\\)/g,
22
+ // LaTeX commands with or without a braced argument.
23
+ /\\[A-Za-z]+(?:\{[^{}]{0,120}\})?/g,
24
+ // Links and identifiers.
25
+ /https?:\/\/[^\s、,。]+/g,
26
+ /\bdoi:\s*10\.\d{4,9}\/\S+/gi,
27
+ /\b10\.\d{4,9}\/[^\s、,。]+/g,
28
+ ];
29
+ export function maskInlineTokens(text) {
30
+ // Match the original string once; overlapping patterns must not nest placeholders.
31
+ const spans = findPreservedLinkSpans(text).map(({ start, end, text }) => ({ start, end, text }));
32
+ for (const pattern of [...MASK_PATTERNS, /`[^`\n]+`/g, /⟦\s*\d+\s*⟧/g, /\[\[\d+\]\]/g]) {
33
+ for (const match of text.matchAll(new RegExp(pattern.source, pattern.flags))) {
34
+ spans.push({ start: match.index, end: match.index + match[0].length, text: match[0] });
35
+ }
36
+ }
37
+ spans.sort((a, b) => a.start - b.start || b.end - a.end);
38
+ const tokens = [];
39
+ let cursor = 0;
40
+ let masked = '';
41
+ let index = 0;
42
+ for (const span of spans) {
43
+ if (span.start < cursor)
44
+ continue;
45
+ let placeholder = placeholderFor(index++);
46
+ while (text.includes(placeholder))
47
+ placeholder = placeholderFor(index++);
48
+ tokens.push({ placeholder, original: span.text });
49
+ masked += text.slice(cursor, span.start) + placeholder;
50
+ cursor = span.end;
51
+ }
52
+ return { text: masked + text.slice(cursor), tokens };
53
+ }
54
+ export function restoreInlineTokens(text, tokens) {
55
+ const originals = new Map(tokens.map(token => [token.placeholder.slice(1, -1), token.original]));
56
+ const counts = new Map();
57
+ let invalid = false;
58
+ // One pass also prevents a literal placeholder in an original formula from being restored twice.
59
+ const out = text.replace(/⟦\s*(\d+)\s*⟧/g, (full, id) => {
60
+ counts.set(id, (counts.get(id) ?? 0) + 1);
61
+ const original = originals.get(id);
62
+ if (original === undefined || (counts.get(id) ?? 0) > 1)
63
+ invalid = true;
64
+ return original ?? full;
65
+ });
66
+ const missing = [...originals.keys()].filter(id => !counts.has(id)).length;
67
+ return { text: out, missing, ...(invalid ? { invalid: true } : {}) };
68
+ }
69
+ //# sourceMappingURL=mask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.js","sourceRoot":"","sources":["../../src/translation/mask.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Heal PDF ligatures and wrapped words without deleting unclassified page content. */
2
+ export declare function normalizeTranslationSource(raw: string): string;
3
+ export declare function translateDocumentText(input: string, target: string, translate: (text: string) => Promise<string>, signal: AbortSignal): Promise<string>;
@@ -0,0 +1,119 @@
1
+ /** Motif's protection, sentence batching and repair flow, shared by page reflow and selection translation. */
2
+ import { buildNumberedPayload, buildTranslateBatches, parseNumberedTranslation } from './batches.js';
3
+ import { splitTranslationChunks } from './chunks.js';
4
+ import { maskInlineTokens, restoreInlineTokens } from './mask.js';
5
+ import { splitLayoutSentences } from './sentences.js';
6
+ /** Heal PDF ligatures and wrapped words without deleting unclassified page content. */
7
+ export function normalizeTranslationSource(raw) {
8
+ const ligatures = { 'ff': 'ff', 'fi': 'fi', 'fl': 'fl', 'ffi': 'ffi', 'ffl': 'ffl', 'ſt': 'st', 'st': 'st' };
9
+ return raw.normalize('NFC').replace(/\u00ad/gu, '')
10
+ .replace(/[fffiflffifflſtst]/gu, hit => ligatures[hit] ?? hit)
11
+ .replace(/(\p{Ll})[-\u2010\u2011\u2012\u2013]\s+(\p{Ll}+)/gu, (full, head, tail) => /^(?:and|or|nor|but|to|vs|versus|per|than)$/iu.test(tail) ? full : `${head}${tail}`)
12
+ .replace(/[^\S\n]+/gu, ' ').trim();
13
+ }
14
+ function restore(text, tokens) {
15
+ const value = restoreInlineTokens(text, tokens);
16
+ return value.missing === 0 && !value.invalid && value.text.trim() !== '' ? value.text.trim() : undefined;
17
+ }
18
+ /** Alphabetic aliases avoid models interpreting a numeric placeholder as a value. */
19
+ function aliasFor(index) {
20
+ return `ZXQ${index.toString(26).replace(/[0-9a-p]/gu, c => String.fromCharCode(65 + Number.parseInt(c, 26)))}QXZ`;
21
+ }
22
+ export async function translateDocumentText(input, target, translate, signal) {
23
+ // Mask before normalization and sentence cutting so formulas and URLs stay byte-exact.
24
+ const masked = maskInlineTokens(input);
25
+ const paragraphs = normalizeTranslationSource(masked.text).split(/\n\s*\n/gu).filter(part => part.trim() !== '');
26
+ const units = [];
27
+ for (const [paragraph, text] of paragraphs.entries()) {
28
+ for (const sentence of splitLayoutSentences(text)) {
29
+ for (const source of splitTranslationChunks(sentence, 2200)) {
30
+ units.push({ source, paragraph, index: units.length,
31
+ tokens: masked.tokens.filter(token => source.includes(token.placeholder)) });
32
+ }
33
+ }
34
+ }
35
+ const output = new Array(units.length);
36
+ const call = async (source) => {
37
+ signal.throwIfAborted();
38
+ const result = await translate(source);
39
+ signal.throwIfAborted();
40
+ return result;
41
+ };
42
+ const gap = /^zh(?:-|$)/iu.test(target) ? '' : ' ';
43
+ const incomplete = (error) => error instanceof Error
44
+ && 'code' in error && error.code === 'TRANSLATION_INCOMPLETE';
45
+ const callUnit = async (source, depth = 0) => {
46
+ try {
47
+ return await call(source);
48
+ }
49
+ catch (error) {
50
+ signal.throwIfAborted();
51
+ if (!incomplete(error) || depth >= 2 || source.length < 64)
52
+ throw error;
53
+ const parts = [];
54
+ for (const part of splitTranslationChunks(source, Math.max(32, Math.ceil(source.length / 2)))) {
55
+ parts.push(await callUnit(part, depth + 1));
56
+ }
57
+ return parts.join(gap);
58
+ }
59
+ };
60
+ const finalize = async (unit, candidate) => {
61
+ const restored = restore(candidate, unit.tokens);
62
+ if (restored !== undefined)
63
+ return restored;
64
+ const retry = restore(await callUnit(unit.source), unit.tokens);
65
+ if (retry !== undefined)
66
+ return retry;
67
+ const aliases = unit.tokens.map((token, index) => ({ ...token, alias: aliasFor(index) }));
68
+ let alternate = unit.source;
69
+ for (const token of aliases)
70
+ alternate = alternate.replaceAll(token.placeholder, token.alias);
71
+ alternate = await callUnit(alternate);
72
+ for (const token of aliases)
73
+ alternate = alternate.replace(new RegExp(token.alias, 'gi'), token.placeholder);
74
+ const repaired = restore(alternate, unit.tokens);
75
+ if (repaired !== undefined)
76
+ return repaired;
77
+ // Last resort from motif: translate only prose between protected spans.
78
+ const parts = unit.source.split(/(⟦\d+⟧)/u);
79
+ for (const [index, part] of parts.entries()) {
80
+ if (/^⟦\d+⟧$/u.test(part) || !/\p{L}/u.test(part))
81
+ continue;
82
+ const leading = part.match(/^[\s.,;:!?。;,:!?)\]]*/u)?.[0] ?? '';
83
+ const translated = await callUnit(part.slice(leading.length));
84
+ // No token was sent in this fragment: invented placeholders are a failure.
85
+ if (/⟦\s*\d+\s*⟧/u.test(translated))
86
+ throw new Error('Translation changed protected formulas or links. Please retry.');
87
+ parts[index] = leading + translated;
88
+ }
89
+ const protectedResult = restore(parts.join(' '), unit.tokens);
90
+ if (protectedResult === undefined)
91
+ throw new Error('Translation changed protected formulas or links. Please retry.');
92
+ return protectedResult;
93
+ };
94
+ // Serial batches keep repair requests bounded and avoid amplifying provider rate limits.
95
+ for (const batch of buildTranslateBatches(units)) {
96
+ signal.throwIfAborted();
97
+ let segments;
98
+ try {
99
+ const result = batch.length === 1 ? await callUnit(batch[0].source) : await call(buildNumberedPayload(batch));
100
+ segments = parseNumberedTranslation(result, batch.length);
101
+ }
102
+ catch (error) {
103
+ signal.throwIfAborted();
104
+ if (!incomplete(error) || batch.length === 1)
105
+ throw error;
106
+ segments = null;
107
+ }
108
+ for (const [index, unit] of batch.entries()) {
109
+ signal.throwIfAborted();
110
+ const candidate = segments?.[index] ?? await callUnit(unit.source);
111
+ output[unit.index] = await finalize(unit, candidate);
112
+ }
113
+ }
114
+ signal.throwIfAborted();
115
+ return paragraphs.map((_, paragraph) => units
116
+ .filter(unit => unit.paragraph === paragraph)
117
+ .map(unit => output[unit.index]).join(gap)).join('\n\n');
118
+ }
119
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/translation/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Citation, cross-reference, and link tokens that machine translation drops
3
+ * or rewrites. Callers mask them before the request and, after restore, use
4
+ * the same spans to put click targets back on the translated text.
5
+ */
6
+ export type PreservedLinkKind = "markdown" | "url" | "citation" | "ref";
7
+ export type PreservedLinkSpan = {
8
+ kind: PreservedLinkKind;
9
+ start: number;
10
+ end: number;
11
+ text: string;
12
+ /** Markdown or bare URL target. Citation and ref spans leave this empty. */
13
+ url?: string;
14
+ };
15
+ /**
16
+ * Drop sentence punctuation glued to a URL. A `)` stays when it closes a
17
+ * parenthesis that belongs to the URL (`Function_(mathematics)`); it is
18
+ * removed only when the slice has more closing parens than opening ones.
19
+ */
20
+ export declare function trimPreservedUrl(raw: string): string;
21
+ /**
22
+ * Non-overlapping link-like spans in reading order. Longer matches win when
23
+ * two patterns cover the same characters, so a Markdown link is not also
24
+ * reported as a bare URL.
25
+ */
26
+ export declare function findPreservedLinkSpans(text: string): PreservedLinkSpan[];
@@ -0,0 +1,127 @@
1
+ // Ported from motif (Copyright (c) 2026 poco-ai, MIT). See MOTIF-LICENSE.txt.
2
+ /**
3
+ * Citation, cross-reference, and link tokens that machine translation drops
4
+ * or rewrites. Callers mask them before the request and, after restore, use
5
+ * the same spans to put click targets back on the translated text.
6
+ */
7
+ const MARKDOWN_LINK = /\[([^\]\n]{1,240})\]\((https?:\/\/[^\s<>\]]+)\)/gi;
8
+ const URL_LINK = /https?:\/\/[^\s、,。<>\]]+/gi;
9
+ /**
10
+ * Drop sentence punctuation glued to a URL. A `)` stays when it closes a
11
+ * parenthesis that belongs to the URL (`Function_(mathematics)`); it is
12
+ * removed only when the slice has more closing parens than opening ones.
13
+ */
14
+ export function trimPreservedUrl(raw) {
15
+ let end = raw.length;
16
+ while (end > 0) {
17
+ const ch = raw[end - 1];
18
+ if (ch && /[.,;:!?]/.test(ch)) {
19
+ end--;
20
+ continue;
21
+ }
22
+ if (ch === ")" && parenBalance(raw.slice(0, end)) < 0) {
23
+ end--;
24
+ continue;
25
+ }
26
+ break;
27
+ }
28
+ return raw.slice(0, end);
29
+ }
30
+ /** Opening parens minus closing parens. Negative means a prose `)` is left over. */
31
+ function parenBalance(text) {
32
+ let balance = 0;
33
+ for (const ch of text) {
34
+ if (ch === "(")
35
+ balance++;
36
+ else if (ch === ")")
37
+ balance--;
38
+ }
39
+ return balance;
40
+ }
41
+ const CITATION_LINK = /[[[]\s*[0-9]+[a-z]?(?:\s*[-–—~,,]\s*[0-9]+[a-z]?)*\s*[\]]]/gi;
42
+ const REF_LINK = /\b(?:Figures?|Figs?\.|Tables?|Tabs?\.|Sections?|Secs?\.|Equations?|Eqs?\.|Eqn?\.|Algorithms?|Algs?\.|Theorems?|Lemmas?|Appendices|Appendix)\s+\(?\d+(?:\.\d+)?[a-z]?(?:\s*[-–—]\s*\(?\d+(?:\.\d+)?[a-z]?)?\)?/gi;
43
+ const AUTHOR_NAME = "(?:[A-Z][A-Za-z'’-]{1,40}|[\\u4e00-\\u9fff]{1,12})";
44
+ // PDF text often pads the parenthesis and the comma. Machine translation
45
+ // then rewrites them to fullwidth brackets, a Chinese comma, and 等人.
46
+ const AUTHOR_TAIL = "(?:\\s*et\\s+al\\.?|\\s*(?:and|&)\\s+[A-Z][A-Za-z'’-]{1,40}|等人|等)?";
47
+ const AUTHOR_CITE = `${AUTHOR_NAME}${AUTHOR_TAIL}\\s*[,,]\\s*\\d{4}[a-z]?(?:\\s*年)?`;
48
+ const AUTHOR_YEAR = new RegExp(`[((]\\s*${AUTHOR_CITE}(?:\\s*[;;]\\s*${AUTHOR_CITE})*\\s*[))]`, "g");
49
+ function isBatchMarker(text, start, end) {
50
+ return text[start - 1] === "[" && text[end] === "]";
51
+ }
52
+ function pushMatches(text, pattern, kind, spans, accept) {
53
+ const flags = pattern.flags.includes("g")
54
+ ? pattern.flags
55
+ : `${pattern.flags}g`;
56
+ const re = new RegExp(pattern.source, flags);
57
+ for (const match of text.matchAll(re)) {
58
+ const start = match.index ?? 0;
59
+ const span = accept
60
+ ? accept(match)
61
+ : { kind, start, end: start + match[0].length, text: match[0] };
62
+ if (!span || span.end <= span.start)
63
+ continue;
64
+ if (kind === "citation" && isBatchMarker(text, span.start, span.end)) {
65
+ continue;
66
+ }
67
+ spans.push(span);
68
+ }
69
+ }
70
+ /**
71
+ * Non-overlapping link-like spans in reading order. Longer matches win when
72
+ * two patterns cover the same characters, so a Markdown link is not also
73
+ * reported as a bare URL.
74
+ */
75
+ export function findPreservedLinkSpans(text) {
76
+ if (!text)
77
+ return [];
78
+ const found = [];
79
+ pushMatches(text, MARKDOWN_LINK, "markdown", found, (match) => {
80
+ const start = match.index ?? 0;
81
+ let url = match[2] ?? "";
82
+ let end = start + match[0].length;
83
+ // The markdown closer can be a `)` that belongs to the URL. Pull those
84
+ // back in until the URL's parentheses balance.
85
+ while (parenBalance(url) > 0 && text[end] === ")") {
86
+ url += ")";
87
+ end += 1;
88
+ }
89
+ url = trimPreservedUrl(url);
90
+ if (!url)
91
+ return null;
92
+ return {
93
+ kind: "markdown",
94
+ start,
95
+ end,
96
+ text: text.slice(start, end),
97
+ url,
98
+ };
99
+ });
100
+ pushMatches(text, URL_LINK, "url", found, (match) => {
101
+ const start = match.index ?? 0;
102
+ const trimmed = trimPreservedUrl(match[0]);
103
+ if (!trimmed)
104
+ return null;
105
+ return {
106
+ kind: "url",
107
+ start,
108
+ end: start + trimmed.length,
109
+ text: trimmed,
110
+ url: trimmed,
111
+ };
112
+ });
113
+ pushMatches(text, CITATION_LINK, "citation", found);
114
+ pushMatches(text, REF_LINK, "ref", found);
115
+ pushMatches(text, AUTHOR_YEAR, "citation", found);
116
+ found.sort((a, b) => a.start - b.start || b.end - a.end);
117
+ const kept = [];
118
+ let cursor = 0;
119
+ for (const span of found) {
120
+ if (span.start < cursor)
121
+ continue;
122
+ kept.push(span);
123
+ cursor = span.end;
124
+ }
125
+ return kept;
126
+ }
127
+ //# sourceMappingURL=preserved-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preserved-links.js","sourceRoot":"","sources":["../../src/translation/preserved-links.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * LLM translation prompts for the Agent path and the OpenAI-compatible Host
3
+ * path (`crates/agentero-core/.../openai_compatible.rs`). Keep the two in sync.
4
+ *
5
+ * The model sees a translation engine, not a chat assistant:
6
+ * - system: engine rules that always apply, plus academic style (or a
7
+ * substantial custom system), plus a short-selection ban when needed
8
+ * - user: the source text only, wrapped in `"""…"""` unless a custom user
9
+ * template contains `{{text}}`
10
+ *
11
+ * A one-line colloquial custom prompt is an additional preference. It does
12
+ * not replace the engine rules.
13
+ */
14
+ /** Names of `{{…}}` tokens that are not targetLang / sourceLang / text. */
15
+ export declare function unknownTranslatePlaceholders(template: string): string[];
16
+ /** Drop `{{…}}` tokens other than the three recognized placeholders. */
17
+ export declare function stripUnknownTranslatePlaceholders(template: string): string;
18
+ /** True when the template has a `{{text}}` placeholder (inner space allowed). */
19
+ export declare function translateTemplateHasText(template: string): boolean;
20
+ /**
21
+ * Replace every `{{text}}` with `text`. A function replacement so `$` in the
22
+ * source is not treated as a replacement pattern.
23
+ */
24
+ export declare function fillTranslateTextPlaceholder(template: string, text: string): string;
25
+ export type TranslateTemplateIssue = {
26
+ kind: "unknown";
27
+ names: string[];
28
+ } | {
29
+ kind: "missingText";
30
+ };
31
+ /** System box: unknown placeholders fail. Empty is the built-in style. */
32
+ export declare function translateSystemTemplateIssue(template: string): TranslateTemplateIssue | null;
33
+ /**
34
+ * User box: empty keeps the quoted source. A non-empty template must include
35
+ * `{{text}}` and only the known placeholders.
36
+ */
37
+ export declare function translateUserTemplateIssue(template: string): TranslateTemplateIssue | null;
38
+ /**
39
+ * Academic style block seeded by Settings → 翻译 → 翻译提示词.
40
+ * A substantial custom system replaces this block only. Engine rules
41
+ * (output-only, no clarification, preserved math / citations / `[[n]]`)
42
+ * stay in effect either way.
43
+ */
44
+ export declare const DEFAULT_TRANSLATE_PROMPT_TEMPLATE = "The source is prose from a research paper, often extracted from a PDF text layer. Translate the meaning, not the word order: write natural, fluent {{targetLang}} the way a researcher in the field would. Re-order clauses and split long sentences when that reads better.\nUse the established {{targetLang}} term for each concept and stay consistent. On a term's first occurrence you may follow it with the original in parentheses, e.g. \u6CE8\u610F\u529B\u673A\u5236\uFF08attention\uFF09.\n\nExamples (return only the translation, this brief):\n- attention \u2192 \u6CE8\u610F\u529B\n- We propose a transformer architecture for sequence transduction. \u2192 \u6211\u4EEC\u63D0\u51FA\u4E00\u79CD\u7528\u4E8E\u5E8F\u5217\u8F6C\u6362\u7684 Transformer \u67B6\u6784\u3002\n- \u5B9E\u9A8C\u8868\u660E\u8BE5\u65B9\u6CD5\u5728 ImageNet \u4E0A\u4F18\u4E8E\u57FA\u7EBF\u3002 \u2192 Experiments show that the method outperforms the baseline on ImageNet.";
45
+ /** True when `text` is a numbered batch payload (`[[1]] …`, `[[2]] …`). */
46
+ export declare function hasNumberedMarkers(text: string): boolean;
47
+ /**
48
+ * One line, no placeholders, at most 180 characters: a spoken preference
49
+ * ("翻译得自然一点", "Be terse."). It must not replace the engine rules.
50
+ */
51
+ export declare function isShortColloquialPrompt(text: string): boolean;
52
+ /**
53
+ * Word / short phrase vs prose. Mirrors Easydict's dictionary-vs-translation
54
+ * split with a small phrase allowance: one to four whitespace-separated
55
+ * tokens, or a Han string of at most eight characters, and no sentence
56
+ * punctuation. Numbered batches stay prose so layout translation is unchanged.
57
+ */
58
+ export declare function classifyTranslateQuery(text: string): "word" | "prose";
59
+ /**
60
+ * Substitute `{{targetLang}}` / `{{sourceLang}}`. Unknown `{{…}}` tokens are
61
+ * removed. `{{text}}` is left for the user template to fill.
62
+ */
63
+ export declare function renderTranslatePromptTemplate(template: string, targetLangName: string, sourceLangName?: string): string;
64
+ export type TranslatePromptInput = {
65
+ text: string;
66
+ targetLangName: string;
67
+ /** Display name. Omitted on the Agent path, which always auto-detects. */
68
+ sourceLangName?: string;
69
+ page?: number;
70
+ surface?: string;
71
+ /**
72
+ * Legacy single template. Used when `customSystem` is empty.
73
+ * A short one-line value is a preference; a longer value replaces the
74
+ * academic style block only.
75
+ */
76
+ customPrompt?: string;
77
+ /** Settings `translate.customSystem`. Wins over `customPrompt`. */
78
+ customSystem?: string;
79
+ /**
80
+ * Settings `translate.customUser`. Applied only when it contains
81
+ * `{{text}}` (inner whitespace allowed). Otherwise the source is still
82
+ * sent quoted, and this text is kept as an additional preference.
83
+ */
84
+ customUser?: string;
85
+ };
86
+ export type TranslateMessages = {
87
+ system: string;
88
+ user: string;
89
+ };
90
+ /**
91
+ * `(system, user)` pair for an OpenAI-compatible chat completion.
92
+ * Agent `runOnce` has a single string; use {@link buildTranslatePrompt}.
93
+ */
94
+ export declare function buildTranslateMessages(opts: TranslatePromptInput): TranslateMessages;
95
+ /**
96
+ * Single string for ACP `runOnce`. System rules come first, then the source.
97
+ * The Host translate envelope must not prefix this with "User request:".
98
+ */
99
+ export declare function buildTranslatePrompt(opts: TranslatePromptInput): string;