opencode-translate 1.0.6 → 2.0.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.
Files changed (45) hide show
  1. package/README.md +77 -6
  2. package/dist/index.js +873 -0
  3. package/index.d.ts +5 -0
  4. package/package.json +15 -16
  5. package/src/activation/chat-message.ts +0 -189
  6. package/src/activation/index.ts +0 -38
  7. package/src/activation/logging.ts +0 -11
  8. package/src/activation/messages-transform.ts +0 -44
  9. package/src/activation/metadata.ts +0 -41
  10. package/src/activation/parts.ts +0 -46
  11. package/src/activation/question-hooks.ts +0 -126
  12. package/src/activation/state.ts +0 -97
  13. package/src/activation/text-complete.ts +0 -50
  14. package/src/activation/trigger.ts +0 -57
  15. package/src/activation/types.ts +0 -47
  16. package/src/activation.ts +0 -1
  17. package/src/anthropic-oauth.ts +0 -148
  18. package/src/auth/codex-request.ts +0 -108
  19. package/src/auth/codex-response.ts +0 -78
  20. package/src/auth/codex-shared.ts +0 -3
  21. package/src/auth/headers.ts +0 -18
  22. package/src/auth/index.ts +0 -177
  23. package/src/auth/oauth-fetch.ts +0 -100
  24. package/src/auth/refresh.ts +0 -102
  25. package/src/auth/retry.ts +0 -70
  26. package/src/auth/store.ts +0 -98
  27. package/src/auth/types.ts +0 -27
  28. package/src/auth.ts +0 -1
  29. package/src/constants/errors.ts +0 -24
  30. package/src/constants/guards.ts +0 -33
  31. package/src/constants/options.ts +0 -55
  32. package/src/constants/plugin.ts +0 -9
  33. package/src/constants/types.ts +0 -159
  34. package/src/constants.ts +0 -5
  35. package/src/formatting.ts +0 -157
  36. package/src/index.ts +0 -7
  37. package/src/labels.ts +0 -3
  38. package/src/prompts.ts +0 -123
  39. package/src/question-tool.ts +0 -234
  40. package/src/translator/index.ts +0 -172
  41. package/src/translator/part-id.ts +0 -43
  42. package/src/translator/provider.ts +0 -411
  43. package/src/translator/retry.ts +0 -62
  44. package/src/translator/types.ts +0 -24
  45. package/src/translator.ts +0 -1
