opencode-translate 0.2.3 → 0.3.0

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.md CHANGED
@@ -58,7 +58,7 @@ Add it to `~/.config/opencode/opencode.json`:
58
58
  {
59
59
  "plugin": [
60
60
  ["opencode-translate", {
61
- "translatorModel": "anthropic/claude-haiku-4-5",
61
+ "model": "anthropic/claude-haiku-4-5",
62
62
  "triggerKeywords": ["$en"],
63
63
  "lang": "Korean",
64
64
  "verbose": false
@@ -84,12 +84,14 @@ $en 프로젝트 루트의 package.json을 읽고 요약해줘
84
84
 
85
85
  | Option | Type | Default |
86
86
  | --- | --- | --- |
87
- | `translatorModel` | string | `anthropic/claude-haiku-4-5` |
87
+ | `model` | string | Required |
88
88
  | `triggerKeywords` | string[] | `[$en]` |
89
89
  | `lang` | string | Required |
90
90
  | `apiKey` | string | `undefined` |
91
91
  | `verbose` | boolean | `false` |
92
92
 
93
+ Set `model` to the translator model in `provider/model-id` form, such as `"anthropic/claude-haiku-4-5"`.
94
+
93
95
  Set `lang` to the full name of the language the user reads and writes, such as `"Korean"`, `"Japanese"`, or `"Brazilian Portuguese"`. The value is injected directly into translation prompts, so language code mapping is not required.
94
96
 
95
97
  ## Privacy
@@ -97,7 +99,7 @@ Set `lang` to the full name of the language the user reads and writes, such as `
97
99
  Using this plugin means text goes to two model providers per turn:
98
100
 
99
101
  - the normal OpenCode chat provider
100
- - the configured `translatorModel` provider
102
+ - the configured `model` provider
101
103
 
102
104
  If you need strict single-provider or self-hosted-only behavior, do not enable this plugin.
103
105
 
@@ -109,7 +111,7 @@ For OAuth records that OpenCode does not expose through the plugin SDK, the plug
109
111
 
110
112
  ## Anthropic OAuth Support
111
113
 
112
- If `translatorModel` uses Anthropic and OpenCode auth is backed by Anthropic OAuth (Claude Pro/Max), the plugin reuses those OAuth credentials for translation requests.
114
+ If `model` uses Anthropic and OpenCode auth is backed by Anthropic OAuth (Claude Pro/Max), the plugin reuses those OAuth credentials for translation requests.
113
115
 
114
116
  Anthropic's `/v1/messages` endpoint rejects OAuth-authenticated requests that do not match the Claude Code CLI fingerprint (response: `429 rate_limit_error` with an empty `"Error"` message). To pass, the plugin applies the same transformation that `@ex-machina/opencode-anthropic-auth` uses for OpenCode's main chat, but only for its own translator requests:
115
117
 
@@ -131,7 +133,7 @@ If you prefer a plain API key, set `ANTHROPIC_API_KEY`, use `opencode auth login
131
133
 
132
134
  ## OpenAI OAuth Support
133
135
 
134
- If `translatorModel` uses OpenAI and OpenCode auth is backed by the ChatGPT/Codex OAuth flow, the plugin reuses those OAuth credentials for translation requests.
136
+ If `model` uses OpenAI and OpenCode auth is backed by the ChatGPT/Codex OAuth flow, the plugin reuses those OAuth credentials for translation requests.
135
137
 
136
138
  For OAuth-backed OpenAI requests, the plugin routes the OpenAI AI SDK request to `https://chatgpt.com/backend-api/codex/responses`, adds the required Codex beta/originator headers, normalizes the request body to Codex's expected typed `input` shape, and converts Codex SSE responses back to JSON for translation calls. This supports models such as `openai/gpt-5.5` when your ChatGPT plan has access.
137
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-translate",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "OpenCode plugin that lets the user chat in a configured language while the main chat loop only sees English.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -8,7 +8,7 @@ import {
8
8
  import { createSyntheticPartID } from "../translator"
9
9
 
10
10
  export function createActivationBannerText(options: ResolvedTranslateOptions): string {
11
- const { modelID } = parseTranslatorModel(options.translatorModel)
11
+ const { modelID } = parseTranslatorModel(options.model)
12
12
  return `✓ Translation mode enabled · translator: ${modelID} · language: ${options.lang}`
13
13
  }
14
14
 
@@ -1,7 +1,20 @@
1
- import { AUTH_ENV_FALLBACK, DEFAULT_TRANSLATOR_MODEL, DEFAULT_TRIGGER_KEYWORDS, PLUGIN_NAME } from "./plugin"
1
+ import { AUTH_ENV_FALLBACK, DEFAULT_TRIGGER_KEYWORDS, PLUGIN_NAME } from "./plugin"
2
2
  import type { ProviderInfo, ResolvedTranslateOptions } from "./types"
3
3
 
4
4
  export function resolveOptions(options: Record<string, unknown>): ResolvedTranslateOptions {
5
+ const model = typeof options.model === "string" ? options.model.trim() : ""
6
+ if (!model) {
7
+ throw new Error(
8
+ `[${PLUGIN_NAME}:INVALID_OPTIONS] options.model is required. Set it to the translator model, e.g. "anthropic/claude-haiku-4-5".`,
9
+ )
10
+ }
11
+ const slash = model.indexOf("/")
12
+ if (slash < 1 || slash === model.length - 1) {
13
+ throw new Error(
14
+ `[${PLUGIN_NAME}:INVALID_OPTIONS] options.model must be in provider/model-id form, e.g. "anthropic/claude-haiku-4-5".`,
15
+ )
16
+ }
17
+
5
18
  const lang = typeof options.lang === "string" ? options.lang.trim() : ""
6
19
  if (!lang) {
7
20
  throw new Error(
@@ -14,10 +27,7 @@ export function resolveOptions(options: Record<string, unknown>): ResolvedTransl
14
27
  : DEFAULT_TRIGGER_KEYWORDS
15
28
 
16
29
  return {
17
- translatorModel:
18
- typeof options.translatorModel === "string" && options.translatorModel.includes("/")
19
- ? options.translatorModel
20
- : DEFAULT_TRANSLATOR_MODEL,
30
+ model,
21
31
  triggerKeywords: triggerKeywords.length > 0 ? triggerKeywords : [...DEFAULT_TRIGGER_KEYWORDS],
22
32
  lang,
23
33
  apiKey: typeof options.apiKey === "string" && options.apiKey.length > 0 ? options.apiKey : undefined,
@@ -1,7 +1,6 @@
1
1
  export const PLUGIN_NAME = "opencode-translate"
2
2
  export const SPEC_VERSION = 2
3
3
  export const LLM_LANGUAGE = "English"
4
- export const DEFAULT_TRANSLATOR_MODEL = "anthropic/claude-haiku-4-5"
5
4
  export const DEFAULT_TRIGGER_KEYWORDS = ["$en"]
6
5
  export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
7
6
  export const NONCE_PATTERN = /^[0-9a-f]{32}$/
@@ -5,7 +5,7 @@ export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promis
5
5
  type ProviderSource = "env" | "config" | "custom" | "api"
6
6
 
7
7
  export interface ResolvedTranslateOptions {
8
- translatorModel: string
8
+ model: string
9
9
  triggerKeywords: string[]
10
10
  lang: string
11
11
  apiKey?: string
@@ -70,8 +70,8 @@ export function createTranslator(
70
70
  if (input.sourceLanguage === input.targetLanguage) return input.text
71
71
 
72
72
  const startedAt = now()
73
- const { providerID, modelID } = parseTranslatorModel(options.translatorModel)
74
- const credentials = await credentialResolver.resolve(options.translatorModel)
73
+ const { providerID, modelID } = parseTranslatorModel(options.model)
74
+ const credentials = await credentialResolver.resolve(options.model)
75
75
  const modelInfo = resolveModelInfo(credentials.provider, modelID)
76
76
  const factory = await loadFactory(providerID, modelInfo)
77
77
  const provider = instantiateProvider(factory, providerID, credentials, modelInfo)
@@ -113,7 +113,7 @@ export function createTranslator(
113
113
  chars_out: translated.length,
114
114
  ms: now() - startedAt,
115
115
  cached: false,
116
- model: options.translatorModel,
116
+ model: options.model,
117
117
  },
118
118
  },
119
119
  })