dsh-think-translate 1.2.9 → 1.2.10

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.de.md CHANGED
@@ -33,7 +33,7 @@ Modelle der DeepSeek-Familie denken oft auf Chinesisch — oder in der Sprache,
33
33
  - **🎚️ Einstellbarer Übersetzungszeitpunkt** — alles vorübersetzen / alte Ketten lazy laden (Standard) / nur beim Aufklappen
34
34
  - **🔗 Dynamische Anbieter-Kette** — die Listenreihenfolge ist die Ausführungsreihenfolge: ziehen zum Sortieren, jede Zeile einzeln ein-/ausschalten. Integriert google gtx / bing / lokales Ollama plus beliebige eigene Endpunkte
35
35
  - **🔌 Eigene Anbieter (OpenAI & Anthropic)** — im Panel jeden OpenAI-kompatiblen Endpunkt (`/v1/chat/completions`) oder die **Anthropic Messages API** (Claude) hinzufügen: Typ, Vorlage, Basis-URL, API-Schlüssel, Modell
36
- - **🪄 DSH-konfigurierte Anbieter übernehmen** — liest schreibgeschützte DSH-Zeilen aus `settings.yaml` (`llm-pi-ai.providers`); eine Schaltfläche liest neu ein und fügt alle der Kette hinzu. Der Schlüssel wird pro Anfrage aus `.credentials.yaml` aufgelöst und nie in der Plugin-Konfiguration gespeichert. Auch die eigene Standardroute des Harness zählt: zeigt `agent-default-model` auf `deepseek-official`, erscheint die offizielle DeepSeek-API als weitere DSH-Zeile
36
+ - **🪄 DSH-konfigurierte Anbieter übernehmen** — liest schreibgeschützte DSH-Zeilen aus `profiles/<profile>/cordis.patch.yml` / älteres `settings.yaml` (`llm-pi-ai.providers`); eine Schaltfläche liest neu ein und fügt alle der Kette hinzu. Der Schlüssel wird pro Anfrage aus `.credentials.yaml` aufgelöst und nie in der Plugin-Konfiguration gespeichert. Auch die eigene Standardroute des Harness zählt: zeigt `default-model` / `agent-default-model` auf `deepseek-official`, erscheint die offizielle DeepSeek-API als weitere DSH-Zeile
37
37
  - **⏱️ Robust** — fehlgeschlagene Übersetzungen werden 30 Sekunden im Arbeitsspeicher zwischengespeichert und danach automatisch erneut versucht; jede Anbieterzeile hat eine Test-Schaltfläche
38
38
 
39
39
  ## 📦 Installation
@@ -75,6 +75,8 @@ Für DSH **0.2.0-rc.2** verwenden Sie **dsh-think-translate 1.2.5 oder neuer**.
75
75
 
76
76
  **1.2.8 Übertragung und Wiederaufnahme:** Normale HTTP/HTTPS-Übersetzungen nutzen die nativen Netzwerkfunktionen von Node statt eines curl-Prozesses pro Textpaket. SOCKS-Proxys und der Ollama-Installer verwenden weiterhin curl. Fehler bleiben 30 Sekunden im Arbeitsspeicher; danach werden Denk- und Antwortübersetzungen automatisch erneut versucht. Die Zeitlimits berücksichtigen das Laden lokaler Modelle und alternative Anbieter.
77
77
 
78
+ **1.2.10 DSH-Profilkompatibilität:** Die Erkennung folgt dem laufenden Desktop/Web-Profil und liest `profiles/<profile>/cordis.patch.yml` (`id` + `config`), danach die Home-Patches und Start-Overlays. Das ältere `settings.yaml` bleibt unterstützt. Die Standardmodell-Zeile heißt `default-model`; Zugangsdaten bleiben in `.credentials.yaml` im DSH-Home und nur im Arbeitsspeicher. Die Diagnose zeigt die tatsächlich gelesene Datei.
79
+
78
80
  **1.2.9 Sprachkorrekturen:** Japanische Vokabelhinweise gelten nur für Japanisch; OpenAI/Anthropic erhalten den vollständigen Sprachnamen. Bing nutzt `auto-detect` und `zh-Hans`/`zh-Hant`. Die Erkennung chinesischer Prosa blendet Pfade, Code und JSON aus: vereinfachte Prosa wird übersprungen, häufige traditionelle Schriftzeichen lösen eine Übersetzungsanfrage aus, japanische/koreanische Prosa wird übersetzt. Alte Übersetzungen werden einmalig aus dem Cache entfernt; Einstellungen bleiben erhalten.
79
81
 
80
82
  ## 🚀 Verwendung
@@ -83,7 +85,7 @@ Für DSH **0.2.0-rc.2** verwenden Sie **dsh-think-translate 1.2.5 oder neuer**.
83
85
  2. Die **Zielsprache** wählen (z. B. Deutsch) — Einstellungen, Denkzeilen und Karten wechseln in diese Sprache
84
86
  3. Die **Anbieterkette** verwalten (ziehen zum Sortieren, ankreuzen zum Aktivieren):
85
87
  - Integriert: **google gtx / bing** (kostenlos, über den Systemproxy) und **lokales Ollama**; vorhandene Software/Modelle werden wiederverwendet. Fehlende Komponenten werden erst über die Vorbereitungsschaltfläche installiert/heruntergeladen, mit Fortschritt (7b/14b oder eigenes Modell)
86
- - **DSH-Anbieter**: in `settings.yaml` konfigurierte Endpunkte erscheinen automatisch (schreibgeschützt; ankreuzen fügt sie der Kette hinzu). Die Schaltfläche **Aus DSH-Konfiguration übernehmen** direkt unter der Liste liest die Konfiguration neu ein und fügt alle auf einmal hinzu – keine baseURL und kein Schlüssel zum Neutippen, denn der Schlüssel wird aus den DSH-Credentials aufgelöst
88
+ - **DSH-Anbieter**: in `profiles/<profile>/cordis.patch.yml` / älteres `settings.yaml` konfigurierte Endpunkte erscheinen automatisch (schreibgeschützt; ankreuzen fügt sie der Kette hinzu). Die Schaltfläche **Aus DSH-Konfiguration übernehmen** direkt unter der Liste liest die Konfiguration neu ein und fügt alle auf einmal hinzu – keine baseURL und kein Schlüssel zum Neutippen, denn der Schlüssel wird aus den DSH-Credentials aufgelöst
87
89
  - Der Schlüssel wird eingetippt oder kommt als **`apiKeyEnv`** aus einer Vorlage bzw. einer DSH-Zeile: diese Zeile zeigt das `env:NAME`-Abzeichen, und der Schlüssel wird pro Anfrage aufgelöst und nie in `config.json` geschrieben. Das Bearbeitungsformular hat kein Feld für die Umgebungsvariable (der Wert bleibt erhalten), aber ein geleertes Feld wird wirklich gelöscht (als explizite Löschung gesendet)
88
90
  - Abgewählte Anbieter werden übersprungen. Details stehen in [README.md](README.md)
89
91
  4. Nachricht senden und die Think-Zeile aufklappen, um die Übersetzung zu sehen
@@ -100,7 +102,7 @@ Browser → POST /_xlate/translate (gleiche Origin, kein CORS)
100
102
  ```
101
103
 
102
104
  - Die **Anbieter-Konfiguration** liegt in `config.json` (zur Laufzeit erzeugt, gitignoriert): `chain` (geordnete IDs), `fallback` (enabled + chain, nur Datei), `providers` (je Anbieter `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`). Alte `priority`-Konfigurationen werden automatisch migriert; ein Anbieter mit `apiKeyEnv` löst seinen Schlüssel pro Anfrage aus dieser Umgebungsvariable auf (der literale `apiKey` bleibt als Rückfall) und kein aufgelöster Schlüssel wird je in `config.json` zurückgeschrieben; ein `null` in einem Patch löscht das Feld — so leert die Oberfläche eines
103
- - Die **DSH-Erkennung** liest beim Laden `settings.yaml` (`llm-pi-ai.providers`) und `.credentials.yaml` (`refs`) des Harness; erkannte Anbieter tragen `source: "dsh"`, aufgelöste Schlüssel bleiben im Speicher (nie in `config.json`), und die Route `/_xlate/dsh-scan` liest sie bei Bedarf neu ein
105
+ - Die **DSH-Erkennung** liest beim Laden `profiles/<profile>/cordis.patch.yml` / älteres `settings.yaml` (`llm-pi-ai.providers`) und `.credentials.yaml` (`refs`) des Harness; erkannte Anbieter tragen `source: "dsh"`, aufgelöste Schlüssel bleiben im Speicher (nie in `config.json`), und die Route `/_xlate/dsh-scan` liest sie bei Bedarf neu ein
104
106
  - **Host-Hälfte** (`lib/index.js`): Anbieteradapter, LRU-Cache (600), `/_xlate/models`, `/_xlate/model/pull` + `pull-status` (automatische Konfiguration am Ende)
105
107
  - **Client-Hälfte** (`lib/client.js`): 8-sprachige UI, satzweise Batch-Übersetzung, Streaming-Think-Zeilen, localStorage-Persistenz
106
108
  - Reine Anzeigeschicht: Originale bleiben im Verlauf und im Modellkontext erhalten
package/README.es.md CHANGED
@@ -33,7 +33,7 @@ Los modelos de la familia DeepSeek suelen razonar en chino — o en el idioma en
33
33
  - **🎚️ Momento de traducción ajustable** — pre-traducir todo / carga diferida de cadenas antiguas (por defecto) / solo al expandir
34
34
  - **🔗 Cadena de proveedores dinámica** — el orden de la lista es el orden de uso: arrastra para reordenar y activa o desactiva cada fila. Integrados google gtx / bing / Ollama local más cualquier endpoint personalizado
35
35
  - **🔌 Proveedores personalizados (OpenAI y Anthropic)** — añade desde el panel cualquier endpoint compatible con OpenAI (`/v1/chat/completions`) o la **Anthropic Messages API** (Claude): tipo, preajuste, URL base, clave de API y modelo
36
- - **🪄 Hereda los proveedores configurados en DSH** — detecta filas DSH de solo lectura desde `settings.yaml` (`llm-pi-ai.providers`) y un botón reescanea y las añade todas a la cadena; la clave se resuelve en cada petición desde `.credentials.yaml` y nunca se guarda en la config del plugin. La ruta por defecto del propio harness también cuenta: si `agent-default-model` apunta a `deepseek-official`, la API oficial de DeepSeek aparece como una fila DSH más
36
+ - **🪄 Hereda los proveedores configurados en DSH** — detecta filas DSH de solo lectura desde `profiles/<profile>/cordis.patch.yml` / anterior `settings.yaml` (`llm-pi-ai.providers`) y un botón reescanea y las añade todas a la cadena; la clave se resuelve en cada petición desde `.credentials.yaml` y nunca se guarda en la config del plugin. La ruta por defecto del propio harness también cuenta: si `default-model` / `agent-default-model` apunta a `deepseek-official`, la API oficial de DeepSeek aparece como una fila DSH más
37
37
  - **⏱️ Resistente** — los fallos se guardan en memoria durante 30 segundos para evitar reintentos continuos y después se reintentan automáticamente; cada proveedor tiene su botón de prueba
38
38
 
39
39
  ## 📦 Instalación
@@ -75,6 +75,8 @@ Para DSH **0.2.0-rc.2**, usa **dsh-think-translate 1.2.5 o posterior**. La versi
75
75
 
76
76
  **1.2.8 transporte y recuperación:** las traducciones HTTP/HTTPS normales usan la red nativa de Node en lugar de iniciar curl por cada lote de texto. Los proxies SOCKS y el instalador de Ollama conservan la compatibilidad con curl. Los fallos permanecen en memoria durante 30 segundos y después se reintentan automáticamente las traducciones del razonamiento y las respuestas. Los tiempos de espera permiten cargar modelos locales y probar proveedores alternativos.
77
77
 
78
+ **1.2.10 compatibilidad con perfiles DSH:** el descubrimiento sigue el perfil Desktop/Web activo y lee `profiles/<profile>/cordis.patch.yml` (`id` + `config`), después los parches del directorio DSH y las superposiciones de inicio. Se mantiene el formato anterior `settings.yaml`. El modelo predeterminado usa `default-model`; las credenciales siguen en `.credentials.yaml` del directorio DSH y solo en memoria. El diagnóstico muestra el archivo leído.
79
+
78
80
  **1.2.9 correcciones de idioma:** las pistas de vocabulario japonés solo se aplican al japonés; OpenAI/Anthropic reciben el nombre completo del idioma. Bing usa `auto-detect` y `zh-Hans`/`zh-Hant`. La detección del chino excluye rutas, código y JSON: omite la prosa ya simplificada, solicita conversión al detectar caracteres tradicionales frecuentes y traduce la prosa japonesa/coreana. Se elimina una vez la caché de traducciones anterior, conservando los ajustes.
79
81
 
80
82
  ## 🚀 Uso
@@ -83,7 +85,7 @@ Para DSH **0.2.0-rc.2**, usa **dsh-think-translate 1.2.5 o posterior**. La versi
83
85
  2. Elige el **idioma de destino** (p. ej. Español) — el panel, las filas y las tarjetas cambian a ese idioma
84
86
  3. Gestiona la **cadena de proveedores** (arrastra para ordenar, marca para activar):
85
87
  - Integrados: **google gtx / bing** (gratis, mediante el proxy del sistema) y **Ollama local**; se reutilizan el software y los modelos existentes. Los componentes que falten requieren el botón de preparación, con progreso (7b/14b o un modelo personalizado)
86
- - **Proveedores DSH**: los endpoints ya configurados en `settings.yaml` aparecen solos (solo lectura; márcalos para añadirlos a la cadena). El botón **Importar desde la config de DSH**, justo debajo de la lista, vuelve a escanear y los añade todos de una vez: ni baseURL ni clave que reescribir, porque la clave se resuelve desde las credenciales de DSH
88
+ - **Proveedores DSH**: los endpoints ya configurados en `profiles/<profile>/cordis.patch.yml` / anterior `settings.yaml` aparecen solos (solo lectura; márcalos para añadirlos a la cadena). El botón **Importar desde la config de DSH**, justo debajo de la lista, vuelve a escanear y los añade todos de una vez: ni baseURL ni clave que reescribir, porque la clave se resuelve desde las credenciales de DSH
87
89
  - La clave se escribe a mano o llega como **`apiKeyEnv`** desde un preajuste o una fila DSH: esa fila muestra la insignia `env:NAME`, y la clave se resuelve en cada petición sin escribirse nunca en `config.json`. El formulario de edición no tiene campo de variable de entorno (el valor se conserva intacto), pero vaciar un campo lo elimina de verdad (se envía como borrado explícito)
88
90
  - Si desmarcas uno, se omite. Los detalles están en [README.md](README.md)
89
91
  4. Envía un mensaje y expande la fila Think para ver la traducción
@@ -100,7 +102,7 @@ navegador → POST /_xlate/translate (mismo origen, sin CORS)
100
102
  ```
