feat(web-search-deepseek): resolve provider options from the settings section
The provider now takes a thunk rather than a value: it projects the
authoritative section per search, so a stored endpoint, model, or key
reference reaches the next call without re-registering the provider — which
would make the seam's provider selection observable as a flicker.
apiKey already carries role('secret'), so the section is safe to describe:
the literal never rides a response in any layer and a configuration surface
learns only that a key is set.
This commit is contained in:
@@ -9,6 +9,7 @@ import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import type {} from '@deepseek-ai/dsh-agent'
|
||||
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
||||
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
import { environmentOf } from '@deepseek-ai/dsh-environment'
|
||||
import type {} from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-web'
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
DEEPSEEK_DEFAULT_MAX_USES,
|
||||
DEEPSEEK_DEFAULT_MODEL,
|
||||
} from './provider.ts'
|
||||
import type { DeepSeekSearchProviderOptions } from './provider.ts'
|
||||
|
||||
export {
|
||||
DeepSeekSearchProvider,
|
||||
@@ -76,15 +78,23 @@ export const Config: z<Config> = z.object({
|
||||
*/
|
||||
const SEARCH_BASE_URL_ENV = 'DEEPSEEK_SEARCH_BASE_URL'
|
||||
|
||||
/** Register the DeepSeek search provider with `ctx.web`. */
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
const maxTokens = config.maxTokens ?? DEEPSEEK_DEFAULT_MAX_TOKENS
|
||||
const maxUses = config.maxUses ?? DEEPSEEK_DEFAULT_MAX_USES
|
||||
/** Settings namespace carrying this provider's endpoint, model, and key reference. */
|
||||
export const WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE = settingsNamespace('web-search-deepseek')
|
||||
|
||||
/**
|
||||
* Project one resolved section into the options the provider serves its next
|
||||
* search with. Environment fallbacks stay here rather than in the provider:
|
||||
* every value it reads is already fully defaulted.
|
||||
* @param ctx - plugin context supplying the credential and environment planes.
|
||||
* @param config - the currently authoritative section.
|
||||
* @returns options for one search.
|
||||
*/
|
||||
function resolveOptions(ctx: Context, config: Config): DeepSeekSearchProviderOptions {
|
||||
const apiKeyEnv = credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV)
|
||||
const literalApiKey = config.apiKey !== undefined && config.apiKey.length > 0
|
||||
? config.apiKey
|
||||
: undefined
|
||||
ctx.web.registerSearchProvider(new DeepSeekSearchProvider({
|
||||
return {
|
||||
...literalApiKey === undefined ? {} : { apiKey: literalApiKey },
|
||||
resolveApiKey: async () => {
|
||||
const credentials = ctx.get('credentials')
|
||||
@@ -99,13 +109,27 @@ export function apply(ctx: Context, config: Config): void {
|
||||
?? DEEPSEEK_DEFAULT_BASE_URL,
|
||||
model: config.model ?? DEEPSEEK_DEFAULT_MODEL,
|
||||
apiVersion: config.apiVersion ?? DEEPSEEK_DEFAULT_API_VERSION,
|
||||
maxTokens,
|
||||
maxUses,
|
||||
maxTokens: config.maxTokens ?? DEEPSEEK_DEFAULT_MAX_TOKENS,
|
||||
maxUses: config.maxUses ?? DEEPSEEK_DEFAULT_MAX_USES,
|
||||
recordRequest: (request) => {
|
||||
ctx.get('agents')?.currentInitiator()?.session.append(
|
||||
'web/deepseek-search-llm-request',
|
||||
request,
|
||||
)
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/** Register the DeepSeek search provider with `ctx.web`. */
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
let current: () => Config = () => config
|
||||
installSettingsSection(ctx, WEB_SEARCH_DEEPSEEK_SETTINGS_NAMESPACE, Config, config, {
|
||||
setSource: (source) => {
|
||||
current = source
|
||||
},
|
||||
// The registration carries no resolved value: the provider projects the
|
||||
// section per search, so a committed change needs no re-registration.
|
||||
onChange: () => {},
|
||||
})
|
||||
ctx.web.registerSearchProvider(new DeepSeekSearchProvider(() => resolveOptions(ctx, current())))
|
||||
}
|
||||
|
||||
@@ -177,7 +177,18 @@ export function mapAnthropicResponse(response: AnthropicResponse): WebSearchResu
|
||||
export class DeepSeekSearchProvider implements WebSearchProvider {
|
||||
readonly id = DEEPSEEK_PROVIDER_ID
|
||||
|
||||
constructor(private readonly options: DeepSeekSearchProviderOptions) {}
|
||||
/**
|
||||
* @param resolveOptions - the options for the NEXT operation. A thunk rather
|
||||
* than a value because the plugin's settings section can change between
|
||||
* searches, and re-registering the provider to carry a new endpoint would
|
||||
* make the seam's selection observable to the user as a flicker.
|
||||
*/
|
||||
constructor(private readonly resolveOptions: () => DeepSeekSearchProviderOptions) {}
|
||||
|
||||
/** Options resolved per read, so a committed settings change reaches the next search. */
|
||||
private get options(): DeepSeekSearchProviderOptions {
|
||||
return this.resolveOptions()
|
||||
}
|
||||
|
||||
available(): boolean {
|
||||
return ((this.options.apiKey?.length ?? 0) > 0 || this.options.resolveApiKey !== undefined)
|
||||
|
||||
Reference in New Issue
Block a user