vibe-coding-master 0.3.16 → 0.3.18
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 +8 -7
- package/dist/backend/api/translation-routes.js +0 -13
- package/dist/backend/runtime/node-pty-runtime.js +13 -3
- package/dist/backend/server.js +1 -3
- package/dist/backend/services/app-settings-service.js +6 -31
- package/dist/backend/services/codex-translation-service.js +281 -130
- package/dist/backend/services/session-service.js +15 -8
- package/dist/backend/services/translation-service.js +198 -245
- package/dist/backend/templates/harness/codex-review.js +20 -8
- package/dist/shared/types/app-settings.js +5 -0
- package/dist/shared/types/session.js +5 -5
- package/dist/shared/types/translation.js +0 -5
- package/dist-frontend/assets/index-DOKW0Y5n.js +95 -0
- package/dist-frontend/assets/index-Dmefx9m7.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/codex-translation-plan.md +107 -63
- package/docs/gateway-design.md +2 -2
- package/docs/product-design.md +22 -22
- package/package.json +1 -1
- package/dist/backend/adapters/translation-provider.js +0 -145
- package/dist/backend/services/translation-prompts.js +0 -173
- package/dist-frontend/assets/index-BNJJ_Tcf.js +0 -92
- package/dist-frontend/assets/index-C1FPjOXU.css +0 -32
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
export class TranslationProviderError extends Error {
|
|
2
|
-
code;
|
|
3
|
-
elapsedMs;
|
|
4
|
-
constructor(message, code, elapsedMs = 0) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = "TranslationProviderError";
|
|
7
|
-
this.code = code;
|
|
8
|
-
this.elapsedMs = elapsedMs;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function createOpenAiCompatibleTranslationProvider(fetchImpl = fetch) {
|
|
12
|
-
return {
|
|
13
|
-
async testConnection(settings, secrets) {
|
|
14
|
-
const startedAt = performance.now();
|
|
15
|
-
try {
|
|
16
|
-
await this.translate({
|
|
17
|
-
settings,
|
|
18
|
-
secrets,
|
|
19
|
-
systemPrompt: "Reply with exactly: ok",
|
|
20
|
-
userPrompt: "ok"
|
|
21
|
-
});
|
|
22
|
-
return {
|
|
23
|
-
ok: true,
|
|
24
|
-
model: settings.model,
|
|
25
|
-
elapsedMs: Math.round(performance.now() - startedAt)
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
return {
|
|
30
|
-
ok: false,
|
|
31
|
-
model: settings.model,
|
|
32
|
-
elapsedMs: Math.round(performance.now() - startedAt),
|
|
33
|
-
error: error instanceof Error ? error.message : "Translation provider failed."
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
async translate(input) {
|
|
38
|
-
const apiKey = input.secrets.apiKey?.trim();
|
|
39
|
-
if (!apiKey) {
|
|
40
|
-
throw new TranslationProviderError("Translation API key is not configured.", "config");
|
|
41
|
-
}
|
|
42
|
-
const startedAt = performance.now();
|
|
43
|
-
const elapsed = () => Math.round(performance.now() - startedAt);
|
|
44
|
-
const controller = new AbortController();
|
|
45
|
-
const timeout = setTimeout(() => controller.abort(), input.settings.requestTimeoutMs);
|
|
46
|
-
const externalAbort = () => controller.abort();
|
|
47
|
-
if (input.signal) {
|
|
48
|
-
if (input.signal.aborted) {
|
|
49
|
-
controller.abort();
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
input.signal.addEventListener("abort", externalAbort, { once: true });
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
const response = await fetchImpl(buildChatCompletionsUrl(input.settings.baseUrl), {
|
|
57
|
-
method: "POST",
|
|
58
|
-
headers: {
|
|
59
|
-
"content-type": "application/json",
|
|
60
|
-
authorization: `Bearer ${apiKey}`
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({
|
|
63
|
-
model: input.settings.model,
|
|
64
|
-
messages: [
|
|
65
|
-
{ role: "system", content: input.systemPrompt },
|
|
66
|
-
{ role: "user", content: input.userPrompt }
|
|
67
|
-
],
|
|
68
|
-
temperature: input.settings.temperature,
|
|
69
|
-
stream: false
|
|
70
|
-
}),
|
|
71
|
-
signal: controller.signal
|
|
72
|
-
});
|
|
73
|
-
const rawText = await response.text();
|
|
74
|
-
let payload = null;
|
|
75
|
-
try {
|
|
76
|
-
payload = rawText ? JSON.parse(rawText) : null;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
throw new TranslationProviderError(rawText || response.statusText, `HTTP ${response.status}`, elapsed());
|
|
81
|
-
}
|
|
82
|
-
throw new TranslationProviderError("Translation provider returned invalid JSON.", "parse", elapsed());
|
|
83
|
-
}
|
|
84
|
-
if (!response.ok) {
|
|
85
|
-
throw new TranslationProviderError(extractErrorMessage(payload) ?? response.statusText, `HTTP ${response.status}`, elapsed());
|
|
86
|
-
}
|
|
87
|
-
const content = extractContent(payload);
|
|
88
|
-
if (!content) {
|
|
89
|
-
throw new TranslationProviderError("Translation provider returned empty content.", "parse", elapsed());
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
text: content,
|
|
93
|
-
elapsedMs: elapsed(),
|
|
94
|
-
tokenUsage: parseOpenAiUsage(payload?.usage)
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
if (error instanceof TranslationProviderError) {
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
101
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
-
const code = message.toLowerCase().includes("abort") ? "timeout" : "network";
|
|
103
|
-
throw new TranslationProviderError(message, code, elapsed());
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
clearTimeout(timeout);
|
|
107
|
-
input.signal?.removeEventListener("abort", externalAbort);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
export function buildChatCompletionsUrl(baseUrl) {
|
|
113
|
-
return `${baseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
114
|
-
}
|
|
115
|
-
export function parseOpenAiUsage(raw) {
|
|
116
|
-
if (!raw || typeof raw !== "object") {
|
|
117
|
-
return undefined;
|
|
118
|
-
}
|
|
119
|
-
const usage = raw;
|
|
120
|
-
const input = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
|
|
121
|
-
const output = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
|
|
122
|
-
const total = typeof usage.total_tokens === "number" ? usage.total_tokens : input + output;
|
|
123
|
-
if (input === 0 && output === 0 && total === 0) {
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
return { input, output, total };
|
|
127
|
-
}
|
|
128
|
-
function extractContent(payload) {
|
|
129
|
-
const choices = payload?.choices;
|
|
130
|
-
if (!Array.isArray(choices) || choices.length === 0) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const content = choices[0].message?.content;
|
|
134
|
-
return typeof content === "string" ? content.trim() : null;
|
|
135
|
-
}
|
|
136
|
-
function extractErrorMessage(payload) {
|
|
137
|
-
const error = payload?.error;
|
|
138
|
-
if (!error) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
if (typeof error === "string") {
|
|
142
|
-
return error;
|
|
143
|
-
}
|
|
144
|
-
return error.message ?? JSON.stringify(error);
|
|
145
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { TRANSLATION_PROMPT_KEYS } from "../../shared/types/translation.js";
|
|
2
|
-
const ZH_TO_EN_BASE = `You are a professional translator. The user types Chinese instructions for Claude Code (an AI coding assistant CLI), and your job is to translate the Chinese into natural, professional technical English that Claude Code will read as the user's prompt.
|
|
3
|
-
|
|
4
|
-
Output ONLY the translation. No preface, no quotation marks, no commentary, no notes about the translation.
|
|
5
|
-
|
|
6
|
-
Preserve verbatim (NEVER translate):
|
|
7
|
-
- code blocks (fenced or indented), code fragments, anything in backticks
|
|
8
|
-
- identifier names (variables, functions, types, classes, files, modules, packages, branches, tags)
|
|
9
|
-
- file paths, glob patterns, URLs
|
|
10
|
-
- command-line snippets, flags, environment variable names, git refs
|
|
11
|
-
- error messages quoted in the source
|
|
12
|
-
- numbers, units, version strings, hex / hash values
|
|
13
|
-
|
|
14
|
-
Translate only the prose around these.
|
|
15
|
-
|
|
16
|
-
Style:
|
|
17
|
-
- Concise. Match the source's length; do not pad with explanations the source does not contain.
|
|
18
|
-
- Imperative voice when the source is imperative ("帮我看一下" → "Take a look at"); declarative otherwise.
|
|
19
|
-
- Silently fix obvious typos, missing words, and ungrammatical fragments. Common patterns from a Chinese IME:
|
|
20
|
-
· 同音/近音字混用: "在" ↔ "再", "的/地/得", "做/作", "象/像", "因为/应为"
|
|
21
|
-
· 错别字: "测式" → "测试", "克立" → "克隆", "提价" → "提交"
|
|
22
|
-
· 漏字 / 多字: "一条录" → "一条记录"
|
|
23
|
-
· 缺主语或谓语: 句子片段 → 完整祈使句
|
|
24
|
-
ONLY fix when the intent is unambiguous. If a word could legitimately mean either reading (e.g. "在" really meant 在 not 再), translate AS-IS rather than guess. Conservative repair beats confident wrong-fix.
|
|
25
|
-
- If the input is brief or fragmentary, produce a complete grammatical English sentence reflecting the user's likely intent — but do not invent specifics the user did not imply.
|
|
26
|
-
- Aim for the register of a competent engineer writing a message to a colleague: clear, direct, no fluff, no hedging.`;
|
|
27
|
-
const ZH_TO_EN_WITH_CONTEXT_BASE = `You are a professional translator AND a quick sanity-check filter. The user types Chinese instructions for Claude Code (an AI coding assistant CLI). The PRIOR_REPLY block below is Claude Code's previous English reply — use it to disambiguate pronouns ("那个" / "你说的"), expand elliptical references ("再加一条" → "add one more entry"), silently correct obvious typos, AND detect when the user's input is unlikely to be actionable in this context.
|
|
28
|
-
|
|
29
|
-
Output format (MANDATORY — two parts separated by ONE blank line):
|
|
30
|
-
|
|
31
|
-
Part 1 — first line, status:
|
|
32
|
-
"OK" — normal: the user's input is clear given the prior reply
|
|
33
|
-
"WARN: <说明>" — flag a likely problem; ONE Chinese sentence (≤30 字)
|
|
34
|
-
|
|
35
|
-
(single blank line)
|
|
36
|
-
|
|
37
|
-
Part 2 — the English translation of the NEW USER INPUT, exactly as
|
|
38
|
-
you would translate it. Even when WARN is set, still translate as
|
|
39
|
-
faithfully as possible — the user may decide to send it anyway.
|
|
40
|
-
|
|
41
|
-
WARN ONLY when confident a Chinese-speaking engineer reading the
|
|
42
|
-
prior reply + the user's input would also see a problem. Triggers:
|
|
43
|
-
- The user refers to something not in PRIOR_REPLY ("那个文件" but
|
|
44
|
-
no file mentioned; "刚才说的方案" but Claude proposed nothing)
|
|
45
|
-
- The user answers ambiguously to a clear multiple-choice question
|
|
46
|
-
("好的" / "随便" when Claude asked "A or B?")
|
|
47
|
-
- The user's input contradicts the topic of PRIOR_REPLY (different
|
|
48
|
-
subject, mismatched verb)
|
|
49
|
-
- The input is garbled enough that even with context the
|
|
50
|
-
translation is just a guess
|
|
51
|
-
|
|
52
|
-
Output OK when in doubt. Spurious warnings slow the user down for
|
|
53
|
-
nothing; missed warnings just route through Claude Code, which can
|
|
54
|
-
ask for clarification itself. Do NOT WARN merely because the input
|
|
55
|
-
is short — short imperatives ("继续", "好") after a clear prior
|
|
56
|
-
reply are normal.
|
|
57
|
-
|
|
58
|
-
Translate only the prose; preserve verbatim:
|
|
59
|
-
- code blocks (fenced or indented), code fragments, anything in backticks
|
|
60
|
-
- identifier names, file paths, glob patterns, URLs
|
|
61
|
-
- command-line snippets, flags, environment variable names, git refs
|
|
62
|
-
- error messages quoted in the source
|
|
63
|
-
- numbers, units, version strings, hex / hash values
|
|
64
|
-
|
|
65
|
-
Style:
|
|
66
|
-
- Concise. Match the new input's length; do not pad with explanations.
|
|
67
|
-
- Imperative voice when the source is imperative; declarative otherwise.
|
|
68
|
-
- Silently fix obvious typos, missing words, and ungrammatical fragments. Common patterns from a Chinese IME:
|
|
69
|
-
· 同音/近音字混用: "在" ↔ "再", "的/地/得", "做/作", "象/像"
|
|
70
|
-
· 错别字: "测式" → "测试", "克立" → "克隆", "提价" → "提交"
|
|
71
|
-
· 漏字 / 多字: "一条录" → "一条记录"
|
|
72
|
-
The PRIOR_REPLY block is a strong disambiguation signal — if Claude just asked "Should I commit and push?" and the user types "提价 一下", "提价" is almost certainly "提交" given the context. ONLY fix when the intent is unambiguous; if uncertain, translate AS-IS.
|
|
73
|
-
- If the input is fragmentary, produce a complete grammatical English sentence reflecting the user's likely intent — using the prior reply as the disambiguation source, not as content to add.
|
|
74
|
-
- Aim for the register of a competent engineer writing a message to a colleague.`;
|
|
75
|
-
const EN_TO_ZH_BASE = `You are a professional translator. Claude Code (an AI coding assistant CLI) replies in English to a Chinese-speaking developer; your job is to render the English faithfully into natural, professional Simplified Chinese.
|
|
76
|
-
|
|
77
|
-
Output ONLY the translation. No preface, no quotation marks, no commentary.
|
|
78
|
-
|
|
79
|
-
Preserve verbatim (NEVER translate):
|
|
80
|
-
- code blocks (fenced or indented), code fragments, anything in backticks
|
|
81
|
-
- identifier names, file paths, glob patterns, URLs
|
|
82
|
-
- command-line snippets, flags, environment variable names, git refs
|
|
83
|
-
- error messages and stack traces inside code fences
|
|
84
|
-
- markdown structure (headings #, lists -/*/+, tables |, links, images, blockquotes >)
|
|
85
|
-
- numbers, units, version strings, hex / hash values
|
|
86
|
-
|
|
87
|
-
Translate only the prose around these.
|
|
88
|
-
|
|
89
|
-
Style:
|
|
90
|
-
- Concise. Match the source's length and structure.
|
|
91
|
-
- Natural Chinese technical writing conventions:
|
|
92
|
-
· 中英混排时,英文术语前后留半角空格(如 "调用 \`fetch\` 时")。
|
|
93
|
-
· 中文之间用全角标点(。,?!;:"" '')。
|
|
94
|
-
· 半角符号包裹的内容(括号 / 引号里全是英文)保持半角。
|
|
95
|
-
- Do not invent technical detail. If the English is ambiguous, leave the Chinese ambiguous; do not over-specify.
|
|
96
|
-
- The reader is a software engineer; sound like a Chinese-speaking engineer writing for them.`;
|
|
97
|
-
const PROMPT_BASES = {
|
|
98
|
-
"zh-to-en": ZH_TO_EN_BASE,
|
|
99
|
-
"zh-to-en-with-context": ZH_TO_EN_WITH_CONTEXT_BASE,
|
|
100
|
-
"en-to-zh": EN_TO_ZH_BASE
|
|
101
|
-
};
|
|
102
|
-
const PROMPT_LABELS = {
|
|
103
|
-
"zh-to-en": "zh-to-en",
|
|
104
|
-
"zh-to-en-with-context": "zh-to-en-with-context",
|
|
105
|
-
"en-to-zh": "en-to-zh"
|
|
106
|
-
};
|
|
107
|
-
export function buildTranslationPrompt(input) {
|
|
108
|
-
const key = getTranslationPromptKey(input);
|
|
109
|
-
if (input.direction === "user-input-to-english") {
|
|
110
|
-
if (key === "zh-to-en-with-context") {
|
|
111
|
-
return {
|
|
112
|
-
systemPrompt: resolveTranslationSystemPrompt(key, input.settings),
|
|
113
|
-
userPrompt: `[PRIOR CLAUDE CODE REPLY]\n${input.contextText}\n\n[NEW USER INPUT - translate only this]\n${input.text}`,
|
|
114
|
-
parseWarning: true
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
systemPrompt: resolveTranslationSystemPrompt(key, input.settings),
|
|
119
|
-
userPrompt: input.text,
|
|
120
|
-
parseWarning: false
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
systemPrompt: resolveTranslationSystemPrompt(key, input.settings),
|
|
125
|
-
userPrompt: input.text,
|
|
126
|
-
parseWarning: false
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
export function getTranslationPromptKey(input) {
|
|
130
|
-
if (input.direction === "user-input-to-english") {
|
|
131
|
-
return input.contextText?.trim()
|
|
132
|
-
? "zh-to-en-with-context"
|
|
133
|
-
: "zh-to-en";
|
|
134
|
-
}
|
|
135
|
-
return "en-to-zh";
|
|
136
|
-
}
|
|
137
|
-
export function getBaseTranslationPrompt(key, _settings) {
|
|
138
|
-
return PROMPT_BASES[key];
|
|
139
|
-
}
|
|
140
|
-
export function resolveTranslationSystemPrompt(key, settings) {
|
|
141
|
-
const override = settings.prompts?.[key];
|
|
142
|
-
return override?.trim() ? override : getBaseTranslationPrompt(key, settings);
|
|
143
|
-
}
|
|
144
|
-
export function getTranslationPromptPreviews(settings) {
|
|
145
|
-
return TRANSLATION_PROMPT_KEYS.map((key) => {
|
|
146
|
-
const defaultPrompt = getBaseTranslationPrompt(key, settings);
|
|
147
|
-
const userPrompt = settings.prompts?.[key]?.trim() ? settings.prompts[key] ?? "" : "";
|
|
148
|
-
return {
|
|
149
|
-
key,
|
|
150
|
-
label: PROMPT_LABELS[key],
|
|
151
|
-
defaultPrompt,
|
|
152
|
-
userPrompt,
|
|
153
|
-
customized: Boolean(userPrompt)
|
|
154
|
-
};
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
export function parseTranslationWarning(raw) {
|
|
158
|
-
const trimmed = raw.trim();
|
|
159
|
-
const firstNewline = trimmed.indexOf("\n");
|
|
160
|
-
if (firstNewline === -1) {
|
|
161
|
-
return { text: trimmed };
|
|
162
|
-
}
|
|
163
|
-
const firstLine = trimmed.slice(0, firstNewline).trim();
|
|
164
|
-
const rest = trimmed.slice(firstNewline + 1).trim();
|
|
165
|
-
if (firstLine === "OK") {
|
|
166
|
-
return { text: rest };
|
|
167
|
-
}
|
|
168
|
-
if (firstLine.startsWith("WARN:")) {
|
|
169
|
-
const warning = firstLine.slice("WARN:".length).trim();
|
|
170
|
-
return warning ? { warning, text: rest } : { text: rest };
|
|
171
|
-
}
|
|
172
|
-
return { text: trimmed };
|
|
173
|
-
}
|