package/src/auth/index.ts DELETED
@@ -1,177 +0,0 @@
1
- import { setTimeout as sleep } from "node:timers/promises"
2
- import {
3
- AUTH_ENV_FALLBACK,
4
- buildAuthUnavailableError,
5
- buildOAuthRefreshError,
6
- getEnvVarHint,
7
- normalizeReason,
8
- OAUTH_DUMMY_KEY,
9
- type OAuthInfo,
10
- type PluginClientLike,
11
- type ProviderInfo,
12
- parseTranslatorModel,
13
- unwrapData,
14
- } from "../constants"
15
- import { buildOAuthFetch } from "./oauth-fetch"
16
- import { refreshAnthropic, refreshOpenAI } from "./refresh"
17
- import { ensureOAuthInfo, normalizeProviderKey, readAuthMap } from "./store"
18
- import type { AuthDependencies, AuthRuntime, ResolvedCredential } from "./types"
19
-
20
- export function __resetAuthCachesForTest() {
21
- // Resolver instances own their caches; this remains as a stable test helper.
22
- }
23
-
24
- function isMissingCredentialError(error: unknown): boolean {
25
- const message = normalizeReason(error).toLowerCase()
26
- return (
27
- message.includes("api key") ||
28
- message.includes("api-key") ||
29
- message.includes("missing credentials") ||
30
- message.includes("missing authentication") ||
31
- message.includes("missing auth") ||
32
- message.includes("no auth")
33
- )
34
- }
35
-
36
- function hasOAuthRequestAdapter(providerID: string): boolean {
37
- return providerID === "anthropic" || providerID === "openai" || providerID === "github-copilot"
38
- }
39
-
40
- async function refreshProviderOAuth(
41
- providerID: string,
42
- info: OAuthInfo,
43
- client: PluginClientLike,
44
- runtime: AuthRuntime,
45
- ) {
46
- let refreshed: OAuthInfo
47
- try {
48
- if (providerID === "anthropic") refreshed = await refreshAnthropic(info, runtime)
49
- else if (providerID === "openai") refreshed = await refreshOpenAI(info, runtime)
50
- else return info
51
- } catch (error) {
52
- if (error instanceof Error && error.message.includes(":OAUTH_REFRESH_FAILED]")) throw error
53
- throw buildOAuthRefreshError(providerID, normalizeReason(error))
54
- }
55
-
56
- await client.auth.set({ path: { id: providerID }, body: refreshed })
57
- return refreshed
58
- }
59
-
60
- async function getProvider(client: PluginClientLike, providerID: string): Promise<ProviderInfo | undefined> {
61
- try {
62
- const listed = unwrapData(await client.provider.list({ throwOnError: true }))
63
- return listed.all.find((provider) => provider.id === providerID)
64
- } catch {
65
- return undefined
66
- }
67
- }
68
-
69
- export function createCredentialResolver(client: PluginClientLike, deps: AuthDependencies = {}) {
70
- const credentialCache = new Map<string, ResolvedCredential>()
71
- const oauthRefreshInflight = new Map<string, Promise<OAuthInfo>>()
72
- const runtime: AuthRuntime = {
73
- fetchImpl: deps.fetchImpl ?? fetch,
74
- sleep: deps.sleep ?? ((ms: number) => sleep(ms)),
75
- }
76
- const now = deps.now ?? (() => Date.now())
77
-
78
- async function resolveOAuth(providerID: string): Promise<OAuthInfo | undefined> {
79
- const authMap = await readAuthMap(deps)
80
- const info = ensureOAuthInfo(authMap?.[providerID])
81
- if (!info) return undefined
82
- if (info.expires >= now() + 60_000) return info
83
-
84
- const inflightKey = `${providerID}:${info.refresh}`
85
- const existing = oauthRefreshInflight.get(inflightKey)
86
- if (existing) return existing
87
-
88
- const refreshPromise = refreshProviderOAuth(providerID, info, client, runtime).finally(() => {
89
- oauthRefreshInflight.delete(inflightKey)
90
- })
91
- oauthRefreshInflight.set(inflightKey, refreshPromise)
92
- return refreshPromise
93
- }
94
-
95
- async function resolveAuthInfo(providerID: string) {
96
- return (await readAuthMap(deps))?.[providerID]
97
- }
98
-
99
- function credentialFromOAuth(
100
- providerID: string,
101
- provider: ProviderInfo | undefined,
102
- authInfo: OAuthInfo,
103
- ): ResolvedCredential {
104
- return {
105
- providerID,
106
- provider,
107
- authInfo,
108
- apiKey: "",
109
- fetch: buildOAuthFetch({ ...runtime, providerID, resolveOAuth, packageVersion: deps.packageVersion }),
110
- mode: "oauth",
111
- }
112
- }
113
-
114
- async function resolve(providerModel: string): Promise<ResolvedCredential> {
115
- const { providerID } = parseTranslatorModel(providerModel)
116
- const cached = credentialCache.get(providerID)
117
- if (cached && providerID !== "openai") return cached
118
-
119
- const provider = await getProvider(client, providerID)
120
- const authInfo = await resolveAuthInfo(providerID)
121
- if (providerID === "openai" && authInfo?.type === "oauth") {
122
- const oauthInfo = await resolveOAuth(providerID)
123
- if (oauthInfo) {
124
- const resolved = credentialFromOAuth(providerID, provider, oauthInfo)
125
- credentialCache.set(providerID, resolved)
126
- return resolved
127
- }
128
- }
129
- if (cached && !(providerID === "openai" && cached.mode === "oauth" && authInfo?.type !== "oauth")) return cached
130
-
131
- const providerKey = normalizeProviderKey(provider?.key)
132
- if (providerKey) {
133
- const resolved = { providerID, provider, authInfo, apiKey: providerKey, mode: "apiKey" as const }
134
- credentialCache.set(providerID, resolved)
135
- return resolved
136
- }
137
-
138
- if (authInfo?.type === "api" && authInfo.key) {
139
- const resolved = { providerID, provider, authInfo, apiKey: authInfo.key, mode: "apiKey" as const }
140
- credentialCache.set(providerID, resolved)
141
- return resolved
142
- }
143
-
144
- if (provider?.source === "custom" || provider?.key === OAUTH_DUMMY_KEY || hasOAuthRequestAdapter(providerID)) {
145
- const oauthInfo = await resolveOAuth(providerID)
146
- if (oauthInfo) {
147
- const resolved = credentialFromOAuth(providerID, provider, oauthInfo)
148
- credentialCache.set(providerID, resolved)
149
- return resolved
150
- }
151
- }
152
-
153
- if (authInfo?.type === "oauth" && authInfo.access && provider?.options?.apiKey === undefined) {
154
- const resolved = { providerID, provider, authInfo, apiKey: authInfo.access, mode: "oauth" as const }
155
- credentialCache.set(providerID, resolved)
156
- return resolved
157
- }
158
-
159
- if (provider?.key === undefined && (provider?.env.length ?? 0) > 1) {
160
- const resolved = { providerID, provider, authInfo, mode: "default" as const }
161
- credentialCache.set(providerID, resolved)
162
- return resolved
163
- }
164
-
165
- return { providerID, provider, authInfo, mode: "default" }
166
- }
167
-
168
- return {
169
- resolve,
170
- authUnavailable: (providerID: string, provider?: ProviderInfo) =>
171
- buildAuthUnavailableError(providerID, getEnvVarHint(provider)),
172
- isMissingCredentialError,
173
- envFallback: AUTH_ENV_FALLBACK,
174
- }
175
- }
176
-
177
- export type { AuthDependencies, ResolvedCredential }
@@ -1,100 +0,0 @@
1
- import {
2
- isAnthropicMessagesRequest,
3
- rewriteMessagesBody,
4
- rewriteMessagesURL,
5
- setOAuthHeaders as setAnthropicOAuthHeaders,
6
- } from "../anthropic-oauth"
7
- import type { FetchLike, OAuthInfo } from "../constants"
8
- import { rewriteOpenAICodexBody } from "./codex-request"
9
- import { convertCodexSSEToJSON } from "./codex-response"
10
- import { copyHeaders, packageUserAgent, setUserAgent } from "./headers"
11
- import { exchangeCopilotToken } from "./refresh"
12
- import type { AuthRuntime, OAuthResolver } from "./types"
13
-
14
- interface OAuthFetchOptions extends AuthRuntime {
15
- providerID: string
16
- resolveOAuth: OAuthResolver
17
- packageVersion?: string
18
- }
19
-
20
- interface RequestState {
21
- headers: Headers
22
- inputUrl: URL
23
- body: BodyInit | null | undefined
24
- convertCodexResponse: boolean
25
- }
26
-
27
- function applyAnthropicRequest(state: RequestState, info: OAuthInfo) {
28
- setAnthropicOAuthHeaders(state.headers, info.access)
29
- state.headers.set("anthropic-version", "2023-06-01")
30
- rewriteMessagesURL(state.inputUrl)
31
- if (isAnthropicMessagesRequest(state.inputUrl) && typeof state.body === "string") {
32
- state.body = rewriteMessagesBody(state.body)
33
- }
34
- }
35
-
36
- function applyOpenAIRequest(state: RequestState, info: OAuthInfo) {
37
- state.headers.set("Authorization", `Bearer ${info.access}`)
38
- if (info.accountId) state.headers.set("ChatGPT-Account-Id", info.accountId)
39
- if (
40
- state.inputUrl.hostname !== "api.openai.com" ||
41
- (state.inputUrl.pathname !== "/v1/chat/completions" && state.inputUrl.pathname !== "/v1/responses")
42
- ) {
43
- return
44
- }
45
-
46
- const rewritten = rewriteOpenAICodexBody(state.body)
47
- state.inputUrl.protocol = "https:"
48
- state.inputUrl.hostname = "chatgpt.com"
49
- state.inputUrl.pathname = "/backend-api/codex/responses"
50
- state.inputUrl.search = ""
51
- state.body = rewritten.body
52
- state.convertCodexResponse = !rewritten.originalStream
53
- state.headers.set("OpenAI-Beta", "responses=experimental")
54
- state.headers.set("originator", "codex_cli_rs")
55
- state.headers.set("accept", "text/event-stream")
56
- state.headers.delete("content-length")
57
- }
58
-
59
- async function applyCopilotRequest(state: RequestState, info: OAuthInfo, options: OAuthFetchOptions) {
60
- const session = await exchangeCopilotToken(info, options)
61
- state.headers.set("Authorization", `Bearer ${session.token}`)
62
- state.headers.set("Editor-Version", packageUserAgent(options.packageVersion))
63
- state.headers.set("Editor-Plugin-Version", packageUserAgent(options.packageVersion))
64
- state.headers.set("Copilot-Integration-Id", "vscode-chat")
65
- state.headers.delete("x-api-key")
66
-
67
- if (info.enterpriseUrl) {
68
- const target = new URL(info.enterpriseUrl.includes("://") ? info.enterpriseUrl : `https://${info.enterpriseUrl}`)
69
- state.inputUrl.protocol = target.protocol
70
- state.inputUrl.hostname = target.hostname
71
- state.inputUrl.port = target.port
72
- }
73
- }
74
-
75
- function inputToURL(input: RequestInfo | URL): URL {
76
- return input instanceof URL ? new URL(input.href) : new URL(typeof input === "string" ? input : input.url)
77
- }
78
-
79
- export function buildOAuthFetch(options: OAuthFetchOptions): FetchLike {
80
- return async (input, init) => {
81
- const info = await options.resolveOAuth(options.providerID)
82
- if (!info) return options.fetchImpl(input, init)
83
-
84
- const state: RequestState = {
85
- headers: copyHeaders(init?.headers),
86
- inputUrl: inputToURL(input),
87
- body: init?.body,
88
- convertCodexResponse: false,
89
- }
90
- setUserAgent(state.headers, options.packageVersion)
91
-
92
- if (options.providerID === "anthropic") applyAnthropicRequest(state, info)
93
- if (options.providerID === "openai") applyOpenAIRequest(state, info)
94
- if (options.providerID === "github-copilot") await applyCopilotRequest(state, info, options)
95
-
96
- const response = await options.fetchImpl(state.inputUrl, { ...init, headers: state.headers, body: state.body })
97
- if (state.convertCodexResponse && response.ok) return convertCodexSSEToJSON(response)
98
- return response
99
- }
100
- }
@@ -1,102 +0,0 @@
1
- import { buildOAuthRefreshError, normalizeReason, type OAuthInfo } from "../constants"
2
- import { withRetry } from "./retry"
3
- import type { AuthRuntime } from "./types"
4
-
5
- async function postOAuthToken(url: string, init: RequestInit, deps: AuthRuntime): Promise<Response> {
6
- return withRetry(
7
- () =>
8
- deps.fetchImpl(url, init).then(async (result) => {
9
- if (!result.ok) {
10
- const error = new Error(`HTTP ${result.status}`) as Error & { response?: Response; status?: number }
11
- error.response = result
12
- error.status = result.status
13
- throw error
14
- }
15
- return result
16
- }),
17
- deps,
18
- )
19
- }
20
-
21
- export async function refreshAnthropic(info: OAuthInfo, deps: AuthRuntime): Promise<OAuthInfo> {
22
- const response = await postOAuthToken(
23
- "https://console.anthropic.com/v1/oauth/token",
24
- {
25
- method: "POST",
26
- headers: { "Content-Type": "application/json" },
27
- body: JSON.stringify({
28
- grant_type: "refresh_token",
29
- refresh_token: info.refresh,
30
- client_id: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
31
- }),
32
- },
33
- deps,
34
- )
35
-
36
- let body: Record<string, unknown>
37
- try {
38
- body = (await response.json()) as Record<string, unknown>
39
- } catch (error) {
40
- throw buildOAuthRefreshError("anthropic", normalizeReason(error))
41
- }
42
-
43
- if (typeof body.access_token !== "string" || typeof body.refresh_token !== "string") {
44
- throw buildOAuthRefreshError("anthropic", "Invalid token response")
45
- }
46
-
47
- return {
48
- type: "oauth",
49
- access: body.access_token,
50
- refresh: body.refresh_token,
51
- expires: Date.now() + (typeof body.expires_in === "number" ? body.expires_in : 3600) * 1000,
52
- accountId: info.accountId,
53
- enterpriseUrl: info.enterpriseUrl,
54
- }
55
- }
56
-
57
- export async function refreshOpenAI(info: OAuthInfo, deps: AuthRuntime): Promise<OAuthInfo> {
58
- const response = await postOAuthToken(
59
- "https://auth.openai.com/oauth/token",
60
- {
61
- method: "POST",
62
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
63
- body: new URLSearchParams({
64
- grant_type: "refresh_token",
65
- refresh_token: info.refresh,
66
- client_id: "app_EMoamEEZ73f0CkXaXp7hrann",
67
- }),
68
- },
69
- deps,
70
- )
71
-
72
- let parsed: Record<string, unknown>
73
- try {
74
- parsed = (await response.json()) as Record<string, unknown>
75
- } catch (error) {
76
- throw buildOAuthRefreshError("openai", normalizeReason(error))
77
- }
78
-
79
- if (typeof parsed.access_token !== "string" || typeof parsed.refresh_token !== "string") {
80
- throw buildOAuthRefreshError("openai", "Invalid token response")
81
- }
82
-
83
- return {
84
- type: "oauth",
85
- access: parsed.access_token,
86
- refresh: parsed.refresh_token,
87
- expires: Date.now() + (typeof parsed.expires_in === "number" ? parsed.expires_in : 3600) * 1000,
88
- accountId: info.accountId,
89
- enterpriseUrl: info.enterpriseUrl,
90
- }
91
- }
92
-
93
- export async function exchangeCopilotToken(info: OAuthInfo, deps: AuthRuntime): Promise<{ token: string }> {
94
- const response = await postOAuthToken(
95
- "https://api.github.com/copilot_internal/v2/token",
96
- { method: "GET", headers: { Authorization: `token ${info.refresh}` } },
97
- deps,
98
- )
99
- const parsed = (await response.json()) as Record<string, unknown>
100
- if (typeof parsed.token !== "string") throw buildOAuthRefreshError("github-copilot", "Invalid token response")
101
- return { token: parsed.token }
102
- }
package/src/auth/retry.ts DELETED
@@ -1,70 +0,0 @@
1
- import { normalizeReason } from "../constants"
2
- import { headerValue } from "./headers"
3
- import type { AuthDependencies } from "./types"
4
-
5
- function getStatus(error: unknown): number | undefined {
6
- if (!error || typeof error !== "object") return undefined
7
- const record = error as Record<string, unknown>
8
- if (typeof record.status === "number") return record.status
9
- if (typeof record.statusCode === "number") return record.statusCode
10
- const response = record.response
11
- if (response && typeof response === "object") {
12
- const maybeStatus = (response as Record<string, unknown>).status
13
- if (typeof maybeStatus === "number") return maybeStatus
14
- }
15
- return undefined
16
- }
17
-
18
- function getRetryAfterMs(error: unknown): number {
19
- if (!error || typeof error !== "object") return 2000
20
- const response = (error as Record<string, unknown>).response
21
- if (response && typeof response === "object") {
22
- const headers = (response as { headers?: Headers }).headers
23
- if (headers instanceof Headers) {
24
- const retryAfter = headerValue(headers, "retry-after")
25
- if (!retryAfter) return 2000
26
- const seconds = Number(retryAfter)
27
- if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)
28
- const date = Date.parse(retryAfter)
29
- if (Number.isFinite(date)) return Math.max(0, date - Date.now())
30
- }
31
- }
32
- return 2000
33
- }
34
-
35
- function isRetryableError(error: unknown): boolean {
36
- const status = getStatus(error)
37
- if (status === 429) return true
38
- if (status !== undefined) return status >= 500
39
- const message = normalizeReason(error).toLowerCase()
40
- return (
41
- message.includes("network") ||
42
- message.includes("fetch") ||
43
- message.includes("timeout") ||
44
- message.includes("socket") ||
45
- message.includes("econn")
46
- )
47
- }
48
-
49
- export async function withRetry<T>(
50
- task: () => Promise<T>,
51
- deps: Required<Pick<AuthDependencies, "sleep">>,
52
- ): Promise<T> {
53
- let lastError: unknown
54
- for (let attempt = 0; attempt < 3; attempt += 1) {
55
- try {
56
- return await task()
57
- } catch (error) {
58
- lastError = error
59
- if (!isRetryableError(error)) throw error
60
- if (getStatus(error) === 429) {
61
- if (attempt >= 1) throw error
62
- await deps.sleep(getRetryAfterMs(error))
63
- continue
64
- }
65
- if (attempt >= 2) throw error
66
- await deps.sleep(attempt === 0 ? 500 : 1500)
67
- }
68
- }
69
- throw lastError
70
- }
package/src/auth/store.ts DELETED
@@ -1,98 +0,0 @@
1
- import { readFile } from "node:fs/promises"
2
- import os from "node:os"
3
- import path from "node:path"
4
- import { type AuthInfo, OAUTH_DUMMY_KEY, type OAuthInfo } from "../constants"
5
- import type { AuthDependencies } from "./types"
6
-
7
- // Mirrors opencode's xdg-basedir data location; see packages/core/src/global.ts.
8
- function dataHome(): string {
9
- const xdgDataHome = process.env.XDG_DATA_HOME
10
- if (xdgDataHome) return xdgDataHome
11
- return path.join(os.homedir(), ".local", "share")
12
- }
13
-
14
- function authFilePaths(): string[] {
15
- const root = path.join(dataHome(), "opencode")
16
- return [path.join(root, "auth.json"), path.join(root, "auth-v2.json")]
17
- }
18
-
19
- export function normalizeProviderKey(value: string | undefined): string | undefined {
20
- if (!value || value === OAUTH_DUMMY_KEY) return undefined
21
- return value
22
- }
23
-
24
- export function ensureOAuthInfo(value: AuthInfo | undefined): OAuthInfo | undefined {
25
- return value && value.type === "oauth" ? value : undefined
26
- }
27
-
28
- function isAuthInfo(value: unknown): value is AuthInfo {
29
- if (!value || typeof value !== "object" || Array.isArray(value)) return false
30
- const record = value as Record<string, unknown>
31
- if (record.type === "api") return typeof record.key === "string"
32
- if (record.type === "oauth") {
33
- return typeof record.access === "string" && typeof record.refresh === "string" && typeof record.expires === "number"
34
- }
35
- if (record.type === "wellknown") return typeof record.key === "string" && typeof record.token === "string"
36
- return false
37
- }
38
-
39
- function normalizeAuthMap(raw: unknown): Record<string, AuthInfo> | undefined {
40
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined
41
- const record = raw as Record<string, unknown>
42
-
43
- if (
44
- record.version === 2 &&
45
- record.accounts &&
46
- typeof record.accounts === "object" &&
47
- !Array.isArray(record.accounts)
48
- ) {
49
- const accounts = record.accounts as Record<string, unknown>
50
- const active =
51
- record.active && typeof record.active === "object" && !Array.isArray(record.active) ? record.active : {}
52
- const result: Record<string, AuthInfo> = {}
53
-
54
- for (const [serviceID, accountID] of Object.entries(active as Record<string, unknown>)) {
55
- if (typeof accountID !== "string") continue
56
- const account = accounts[accountID]
57
- if (!account || typeof account !== "object" || Array.isArray(account)) continue
58
- const credential = (account as Record<string, unknown>).credential
59
- if (isAuthInfo(credential)) result[serviceID] = credential
60
- }
61
-
62
- for (const account of Object.values(accounts)) {
63
- if (!account || typeof account !== "object" || Array.isArray(account)) continue
64
- const accountRecord = account as Record<string, unknown>
65
- const serviceID = accountRecord.serviceID
66
- const credential = accountRecord.credential
67
- if (typeof serviceID === "string" && result[serviceID] === undefined && isAuthInfo(credential)) {
68
- result[serviceID] = credential
69
- }
70
- }
71
-
72
- return result
73
- }
74
-
75
- const result: Record<string, AuthInfo> = {}
76
- for (const [providerID, info] of Object.entries(record)) {
77
- if (isAuthInfo(info)) result[providerID] = info
78
- }
79
- return result
80
- }
81
-
82
- export async function readAuthMap(deps: AuthDependencies): Promise<Record<string, AuthInfo> | undefined> {
83
- if (process.env.OPENCODE_AUTH_CONTENT) {
84
- try {
85
- return normalizeAuthMap(JSON.parse(process.env.OPENCODE_AUTH_CONTENT))
86
- } catch {}
87
- return undefined
88
- }
89
-
90
- for (const filePath of authFilePaths()) {
91
- try {
92
- const raw = await (deps.readFile ?? readFile)(filePath, "utf8")
93
- const parsed = normalizeAuthMap(JSON.parse(raw))
94
- if (parsed) return parsed
95
- } catch {}
96
- }
97
- return undefined
98
- }
package/src/auth/types.ts DELETED
@@ -1,27 +0,0 @@
1
- import type { AuthInfo, FetchLike, OAuthInfo, ProviderInfo } from "../constants"
2
-
3
- export interface ResolvedCredential {
4
- providerID: string
5
- provider?: ProviderInfo
6
- authInfo?: AuthInfo
7
- apiKey?: string
8
- fetch?: FetchLike
9
- mode: "apiKey" | "oauth" | "default"
10
- }
11
-
12
- export interface AuthDependencies {
13
- fetchImpl?: FetchLike
14
- now?: () => number
15
- sleep?: (ms: number) => Promise<void>
16
- readFile?: (filePath: string, encoding: BufferEncoding) => Promise<string>
17
- packageVersion?: string
18
- }
19
-
20
- export type AuthRuntime = Required<Pick<AuthDependencies, "fetchImpl" | "sleep">>
21
-
22
- export type OAuthResolver = (providerID: string) => Promise<OAuthInfo | undefined>
23
-
24
- export interface CodexBodyRewrite {
25
- body: BodyInit | null | undefined
26
- originalStream: boolean
27
- }
package/src/auth.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./auth/index"
@@ -1,24 +0,0 @@
1
- import { PLUGIN_NAME } from "./plugin"
2
-
3
- export function normalizeReason(error: unknown): string {
4
- const raw = error instanceof Error ? error.message : String(error)
5
- return raw.split(/\r?\n/, 1)[0].trim().slice(0, 200)
6
- }
7
-
8
- export function buildInboundTranslationError(userLanguage: string, reason: string): Error {
9
- return new Error(
10
- `[${PLUGIN_NAME}:INBOUND_TRANSLATION_FAILED] Failed to translate user message from ${userLanguage} to English: ${reason}`,
11
- )
12
- }
13
-
14
- export function buildAuthUnavailableError(providerID: string, envVar: string): Error {
15
- return new Error(
16
- `[${PLUGIN_NAME}:AUTH_UNAVAILABLE] No credential found for provider "${providerID}". Set ${envVar} in the environment or run "opencode auth login ${providerID}".`,
17
- )
18
- }
19
-
20
- export function buildOAuthRefreshError(providerID: string, reason: string): Error {
21
- return new Error(
22
- `[${PLUGIN_NAME}:OAUTH_REFRESH_FAILED] Failed to refresh OAuth token for provider "${providerID}": ${reason}. Re-authenticate with "opencode auth login ${providerID}".`,
23
- )
24
- }
@@ -1,33 +0,0 @@
1
- import { LLM_LANGUAGE, NONCE_PATTERN } from "./plugin"
2
- import type { SDKResponseLike, TextPartLike, TranslateState } from "./types"
3
-
4
- function isNonEmptyString(value: unknown): value is string {
5
- return typeof value === "string" && value.length > 0
6
- }
7
-
8
- export function unwrapData<T>(value: T | SDKResponseLike<T>): T {
9
- if (value && typeof value === "object" && "data" in value && (value as SDKResponseLike<T>).data !== undefined) {
10
- return (value as SDKResponseLike<T>).data as T
11
- }
12
- return value as T
13
- }
14
-
15
- export function isTranslateStateRecord(value: unknown): value is TranslateState {
16
- if (!value || typeof value !== "object") return false
17
- const record = value as Record<string, unknown>
18
- return (
19
- record.translate_enabled === true &&
20
- record.translate_llm_lang === LLM_LANGUAGE &&
21
- isNonEmptyString(record.translate_user_lang) &&
22
- isNonEmptyString(record.translate_nonce) &&
23
- NONCE_PATTERN.test(record.translate_nonce)
24
- )
25
- }
26
-
27
- export function isTextPart(part: TextPartLike): part is TextPartLike & { text: string } {
28
- return part.type === "text" && typeof part.text === "string"
29
- }
30
-
31
- export function isUserAuthoredTextPart(part: TextPartLike): part is TextPartLike & { text: string } {
32
- return isTextPart(part) && part.synthetic !== true && part.ignored !== true
33
- }