mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
feat(ipuaro): add context configuration
- Add ContextConfigSchema with systemPromptTokens, maxContextUsage, autoCompressAt, compressionMethod - Update ContextManager to read compression threshold from config - Update HandleMessage and useSession to pass context config - Add 40 unit tests (32 schema + 8 integration) - Coverage: 97.63% lines, 91.34% branches
This commit is contained in:
@@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.22.3] - 2025-12-02 - Context Configuration
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **ContextConfigSchema (0.22.3)**
|
||||||
|
- New configuration schema for context management in `src/shared/constants/config.ts`
|
||||||
|
- `systemPromptTokens: number` (default: 2000) - token budget for system prompt
|
||||||
|
- `maxContextUsage: number` (default: 0.8) - maximum context window usage ratio (0-1)
|
||||||
|
- `autoCompressAt: number` (default: 0.8) - threshold for automatic context compression (0-1)
|
||||||
|
- `compressionMethod: "llm-summary" | "truncate"` (default: "llm-summary") - compression strategy
|
||||||
|
- Integrated into main ConfigSchema with `.default({})`
|
||||||
|
- Exported `ContextConfig` type from config module
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **ContextManager**
|
||||||
|
- Added optional `config?: ContextConfig` parameter to constructor
|
||||||
|
- Added private `compressionThreshold: number` field (read from config or default)
|
||||||
|
- Added private `compressionMethod: "llm-summary" | "truncate"` field (read from config or default)
|
||||||
|
- Updated `needsCompression()` to use configurable `compressionThreshold` instead of hardcoded constant
|
||||||
|
- Enables dynamic compression threshold configuration per session/deployment
|
||||||
|
|
||||||
|
- **HandleMessage Use Case**
|
||||||
|
- Added optional `contextConfig?: ContextConfig` parameter to constructor
|
||||||
|
- Added `contextConfig?: ContextConfig` to `HandleMessageOptions`
|
||||||
|
- Passes context config to ContextManager during initialization
|
||||||
|
- Context management behavior now fully configurable
|
||||||
|
|
||||||
|
- **useSession Hook**
|
||||||
|
- Passes `deps.config?.context` to HandleMessage constructor
|
||||||
|
- Passes `contextConfig: deps.config?.context` to HandleMessage options
|
||||||
|
- Context configuration flows from config through to ContextManager
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- Total tests: 1630 passed (was 1590, +40 new tests)
|
||||||
|
- New test file: `context-config.test.ts` with 32 tests
|
||||||
|
- Default values validation (systemPromptTokens, maxContextUsage, autoCompressAt, compressionMethod)
|
||||||
|
- `systemPromptTokens` positive integer validation (including edge cases: zero, negative, float rejection)
|
||||||
|
- `maxContextUsage` ratio validation (0-1 range, rejects out-of-bounds)
|
||||||
|
- `autoCompressAt` ratio validation (0-1 range, rejects out-of-bounds)
|
||||||
|
- `compressionMethod` enum validation (llm-summary, truncate)
|
||||||
|
- Partial and full config merging tests
|
||||||
|
- Updated ContextManager tests: 8 new tests for configuration integration
|
||||||
|
- Custom compression threshold behavior
|
||||||
|
- Edge cases: autoCompressAt = 0 and autoCompressAt = 1
|
||||||
|
- Full context config acceptance
|
||||||
|
- Coverage: 97.63% lines, 91.34% branches, 98.77% functions, 97.63% statements
|
||||||
|
- 0 ESLint errors, 0 warnings
|
||||||
|
- Build successful with no TypeScript errors
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
This release completes the third item (0.22.3) of the v0.22.0 Extended Configuration milestone. Remaining items for v0.22.0:
|
||||||
|
- 0.22.4 - Autocomplete Configuration
|
||||||
|
- 0.22.5 - Commands Configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.22.2] - 2025-12-02 - Session Configuration
|
## [0.22.2] - 2025-12-02 - Session Configuration
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -1648,7 +1648,7 @@ interface DiffViewProps {
|
|||||||
## Version 0.22.0 - Extended Configuration ⚙️
|
## Version 0.22.0 - Extended Configuration ⚙️
|
||||||
|
|
||||||
**Priority:** MEDIUM
|
**Priority:** MEDIUM
|
||||||
**Status:** In Progress (2/5 complete)
|
**Status:** In Progress (3/5 complete)
|
||||||
|
|
||||||
### 0.22.1 - Display Configuration ✅
|
### 0.22.1 - Display Configuration ✅
|
||||||
|
|
||||||
@@ -1687,7 +1687,7 @@ export const SessionConfigSchema = z.object({
|
|||||||
- [x] Input history persistence toggle
|
- [x] Input history persistence toggle
|
||||||
- [x] Unit tests (19 new tests)
|
- [x] Unit tests (19 new tests)
|
||||||
|
|
||||||
### 0.22.3 - Context Configuration
|
### 0.22.3 - Context Configuration ✅
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/shared/constants/config.ts additions
|
// src/shared/constants/config.ts additions
|
||||||
@@ -1700,10 +1700,10 @@ export const ContextConfigSchema = z.object({
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- [ ] ContextConfigSchema in config.ts
|
- [x] ContextConfigSchema in config.ts
|
||||||
- [ ] ContextManager reads from config
|
- [x] ContextManager reads from config
|
||||||
- [ ] Configurable compression threshold
|
- [x] Configurable compression threshold
|
||||||
- [ ] Unit tests
|
- [x] Unit tests (40 new tests: 32 schema, 8 ContextManager integration)
|
||||||
|
|
||||||
### 0.22.4 - Autocomplete Configuration
|
### 0.22.4 - Autocomplete Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { ContextState, Session } from "../../domain/entities/Session.js"
|
|||||||
import type { ILLMClient } from "../../domain/services/ILLMClient.js"
|
import type { ILLMClient } from "../../domain/services/ILLMClient.js"
|
||||||
import { type ChatMessage, createSystemMessage } from "../../domain/value-objects/ChatMessage.js"
|
import { type ChatMessage, createSystemMessage } from "../../domain/value-objects/ChatMessage.js"
|
||||||
import { CONTEXT_COMPRESSION_THRESHOLD, CONTEXT_WINDOW_SIZE } from "../../domain/constants/index.js"
|
import { CONTEXT_COMPRESSION_THRESHOLD, CONTEXT_WINDOW_SIZE } from "../../domain/constants/index.js"
|
||||||
|
import type { ContextConfig } from "../../shared/constants/config.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File in context with token count.
|
* File in context with token count.
|
||||||
@@ -39,9 +40,13 @@ export class ContextManager {
|
|||||||
private readonly filesInContext = new Map<string, FileContext>()
|
private readonly filesInContext = new Map<string, FileContext>()
|
||||||
private currentTokens = 0
|
private currentTokens = 0
|
||||||
private readonly contextWindowSize: number
|
private readonly contextWindowSize: number
|
||||||
|
private readonly compressionThreshold: number
|
||||||
|
private readonly compressionMethod: "llm-summary" | "truncate"
|
||||||
|
|
||||||
constructor(contextWindowSize: number = CONTEXT_WINDOW_SIZE) {
|
constructor(contextWindowSize: number = CONTEXT_WINDOW_SIZE, config?: ContextConfig) {
|
||||||
this.contextWindowSize = contextWindowSize
|
this.contextWindowSize = contextWindowSize
|
||||||
|
this.compressionThreshold = config?.autoCompressAt ?? CONTEXT_COMPRESSION_THRESHOLD
|
||||||
|
this.compressionMethod = config?.compressionMethod ?? "llm-summary"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,7 +102,7 @@ export class ContextManager {
|
|||||||
* Check if compression is needed.
|
* Check if compression is needed.
|
||||||
*/
|
*/
|
||||||
needsCompression(): boolean {
|
needsCompression(): boolean {
|
||||||
return this.getUsage() > CONTEXT_COMPRESSION_THRESHOLD
|
return this.getUsage() > this.compressionThreshold
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export interface HandleMessageOptions {
|
|||||||
maxToolCalls?: number
|
maxToolCalls?: number
|
||||||
maxHistoryMessages?: number
|
maxHistoryMessages?: number
|
||||||
saveInputHistory?: boolean
|
saveInputHistory?: boolean
|
||||||
|
contextConfig?: import("../../shared/constants/config.js").ContextConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_MAX_TOOL_CALLS = 20
|
const DEFAULT_MAX_TOOL_CALLS = 20
|
||||||
@@ -98,13 +99,14 @@ export class HandleMessage {
|
|||||||
llm: ILLMClient,
|
llm: ILLMClient,
|
||||||
tools: IToolRegistry,
|
tools: IToolRegistry,
|
||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
|
contextConfig?: import("../../shared/constants/config.js").ContextConfig,
|
||||||
) {
|
) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.sessionStorage = sessionStorage
|
this.sessionStorage = sessionStorage
|
||||||
this.llm = llm
|
this.llm = llm
|
||||||
this.tools = tools
|
this.tools = tools
|
||||||
this.projectRoot = projectRoot
|
this.projectRoot = projectRoot
|
||||||
this.contextManager = new ContextManager(llm.getContextWindowSize())
|
this.contextManager = new ContextManager(llm.getContextWindowSize(), contextConfig)
|
||||||
this.executeTool = new ExecuteTool(storage, sessionStorage, tools, projectRoot)
|
this.executeTool = new ExecuteTool(storage, sessionStorage, tools, projectRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,16 @@ export const SessionConfigSchema = z.object({
|
|||||||
saveInputHistory: z.boolean().default(true),
|
saveInputHistory: z.boolean().default(true),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context configuration schema.
|
||||||
|
*/
|
||||||
|
export const ContextConfigSchema = z.object({
|
||||||
|
systemPromptTokens: z.number().int().positive().default(2000),
|
||||||
|
maxContextUsage: z.number().min(0).max(1).default(0.8),
|
||||||
|
autoCompressAt: z.number().min(0).max(1).default(0.8),
|
||||||
|
compressionMethod: z.enum(["llm-summary", "truncate"]).default("llm-summary"),
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full configuration schema.
|
* Full configuration schema.
|
||||||
*/
|
*/
|
||||||
@@ -119,6 +129,7 @@ export const ConfigSchema = z.object({
|
|||||||
input: InputConfigSchema.default({}),
|
input: InputConfigSchema.default({}),
|
||||||
display: DisplayConfigSchema.default({}),
|
display: DisplayConfigSchema.default({}),
|
||||||
session: SessionConfigSchema.default({}),
|
session: SessionConfigSchema.default({}),
|
||||||
|
context: ContextConfigSchema.default({}),
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,6 +145,7 @@ export type EditConfig = z.infer<typeof EditConfigSchema>
|
|||||||
export type InputConfig = z.infer<typeof InputConfigSchema>
|
export type InputConfig = z.infer<typeof InputConfigSchema>
|
||||||
export type DisplayConfig = z.infer<typeof DisplayConfigSchema>
|
export type DisplayConfig = z.infer<typeof DisplayConfigSchema>
|
||||||
export type SessionConfig = z.infer<typeof SessionConfigSchema>
|
export type SessionConfig = z.infer<typeof SessionConfigSchema>
|
||||||
|
export type ContextConfig = z.infer<typeof ContextConfigSchema>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default configuration.
|
* Default configuration.
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ async function initializeSession(
|
|||||||
deps.llm,
|
deps.llm,
|
||||||
deps.tools,
|
deps.tools,
|
||||||
deps.projectRoot,
|
deps.projectRoot,
|
||||||
|
deps.config?.context,
|
||||||
)
|
)
|
||||||
if (deps.projectStructure) {
|
if (deps.projectStructure) {
|
||||||
handleMessage.setProjectStructure(deps.projectStructure)
|
handleMessage.setProjectStructure(deps.projectStructure)
|
||||||
@@ -117,6 +118,7 @@ async function initializeSession(
|
|||||||
autoApply: options.autoApply,
|
autoApply: options.autoApply,
|
||||||
maxHistoryMessages: deps.config?.session.maxHistoryMessages,
|
maxHistoryMessages: deps.config?.session.maxHistoryMessages,
|
||||||
saveInputHistory: deps.config?.session.saveInputHistory,
|
saveInputHistory: deps.config?.session.saveInputHistory,
|
||||||
|
contextConfig: deps.config?.context,
|
||||||
})
|
})
|
||||||
handleMessage.setEvents(createEventHandlers(setters, options))
|
handleMessage.setEvents(createEventHandlers(setters, options))
|
||||||
refs.current.handleMessage = handleMessage
|
refs.current.handleMessage = handleMessage
|
||||||
|
|||||||
@@ -245,4 +245,65 @@ describe("ContextManager", () => {
|
|||||||
expect(state.needsCompression).toBe(false)
|
expect(state.needsCompression).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("configuration", () => {
|
||||||
|
it("should use default compression threshold when no config provided", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE)
|
||||||
|
manager.addToContext("test.ts", CONTEXT_SIZE * 0.85)
|
||||||
|
|
||||||
|
expect(manager.needsCompression()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should use custom compression threshold from config", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, { autoCompressAt: 0.9 })
|
||||||
|
manager.addToContext("test.ts", CONTEXT_SIZE * 0.85)
|
||||||
|
|
||||||
|
expect(manager.needsCompression()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger compression at custom threshold", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, { autoCompressAt: 0.9 })
|
||||||
|
manager.addToContext("test.ts", CONTEXT_SIZE * 0.95)
|
||||||
|
|
||||||
|
expect(manager.needsCompression()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept compression method in config", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, { compressionMethod: "truncate" })
|
||||||
|
|
||||||
|
expect(manager).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should use default compression method when not specified", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, {})
|
||||||
|
|
||||||
|
expect(manager).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept full context config", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, {
|
||||||
|
systemPromptTokens: 3000,
|
||||||
|
maxContextUsage: 0.9,
|
||||||
|
autoCompressAt: 0.85,
|
||||||
|
compressionMethod: "llm-summary",
|
||||||
|
})
|
||||||
|
|
||||||
|
manager.addToContext("test.ts", CONTEXT_SIZE * 0.87)
|
||||||
|
expect(manager.needsCompression()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle edge case: autoCompressAt = 0", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, { autoCompressAt: 0 })
|
||||||
|
manager.addToContext("test.ts", 1)
|
||||||
|
|
||||||
|
expect(manager.needsCompression()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle edge case: autoCompressAt = 1", () => {
|
||||||
|
const manager = new ContextManager(CONTEXT_SIZE, { autoCompressAt: 1 })
|
||||||
|
manager.addToContext("test.ts", CONTEXT_SIZE * 0.99)
|
||||||
|
|
||||||
|
expect(manager.needsCompression()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
221
packages/ipuaro/tests/unit/shared/context-config.test.ts
Normal file
221
packages/ipuaro/tests/unit/shared/context-config.test.ts
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* Tests for ContextConfigSchema.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, expect, it } from "vitest"
|
||||||
|
import { ContextConfigSchema } from "../../../src/shared/constants/config.js"
|
||||||
|
|
||||||
|
describe("ContextConfigSchema", () => {
|
||||||
|
describe("default values", () => {
|
||||||
|
it("should use defaults when empty object provided", () => {
|
||||||
|
const result = ContextConfigSchema.parse({})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
systemPromptTokens: 2000,
|
||||||
|
maxContextUsage: 0.8,
|
||||||
|
autoCompressAt: 0.8,
|
||||||
|
compressionMethod: "llm-summary",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should use defaults via .default({})", () => {
|
||||||
|
const result = ContextConfigSchema.default({}).parse({})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
systemPromptTokens: 2000,
|
||||||
|
maxContextUsage: 0.8,
|
||||||
|
autoCompressAt: 0.8,
|
||||||
|
compressionMethod: "llm-summary",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("systemPromptTokens", () => {
|
||||||
|
it("should accept valid positive integer", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ systemPromptTokens: 1500 })
|
||||||
|
expect(result.systemPromptTokens).toBe(1500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept default value", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ systemPromptTokens: 2000 })
|
||||||
|
expect(result.systemPromptTokens).toBe(2000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept large value", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ systemPromptTokens: 5000 })
|
||||||
|
expect(result.systemPromptTokens).toBe(5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject zero", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ systemPromptTokens: 0 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject negative number", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ systemPromptTokens: -100 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject float", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ systemPromptTokens: 1500.5 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject non-number", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ systemPromptTokens: "2000" })).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("maxContextUsage", () => {
|
||||||
|
it("should accept valid ratio", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ maxContextUsage: 0.7 })
|
||||||
|
expect(result.maxContextUsage).toBe(0.7)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept default value", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ maxContextUsage: 0.8 })
|
||||||
|
expect(result.maxContextUsage).toBe(0.8)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept minimum value (0)", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ maxContextUsage: 0 })
|
||||||
|
expect(result.maxContextUsage).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept maximum value (1)", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ maxContextUsage: 1 })
|
||||||
|
expect(result.maxContextUsage).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject value above 1", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ maxContextUsage: 1.1 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject negative value", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ maxContextUsage: -0.1 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject non-number", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ maxContextUsage: "0.8" })).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("autoCompressAt", () => {
|
||||||
|
it("should accept valid ratio", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ autoCompressAt: 0.75 })
|
||||||
|
expect(result.autoCompressAt).toBe(0.75)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept default value", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ autoCompressAt: 0.8 })
|
||||||
|
expect(result.autoCompressAt).toBe(0.8)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept minimum value (0)", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ autoCompressAt: 0 })
|
||||||
|
expect(result.autoCompressAt).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept maximum value (1)", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ autoCompressAt: 1 })
|
||||||
|
expect(result.autoCompressAt).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject value above 1", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ autoCompressAt: 1.5 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject negative value", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ autoCompressAt: -0.5 })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject non-number", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ autoCompressAt: "0.8" })).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("compressionMethod", () => {
|
||||||
|
it("should accept llm-summary", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ compressionMethod: "llm-summary" })
|
||||||
|
expect(result.compressionMethod).toBe("llm-summary")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept truncate", () => {
|
||||||
|
const result = ContextConfigSchema.parse({ compressionMethod: "truncate" })
|
||||||
|
expect(result.compressionMethod).toBe("truncate")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject invalid method", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ compressionMethod: "invalid" })).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject non-string", () => {
|
||||||
|
expect(() => ContextConfigSchema.parse({ compressionMethod: 123 })).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("partial config", () => {
|
||||||
|
it("should merge partial config with defaults (systemPromptTokens)", () => {
|
||||||
|
const result = ContextConfigSchema.parse({
|
||||||
|
systemPromptTokens: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
systemPromptTokens: 3000,
|
||||||
|
maxContextUsage: 0.8,
|
||||||
|
autoCompressAt: 0.8,
|
||||||
|
compressionMethod: "llm-summary",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should merge partial config with defaults (autoCompressAt)", () => {
|
||||||
|
const result = ContextConfigSchema.parse({
|
||||||
|
autoCompressAt: 0.9,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
systemPromptTokens: 2000,
|
||||||
|
maxContextUsage: 0.8,
|
||||||
|
autoCompressAt: 0.9,
|
||||||
|
compressionMethod: "llm-summary",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should merge multiple partial fields", () => {
|
||||||
|
const result = ContextConfigSchema.parse({
|
||||||
|
maxContextUsage: 0.7,
|
||||||
|
compressionMethod: "truncate",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
systemPromptTokens: 2000,
|
||||||
|
maxContextUsage: 0.7,
|
||||||
|
autoCompressAt: 0.8,
|
||||||
|
compressionMethod: "truncate",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("full config", () => {
|
||||||
|
it("should accept valid full config", () => {
|
||||||
|
const config = {
|
||||||
|
systemPromptTokens: 3000,
|
||||||
|
maxContextUsage: 0.9,
|
||||||
|
autoCompressAt: 0.85,
|
||||||
|
compressionMethod: "truncate" as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = ContextConfigSchema.parse(config)
|
||||||
|
expect(result).toEqual(config)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should accept all defaults explicitly", () => {
|
||||||
|
const config = {
|
||||||
|
systemPromptTokens: 2000,
|
||||||
|
maxContextUsage: 0.8,
|
||||||
|
autoCompressAt: 0.8,
|
||||||
|
compressionMethod: "llm-summary" as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = ContextConfigSchema.parse(config)
|
||||||
|
expect(result).toEqual(config)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user