← Back to home@jack-001-a

deepseek-harness-translation-pane

Translation workbench plugin for DeepSeek Harness: sidebar panel with Bing/Google failover, caching and rate limiting, plus a translate_text tool for the agent.

Stars
0
Language
JavaScript
Created
Oct 23, 2026
Updated
Oct 23, 2026

Introduction

@ybbmm/dsh-translation-pane

A translation workbench for DeepSeek Harness: one sidebar entry, one full-width translation page, and a translate_text tool for the agent.

Published as @ybbmm/dsh-translation-pane on npm and on GitHub.

What it adds

WhereWhat
Sidebar panel railA Translation icon that opens the page in the main column
Translation pageSource/target language pickers, provider picker, source and result cards, a swap action, copy, clear, a live character counter, and Ctrl+Enter to translate
FailoverFalls over to the other anonymous provider when the selected one is unreachable, and says which service served the text
Agent tooltranslate_text(text, targetLanguage, sourceLanguage?) returning the translation

The language and provider choices are remembered in the page's local storage.

How translation runs

The Host half prefers the shipped ctx.translator service when the composition mounts it — that service owns provider routing, request limits and Session-bound result reuse. When it is absent, the Host half speaks the Microsoft Edge and Google Translate endpoints directly. Both routes are anonymous: no login, no API key, no Session.

describe() reports which route is live and which providers that route can actually serve, so the page never offers a provider the active route cannot use. It is deliberately synchronous: the shipped translator's availableProviders() can await credential and model discovery for up to its own 60-second deadline, which would leave the panel stuck on its first paint.

Provider failover and health

The two anonymous endpoints are independent services, and a network that can reach one may be unable to reach the other. The direct route therefore treats a provider-level failure as a routing problem rather than a dead end:

  • What fails over. A transport/connect error, the per-attempt header deadline, HTTP 429 and HTTP 5xx prove the provider itself is unhealthy, so the fragment is handed to the other provider once. A 4xx, a 200 whose body cannot be read, and the local rate limiter never fail over: none of them is fixed by asking a different provider.
  • What it costs. attemptTimeoutMs (default 3000) bounds the wait for response headers, so a dead provider is abandoned quickly. A provider that does answer may keep streaming under the whole-request deadline.
  • What is remembered. A provider that failed is avoided for providerCooldownMs (default five minutes). Later requests go straight to the healthy provider instead of probing the dead one again, and describe() shows the observation as plain serializable data under providerHealth. A provider that recovers is retried automatically once its cooldown expires.
  • What is reported. A result names a provider that actually served it in provider, and adds requestedProvider and failover. A long text may be split and answered by both providers; failover is then still true, and providersServed lists every service that answered. Cache entries keep the same facts, so a cache hit does not pretend the requested provider answered. The page names the services in the meta line, says which service served the text after a failover, and warns when the selected service is known to be unavailable.

The shipped-translator route owns its own routing, so it stays single-provider here: the pane does not invent a cross-provider retry on top of it.

Resilience

The anonymous endpoints rate-limit, return 5xx and occasionally hang. The Host therefore:

  • fails over a provider-level fault to the other anonymous provider once, with a short per-attempt header deadline (attemptTimeoutMs) and a cooldown (providerCooldownMs) that keeps later requests off a provider known to be dead. See Provider failover and health.
  • retries transient failures only — network errors, HTTP 429 and HTTP 5xx — with exponential back-off and jitter, honouring a Retry-After header. Every other 4xx fails immediately instead of burning the budget. The retry budget applies when a second provider is not available (the shipped-translator route, or failover: false); with two providers each is tried once, because the other provider is the better retry.
  • caps the response body, so a broken or hostile endpoint cannot stream unbounded bytes into memory.
  • deadlines every provider call, on both the direct and the translator route: a per-attempt header deadline plus one deadline for the whole request.
  • rate-limits itself with a token bucket, so a burst of panel requests cannot hammer the provider. When the bucket is empty the request fails fast with translation-pane/rate-limited rather than queueing without bound.
  • caches finished translations in a bounded LRU keyed by provider, language pair and source text, and coalesces identical concurrent requests into one provider call.
  • cancels cleanly: the agent tool's cancellation signal and the service's own lifetime both abort an in-flight request.

Fragments of one long document are dispatched with bounded concurrency rather than one at a time, so a large input is not many times slower than it needs to be.

Long input

Input longer than maxTextChars is split on paragraph, then line, then sentence, then word boundaries before dispatch, and the fragments are rejoined so the result matches the source structure exactly. Text beyond the limit is refused with a readable message instead of being truncated silently.

Privacy

The Host never logs conversation content. Log records carry lengths, ids and counters only.

The panel and the tool share one code path

Both callers reach the same Host method, so a failure, a language tag, or a limit behaves identically whether it came from the page or from the model. The page reaches it over the shared connection RPC on translationPane/describe and translationPane/translate; the tool calls the same service object in-process.

That includes routing: the tool gets the same failover, and its output carries the same additive provider / requestedProvider / failover facts (plus providersServed when a split demand was answered by both), so it can tell the user which service answered. Both paths surface the same stable error codes; no raw diagnostic text reaches the page.

The wire contract