101
103
 
102
104
  - La **configuración de proveedores** vive en `config.json` (generado en tiempo de ejecución, ignorado por git): `chain` (ids ordenados), `fallback` (enabled + chain, solo en el archivo), `providers` (por proveedor `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`). Las configuraciones antiguas con `priority` se migran solas; un proveedor que declara `apiKeyEnv` resuelve la clave desde esa variable en cada petición (el `apiKey` literal queda como respaldo) y ninguna clave resuelta se escribe de vuelta en `config.json`; un `null` en un parche borra ese campo, que es como la interfaz vacía uno
103
- - El **descubrimiento DSH** lee `settings.yaml` (`llm-pi-ai.providers`) y `.credentials.yaml` (`refs`) del harness al cargar; los proveedores detectados se marcan con `source: "dsh"`, las claves resueltas se quedan en memoria (nunca en `config.json`) y la ruta `/_xlate/dsh-scan` los relee cuando hace falta
105
+ - El **descubrimiento DSH** lee `profiles/<profile>/cordis.patch.yml` / anterior `settings.yaml` (`llm-pi-ai.providers`) y `.credentials.yaml` (`refs`) del harness al cargar; los proveedores detectados se marcan con `source: "dsh"`, las claves resueltas se quedan en memoria (nunca en `config.json`) y la ruta `/_xlate/dsh-scan` los relee cuando hace falta
104
106
  - **Mitad host** (`lib/index.js`): adaptadores de proveedor, caché LRU (600), `/_xlate/models`, `/_xlate/model/pull` + `pull-status` (configura automáticamente al terminar)
105
107
  - **Mitad cliente** (`lib/client.js`): UI en 8 idiomas, traducción por lotes, filas Think en streaming, persistencia en localStorage
106
108
  - Capa de visualización pura: los originales permanecen en la transcripción y el contexto del modelo
package/README.fr.md CHANGED
@@ -33,7 +33,7 @@ Les modèles de la famille DeepSeek raisonnent souvent en chinois — ou dans la
33
33
  - **🎚️ Moment de traduction réglable** — tout pré-traduire / chargement différé des anciennes chaînes (défaut) / uniquement à l'ouverture
34
34
  - **🔗 Chaîne de fournisseurs dynamique** — l'ordre de la liste est l'ordre d'exécution : glissez pour réordonner, activez ou désactivez chaque ligne. Intégrés google gtx / bing / Ollama local, plus tout endpoint personnalisé
35
35
  - **🔌 Fournisseurs personnalisés (OpenAI et Anthropic)** — ajoutez depuis le panneau tout endpoint compatible OpenAI (`/v1/chat/completions`) ou l'**Anthropic Messages API** (Claude) : type, préréglage, URL de base, clé API et modèle
36
- - **🪄 Hérite des fournisseurs configurés dans DSH** — détecte les lignes DSH en lecture seule depuis `settings.yaml` (`llm-pi-ai.providers`), et un bouton relance l'analyse et les ajoute toutes à la chaîne ; la clé est résolue à chaque requête depuis `.credentials.yaml` et n'est jamais écrite dans la config du plugin. La route par défaut du harness compte aussi : si `agent-default-model` désigne `deepseek-official`, l'API officielle DeepSeek apparaît comme une ligne DSH de plus
36
+ - **🪄 Hérite des fournisseurs configurés dans DSH** — détecte les lignes DSH en lecture seule depuis `profiles/<profile>/cordis.patch.yml` / ancien `settings.yaml` (`llm-pi-ai.providers`), et un bouton relance l'analyse et les ajoute toutes à la chaîne ; la clé est résolue à chaque requête depuis `.credentials.yaml` et n'est jamais écrite dans la config du plugin. La route par défaut du harness compte aussi : si `default-model` / `agent-default-model` désigne `deepseek-official`, l'API officielle DeepSeek apparaît comme une ligne DSH de plus
37
37
  - **⏱️ Résilient** — les échecs sont conservés en mémoire pendant 30 secondes pour éviter les tentatives répétées, puis réessayés automatiquement ; chaque fournisseur dispose de son bouton de test
38
38
 
39
39
  ## 📦 Installation
@@ -75,6 +75,8 @@ Pour DSH **0.2.0-rc.2**, utilisez **dsh-think-translate 1.2.5 ou une version ult
75
75
 
76
76
  **1.2.8 transport et reprise :** les traductions HTTP/HTTPS ordinaires utilisent le réseau natif de Node au lieu de lancer curl pour chaque lot de texte. Les proxys SOCKS et l’installateur Ollama conservent curl. Les échecs restent en mémoire pendant 30 secondes, puis les traductions du raisonnement et des réponses sont réessayées automatiquement. Les délais tiennent compte du chargement des modèles locaux et du recours à d’autres fournisseurs.
77
77
 
78
+ **1.2.10 compatibilité des profils DSH :** la détection suit le profil Desktop/Web actif et lit `profiles/<profile>/cordis.patch.yml` (`id` + `config`), puis applique les correctifs du répertoire DSH et les surcharges de lancement. L’ancien `settings.yaml` reste pris en charge. Le modèle par défaut utilise `default-model` ; les identifiants restent dans `.credentials.yaml` du répertoire DSH et en mémoire uniquement. Le diagnostic indique le fichier lu.
79
+
78
80
  **1.2.9 corrections linguistiques :** les indications de vocabulaire japonais ne concernent que le japonais ; OpenAI/Anthropic reçoivent le nom complet de la langue. Bing utilise `auto-detect` et `zh-Hans`/`zh-Hant`. La détection du chinois écarte chemins, code et JSON : elle ignore la prose déjà simplifiée, demande une conversion en présence de caractères traditionnels courants et traduit la prose japonaise/coréenne. Les anciennes traductions en cache sont supprimées une fois, sans toucher aux réglages.
79
81
 
80
82
  ## 🚀 Utilisation
@@ -83,7 +85,7 @@ Pour DSH **0.2.0-rc.2**, utilisez **dsh-think-translate 1.2.5 ou une version ult
83
85
  2. Choisissez la **langue cible** (p. ex. Français) — le panneau, les lignes et les cartes basculent dans cette langue
84
86
  3. Gérez la **chaîne de fournisseurs** (glisser pour ordonner, cocher pour activer) :
85
87
  - Intégrés : **google gtx / bing** (gratuits, via le proxy système) et **Ollama local** ; les logiciels et modèles existants sont réutilisés. Les composants manquants nécessitent le bouton de préparation, avec progression (7b/14b ou modèle personnalisé)
86
- - **Fournisseurs DSH** : les endpoints déjà configurés dans `settings.yaml` apparaissent seuls (lecture seule ; cochez pour les ajouter à la chaîne). Le bouton **Importer depuis la config DSH**, juste sous la liste, relance l'analyse et les ajoute tous d'un coup : ni baseURL ni clé à ressaisir, la clé étant résolue depuis les identifiants de DSH
88
+ - **Fournisseurs DSH** : les endpoints déjà configurés dans `profiles/<profile>/cordis.patch.yml` / ancien `settings.yaml` apparaissent seuls (lecture seule ; cochez pour les ajouter à la chaîne). Le bouton **Importer depuis la config DSH**, juste sous la liste, relance l'analyse et les ajoute tous d'un coup : ni baseURL ni clé à ressaisir, la clé étant résolue depuis les identifiants de DSH
87
89
  - La clé se saisit à la main ou arrive via **`apiKeyEnv`** depuis un préréglage ou une ligne DSH : cette ligne affiche le badge `env:NAME`, et la clé est résolue à chaque requête sans jamais être écrite dans `config.json`. Le formulaire d'édition n'a pas de champ de variable d'environnement (la valeur est conservée telle quelle), mais vider un champ le supprime réellement (envoyé comme suppression explicite)
