1
0

request-inspection.client.spec.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { describe, expect, it } from 'vitest'
  2. import { SessionSeq } from '@deepseek-ai/dsh-session/types'
  3. import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
  4. import type { SystemPromptNode } from '../src/client/contract/request-inspection.ts'
  5. import { inspectRequestPrompt } from '../src/client/contract/request-inspection.ts'
  6. const CONFIG = { provider: 'test', model: 'test' }
  7. const READ_TOOL = { name: 'read', description: 'Read a file', parameters: { type: 'object' } }
  8. const WRITE_TOOL = { name: 'write', description: 'Write', parameters: { type: 'object' } }
  9. function header(
  10. seq: SessionSeq,
  11. reason: SessionEvent<'request/header'>['data']['reason'],
  12. value: SessionEvent<'request/header'>['data']['header'],
  13. ): SessionEvent<'request/header'> {
  14. return {
  15. type: 'request/header',
  16. seq,
  17. time: 1_700_000_000_000 + seq,
  18. data: { reason, header: value },
  19. }
  20. }
  21. function systemNode(seq: number, text: string, update = false): SystemPromptNode {
  22. return { seq, time: 1_700_000_000_000 + seq, turn: 1, step: seq, text, update }
  23. }
  24. describe('inspectRequestPrompt', () => {
  25. it('classifies the first complete header as the initial prompt anchored at its system node', () => {
  26. expect(inspectRequestPrompt(undefined, header(SessionSeq(3), 'initial', {
  27. config: CONFIG,
  28. tools: [READ_TOOL],
  29. }), systemNode(1, '# System\n\nFollow instructions.'))).toMatchObject({
  30. prompt: {
  31. config: CONFIG,
  32. system: '# System\n\nFollow instructions.',
  33. tools: [{ name: 'read' }],
  34. },
  35. change: { seq: 1, time: 1_700_000_000_001, kind: 'initial' },
  36. })
  37. })
  38. it('anchors an initial header without a system node at the header itself', () => {
  39. expect(inspectRequestPrompt(undefined, header(SessionSeq(2), 'initial', {
  40. config: CONFIG,
  41. }), undefined)).toEqual({
  42. prompt: { config: CONFIG, system: '', tools: [] },
  43. change: { seq: 2, time: 1_700_000_000_002, kind: 'initial' },
  44. })
  45. })
  46. it('suppresses a resume header when the earlier prompt is outside the loaded window', () => {
  47. expect(inspectRequestPrompt(undefined, header(SessionSeq(2), 'resume', {
  48. config: CONFIG,
  49. }), systemNode(1, 'same prompt'))).toEqual({
  50. prompt: { config: CONFIG, system: 'same prompt', tools: [] },
  51. })
  52. })
  53. it('classifies system, tool, and combined changes against the previous prompt', () => {
  54. const initial = inspectRequestPrompt(undefined, header(SessionSeq(2), 'initial', {
  55. config: CONFIG,
  56. tools: [READ_TOOL],
  57. }), systemNode(1, 'first')).prompt
  58. const system = inspectRequestPrompt(initial, header(SessionSeq(4), 'series', {
  59. config: CONFIG,
  60. tools: [...initial.tools],
  61. }), systemNode(3, 'second'))
  62. const tools = inspectRequestPrompt(system.prompt, header(SessionSeq(5), 'change', {
  63. config: CONFIG,
  64. tools: [WRITE_TOOL],
  65. }), systemNode(3, 'second'))
  66. const combined = inspectRequestPrompt(tools.prompt, header(SessionSeq(7), 'change', {
  67. config: CONFIG,
  68. tools: [],
  69. }), systemNode(6, 'third'))
  70. expect(system.change).toEqual({ seq: 3, time: 1_700_000_000_003, kind: 'system', previous: initial })
  71. expect(tools.change).toEqual({ seq: 5, time: 1_700_000_000_005, kind: 'tools', previous: system.prompt })
  72. expect(combined.change).toEqual({
  73. seq: 6, time: 1_700_000_000_006, kind: 'system-and-tools', previous: tools.prompt,
  74. })
  75. })
  76. it('reports an emptied system prompt as a system change anchored at the replacing node', () => {
  77. const initial = inspectRequestPrompt(undefined, header(SessionSeq(2), 'initial', {
  78. config: CONFIG,
  79. }), systemNode(1, 'first')).prompt
  80. expect(inspectRequestPrompt(initial, header(SessionSeq(4), 'series', {
  81. config: CONFIG,
  82. }), systemNode(3, ''))).toEqual({
  83. prompt: { config: CONFIG, system: '', tools: [] },
  84. change: { seq: 3, time: 1_700_000_000_003, kind: 'system', previous: initial },
  85. })
  86. })
  87. it('reports no system change for an in-history update the model already read at its own position', () => {
  88. const initial = inspectRequestPrompt(undefined, header(SessionSeq(2), 'initial', {
  89. config: CONFIG,
  90. tools: [READ_TOOL],
  91. }), systemNode(1, 'first')).prompt
  92. // A later series header carries the updated text without a system change…
  93. const series = inspectRequestPrompt(initial, header(SessionSeq(4), 'series', {
  94. config: CONFIG,
  95. tools: [READ_TOOL],
  96. }), systemNode(3, 'updated', true))
  97. expect(series).toEqual({ prompt: { config: CONFIG, system: 'updated', tools: [READ_TOOL] } })
  98. // …and a tools change alongside the update reports only the tools, anchored at the header.
  99. expect(inspectRequestPrompt(initial, header(SessionSeq(5), 'change', {
  100. config: CONFIG,
  101. tools: [WRITE_TOOL],
  102. }), systemNode(3, 'updated', true)).change).toEqual({
  103. seq: 5, time: 1_700_000_000_005, kind: 'tools', previous: initial,
  104. })
  105. })
  106. it('omits a change when the system node and tools are unchanged', () => {
  107. const previous = inspectRequestPrompt(undefined, header(SessionSeq(2), 'initial', {
  108. config: CONFIG,
  109. }), systemNode(1, 'same')).prompt
  110. expect(inspectRequestPrompt(previous, header(SessionSeq(3), 'resume', {
  111. config: { ...CONFIG, maxTokens: 1_024 },
  112. }), systemNode(1, 'same'))).toEqual({
  113. prompt: { config: { ...CONFIG, maxTokens: 1_024 }, system: 'same', tools: [] },
  114. })
  115. })
  116. })