|
|
@@ -19,16 +19,16 @@ import { Context, Service } from 'cordis'
|
|
|
import z from 'schemastery'
|
|
|
import type { AgentLlmTarget } from '@deepseek-ai/dsh-agent'
|
|
|
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
|
|
-import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
|
+import { installSettingsSection } from '@deepseek-ai/dsh-settings'
|
|
|
import type { ApiProxy } from './api/index.ts'
|
|
|
-import { createApiProxy } from './api-proxy.ts'
|
|
|
+import { API_GATEWAY_SETTINGS_NAMESPACE, createApiProxy } from './api-proxy.ts'
|
|
|
|
|
|
export type * from './api/index.ts'
|
|
|
export { RpcId } from './api/rpc.ts'
|
|
|
export { toFetchHandler } from './fetch/handler.ts'
|
|
|
export { AbstractApiClient, InProcessApiClient } from './fetch/client.ts'
|
|
|
export type { IApiClient } from './fetch/client.ts'
|
|
|
-export { createApiProxy } from './api-proxy.ts'
|
|
|
+export { API_GATEWAY_SETTINGS_NAMESPACE, createApiProxy } from './api-proxy.ts'
|
|
|
export type { ApiProxyDefaults } from './api-proxy.ts'
|
|
|
|
|
|
declare module 'cordis' {
|
|
|
@@ -39,17 +39,9 @@ declare module 'cordis' {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * The settings namespace carrying the user's default route. Named for the
|
|
|
- * gateway rather than for the package, because this key is what a person reads
|
|
|
- * and writes in `settings.yaml`; the row id in a composition happens to match
|
|
|
- * but does not determine it.
|
|
|
- */
|
|
|
-export const API_GATEWAY_SETTINGS_NAMESPACE = settingsNamespace('api-gateway')
|
|
|
-
|
|
|
-/**
|
|
|
- * The user-settable slice of the gateway config: the route a session starts
|
|
|
- * from when its own log names none. `workspaceRoot` is deliberately not part
|
|
|
- * of it — that is a launcher fact, not a preference.
|
|
|
+ * The `api-gateway` settings section: the route a session starts from when its
|
|
|
+ * own log names none. `workspaceRoot` is deliberately not part of it — that is
|
|
|
+ * a launcher fact, not a preference.
|
|
|
*/
|
|
|
export interface DefaultRouteSettings {
|
|
|
/** Default provider route for created agents. */
|
|
|
@@ -60,29 +52,36 @@ export interface DefaultRouteSettings {
|
|
|
reasoningEffort?: string
|
|
|
}
|
|
|
|
|
|
-/** Gateway plugin config: host-level agent routing and Workspace creation root. */
|
|
|
-export interface Config extends DefaultRouteSettings {
|
|
|
+/**
|
|
|
+ * Gateway plugin config: host-level agent routing and Workspace creation root.
|
|
|
+ *
|
|
|
+ * `reasoningEffort` is deliberately absent, so the section carries one field
|
|
|
+ * the composition cannot. The seam resolves a section by MERGING the user
|
|
|
+ * layer over the composition entry per field, and an absent key cannot
|
|
|
+ * override a present one — so a composition-set effort would survive every
|
|
|
+ * later switch to a model that has none, and strand it for the next session
|
|
|
+ * to fail on. Effort is a per-model fact anyway: a deployment default belongs
|
|
|
+ * on the adapter profile (`llm-pi-ai`'s `reasoning`, `llm-deepseek`'s own),
|
|
|
+ * which resolves per model rather than per gateway.
|
|
|
+ */
|
|
|
+export interface Config {
|
|
|
+ /** Default provider route for created agents. */
|
|
|
+ provider: string
|
|
|
+ /** Default model id. */
|
|
|
+ model: string
|
|
|
/** Parent directory for name-created Workspaces; defaults to the Host cwd. */
|
|
|
workspaceRoot?: string
|
|
|
}
|
|
|
|
|
|
-/** The config fields the settings section carries; the rest stay launcher-owned. */
|
|
|
-const DEFAULT_ROUTE_FIELDS = ['provider', 'model', 'reasoningEffort'] as const
|
|
|
-
|
|
|
/**
|
|
|
- * The settings section's schema, picked out of the plugin config rather than
|
|
|
- * restated. The config stays a plain literal because the configuration-catalog
|
|
|
- * generator reads it statically; picking from it is what keeps the section a
|
|
|
- * subset of it as both evolve.
|
|
|
- * @param config - the plugin config schema to pick from.
|
|
|
- * @returns the section schema over {@link DEFAULT_ROUTE_FIELDS}.
|
|
|
+ * Schema of the `api-gateway` section, exported because it IS that section's
|
|
|
+ * contract — the shape anything reading or writing `settings.yaml` addresses.
|
|
|
*/
|
|
|
-function defaultRouteSchema(config: z<Config>): z<DefaultRouteSettings> {
|
|
|
- const fields = Object.fromEntries(
|
|
|
- DEFAULT_ROUTE_FIELDS.map(field => [field, config.dict?.[field]]),
|
|
|
- )
|
|
|
- return z.object(fields) as z<DefaultRouteSettings>
|
|
|
-}
|
|
|
+export const DEFAULT_ROUTE_SCHEMA: z<DefaultRouteSettings> = z.object({
|
|
|
+ provider: z.string().required(),
|
|
|
+ model: z.string().required(),
|
|
|
+ reasoningEffort: z.string(),
|
|
|
+})
|
|
|
|
|
|
/** Project the stored/composed section onto the agent-facing target shape. */
|
|
|
function routeTarget(settings: DefaultRouteSettings): AgentLlmTarget {
|
|
|
@@ -109,7 +108,6 @@ export class ApiProxyService extends Service implements ApiProxy {
|
|
|
static Config: z<Config> = z.object({
|
|
|
provider: z.string().required(),
|
|
|
model: z.string().required(),
|
|
|
- reasoningEffort: z.string(),
|
|
|
workspaceRoot: z.string(),
|
|
|
})
|
|
|
|
|
|
@@ -132,13 +130,9 @@ export class ApiProxyService extends Service implements ApiProxy {
|
|
|
// The composition entry is the shipped default; the settings section
|
|
|
// layers the user's own choice over it, and a deployment without a
|
|
|
// settings provider simply keeps the entry.
|
|
|
- const entry: DefaultRouteSettings = {
|
|
|
- provider: config.provider,
|
|
|
- model: config.model,
|
|
|
- ...config.reasoningEffort === undefined ? {} : { reasoningEffort: config.reasoningEffort },
|
|
|
- }
|
|
|
+ const entry: DefaultRouteSettings = { provider: config.provider, model: config.model }
|
|
|
let route: () => DefaultRouteSettings = () => entry
|
|
|
- installSettingsSection(ctx, API_GATEWAY_SETTINGS_NAMESPACE, defaultRouteSchema(ApiProxyService.Config), entry, {
|
|
|
+ installSettingsSection(ctx, API_GATEWAY_SETTINGS_NAMESPACE, DEFAULT_ROUTE_SCHEMA, entry, {
|
|
|
setSource: (current) => {
|
|
|
route = current
|
|
|
},
|
|
|
@@ -150,8 +144,10 @@ export class ApiProxyService extends Service implements ApiProxy {
|
|
|
defaultTarget: () => routeTarget(route()),
|
|
|
// Wholesale, never a merge: switching to a model with no reasoning
|
|
|
// effort must clear a stored one, and a merged patch would strand it
|
|
|
- // for the next session to fail on. The section holds no secrets, so
|
|
|
- // there is nothing a replace can collaterally drop.
|
|
|
+ // for the next session to fail on. This clears it because the entry
|
|
|
+ // below the user layer carries no effort to re-inherit — the reason
|
|
|
+ // `Config` deliberately has no such field. The section holds no
|
|
|
+ // secrets, so there is nothing a replace can collaterally drop.
|
|
|
persistDefaultTarget: async (target) => {
|
|
|
await ctx.get('settings')?.replace(API_GATEWAY_SETTINGS_NAMESPACE, target)
|
|
|
},
|