88
90
  - Décocher un fournisseur l'ignore. Détails dans [README.md](README.md)
89
91
  4. Envoyez un message et dépliez la ligne Think pour voir la traduction
@@ -100,7 +102,7 @@ navigateur → POST /_xlate/translate (même origine, sans CORS)
100
102
  ```
101
103
 
102
104
  - La **configuration des fournisseurs** vit dans `config.json` (généré à l'exécution, ignoré par git) : `chain` (ids ordonnés), `fallback` (enabled + chain, fichier uniquement), `providers` (par fournisseur `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`). Les anciennes configurations `priority` sont migrées automatiquement ; un fournisseur qui déclare `apiKeyEnv` résout sa clé depuis cette variable à chaque requête (le `apiKey` littéral reste en secours) et aucune clé résolue n'est réécrite dans `config.json` ; un `null` dans un patch supprime ce champ, c'est ainsi que l'interface en vide un
103
- - La **découverte DSH** lit `settings.yaml` (`llm-pi-ai.providers`) et `.credentials.yaml` (`refs`) du harness au chargement ; les fournisseurs détectés portent `source: "dsh"`, les clés résolues restent en mémoire (jamais dans `config.json`), et la route `/_xlate/dsh-scan` les relit à la demande
105
+ - La **découverte DSH** lit `profiles/<profile>/cordis.patch.yml` / ancien `settings.yaml` (`llm-pi-ai.providers`) et `.credentials.yaml` (`refs`) du harness au chargement ; les fournisseurs détectés portent `source: "dsh"`, les clés résolues restent en mémoire (jamais dans `config.json`), et la route `/_xlate/dsh-scan` les relit à la demande
104
106
  - **Moitié host** (`lib/index.js`) : adaptateurs de fournisseurs, cache LRU (600), `/_xlate/models`, `/_xlate/model/pull` + `pull-status` (configuration automatique à la fin)
105
107
  - **Moitié client** (`lib/client.js`) : UI en 8 langues, traduction par lots, lignes Think en streaming, persistance localStorage
106
108
  - Couche d'affichage pure : les originaux restent dans la transcription et le contexte du modèle
package/README.ja.md CHANGED
@@ -33,7 +33,7 @@ DeepSeek 系モデルは中国語で考えることが多く、あるいはた
33
33
  - **🎚️ 翻訳タイミングを調整可能** — すべて事前翻訳 / 履歴を遅延ロード(既定)/ 展開時のみ翻訳
34
34
  - **🔗 動的プロバイダーチェーン** — 一覧の順序がそのまま実行順。ドラッグで並べ替え、行ごとにオン/オフ。組み込みの google gtx / bing / ローカル Ollama に加えて任意のカスタム端点
35
35
  - **🔌 カスタムプロバイダー(OpenAI / Anthropic)** — 設定パネルから任意の OpenAI 互換端点(`/v1/chat/completions`)や **Anthropic Messages API**(Claude)を追加:種類・プリセット・ベース URL・API キー・モデル
36
- - **🪄 DSH 設定のプロバイダーを引き継ぐ** — `settings.yaml`(`llm-pi-ai.providers`)から読み取り専用の DSH 行を自動検出し、ボタン1つで再スキャンしてまとめてチェーンに追加。キーは `.credentials.yaml` からリクエスト時に解決され、プラグインの設定には保存されません。harness 自身の既定ルートも対象です:`agent-default-model` が `deepseek-official` を指していれば、DeepSeek の公式 API も DSH 行として提供されます
36
+ - **🪄 DSH 設定のプロバイダーを引き継ぐ** — `profiles/<profile>/cordis.patch.yml` / 旧形式 `settings.yaml`(`llm-pi-ai.providers`)から読み取り専用の DSH 行を自動検出し、ボタン1つで再スキャンしてまとめてチェーンに追加。キーは `.credentials.yaml` からリクエスト時に解決され、プラグインの設定には保存されません。harness 自身の既定ルートも対象です:`default-model` / `agent-default-model` が `deepseek-official` を指していれば、DeepSeek の公式 API も DSH 行として提供されます
37
37
  - **⏱️ 失敗からの復帰** — 失敗結果はメモリ内で30秒間キャッシュして連続再試行を抑え、期限後に自動再試行。各プロバイダーにテストボタンを用意
38
38
 
39
39
  ## 📦 インストール
@@ -75,6 +75,8 @@ DSH **0.2.0-rc.2** では **dsh-think-translate 1.2.5 以降**を使用してく
75
75
 
76
76
  **1.2.8 通信と復帰:**通常の HTTP/HTTPS 翻訳は、テキストのバッチごとに curl を起動せず Node の標準ネットワーク機能を使います。SOCKS プロキシと Ollama インストーラーは引き続き curl に対応します。失敗結果をメモリ内に30秒間保持し、その後は思考・回答の翻訳を自動再試行します。タイムアウトはローカルモデルのロードと代替プロバイダーへの切り替えを考慮しています。
77
77
 
78
+ **1.2.10 DSH プロファイル対応:**実行中の Desktop/Web プロファイルに従い、`profiles/<profile>/cordis.patch.yml`(`id` + `config`)、DSH home のパッチ、起動時のオーバーレイを順に読み込みます。旧形式の `settings.yaml` も対応。既定モデル行は `default-model` に対応し、資格情報は DSH home の `.credentials.yaml` から解決してメモリ内だけに保持します。再スキャン時に実際の設定パスを表示します。
79
+
78
80
  **1.2.9 言語修正:**日本語の語彙ヒントは日本語の翻訳先に限定し、OpenAI/Anthropic に完全な言語名を渡します。Bing は `auto-detect` と `zh-Hans`/`zh-Hant` を使用。中国語の判定ではパス・コード・JSON を除外し、簡体字の本文はスキップ、よく使われる繁体字を検出すると変換を依頼、日本語・韓国語の本文は翻訳します。旧訳文キャッシュを一度削除し、設定は保持します。
79
81
 
80
82
  ## 🚀 使い方
@@ -83,7 +85,7 @@ DSH **0.2.0-rc.2** では **dsh-think-translate 1.2.5 以降**を使用してく
83
85
  2. **対象言語**を選択(例:日本語)— 設定パネル・思考行・タスクカードがすべてその言語に切替
84
86
  3. **プロバイダーチェーン**を管理(ドラッグで並べ替え、チェックで有効化):
85
87
  - 組み込み:**google gtx / bing**(無料、システムプロキシ経由)と**ローカル Ollama**。既存のソフト・モデルを再利用し、不足分は準備ボタンを押してからインストール・ダウンロード、進捗を表示(7b/14b または任意のモデル)
86
- - **DSH プロバイダー**:`settings.yaml` に設定済みのエンドポイントが自動表示(読み取り専用、チェックでチェーンに追加)。リスト下の **DSH の設定から取り込む** は再スキャンしてまとめて追加します(baseURL もキーも入力不要。キーは DSH 側の資格情報から解決されます)
88
+ - **DSH プロバイダー**:`profiles/<profile>/cordis.patch.yml` / 旧形式 `settings.yaml` に設定済みのエンドポイントが自動表示(読み取り専用、チェックでチェーンに追加)。リスト下の **DSH の設定から取り込む** は再スキャンしてまとめて追加します(baseURL もキーも入力不要。キーは DSH 側の資格情報から解決されます)
87
89
  - キーは直接入力するほか、プリセットや DSH 行が持つ **`apiKeyEnv`** でも指定できます。その行には `env:NAME` バッジが付き、キーはリクエスト時に解決されて `config.json` には保存されません。編集フォームに環境変数の入力欄はありません(値はそのまま保持されます)が、フィールドを空にすると本当に削除されます(明示的な削除として送信)
88
90
  - チェックを外すとそのプロバイダーは使われません。詳細は [README.md](README.md)
89
91
  4. メッセージを送信し、Think 行を展開して訳文を確認
@@ -100,7 +102,7 @@ DSH **0.2.0-rc.2** では **dsh-think-translate 1.2.5 以降**を使用してく
100
102
  ```
101
103
 
102
104
  - **プロバイダー設定** は `config.json`(実行時生成・gitignore 対象)にあります:`chain`(順序付き ID)、`fallback`(enabled と chain、設定ファイル専用)、`providers`(各項目の `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`)。旧 `priority` 形式は自動移行されます。`apiKeyEnv` を宣言した提供元はリクエスト時にその環境変数からキーを解決し(リテラル `apiKey` はフォールバック)、解決したキーを `config.json` に書き戻すことはありません。パッチ内の `null` はそのフィールドの削除を意味し、UI のクリアはこれを使っています
103
- - **DSH の自動検出** は読み込み時に harness の `settings.yaml`(`llm-pi-ai.providers`)と `.credentials.yaml`(`refs`)を読みます。検出された提供元は `source: "dsh"` が付き、解決済みのキーはメモリ内のみ(`config.json` には書き込まれません)。`/_xlate/dsh-scan` でいつでも再読み込みできます
105
+ - **DSH の自動検出** は読み込み時に harness の `profiles/<profile>/cordis.patch.yml` / 旧形式 `settings.yaml`(`llm-pi-ai.providers`)と `.credentials.yaml`(`refs`)を読みます。検出された提供元は `source: "dsh"` が付き、解決済みのキーはメモリ内のみ(`config.json` には書き込まれません)。`/_xlate/dsh-scan` でいつでも再読み込みできます
104
106
  - **host 側**(`lib/index.js`):プロバイダーアダプタ、LRU キャッシュ(600)、`/_xlate/models` モデル一覧、`/_xlate/model/pull` + `pull-status` モデルダウンロード管理(完了時自動設定)
105
107
  - **client 側**(`lib/client.js`):8 言語 UI、文単位バッチ翻訳、ストリーミング Think 行、localStorage 永続化
106
108
  - 純表示層:原文は会話履歴とモデルコンテキストに完全保持
package/README.ko.md CHANGED
@@ -33,7 +33,7 @@ DeepSeek 계열 모델은 중국어로, 또는 우연히 생각에 쓰는 언어
33
33
  - **🎚️ 번역 시점 조절** — 모두 사전 번역 / 이전 체인 지연 로딩(기본)/ 펼친 체인만 번역
34
34
  - **🔗 동적 제공자 체인** — 목록 순서가 곧 실행 순서. 끌어서 정렬하고 행마다 켜고 끄기. 내장 google gtx / bing / 로컬 Ollama에 더해 임의의 사용자 지정 엔드포인트
35
35
  - **🔌 사용자 지정 제공자(OpenAI / Anthropic)** — 설정 패널에서 OpenAI 호환 엔드포인트(`/v1/chat/completions`)나 **Anthropic Messages API**(Claude)를 추가: 유형, 프리셋, 베이스 URL, API 키, 모델
