|
@@ -1,7 +1,9 @@
|
|
|
/**
|
|
/**
|
|
|
* Configuration schema and provider-profile validation for the pi-ai adapter.
|
|
* Configuration schema and provider-profile validation for the pi-ai adapter.
|
|
|
* Profiles are a dict keyed by provider route, so the composition base and a
|
|
* Profiles are a dict keyed by provider route, so the composition base and a
|
|
|
- * user-settings layer merge per provider and the route set is structural.
|
|
|
|
|
|
|
+ * user-settings layer merge per provider and the route set is structural. An
|
|
|
|
|
+ * adapter-level retry default lets a deployment choose one policy for every
|
|
|
|
|
+ * route that does not override it without changing the core omission default.
|
|
|
*
|
|
*
|
|
|
* A route key is not required to name an installed pi-ai provider. When it does,
|
|
* A route key is not required to name an installed pi-ai provider. When it does,
|
|
|
* that provider's endpoint, protocol, display name, and model catalog are the
|
|
* that provider's endpoint, protocol, display name, and model catalog are the
|
|
@@ -136,7 +138,7 @@ export interface PiAiProviderProfile {
|
|
|
websocketConnectTimeoutMs?: number
|
|
websocketConnectTimeoutMs?: number
|
|
|
/** Maximum provider idle time while one stream read is outstanding. */
|
|
/** Maximum provider idle time while one stream read is outstanding. */
|
|
|
streamIdleTimeoutMs?: number
|
|
streamIdleTimeoutMs?: number
|
|
|
- /** Provider-owned model-request retry policy; omission uses normal defaults. */
|
|
|
|
|
|
|
+ /** Provider-owned model-request retry policy; omission inherits the adapter default, then normal defaults. */
|
|
|
retryPolicy?: RetryPolicyConfig
|
|
retryPolicy?: RetryPolicyConfig
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -168,8 +170,13 @@ export interface ResolvedPiAiProviderProfile
|
|
|
configuredMaxTokens: ReadonlyMap<string, number>
|
|
configuredMaxTokens: ReadonlyMap<string, number>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** Plugin configuration: the provider routes this instance owns. */
|
|
|
|
|
|
|
+/** Plugin configuration: the provider routes this instance owns and their shared defaults. */
|
|
|
export interface Config {
|
|
export interface Config {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Retry policy inherited by every provider profile that omits its own
|
|
|
|
|
+ * `retryPolicy`; omission here uses the bounded normal defaults.
|
|
|
|
|
+ */
|
|
|
|
|
+ defaultRetryPolicy?: RetryPolicyConfig
|
|
|
/**
|
|
/**
|
|
|
* pi-ai provider routes, keyed by provider. An empty (or omitted) dict is
|
|
* pi-ai provider routes, keyed by provider. An empty (or omitted) dict is
|
|
|
* the dormant settings-driven posture: the adapter mounts with no routes
|
|
* the dormant settings-driven posture: the adapter mounts with no routes
|
|
@@ -253,6 +260,7 @@ const profile = z.object({
|
|
|
|
|
|
|
|
/** Runtime schema for {@link Config}. */
|
|
/** Runtime schema for {@link Config}. */
|
|
|
export const Config: z<Config> = z.object({
|
|
export const Config: z<Config> = z.object({
|
|
|
|
|
+ defaultRetryPolicy: RetryPolicySchema,
|
|
|
providers: z.dict(profile).default({}),
|
|
providers: z.dict(profile).default({}),
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -269,7 +277,7 @@ export const Config: z<Config> = z.object({
|
|
|
* @throws Error naming the route and model that cannot be served.
|
|
* @throws Error naming the route and model that cannot be served.
|
|
|
*/
|
|
*/
|
|
|
export function assertServiceable(config: Config): void {
|
|
export function assertServiceable(config: Config): void {
|
|
|
- resolveProfiles(config.providers)
|
|
|
|
|
|
|
+ resolveProfiles(config.providers, config.defaultRetryPolicy)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Reject removed pre-release profile fields and name their replacements. */
|
|
/** Reject removed pre-release profile fields and name their replacements. */
|
|
@@ -294,16 +302,23 @@ function rejectRemovedFields(provider: string, source: PiAiProviderProfile): voi
|
|
|
* Validate profiles and return a detached route-keyed map suitable for
|
|
* Validate profiles and return a detached route-keyed map suitable for
|
|
|
* per-request reads. This is the one explicit resolve step, so an omitted dict
|
|
* per-request reads. This is the one explicit resolve step, so an omitted dict
|
|
|
* resolves to the empty (dormant) route set here rather than through a hidden
|
|
* resolves to the empty (dormant) route set here rather than through a hidden
|
|
|
- * fallback, and each route's models and pi-ai provider are materialized once.
|
|
|
|
|
|
|
+ * fallback, and each route's models, retry policy, and pi-ai provider are
|
|
|
|
|
+ * materialized once.
|
|
|
* @param providers - configured provider profiles keyed by route.
|
|
* @param providers - configured provider profiles keyed by route.
|
|
|
|
|
+ * @param defaultRetryPolicy - adapter policy inherited by profiles that omit one.
|
|
|
* @returns validated profiles in configuration order.
|
|
* @returns validated profiles in configuration order.
|
|
|
*/
|
|
*/
|
|
|
export function resolveProfiles(
|
|
export function resolveProfiles(
|
|
|
providers: Readonly<Record<string, PiAiProviderProfile>> | undefined,
|
|
providers: Readonly<Record<string, PiAiProviderProfile>> | undefined,
|
|
|
|
|
+ defaultRetryPolicy?: RetryPolicyConfig,
|
|
|
): Map<string, ResolvedPiAiProviderProfile> {
|
|
): Map<string, ResolvedPiAiProviderProfile> {
|
|
|
if (Array.isArray(providers)) {
|
|
if (Array.isArray(providers)) {
|
|
|
throw new Error('llm-pi-ai: providers is now a dict keyed by provider route, not an array of profiles')
|
|
throw new Error('llm-pi-ai: providers is now a dict keyed by provider route, not an array of profiles')
|
|
|
}
|
|
}
|
|
|
|
|
+ const resolvedDefaultRetryPolicy = resolveRetryPolicy(
|
|
|
|
|
+ defaultRetryPolicy,
|
|
|
|
|
+ 'llm-pi-ai: defaultRetryPolicy',
|
|
|
|
|
+ )
|
|
|
const entries = Object.entries(providers ?? {})
|
|
const entries = Object.entries(providers ?? {})
|
|
|
const resolved = new Map<string, ResolvedPiAiProviderProfile>()
|
|
const resolved = new Map<string, ResolvedPiAiProviderProfile>()
|
|
|
for (const [provider, source] of entries) {
|
|
for (const [provider, source] of entries) {
|
|
@@ -354,7 +369,9 @@ export function resolveProfiles(
|
|
|
displayName,
|
|
displayName,
|
|
|
...apiKeyEnv === undefined ? {} : { apiKeyEnv: credentialRef(apiKeyEnv) },
|
|
...apiKeyEnv === undefined ? {} : { apiKeyEnv: credentialRef(apiKeyEnv) },
|
|
|
streamIdleTimeoutMs,
|
|
streamIdleTimeoutMs,
|
|
|
- retryPolicy: resolveRetryPolicy(retryPolicy, `llm-pi-ai: provider "${provider}" retryPolicy`),
|
|
|
|
|
|
|
+ retryPolicy: retryPolicy === undefined
|
|
|
|
|
+ ? resolvedDefaultRetryPolicy
|
|
|
|
|
+ : resolveRetryPolicy(retryPolicy, `llm-pi-ai: provider "${provider}" retryPolicy`),
|
|
|
...rest.headers === undefined ? {} : { headers: { ...rest.headers } },
|
|
...rest.headers === undefined ? {} : { headers: { ...rest.headers } },
|
|
|
...rest.thinkingBudgets === undefined ? {} : { thinkingBudgets: { ...rest.thinkingBudgets } },
|
|
...rest.thinkingBudgets === undefined ? {} : { thinkingBudgets: { ...rest.thinkingBudgets } },
|
|
|
configuredMaxTokens: catalog.configuredMaxTokens,
|
|
configuredMaxTokens: catalog.configuredMaxTokens,
|