docs(rfc): propose SDK developer project tooling feat: rename / docs ci: fix windows gates docs: revert
112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
/**
|
|
* Required hand-rolled DeepSeek and custom pi-ai provider behavior.
|
|
*
|
|
* @module @deepseek-ai/dsh-helper/features/builtin/provider
|
|
*/
|
|
|
|
import { JsExpression } from '../../documents/cordis-yaml-file.ts'
|
|
import { featureId } from '../../ids.ts'
|
|
import type { FeatureSelection, ProjectProfile } from '../../project/types.ts'
|
|
import {
|
|
FeatureOption,
|
|
ExclusiveOptionFeature,
|
|
type FeatureProjectView,
|
|
} from '../feature.ts'
|
|
import { ProjectContribution } from '../resources.ts'
|
|
import { npmCordisConfigEntry, environment } from './helpers.ts'
|
|
|
|
const ID = featureId('provider')
|
|
const DEFAULT_MODEL = 'deepseek-v4-flash'
|
|
const API_KEY_COMMENT = 'Required before start; an empty value makes provider startup fail.'
|
|
|
|
class DeepSeekOption extends FeatureOption {
|
|
override readonly id = 'deepseek'
|
|
override readonly label = 'DeepSeek'
|
|
override readonly secrets = [{
|
|
id: 'apiKey',
|
|
environment: 'DEEPSEEK_API_KEY',
|
|
message: 'DeepSeek API key',
|
|
required: true,
|
|
}]
|
|
|
|
override contribution(_profile: ProjectProfile, secrets: Readonly<Record<string, string>>): ProjectContribution {
|
|
return new ProjectContribution([
|
|
...npmCordisConfigEntry(ID, {
|
|
id: 'llm-deepseek',
|
|
name: '@deepseek-ai/dsh-llm-deepseek',
|
|
config: { apiKey: new JsExpression('process.env.DEEPSEEK_API_KEY') },
|
|
}, ['apiKey', 'baseURL', 'models']),
|
|
environment(ID, 'DEEPSEEK_API_KEY', secrets.apiKey, API_KEY_COMMENT),
|
|
])
|
|
}
|
|
}
|
|
|
|
class CustomOption extends FeatureOption {
|
|
override readonly id = 'custom'
|
|
override readonly label = 'Custom endpoint (pi-ai)'
|
|
override readonly secrets = [{
|
|
id: 'apiKey',
|
|
environment: 'DEEPSEEK_API_KEY',
|
|
message: 'Custom provider API key',
|
|
required: true,
|
|
}]
|
|
override readonly inputs = [{
|
|
id: 'baseURL',
|
|
message: 'Custom provider base URL',
|
|
}]
|
|
|
|
override contribution(_profile: ProjectProfile, secrets: Readonly<Record<string, string>>): ProjectContribution {
|
|
return new ProjectContribution([
|
|
...npmCordisConfigEntry(ID, {
|
|
id: 'llm-pi-ai',
|
|
name: '@deepseek-ai/dsh-llm-pi-ai',
|
|
config: { apiKey: new JsExpression('process.env.DEEPSEEK_API_KEY') },
|
|
}, ['apiKey', 'baseURL', 'models']),
|
|
environment(ID, 'DEEPSEEK_API_KEY', secrets.apiKey, API_KEY_COMMENT),
|
|
])
|
|
}
|
|
}
|
|
|
|
/** Required provider feature with DeepSeek and custom pi-ai options. */
|
|
export class ProviderFeature extends ExclusiveOptionFeature {
|
|
override readonly id = ID
|
|
override readonly summary = 'Model provider'
|
|
override readonly required = true
|
|
override readonly options = [new DeepSeekOption(), new CustomOption()]
|
|
|
|
/** Prefer the hand-rolled adapter and its public endpoint defaults. */
|
|
override defaultOptions(): readonly string[] {
|
|
return ['deepseek']
|
|
}
|
|
|
|
/** Recover literal endpoint overrides from either provider entry. */
|
|
override readSelection(project: FeatureProjectView, selection: FeatureSelection): FeatureSelection {
|
|
const base = super.readSelection(project, selection)
|
|
const entry = project.cordisConfigEntries().find(item => item.id === 'llm-deepseek' || item.id === 'llm-pi-ai')
|
|
const baseURL = entry?.config?.baseURL
|
|
return typeof baseURL === 'string' ? { ...base, values: { baseURL } } : base
|
|
}
|
|
|
|
/** Apply explicit endpoint/model overrides while omitting provider defaults. */
|
|
override contribution(selection: FeatureSelection, profile: ProjectProfile): ProjectContribution {
|
|
const contribution = super.contribution(selection, profile)
|
|
const baseURL = selection.values?.baseURL
|
|
if (baseURL !== undefined && typeof baseURL !== 'string') throw new Error('provider baseURL must be a string')
|
|
return new ProjectContribution(contribution.resources.map((resource) => {
|
|
if (resource.kind !== 'cordis-config-entry' || (resource.entry.id !== 'llm-deepseek'
|
|
&& resource.entry.id !== 'llm-pi-ai')) return resource
|
|
return {
|
|
...resource,
|
|
entry: {
|
|
...resource.entry,
|
|
config: {
|
|
...resource.entry.config,
|
|
...baseURL ? { baseURL } : {},
|
|
...profile.runtime.model === DEFAULT_MODEL ? {} : { models: [profile.runtime.model] },
|
|
},
|
|
},
|
|
}
|
|
}))
|
|
}
|
|
}
|