opencode-translate 0.0.14 → 0.0.15
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 +8 -0
- package/package.json +1 -1
- package/src/auth.ts +106 -1
- package/src/translator.ts +7 -1
package/README.md
CHANGED
|
@@ -123,6 +123,14 @@ Tradeoffs:
|
|
|
123
123
|
|
|
124
124
|
If you prefer a plain API key, set `ANTHROPIC_API_KEY` in the environment or pass `apiKey` in plugin options. The plugin prefers explicit `apiKey`, then `ANTHROPIC_API_KEY`, then OAuth.
|
|
125
125
|
|
|
126
|
+
## OpenAI OAuth Support
|
|
127
|
+
|
|
128
|
+
If `translatorModel` uses OpenAI and OpenCode auth is backed by the ChatGPT/Codex OAuth flow, the plugin reuses those OAuth credentials for translation requests.
|
|
129
|
+
|
|
130
|
+
For OAuth-backed OpenAI requests, the plugin routes the OpenAI AI SDK request to `https://chatgpt.com/backend-api/codex/responses` and normalizes the request body to Codex's expected typed `input` shape. This supports models such as `openai/gpt-5.5` when your ChatGPT plan has access.
|
|
131
|
+
|
|
132
|
+
If you prefer a plain API key, set `OPENAI_API_KEY` in the environment or pass `apiKey` in plugin options. The plugin prefers explicit `apiKey`, then `OPENAI_API_KEY`, then OAuth.
|
|
133
|
+
|
|
126
134
|
## Manual Smoke Test
|
|
127
135
|
|
|
128
136
|
1. Install the plugin and configure `sourceLanguage: "ko"`, `displayLanguage: "ko"`.
|
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -78,6 +78,110 @@ function setUserAgent(headers: Headers, packageVersion?: string) {
|
|
|
78
78
|
headers.set("User-Agent", packageVersion ? `${USER_AGENT.replace("0.0.0", packageVersion)}` : USER_AGENT)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
82
|
+
return !!value && typeof value === "object" && !Array.isArray(value)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function textFromContent(content: unknown): string | undefined {
|
|
86
|
+
if (typeof content === "string") return content
|
|
87
|
+
if (!Array.isArray(content)) return undefined
|
|
88
|
+
|
|
89
|
+
const text = content
|
|
90
|
+
.map((part) => (isRecord(part) && typeof part.text === "string" ? part.text : undefined))
|
|
91
|
+
.filter((value): value is string => value !== undefined)
|
|
92
|
+
.join("\n")
|
|
93
|
+
|
|
94
|
+
return text || undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeCodexContent(role: string, content: unknown): Record<string, unknown>[] {
|
|
98
|
+
const textType = role === "assistant" ? "output_text" : "input_text"
|
|
99
|
+
if (typeof content === "string") return [{ type: textType, text: content }]
|
|
100
|
+
if (!Array.isArray(content)) return []
|
|
101
|
+
|
|
102
|
+
const result: Record<string, unknown>[] = []
|
|
103
|
+
for (const part of content) {
|
|
104
|
+
if (!isRecord(part)) continue
|
|
105
|
+
const type = part.type
|
|
106
|
+
if (type === "input_text" || type === "output_text") {
|
|
107
|
+
result.push({ ...part, type: textType })
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (type === "input_image") {
|
|
111
|
+
result.push({ ...part })
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (typeof part.text === "string") {
|
|
115
|
+
result.push({ type: textType, text: part.text })
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeCodexInputItem(item: unknown, instructions: string[]): unknown | undefined {
|
|
123
|
+
if (!isRecord(item)) return item
|
|
124
|
+
const role = typeof item.role === "string" ? item.role : undefined
|
|
125
|
+
|
|
126
|
+
if (role === "system" || role === "developer") {
|
|
127
|
+
const text = textFromContent(item.content)
|
|
128
|
+
if (text) instructions.push(text)
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (item.type === "message" && role) {
|
|
133
|
+
const content = normalizeCodexContent(role, item.content)
|
|
134
|
+
return content.length > 0 ? { ...item, role, content } : undefined
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (role) {
|
|
138
|
+
const content = normalizeCodexContent(role, item.content)
|
|
139
|
+
return content.length > 0 ? { type: "message", role, content } : undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return item
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rewriteOpenAICodexBody(body: BodyInit | null | undefined): BodyInit | null | undefined {
|
|
146
|
+
if (typeof body !== "string") return body
|
|
147
|
+
|
|
148
|
+
let parsed: unknown
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(body)
|
|
151
|
+
} catch {
|
|
152
|
+
return body
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!isRecord(parsed)) return body
|
|
156
|
+
|
|
157
|
+
const sourceInput = Array.isArray(parsed.input)
|
|
158
|
+
? parsed.input
|
|
159
|
+
: Array.isArray(parsed.messages)
|
|
160
|
+
? parsed.messages
|
|
161
|
+
: undefined
|
|
162
|
+
if (!sourceInput) return body
|
|
163
|
+
|
|
164
|
+
const instructions: string[] = []
|
|
165
|
+
if (typeof parsed.instructions === "string" && parsed.instructions) instructions.push(parsed.instructions)
|
|
166
|
+
|
|
167
|
+
const input = sourceInput
|
|
168
|
+
.map((item) => normalizeCodexInputItem(item, instructions))
|
|
169
|
+
.filter((item): item is unknown => item !== undefined)
|
|
170
|
+
|
|
171
|
+
return JSON.stringify({
|
|
172
|
+
...parsed,
|
|
173
|
+
instructions: instructions.join("\n\n"),
|
|
174
|
+
input,
|
|
175
|
+
tools: Array.isArray(parsed.tools) ? parsed.tools : [],
|
|
176
|
+
tool_choice: typeof parsed.tool_choice === "string" ? parsed.tool_choice : "auto",
|
|
177
|
+
parallel_tool_calls: typeof parsed.parallel_tool_calls === "boolean" ? parsed.parallel_tool_calls : false,
|
|
178
|
+
store: typeof parsed.store === "boolean" ? parsed.store : false,
|
|
179
|
+
stream: typeof parsed.stream === "boolean" ? parsed.stream : false,
|
|
180
|
+
include: Array.isArray(parsed.include) ? parsed.include : [],
|
|
181
|
+
messages: undefined,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
81
185
|
function isMissingCredentialError(error: unknown): boolean {
|
|
82
186
|
const message = normalizeReason(error).toLowerCase()
|
|
83
187
|
return (
|
|
@@ -244,7 +348,6 @@ async function refreshOpenAI(
|
|
|
244
348
|
grant_type: "refresh_token",
|
|
245
349
|
refresh_token: info.refresh,
|
|
246
350
|
client_id: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
247
|
-
scope: "openid profile email offline_access",
|
|
248
351
|
})
|
|
249
352
|
const response = await withRetry(
|
|
250
353
|
() =>
|
|
@@ -415,6 +518,8 @@ export function createCredentialResolver(
|
|
|
415
518
|
inputUrl.hostname = "chatgpt.com"
|
|
416
519
|
inputUrl.pathname = "/backend-api/codex/responses"
|
|
417
520
|
inputUrl.search = ""
|
|
521
|
+
nextBody = rewriteOpenAICodexBody(nextBody)
|
|
522
|
+
headers.delete("content-length")
|
|
418
523
|
}
|
|
419
524
|
}
|
|
420
525
|
|
package/src/translator.ts
CHANGED
|
@@ -210,6 +210,12 @@ function instantiateModel(provider: unknown, modelID: string): unknown {
|
|
|
210
210
|
throw new Error(`Unable to instantiate model "${modelID}"`)
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
function supportsTemperature(providerID: string, modelID: string): boolean {
|
|
214
|
+
if (providerID !== "openai") return true
|
|
215
|
+
if (modelID.startsWith("o1") || modelID.startsWith("o3") || modelID.startsWith("o4-mini")) return false
|
|
216
|
+
return !(modelID.startsWith("gpt-5") && !modelID.startsWith("gpt-5-chat"))
|
|
217
|
+
}
|
|
218
|
+
|
|
213
219
|
function isAuthMessage(error: unknown): boolean {
|
|
214
220
|
if (!(error instanceof Error)) return false
|
|
215
221
|
return error.message.includes(":AUTH_UNAVAILABLE]") || error.message.includes(":OAUTH_REFRESH_FAILED]")
|
|
@@ -272,7 +278,7 @@ export function createTranslator(
|
|
|
272
278
|
targetLanguage: input.targetLanguage,
|
|
273
279
|
text: input.text,
|
|
274
280
|
}),
|
|
275
|
-
temperature: 0,
|
|
281
|
+
...(supportsTemperature(providerID, modelID) ? { temperature: 0 } : {}),
|
|
276
282
|
prompt: buildUserPrompt({
|
|
277
283
|
sourceLanguage: input.sourceLanguage,
|
|
278
284
|
targetLanguage: input.targetLanguage,
|