model-selection-state.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. /** Durable per-session state for the user-controlled model-selection opt-in. */
  2. import { z as zod } from 'zod'
  3. import type { Session } from '@deepseek-ai/dsh-session'
  4. import type SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
  5. import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection'
  6. import { assertAllowedModelRoutes, type AllowedModelRoute } from './model-selection.ts'
  7. declare module '@deepseek-ai/dsh-session/types' {
  8. interface SessionEventMap {
  9. /**
  10. * Records that this session's delegation tool exposes child provider,
  11. * model, and reasoning-effort selection. Appended before the first model
  12. * request; absence means the fixed-route definition. Log-only: it carries
  13. * no `surfaceOp` and never enters model history.
  14. */
  15. 'subagent/model-selection-policy': {
  16. /** Exact routes this Session may select explicitly for a child. */
  17. allowedModels: AllowedModelRoute[]
  18. }
  19. }
  20. }
  21. declare module '@deepseek-ai/dsh-session-projection/types' {
  22. interface SessionProjectionStateMap {
  23. /** Exact routes authorized for child LLM selection, or null when disabled. */
  24. subagentModelSelectionPolicy: AllowedModelRoute[] | null
  25. }
  26. }
  27. const modelSelectionPolicySchema: zod.ZodType<AllowedModelRoute[] | null> = zod.array(zod.object({
  28. provider: zod.string().min(1),
  29. model: zod.string().min(1),
  30. }).strict()).min(1).nullable()
  31. /** Host-only projection of the durable model-selection policy. */
  32. export const subagentModelSelectionProjectionDefinition = {
  33. key: 'subagentModelSelectionPolicy',
  34. stateVersion: 1,
  35. stateSchema: modelSelectionPolicySchema,
  36. init: () => null,
  37. apply: (policy, event) => {
  38. if (policy !== null || event.type !== 'subagent/model-selection-policy') return policy
  39. const { allowedModels } = event.data
  40. assertAllowedModelRoutes(allowedModels)
  41. if (allowedModels.length === 0) {
  42. throw new Error('subagent/model-selection-policy requires at least one route')
  43. }
  44. return allowedModels
  45. },
  46. } satisfies ProjectionDefinition<'subagentModelSelectionPolicy', AllowedModelRoute[] | null>
  47. /**
  48. * Read the exact route list captured for a model-selectable definition.
  49. * @param projections - registry that owns the policy projection.
  50. * @param session - session whose durable decision is read.
  51. * @returns a detached route list, or undefined for the fixed-route definition.
  52. */
  53. export function subagentModelSelectionPolicy(
  54. projections: Pick<SessionProjectionRegistry, 'stateOf'>,
  55. session: Session,
  56. ): AllowedModelRoute[] | undefined {
  57. return projections.stateOf(session, 'subagentModelSelectionPolicy')?.map(route => ({ ...route }))
  58. }
  59. /**
  60. * Append the route policy once, before its definition can reach a model request.
  61. * @param projections - registry that owns the policy projection.
  62. * @param session - session receiving the model-selectable definition.
  63. * @param allowedModels - exact routes the definition may select explicitly.
  64. */
  65. export function recordSubagentModelSelection(
  66. projections: Pick<SessionProjectionRegistry, 'stateOf'>,
  67. session: Session,
  68. allowedModels: readonly AllowedModelRoute[],
  69. ): void {
  70. if (subagentModelSelectionPolicy(projections, session) !== undefined) return
  71. session.append('subagent/model-selection-policy', {
  72. allowedModels: allowedModels.map(route => ({ ...route })),
  73. })
  74. }