config.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. /**
  2. * Runtime defaulting and policy validation for compact-basic.
  3. *
  4. * @module @deepseek-ai/dsh-compact-basic/config
  5. */
  6. import { deepFreeze } from '@deepseek-ai/dsh-llm'
  7. import type { TokenMeterService } from '@deepseek-ai/dsh-token-meter'
  8. import type { BasicCompactConfig, ResolvedConfig } from './types.ts'
  9. /** Default request-pressure fraction of the token meter's context window. */
  10. const DEFAULT_THRESHOLD_RATIO = 0.8
  11. /** Default verbatim-tail fraction of the token meter's context window. */
  12. const DEFAULT_RETAIN_RATIO = 0.16
  13. /** Complete public configuration key set. */
  14. const BASIC_COMPACT_CONFIG_KEYS: ReadonlySet<string> = new Set([
  15. 'thresholdRatio',
  16. 'retainTokens',
  17. 'summarizationProvider',
  18. 'summarizationModel',
  19. 'maxTokens',
  20. 'compactionRetries',
  21. 'maxOverflowRetries',
  22. 'auto',
  23. ])
  24. /** Reject stale or misspelled keys before defaults can hide them. */
  25. function validateConfigKeys(config: BasicCompactConfig): void {
  26. for (const key of Object.keys(config)) {
  27. if (!BASIC_COMPACT_CONFIG_KEYS.has(key)) {
  28. throw new Error(
  29. `BasicCompactConfig: unknown key "${key}" `
  30. + '(allowed: thresholdRatio, retainTokens, summarizationProvider, summarizationModel, '
  31. + 'maxTokens, compactionRetries, maxOverflowRetries, auto)',
  32. )
  33. }
  34. }
  35. }
  36. /**
  37. * Resolve defaults and validate the service-wide compaction policy.
  38. * @param config - raw compact-basic configuration.
  39. * @param tokenMeter - token meter supplying the context capacity.
  40. * @returns a detached deeply immutable configuration.
  41. */
  42. export function resolveConfig(
  43. config: BasicCompactConfig = {},
  44. tokenMeter: TokenMeterService,
  45. ): ResolvedConfig {
  46. validateConfigKeys(config)
  47. const thresholdRatio = config.thresholdRatio ?? DEFAULT_THRESHOLD_RATIO
  48. const retainTokens = config.retainTokens
  49. ?? Math.floor(tokenMeter.contextWindow * DEFAULT_RETAIN_RATIO)
  50. const resolved: ResolvedConfig = {
  51. thresholdRatio,
  52. retainTokens,
  53. summarizationProvider: config.summarizationProvider ?? '',
  54. summarizationModel: config.summarizationModel ?? '',
  55. maxTokens: config.maxTokens ?? 8192,
  56. compactionRetries: config.compactionRetries ?? 1,
  57. maxOverflowRetries: config.maxOverflowRetries ?? 1,
  58. auto: config.auto ?? true,
  59. }
  60. assertRatio('thresholdRatio', resolved.thresholdRatio)
  61. assertNonNegativeInteger('retainTokens', resolved.retainTokens)
  62. const thresholdTokens = Math.floor(tokenMeter.contextWindow * resolved.thresholdRatio)
  63. if (resolved.retainTokens >= thresholdTokens) {
  64. throw new Error(
  65. `BasicCompactConfig: retainTokens (${resolved.retainTokens}) must be less than threshold tokens ${thresholdTokens}`,
  66. )
  67. }
  68. assertPositiveInteger('maxTokens', resolved.maxTokens)
  69. assertNonNegativeInteger('compactionRetries', resolved.compactionRetries)
  70. assertNonNegativeInteger('maxOverflowRetries', resolved.maxOverflowRetries)
  71. if (typeof resolved.summarizationProvider !== 'string') {
  72. throw new Error('BasicCompactConfig: summarizationProvider must be a string')
  73. }
  74. if (typeof resolved.summarizationModel !== 'string') {
  75. throw new Error('BasicCompactConfig: summarizationModel must be a string')
  76. }
  77. if ((resolved.summarizationProvider.length === 0) !== (resolved.summarizationModel.length === 0)) {
  78. throw new Error(
  79. 'BasicCompactConfig: summarizationProvider and summarizationModel must both be set or both be empty',
  80. )
  81. }
  82. if (typeof resolved.auto !== 'boolean') {
  83. throw new Error('BasicCompactConfig: auto must be a boolean')
  84. }
  85. return deepFreeze(resolved)
  86. }
  87. function assertPositiveInteger(name: string, value: number): void {
  88. if (!Number.isInteger(value) || value <= 0) {
  89. throw new Error(`BasicCompactConfig: ${name} (${value}) must be a positive integer`)
  90. }
  91. }
  92. function assertNonNegativeInteger(name: string, value: number): void {
  93. if (!Number.isInteger(value) || value < 0) {
  94. throw new Error(`BasicCompactConfig: ${name} (${value}) must be a non-negative integer`)
  95. }
  96. }
  97. function assertRatio(name: string, value: number): void {
  98. if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1) {
  99. throw new Error(`BasicCompactConfig: ${name} (${value}) must be a number in (0, 1]`)
  100. }
  101. }