36
- - **🪄 DSH 설정의 제공자 상속** — `settings.yaml`(`llm-pi-ai.providers`)에서 읽기 전용 DSH 행을 자동 발견하고, 버튼 한 번으로 다시 검사해 모두 체인에 추가. 키는 요청 시 `.credentials.yaml`에서 해석되며 플러그인 설정에 저장되지 않음. harness 자체의 기본 경로도 포함됩니다: `agent-default-model`이 `deepseek-official`을 가리키면 DeepSeek 공식 API도 DSH 행으로 제공됩니다
36
+ - **🪄 DSH 설정의 제공자 상속** — `profiles/<profile>/cordis.patch.yml` / 이전 `settings.yaml`(`llm-pi-ai.providers`)에서 읽기 전용 DSH 행을 자동 발견하고, 버튼 한 번으로 다시 검사해 모두 체인에 추가. 키는 요청 시 `.credentials.yaml`에서 해석되며 플러그인 설정에 저장되지 않음. harness 자체의 기본 경로도 포함됩니다: `default-model` / `agent-default-model`이 `deepseek-official`을 가리키면 DeepSeek 공식 API도 DSH 행으로 제공됩니다
37
37
  - **⏱️ 실패 후 복구** — 실패 결과를 메모리에 30초간 캐시해 반복 요청을 줄이고 이후 자동으로 재시도합니다. 제공자마다 테스트 버튼이 있습니다
38
38
 
39
39
  ## 📦 설치
@@ -75,6 +75,8 @@ DSH **0.2.0-rc.2**에서는 **dsh-think-translate 1.2.5 이상**을 사용하세
75
75
 
76
76
  **1.2.8 전송 및 복구:** 일반 HTTP/HTTPS 번역은 텍스트 배치마다 curl을 실행하는 대신 Node 기본 네트워크 기능을 사용합니다. SOCKS 프록시와 Ollama 설치 프로그램은 curl 호환 경로를 유지합니다. 실패 결과를 메모리에 30초간 보관한 뒤 사고·답변 번역을 자동 재시도합니다. 타임아웃은 로컬 모델 로드와 다른 제공자로의 전환 시간을 고려합니다.
77
77
 
78
+ **1.2.10 DSH 프로필 호환:** 실행 중인 Desktop/Web 프로필의 `profiles/<profile>/cordis.patch.yml`(`id` + `config`)을 읽고 DSH 홈 패치와 시작 오버레이를 순서대로 적용합니다. 이전 `settings.yaml`도 지원합니다. 기본 모델 행은 `default-model`을 지원하며 자격 증명은 DSH 홈의 `.credentials.yaml`에서 읽어 메모리에만 유지합니다. 다시 검사하면 실제 설정 경로가 표시됩니다.
79
+
78
80
  **1.2.9 언어 수정:** 일본어 어휘 힌트는 일본어 대상에만 적용하며 OpenAI/Anthropic에 전체 언어 이름을 전달합니다. Bing은 `auto-detect`와 `zh-Hans`/`zh-Hant`를 사용합니다. 중국어 감지 시 경로·코드·JSON을 제외하고, 이미 간체인 본문은 건너뛰며 자주 쓰는 번체 글자를 감지하면 변환을 요청합니다. 일본어·한국어 본문은 번역합니다. 이전 번역 캐시만 한 번 삭제하고 설정은 유지합니다.
79
81
 
80
82
  ## 🚀 사용법
@@ -83,7 +85,7 @@ DSH **0.2.0-rc.2**에서는 **dsh-think-translate 1.2.5 이상**을 사용하세
83
85
  2. **대상 언어** 선택(예: 한국어) — 설정 패널, 사고 행, 작업 카드가 모두 전환
84
86
  3. **제공자 체인** 관리(드래그로 순서 변경, 체크로 사용):
85
87
  - 내장: **google gtx / bing**(무료, 시스템 프록시 경유)과 **로컬 Ollama**. 기존 소프트웨어와 모델은 재사용하고, 없는 항목은 준비 버튼을 누른 뒤 설치·다운로드하며 진행률을 표시합니다(7b/14b 또는 사용자 지정 모델)
86
- - **DSH 제공자**: `settings.yaml`에 설정된 엔드포인트가 자동 표시(읽기 전용, 체크하면 체인에 추가). 목록 아래의 **DSH 설정에서 가져오기**는 다시 검사해 한 번에 모두 추가합니다(baseURL과 키를 다시 입력할 필요가 없고, 키는 DSH 자체 자격 증명에서 해석됩니다)
88
+ - **DSH 제공자**: `profiles/<profile>/cordis.patch.yml` / 이전 `settings.yaml`에 설정된 엔드포인트가 자동 표시(읽기 전용, 체크하면 체인에 추가). 목록 아래의 **DSH 설정에서 가져오기**는 다시 검사해 한 번에 모두 추가합니다(baseURL과 키를 다시 입력할 필요가 없고, 키는 DSH 자체 자격 증명에서 해석됩니다)
87
89
  - 키는 직접 입력하거나 프리셋·DSH 행이 가진 **`apiKeyEnv`** 로 지정할 수 있습니다. 그런 행에는 `env:NAME` 배지가 붙고 키는 요청 시 해석되며 `config.json`에 저장되지 않습니다. 편집 폼에는 환경 변수 입력란이 없지만(값은 그대로 유지) 필드를 비우면 실제로 삭제됩니다(명시적 삭제로 전송)
88
90
  - 체크를 해제하면 해당 제공자는 건너뜁니다. 자세한 내용은 [README.md](README.md)
89
91
  4. 메시지를 보내고 Think 행을 펼쳐 번역 확인
@@ -100,7 +102,7 @@ DSH **0.2.0-rc.2**에서는 **dsh-think-translate 1.2.5 이상**을 사용하세
100
102
  ```
101
103
 
102
104
  - **제공자 설정**은 `config.json`(런타임 생성, gitignore 대상)에 있습니다: `chain`(순서 있는 id), `fallback`(enabled + chain, 설정 파일 전용), `providers`(각 항목의 `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`). 예전 `priority` 형식은 자동 마이그레이션됩니다. `apiKeyEnv`를 선언한 제공자는 요청 시 해당 환경 변수에서 키를 해석하며(리터럴 `apiKey`는 폴백), 해석된 키를 `config.json`에 다시 쓰지 않습니다. 패치의 `null`은 해당 필드 삭제를 뜻하며 UI가 그렇게 비웁니다
103
- - **DSH 자동 발견**은 로드 시 harness의 `settings.yaml`(`llm-pi-ai.providers`)과 `.credentials.yaml`(`refs`)을 읽습니다. 발견된 제공자는 `source: "dsh"`로 표시되고 해석된 키는 메모리에만 남으며(`config.json`에 기록되지 않음), `/_xlate/dsh-scan`으로 필요할 때 다시 읽습니다
105
+ - **DSH 자동 발견**은 로드 시 harness의 `profiles/<profile>/cordis.patch.yml` / 이전 `settings.yaml`(`llm-pi-ai.providers`)과 `.credentials.yaml`(`refs`)을 읽습니다. 발견된 제공자는 `source: "dsh"`로 표시되고 해석된 키는 메모리에만 남으며(`config.json`에 기록되지 않음), `/_xlate/dsh-scan`으로 필요할 때 다시 읽습니다
104
106
  - **host 측**(`lib/index.js`): 제공자 어댑터, LRU 캐시(600), `/_xlate/models` 모델 목록, `/_xlate/model/pull` + `pull-status` 모델 다운로드 관리(완료 시 자동 설정)
105
107
  - **client 측**(`lib/client.js`): 8개 언어 UI, 문장 배치 번역, 스트리밍 Think 행, localStorage 영구 저장
106
108
  - 순수 표시 계층: 원문은 대화 기록과 모델 컨텍스트에 완전 보존
package/README.md CHANGED
@@ -24,7 +24,7 @@ DeepSeek-class models often reason in Chinese — or in whatever language they h
24
24
  - **🌍 8 languages, one consistent UI** — 中文 / English / 日本語 / 한국어 / Español / Français / Deutsch / Русский; the settings panel, thinking rows and task cards all follow your choice, and it persists across reloads
25
25
  - **🔗 Dynamic provider chain** — order providers by drag-and-drop and enable/disable each one. Built-ins (google gtx, bing, local Ollama) plus any number of custom providers; the chain already ends with the free providers, so a second fallback pass is a config-file option, not a UI one
26
26
  - **🔌 Custom providers (OpenAI & Anthropic)** — add arbitrary OpenAI-compatible endpoints (any `/v1/chat/completions` gateway) or native **Anthropic Messages API** endpoints (Claude) from the settings panel: name, type, base URL, API key, model
27
- - **🪄 DSH provider discovery** — providers already configured in DSH's `settings.yaml` (`llm-pi-ai.providers`, e.g. linuxdo-hub, coding-hub) are auto-discovered and appear in the chain as read-only "DSH" entries, and **one button in settings rescans and adds them all at once**; keys are resolved from `.credentials.yaml` at runtime and never written to the plugin's config. The harness's own default route counts too: when `agent-default-model` points at `deepseek-official`, DeepSeek's API is offered as one more DSH entry
27
+ - **🪄 DSH provider discovery** — providers already configured in DSH's `profiles/<profile>/cordis.patch.yml` / legacy `settings.yaml` (`llm-pi-ai.providers`, e.g. linuxdo-hub, coding-hub) are auto-discovered and appear in the chain as read-only "DSH" entries, and **one button in settings rescans and adds them all at once**; keys are resolved from `.credentials.yaml` at runtime and never written to the plugin's config. The harness's own default route counts too: when `default-model` / `agent-default-model` points at `deepseek-official`, DeepSeek's API is offered as one more DSH entry
28
28
  - **🔒 Local Ollama** — reuse an existing installation and model. When enabled Ollama is first in priority, or you click Test, the plugin starts the service and loads the model as needed. Missing software/models require the setup button before installation/download, with live progress
29
29
  - **🧠 Zero context cost** — pure display layer: the model still sees the original text, and translated text never consumes the context window
30
30
  - **☁️ Google / Bing fallback** — automatic switch when other providers are unavailable (google goes through a Node CONNECT tunnel using the system proxy, bypassing anti-bot blocks)
@@ -72,6 +72,8 @@ Version **1.2.6** fixes duplicated thinking and answer text in DSH 0.2: the assi
72
72
 
73
73
  **1.2.8 transport and recovery:** ordinary HTTP/HTTPS translation requests use native Node networking instead of spawning curl per text batch; SOCKS proxies and the Ollama installer retain curl compatibility. Failures stay in memory for 30 seconds, then reasoning and answer translation resume automatically. Request deadlines allow time for local-model loading and provider fallback.
74
74
 
75
+ **1.2.10 DSH profile compatibility:** discovery follows the running Desktop/Web profile and reads `profiles/<profile>/cordis.patch.yml` (`id` + `config`), with home patches and launcher overlays applied afterward. Legacy `settings.yaml` is still supported. Default model rows use `default-model`; credentials remain in the DSH home `.credentials.yaml` and stay in memory. Rescan diagnostics show the file actually read.
76
+
75
77
  **1.2.9 language fixes:** Japanese vocabulary hints now apply only to Japanese targets; OpenAI/Anthropic requests include a full language name. Bing uses `auto-detect` and maps Chinese targets to `zh-Hans`/`zh-Hant`. Chinese detection examines prose after removing paths, code and JSON: already-Simplified prose is skipped, common Traditional script cues request conversion, and Japanese/Korean prose is translated. Old persisted translation results are cleared once; settings remain intact.
76
78
 
77
79
  ## 🚀 Usage
@@ -102,7 +104,7 @@ browser → POST /_xlate/translate (same-origin, no CORS)
102
104
  ```
