opencode-translate 0.0.13 → 0.0.14

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/activation.ts +28 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-translate",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "OpenCode plugin that lets the user chat in a configured source language while the main chat loop only sees English.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/activation.ts CHANGED
@@ -134,12 +134,17 @@ function mergeTranslatedMetadata(state: TranslateState, part: TextPartLike, engl
134
134
  }
135
135
  }
136
136
 
137
- // OpenCode's flag semantics, observed from packages/opencode and packages/ui:
137
+ // OpenCode's flag semantics are not uniform across every UI path:
138
138
  // synthetic: true -> hidden from the user UI, still sent to the LLM
139
- // ignored: true -> hidden from the LLM, still shown in the user UI
139
+ // ignored: true -> hidden from the LLM, still shown in the main message UI
140
140
  // both true -> hidden from both (used for metadata-only marker
141
141
  // parts like the activation banner that exist purely
142
142
  // to carry state across reloads)
143
+ // Some secondary UIs, including the web `/fork` dialog, only consider text
144
+ // parts that are neither synthetic nor ignored. Persist translated user display
145
+ // parts as non-ignored so those flows can find them; before model serialization,
146
+ // `experimental.chat.messages.transform` marks those display parts ignored and
147
+ // lets the synthetic English twins carry the actual prompt text.
143
148
  // The plugin's user-facing status text (translation preview, activation
144
149
  // banner, failure notice) lives inline on the source-language user part
145
150
  // itself rather than as sibling parts, because OpenCode's
@@ -148,8 +153,9 @@ function mergeTranslatedMetadata(state: TranslateState, part: TextPartLike, engl
148
153
 
149
154
  // LLM-only text part: hidden from the TUI but the only LLM-visible
150
155
  // representation of the user's source-language text. The original
151
- // user-authored part is marked `ignored:true` so the LLM never sees it,
152
- // and this synthetic English twin carries the actual prompt content.
156
+ // user-authored part is marked `ignored:true` in the model-bound transform so
157
+ // the LLM never sees it, and this synthetic English twin carries the actual
158
+ // prompt content.
153
159
  function createLlmOnlyTextPart(
154
160
  sessionID: string,
155
161
  messageID: string,
@@ -168,6 +174,11 @@ function createLlmOnlyTextPart(
168
174
  }
169
175
  }
170
176
 
177
+ function isTranslatedUserDisplayPart(part: TextPartLike): boolean {
178
+ if (!isTextPart(part) || part.synthetic === true) return false
179
+ return extractStateFromMetadata(asMetadata(part)) !== undefined
180
+ }
181
+
171
182
  function escapeRegex(value: string): string {
172
183
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
173
184
  }
@@ -367,15 +378,14 @@ export function createHooks(ctx: PluginInput, rawOptions: PluginOptions = {}, de
367
378
  // ONE text part per user message — the first non-synthetic —
368
379
  // so a sibling `synthetic:false + ignored:true` preview part
369
380
  // would never reach the screen. Combining them here keeps
370
- // the original visible alongside the English twin while
371
- // `ignored:true` still hides the whole thing from the LLM
372
- // serializer (`message-v2.ts:773`); the LLM-only synthetic
373
- // twin below carries the clean English prompt.
381
+ // the original visible alongside the English twin while still
382
+ // satisfying secondary UI filters like `/fork`, which require
383
+ // non-synthetic, non-ignored text. The transform hook marks this
384
+ // display part ignored in the model-bound copy.
374
385
  part.text = `${part.text}\n\n_→ EN: ${english}_`
375
- part.ignored = true
376
386
 
377
387
  // LLM-only English twin. This is the actual prompt the model
378
- // sees in place of the now-`ignored` source-language part.
388
+ // sees in place of the source-language display part.
379
389
  nextParts.push(
380
390
  createLlmOnlyTextPart(part.sessionID, part.messageID, english, {
381
391
  translate_role: "llm_only_translation",
@@ -465,12 +475,15 @@ export function createHooks(ctx: PluginInput, rawOptions: PluginOptions = {}, de
465
475
  const activeState = resolved.state
466
476
  if (!activeState) return
467
477
 
468
- // User parts need no in-place rewriting: the source-language text
469
- // part is `ignored:true` so the LLM serializer skips it, and a
470
- // synthetic English twin (created in `chat.message`) carries the
471
- // actual prompt content. Only assistant parts still need their
472
- // localized trailer stripped before re-entering the model.
473
478
  for (const message of output.messages as MessageWithPartsLike[]) {
479
+ if (message.info.role === "user") {
480
+ for (const part of message.parts) {
481
+ if (!isTranslatedUserDisplayPart(part)) continue
482
+ part.ignored = true
483
+ }
484
+ continue
485
+ }
486
+
474
487
  if (message.info.role !== "assistant") continue
475
488
  for (const part of message.parts) {
476
489
  if (!isTextPart(part)) continue