// dsh-think-any-lang — Host half. // Registers the `think-any-lang` settings namespace (one `language` field) and // mounts a system-prompt section instructing the model to reason in that // language while a non-`off` value is selected. The browser half renders the // General-settings selector row. // // The browser half does NOT read/write the namespace through the settings RPC // surface (`settings.describe`/`settings.mutate`): the host API proxy only // exposes an explicit allowlist of namespaces to configuration clients // (WEB_SETTINGS_NAMESPACES / PRODUCT_SETTINGS_NAMESPACES in dsh-host-apiproxy), // and any third-party namespace answers `settings-not-exposed` even after its // owner registered it. Instead this half registers a private loopback RPC // channel (`/think-any-lang`) that proxies reads/writes into the settings // scope in-process — the seam itself is unrestricted, only the web wire layer // filters it — so persistence and the system-prompt linkage work as designed. // `@deepseek-ai/schemastery` publishes only the default `Schema` export (the // named `z` alias exists only in the harness source tree), so use default import. import z from '@deepseek-ai/schemastery' export const name = 'dsh-think-any-lang' export const inject = ['settings', 'systemPrompt', 'connection', 'webServer'] const NAMESPACE = 'think-any-lang' const FIELD = 'language' const SECTION_NAME = 'think-any-lang' const RPC_CHANNEL = '/think-any-lang' // The instruction is written in the target language itself: a model follows a // thinking-language directive written in that language more reliably. `off` // mounts no section at all. const LANGUAGES = { off: { instruction: null }, zh: { instruction: '请始终使用简体中文进行思考:你的推理、分析和内部思考过程(chain of thought)必须用中文书写,即使对话使用其他语言。最终回复仍然使用与用户一致的语言,但思考过程一律使用中文。', }, en: { instruction: "Always think and reason in English: write your chain of thought and analysis in English, even if the conversation uses another language. Keep the final reply in the user's language, but always think in English.", }, ja: { instruction: '常に日本語で思考してください。会話が他の言語でも、あなたの推論・分析・内部思考(chain of thought)は必ず日本語で書いてください。最終的な返信はユーザーの言語で構いませんが、思考は常に日本語で行ってください。', }, ko: { instruction: '항상 한국어로 생각하세요. 대화가 다른 언어로 진행되어도 추론·분석·내부 사고(chain of thought)는 반드시 한국어로 작성하세요. 최종 답변은 사용자 언어를 따르되, 생각은 항상 한국어로 하세요.', }, de: { instruction: 'Denken Sie immer auf Deutsch: Schreiben Sie Ihre Überlegungen, Analysen und Ihren internen Gedankengang (Chain of Thought) auf Deutsch, auch wenn die Konversation in einer anderen Sprache stattfindet. Die endgültige Antwort folgt der Sprache des Nutzers, aber denken Sie immer auf Deutsch.', }, fr: { instruction: "Réfléchissez toujours en français : écrivez votre raisonnement, votre analyse et votre processus de pensée interne (chain of thought) en français, même si la conversation se déroule dans une autre langue. La réponse finale reste dans la langue de l'utilisateur, mais réfléchissez toujours en français.", }, es: { instruction: 'Piensa siempre en español: escribe tu razonamiento, análisis y proceso de pensamiento interno (chain of thought) en español, incluso si la conversación es en otro idioma. La respuesta final mantiene el idioma del usuario, pero piensa siempre en español.', }, pt: { instruction: 'Pense sempre em português: escreva o seu raciocínio, análise e processo de pensamento interno (chain of thought) em português, mesmo que a conversa esteja noutro idioma. A resposta final mantém o idioma do utilizador, mas pense sempre em português.', }, ru: { instruction: 'Всегда думайте на русском языке: записывайте свои рассуждения, анализ и внутренний мыслительный процесс (chain of thought) на русском, даже если разговор идёт на другом языке. Итоговый ответ остаётся на языке пользователя, но думайте всегда на русском.', }, it: { instruction: "Pensa sempre in italiano: scrivi il tuo ragionamento, la tua analisi e il tuo processo di pensiero interno (chain of thought) in italiano, anche se la conversazione è in un'altra lingua. La risposta finale resta nella lingua dell'utente, ma pensa sempre in italiano.", }, ar: { instruction: 'فكر دائمًا باللغة العربية: اكتب تفكيرك وتحليلك وعملية تفكيرك الداخلية (chain of thought) باللغة العربية حتى لو كانت المحادثة بلغة أخرى. تظل الإجابة النهائية بلغة المستخدم، لكن فكر دائمًا بالعربية.', }, hi: { instruction: 'हमेशा हिंदी में सोचें: अपने तर्क, विश्लेषण और आंतरिक विचार प्रक्रिया (chain of thought) को हिंदी में लिखें, भले ही बातचीत किसी अन्य भाषा में हो। अंतिम उत्तर उपयोगकर्ता की भाषा में रहे, लेकिन हमेशा हिंदी में सोचें।', }, } const LANGS = Object.keys(LANGUAGES) const schema = z.object({ [FIELD]: z.union(LANGS).default('zh'), }) export function apply(ctx) { const scope = ctx.settings.register(NAMESPACE, schema, { applies: 'live' }) ctx.effect(() => { let disposeSection = null const sync = () => { if (disposeSection) { disposeSection() disposeSection = null } const instruction = LANGUAGES[scope.get()[FIELD]]?.instruction if (instruction) { disposeSection = ctx.systemPrompt.section({ name: SECTION_NAME, order: 10, text: instruction, }) } } sync() return scope.watch(() => { sync() }) }, 'think-any-lang: section sync') // Private loopback RPC channel consumed by the browser half. The settings // wire layer refuses non-allowlisted namespaces, so the selector row talks // to this channel instead; both endpoints operate on the same in-process // scope the system-prompt section watches. ctx.connection.rpc.handle(RPC_CHANNEL, async (endpoint, payload) => { if (endpoint === 'get') { return { ok: true, value: scope.get() } } if (endpoint === 'set') { const language = payload && payload[FIELD] if (typeof language !== 'string' || !LANGS.includes(language)) { return { ok: false, error: { code: 'bad-request', message: `language must be one of: ${LANGS.join(', ')}`, details: {}, }, } } try { await scope.update({ [FIELD]: language }) return { ok: true, value: scope.get() } } catch (error) { return { ok: false, error: { code: 'write-failed', message: error instanceof Error ? error.message : String(error), details: {}, }, } } } return { ok: false, error: { code: 'not-found', message: `unknown endpoint "${endpoint}"`, details: {}, }, } }, { authority: 'loopback' }) }