103
105
 
104
106
  - **Provider config** lives in `config.json` (runtime, gitignored): `chain` (ordered ids), `fallback` (enabled + chain, config-only), `providers` (per-provider `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`). Old `priority`-based configs auto-migrate. A provider that declares `apiKeyEnv` resolves its key from that environment variable at request time (the literal `apiKey` stays as the fallback), and no env-resolved key is ever written back to `config.json`. A `null` field in a config patch deletes that field, which is how the UI clears one.
105
- - **DSH discovery** reads the harness `settings.yaml` (`llm-pi-ai.providers`) and `.credentials.yaml` (`refs`) on load; discovered providers are marked `source: "dsh"`, resolved keys stay in memory (never written to `config.json`), and a `/_xlate/dsh-scan` route re-reads them on demand.
107
+ - **DSH discovery** reads the harness `profiles/<profile>/cordis.patch.yml` / legacy `settings.yaml` (`llm-pi-ai.providers`) and `.credentials.yaml` (`refs`) on load; discovered providers are marked `source: "dsh"`, resolved keys stay in memory (never written to `config.json`), and a `/_xlate/dsh-scan` route re-reads them on demand.
106
108
  - **Host half** (`lib/index.js`): provider adapters, ordered chain + fallback execution, LRU cache (600), per-provider override for tests, `/_xlate/models` listing, `/_xlate/model/pull` + `pull-status` model download management (auto-configures on completion)
107
109
  - **Client half** (`lib/client.js`): 8-language UI, drag-reorderable provider list, add/edit/delete custom providers, per-provider test buttons, sentence/paragraph-batched translation, streaming Think rows, localStorage persistence (settings + translation cache)
108
110
  - Pure display layer: originals remain in the transcript and model context
package/README.ru.md CHANGED
@@ -33,7 +33,7 @@
33
33
  - **🎚️ Настраиваемый момент перевода** — переводить всё / ленивая загрузка старых цепочек (по умолчанию) / только при раскрытии
34
34
  - **🔗 Динамическая цепочка провайдеров** — порядок списка и есть порядок обращения: перетаскивайте для сортировки, включайте и выключайте каждую строку. Встроенные google gtx / bing / локальный Ollama плюс любые свои эндпоинты
35
35
  - **🔌 Свои провайдеры (OpenAI и Anthropic)** — из панели можно добавить любой OpenAI-совместимый эндпоинт (`/v1/chat/completions`) или **Anthropic Messages API** (Claude): тип, шаблон, базовый URL, ключ API, модель
36
- - **🪄 Наследование провайдеров из DSH** — читает из `settings.yaml` (`llm-pi-ai.providers`) строки DSH только для чтения; одна кнопка заново сканирует и добавляет их все в цепочку. Ключ разрешается на каждый запрос из `.credentials.yaml` и никогда не попадает в конфиг плагина. Собственный маршрут харнесса тоже учитывается: если `agent-default-model` указывает на `deepseek-official`, официальный API DeepSeek появляется как ещё одна строка DSH
36
+ - **🪄 Наследование провайдеров из DSH** — читает из `profiles/<profile>/cordis.patch.yml` / прежний `settings.yaml` (`llm-pi-ai.providers`) строки DSH только для чтения; одна кнопка заново сканирует и добавляет их все в цепочку. Ключ разрешается на каждый запрос из `.credentials.yaml` и никогда не попадает в конфиг плагина. Собственный маршрут харнесса тоже учитывается: если `default-model` / `agent-default-model` указывает на `deepseek-official`, официальный API DeepSeek появляется как ещё одна строка DSH
37
37
  - **⏱️ Восстановление после ошибок** — неудачные переводы сохраняются в памяти на 30 секунд, чтобы избежать частых повторов, затем автоматически повторяются; у каждого провайдера есть кнопка проверки
38
38
 
39
39
  ## 📦 Установка
@@ -75,6 +75,8 @@ New-Item -ItemType Junction -Path "$HOME\.dsh\profiles\node_modules\dsh-think-tr
75
75
 
76
76
  **1.2.8 передача запросов и восстановление:** обычные HTTP/HTTPS-запросы перевода используют сетевые средства Node без запуска curl для каждого пакета текста. Для SOCKS-прокси и установщика Ollama сохранён путь через curl. Ошибки хранятся в памяти 30 секунд, после чего перевод рассуждений и ответов автоматически повторяется. Тайм-ауты учитывают загрузку локальной модели и переход к другим провайдерам.
77
77
 
78
+ **1.2.10 совместимость с профилями DSH:** обнаружение следует активному профилю Desktop/Web и читает `profiles/<profile>/cordis.patch.yml` (`id` + `config`), затем патчи домашнего каталога DSH и параметры запуска. Прежний `settings.yaml` поддерживается. Модель по умолчанию использует `default-model`; ключи читаются из `.credentials.yaml` домашнего каталога DSH и остаются только в памяти. Диагностика показывает прочитанный файл.
79
+
78
80
  **1.2.9 языковые исправления:** подсказки японской лексики применяются только к японскому; OpenAI/Anthropic получают полное название языка. Bing использует `auto-detect` и `zh-Hans`/`zh-Hant`. Определение китайского исключает пути, код и JSON: упрощённая проза пропускается, частые традиционные иероглифы вызывают запрос преобразования, японская/корейская проза переводится. Старый кэш переводов очищается один раз, настройки сохраняются.
79
81
 
80
82
  ## 🚀 Использование
@@ -83,7 +85,7 @@ New-Item -ItemType Junction -Path "$HOME\.dsh\profiles\node_modules\dsh-think-tr
83
85
  2. Выберите **целевой язык** (например, Русский) — панель, строки и карточки переключатся на этот язык
84
86
  3. Управляйте **цепочкой провайдеров** (перетаскивание — порядок, галочка — включение):
85
87
  - Встроенные: **google gtx / bing** (бесплатно, через системный прокси) и **локальный Ollama**. Существующие программа и модели используются повторно; для установки или скачивания недостающих компонентов нужно нажать кнопку подготовки, прогресс отображается (7b/14b или своя модель)
86
- - **Провайдеры DSH**: настроенные в `settings.yaml` эндпоинты появляются сами (только чтение; галочка добавляет их в цепочку). Кнопка **Импортировать из конфигурации DSH** сразу под списком заново сканирует конфигурацию и добавляет их все за один раз — ни baseURL, ни ключ вводить не нужно: ключ берётся из учётных данных DSH
88
+ - **Провайдеры DSH**: настроенные в `profiles/<profile>/cordis.patch.yml` / прежний `settings.yaml` эндпоинты появляются сами (только чтение; галочка добавляет их в цепочку). Кнопка **Импортировать из конфигурации DSH** сразу под списком заново сканирует конфигурацию и добавляет их все за один раз — ни baseURL, ни ключ вводить не нужно: ключ берётся из учётных данных DSH
87
89
  - Ключ вводится вручную или приходит как **`apiKeyEnv`** из шаблона либо строки DSH: у такой строки появляется значок `env:NAME`, а ключ разрешается на каждый запрос и никогда не пишется в `config.json`. В форме редактирования нет поля переменной окружения (значение сохраняется как есть), но очистка поля действительно его удаляет (отправляется как явное удаление)
88
90
  - Снятая галочка означает, что провайдер пропускается. Подробности — в [README.md](README.md)
89
91
  4. Отправьте сообщение и разверните строку Think, чтобы увидеть перевод
@@ -100,7 +102,7 @@ New-Item -ItemType Junction -Path "$HOME\.dsh\profiles\node_modules\dsh-think-tr
100
102
  ```
101
103
 
102
104
  - **Конфигурация провайдеров** живёт в `config.json` (создаётся во время работы, в gitignore): `chain` (упорядоченные id), `fallback` (enabled + chain, только в файле), `providers` (у каждого `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`). Старые конфиги с `priority` мигрируют автоматически; провайдер с `apiKeyEnv` разрешает ключ из этой переменной на каждый запрос (литеральный `apiKey` остаётся запасным), и разрешённый ключ никогда не пишется обратно в `config.json`; `null` в патче удаляет поле — так интерфейс и очищает значения
103
- - **Обнаружение DSH** при загрузке читает `settings.yaml` (`llm-pi-ai.providers`) и `.credentials.yaml` (`refs`) харнесса; найденные провайдеры помечаются `source: "dsh"`, разрешённые ключи остаются в памяти (никогда в `config.json`), а маршрут `/_xlate/dsh-scan` перечитывает их по запросу
105
+ - **Обнаружение DSH** при загрузке читает `profiles/<profile>/cordis.patch.yml` / прежний `settings.yaml` (`llm-pi-ai.providers`) и `.credentials.yaml` (`refs`) харнесса; найденные провайдеры помечаются `source: "dsh"`, разрешённые ключи остаются в памяти (никогда в `config.json`), а маршрут `/_xlate/dsh-scan` перечитывает их по запросу
104
106
  - **Host-часть** (`lib/index.js`): адаптеры провайдеров, LRU-кэш (600), `/_xlate/models`, `/_xlate/model/pull` + `pull-status` (автонастройка по завершении)
105
107
  - **Client-часть** (`lib/client.js`): интерфейс на 8 языках, пакетный перевод, потоковые строки Think, localStorage
106
108
  - Чистый слой отображения: оригиналы остаются в истории и контексте модели
package/README.zh-CN.md CHANGED
@@ -33,7 +33,7 @@ DeepSeek 系模型经常用中文思考——或者用它们碰巧习惯的语
33
33
  - **🎚️ 可调翻译时机** — 三档:全部预翻译 / 懒加载历史(默认)/ 仅展开时翻译
34
34
  - **🔗 动态提供方链** — 列表顺序就是投递顺序:拖动排序、逐行开关;内置 google gtx / bing / 本地 Ollama,外加任意数量的自定义端点
35
35
  - **🔌 自定义提供方(OpenAI 与 Anthropic)** — 设置面板里可添加任意 OpenAI 兼容端点(任何 `/v1/chat/completions` 网关)或原生 **Anthropic Messages API**(Claude):类型、预设、接口地址、API 密钥、模型
36
- - **🪄 继承 DSH 已配置的提供方** — 自动发现 `settings.yaml`(`llm-pi-ai.providers`)里的只读 DSH 行,一个按钮重新扫描并全部加入链;密钥在请求时从 `.credentials.yaml` 解析,绝不写入插件配置;harness 自己的默认路线也算:`agent-default-model` 指向 `deepseek-official` 时,DeepSeek 官方 API 也会作为一条 DSH 条目提供
36
+ - **🪄 继承 DSH 已配置的提供方** — 自动发现 `profiles/<profile>/cordis.patch.yml` / 旧版 `settings.yaml`(`llm-pi-ai.providers`)里的只读 DSH 行,一个按钮重新扫描并全部加入链;密钥在请求时从 `.credentials.yaml` 解析,绝不写入插件配置;harness 自己的默认路线也算:`default-model` / `agent-default-model` 指向 `deepseek-official` 时,DeepSeek 官方 API 也会作为一条 DSH 条目提供
37
37
  - **⏱️ 失败韧性** — 失败结果仅在内存缓存 30 秒,避免频繁重试;冷却结束后自动重试,每行保留独立的测试按钮
38
38
 
39
39
  ## 📦 安装
@@ -75,6 +75,8 @@ DSH **0.2.0-rc.2** 请使用 **dsh-think-translate 1.2.5 或更新版本**。1.2
75
75
 
76
76
  **1.2.8 传输与恢复:**普通 HTTP/HTTPS 翻译请求改用 Node 原生网络,不再为每个文本批次启动 curl;SOCKS 代理和 Ollama 安装器保留 curl 兼容路径。失败结果在内存缓存 30 秒,冷却结束后思考与回答翻译会自动恢复重试。请求超时为本地模型加载和提供方回退留出时间。
77
77
 
78
+ **1.2.10 DSH 配置兼容:**发现提供方时跟随当前 Desktop/Web profile,读取 `profiles/<profile>/cordis.patch.yml`(`id` + `config`),随后应用 home 补丁与启动覆盖;继续支持旧版 `settings.yaml`。默认模型行支持 `default-model`,凭据仍从 DSH home 的 `.credentials.yaml` 解析且只留在内存;重新扫描会显示实际读取的配置路径。
79
+
78
80
  **1.2.9 语言修复:**日语词汇提示仅用于日语目标,OpenAI/Anthropic 请求带上完整语言名称。Bing 使用 `auto-detect`,并将中文目标映射为 `zh-Hans`/`zh-Hant`。中文检测先排除路径、代码和 JSON:已是简体的正文跳过,常用繁体区别字触发转换请求,日语/韩语正文正常翻译。升级时一次性清理旧译文缓存,保留设置。
79
81
 
80
82
  ## 🚀 使用
@@ -83,7 +85,7 @@ DSH **0.2.0-rc.2** 请使用 **dsh-think-translate 1.2.5 或更新版本**。1.2
83
85
  2. 选择**目标语言**(比如日本語)——设置面板、思考行、任务卡片全部切换为该语言
84
86
  3. 管理**供应商链**(拖动排序,勾选即启用):
85
87
  - 内置:**google gtx / bing**(免费,使用系统代理)与**本地模型(Ollama)**;复用已有软件和模型,缺少时点击准备按钮后才安装/下载,并显示进度(支持 7b/14b 或自定义模型)
86
- - **DSH 供应商**:`settings.yaml` 里已配置的端点会自动出现(只读,勾选即加入链);列表下方的 **继承 DSH 已配置的 API** 会重新扫描并一键全部加入——不用重填 baseURL 或密钥(密钥在请求时从 DSH 自己的凭据里解析)
88
+ - **DSH 供应商**:`profiles/<profile>/cordis.patch.yml` / 旧版 `settings.yaml` 里已配置的端点会自动出现(只读,勾选即加入链);列表下方的 **继承 DSH 已配置的 API** 会重新扫描并一键全部加入——不用重填 baseURL 或密钥(密钥在请求时从 DSH 自己的凭据里解析)
87
89
  - 密钥可以直接填,也可以由预设或 DSH 行带上 **`apiKeyEnv`**:那类行显示 `env:NAME` 徽标,密钥在请求时解析、绝不写入 `config.json`。编辑表单里没有环境变量输入框(值会原样保留),清空某个字段会真的删除它(以显式删除提交),所以从环境变量名切回字面密钥是可行的
88
90
  - 取消勾选即跳过该供应商;更细的说明以 [README.md](README.md) 为准
89
91
  4. 发消息让模型思考,展开 Think 行查看译文
@@ -100,7 +102,7 @@ DSH **0.2.0-rc.2** 请使用 **dsh-think-translate 1.2.5 或更新版本**。1.2
100
102
  ```
