Merge updated origin/master into feat/tui-master-port

This commit is contained in:
Tianyi Cui
2026-07-27 23:55:53 +08:00
118 changed files with 2540 additions and 848 deletions

View File

@@ -26,6 +26,7 @@ import type {
LlmModelInfo,
LlmResolvedModelInfo,
ReasoningEffortId as ReasoningEffortIdType,
ResolvedRetryPolicy,
StreamChunk,
} from '@deepseek-ai/dsh-llm'
import { idleWatchdog, timeoutOf } from '@deepseek-ai/dsh-timeout'
@@ -44,7 +45,10 @@ export interface PiAiAdapterOptions {
* Resolve a catalog model dynamically and apply only the configured endpoint
* override, preserving the catalog's API/capability/compatibility metadata.
*/
function resolvePiModel(profile: PiAiProviderProfile, modelId: string): Model<Api> {
function resolvePiModel(
profile: Omit<PiAiProviderProfile, 'retryPolicy'>,
modelId: string,
): Model<Api> {
const model = getBuiltinModels(profile.provider as BuiltinProvider).find(candidate => candidate.id === modelId) as Model<Api> | undefined
if (model === undefined) {
throw new LlmError(`pi-ai provider "${profile.provider}" has no catalog model "${modelId}"`, 'UNKNOWN_MODEL')
@@ -54,7 +58,7 @@ function resolvePiModel(profile: PiAiProviderProfile, modelId: string): Model<Ap
/** Copy profile stream knobs into pi-ai's common option vocabulary. */
function profileOptions(
profile: PiAiProviderProfile,
profile: Omit<PiAiProviderProfile, 'retryPolicy'>,
reasoning: ModelThinkingLevel | undefined,
): SimpleStreamOptions {
const enabledReasoning: ThinkingLevel | undefined = reasoning === 'off' ? undefined : reasoning
@@ -107,6 +111,10 @@ export class PiAiAdapter extends LlmAdapter {
this.profiles = new Map(resolveProfiles(options.profiles).map(profile => [profile.provider, profile]))
}
override providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined {
return this.profiles.get(provider)?.retryPolicy
}
override listModels(provider: string): Promise<readonly LlmModelInfo[]> {
const profile = this.profiles.get(provider)
if (profile === undefined) {

View File

@@ -8,6 +8,8 @@ import { getBuiltinProviders } from '@earendil-works/pi-ai/providers/all'
import type { CacheRetention, ModelThinkingLevel, ThinkingBudgets, Transport } from '@earendil-works/pi-ai'
import z from 'schemastery'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { resolveRetryPolicy, RetryPolicySchema } from '@deepseek-ai/dsh-llm'
import type { ResolvedRetryPolicy, RetryPolicyConfig } from '@deepseek-ai/dsh-llm'
/** Default maximum idle interval while an adapter stream read is outstanding. */
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000
@@ -36,12 +38,16 @@ export interface PiAiProviderProfile {
websocketConnectTimeoutMs?: number
/** Maximum provider idle time while one stream read is outstanding. */
streamIdleTimeoutMs?: number
/** Provider-owned model-request retry policy; omission uses normal defaults. */
retryPolicy?: RetryPolicyConfig
}
/** Validated profile with every adapter-owned default resolved. */
export interface ResolvedPiAiProviderProfile extends PiAiProviderProfile {
export interface ResolvedPiAiProviderProfile extends Omit<PiAiProviderProfile, 'retryPolicy'> {
/** Positive finite provider-idle interval after defaulting. */
streamIdleTimeoutMs: number
/** Immutable retry policy captured with this provider route. */
retryPolicy: ResolvedRetryPolicy
}
/** Plugin configuration: the non-empty provider profiles this instance owns. */
@@ -69,6 +75,7 @@ const profile = z.object({
timeoutMs: z.natural(),
websocketConnectTimeoutMs: z.natural(),
streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
retryPolicy: RetryPolicySchema,
})
/** Runtime schema for {@link Config}. */
@@ -115,6 +122,10 @@ export function resolveProfiles(profiles: readonly PiAiProviderProfile[]): Resol
return {
...source,
streamIdleTimeoutMs,
retryPolicy: resolveRetryPolicy(
source.retryPolicy,
`llm-pi-ai: provider "${source.provider}" retryPolicy`,
),
...source.headers === undefined ? {} : { headers: { ...source.headers } },
...source.thinkingBudgets === undefined ? {} : { thinkingBudgets: { ...source.thinkingBudgets } },
}

View File

@@ -10,6 +10,9 @@
* providers:
* - provider: openai
* apiKey: !!js process.env.OPENAI_API_KEY
* retryPolicy:
* mode: normal
* maxRetries: 2
* - provider: anthropic
* apiKey: !!js process.env.ANTHROPIC_API_KEY
* - provider: openrouter
@@ -36,6 +39,6 @@ export const inject = ['llm']
/** Register one generic pi-ai adapter for all configured provider routes. */
export function apply(ctx: Context, config: Config): void {
const profiles = resolveProfiles(config.providers)
const adapter = new PiAiAdapter({ profiles })
const adapter = new PiAiAdapter({ profiles: config.providers })
ctx.llm.registerAdapter(profiles.map(entry => entry.provider), adapter)
}