The API Gateway derives a Remote method's wire fields from the method's parameter names, rejects any argument the descriptor does not name, and calls the method positionally in that order. Two consequences matter when maintaining this plugin:

  • A Remote method's signature must be plain identifiers — no default value, no destructuring, no rest parameter. The Gateway parses the signature as text and refuses anything else with gateway/signature-invalid.
  • A trailing parameter named signal is the cancellation slot: the Gateway injects the caller's AbortSignal there, which is how the page's Cancel button aborts the Host request and not just the browser-side promise.

translate therefore declares both the flat and the nested request shape, so the page and the in-process tool can share one entry point. The contract is pinned by test/gateway-contract.test.mjs, which re-implements the Gateway's own derivation, validation and dispatch and drives the real Host with the payload the browser sends.

Error codes

The Host answers with stable codes and the page localizes them, so a failure never surfaces a raw diagnostic string.

CodeMeaning
translation-pane/invalid-requestMalformed request (defensive)
translation-pane/text-limitThe text exceeds the configured limit
translation-pane/provider-unavailableThe selected provider cannot serve this request
translation-pane/provider-unreachableThe Host could not reach the provider over the network
translation-pane/timeoutThe provider did not answer within the deadline
translation-pane/rate-limitedThe local rate limiter refused the request
translation-pane/provider-errorThe provider answered with an error status
translation-pane/invalid-responseThe provider answered with an unusable body
translation-pane/offlineThe Harness is not connected to its Host (carrier failure only)

The Host raises provider-unreachable when its own network cannot reach the provider, while offline is the browser losing its Host. Keeping them apart matters: only the second one is fixed by checking that the Host is running.

Accessibility

  • The result is an atomic live region, so a screen reader announces a new translation as one sentence.
  • Every control has a programmatic label. The counter and the keyboard hint are associated with the editor through aria-describedby, and the Translate action also describes the counter so an over-limit refusal is heard.
  • An over-limit source is marked with aria-invalid, and the result region is announced even when the user never focuses it.
  • Both text surfaces declare dir="auto", so right-to-left source text and translations render in the correct direction.
  • The in-flight request can be cancelled from the keyboard, and the shortcut is advertised with aria-keyshortcuts.
  • Focus rings are visible on every interactive element, and the design uses only --dsw-alias-* theme tokens, so light and dark themes follow the app.

Config

Tunables live in the profile patch so they survive upgrades. Every key is optional — the Host falls back to the documented default when a key is absent or invalid.

- id: ybbmm-dsh-translation-pane
  name: '@ybbmm/dsh-translation-pane'
  config:
    bingEndpoint: https://edge.microsoft.com/translate/translatetext
    googleEndpoint: https://translate.googleapis.com/translate_a/single
    timeoutMs: 20000
    attemptTimeoutMs: 3000
    providerCooldownMs: 300000
    failover: true
    maxTextChars: 20000
    fragmentChars: 3000
    maxRetries: 2
    retryBaseMs: 250
    retryMaxMs: 4000
    maxResponseBytes: 1048576
    cacheSize: 64
    rateLimitPerMinute: 60
    maxConcurrency: 3
KeyDefaultMeaning
bingEndpointthe Edge endpointAnonymous Bing-compatible endpoint
googleEndpointthe Google endpointAnonymous Google-compatible endpoint
timeoutMs20000Deadline for the whole request, fragments included
attemptTimeoutMs3000Wait for one provider to start answering (response headers); capped by timeoutMs
providerCooldownMs300000How long a provider that failed is avoided; 0 disables the observation window
failovertrueLet a fragment fall over to the other direct provider once
maxTextChars20000Largest input accepted for one request
fragmentChars3000Provider-sized fragment length
maxRetries2Retries for transient failures only; used when no second provider is available
retryBaseMs250First back-off delay
retryMaxMs4000Ceiling of the back-off delay
maxResponseBytes1048576Largest provider response accepted
cacheSize64Bounded LRU of finished translations; 0 disables it
rateLimitPerMinute60Token bucket across all callers
maxConcurrency3Fragments of one long request in flight at once

Install

From the plugin market: search for Translation Pane, or add it by name:

dsh plugin add @ybbmm/dsh-translation-pane

From a local directory (development):

dsh plugin add ./dsh-translation-pane

Restart the Harness if your profile does not hot-reload plugin bundles.

Layout and scrolling

The page fills the main column and is itself the scroll container, so a short window scrolls instead of clipping the lower controls. Each text card is capped at 45vh (never below 264px): a long translation scrolls inside its own card rather than stretching the page, and the source textarea scrolls internally once the input outgrows the card.

Development

npm test        # node --test

The tests run against the real module with a fake Cordis context and a stubbed fetch; they need no Harness installation and no network.

Known limitations

  • The anonymous endpoints are not supported third-party APIs. They can rate-limit, change their response shape, or be unreachable from a given network. Failover keeps a translation working when one of them is, but it is still only two providers: when both are down, the request fails honestly.
  • A provider's observed health is in-memory and per service instance. A restart forgets it, and the first request after a provider becomes unreachable pays one bounded attempt (attemptTimeoutMs) before the failover answers.
  • Editing the source text while a translation is in flight does not cancel it; the answer that arrives belongs to the text that was submitted, and can look stale next to the newer input. Cancel or translate again to replace it.
  • UI copy is English and Simplified Chinese only; the language dropdown labels follow the active UI locale through the same two-language choice.
  • Machine translation can alter Markdown or code examples. The source text is never modified.
  • The cache is in-memory only and does not survive a restart, and a cached result keeps the provider and failover facts of the request that produced it.

License

MIT