101
103
 
102
104
  - **提供方配置** 存在 `config.json`(运行期生成,已 gitignore):`chain`(有序 id)、`fallback`(enabled + chain,仅配置文件)、`providers`(每项的 `type`/`enabled`/`baseURL`/`apiKey`/`apiKeyEnv`/`model`)。旧的 `priority` 配置会自动迁移;声明 `apiKeyEnv` 的提供方在请求时从该环境变量解析密钥(字面 `apiKey` 作为兜底),解析出的密钥绝不写回 `config.json`;patch 里显式的 `null` 表示删除该字段,UI 就是用它清空字段的
103
- - **DSH 发现** 在加载时读取 harness 的 `settings.yaml`(`llm-pi-ai.providers`)与 `.credentials.yaml`(`refs`);被发现的提供方标记为 `source: "dsh"`,解析出的密钥只留在内存中(绝不写入 `config.json`),`/_xlate/dsh-scan` 路由可按需重新读取
105
+ - **DSH 发现** 在加载时读取 harness 的 `profiles/<profile>/cordis.patch.yml` / 旧版 `settings.yaml`(`llm-pi-ai.providers`)与 `.credentials.yaml`(`refs`);被发现的提供方标记为 `source: "dsh"`,解析出的密钥只留在内存中(绝不写入 `config.json`),`/_xlate/dsh-scan` 路由可按需重新读取
104
106
  - **host 半边**(`lib/index.js`):供应商适配器、LRU 缓存(600)、`/_xlate/models` 模型列表、`/_xlate/model/pull` + `pull-status` 模型下载管理(完成后自动配置启用)
105
107
  - **client 半边**(`lib/client.js`):8 语言 UI、段落/句子分批翻译、流式 Think 行、设置与译文缓存持久化(localStorage)
106
108
  - 纯显示层:原文完整保留在会话日志与模型上下文中
package/lib/client.js CHANGED
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
23
23
  // there yet (an older process still serving the port), and the contract test
24
24
  // pins PLUGIN_VERSION to package.json so the two can never drift.
25
25
  var PLUGIN_NAME = "dsh-think-translate";
26
- var PLUGIN_VERSION = "1.2.9";
26
+ var PLUGIN_VERSION = "1.2.10";
27
27
  var PLUGIN_REPO = "https://github.com/UncleK/dsh-think-translate";
28
28
  // The official MarkdownText contract changed in 0.1.2-alpha: it now takes a
29
29
  // nested `labels` object and dereferences it without defaults inside its code
@@ -2111,8 +2111,8 @@ window.__ModuleLoader__.load({
2111
2111
  var provs = cfg.providers || {};
2112
2112
 
2113
2113
  // ---- inherit the harness's own providers ------------------------
2114
- // DSH already knows its endpoints (llm-pi-ai.providers in the harness
2115
- // settings.yaml) and where their keys live (.credentials.yaml). The host
2114
+ // DSH already knows its endpoints (llm-pi-ai.providers in the active
2115
+ // profile) and where their keys live (.credentials.yaml). The host
2116
2116
  // exposes them as read-only `source: 'dsh'` entries and re-reads them on
2117
2117
  // demand, so the one-click path is: rescan, then add — never retype a base
2118
2118
  // URL or a key per provider.
@@ -2120,7 +2120,7 @@ window.__ModuleLoader__.load({
2120
2120
  var dshAvailable = dshAll.filter(function (id) { return chain.indexOf(id) < 0; });
2121
2121
  // The host already merges the harness's providers into the config it serves,
2122
2122
  // so they show up in the "available providers" list without being asked for.
2123
- // What the button adds is the RE-READ: editing ~/.dsh/settings.yaml (or
2123
+ // What the button adds is the RE-READ: editing the active DSH profile (or
2124
2124
  // fixing the home the host looks at) otherwise needs a web restart.
2125
2125
  var rescanDsh = function () {
2126
2126
  setMsg(t.dshRescanning);
@@ -2133,7 +2133,7 @@ window.__ModuleLoader__.load({
2133
2133
  var found = (d.providers || []).length;
2134
2134
  setMsg(found
2135
2135
  ? t.dshRescanDone + " · " + found
2136
- : t.dshImportEmpty + (d.home ? " · " + d.home + "/settings.yaml" : ""));
2136
+ : t.dshImportEmpty + (d.sourcePath ? " · " + d.sourcePath : (d.home ? " · " + d.home + "/settings.yaml" : "")) + (d.error ? " · " + d.error : ""));
2137
2137
  })
2138
2138
  .catch(function (e) { setMsg(t.connFail + ": " + (e && e.message ? e.message : e)); });
2139
2139
  };
@@ -0,0 +1,162 @@
1
+ // DSH moved settings into profile-owned Cordis rows. Read only literal values;
2
+ // never evaluate a profile's !!js expressions while discovering providers.
3
+ import { existsSync, readFileSync } from 'node:fs'
4
+ import { join, basename } from 'node:path'
5
+ import { load, DEFAULT_SCHEMA, Type } from 'js-yaml'
6
+
7
+ const EXPRESSION = Object.freeze({ dshExpression: true })
8
+ const schema = DEFAULT_SCHEMA.extend([
9
+ new Type('tag:yaml.org,2002:js', { kind: 'scalar', construct: () => EXPRESSION }),
10
+ ])
11
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value) && value !== EXPRESSION
12
+ const text = value => typeof value === 'string' && value.trim() ? value.trim() : undefined
13
+
14
+ export function parseDshYaml(source) {
15
+ try { return load(source, { schema, maxDepth: 40, maxTotalMergeKeys: 1000 }) }
16
+ // YAMLException includes source lines, which can contain credentials.
17
+ catch { throw new Error('Invalid DSH YAML') }
18
+ }
19
+
20
+ // Cordis replaces a row's config when a later patch supplies that field.
21
+ // Flatten groups and insert lists without following Includes or running code.
22
+ export function dshSections(documents) {
23
+ const rows = new Map()
24
+ function visit(document, parentDisabled = false, ancestors = new Set()) {
25
+ if (!document || typeof document !== 'object' || ancestors.has(document)) return
26
+ const next = new Set(ancestors).add(document)
27
+ if (Array.isArray(document)) {
28
+ for (const row of document) visit(row, parentDisabled, next)
29
+ return
30
+ }
31
+ if (!object(document)) return
32
+ if (Object.hasOwn(document, 'insert')) visit(document.insert, parentDisabled, next)
33
+ const id = text(document.id)
34
+ if (id) {
35
+ const old = rows.get(id) || {}
36
+ if (old.name && document.name && old.name !== document.name) return
37
+ const row = { ...old, ...document }
38
+ if (parentDisabled) row.disabled = true
39
+ rows.set(id, row)
40
+ if (row.group && Array.isArray(row.config)) visit(row.config, parentDisabled || (row.disabled != null && row.disabled !== false), next)
41
+ } else if (!Object.hasOwn(document, 'insert')) {
42
+ // Legacy settings.yaml uses section names instead of id/config rows.
43
+ for (const [key, config] of Object.entries(document)) {
44
+ if (object(config)) rows.set(key === 'agent-default-model' ? 'default-model' : key, { id: key, config })
45
+ }
46
+ }
47
+ }
48
+ for (const document of documents) visit(document)
49
+ const sections = new Map()
50
+ for (const [id, row] of rows) {
51
+ const key = id === 'agent-default-model' ? 'default-model' : id
52
+ // Keep a disabled row as a null section so a successful scan can remove
53
+ // its old imported providers, rather than retaining stale credentials.
54
+ sections.set(key, row.disabled != null && row.disabled !== false ? null : (object(row.config) ? row.config : {}))
55
+ }
56
+ return sections
57
+ }
58
+
59
+ export function providersFromSections(sections) {
60
+ const out = {}
61
+ const providers = sections.get('llm-pi-ai')?.providers
62
+ if (!object(providers)) return out
63
+ for (const [id, raw] of Object.entries(providers)) {
64
+ if (['__proto__', 'constructor', 'prototype'].includes(id)) continue
65
+ if (!object(raw) || raw.enabled === false || raw.disabled === true) continue
66
+ const baseURL = text(raw.baseURL)
67
+ const model = Array.isArray(raw.models) ? raw.models.map(m => object(m) ? text(m.id) : undefined).find(Boolean) : undefined
68
+ // Preserve the existing supported discovery protocol; Responses-only
69
+ // endpoints cannot be driven through our chat/completions adapter.
70
+ if (!baseURL || !model || (raw.api !== undefined && raw.api !== 'openai-completions')) continue
71
+ const entry = { baseURL, model }
72
+ if (text(raw.apiKeyEnv)) entry.apiKeyEnv = text(raw.apiKeyEnv)
73
+ if (text(raw.apiKey)) entry.apiKey = text(raw.apiKey)
74
+ Object.defineProperty(out, id, { value: entry, enumerable: true, configurable: true, writable: true })
75
+ }
76
+ return out
77
+ }
78
+
79
+ export function defaultRouteFromSections(sections) {
80
+ const config = sections.get('default-model')
81
+ if (config?.provider !== 'deepseek-official' || !text(config.model)) return null
82
+ const deepseek = sections.get('llm-deepseek')
83
+ if (sections.has('llm-deepseek') && deepseek === null) return null
84
+ return {
85
+ id: 'deepseek-official',
86
+ entry: {
87
+ type: 'openai',
88
+ baseURL: text(deepseek?.baseURL) || 'https://api.deepseek.com',
89
+ model: text(config.model), apiKeyEnv: 'DEEPSEEK_API_KEY',
90
+ },
91
+ }
92
+ }
93
+
94
+ export function extractDshProviders(source) {
95
+ return providersFromSections(dshSections([parseDshYaml(source)]))
96
+ }
97
+
98
+ export function extractHarnessDefaultRoute(source) {
99
+ return defaultRouteFromSections(dshSections([parseDshYaml(source)]))
100
+ }
101
+
102
+ export function extractCredRefs(source) {
103
+ const raw = parseDshYaml(source)?.refs
104
+ if (!object(raw)) return {}
105
+ return Object.fromEntries(Object.entries(raw).filter(([, value]) => typeof value === 'string' && value.length))
106
+ }
107
+
108
+ export function scanDshProviders({ candidates = [], profileContext, modulePath = '', runtimeRows = [], exists = existsSync, readText = p => readFileSync(p, 'utf8') } = {}) {
109
+ // profileContext is supplied by the official launcher and wins over all
110
+ // path guesses, including stale settings.yaml under another DSH_HOME.
111
+ const installed = modulePath.replace(/\\/g, '/').match(/^(.*)\/profiles\/([^/]+)\/node_modules\//)
112
+ let home = text(profileContext?.home) || installed?.[1]
113
+ let profile = text(profileContext?.name) || installed?.[2]
114
+ const has = path => { try { return exists(path) } catch { return false } }
115
+ if (!home) home = candidates.find(dir => has(join(dir, 'settings.yaml')) || has(join(dir, 'cordis.patch.yml')) || ['desktop', 'web'].some(name => has(join(dir, 'profiles', name, 'cordis.patch.yml')))) || candidates[0] || ''
116
+ if (!profile && !profileContext?.dir && !profileContext?.patchPath) {
117
+ const found = ['desktop', 'web'].filter(name => has(join(home, 'profiles', name, 'cordis.patch.yml')))
118
+ if (found.length === 1) profile = found[0]
119
+ // When both profiles exist the caller must identify the running one.
120
+ if (found.length > 1) return { providers: {}, refs: {}, ok: false, diag: { home, profile: null, sourcePath: join(home, 'profiles'), sourcePaths: [], providers: [], error: 'Cannot identify the active DSH profile' } }
121
+ }
122
+ const dir = text(profileContext?.dir) || (profile ? join(home, 'profiles', profile) : undefined)
123
+ const patchPath = text(profileContext?.patchPath) || (dir ? join(dir, 'cordis.patch.yml') : undefined)
124
+ const legacyPath = join(home, 'settings.yaml')
125
+ const modern = Boolean(profileContext?.dir || profileContext?.patchPath || runtimeRows.length
126
+ || (dir && has(join(dir, 'cordis.yml'))) || (patchPath && has(patchPath)) || has(join(home, 'cordis.patch.yml')))
127
+ const paths = modern ? [dir && join(dir, 'cordis.yml'), patchPath, join(home, 'cordis.patch.yml')].filter(Boolean) : [legacyPath]
128
+ const diag = { home, profile: profile || (dir ? basename(dir) : null), sourcePath: patchPath || paths[0], sourcePaths: [], settingsFound: has(legacyPath), credsFound: false, providers: [], error: null }
129
+ const result = { providers: {}, refs: {}, ok: false, diag }
130
+ try {
131
+ const documents = modern ? [runtimeRows] : []
132
+ for (const path of [...new Set(paths)]) {
133
+ diag.sourcePath = path
134
+ let source
135
+ try { source = readText(path) } catch (error) {
136
+ if (error?.code === 'ENOENT') continue
137
+ throw new Error('Unable to read DSH configuration')
138
+ }
139
+ documents.push(parseDshYaml(source))
140
+ diag.sourcePaths.push(path)
141
+ }
142
+ if (modern && Array.isArray(profileContext?.overlays)) documents.push(profileContext.overlays)
143
+ const sections = dshSections(documents)
144
+ const providers = providersFromSections(sections)
145
+ const def = defaultRouteFromSections(sections)
146
+ for (const [id, entry] of Object.entries(providers)) result.providers[id] = { type: 'openai', source: 'dsh', enabled: false, ...entry }
147
+ if (def && !Object.hasOwn(result.providers, def.id)) result.providers[def.id] = { source: 'dsh', enabled: false, ...def.entry }
148
+ result.ok = sections.has('llm-pi-ai') || sections.has('default-model') || (modern && runtimeRows.length > 0)
149
+ const credsPath = join(home, '.credentials.yaml')
150
+ let credentials = ''
151
+ try { credentials = readText(credsPath); diag.credsFound = true } catch (error) {
152
+ if (error?.code !== 'ENOENT') throw new Error('Unable to read DSH credentials')
153
+ }
154
+ result.refs = extractCredRefs(credentials)
155
+ diag.providers = Object.keys(result.providers)
156
+ diag.sourcePath = diag.sourcePaths.includes(patchPath) ? patchPath : (diag.sourcePaths.at(-1) || patchPath || paths[0])
157
+ } catch (error) {
158
+ result.providers = {}; result.refs = {}; result.ok = false
159
+ diag.error = error.message
160
+ }
161
+ return result
162
+ }
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ import { randomUUID } from 'node:crypto'
12
12
  import { createLocalPreparation, ollamaOrigin, localFirst, validOllamaSignature } from './ollama.js'
13
13
  import { fileURLToPath } from 'node:url'
14
14
  import { requestText, isLoopback } from './transport.js'
15
+ import { scanDshProviders, extractDshProviders, extractCredRefs, extractHarnessDefaultRoute } from './discovery.js'
15
16
 
16
17
  export const name = 'dsh-think-translate'
17
18
 
@@ -23,16 +24,8 @@ export const inject = ['webServer', 'subprocess', 'timer']
23
24
  const CONFIG_PATH = fileURLToPath(new URL('./config.json', import.meta.url))
24
25
  const PACKAGE_PATH = fileURLToPath(new URL('../package.json', import.meta.url))
25
26
 
26
- // Where the harness keeps its settings.yaml and .credentials.yaml.
27
- //
28
- // This mirrors DSH's own resolution — `$DSH_HOME`, then `~/.dsh` (see
29
- // @deepseek-ai/dsh-home-paths: an explicit path, then DSH_HOME, then
30
- // homedir()/.dsh, with a blank DSH_HOME treated as unset) — and only then the
31
- // desktop-app layouts. Picking one guess was the bug: an install whose home is
32
- // ~/.dsh fell through to %APPDATA%\dsh-desktop\harness, which does not exist
33
- // there, so discovery silently reported "no provider configured" while the
34
- // settings file sat right there. The candidates are resolved per call and the
35
- // first one that actually holds a settings.yaml wins (see resolveHarnessHome).
27
+ // Fallback locations for launchers without profileContext. Modern launchers
28
+ // supply the active profile and home directly; discovery.js gives them priority.
36
29
  function harnessHomeCandidates() {
37
30
  const out = []
38
31
  const env = process.env.DSH_HOME
@@ -85,6 +78,8 @@ const VALID_TYPES = ['google', 'bing', 'openai', 'anthropic']
85
78
 
86
79
  let subprocessRef = null
87
80
  let timerRef = null
81
+ let profileContextRef = null
82
+ let discoveryContextRef = null
88
83
 
89
84
  async function localJson(url, options = {}, timeoutMs = 10000) {
90
85
  const response = await fetch(url, { ...options, redirect: 'error', signal: AbortSignal.timeout(timeoutMs) })
@@ -828,162 +823,29 @@ function applyFullProviders(live, fullMap) {
828
823
 
829
824
  // ---------------------------------------------------------------- dsh provider discovery
830
825
 
831
- // (2026-09-01) Parse DSH settings.yaml to discover llm-pi-ai providers.
832
- // settings.yaml has a predictable nested structure that a simple regex
833
- // extractor can handle without a full YAML parser.
834
- // Returns { providerName: { apiKeyEnv, baseURL, model } }.
835
- //
836
- // Indentation reference (real file):
837
- // llm-pi-ai: # 0 spaces (top-level key)
838
- // providers: # 2 spaces
839
- // linuxdo-hub: # 4 spaces (provider name)
840
- // apiKeyEnv: # 6 spaces (provider fields)
841
- // api: # 6 spaces
842
- // baseURL: # 6 spaces
843
- // models: # 6 spaces
844
- // - id: # 8 spaces (model list item)
845
- function extractDshProviders(txt) {
846
- const providers = {}
847
- // Tolerate sibling keys between "llm-pi-ai:" and the first "providers:"
848
- // at 2-space indent (Important 5). Body ends at the next column-0 key
849
- // (Important 2: cosmetic blank lines within the section are NOT a boundary).
850
- const block = txt.match(/llm-pi-ai:[\s\S]*?\n[ \t]{2}providers:[^\n]*\n([\s\S]*?)(?=\n\S|$)/)
851
- if (!block) return providers
852
- const body = block[1]
853
- // Each provider: " name:" followed by indented fields. Use [ \t] instead
854
- // of \s so that blank lines between provider blocks naturally end the field
855
- // group (Important 2). Use lookbehind to avoid consuming the newline
856
- // separator between provider blocks.
857
- const providerBlocks = body.match(/(?:^|(?<=\n))[ \t]{4}[a-zA-Z0-9_-]+:[^\n]*\n(?:[ \t]{6}[^\n]*\n?)*/g)
858
- if (!providerBlocks) return providers
859
- for (const pb of providerBlocks) {
860
- const nameMatch = pb.match(/[ \t]{4}([a-zA-Z0-9_-]+):/)
861
- if (!nameMatch) continue
862
- const name = nameMatch[1]
863
- const apiKeyEnv = (pb.match(/[ \t]{6}apiKeyEnv:\s*(\S+)/) || [])[1]
864
- const api = (pb.match(/[ \t]{6}api:\s*(\S+)/) || [])[1]
865
- const baseURL = (pb.match(/[ \t]{6}baseURL:\s*(\S+)/) || [])[1]
866
- // First model id from the models list. Real settings.yaml indents
867
- // `models:` at 6 spaces and `- id:` at 8 spaces.
868
- const modelMatch = pb.match(/[ \t]{6}models:[^\n]*\n[ \t]{8}- id:\s*(\S+)/)
869
- const model = modelMatch ? modelMatch[1] : null
870
- // Only accept openai-compatible providers (api absent or openai-completions)
871
- if (baseURL && model && (!api || api === 'openai-completions')) {
872
- providers[name] = { apiKeyEnv, baseURL, model }
873
- }
874
- }
875
- return providers
876
- }
877
-
878
- // The harness ALSO has one built-in provider route that never shows up in
879
- // llm-pi-ai.providers: `deepseek-official`, registered by llm-deepseek and
880
- // configured through `agent-default-model` (provider + model) plus a credential
881
- // ref. Its transport is OpenAI-compatible — llm-deepseek itself posts to
882
- // `${baseURL}/chat/completions` with PUBLIC_BASE_URL = https://api.deepseek.com
883
- // and the DEEPSEEK_API_KEY credential — so this plugin can drive it with the
884
- // same adapter, which is what lets "inherit from DSH" offer DeepSeek's own API
885
- // and not just the custom endpoints someone declared by hand.
886
- const BUILTIN_HARNESS_ROUTES = {
887
- 'deepseek-official': { type: 'openai', baseURL: 'https://api.deepseek.com', apiKeyEnv: 'DEEPSEEK_API_KEY' },
888
- }
889
-
890
- // Returns { id, entry } for the harness's default model route when the plugin
891
- // knows how to speak to it, else null. `refs` is the credential map, used only to
892
- // confirm the key exists somewhere.
893
- function extractHarnessDefaultRoute(txt, refs) {
894
- const block = txt.match(/^agent-default-model:[^\n]*\n((?:[ \t]+[^\n]*\n?)*)/m)
895
- if (!block) return null
896
- const provider = (block[1].match(/^[ \t]+provider:[ \t]*(\S+)/m) || [])[1]
897
- const model = (block[1].match(/^[ \t]+model:[ \t]*(\S+)/m) || [])[1]
898
- if (!provider || !model) return null
899
- const route = BUILTIN_HARNESS_ROUTES[provider]
900
- if (!route) return null
901
- const entry = { type: route.type, baseURL: route.baseURL, model: model }
902
- // The ref is the route's documented credential; carry it even when the user
903
- // keeps the key in the environment instead of .credentials.yaml, because the
904
- // host resolves apiKeyEnv from both.
905
- if (route.apiKeyEnv) entry.apiKeyEnv = route.apiKeyEnv
906
- void refs
907
- return { id: provider, entry: entry }
908
- }
909
-
910
- // (2026-09-01) Parse .credentials.yaml refs. Returns { envName: value }.
911
- function extractCredRefs(txt) {
912
- const refs = {}
913
- const block = txt.match(/refs:\s*\n([\s\S]*?)(?=\n\S|$)/)
914
- if (!block) return refs
915
- const body = block[1]
916
- const pairs = body.match(/^\s{2}[a-zA-Z0-9_-]+:.*$/gm)
917
- if (!pairs) return refs
918
- for (const line of pairs) {
919
- const m = line.match(/^\s{2}([a-zA-Z0-9_-]+):\s*(\S+)/)
920
- if (m) refs[m[1]] = m[2]
921
- }
922
- return refs
923
- }
924
-
925
- // DSH credentials refs cache (2026-09-01): env name -> api key.
926
- // Read from .credentials.yaml (600-perm) at discovery time; values live in
927
- // memory only and are NEVER persisted to the world-readable config.json.
826
+ // Resolved credentials live only in memory. A failed scan cannot remove the
827
+ // existing DSH entries; only a successfully read provider section can do so.
928
828
  const credRefsCache = new Map()
929
-
930
- // (2026-09-01) True when the last discovery actually parsed the llm-pi-ai
931
- // providers section. Guards the ghost cleanup in mergeDshProviders: if
932
- // settings.yaml is temporarily unreadable, discovery returns an empty set and
933
- // cleanup would otherwise wipe every DSH provider.
934
829
  let dshScanOk = false
935
830
 
936
- // (2026-09-01) Discover DSH-configured service providers from the harness
937
- // config files. The returned entries carry apiKeyEnv but NOT the apiKey
938
- // value itself — the key is resolved in-memory during mergeDshProviders()
939
- // from credRefsCache so it is never written to disk.
940
831
  async function discoverDshProviders() {
941
- const fs = await import('node:fs')
942
- const out = {}
943
- const candidates = harnessHomeCandidates()
944
- const picked = pickHarnessHome(candidates, function (p) { return fs.existsSync(p) })
945
- const home = picked.home
946
- const diag = { home: home, candidates: candidates, settingsFound: picked.settingsFound, credsFound: false, providers: [], error: null }
832
+ let runtimeRows = []
947
833
  try {
948
- const settingsPath = home + '/settings.yaml'
949
- const credsPath = home + '/.credentials.yaml'
950
- let settingsTxt = '', credsTxt = ''
951
- try { settingsTxt = fs.readFileSync(settingsPath, 'utf8') } catch (e) {}
952
- try { credsTxt = fs.readFileSync(credsPath, 'utf8') } catch (e) {}
953
- diag.credsFound = credsTxt.length > 0
954
- const providers = extractDshProviders(settingsTxt)
955
- const refs = extractCredRefs(credsTxt)
956
- dshScanOk = /llm-pi-ai:[\s\S]*?\n[ \t]{2}providers:/m.test(settingsTxt)
957
- // Refresh the credential refs cache from the real file (read once per
958
- // discovery, values live in memory only).
959
- credRefsCache.clear()
960
- for (const k of Object.keys(refs)) credRefsCache.set(k, refs[k])
961
- for (const name of Object.keys(providers)) {
962
- const p = providers[name]
963
- if (!p.baseURL || !p.model) { diag.skipped = (diag.skipped || []).concat(name); continue }
964
- const entry = {
965
- type: 'openai',
966
- source: 'dsh',
967
- baseURL: p.baseURL,
968
- model: p.model,
969
- enabled: false, // user must explicitly add to chain
970
- }
971
- if (p.apiKeyEnv) entry.apiKeyEnv = p.apiKeyEnv
972
- out[name] = entry
973
- diag.providers.push(name)
834
+ // Resolve at scan time: the optional editor may mount after this plugin.
835
+ const editor = discoveryContextRef?.get('configEditor')
836
+ if (typeof editor?.entries === 'function') {
837
+ runtimeRows = editor.entries().map(entry => entry.options)
974
838
  }
975
- // The harness's built-in route (deepseek-official): never in llm-pi-ai.providers,
976
- // configured by agent-default-model. An id declared by hand wins.
977
- const def = extractHarnessDefaultRoute(settingsTxt, refs)
978
- if (def && !out[def.id]) {
979
- out[def.id] = Object.assign({ source: 'dsh', enabled: false }, def.entry)
980
- diag.providers.push(def.id)
981
- }
982
- } catch (e) {
983
- diag.error = e instanceof Error ? e.message : String(e)
984
- }
985
- dshScanDiag = diag
986
- return out
839
+ } catch { /* File discovery also works without the optional editor service. */ }
840
+ const scan = scanDshProviders({
841
+ candidates: harnessHomeCandidates(), profileContext: profileContextRef,
842
+ modulePath: fileURLToPath(import.meta.url), runtimeRows,
843
+ })
844
+ dshScanDiag = scan.diag
845
+ dshScanOk = scan.ok
846
+ credRefsCache.clear()
847
+ for (const [key, value] of Object.entries(scan.refs)) credRefsCache.set(key, value)
848
+ return scan.providers
987
849
  }
988
850
 
989
851
  // (2026-09-01) Remove a provider id from the chain and fallback chain.
@@ -1000,7 +862,7 @@ function removeFromChain(cfg, id) {
1000
862
  // DSH-managed fields (baseURL/model/apiKeyEnv) come from the fresh scan;
1001
863
  // only the user-controlled `enabled` flag survives from the persisted entry.
1002
864
  // apiKey is resolved from the in-memory credRefsCache (Important 1+3).
1003
- // Providers that disappeared from settings.yaml are cleaned up (Important 4).
865
+ // Providers that disappeared from the active DSH configuration are cleaned up (Important 4).
1004
866
  async function mergeDshProviders(cfg) {
1005
867
  const dsh = await discoverDshProviders()
1006
868
  const discovered = new Set(Object.keys(dsh))
@@ -1018,10 +880,10 @@ async function mergeDshProviders(cfg) {
1018
880
  cfg.providers = cfg.providers || {}
1019
881
  cfg.providers[id] = entry
1020
882
  }
1021
- // Ghost cleanup: drop DSH entries that no longer exist in settings.yaml;
883
+ // Ghost cleanup: drop DSH entries that no longer exist in the active profile;
1022
884
  // also purge their id from the chain to avoid dead references. Only when
1023
885
  // the last discovery actually parsed the providers section — otherwise a
1024
- // transient settings.yaml read failure would wipe every DSH provider.
886
+ // transient configuration read failure would wipe every DSH provider.
1025
887
  if (dshScanOk) {
1026
888
  const provs = cfg.providers || {}
1027
889
  for (const id of Object.keys(provs)) {
@@ -1440,6 +1302,9 @@ async function handlePullStatus() {
1440
1302
  export function apply(ctx) {
1441
1303
  subprocessRef = ctx.get('subprocess')
1442
1304
  timerRef = ctx.get('timer')
1305
+ // Optional for compatibility with DSH releases predating profiles.
1306
+ profileContextRef = ctx.get('profileContext')
1307
+ discoveryContextRef = ctx
1443
1308
 
1444
1309
  const webServer = ctx.get('webServer')
1445
1310
  if (webServer === undefined) return
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-think-translate",
3
3
  "description": "Display-layer translation for the DSH web app: thinking chain, task cards, and optional answer text, in 8 target languages (local Ollama / any OpenAI-compatible or Anthropic endpoint / Google / Bing, fail-open)",
4
- "version": "1.2.9",
4
+ "version": "1.2.10",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -14,6 +14,7 @@
14
14
  "lib/client.js",
15
15
  "lib/ollama.js",
16
16
  "lib/transport.js",
17
+ "lib/discovery.js",
17
18
  "cordis.patch.yml",
18
19
  "demo/demo.gif",
19
20
  "demo/demo2.gif"
@@ -70,5 +71,8 @@
70
71
  "optional": true
71
72
  }
72
73
  },
73
- "license": "MIT"
74
+ "license": "MIT",
75
+ "dependencies": {
76
+ "js-yaml": "4.3.2"
77
+ }
74
78
  }