Compare commits
2 commits
dev
...
refactor/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93393e7086 | ||
|
|
4c1482c123 |
1 changed files with 29 additions and 58 deletions
|
|
@ -3,13 +3,13 @@ import fuzzysort from "fuzzysort"
|
||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
|
import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
|
||||||
import { NoSuchModelError, type Provider as SDK } from "ai"
|
import { NoSuchModelError, type Provider as SDK } from "ai"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import { Log } from "@opencode-ai/core/util/log"
|
||||||
import { Npm } from "@opencode-ai/core/npm"
|
import { Npm } from "@opencode-ai/core/npm"
|
||||||
import { Hash } from "@opencode-ai/core/util/hash"
|
import { Hash } from "@opencode-ai/core/util/hash"
|
||||||
import { Plugin } from "../plugin"
|
import { Plugin } from "../plugin"
|
||||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
||||||
import * as ModelsDev from "@opencode-ai/core/models-dev"
|
import { ModelsDev } from "@opencode-ai/core/models-dev"
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
import { Env } from "../env"
|
import { Env } from "../env"
|
||||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||||
|
|
@ -24,7 +24,7 @@ import { EffectPromise } from "@/effect/promise"
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { isRecord } from "@/util/record"
|
import { isRecord } from "@/util/record"
|
||||||
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||||
import * as ProviderTransform from "./transform"
|
import { ProviderTransform } from "./transform"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { ModelStatus } from "./model-status"
|
import { ModelStatus } from "./model-status"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
@ -32,11 +32,6 @@ import { ProviderError } from "./error"
|
||||||
|
|
||||||
const log = Log.create({ service: "provider" })
|
const log = Log.create({ service: "provider" })
|
||||||
const OPENAI_HEADER_TIMEOUT_DEFAULT = 10_000
|
const OPENAI_HEADER_TIMEOUT_DEFAULT = 10_000
|
||||||
function shouldUseCopilotResponsesApi(modelID: string): boolean {
|
|
||||||
const match = /^gpt-(\d+)/.exec(modelID)
|
|
||||||
if (!match) return false
|
|
||||||
return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
||||||
if (typeof ms !== "number" || ms <= 0) return res
|
if (typeof ms !== "number" || ms <= 0) return res
|
||||||
|
|
@ -152,10 +147,6 @@ type CustomDep = {
|
||||||
get: (key: string) => Effect.Effect<string | undefined>
|
get: (key: string) => Effect.Effect<string | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
function useLanguageModel(sdk: any) {
|
|
||||||
return sdk.responses === undefined && sdk.chat === undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectAzureLanguageModel(sdk: any, modelID: string, useChat: boolean) {
|
function selectAzureLanguageModel(sdk: any, modelID: string, useChat: boolean) {
|
||||||
if (useChat && sdk.chat) return sdk.chat(modelID)
|
if (useChat && sdk.chat) return sdk.chat(modelID)
|
||||||
if (sdk.responses) return sdk.responses(modelID)
|
if (sdk.responses) return sdk.responses(modelID)
|
||||||
|
|
@ -218,8 +209,10 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||||
Effect.succeed({
|
Effect.succeed({
|
||||||
autoload: false,
|
autoload: false,
|
||||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||||
if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
|
if (sdk.responses === undefined && sdk.chat === undefined) return sdk.languageModel(modelID)
|
||||||
return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID)
|
const match = /^gpt-(\d+)/.exec(modelID)
|
||||||
|
if (match && Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")) return sdk.responses(modelID)
|
||||||
|
return sdk.chat(modelID)
|
||||||
},
|
},
|
||||||
options: {},
|
options: {},
|
||||||
}),
|
}),
|
||||||
|
|
@ -582,11 +575,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||||
const instanceUrl = (yield* dep.get("GITLAB_INSTANCE_URL")) || "https://gitlab.com"
|
const instanceUrl = (yield* dep.get("GITLAB_INSTANCE_URL")) || "https://gitlab.com"
|
||||||
|
|
||||||
const auth = yield* dep.auth(input.id)
|
const auth = yield* dep.auth(input.id)
|
||||||
const apiKey = yield* Effect.sync(() => {
|
const apiKey = auth?.type === "oauth" ? auth.access : auth?.type === "api" ? auth.key : undefined
|
||||||
if (auth?.type === "oauth") return auth.access
|
|
||||||
if (auth?.type === "api") return auth.key
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
const token = apiKey ?? (yield* dep.get("GITLAB_TOKEN"))
|
const token = apiKey ?? (yield* dep.get("GITLAB_TOKEN"))
|
||||||
|
|
||||||
const providerConfig = (yield* dep.config()).provider?.["gitlab"]
|
const providerConfig = (yield* dep.config()).provider?.["gitlab"]
|
||||||
|
|
@ -734,12 +723,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = yield* Effect.gen(function* () {
|
const apiKey = env["CLOUDFLARE_API_KEY"] || (auth?.type === "api" ? auth.key : undefined)
|
||||||
const envToken = env["CLOUDFLARE_API_KEY"]
|
|
||||||
if (envToken) return envToken
|
|
||||||
if (auth?.type === "api") return auth.key
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
autoload: !!apiKey,
|
autoload: !!apiKey,
|
||||||
|
|
@ -785,12 +769,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get API token from env or auth - required for authenticated gateways
|
// Get API token from env or auth - required for authenticated gateways
|
||||||
const apiToken = yield* Effect.gen(function* () {
|
const apiToken =
|
||||||
const envToken = env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"]
|
env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"] || (auth?.type === "api" ? auth.key : undefined)
|
||||||
if (envToken) return envToken
|
|
||||||
if (auth?.type === "api") return auth.key
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!apiToken) {
|
if (!apiToken) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -1171,18 +1151,15 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function suggestionModelIDs(provider: Info | undefined, enableExperimentalModels: boolean) {
|
function modelSuggestions(provider: Info | undefined, modelID: ProviderV2.ModelID, enableExperimentalModels: boolean) {
|
||||||
if (!provider) return []
|
const available = provider
|
||||||
return Object.keys(provider.models).filter((id) => {
|
? Object.keys(provider.models).filter((id) => {
|
||||||
const model = provider.models[id]
|
const model = provider.models[id]
|
||||||
if (model.status === "deprecated") return false
|
if (model.status === "deprecated") return false
|
||||||
if (model.status === "alpha" && !enableExperimentalModels) return false
|
if (model.status === "alpha" && !enableExperimentalModels) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
: []
|
||||||
|
|
||||||
function modelSuggestions(provider: Info | undefined, modelID: ProviderV2.ModelID, enableExperimentalModels: boolean) {
|
|
||||||
const available = suggestionModelIDs(provider, enableExperimentalModels)
|
|
||||||
const fuzzy = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 }).map((m) => m.target)
|
const fuzzy = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 }).map((m) => m.target)
|
||||||
if (fuzzy.length) return fuzzy
|
if (fuzzy.length) return fuzzy
|
||||||
const query = modelID
|
const query = modelID
|
||||||
|
|
@ -1681,15 +1658,10 @@ export const layer = Layer.effect(
|
||||||
return loaded as SDK
|
return loaded as SDK
|
||||||
}
|
}
|
||||||
|
|
||||||
let installedPath: string
|
const local = model.api.npm.startsWith("file://")
|
||||||
if (!model.api.npm.startsWith("file://")) {
|
if (local) log.info("loading local provider", { pkg: model.api.npm })
|
||||||
const item = await Npm.add(model.api.npm)
|
const installedPath = local ? model.api.npm : (await Npm.add(model.api.npm)).entrypoint
|
||||||
if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
|
if (!installedPath) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
|
||||||
installedPath = item.entrypoint
|
|
||||||
} else {
|
|
||||||
log.info("loading local provider", { pkg: model.api.npm })
|
|
||||||
installedPath = model.api.npm
|
|
||||||
}
|
|
||||||
|
|
||||||
// `installedPath` is a local entry path or an existing `file://` URL. Normalize
|
// `installedPath` is a local entry path or an existing `file://` URL. Normalize
|
||||||
// only path inputs so Node on Windows accepts the dynamic import.
|
// only path inputs so Node on Windows accepts the dynamic import.
|
||||||
|
|
@ -1788,7 +1760,7 @@ export const layer = Layer.effect(
|
||||||
const provider = s.providers[providerID]
|
const provider = s.providers[providerID]
|
||||||
if (!provider) return undefined
|
if (!provider) return undefined
|
||||||
|
|
||||||
let priority = [
|
const defaultPriority = [
|
||||||
"claude-haiku-4-5",
|
"claude-haiku-4-5",
|
||||||
"claude-haiku-4.5",
|
"claude-haiku-4.5",
|
||||||
"3-5-haiku",
|
"3-5-haiku",
|
||||||
|
|
@ -1797,12 +1769,11 @@ export const layer = Layer.effect(
|
||||||
"gemini-2.5-flash",
|
"gemini-2.5-flash",
|
||||||
"gpt-5-nano",
|
"gpt-5-nano",
|
||||||
]
|
]
|
||||||
if (providerID.startsWith("opencode")) {
|
const priority = providerID.startsWith("opencode")
|
||||||
priority = ["gpt-5-nano"]
|
? ["gpt-5-nano"]
|
||||||
}
|
: providerID.startsWith("github-copilot")
|
||||||
if (providerID.startsWith("github-copilot")) {
|
? ["gpt-5-mini", "claude-haiku-4.5", ...defaultPriority]
|
||||||
priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
|
: defaultPriority
|
||||||
}
|
|
||||||
for (const item of priority) {
|
for (const item of priority) {
|
||||||
if (providerID === ProviderV2.ID.amazonBedrock) {
|
if (providerID === ProviderV2.ID.amazonBedrock) {
|
||||||
const crossRegionPrefixes = ["global.", "us.", "eu."]
|
const crossRegionPrefixes = ["global.", "us.", "eu."]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue