Files
deepseek-harness/packages/llm/llm/tests/call-config.spec.ts
Tianyi Cui 2093a8898b loop: every request is built from the log — boundary snapshot, header events, config-only waterfall
The loop is now transmission-stateless; a request is a pure function of
(session log, this step's rendered assembly, current AgentOptions):

- The reconstruction boundary is step/start: the messages snapshot is
  taken in the same synchronous frame immediately before the step/start
  append, so the request's messages are exactly the derivation over
  events[0..stepStartSeq) — an inject() from an agent/request listener
  (or any concurrent task) lands after the boundary and joins the NEXT
  request. This changes behavior for a synchronous step/start
  session/event listener that appends content (master derived after the
  append, so such a listener could reach the current request):
  agent/pre-step is the sanctioned seam for current-request content.
- agent/request is re-typed to config-only: (agent, turn, step,
  config: LlmCallConfig, next) → LlmCallConfig. The frozen seed comes
  from AgentOptions on a loop instance's first request (explicit options
  beat the logged baseline — fork overrides and resume reconfiguration
  stay correct) and from the log's folded header afterwards; listeners
  return a replacement to switch. Content shaping through the request is
  no longer expressible — model-visible content flows through the log
  channels.
- recordRequestHeader appends whatever header event the request owes the
  log before dispatch: an 'initial'/'resume' snapshot anchoring each
  loop instance, a round-trip-verified delta on change, a 'fallback'
  snapshot when the encoding cannot express it. Session.requestHeader()
  is the log's incrementally-folded baseline.
- Requests are deep-frozen before dispatch (deepFreeze exempts the
  AbortSignal — freezing one breaks AbortController.abort() outright);
  frozen + sessionId is the loop-built marker the dev invariant keys on.

Ported from #162 and re-anchored on the log: the append-extension /
frozen-end-to-end / compaction-resend / prompt-change property tests,
plus new specs for the boundary semantics, resume anchoring, and the
end-to-end theorem (every recorded request rebuilds byte-equal from the
log alone). Live cache-hit e2e (request-cache.e2e.ts) verified against
the real DeepSeek API. Snapshot goldens intentionally stale until the
single re-record after the compact/summary envelope lands.
2026-07-06 03:07:34 +08:00

57 lines
2.5 KiB
TypeScript

/**
* call-config unit tests: field-wise LlmCallConfig equality (the real-change
* detector behind logged header deltas) and the deepFreeze ownership helper
* the loop applies to every built request.
*/
import { describe, expect, it } from 'vitest'
import { callConfigEquals, deepFreeze } from '../src/call-config.ts'
describe('callConfigEquals', () => {
it('compares every field, including the stop list element-wise', () => {
expect(callConfigEquals({ model: 'm' }, { model: 'm' })).toBe(true)
expect(callConfigEquals({ model: 'm' }, { model: 'x' })).toBe(false)
expect(callConfigEquals({ model: 'm', temperature: 0.5 }, { model: 'm' })).toBe(false)
expect(callConfigEquals({ model: 'm', maxTokens: 1 }, { model: 'm', maxTokens: 2 })).toBe(false)
expect(callConfigEquals({ model: 'm', stop: ['a'] }, { model: 'm' })).toBe(false)
expect(callConfigEquals({ model: 'm', stop: ['a'] }, { model: 'm', stop: ['a', 'b'] })).toBe(false)
expect(callConfigEquals({ model: 'm', stop: ['a'] }, { model: 'm', stop: ['b'] })).toBe(false)
expect(callConfigEquals({ model: 'm', stop: ['a', 'b'] }, { model: 'm', stop: ['a', 'b'] })).toBe(true)
})
})
describe('deepFreeze', () => {
it('freezes nested structure in place and returns the same reference', () => {
const value = { a: { b: [1, { c: 'x' }] } }
const frozen = deepFreeze(value)
expect(frozen).toBe(value)
expect(Object.isFrozen(value)).toBe(true)
expect(Object.isFrozen(value.a)).toBe(true)
expect(Object.isFrozen(value.a.b)).toBe(true)
expect(Object.isFrozen(value.a.b[1])).toBe(true)
// ESM runs in strict mode: mutation throws rather than silently failing.
expect(() => { (value.a.b[1] as { c: string }).c = 'y' }).toThrow(TypeError)
})
it('never freezes an AbortSignal: the live cancellation channel keeps working', () => {
const controller = new AbortController()
const request = deepFreeze({ model: 'm', signal: controller.signal })
expect(Object.isFrozen(request)).toBe(true)
expect(Object.isFrozen(controller.signal)).toBe(false)
let fired = false
controller.signal.addEventListener('abort', () => { fired = true }, { once: true })
controller.abort('stop')
expect(fired).toBe(true)
expect(controller.signal.aborted).toBe(true)
})
it('passes primitives through and terminates on cycles', () => {
expect(deepFreeze(42)).toBe(42)
expect(deepFreeze(null)).toBeNull()
const cyclic = { self: undefined as unknown }
cyclic.self = cyclic
deepFreeze(cyclic)
expect(Object.isFrozen(cyclic)).toBe(true)
})
})