mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den): add org-managed llm provider library (#1343)
* feat(den): add org-managed llm provider library Let Den admins curate shared providers and models with encrypted credentials, then let the app connect through the existing add-provider flow. This keeps org-wide model access consistent without requiring per-user OAuth setup. * docs(den): prefer longer db encryption keys * fix(den): pass db encryption key through local dev --------- Co-authored-by: src-opn <src-opn@users.noreply.github.com>
This commit is contained in:
143
ee/apps/den-api/src/llm/models-dev.ts
Normal file
143
ee/apps/den-api/src/llm/models-dev.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
const MODELS_DEV_API_URL = "https://models.dev/api.json"
|
||||
const MODELS_DEV_CACHE_TTL_MS = 1000 * 60 * 10
|
||||
|
||||
type JsonRecord = Record<string, unknown>
|
||||
|
||||
export type ModelsDevProviderSummary = {
|
||||
id: string
|
||||
name: string
|
||||
npm: string | null
|
||||
env: string[]
|
||||
doc: string | null
|
||||
api: string | null
|
||||
modelCount: number
|
||||
}
|
||||
|
||||
export type ModelsDevModel = {
|
||||
id: string
|
||||
name: string
|
||||
config: JsonRecord
|
||||
}
|
||||
|
||||
export type ModelsDevProvider = {
|
||||
id: string
|
||||
name: string
|
||||
npm: string | null
|
||||
env: string[]
|
||||
doc: string | null
|
||||
api: string | null
|
||||
config: JsonRecord
|
||||
models: ModelsDevModel[]
|
||||
}
|
||||
|
||||
let modelsDevCache:
|
||||
| {
|
||||
expiresAt: number
|
||||
providers: ModelsDevProvider[]
|
||||
providersById: Map<string, ModelsDevProvider>
|
||||
}
|
||||
| null = null
|
||||
|
||||
function isRecord(value: unknown): value is JsonRecord {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function asString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : null
|
||||
}
|
||||
|
||||
function asStringList(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
||||
: []
|
||||
}
|
||||
|
||||
async function loadModelsDevCatalog() {
|
||||
if (modelsDevCache && modelsDevCache.expiresAt > Date.now()) {
|
||||
return modelsDevCache
|
||||
}
|
||||
|
||||
const response = await fetch(MODELS_DEV_API_URL, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"User-Agent": "OpenWork Den API",
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`models.dev returned ${response.status}`)
|
||||
}
|
||||
|
||||
const payload = await response.json()
|
||||
if (!isRecord(payload)) {
|
||||
throw new Error("models.dev returned an invalid payload")
|
||||
}
|
||||
|
||||
const providers = Object.entries(payload)
|
||||
.map(([providerKey, rawProvider]) => {
|
||||
if (!isRecord(rawProvider)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const providerId = asString(rawProvider.id) ?? providerKey
|
||||
const name = asString(rawProvider.name) ?? providerId
|
||||
const modelsRecord = isRecord(rawProvider.models) ? rawProvider.models : {}
|
||||
const { models: _models, ...providerConfig } = rawProvider
|
||||
const models = Object.entries(modelsRecord)
|
||||
.map(([modelKey, rawModel]) => {
|
||||
if (!isRecord(rawModel)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const modelId = asString(rawModel.id) ?? modelKey
|
||||
const modelName = asString(rawModel.name) ?? modelId
|
||||
return {
|
||||
id: modelId,
|
||||
name: modelName,
|
||||
config: rawModel,
|
||||
} satisfies ModelsDevModel
|
||||
})
|
||||
.filter((entry): entry is ModelsDevModel => entry !== null)
|
||||
.sort((left, right) => left.name.localeCompare(right.name))
|
||||
|
||||
return {
|
||||
id: providerId,
|
||||
name,
|
||||
npm: asString(rawProvider.npm),
|
||||
env: asStringList(rawProvider.env),
|
||||
doc: asString(rawProvider.doc),
|
||||
api: asString(rawProvider.api),
|
||||
config: providerConfig,
|
||||
models,
|
||||
} satisfies ModelsDevProvider
|
||||
})
|
||||
.filter((entry): entry is ModelsDevProvider => entry !== null)
|
||||
.sort((left, right) => left.name.localeCompare(right.name))
|
||||
|
||||
const nextCache = {
|
||||
expiresAt: Date.now() + MODELS_DEV_CACHE_TTL_MS,
|
||||
providers,
|
||||
providersById: new Map(providers.map((provider) => [provider.id, provider])),
|
||||
}
|
||||
|
||||
modelsDevCache = nextCache
|
||||
return nextCache
|
||||
}
|
||||
|
||||
export async function listModelsDevProviders(): Promise<ModelsDevProviderSummary[]> {
|
||||
const catalog = await loadModelsDevCatalog()
|
||||
return catalog.providers.map((provider) => ({
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
npm: provider.npm,
|
||||
env: provider.env,
|
||||
doc: provider.doc,
|
||||
api: provider.api,
|
||||
modelCount: provider.models.length,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getModelsDevProvider(providerId: string): Promise<ModelsDevProvider | null> {
|
||||
const catalog = await loadModelsDevCatalog()
|
||||
return catalog.providersById.get(providerId) ?? null
|
||||
}
|
||||
Reference in New Issue
Block a user