diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml
index 47a3ebcc7c..38e0292e93 100644
--- a/docs/capability-seams.i18n.yaml
+++ b/docs/capability-seams.i18n.yaml
@@ -6,5 +6,5 @@
en: 41d772192075e133
zh: 5ef3da69b4de1d8a
/capability-seams-and-core-services:
- en: 06fac1d7fa402fba
- zh: cc9b56495dc42e69
+ en: 33821c0f8d302ccf
+ zh: 3c3534f7e07bc884
diff --git a/docs/capability-seams.md b/docs/capability-seams.md
index 8a936f118c..fb59352634 100644
--- a/docs/capability-seams.md
+++ b/docs/capability-seams.md
@@ -7,6 +7,8 @@ A service can be a core spine service, a swappable capability seam, a bundle/com
```mermaid
flowchart LR
+ pkg_experimental_translator["experimental-translator"]
+ svc_translator["ctx.translator
Anonymous text translation"]
pkg_hmr["hmr"]
svc_hmr["ctx.hmr
Serialized module and configuration reloads"]
pkg_app_boot["app-boot"]
@@ -327,6 +329,7 @@ flowchart LR
pkg_experimental_ptc_runtime_python --> svc_ptcRuntime
pkg_experimental_speech_to_text --> svc_speechToText
pkg_experimental_speech_to_text_sensevoice --> svc_speechToText
+ pkg_experimental_translator --> svc_translator
pkg_file_reference --> svc_fileReferences
pkg_file_reference_local --> svc_fileReferences
pkg_fs --> svc_fs
@@ -571,6 +574,7 @@ flowchart LR
| ctx key | Role | Owner | Implementations | Direct consumers | Companion plugins | Note |
| --- | --- | --- | --- | --- | --- | --- |
+| `ctx.translator` | `service` | [`experimental-translator`](../packages/experimental/translator) | - | - | - | Resolves Google or Bing requests and bounds provider calls independently of Sessions. |
| `ctx.hmr` | `core` | [`hmr`](../packages/boot/hmr) | - | [`app-boot`](../packages/boot/app-boot) | - | Owns module and exact configuration watchers; application mutations share its queue and automatic reloads await the application file lock. |
| `ctx.pluginRegistryProbe` | `core` | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | Races public registry responses on the Host; the Client owns the initial registry recommendation. |
| `ctx.pluginManager` | `core` | [`plugin-manager`](../packages/boot/plugin-manager) | - | [`plugin-manager`](../packages/boot/plugin-manager), `ui-settings-plugin-inventory` | - | Shares profile package operations with the CLI and reports persisted and running state to Web and agent callers. |
diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md
index b7f2b311c7..a817c8dd22 100644
--- a/docs/capability-seams.zh.md
+++ b/docs/capability-seams.zh.md
@@ -9,6 +9,8 @@
```mermaid
flowchart LR
+ pkg_experimental_translator["experimental-translator"]
+ svc_translator["ctx.translator
Anonymous text translation"]
pkg_hmr["hmr"]
svc_hmr["ctx.hmr
Serialized module and configuration reloads"]
pkg_app_boot["app-boot"]
@@ -329,6 +331,7 @@ flowchart LR
pkg_experimental_ptc_runtime_python --> svc_ptcRuntime
pkg_experimental_speech_to_text --> svc_speechToText
pkg_experimental_speech_to_text_sensevoice --> svc_speechToText
+ pkg_experimental_translator --> svc_translator
pkg_file_reference --> svc_fileReferences
pkg_file_reference_local --> svc_fileReferences
pkg_fs --> svc_fs
@@ -573,6 +576,7 @@ flowchart LR
| ctx 键 | 角色 | 所属包 | 实现 | 直接消费方 | 配套插件 | 说明 |
| --- | --- | --- | --- | --- | --- | --- |
+| `ctx.translator` | `service` | [`experimental-translator`](../packages/experimental/translator) | - | - | - | 独立于 Session 解析 Google 或 Bing 请求,并限制 Provider 调用。 |
| `ctx.hmr` | `core` | [`hmr`](../packages/boot/hmr) | - | [`app-boot`](../packages/boot/app-boot) | - | 负责模块和精确配置监听;应用修改共用其队列,自动重载等待应用文件锁。 |
| `ctx.pluginRegistryProbe` | `core` | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | 在 Host 上并发比较公共安装源响应;初始安装源推荐由 Client 负责。 |
| `ctx.pluginManager` | `core` | [`plugin-manager`](../packages/boot/plugin-manager) | - | [`plugin-manager`](../packages/boot/plugin-manager), `ui-settings-plugin-inventory` | - | 与 CLI 共享 profile 包操作,并向 Web 和 Agent 调用方分别报告持久状态与运行状态。 |
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index 2d38ab513d..1b3c4a45d3 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -1225,6 +1225,35 @@ export interface Config {
```
+
+
+
+## `@deepseek-ai/dsh-experimental-translator`
+
+- `source`: [`packages/experimental/translator/src/index.ts:19`](../packages/experimental/translator/src/index.ts)
+
+```ts config-catalog
+/** Routing and bounds for one anonymous provider request. */
+export interface Config {
+ /** Provider selected when a consumer omits it. */
+ provider: TranslationProvider
+ /** Google-compatible anonymous translation endpoint. */
+ googleEndpoint: string
+ /** Bing-compatible Microsoft Edge browser translation endpoint. */
+ bingEndpoint: string
+ /** Deadline covering the request and complete response body, in milliseconds. */
+ timeoutMs: number
+ /** Maximum UTF-16 code units of submitted text per request. */
+ maxTextChars: number
+ /** Maximum response body bytes before JSON parsing. */
+ maxResponseBytes: number
+}
+
+/** Anonymous browser endpoint selected for one translation. */
+export type TranslationProvider = 'google' | 'bing'
+```
+
+
diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md
index 462a637a8c..c7b675a049 100644
--- a/docs/config-catalog.zh.md
+++ b/docs/config-catalog.zh.md
@@ -1227,6 +1227,35 @@ export interface Config {
```
+
+
+
+## `@deepseek-ai/dsh-experimental-translator`
+
+- `source`: [`packages/experimental/translator/src/index.ts:19`](../packages/experimental/translator/src/index.ts)
+
+```ts config-catalog
+/** Routing and bounds for one anonymous provider request. */
+export interface Config {
+ /** Provider selected when a consumer omits it. */
+ provider: TranslationProvider
+ /** Google-compatible anonymous translation endpoint. */
+ googleEndpoint: string
+ /** Bing-compatible Microsoft Edge browser translation endpoint. */
+ bingEndpoint: string
+ /** Deadline covering the request and complete response body, in milliseconds. */
+ timeoutMs: number
+ /** Maximum UTF-16 code units of submitted text per request. */
+ maxTextChars: number
+ /** Maximum response body bytes before JSON parsing. */
+ maxResponseBytes: number
+}
+
+/** Anonymous browser endpoint selected for one translation. */
+export type TranslationProvider = 'google' | 'bing'
+```
+
+
diff --git a/docs/module-graph.md b/docs/module-graph.md
index 45e6681108..b091be16a6 100644
--- a/docs/module-graph.md
+++ b/docs/module-graph.md
@@ -267,6 +267,7 @@ flowchart TD
pkg_experimental_speech_to_text["experimental-speech-to-text"]
pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"]
pkg_experimental_tool_agent_team["experimental-tool-agent-team"]
+ pkg_experimental_translator["experimental-translator"]
pkg_experimental_voice_input_bundle["experimental-voice-input-bundle"]
pkg_experimental_webworker_packer["experimental-webworker-packer"]
pkg_experimental_webworker_runtime["experimental-webworker-runtime"]
@@ -451,6 +452,8 @@ flowchart TD
pkg_credentials_local --> pkg_credentials
pkg_credentials_local --> pkg_home_paths
pkg_credentials_local --> pkg_launch_environment
+ pkg_experimental_translator --> pkg_timeout
+ pkg_experimental_translator --> pkg_util_values
pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse
pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_native
pkg_host_directory_picker_auto --> pkg_host_directory_picker_browse
@@ -1500,6 +1503,7 @@ flowchart TD
| [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) |
| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`llm`](../packages/llm/llm) |
| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) |
+| [`experimental-translator`](../packages/experimental/translator) | `experimental` | [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) |
| [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) |
| [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths) |
| [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) |
diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md
index cb80078509..38e2ac0811 100644
--- a/docs/module-graph.zh.md
+++ b/docs/module-graph.zh.md
@@ -269,6 +269,7 @@ flowchart TD
pkg_experimental_speech_to_text["experimental-speech-to-text"]
pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"]
pkg_experimental_tool_agent_team["experimental-tool-agent-team"]
+ pkg_experimental_translator["experimental-translator"]
pkg_experimental_voice_input_bundle["experimental-voice-input-bundle"]
pkg_experimental_webworker_packer["experimental-webworker-packer"]
pkg_experimental_webworker_runtime["experimental-webworker-runtime"]
@@ -453,6 +454,8 @@ flowchart TD
pkg_credentials_local --> pkg_credentials
pkg_credentials_local --> pkg_home_paths
pkg_credentials_local --> pkg_launch_environment
+ pkg_experimental_translator --> pkg_timeout
+ pkg_experimental_translator --> pkg_util_values
pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse
pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_native
pkg_host_directory_picker_auto --> pkg_host_directory_picker_browse
@@ -1502,6 +1505,7 @@ flowchart TD
| [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) |
| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`llm`](../packages/llm/llm) |
| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) |
+| [`experimental-translator`](../packages/experimental/translator) | `experimental` | [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) |
| [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) |
| [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths) |
| [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) |
diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml
index e22cd2af7c..d26f4a3939 100644
--- a/docs/subsystems/README.i18n.yaml
+++ b/docs/subsystems/README.i18n.yaml
@@ -3,5 +3,5 @@
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/README.md
/subsystems:
- en: d1e3439cb56ffc96
- zh: 18e3970ad3f6237b
+ en: 812a07d10624df7e
+ zh: 53866e572aeb7977
diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md
index 37261976ba..5ce965fe88 100644
--- a/docs/subsystems/README.md
+++ b/docs/subsystems/README.md
@@ -47,6 +47,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
| [compaction.md](compaction.md) | the compaction seam: the `compaction/*` session events, `CompactionResult`, the `CompactionEngine` interface |
| [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |
| [voice-input.md](voice-input.md) | experimental named recognizers, transient audio and revision-guarded draft insertion |
+| [translation.md](translation.md) | experimental anonymous text translation and explicit provider requests |
| [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, durable peer mailbox, and shared task DAG |
| [claude-code-mods.md](claude-code-mods.md) | Claude Code mods through the experimental bridge: every event, `$` member, chain rule, drawing feature, and example mod that differs from Claude Code |
| [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` |
diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md
index 6b9ca4e66f..796c814081 100644
--- a/docs/subsystems/README.zh.md
+++ b/docs/subsystems/README.zh.md
@@ -47,6 +47,7 @@
| [compaction.md](compaction.zh.md) | 压缩(compaction)seam:`compaction/*` 会话事件、`CompactionResult`、`CompactionEngine` 接口 |
| [subagent.md](subagent.zh.md) | subagent seam:命名提供方注册表、`SubagentStartRequest`/`Result`/`Run`、启动时与运行时能力拆分 |
| [voice-input.md](voice-input.zh.md) | 实验性具名语音识别、临时音频和受版本保护的草稿插入 |
+| [translation.md](translation.zh.md) | 实验性免登录文本翻译与显式 Provider 请求 |
| [agent-team.md](agent-team.zh.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、持久 peer mailbox 与共享任务 DAG |
| [claude-code-mods.md](claude-code-mods.zh.md) | 经实验性桥接运行的 Claude Code 模组:与 Claude Code 不同的每个事件、`$` 成员、链规则、绘制特性与示例模组 |
| [web.md](web.zh.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` |
diff --git a/docs/subsystems/translation.i18n.yaml b/docs/subsystems/translation.i18n.yaml
new file mode 100644
index 0000000000..9ca8e1deb1
--- /dev/null
+++ b/docs/subsystems/translation.i18n.yaml
@@ -0,0 +1,13 @@
+# Bilingual-pair consistency record for translation.md (docs/i18n/README.md): per heading
+# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
+# After editing either side, bring the other along and re-record with:
+# pnpm run verify-translation-pairing --write docs/subsystems/translation.md
+/text-translation:
+ en: b1e47fe6996df09d
+ zh: 974f44f8dae7aba0
+/text-translation/requests-and-results:
+ en: 8fe5bebd5030a01f
+ zh: 185250651bbc0831
+/text-translation/failures-and-data-handling:
+ en: 5d616af4369e04aa
+ zh: 9bfa38d61ee3e150
diff --git a/docs/subsystems/translation.md b/docs/subsystems/translation.md
new file mode 100644
index 0000000000..c5a7c9697e
--- /dev/null
+++ b/docs/subsystems/translation.md
@@ -0,0 +1,51 @@
+# Text translation
+
+English | [中文](translation.zh.md)
+
+The experimental [translator](../../packages/experimental/translator/README.md) translates text through anonymous Google or Bing browser endpoints. It runs independently of Agents and Sessions; consumers own display, scheduling, and retention.
+
+## Requests and results
+
+`TranslationProvider` selects `google` or `bing`. `TranslationRequest` supplies `text`, `targetLanguage`, and optional `sourceLanguage` and `provider`. `resolve()` captures explicit routing in `TranslationSpec`: the configured provider defaults to Google and omitted source language uses automatic detection. Common Chinese locale tags map to the selected provider's language codes. `translate()` returns plain text from the resolved provider.
+
+The service exposes its configured `maxTextChars`, measured in UTF-16 code units. Consumers split longer inputs before requesting translation. Each call bounds the response bytes and includes the response-body read in its deadline. Caller cancellation and service disposal abort accepted requests; disposal waits for them to settle.
+
+## Failures and data handling
+
+`TranslationError` carries a `TranslationErrorCode` distinguishing text limits, HTTP failures, invalid responses, response limits, and request failures. Provider diagnostics omit submitted text and response bodies. Failed calls do not select another provider. Responses are validated as external JSON and redirects are rejected.
+
+Google receives source text in the query URL; Bing receives it in a JSON request body. Both are remote services. Their anonymous endpoints provide no supported developer availability or quota guarantee. Regional connectivity must be verified on the user's network.
+
+
+
+
+
+## Cordis API
+
+Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
+
+
+
+### `ctx.translator` — `Translator`
+
+One Host service with explicit routing, cancellation and quiescent unload.
+
+```ts cordis-catalog
+/**
+ * Resolve provider and source-language defaults without sending text.
+ * @param request - consumer text, destination and optional routing choices.
+ * @returns a complete specification; exceeding `maxTextChars` throws `TRANSLATION_TEXT_LIMIT`.
+ */
+resolve(request: TranslationRequest): TranslationSpec
+
+/**
+ * Translate one resolved specification; the selected provider receives its text.
+ * @param spec - complete routing and language choices from `resolve()`.
+ * @param signal - optional caller cancellation, combined with service disposal.
+ * @returns translated plain text; rejects provider/limit failures and preserves cancellation reasons.
+ */
+translate(spec: TranslationSpec, signal?: AbortSignal): Promise
+```
+
+Source: [`packages/experimental/translator/src/index.ts`](../../packages/experimental/translator/src/index.ts)
+
diff --git a/docs/subsystems/translation.zh.md b/docs/subsystems/translation.zh.md
new file mode 100644
index 0000000000..4edd902cbf
--- /dev/null
+++ b/docs/subsystems/translation.zh.md
@@ -0,0 +1,51 @@
+# 文本翻译
+
+[English](translation.md) | 中文
+
+实验性 [translator](../../packages/experimental/translator/README.zh.md) 通过免登录 Google 或 Bing 浏览器端点翻译文本。它独立于 Agent 和 Session 运行;消费者负责展示、调度与保留结果。
+
+## 请求与结果
+
+`TranslationProvider` 选择 `google` 或 `bing`。`TranslationRequest` 提供 `text`、`targetLanguage` 及可选的 `sourceLanguage` 和 `provider`。`resolve()` 将显式路由写入 `TranslationSpec`:配置的默认 Provider 为 Google,省略源语言时自动检测。常见中文 locale 标签映射为所选 Provider 的语言代码。`translate()` 返回所选 Provider 翻译后的纯文本。
+
+服务公开配置的 `maxTextChars`,单位为 UTF-16 code unit。消费者先拆分超长输入,再请求翻译。每次调用限制响应字节数,并将响应体读取计入超时。调用者取消和服务卸载都会中止已接收的请求;卸载等待这些请求完成。
+
+## 失败与数据处理
+
+`TranslationError` 的 `TranslationErrorCode` 区分文本超限、HTTP 失败、无效响应、响应超限与请求失败。Provider 错误信息省略提交的文本与响应体。失败时不选择其他 Provider。响应作为外部 JSON 校验,重定向被拒绝。
+
+Google 在查询 URL 中接收原文,Bing 在 JSON 请求体中接收原文。两者都是远端服务。免登录端点不保证开发者 API 的可用性或免费额度。区域连通性须在用户网络上验证。
+
+
+
+
+
+## Cordis API
+
+Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
+
+
+
+### `ctx.translator` — `Translator`
+
+One Host service with explicit routing, cancellation and quiescent unload.
+
+```ts cordis-catalog
+/**
+ * Resolve provider and source-language defaults without sending text.
+ * @param request - consumer text, destination and optional routing choices.
+ * @returns a complete specification; exceeding `maxTextChars` throws `TRANSLATION_TEXT_LIMIT`.
+ */
+resolve(request: TranslationRequest): TranslationSpec
+
+/**
+ * Translate one resolved specification; the selected provider receives its text.
+ * @param spec - complete routing and language choices from `resolve()`.
+ * @param signal - optional caller cancellation, combined with service disposal.
+ * @returns translated plain text; rejects provider/limit failures and preserves cancellation reasons.
+ */
+translate(spec: TranslationSpec, signal?: AbortSignal): Promise
+```
+
+Source: [`packages/experimental/translator/src/index.ts`](../../packages/experimental/translator/src/index.ts)
+
diff --git a/packages/experimental/README.i18n.yaml b/packages/experimental/README.i18n.yaml
index 5209aeff1e..a8df9ddb24 100644
--- a/packages/experimental/README.i18n.yaml
+++ b/packages/experimental/README.i18n.yaml
@@ -15,8 +15,8 @@
en: 1eb35b015a6c423f
zh: a8a3dceee2c9e943
/packages-experimental/packages:
- en: f62358cfdf93a98c
- zh: dd40ddceec13490c
+ en: 78f516ce8ced456e
+ zh: bab52089e5a652d9
/packages-experimental/related-documentation:
en: f8ab36525b5a89ec
zh: 1e828d1993e102f8
diff --git a/packages/experimental/README.md b/packages/experimental/README.md
index f9f29fd9c2..aadb2653b7 100644
--- a/packages/experimental/README.md
+++ b/packages/experimental/README.md
@@ -24,6 +24,7 @@ Experimental prototypes may change their contracts and carry no support promise.
| Package | Role | ctx key |
|---|---|---|
+| [`translator`](translator/README.md) | Anonymous Google and Bing text translation | `ctx.translator` |
| [`speech-to-text`](speech-to-text/README.md) | Named speech recognition providers | `ctx.speechToText` |
| [`speech-to-text-sensevoice`](speech-to-text-sensevoice/README.md) | Managed local SenseVoice inference | — |
| [`api-speech-to-text`](api-speech-to-text/README.md) | Authenticated transient transcription Remote | `ctx.speechController` |
diff --git a/packages/experimental/README.zh.md b/packages/experimental/README.zh.md
index 49897f940c..0aa3df8b5d 100644
--- a/packages/experimental/README.zh.md
+++ b/packages/experimental/README.zh.md
@@ -24,6 +24,7 @@ kind: "package-group"
| 包 | 职责 | ctx 键 |
|---|---|---|
+| [`translator`](translator/README.zh.md) | 免登录 Google 与 Bing 文本翻译 | `ctx.translator` |
| [`speech-to-text`](speech-to-text/README.zh.md) | 具名语音识别 Provider | `ctx.speechToText` |
| [`speech-to-text-sensevoice`](speech-to-text-sensevoice/README.zh.md) | 托管本地 SenseVoice 推理 | — |
| [`api-speech-to-text`](api-speech-to-text/README.zh.md) | 带认证的临时转写 Remote | `ctx.speechController` |
diff --git a/packages/experimental/translator/README.i18n.yaml b/packages/experimental/translator/README.i18n.yaml
new file mode 100644
index 0000000000..07f715dbd1
--- /dev/null
+++ b/packages/experimental/translator/README.i18n.yaml
@@ -0,0 +1,37 @@
+# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
+# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
+# After editing either side, bring the other along and re-record with:
+# pnpm run verify-translation-pairing --write packages/experimental/translator/README.md
+/:
+ en: 61df7e6d8c9c7f65
+ zh: 54500d58d2874267
+/deepseek-ai-dsh-experimental-translator:
+ en: de5ee8ccf9094431
+ zh: ca8ed948d6fb6553
+/deepseek-ai-dsh-experimental-translator/summary:
+ en: fcb182cba2a0a0f2
+ zh: 572c57a1f3f1b7bd
+/deepseek-ai-dsh-experimental-translator/table-of-contents:
+ en: d152484eb41ac6b4
+ zh: bdb230156938797d
+/deepseek-ai-dsh-experimental-translator/use-this-package:
+ en: ffb7a5b392910393
+ zh: cc6652f3a0bba01c
+/deepseek-ai-dsh-experimental-translator/understand-the-implementation:
+ en: 716573bfdb1b2e0d
+ zh: 59ac75672cb77e98
+/deepseek-ai-dsh-experimental-translator/further-exploration:
+ en: 17792a9fe5429633
+ zh: b51e5e3c30b0e14d
+/deepseek-ai-dsh-experimental-translator/model-experience:
+ en: e5107394c680a585
+ zh: 756a674220213e2a
+/deepseek-ai-dsh-experimental-translator/model-experience/kv-cache-effect:
+ en: d14db5986844ccca
+ zh: 2e67f5a50b4cc9e4
+/deepseek-ai-dsh-experimental-translator/known-limitations-and-deferred-work:
+ en: f84dfde870cb510e
+ zh: 695c14ff852b39a8
+/deepseek-ai-dsh-experimental-translator/known-limitations-and-deferred-work/dev-note:
+ en: 0225ba087c5266f3
+ zh: f98959d61a712c7a
diff --git a/packages/experimental/translator/README.md b/packages/experimental/translator/README.md
new file mode 100644
index 0000000000..65285e9c02
--- /dev/null
+++ b/packages/experimental/translator/README.md
@@ -0,0 +1,92 @@
+---
+description: "Translate transient text through anonymous Google and Bing browser endpoints."
+kind: "package-reference"
+---
+
+# @deepseek-ai/dsh-experimental-translator
+
+English | [中文](README.zh.md)
+
+## Summary
+
+`ctx.translator` translates one bounded text request through Google or Bing. It requires no API key or login and defaults to Google. This experimental package is opt-in.
+
+## Table of Contents
+
+- [Use this package](#use-this-package)
+- [Understand the implementation](#understand-the-implementation)
+- [Further Exploration](#further-exploration)
+- [Model Experience](#model-experience)
+- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
+- [Dev Note](#dev-note)
+
+-----
+
+
+## Use this package
+
+Mount the package as a Cordis service. Call `resolve({ text, targetLanguage, sourceLanguage?, provider? })`, then `translate(spec, signal?)`. The result is plain translated text. An omitted source uses `auto`; an omitted provider uses configured `provider`, initially `google`. Consumers can read `maxTextChars` to split longer text before submission.
+
+| Config | Default | Meaning |
+|---|---|---|
+| `provider` | `google` | `google` or `bing` |
+| `googleEndpoint` | `https://translate.googleapis.com/translate_a/single` | Google-compatible anonymous endpoint |
+| `bingEndpoint` | `https://edge.microsoft.com/translate/translatetext` | Microsoft Edge browser translation endpoint |
+| `timeoutMs` | `10000` | Deadline for the request and complete response body |
+| `maxTextChars` | `4000` | Maximum submitted UTF-16 code units per request |
+| `maxResponseBytes` | `1048576` | Maximum response body bytes before JSON parsing |
+
+Endpoints accept HTTP(S) URLs without credentials or fragments. Native `fetch` uses the Host's global dispatcher and the HTTP proxy policy installed by the `dsh` launcher. No new translation library is required.
+
+The service maps `zh`, `zh-CN`, `zh-SG` and `zh-Hans` to Simplified Chinese, and `zh-TW`, `zh-HK`, `zh-MO` and `zh-Hant` to Traditional Chinese. Other tags pass through to the selected provider. Empty text returns an empty string without a network request.
+
+-----
+
+
+## Understand the implementation
+
+
+Maintainer details — click to expand
+
+Google receives a GET request using the `gtx` client; Bing receives a JSON text array through Microsoft's Edge endpoint. The selected service receives the submitted text. Cookies, HTTP redirects and automatic provider fallback are disabled. Text and translations remain transient; this service changes neither Session events nor model requests.
+
+`TranslationError.code` distinguishes input limits, HTTP failures, response limits, invalid provider responses and transport failures. Diagnostics include no submitted text or provider error body. Caller cancellation and timeouts retain their original abort reasons. Unloading aborts accepted requests and waits for settlement. Input limits apply in both `resolve()` and `translate()`.
+
+
+
+-----
+
+
+## Further Exploration
+
+[Experimental packages](../README.md) · [HTTP proxy behavior](../../util/http-proxy/README.md)
+
+-----
+
+
+## Model Experience
+
+None, as translation is transient and does not contribute to model requests or Session logs.
+
+#### KV Cache effect
+
+No direct effect; translation does not change model history.
+
+## Known Limitations and Deferred Work
+
+
+
+- These unofficial browser endpoints have no supported third-party API guarantee. They may rate-limit requests, become unavailable or change response formats. Availability from mainland China depends on the user's network; the service guarantees no region-wide connectivity.
+- Unsupported language tags fail through the selected provider. Consumers own paragraph scheduling, input splitting, caching and any display fallback; this service performs one request and never retries or switches providers.
+
+-----
+
+
+### Dev Note
+
+
+Maintainer details — click to expand
+
+None.
+
+
diff --git a/packages/experimental/translator/README.zh.md b/packages/experimental/translator/README.zh.md
new file mode 100644
index 0000000000..ef999a2700
--- /dev/null
+++ b/packages/experimental/translator/README.zh.md
@@ -0,0 +1,92 @@
+---
+description: "通过无需登录的 Google 和 Bing 浏览器端点翻译临时文本。"
+kind: "package-reference"
+---
+
+# @deepseek-ai/dsh-experimental-translator
+
+[English](README.md) | 中文
+
+## 概述
+
+`ctx.translator` 通过 Google 或 Bing 翻译一次有大小限制的文本请求。它无需 API 密钥或登录,默认使用 Google。此实验包需要显式启用。
+
+## 目录
+
+- [使用本包](#use-this-package)
+- [理解实现](#understand-the-implementation)
+- [进一步探索](#further-exploration)
+- [模型体验](#model-experience)
+- [已知限制与待办事项](#known-limitations-and-deferred-work)
+- [开发者备注](#dev-note)
+
+-----
+
+
+## 使用本包
+
+将本包挂载为 Cordis 服务。先调用 `resolve({ text, targetLanguage, sourceLanguage?, provider? })`,再调用 `translate(spec, signal?)`,结果为翻译后的纯文本。省略源语言时使用 `auto`;省略提供者时使用配置中的 `provider`,初始为 `google`。消费者可以读取 `maxTextChars`,在提交前拆分较长文本。
+
+| 配置 | 默认值 | 含义 |
+|---|---|---|
+| `provider` | `google` | `google` 或 `bing` |
+| `googleEndpoint` | `https://translate.googleapis.com/translate_a/single` | 兼容 Google 的匿名端点 |
+| `bingEndpoint` | `https://edge.microsoft.com/translate/translatetext` | Microsoft Edge 浏览器翻译端点 |
+| `timeoutMs` | `10000` | 请求及完整响应正文的截止时间 |
+| `maxTextChars` | `4000` | 每次请求提交文本的 UTF-16 代码单元上限 |
+| `maxResponseBytes` | `1048576` | JSON 解析前响应正文的字节上限 |
+
+端点接受不含凭据或片段的 HTTP(S) URL。原生 `fetch` 使用 Host 的全局分发器及 `dsh` 启动器安装的 HTTP 代理策略。无需新增翻译库。
+
+服务将 `zh`、`zh-CN`、`zh-SG` 和 `zh-Hans` 映射为简体中文,将 `zh-TW`、`zh-HK`、`zh-MO` 和 `zh-Hant` 映射为繁体中文。其他标签原样传给所选提供者。空文本直接返回空字符串,不发送网络请求。
+
+-----
+
+
+## 理解实现
+
+
+维护者细节 — 点击展开
+
+Google 接收使用 `gtx` 客户端的 GET 请求;Bing 通过 Microsoft Edge 端点接收 JSON 文本数组。所选服务会收到提交的文本。Cookie、HTTP 重定向和自动切换提供者均已禁用。文本和译文保持临时状态;本服务不会修改 Session 事件或模型请求。
+
+`TranslationError.code` 区分输入限制、HTTP 失败、响应限制、无效的提供者响应及传输失败。诊断不包含提交的文本或提供者错误正文。调用者取消和超时保留原始中止原因。卸载时中止已接受的请求并等待它们结束。`resolve()` 和 `translate()` 都执行输入限制。
+
+
+
+-----
+
+
+## 进一步探索
+
+[实验包](../README.zh.md) · [HTTP 代理行为](../../util/http-proxy/README.zh.md)
+
+-----
+
+
+## 模型体验
+
+无,因为翻译是临时操作,不会加入模型请求或 Session 日志。
+
+#### KV 缓存影响
+
+无直接影响;翻译不会改变模型历史。
+
+## 已知限制与待办事项
+
+
+
+- 这些非官方浏览器端点不保证支持第三方 API 调用。它们可能限流、不可用或更改响应格式。在中国大陆的可用性取决于用户网络;服务不保证覆盖整个地区的连接能力。
+- 不支持的语言标签由所选提供者报错。消费者负责段落调度、输入拆分、缓存及显示回退;本服务只执行一次请求,不重试或切换提供者。
+
+-----
+
+
+### 开发者备注
+
+
+维护者细节 — 点击展开
+
+无。
+
+
diff --git a/packages/experimental/translator/package.json b/packages/experimental/translator/package.json
new file mode 100644
index 0000000000..603ffd7620
--- /dev/null
+++ b/packages/experimental/translator/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "@deepseek-ai/dsh-experimental-translator",
+ "description": "Cancellation-aware anonymous Google and Bing text translation",
+ "version": "0.2.1-alpha.1",
+ "publishConfig": { "access": "public" },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
+ "directory": "packages/experimental/translator"
+ },
+ "type": "module",
+ "main": "lib/index.js",
+ "types": "lib/types/index.d.ts",
+ "exports": {
+ ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
+ "./src/*": "./src/*",
+ "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" },
+ "./package.json": "./package.json"
+ },
+ "files": ["lib/index.js", "lib/types/**/*.js", "lib/types/**/*.d.ts"],
+ "license": "MIT",
+ "dependencies": { "@deepseek-ai/schemastery": "workspace:~" },
+ "peerDependencies": {
+ "@deepseek-ai/cordis": "workspace:~",
+ "@deepseek-ai/dsh-timeout": "workspace:*",
+ "@deepseek-ai/dsh-util-values": "workspace:*"
+ },
+ "devDependencies": {
+ "@deepseek-ai/cordis": "workspace:~",
+ "@deepseek-ai/dsh-app-boot": "workspace:*",
+ "@deepseek-ai/dsh-timeout": "workspace:*",
+ "@deepseek-ai/dsh-util-values": "workspace:*"
+ }
+}
diff --git a/packages/experimental/translator/src/error.ts b/packages/experimental/translator/src/error.ts
new file mode 100644
index 0000000000..0735dbf463
--- /dev/null
+++ b/packages/experimental/translator/src/error.ts
@@ -0,0 +1,15 @@
+/** Safe translation diagnostics that never include submitted text or response bodies. */
+import type { TranslationErrorCode } from './types.ts'
+
+/** Provider failure or configured request/response limit rejection. */
+export class TranslationError extends Error {
+ override name = 'TranslationError'
+
+ /**
+ * @param code - failure classification independent of localized UI wording.
+ * @param message - diagnostic without source text or provider response content.
+ */
+ constructor(readonly code: TranslationErrorCode, message: string) {
+ super(message)
+ }
+}
diff --git a/packages/experimental/translator/src/index.ts b/packages/experimental/translator/src/index.ts
new file mode 100644
index 0000000000..9e9ada768d
--- /dev/null
+++ b/packages/experimental/translator/src/index.ts
@@ -0,0 +1,100 @@
+/** Transient text translation through configurable anonymous Google and Bing endpoints. */
+import { Context, Service } from '@deepseek-ai/cordis'
+import z from '@deepseek-ai/schemastery'
+import type { TranslationProvider, TranslationRequest, TranslationSpec } from './types.ts'
+import { TranslationError } from './error.ts'
+import { translateText } from './provider.ts'
+
+export type * from './types.ts'
+export { TranslationError } from './error.ts'
+
+declare module '@deepseek-ai/cordis' {
+ interface Context {
+ /** Experimental transient text translator. */
+ translator: Translator
+ }
+}
+
+/** Routing and bounds for one anonymous provider request. */
+export interface Config {
+ /** Provider selected when a consumer omits it. */
+ provider: TranslationProvider
+ /** Google-compatible anonymous translation endpoint. */
+ googleEndpoint: string
+ /** Bing-compatible Microsoft Edge browser translation endpoint. */
+ bingEndpoint: string
+ /** Deadline covering the request and complete response body, in milliseconds. */
+ timeoutMs: number
+ /** Maximum UTF-16 code units of submitted text per request. */
+ maxTextChars: number
+ /** Maximum response body bytes before JSON parsing. */
+ maxResponseBytes: number
+}
+
+function endpoint(value: string): string {
+ const url = new URL(value)
+ if (!['http:', 'https:'].includes(url.protocol) || url.username !== '' || url.password !== '' || url.hash !== '') {
+ throw new Error('Translation endpoint must be an HTTP(S) URL without credentials or a fragment')
+ }
+ return url.href
+}
+
+/** One Host service with explicit routing, cancellation and quiescent unload. */
+export default class Translator extends Service {
+ static Config = z.object({
+ provider: z.union(['google', 'bing'] as const).default('google'),
+ googleEndpoint: z.transform(z.string().default('https://translate.googleapis.com/translate_a/single'), endpoint),
+ bingEndpoint: z.transform(z.string().default('https://edge.microsoft.com/translate/translatetext'), endpoint),
+ timeoutMs: z.natural().min(1).max(2_147_483_647).default(10_000),
+ maxTextChars: z.natural().min(2).default(4_000),
+ maxResponseBytes: z.natural().min(1).default(1024 * 1024),
+ })
+
+ private readonly lifetime = new AbortController()
+ private readonly pending = new Set>()
+
+ constructor(ctx: Context, private readonly config: Config) {
+ super(ctx, 'translator')
+ ctx.effect(() => async () => {
+ this.lifetime.abort(new Error('Translator service disposed'))
+ await Promise.allSettled(this.pending)
+ })
+ }
+
+ /** Maximum UTF-16 code units accepted by one provider request. */
+ get maxTextChars(): number { return this.config.maxTextChars }
+
+ /**
+ * Resolve provider and source-language defaults without sending text.
+ * @param request - consumer text, destination and optional routing choices.
+ * @returns a complete specification; exceeding `maxTextChars` throws `TRANSLATION_TEXT_LIMIT`.
+ */
+ resolve(request: TranslationRequest): TranslationSpec {
+ this.lifetime.signal.throwIfAborted()
+ this.assertTextLimit(request.text)
+ return { text: request.text, targetLanguage: request.targetLanguage,
+ sourceLanguage: request.sourceLanguage ?? 'auto', provider: request.provider ?? this.config.provider }
+ }
+
+ /**
+ * Translate one resolved specification; the selected provider receives its text.
+ * @param spec - complete routing and language choices from `resolve()`.
+ * @param signal - optional caller cancellation, combined with service disposal.
+ * @returns translated plain text; rejects provider/limit failures and preserves cancellation reasons.
+ */
+ translate(spec: TranslationSpec, signal?: AbortSignal): Promise {
+ this.lifetime.signal.throwIfAborted()
+ this.assertTextLimit(spec.text)
+ const combined = signal === undefined ? this.lifetime.signal : AbortSignal.any([signal, this.lifetime.signal])
+ combined.throwIfAborted()
+ const task = translateText(spec, this.config, combined)
+ this.pending.add(task)
+ return task.finally(() => { this.pending.delete(task) })
+ }
+
+ private assertTextLimit(text: string): void {
+ if (text.length > this.config.maxTextChars) {
+ throw new TranslationError('TRANSLATION_TEXT_LIMIT', `Translation text exceeds ${this.config.maxTextChars} UTF-16 code units`)
+ }
+ }
+}
diff --git a/packages/experimental/translator/src/provider.ts b/packages/experimental/translator/src/provider.ts
new file mode 100644
index 0000000000..c4cf40ce2e
--- /dev/null
+++ b/packages/experimental/translator/src/provider.ts
@@ -0,0 +1,126 @@
+/** Anonymous provider protocols, bounded JSON intake and normalized Chinese language tags. */
+import { deadline } from '@deepseek-ai/dsh-timeout'
+import { assertNever } from '@deepseek-ai/dsh-util-values'
+import type { Config } from './index.ts'
+import type { TranslationProvider, TranslationSpec } from './types.ts'
+import { TranslationError } from './error.ts'
+
+function language(tag: string, provider: TranslationProvider): string {
+ const lower = tag.toLowerCase()
+ if (['zh', 'zh-cn', 'zh-sg', 'zh-hans'].includes(lower)) return provider === 'google' ? 'zh-CN' : 'zh-Hans'
+ if (['zh-tw', 'zh-hk', 'zh-mo', 'zh-hant'].includes(lower)) return provider === 'google' ? 'zh-TW' : 'zh-Hant'
+ return tag
+}
+
+function requestFor(spec: TranslationSpec, config: Config): { url: URL; init: RequestInit } {
+ const source = language(spec.sourceLanguage, spec.provider)
+ const target = language(spec.targetLanguage, spec.provider)
+ switch (spec.provider) {
+ case 'google': {
+ const url = new URL(config.googleEndpoint)
+ for (const [key, value] of Object.entries({ client: 'gtx', sl: source, tl: target, dt: 't', q: spec.text })) {
+ url.searchParams.set(key, value)
+ }
+ return { url, init: { method: 'GET' } }
+ }
+ case 'bing': {
+ const url = new URL(config.bingEndpoint)
+ if (source !== 'auto') url.searchParams.set('from', source)
+ else url.searchParams.delete('from')
+ url.searchParams.set('to', target)
+ url.searchParams.set('isEnterpriseClient', 'false')
+ return { url, init: { method: 'POST', body: JSON.stringify([spec.text]),
+ headers: { 'content-type': 'application/json', 'user-agent': 'Mozilla/5.0 (compatible; DeepSeekHarnessTranslator)' } } }
+ }
+ /* v8 ignore next -- TranslationProvider is a closed typed union; JSON/config validation owns admission. */
+ default: return assertNever(spec.provider)
+ }
+}
+
+function invalidResponse(provider: TranslationProvider): TranslationError {
+ return new TranslationError('TRANSLATION_INVALID_RESPONSE', `${provider} returned an invalid translation response`)
+}
+
+function translationOf(payload: unknown, provider: TranslationProvider): string {
+ if (!Array.isArray(payload)) throw invalidResponse(provider)
+ const first: unknown = payload[0]
+ switch (provider) {
+ case 'google': {
+ if (!Array.isArray(first) || first.length === 0) throw invalidResponse(provider)
+ const segments: unknown[] = first
+ return segments.map((segment) => {
+ if (!Array.isArray(segment)) throw invalidResponse(provider)
+ const text: unknown = segment[0]
+ if (typeof text !== 'string') throw invalidResponse(provider)
+ return text
+ }).join('')
+ }
+ case 'bing': {
+ if (typeof first !== 'object' || first === null || !('translations' in first) || !Array.isArray(first.translations)) {
+ throw invalidResponse(provider)
+ }
+ const translated: unknown = first.translations[0]
+ if (typeof translated !== 'object' || translated === null || !('text' in translated) || typeof translated.text !== 'string') {
+ throw invalidResponse(provider)
+ }
+ return translated.text
+ }
+ /* v8 ignore next -- TranslationProvider is a closed typed union; JSON/config validation owns admission. */
+ default: return assertNever(provider)
+ }
+}
+
+async function responseJson(response: Response, config: Config, provider: TranslationProvider): Promise {
+ if (response.body === null) throw invalidResponse(provider)
+ const reader = response.body.getReader()
+ const chunks: Uint8Array[] = []
+ let bytes = 0
+ try {
+ while (true) {
+ const chunk = await reader.read()
+ if (chunk.done) break
+ bytes += chunk.value.byteLength
+ if (bytes > config.maxResponseBytes) {
+ await reader.cancel()
+ throw new TranslationError('TRANSLATION_RESPONSE_LIMIT', `${provider} translation response exceeds ${config.maxResponseBytes} bytes`)
+ }
+ chunks.push(chunk.value)
+ }
+ } finally {
+ reader.releaseLock()
+ }
+ try {
+ const payload: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))
+ return payload
+ } catch (_error) {
+ throw new TranslationError('TRANSLATION_INVALID_RESPONSE', `${provider} returned invalid translation JSON`)
+ }
+}
+
+/**
+ * Send one bounded request without cookies, credentials, redirects or provider fallback.
+ * @param spec - resolved text and provider-language choices.
+ * @param config - endpoints, deadline and response-byte limit.
+ * @param signal - caller and service lifetime cancellation.
+ * @returns provider-translated text, or a safe failure preserving cancellation.
+ */
+export async function translateText(spec: TranslationSpec, config: Config, signal: AbortSignal): Promise {
+ using timeout = deadline(signal, config.timeoutMs, 'TRANSLATION_TIMEOUT')
+ try {
+ timeout.signal.throwIfAborted()
+ if (spec.text === '') return ''
+ const { url, init } = requestFor(spec, config)
+ const response = await fetch(url, { ...init, redirect: 'error', credentials: 'omit', signal: timeout.signal })
+ if (!response.ok) {
+ await response.body?.cancel()
+ throw new TranslationError('TRANSLATION_HTTP_ERROR', `${spec.provider} translation failed (HTTP ${response.status})`)
+ }
+ const payload = await responseJson(response, config, spec.provider)
+ timeout.signal.throwIfAborted()
+ return translationOf(payload, spec.provider)
+ } catch (error) {
+ timeout.signal.throwIfAborted()
+ if (error instanceof TranslationError) throw error
+ throw new TranslationError('TRANSLATION_REQUEST_FAILED', `${spec.provider} translation request failed`)
+ }
+}
diff --git a/packages/experimental/translator/src/types.ts b/packages/experimental/translator/src/types.ts
new file mode 100644
index 0000000000..ae57724434
--- /dev/null
+++ b/packages/experimental/translator/src/types.ts
@@ -0,0 +1,32 @@
+/** Provider-independent inputs to the experimental translation service. */
+
+/** Anonymous browser endpoint selected for one translation. */
+export type TranslationProvider = 'google' | 'bing'
+
+/** Text and provider-supported language tags supplied by a consumer. */
+export interface TranslationRequest {
+ /** Complete text submitted to the selected provider. */
+ readonly text: string
+ /** Destination language; common Chinese locale tags are normalized per provider. */
+ readonly targetLanguage: string
+ /** Source language; omission uses automatic detection. */
+ readonly sourceLanguage?: string
+ /** Endpoint selection; omission uses the service's configured provider. */
+ readonly provider?: TranslationProvider
+}
+
+/** Fully resolved routing and language choices for one provider request. */
+export interface TranslationSpec {
+ /** Complete text submitted to the selected provider. */
+ readonly text: string
+ /** Destination language tag. */
+ readonly targetLanguage: string
+ /** Source language tag, or `auto` for automatic detection. */
+ readonly sourceLanguage: string
+ /** Selected anonymous browser endpoint. */
+ readonly provider: TranslationProvider
+}
+
+/** Failures distinct from caller cancellation and service disposal. */
+export type TranslationErrorCode = 'TRANSLATION_TEXT_LIMIT' | 'TRANSLATION_HTTP_ERROR'
+ | 'TRANSLATION_INVALID_RESPONSE' | 'TRANSLATION_RESPONSE_LIMIT' | 'TRANSLATION_REQUEST_FAILED'
diff --git a/packages/experimental/translator/tests/fixtures/cordis.yml b/packages/experimental/translator/tests/fixtures/cordis.yml
new file mode 100644
index 0000000000..aa57c17566
--- /dev/null
+++ b/packages/experimental/translator/tests/fixtures/cordis.yml
@@ -0,0 +1,4 @@
+- id: translator
+ name: cordis:translator
+ config:
+ googleEndpoint: TRANSLATION_ENDPOINT
diff --git a/packages/experimental/translator/tests/translator.spec.ts b/packages/experimental/translator/tests/translator.spec.ts
new file mode 100644
index 0000000000..02c97a6197
--- /dev/null
+++ b/packages/experimental/translator/tests/translator.spec.ts
@@ -0,0 +1,226 @@
+import { Context } from '@deepseek-ai/cordis'
+import z from '@deepseek-ai/schemastery'
+import { boot } from '@deepseek-ai/dsh-app-boot'
+import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
+import { once } from 'node:events'
+import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import Translator, { TranslationError, type Config, type TranslationSpec } from '../src/index.ts'
+
+type Reply = (req: IncomingMessage, res: ServerResponse, body: string) => void
+const cleanups: Array<() => Promise> = []
+
+afterEach(async () => {
+ for (const cleanup of cleanups.splice(0).reverse()) await cleanup()
+})
+
+async function fixture(reply: Reply, overrides: Partial = {}) {
+ const requests: Array<{ url: URL; method: string | undefined; headers: IncomingMessage['headers']; body: string }> = []
+ const server = createServer((req, res) => {
+ let body = ''
+ req.setEncoding('utf8')
+ req.on('data', (chunk: string) => { body += chunk })
+ req.on('end', () => {
+ requests.push({ url: new URL(req.url!, 'http://localhost'), method: req.method, headers: req.headers, body })
+ reply(req, res, body)
+ })
+ })
+ cleanups.push(async () => {
+ server.closeAllConnections()
+ await new Promise((resolve, reject) => server.close((error) => {
+ if (error === undefined) resolve()
+ else reject(error)
+ }))
+ })
+ server.listen(0, '127.0.0.1')
+ await once(server, 'listening')
+ const address = server.address()
+ if (address === null || typeof address === 'string') throw new Error('Missing loopback listener')
+ const base = `http://127.0.0.1:${address.port}`
+ const ctx = new Context()
+ cleanups.push(async () => { await ctx.fiber.dispose() })
+ const config = Translator.Config({ googleEndpoint: `${base}/google`, bingEndpoint: `${base}/bing?from=fr`, ...overrides })
+ const fiber = ctx.plugin(Translator, config)
+ await fiber
+ return { ctx, fiber, config, requests, translator: ctx.translator }
+}
+
+function json(res: ServerResponse, payload: unknown): void {
+ res.writeHead(200, { 'content-type': 'application/json' })
+ res.end(JSON.stringify(payload))
+}
+
+const googlePayload = [[['你好', 'Hello', null, null], [',世界。', 'world', null, null]], null, 'en']
+const bingPayload = [{ translations: [{ text: '你好,世界。', to: 'zh-Hans' }] }]
+
+describe('anonymous translator', () => {
+ it('resolves Google and automatic detection and combines translated segments', async () => {
+ const { translator, requests } = await fixture((_req, res) => { json(res, googlePayload) })
+ const spec = translator.resolve({ text: 'Hello world.', targetLanguage: 'zh' })
+ expect(spec).toEqual({ text: 'Hello world.', targetLanguage: 'zh', sourceLanguage: 'auto', provider: 'google' })
+ expect(translator.maxTextChars).toBe(4000)
+ expect(await translator.translate(spec)).toBe('你好,世界。')
+ const request = requests[0]!
+ expect(request.method).toBe('GET')
+ expect(Object.fromEntries(request.url.searchParams)).toEqual({ client: 'gtx', sl: 'auto', tl: 'zh-CN', dt: 't', q: 'Hello world.' })
+ expect(request.headers.cookie).toBeUndefined()
+ expect(request.headers.authorization).toBeUndefined()
+ })
+
+ it('uses configured Bing and lets an explicit provider override it', async () => {
+ const { translator, requests } = await fixture((req, res) => {
+ json(res, req.url?.startsWith('/bing') ? bingPayload : googlePayload)
+ }, { provider: 'bing' })
+ expect(await translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'zh-Hans' }))).toBe('你好,世界。')
+ expect(requests[0]!.url.searchParams.has('from')).toBe(false)
+ expect(await translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'en', sourceLanguage: 'zh-Hant' }))).toBe('你好,世界。')
+ const explicit = requests[1]!
+ expect(explicit.method).toBe('POST')
+ expect(explicit.headers['content-type']).toBe('application/json')
+ expect(JSON.parse(explicit.body)).toEqual(['hello'])
+ expect(Object.fromEntries(explicit.url.searchParams)).toEqual({ from: 'zh-Hant', to: 'en', isEnterpriseClient: 'false' })
+ expect(await translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'zh-TW', sourceLanguage: 'en', provider: 'google' }))).toBe('你好,世界。')
+ expect(requests[2]!.url.searchParams.get('tl')).toBe('zh-TW')
+ expect(requests[2]!.url.searchParams.get('sl')).toBe('en')
+ })
+
+ it('maps Simplified and Traditional tags to Bing language codes', async () => {
+ const { translator, requests } = await fixture((_req, res) => { json(res, bingPayload) })
+ await translator.translate(translator.resolve({ text: '你好', targetLanguage: 'zh-HK', sourceLanguage: 'zh-CN', provider: 'bing' }))
+ expect(Object.fromEntries(requests[0]!.url.searchParams)).toEqual({ from: 'zh-Hans', to: 'zh-Hant', isEnterpriseClient: 'false' })
+ })
+
+ it('returns empty text locally and accepts the exact UTF-16 limit', async () => {
+ const { translator, requests } = await fixture((_req, res) => { json(res, googlePayload) }, { maxTextChars: 2 })
+ expect(await translator.translate(translator.resolve({ text: '', targetLanguage: 'zh' }))).toBe('')
+ expect(requests).toHaveLength(0)
+ await translator.translate(translator.resolve({ text: '😀', targetLanguage: 'zh' }))
+ expect(requests).toHaveLength(1)
+ expect(() => translator.resolve({ text: '😀a', targetLanguage: 'zh' })).toThrow(TranslationError)
+ const oversized: TranslationSpec = { text: '😀a', targetLanguage: 'zh', sourceLanguage: 'auto', provider: 'google' }
+ expect(() => translator.translate(oversized)).toThrow(expect.objectContaining({ code: 'TRANSLATION_TEXT_LIMIT' }))
+ expect(requests).toHaveLength(1)
+ })
+
+ it.each([400, 429, 503, 304])('reports HTTP %s without provider error content or fallback', async (status) => {
+ const { translator, requests } = await fixture((_req, res) => { res.writeHead(status); res.end('private source text') })
+ await expect(translator.translate(translator.resolve({ text: 'private source text', targetLanguage: 'zh' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_HTTP_ERROR', message: `google translation failed (HTTP ${status})` }))
+ expect(requests).toHaveLength(1)
+ })
+
+ it('rejects redirects without contacting the redirected endpoint', async () => {
+ const { translator, requests } = await fixture((_req, res) => { res.writeHead(307, { location: '/other-provider' }); res.end() })
+ await expect(translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'zh' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_REQUEST_FAILED' }))
+ expect(requests).toHaveLength(1)
+ })
+
+ it('normalizes a disconnected provider into a safe transport failure', async () => {
+ const { translator } = await fixture((req) => { req.socket.destroy() })
+ await expect(translator.translate(translator.resolve({ text: 'private source text', targetLanguage: 'zh' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_REQUEST_FAILED', message: 'google translation request failed' }))
+ })
+
+ it('rejects missing bodies and invalid JSON', async () => {
+ let empty = true
+ const { translator } = await fixture((_req, res) => {
+ if (empty) { res.writeHead(204); res.end() } else res.end('not JSON')
+ })
+ const spec = translator.resolve({ text: 'hello', targetLanguage: 'zh' })
+ await expect(translator.translate(spec)).rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_INVALID_RESPONSE' }))
+ empty = false
+ await expect(translator.translate(spec)).rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_INVALID_RESPONSE' }))
+ })
+
+ it.each([null, {}, [], [null], [[]], [['not a segment']], [[[null]]]])('rejects malformed Google response %j', async (payload) => {
+ const { translator } = await fixture((_req, res) => { json(res, payload) })
+ await expect(translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'zh' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_INVALID_RESPONSE' }))
+ })
+
+ it.each([null, [], [1], [null], [{}], [{ translations: null }], [{ translations: [] }], [{ translations: [1] }],
+ [{ translations: [null] }], [{ translations: [{}] }], [{ translations: [{ text: 1 }] }]])('rejects malformed Bing response %j', async (payload) => {
+ const { translator } = await fixture((_req, res) => { json(res, payload) })
+ await expect(translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'zh', provider: 'bing' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_INVALID_RESPONSE' }))
+ })
+
+ it('enforces response-byte limits on multibyte text at and below the exact limit', async () => {
+ const payload = [[['中']]]
+ const bytes = Buffer.byteLength(JSON.stringify(payload))
+ const exact = await fixture((_req, res) => { json(res, payload) }, { maxResponseBytes: bytes })
+ expect(await exact.translator.translate(exact.translator.resolve({ text: 'hello', targetLanguage: 'zh' }))).toBe('中')
+ const short = await fixture((_req, res) => { json(res, payload) }, { maxResponseBytes: bytes - 1 })
+ await expect(short.translator.translate(short.translator.resolve({ text: 'hello', targetLanguage: 'zh' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_RESPONSE_LIMIT' }))
+ })
+
+ it('honors caller cancellation before admission and during response intake', async () => {
+ const entered = Promise.withResolvers()
+ const { translator, requests } = await fixture((_req, res) => { res.writeHead(200); res.write('['); entered.resolve(undefined) })
+ const spec = translator.resolve({ text: 'hello', targetLanguage: 'zh' })
+ const already = new AbortController()
+ const reason = new Error('consumer stopped translation')
+ already.abort(reason)
+ expect(() => translator.translate(spec, already.signal)).toThrow(reason)
+ expect(requests).toHaveLength(0)
+ const controller = new AbortController()
+ const pending = translator.translate(spec, controller.signal)
+ const rejection = expect(pending).rejects.toBe(reason)
+ await entered.promise
+ controller.abort(reason)
+ await rejection
+ })
+
+ it('applies its deadline while a provider leaves the response unfinished', async () => {
+ const { translator } = await fixture((_req, res) => { res.writeHead(200); res.write('[') }, { timeoutMs: 40 })
+ await expect(translator.translate(translator.resolve({ text: 'hello', targetLanguage: 'zh' })))
+ .rejects.toThrow(expect.objectContaining({ code: 'TRANSLATION_TIMEOUT', timeoutMs: 40 }))
+ })
+
+ it('unloads the service after aborting and joining accepted translations', async () => {
+ const entered = Promise.withResolvers()
+ const { ctx, fiber, translator } = await fixture((_req, res) => { res.writeHead(200); res.write('['); entered.resolve(undefined) })
+ const spec = translator.resolve({ text: 'hello', targetLanguage: 'zh' })
+ const pending = translator.translate(spec)
+ const rejection = expect(pending).rejects.toThrow('Translator service disposed')
+ await entered.promise
+ await fiber.dispose()
+ await rejection
+ expect(ctx.get('translator')).toBeUndefined()
+ expect(() => translator.resolve({ text: 'hello', targetLanguage: 'zh' })).toThrow('Translator service disposed')
+ expect(() => translator.translate(spec)).toThrow('Translator service disposed')
+ })
+
+ it('validates endpoint URLs and numerical limits before activation', () => {
+ expect(Translator.Config({})).toMatchObject({ provider: 'google', maxTextChars: 4000, timeoutMs: 10000 })
+ for (const googleEndpoint of ['not a URL', 'ftp://provider.invalid/', 'https://user:password@provider.invalid/', 'https://provider.invalid/#fragment']) {
+ expect(() => Translator.Config({ googleEndpoint })).toThrow()
+ }
+ for (const field of ['timeoutMs', 'maxTextChars', 'maxResponseBytes']) {
+ expect(() => Translator.Config({ [field]: 0 })).toThrow()
+ }
+ expect(() => z.resolve({ provider: 'other' }, Translator.Config, {})).toThrow()
+ expect(() => Translator.Config({ maxTextChars: 1 })).toThrow()
+ })
+
+ it('boots a real Loader composition and translates through its configured provider', async () => {
+ const { config, requests } = await fixture((_req, res) => { json(res, googlePayload) })
+ const root = await mkdtemp(join(tmpdir(), 'dsh-translator-composition-'))
+ cleanups.push(async () => { await rm(root, { recursive: true, force: true }) })
+ const source = await readFile(new URL('./fixtures/cordis.yml', import.meta.url), 'utf8')
+ const path = join(root, 'cordis.yml')
+ await writeFile(path, source.replace('TRANSLATION_ENDPOINT', config.googleEndpoint))
+ const ctx = await boot('translator-composition', path, [], (host) => {
+ host.loader.builtins.translator = Translator
+ })
+ cleanups.push(async () => { await ctx.fiber.dispose() })
+ expect(ctx.translator.resolve({ text: 'Hello world.', targetLanguage: 'zh' }).provider).toBe('google')
+ expect(await ctx.translator.translate(ctx.translator.resolve({ text: 'Hello world.', targetLanguage: 'zh' }))).toBe('你好,世界。')
+ expect(requests).toHaveLength(1)
+ expect(requests[0]!.url.searchParams.get('tl')).toBe('zh-CN')
+ })
+})
diff --git a/packages/experimental/translator/tsconfig.json b/packages/experimental/translator/tsconfig.json
new file mode 100644
index 0000000000..93444523e9
--- /dev/null
+++ b/packages/experimental/translator/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": { "rootDir": "src", "outDir": "lib/types" },
+ "include": ["src"],
+ "references": [
+ { "path": "../../../vendor/cordis" },
+ { "path": "../../../vendor/schemastery" },
+ { "path": "../../util/timeout" },
+ { "path": "../../util/values" }
+ ]
+}
diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts
index f03e9d77a1..81777a4db6 100644
--- a/packages/extensions/tool-cordis/src/api-catalog.ts
+++ b/packages/extensions/tool-cordis/src/api-catalog.ts
@@ -3335,6 +3335,25 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
],
},
+ {
+ key: 'translator',
+ summary: 'One Host service with explicit routing, cancellation and quiescent unload.',
+ description: 'One Host service with explicit routing, cancellation and quiescent unload.',
+ methods: [
+ {
+ signature: 'resolve(request: TranslationRequest): TranslationSpec',
+ description: 'Resolve provider and source-language defaults without sending text.',
+ parameters: [{ name: 'request', description: 'consumer text, destination and optional routing choices.' }],
+ returns: 'a complete specification; exceeding `maxTextChars` throws `TRANSLATION_TEXT_LIMIT`.',
+ },
+ {
+ signature: 'translate(spec: TranslationSpec, signal?: AbortSignal): Promise',
+ description: 'Translate one resolved specification; the selected provider receives its text.',
+ parameters: [{ name: 'spec', description: 'complete routing and language choices from `resolve()`.' }, { name: 'signal', description: 'optional caller cancellation, combined with service disposal.' }],
+ returns: 'translated plain text; rejects provider/limit failures and preserves cancellation reasons.',
+ },
+ ],
+ },
{
key: 'typert',
summary: 'Registry of generated schemas, package reflection, invocations, and Remote dependency providers.',
@@ -7912,6 +7931,18 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'TranscriptionRequest',
declaration: 'export interface TranscriptionRequest {\n readonly audioBase64: string;\n readonly providerId?: SpeechProviderId;\n readonly language?: string;\n}',
},
+ {
+ name: 'TranslationProvider',
+ declaration: 'export type TranslationProvider = \'google\' | \'bing\';',
+ },
+ {
+ name: 'TranslationRequest',
+ declaration: 'export interface TranslationRequest {\n readonly text: string;\n readonly targetLanguage: string;\n readonly sourceLanguage?: string;\n readonly provider?: TranslationProvider;\n}',
+ },
+ {
+ name: 'TranslationSpec',
+ declaration: 'export interface TranslationSpec {\n readonly text: string;\n readonly targetLanguage: string;\n readonly sourceLanguage: string;\n readonly provider: TranslationProvider;\n}',
+ },
{
name: 'TurnCompleteInput',
declaration: 'export interface TurnCompleteInput {\n turnId: string;\n answer: string;\n durationMs: number;\n isAborted: boolean;\n reason: \'answer\' | \'aborted\' | \'error\';\n agentId?: string;\n usage?: TurnUsage;\n}',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8824a42631..689813b284 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8346,6 +8346,25 @@ importers:
specifier: workspace:*
version: link:../../core/tools
+ packages/experimental/translator:
+ dependencies:
+ '@deepseek-ai/schemastery':
+ specifier: link:../../../vendor/schemastery
+ version: link:../../../vendor/schemastery
+ devDependencies:
+ '@deepseek-ai/cordis':
+ specifier: workspace:~
+ version: link:../../../vendor/cordis
+ '@deepseek-ai/dsh-app-boot':
+ specifier: workspace:*
+ version: link:../../boot/app-boot
+ '@deepseek-ai/dsh-timeout':
+ specifier: workspace:*
+ version: link:../../util/timeout
+ '@deepseek-ai/dsh-util-values':
+ specifier: workspace:*
+ version: link:../../util/values
+
packages/experimental/voice-input-bundle:
dependencies:
'@deepseek-ai/dsh-experimental-api-speech-to-text':
diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts
index 05399cc4d4..1ae3d076df 100644
--- a/scripts/gen-cordis-catalog.ts
+++ b/scripts/gen-cordis-catalog.ts
@@ -50,6 +50,7 @@ export { REGION_BEGIN, REGION_END }
* errors, so the partition can never silently drift from the service API.
*/
export const SERVICE_PAGE: Record = {
+ translator: 'translation.md',
speechToText: 'voice-input.md',
speechController: 'voice-input.md',
otel: 'otel.md',
@@ -665,6 +666,11 @@ export const LINK_MAP: Readonly> = {
SpeechProviderInfo: 'voice-input.md',
SpeechInput: 'voice-input.md',
SpeechRequest: 'voice-input.md',
+ TranslationProvider: 'translation.md',
+ TranslationRequest: 'translation.md',
+ TranslationSpec: 'translation.md',
+ TranslationError: 'translation.md',
+ TranslationErrorCode: 'translation.md',
SpeechSpec: 'voice-input.md',
Transcript: 'voice-input.md',
SpeechCatalog: 'voice-input.md',
diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts
index 287394e98f..ede61ec34f 100644
--- a/scripts/gen-doc-graphs.ts
+++ b/scripts/gen-doc-graphs.ts
@@ -107,6 +107,13 @@ const GROUP_ORDER = [
]
const SERVICE_ROLES: ServiceRole[] = [
+ {
+ key: 'translator',
+ pkg: 'experimental-translator',
+ title: 'Anonymous text translation',
+ mode: 'service',
+ note: 'Resolves Google or Bing requests and bounds provider calls independently of Sessions.',
+ },
{
key: 'hmr',
pkg: 'hmr',
diff --git a/scripts/release/families.spec.ts b/scripts/release/families.spec.ts
index b68b642339..64c2779105 100644
--- a/scripts/release/families.spec.ts
+++ b/scripts/release/families.spec.ts
@@ -69,6 +69,7 @@ describe('release families', () => {
'@deepseek-ai/dsh-experimental-speech-to-text-sensevoice',
'@deepseek-ai/dsh-experimental-speech-to-text',
'@deepseek-ai/dsh-experimental-tool-agent-team',
+ '@deepseek-ai/dsh-experimental-translator',
'@deepseek-ai/dsh-experimental-voice-input-bundle',
'@deepseek-ai/dsh-experimental-webworker-packer',
'@deepseek-ai/dsh-experimental-webworker-runtime',
diff --git a/tsconfig.base.json b/tsconfig.base.json
index f18926dcaf..97f13c2f04 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -42,6 +42,8 @@
"@deepseek-ai/dsh-deepseek-account": ["./packages/credentials/deepseek-account/src/index.ts"],
"@deepseek-ai/dsh-deepseek-account/types": ["./packages/credentials/deepseek-account/src/types.ts"],
"@deepseek-ai/dsh-experimental-speech-to-text": ["./packages/experimental/speech-to-text/src/index.ts"],
+ "@deepseek-ai/dsh-experimental-translator": ["./packages/experimental/translator/src/index.ts"],
+ "@deepseek-ai/dsh-experimental-translator/types": ["./packages/experimental/translator/src/types.ts"],
"@deepseek-ai/dsh-experimental-speech-to-text-sensevoice": ["./packages/experimental/speech-to-text-sensevoice/src/index.ts"],
"@deepseek-ai/dsh-experimental-api-speech-to-text": ["./packages/experimental/api-speech-to-text/src/index.ts"],
"@deepseek-ai/dsh-experimental-client-ui-voice-input": ["./packages/experimental/client-ui-voice-input/src/index.ts"],
diff --git a/tsconfig.host.json b/tsconfig.host.json
index f61cdcfd0e..ac7baf116f 100644
--- a/tsconfig.host.json
+++ b/tsconfig.host.json
@@ -211,6 +211,7 @@
"scripts/client-bundle-purity.spec.ts"
],
"references": [
+ { "path": "./packages/experimental/translator" },
{ "path": "./packages/client/product-analytics/tsconfig.host.json" },
{ "path": "./packages/telemetry/otel" },
{ "path": "./packages/experimental/speech-to-text" },