opencode-translate 1.0.5 → 1.0.7
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/dist/index.js +2271 -0
- package/index.d.ts +5 -0
- package/package.json +10 -4
- package/src/activation/chat-message.ts +0 -164
- package/src/activation/index.ts +0 -38
- package/src/activation/logging.ts +0 -11
- package/src/activation/messages-transform.ts +0 -44
- package/src/activation/metadata.ts +0 -41
- package/src/activation/parts.ts +0 -46
- package/src/activation/question-hooks.ts +0 -126
- package/src/activation/state.ts +0 -97
- package/src/activation/text-complete.ts +0 -50
- package/src/activation/trigger.ts +0 -57
- package/src/activation/types.ts +0 -47
- package/src/activation.ts +0 -1
- package/src/anthropic-oauth.ts +0 -148
- package/src/auth/codex-request.ts +0 -108
- package/src/auth/codex-response.ts +0 -78
- package/src/auth/codex-shared.ts +0 -3
- package/src/auth/headers.ts +0 -18
- package/src/auth/index.ts +0 -177
- package/src/auth/oauth-fetch.ts +0 -100
- package/src/auth/refresh.ts +0 -102
- package/src/auth/retry.ts +0 -70
- package/src/auth/store.ts +0 -98
- package/src/auth/types.ts +0 -27
- package/src/auth.ts +0 -1
- package/src/constants/errors.ts +0 -24
- package/src/constants/guards.ts +0 -33
- package/src/constants/options.ts +0 -55
- package/src/constants/plugin.ts +0 -9
- package/src/constants/types.ts +0 -159
- package/src/constants.ts +0 -5
- package/src/formatting.ts +0 -157
- package/src/index.ts +0 -7
- package/src/labels.ts +0 -3
- package/src/prompts.ts +0 -123
- package/src/question-tool.ts +0 -234
- package/src/translator/index.ts +0 -172
- package/src/translator/part-id.ts +0 -43
- package/src/translator/provider.ts +0 -411
- package/src/translator/retry.ts +0 -62
- package/src/translator/types.ts +0 -24
- package/src/translator.ts +0 -1
package/src/translator/index.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { setTimeout as sleep } from "node:timers/promises"
|
|
2
|
-
import { generateText, type LanguageModel } from "ai"
|
|
3
|
-
import { createCredentialResolver } from "../auth"
|
|
4
|
-
import {
|
|
5
|
-
buildAuthUnavailableError,
|
|
6
|
-
PLUGIN_NAME,
|
|
7
|
-
type PluginClientLike,
|
|
8
|
-
type ProviderInfo,
|
|
9
|
-
parseTranslatorModel,
|
|
10
|
-
type ResolvedTranslateOptions,
|
|
11
|
-
} from "../constants"
|
|
12
|
-
import {
|
|
13
|
-
buildBatchSystemPrompt,
|
|
14
|
-
buildBatchUserPrompt,
|
|
15
|
-
buildSystemPrompt,
|
|
16
|
-
buildUserPrompt,
|
|
17
|
-
parseBatchSegments,
|
|
18
|
-
unwrapEchoedTextEnvelope,
|
|
19
|
-
} from "../prompts"
|
|
20
|
-
import { __resetSyntheticPartIDForTest } from "./part-id"
|
|
21
|
-
import {
|
|
22
|
-
__resetProviderFactoryCacheForTest,
|
|
23
|
-
buildVariantProviderOptions,
|
|
24
|
-
instantiateModel,
|
|
25
|
-
instantiateProvider,
|
|
26
|
-
loadFactory,
|
|
27
|
-
resolveModelInfo,
|
|
28
|
-
supportsTemperature,
|
|
29
|
-
} from "./provider"
|
|
30
|
-
import { withRetry } from "./retry"
|
|
31
|
-
import type { TranslateTextInput, TranslateTextsInput, TranslatorDependencies } from "./types"
|
|
32
|
-
|
|
33
|
-
const DEFAULT_TRANSLATE_TIMEOUT_MS = 180_000
|
|
34
|
-
|
|
35
|
-
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
|
36
|
-
return new Promise<T>((resolve, reject) => {
|
|
37
|
-
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
38
|
-
promise.then(
|
|
39
|
-
(value) => {
|
|
40
|
-
clearTimeout(timer)
|
|
41
|
-
resolve(value)
|
|
42
|
-
},
|
|
43
|
-
(error) => {
|
|
44
|
-
clearTimeout(timer)
|
|
45
|
-
reject(error)
|
|
46
|
-
},
|
|
47
|
-
)
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isAuthMessage(error: unknown): boolean {
|
|
52
|
-
if (!(error instanceof Error)) return false
|
|
53
|
-
return error.message.includes(":AUTH_UNAVAILABLE]") || error.message.includes(":OAUTH_REFRESH_FAILED]")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function modelProviderHint(providerID: string, provider?: ProviderInfo): Error {
|
|
57
|
-
return buildAuthUnavailableError(providerID, provider?.env[0] || "the provider's API key env var")
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function __resetTranslatorCachesForTest() {
|
|
61
|
-
__resetProviderFactoryCacheForTest()
|
|
62
|
-
__resetSyntheticPartIDForTest()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function createTranslator(
|
|
66
|
-
client: PluginClientLike,
|
|
67
|
-
options: ResolvedTranslateOptions,
|
|
68
|
-
deps: TranslatorDependencies = {},
|
|
69
|
-
) {
|
|
70
|
-
const sleepImpl = deps.sleep ?? ((ms: number) => sleep(ms))
|
|
71
|
-
const now = deps.now ?? (() => Date.now())
|
|
72
|
-
const generateTextImpl = deps.generateTextImpl ?? generateText
|
|
73
|
-
const credentialResolver = deps.credentialResolver ?? createCredentialResolver(client)
|
|
74
|
-
const timeoutMs = deps.timeoutMs ?? DEFAULT_TRANSLATE_TIMEOUT_MS
|
|
75
|
-
|
|
76
|
-
async function generateFromPrompts(system: string, prompt: string): Promise<string> {
|
|
77
|
-
const { providerID, modelID } = parseTranslatorModel(options.model)
|
|
78
|
-
const credentials = await credentialResolver.resolve(options.model)
|
|
79
|
-
const modelInfo = resolveModelInfo(credentials.provider, modelID)
|
|
80
|
-
const variantProviderOptions = buildVariantProviderOptions(providerID, modelID, modelInfo, options.variant)
|
|
81
|
-
const factory = await loadFactory(providerID, modelInfo)
|
|
82
|
-
const provider = instantiateProvider(factory, providerID, credentials, modelInfo)
|
|
83
|
-
const providerOptions = { ...(credentials.provider?.options ?? {}), ...(modelInfo.options ?? {}) }
|
|
84
|
-
const model = instantiateModel(provider, modelID, providerID, modelInfo, providerOptions) as LanguageModel
|
|
85
|
-
|
|
86
|
-
return withRetry(async () => {
|
|
87
|
-
try {
|
|
88
|
-
const result = await withTimeout(
|
|
89
|
-
generateTextImpl({
|
|
90
|
-
model,
|
|
91
|
-
system,
|
|
92
|
-
...(supportsTemperature(providerID, modelID, modelInfo) ? { temperature: 0 } : {}),
|
|
93
|
-
...(variantProviderOptions ? { providerOptions: variantProviderOptions } : {}),
|
|
94
|
-
prompt,
|
|
95
|
-
}),
|
|
96
|
-
timeoutMs,
|
|
97
|
-
"Translator generateText",
|
|
98
|
-
)
|
|
99
|
-
return result.text
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (isAuthMessage(error)) throw error
|
|
102
|
-
if (credentials.mode === "default" && credentialResolver.isMissingCredentialError(error)) {
|
|
103
|
-
throw modelProviderHint(providerID, credentials.provider)
|
|
104
|
-
}
|
|
105
|
-
throw error
|
|
106
|
-
}
|
|
107
|
-
}, sleepImpl)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function translateText(input: TranslateTextInput): Promise<string> {
|
|
111
|
-
if (!input.text) return input.text
|
|
112
|
-
if (input.sourceLanguage === input.targetLanguage) return input.text
|
|
113
|
-
|
|
114
|
-
const startedAt = now()
|
|
115
|
-
const rawTranslated = await generateFromPrompts(buildSystemPrompt(input), buildUserPrompt(input))
|
|
116
|
-
const translated = unwrapEchoedTextEnvelope(rawTranslated)
|
|
117
|
-
|
|
118
|
-
if (options.verbose) {
|
|
119
|
-
await client.app.log({
|
|
120
|
-
body: {
|
|
121
|
-
service: PLUGIN_NAME,
|
|
122
|
-
level: "info",
|
|
123
|
-
message: "translated",
|
|
124
|
-
extra: {
|
|
125
|
-
direction: input.direction,
|
|
126
|
-
chars_in: input.text.length,
|
|
127
|
-
chars_out: translated.length,
|
|
128
|
-
ms: now() - startedAt,
|
|
129
|
-
cached: false,
|
|
130
|
-
model: options.model,
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return translated
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function translateTexts(input: TranslateTextsInput): Promise<string[]> {
|
|
140
|
-
if (input.texts.length === 0) return []
|
|
141
|
-
if (input.sourceLanguage === input.targetLanguage) return [...input.texts]
|
|
142
|
-
|
|
143
|
-
const startedAt = now()
|
|
144
|
-
const rawTranslated = await generateFromPrompts(buildBatchSystemPrompt(input), buildBatchUserPrompt(input))
|
|
145
|
-
const translated = parseBatchSegments(rawTranslated, input.texts.length).map(unwrapEchoedTextEnvelope)
|
|
146
|
-
|
|
147
|
-
if (options.verbose) {
|
|
148
|
-
await client.app.log({
|
|
149
|
-
body: {
|
|
150
|
-
service: PLUGIN_NAME,
|
|
151
|
-
level: "info",
|
|
152
|
-
message: "translated",
|
|
153
|
-
extra: {
|
|
154
|
-
direction: input.direction,
|
|
155
|
-
chars_in: input.texts.reduce((total, text) => total + text.length, 0),
|
|
156
|
-
chars_out: translated.reduce((total, text) => total + text.length, 0),
|
|
157
|
-
segments: input.texts.length,
|
|
158
|
-
ms: now() - startedAt,
|
|
159
|
-
cached: false,
|
|
160
|
-
model: options.model,
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return translated
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return { translateText, translateTexts }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export { __resetSyntheticPartIDForTest, createSyntheticPartID, hashText } from "./part-id"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto"
|
|
2
|
-
|
|
3
|
-
const PART_ID_LENGTH = 26
|
|
4
|
-
const PART_ID_PREFIX = "prt"
|
|
5
|
-
const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
6
|
-
|
|
7
|
-
let partLastTimestamp = 0
|
|
8
|
-
let partCounter = 0
|
|
9
|
-
|
|
10
|
-
export function __resetSyntheticPartIDForTest() {
|
|
11
|
-
partLastTimestamp = 0
|
|
12
|
-
partCounter = 0
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function randomBase62(length: number): string {
|
|
16
|
-
const bytes = randomBytes(length)
|
|
17
|
-
let result = ""
|
|
18
|
-
for (let index = 0; index < length; index += 1) {
|
|
19
|
-
result += BASE62_CHARS[bytes[index] % BASE62_CHARS.length]
|
|
20
|
-
}
|
|
21
|
-
return result
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function hashText(text: string): string {
|
|
25
|
-
return createHash("sha256").update(text, "utf8").digest("hex").slice(0, 16)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createSyntheticPartID(): string {
|
|
29
|
-
const currentTimestamp = Date.now()
|
|
30
|
-
if (currentTimestamp !== partLastTimestamp) {
|
|
31
|
-
partLastTimestamp = currentTimestamp
|
|
32
|
-
partCounter = 0
|
|
33
|
-
}
|
|
34
|
-
partCounter += 1
|
|
35
|
-
|
|
36
|
-
const encoded = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(partCounter)
|
|
37
|
-
const timeBytes = Buffer.alloc(6)
|
|
38
|
-
for (let index = 0; index < 6; index += 1) {
|
|
39
|
-
timeBytes[index] = Number((encoded >> BigInt(40 - 8 * index)) & BigInt(0xff))
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return `${PART_ID_PREFIX}_${timeBytes.toString("hex")}${randomBase62(PART_ID_LENGTH - 12)}`
|
|
43
|
-
}
|
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
import { type AuthInfo, type FetchLike, PLUGIN_NAME, type ProviderInfo, type ProviderModelInfo } from "../constants"
|
|
2
|
-
|
|
3
|
-
const providerFactoryCache = new Map<string, unknown>()
|
|
4
|
-
|
|
5
|
-
const PROVIDER_PACKAGE_FALLBACK: Record<string, string> = {
|
|
6
|
-
anthropic: "@ai-sdk/anthropic",
|
|
7
|
-
openai: "@ai-sdk/openai",
|
|
8
|
-
google: "@ai-sdk/google",
|
|
9
|
-
"google-vertex": "@ai-sdk/google-vertex",
|
|
10
|
-
"amazon-bedrock": "@ai-sdk/amazon-bedrock",
|
|
11
|
-
"github-copilot": "@ai-sdk/openai-compatible",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const CREATE_EXPORT_FALLBACK: Record<string, string[]> = {
|
|
15
|
-
"@ai-sdk/amazon-bedrock": ["createAmazonBedrock", "bedrock"],
|
|
16
|
-
"@ai-sdk/anthropic": ["createAnthropic", "anthropic"],
|
|
17
|
-
"@ai-sdk/azure": ["createAzure", "azure"],
|
|
18
|
-
"@ai-sdk/gateway": ["createGateway", "gateway"],
|
|
19
|
-
"@ai-sdk/google": ["createGoogleGenerativeAI", "google"],
|
|
20
|
-
"@ai-sdk/google-vertex": ["createVertex", "vertex"],
|
|
21
|
-
"@ai-sdk/openai": ["createOpenAI", "openai"],
|
|
22
|
-
"@ai-sdk/openai-compatible": ["createOpenAICompatible"],
|
|
23
|
-
"@openrouter/ai-sdk-provider": ["createOpenRouter", "openrouter"],
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const PROVIDER_OPTIONS_KEY: Record<string, string> = {
|
|
27
|
-
"@ai-sdk/amazon-bedrock": "bedrock",
|
|
28
|
-
"@ai-sdk/amazon-bedrock/mantle": "openai",
|
|
29
|
-
"@ai-sdk/anthropic": "anthropic",
|
|
30
|
-
"@ai-sdk/azure": "openai",
|
|
31
|
-
"@ai-sdk/gateway": "gateway",
|
|
32
|
-
"@ai-sdk/github-copilot": "openai",
|
|
33
|
-
"@ai-sdk/google": "google",
|
|
34
|
-
"@ai-sdk/google-vertex": "vertex",
|
|
35
|
-
"@ai-sdk/google-vertex/anthropic": "anthropic",
|
|
36
|
-
"@ai-sdk/openai": "openai",
|
|
37
|
-
"@openrouter/ai-sdk-provider": "openrouter",
|
|
38
|
-
"ai-gateway-provider": "openaiCompatible",
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type JsonValue = null | string | number | boolean | JsonObject | JsonArray
|
|
42
|
-
type JsonObject = { [key: string]: JsonValue | undefined }
|
|
43
|
-
type JsonArray = JsonValue[]
|
|
44
|
-
type VariantProviderOptions = Record<string, JsonObject>
|
|
45
|
-
|
|
46
|
-
interface ProviderCredentials {
|
|
47
|
-
provider?: ProviderInfo
|
|
48
|
-
authInfo?: AuthInfo
|
|
49
|
-
apiKey?: string
|
|
50
|
-
fetch?: FetchLike
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function __resetProviderFactoryCacheForTest() {
|
|
54
|
-
providerFactoryCache.clear()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function providerPackage(providerID: string, model?: ProviderModelInfo): string {
|
|
58
|
-
const packageName = model?.api?.npm || PROVIDER_PACKAGE_FALLBACK[providerID]
|
|
59
|
-
if (!packageName) throw new Error(`Unsupported translator provider "${providerID}"`)
|
|
60
|
-
return packageName
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function pickFactory(mod: Record<string, unknown>, packageName: string): unknown {
|
|
64
|
-
for (const key of CREATE_EXPORT_FALLBACK[packageName] ?? []) {
|
|
65
|
-
if (typeof mod[key] === "function") return mod[key]
|
|
66
|
-
}
|
|
67
|
-
const createKey = Object.keys(mod).find((key) => key.startsWith("create") && typeof mod[key] === "function")
|
|
68
|
-
return createKey ? mod[createKey] : undefined
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function loadFactory(providerID: string, model?: ProviderModelInfo): Promise<unknown> {
|
|
72
|
-
const packageName = providerPackage(providerID, model)
|
|
73
|
-
const cached = providerFactoryCache.get(packageName)
|
|
74
|
-
if (cached) return cached
|
|
75
|
-
|
|
76
|
-
let mod: Record<string, unknown>
|
|
77
|
-
try {
|
|
78
|
-
mod = (await import(packageName)) as Record<string, unknown>
|
|
79
|
-
} catch (error) {
|
|
80
|
-
throw new Error(`Unable to load provider package "${packageName}" for "${providerID}": ${String(error)}`)
|
|
81
|
-
}
|
|
82
|
-
const factory = pickFactory(mod, packageName)
|
|
83
|
-
|
|
84
|
-
if (typeof factory !== "function") {
|
|
85
|
-
throw new Error(`Unable to load provider factory from "${packageName}" for "${providerID}"`)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
providerFactoryCache.set(packageName, factory)
|
|
89
|
-
return factory
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function resolveModelInfo(provider: ProviderInfo | undefined, modelID: string): ProviderModelInfo {
|
|
93
|
-
return provider?.models?.[modelID] ?? { id: modelID, api: { id: modelID } }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function sdkProviderOptionsKey(providerID: string, model?: ProviderModelInfo): string {
|
|
97
|
-
const packageName = model?.api?.npm
|
|
98
|
-
if (packageName && PROVIDER_OPTIONS_KEY[packageName]) return PROVIDER_OPTIONS_KEY[packageName]
|
|
99
|
-
if (packageName === "@ai-sdk/openai-compatible" || packageName === "@ai-sdk/openai") return providerID.split(".")[0]
|
|
100
|
-
return providerID
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function invalidVariantError(providerID: string, modelID: string, model: ProviderModelInfo, variant: string) {
|
|
104
|
-
const variants = Object.keys(model.variants ?? {}).sort()
|
|
105
|
-
const modelName = `${providerID}/${modelID}`
|
|
106
|
-
if (variants.length === 0) {
|
|
107
|
-
return new Error(
|
|
108
|
-
`[${PLUGIN_NAME}:INVALID_VARIANT] options.variant "${variant}" is not available for "${modelName}". This model has no configurable variants.`,
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
return new Error(
|
|
112
|
-
`[${PLUGIN_NAME}:INVALID_VARIANT] options.variant "${variant}" is not available for "${modelName}". Available variants: ${variants.join(", ")}.`,
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function buildVariantProviderOptions(
|
|
117
|
-
providerID: string,
|
|
118
|
-
modelID: string,
|
|
119
|
-
model: ProviderModelInfo,
|
|
120
|
-
variant?: string,
|
|
121
|
-
): VariantProviderOptions | undefined {
|
|
122
|
-
if (!variant) return undefined
|
|
123
|
-
const selected = model.variants?.[variant]
|
|
124
|
-
if (!selected) throw invalidVariantError(providerID, modelID, model, variant)
|
|
125
|
-
const providerOptions = selected as JsonObject
|
|
126
|
-
if (model.api?.npm === "@ai-sdk/azure") return { openai: providerOptions, azure: providerOptions }
|
|
127
|
-
return { [sdkProviderOptionsKey(providerID, model)]: providerOptions }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function headerRecord(value: unknown): Record<string, string> {
|
|
131
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {}
|
|
132
|
-
return Object.fromEntries(
|
|
133
|
-
Object.entries(value as Record<string, unknown>).filter((entry): entry is [string, string] => {
|
|
134
|
-
return typeof entry[1] === "string"
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function substitutionVars(options: Record<string, unknown>, authInfo?: AuthInfo): Record<string, string | undefined> {
|
|
140
|
-
const metadata = authInfo?.type === "api" ? authInfo.metadata : undefined
|
|
141
|
-
const location =
|
|
142
|
-
stringOption(options.location) ?? process.env.GOOGLE_VERTEX_LOCATION ?? process.env.GOOGLE_CLOUD_LOCATION
|
|
143
|
-
const vertexEndpoint =
|
|
144
|
-
location === "global" ? "aiplatform.googleapis.com" : location ? `${location}-aiplatform.googleapis.com` : undefined
|
|
145
|
-
return {
|
|
146
|
-
...process.env,
|
|
147
|
-
AZURE_RESOURCE_NAME:
|
|
148
|
-
stringOption(options.resourceName) ?? metadata?.resourceName ?? process.env.AZURE_RESOURCE_NAME,
|
|
149
|
-
GOOGLE_VERTEX_PROJECT:
|
|
150
|
-
stringOption(options.project) ??
|
|
151
|
-
process.env.GOOGLE_VERTEX_PROJECT ??
|
|
152
|
-
process.env.GOOGLE_CLOUD_PROJECT ??
|
|
153
|
-
process.env.GCP_PROJECT ??
|
|
154
|
-
process.env.GCLOUD_PROJECT,
|
|
155
|
-
GOOGLE_VERTEX_LOCATION: location,
|
|
156
|
-
GOOGLE_VERTEX_ENDPOINT: vertexEndpoint ?? process.env.GOOGLE_VERTEX_ENDPOINT,
|
|
157
|
-
CLOUDFLARE_ACCOUNT_ID: metadata?.accountId ?? process.env.CLOUDFLARE_ACCOUNT_ID,
|
|
158
|
-
CLOUDFLARE_GATEWAY_ID: metadata?.gatewayId ?? process.env.CLOUDFLARE_GATEWAY_ID,
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function stringOption(value: unknown): string | undefined {
|
|
163
|
-
return typeof value === "string" && value.length > 0 ? value : undefined
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function resolveBaseURL(baseURL: unknown, apiURL: unknown, options: Record<string, unknown>, authInfo?: AuthInfo) {
|
|
167
|
-
let url = stringOption(baseURL) ?? stringOption(apiURL)
|
|
168
|
-
if (!url) return undefined
|
|
169
|
-
const vars = substitutionVars(options, authInfo)
|
|
170
|
-
url = url.replace(/\$\{([^}]+)\}/g, (match, key) => vars[String(key)] ?? match)
|
|
171
|
-
return url
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function wrapSSE(response: Response, ms: number, controller: AbortController) {
|
|
175
|
-
if (typeof ms !== "number" || ms <= 0) return response
|
|
176
|
-
if (!response.body) return response
|
|
177
|
-
if (!response.headers.get("content-type")?.includes("text/event-stream")) return response
|
|
178
|
-
|
|
179
|
-
const reader = response.body.getReader()
|
|
180
|
-
const body = new ReadableStream<Uint8Array>({
|
|
181
|
-
async pull(ctrl) {
|
|
182
|
-
const part = await new Promise<Awaited<ReturnType<typeof reader.read>>>((resolve, reject) => {
|
|
183
|
-
const id = setTimeout(() => {
|
|
184
|
-
const error = new Error("SSE read timed out")
|
|
185
|
-
controller.abort(error)
|
|
186
|
-
void reader.cancel(error)
|
|
187
|
-
reject(error)
|
|
188
|
-
}, ms)
|
|
189
|
-
|
|
190
|
-
reader.read().then(
|
|
191
|
-
(value) => {
|
|
192
|
-
clearTimeout(id)
|
|
193
|
-
resolve(value)
|
|
194
|
-
},
|
|
195
|
-
(error) => {
|
|
196
|
-
clearTimeout(id)
|
|
197
|
-
reject(error)
|
|
198
|
-
},
|
|
199
|
-
)
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
if (part.done) {
|
|
203
|
-
ctrl.close()
|
|
204
|
-
return
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
ctrl.enqueue(part.value)
|
|
208
|
-
},
|
|
209
|
-
async cancel(reason) {
|
|
210
|
-
controller.abort(reason)
|
|
211
|
-
await reader.cancel(reason)
|
|
212
|
-
},
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
return new Response(body, {
|
|
216
|
-
headers: new Headers(response.headers),
|
|
217
|
-
status: response.status,
|
|
218
|
-
statusText: response.statusText,
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function anySignal(signals: AbortSignal[]): AbortSignal | undefined {
|
|
223
|
-
if (signals.length === 0) return undefined
|
|
224
|
-
if (signals.length === 1) return signals[0]
|
|
225
|
-
const signalAny = (AbortSignal as typeof AbortSignal & { any?: (signals: AbortSignal[]) => AbortSignal }).any
|
|
226
|
-
return signalAny ? signalAny(signals) : signals[0]
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function stripOpenAIItemIDs(packageName: string, init: RequestInit) {
|
|
230
|
-
if (packageName !== "@ai-sdk/openai" && packageName !== "@ai-sdk/azure") return
|
|
231
|
-
if (!init.body || init.method !== "POST" || typeof init.body !== "string") return
|
|
232
|
-
try {
|
|
233
|
-
const body = JSON.parse(init.body) as Record<string, unknown>
|
|
234
|
-
if (body.store === true || !Array.isArray(body.input)) return
|
|
235
|
-
for (const item of body.input) {
|
|
236
|
-
if (item && typeof item === "object" && !Array.isArray(item)) delete (item as Record<string, unknown>).id
|
|
237
|
-
}
|
|
238
|
-
init.body = JSON.stringify(body)
|
|
239
|
-
} catch {}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function withOpenCodeFetch(config: Record<string, unknown>, packageName: string) {
|
|
243
|
-
const configuredFetch = typeof config.fetch === "function" ? (config.fetch as FetchLike) : undefined
|
|
244
|
-
const chunkTimeout = typeof config.chunkTimeout === "number" ? config.chunkTimeout : undefined
|
|
245
|
-
delete config.chunkTimeout
|
|
246
|
-
|
|
247
|
-
config.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
248
|
-
const requestInit = { ...(init ?? {}) }
|
|
249
|
-
const signals: AbortSignal[] = []
|
|
250
|
-
const chunkController = chunkTimeout && chunkTimeout > 0 ? new AbortController() : undefined
|
|
251
|
-
if (requestInit.signal) signals.push(requestInit.signal)
|
|
252
|
-
if (chunkController) signals.push(chunkController.signal)
|
|
253
|
-
if (typeof config.timeout === "number" && config.timeout > 0) signals.push(AbortSignal.timeout(config.timeout))
|
|
254
|
-
const signal = anySignal(signals)
|
|
255
|
-
if (signal) requestInit.signal = signal
|
|
256
|
-
stripOpenAIItemIDs(packageName, requestInit)
|
|
257
|
-
|
|
258
|
-
const response = await (configuredFetch ?? fetch)(input, { ...requestInit, timeout: false } as RequestInit)
|
|
259
|
-
return chunkController && chunkTimeout ? wrapSSE(response, chunkTimeout, chunkController) : response
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function providerConfig(
|
|
264
|
-
providerID: string,
|
|
265
|
-
credentials: ProviderCredentials,
|
|
266
|
-
model?: ProviderModelInfo,
|
|
267
|
-
): Record<string, unknown> {
|
|
268
|
-
const provider = credentials.provider
|
|
269
|
-
const packageName = providerPackage(providerID, model)
|
|
270
|
-
const config: Record<string, unknown> = { ...(provider?.options ?? {}) }
|
|
271
|
-
|
|
272
|
-
if (providerID === "google-vertex" && !packageName.includes("@ai-sdk/openai-compatible")) delete config.fetch
|
|
273
|
-
if (packageName.includes("@ai-sdk/openai-compatible") && config.includeUsage !== false) config.includeUsage = true
|
|
274
|
-
|
|
275
|
-
const baseURL = resolveBaseURL(config.baseURL, model?.api?.url, config, credentials.authInfo)
|
|
276
|
-
if (baseURL !== undefined) config.baseURL = baseURL
|
|
277
|
-
if (credentials.apiKey !== undefined) config.apiKey = credentials.apiKey
|
|
278
|
-
if (credentials.fetch) config.fetch = credentials.fetch
|
|
279
|
-
if (model?.headers) config.headers = { ...headerRecord(config.headers), ...model.headers }
|
|
280
|
-
if (providerID === "github-copilot" && config.baseURL === undefined) config.baseURL = "https://api.githubcopilot.com"
|
|
281
|
-
if (
|
|
282
|
-
providerID === "amazon-bedrock" &&
|
|
283
|
-
credentials.authInfo?.type === "api" &&
|
|
284
|
-
!process.env.AWS_BEARER_TOKEN_BEDROCK
|
|
285
|
-
) {
|
|
286
|
-
process.env.AWS_BEARER_TOKEN_BEDROCK = credentials.authInfo.key
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
withOpenCodeFetch(config, packageName)
|
|
290
|
-
return { name: providerID, ...config }
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export function instantiateProvider(
|
|
294
|
-
factory: unknown,
|
|
295
|
-
providerID: string,
|
|
296
|
-
credentials: ProviderCredentials,
|
|
297
|
-
model?: ProviderModelInfo,
|
|
298
|
-
): unknown {
|
|
299
|
-
if (typeof factory !== "function") throw new Error(`Invalid provider factory for "${providerID}"`)
|
|
300
|
-
return (factory as (config: Record<string, unknown>) => unknown)(providerConfig(providerID, credentials, model))
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function shouldUseCopilotResponsesApi(modelID: string): boolean {
|
|
304
|
-
const match = /^gpt-(\d+)/.exec(modelID)
|
|
305
|
-
if (!match) return false
|
|
306
|
-
return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function selectAzureLanguageModel(record: Record<string, unknown>, modelID: string, useChat: boolean): unknown {
|
|
310
|
-
if (useChat && typeof record.chat === "function") return (record.chat as (id: string) => unknown)(modelID)
|
|
311
|
-
if (typeof record.responses === "function") return (record.responses as (id: string) => unknown)(modelID)
|
|
312
|
-
if (typeof record.messages === "function") return (record.messages as (id: string) => unknown)(modelID)
|
|
313
|
-
if (typeof record.chat === "function") return (record.chat as (id: string) => unknown)(modelID)
|
|
314
|
-
if (typeof record.languageModel === "function") return (record.languageModel as (id: string) => unknown)(modelID)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function bedrockModelID(modelID: string, region: unknown): string {
|
|
318
|
-
const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
|
|
319
|
-
if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) return modelID
|
|
320
|
-
if (typeof region !== "string") return modelID
|
|
321
|
-
|
|
322
|
-
let regionPrefix = region.split("-")[0]
|
|
323
|
-
if (regionPrefix === "us") {
|
|
324
|
-
const modelRequiresPrefix = [
|
|
325
|
-
"nova-micro",
|
|
326
|
-
"nova-lite",
|
|
327
|
-
"nova-pro",
|
|
328
|
-
"nova-premier",
|
|
329
|
-
"nova-2",
|
|
330
|
-
"claude",
|
|
331
|
-
"deepseek",
|
|
332
|
-
].some((value) => modelID.includes(value))
|
|
333
|
-
if (modelRequiresPrefix && !region.startsWith("us-gov")) return `${regionPrefix}.${modelID}`
|
|
334
|
-
}
|
|
335
|
-
if (regionPrefix === "eu") {
|
|
336
|
-
const regionRequiresPrefix = [
|
|
337
|
-
"eu-west-1",
|
|
338
|
-
"eu-west-2",
|
|
339
|
-
"eu-west-3",
|
|
340
|
-
"eu-north-1",
|
|
341
|
-
"eu-central-1",
|
|
342
|
-
"eu-south-1",
|
|
343
|
-
"eu-south-2",
|
|
344
|
-
].some((value) => region.includes(value))
|
|
345
|
-
const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((value) =>
|
|
346
|
-
modelID.includes(value),
|
|
347
|
-
)
|
|
348
|
-
if (regionRequiresPrefix && modelRequiresPrefix) return `${regionPrefix}.${modelID}`
|
|
349
|
-
}
|
|
350
|
-
if (regionPrefix === "ap") {
|
|
351
|
-
const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region)
|
|
352
|
-
const isTokyoRegion = region === "ap-northeast-1"
|
|
353
|
-
if (
|
|
354
|
-
isAustraliaRegion &&
|
|
355
|
-
["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((value) => modelID.includes(value))
|
|
356
|
-
) {
|
|
357
|
-
regionPrefix = "au"
|
|
358
|
-
return `${regionPrefix}.${modelID}`
|
|
359
|
-
}
|
|
360
|
-
const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((value) =>
|
|
361
|
-
modelID.includes(value),
|
|
362
|
-
)
|
|
363
|
-
if (modelRequiresPrefix) return `${isTokyoRegion ? "jp" : "apac"}.${modelID}`
|
|
364
|
-
}
|
|
365
|
-
return modelID
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export function instantiateModel(
|
|
369
|
-
provider: unknown,
|
|
370
|
-
modelID: string,
|
|
371
|
-
providerID?: string,
|
|
372
|
-
model?: ProviderModelInfo,
|
|
373
|
-
providerOptions?: Record<string, unknown>,
|
|
374
|
-
): unknown {
|
|
375
|
-
const apiID = model?.api?.id || model?.id || modelID
|
|
376
|
-
if (typeof provider === "function") return provider(modelID)
|
|
377
|
-
if (provider && typeof provider === "object") {
|
|
378
|
-
const record = provider as Record<string, unknown>
|
|
379
|
-
if ((providerID === "openai" || providerID === "xai") && typeof record.responses === "function") {
|
|
380
|
-
return (record.responses as (id: string) => unknown)(apiID)
|
|
381
|
-
}
|
|
382
|
-
if (
|
|
383
|
-
providerID === "github-copilot" &&
|
|
384
|
-
typeof record.responses === "function" &&
|
|
385
|
-
typeof record.chat === "function"
|
|
386
|
-
) {
|
|
387
|
-
return shouldUseCopilotResponsesApi(apiID)
|
|
388
|
-
? (record.responses as (id: string) => unknown)(apiID)
|
|
389
|
-
: (record.chat as (id: string) => unknown)(apiID)
|
|
390
|
-
}
|
|
391
|
-
if (providerID === "azure" || providerID === "azure-cognitive-services") {
|
|
392
|
-
const selected = selectAzureLanguageModel(record, apiID, providerOptions?.useCompletionUrls === true)
|
|
393
|
-
if (selected) return selected
|
|
394
|
-
}
|
|
395
|
-
if (providerID === "amazon-bedrock" && typeof record.languageModel === "function") {
|
|
396
|
-
return (record.languageModel as (id: string) => unknown)(bedrockModelID(apiID, providerOptions?.region))
|
|
397
|
-
}
|
|
398
|
-
if (typeof record.chatModel === "function") return (record.chatModel as (id: string) => unknown)(modelID)
|
|
399
|
-
if (typeof record.languageModel === "function") return (record.languageModel as (id: string) => unknown)(apiID)
|
|
400
|
-
if (typeof record.chat === "function") return (record.chat as (id: string) => unknown)(apiID)
|
|
401
|
-
if (typeof record.responses === "function") return (record.responses as (id: string) => unknown)(apiID)
|
|
402
|
-
}
|
|
403
|
-
throw new Error(`Unable to instantiate model "${modelID}"`)
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export function supportsTemperature(providerID: string, modelID: string, model?: ProviderModelInfo): boolean {
|
|
407
|
-
if (typeof model?.capabilities?.temperature === "boolean") return model.capabilities.temperature
|
|
408
|
-
if (providerID !== "openai") return true
|
|
409
|
-
if (modelID.startsWith("o1") || modelID.startsWith("o3") || modelID.startsWith("o4-mini")) return false
|
|
410
|
-
return !(modelID.startsWith("gpt-5") && !modelID.startsWith("gpt-5-chat"))
|
|
411
|
-
}
|
package/src/translator/retry.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { normalizeReason } from "../constants"
|
|
2
|
-
|
|
3
|
-
function getStatus(error: unknown): number | undefined {
|
|
4
|
-
if (!error || typeof error !== "object") return undefined
|
|
5
|
-
const record = error as Record<string, unknown>
|
|
6
|
-
if (typeof record.status === "number") return record.status
|
|
7
|
-
if (typeof record.statusCode === "number") return record.statusCode
|
|
8
|
-
const response = record.response
|
|
9
|
-
if (response && typeof response === "object") {
|
|
10
|
-
const status = (response as Record<string, unknown>).status
|
|
11
|
-
if (typeof status === "number") return status
|
|
12
|
-
}
|
|
13
|
-
return undefined
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function getRetryAfterMs(error: unknown): number {
|
|
17
|
-
if (!error || typeof error !== "object") return 2000
|
|
18
|
-
const response = (error as Record<string, unknown>).response
|
|
19
|
-
if (!response || typeof response !== "object") return 2000
|
|
20
|
-
const headers = (response as { headers?: Headers }).headers
|
|
21
|
-
if (!(headers instanceof Headers)) return 2000
|
|
22
|
-
const retryAfter = headers.get("retry-after")
|
|
23
|
-
if (!retryAfter) return 2000
|
|
24
|
-
const seconds = Number(retryAfter)
|
|
25
|
-
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)
|
|
26
|
-
const date = Date.parse(retryAfter)
|
|
27
|
-
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : 2000
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isRetryable(error: unknown): boolean {
|
|
31
|
-
const status = getStatus(error)
|
|
32
|
-
if (status === 429) return true
|
|
33
|
-
if (status !== undefined) return status >= 500
|
|
34
|
-
const message = normalizeReason(error).toLowerCase()
|
|
35
|
-
return (
|
|
36
|
-
message.includes("network") ||
|
|
37
|
-
message.includes("fetch") ||
|
|
38
|
-
message.includes("timeout") ||
|
|
39
|
-
message.includes("socket") ||
|
|
40
|
-
message.includes("econn")
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function withRetry<T>(task: () => Promise<T>, sleepImpl: (ms: number) => Promise<void>): Promise<T> {
|
|
45
|
-
let lastError: unknown
|
|
46
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
47
|
-
try {
|
|
48
|
-
return await task()
|
|
49
|
-
} catch (error) {
|
|
50
|
-
lastError = error
|
|
51
|
-
if (!isRetryable(error)) throw error
|
|
52
|
-
if (getStatus(error) === 429) {
|
|
53
|
-
if (attempt >= 1) throw error
|
|
54
|
-
await sleepImpl(getRetryAfterMs(error))
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
if (attempt >= 2) throw error
|
|
58
|
-
await sleepImpl(attempt === 0 ? 500 : 1500)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
throw lastError
|
|
62
|
-
}
|