sqlite-integration.spec.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { afterEach, describe, expect, it } from 'vitest'
  2. import { Context } from 'cordis'
  3. import { mkdtemp, rm } from 'node:fs/promises'
  4. import { tmpdir } from 'node:os'
  5. import { join } from 'node:path'
  6. import type { Agent } from '@deepseek-ai/dsh-agent'
  7. import { CallId } from '@deepseek-ai/dsh-llm'
  8. import SessionStore, {
  9. SESSION_FORMAT_VERSION,
  10. SessionId,
  11. type Session,
  12. } from '@deepseek-ai/dsh-session'
  13. import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
  14. import SessionQuerySqlite from '@deepseek-ai/dsh-session-query-sqlite'
  15. import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
  16. import ToolRegistry from '@deepseek-ai/dsh-tools'
  17. import * as ToolSessionQuery from '@deepseek-ai/dsh-tool-session-query'
  18. const temporaryDirectories: string[] = []
  19. const contexts: Context[] = []
  20. afterEach(async () => {
  21. for (const ctx of contexts.splice(0)) await ctx.fiber.dispose()
  22. for (const directory of temporaryDirectories.splice(0)) {
  23. await rm(directory, { recursive: true, force: true })
  24. }
  25. })
  26. function fakeAgent(session: Session): Agent {
  27. return { id: session.id, session } as unknown as Agent
  28. }
  29. describe('tool-session-query with the real SQLite provider', () => {
  30. it('searches live prior-step history and a persisted same-workspace log', async () => {
  31. const root = await mkdtemp(join(tmpdir(), 'dsh-tool-session-query-'))
  32. temporaryDirectories.push(root)
  33. const ctx = new Context()
  34. contexts.push(ctx)
  35. await ctx.plugin(SessionStore)
  36. await ctx.plugin(SystemPrompt)
  37. await ctx.plugin(ToolRegistry)
  38. await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
  39. await ctx.plugin(SessionQuerySqlite, { path: join(root, 'session-query.db') })
  40. await ctx.plugin(ToolSessionQuery)
  41. const persisted = SessionId('persisted')
  42. await ctx.sessionPersistence.create({
  43. version: SESSION_FORMAT_VERSION,
  44. id: persisted,
  45. createdAt: 1,
  46. cwd: '/work',
  47. })
  48. await ctx.sessionPersistence.append(persisted, [{
  49. type: 'user/message',
  50. seq: 0,
  51. time: 2,
  52. data: {
  53. content: [{ type: 'text', text: 'persisted integration needle' }],
  54. source: { kind: 'user' },
  55. },
  56. surfaceOp: 'append',
  57. }])
  58. const caller = ctx.sessions.create(SessionId('caller'), {
  59. meta: { createdAt: 10, cwd: '/work' },
  60. })
  61. caller.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
  62. caller.append(
  63. 'user/message',
  64. { content: [{ type: 'text', text: 'live integration needle' }], source: { kind: 'user' } },
  65. { surfaceOp: 'append' },
  66. )
  67. caller.append('step/start', { turn: 1, step: 1 })
  68. let call = 0
  69. const execute = (name: string, args: unknown) => ctx.tools.execute({
  70. name,
  71. arguments: args,
  72. callId: CallId(`integration-${++call}`),
  73. signal: new AbortController().signal,
  74. agent: fakeAgent(caller),
  75. })
  76. const sessions = await execute('session_search', { query: 'persisted integration needle' })
  77. expect(sessions.isError).toBe(false)
  78. expect(sessions.content.map(block => block.type === 'text' ? block.text : '').join('\n'))
  79. .toContain('Session persisted')
  80. const persistedEvents = await execute('session_event_search', {
  81. session_id: persisted,
  82. query: 'persisted integration needle',
  83. })
  84. expect(persistedEvents.isError).toBe(false)
  85. expect(persistedEvents.content.map(block => block.type === 'text' ? block.text : '').join('\n'))
  86. .toContain('seq 0')
  87. const liveEvents = await execute('session_event_search', { query: 'live integration needle' })
  88. expect(liveEvents.isError).toBe(false)
  89. expect(liveEvents.content.map(block => block.type === 'text' ? block.text : '').join('\n'))
  90. .toContain('seq 1')
  91. })
  92. it('passes finite fractional epoch-millisecond bounds through SQLite comparisons', async () => {
  93. const root = await mkdtemp(join(tmpdir(), 'dsh-tool-session-query-fractional-'))
  94. temporaryDirectories.push(root)
  95. const ctx = new Context()
  96. contexts.push(ctx)
  97. await ctx.plugin(SessionStore)
  98. await ctx.plugin(SystemPrompt)
  99. await ctx.plugin(ToolRegistry)
  100. await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
  101. await ctx.plugin(SessionQuerySqlite, { path: join(root, 'session-query.db') })
  102. await ctx.plugin(ToolSessionQuery)
  103. const base = Date.parse('2026-07-24T00:00:00.000Z')
  104. const persisted = SessionId('fractional-persisted')
  105. await ctx.sessionPersistence.create({
  106. version: SESSION_FORMAT_VERSION,
  107. id: persisted,
  108. createdAt: base,
  109. cwd: '/work',
  110. })
  111. await ctx.sessionPersistence.append(persisted, [
  112. {
  113. type: 'user/message',
  114. seq: 0,
  115. time: base + 123,
  116. data: {
  117. content: [{ type: 'text', text: 'fractional integration needle' }],
  118. source: { kind: 'user' },
  119. },
  120. surfaceOp: 'append',
  121. },
  122. {
  123. type: 'user/message',
  124. seq: 1,
  125. time: base + 124,
  126. data: {
  127. content: [{ type: 'text', text: 'fractional integration needle' }],
  128. source: { kind: 'user' },
  129. },
  130. surfaceOp: 'append',
  131. },
  132. {
  133. type: 'user/message',
  134. seq: 2,
  135. time: -124,
  136. data: {
  137. content: [{ type: 'text', text: 'pre-epoch fractional needle' }],
  138. source: { kind: 'user' },
  139. },
  140. surfaceOp: 'append',
  141. },
  142. {
  143. type: 'user/message',
  144. seq: 3,
  145. time: -123,
  146. data: {
  147. content: [{ type: 'text', text: 'pre-epoch fractional needle' }],
  148. source: { kind: 'user' },
  149. },
  150. surfaceOp: 'append',
  151. },
  152. ])
  153. const caller = ctx.sessions.create(SessionId('fractional-caller'), {
  154. meta: { createdAt: base + 1_000, cwd: '/work' },
  155. })
  156. let call = 0
  157. const execute = (args: unknown) => ctx.tools.execute({
  158. name: 'session_event_search',
  159. arguments: args,
  160. callId: CallId(`fractional-integration-${++call}`),
  161. signal: new AbortController().signal,
  162. agent: fakeAgent(caller),
  163. })
  164. const lowerBound = await execute({
  165. session_id: persisted,
  166. query: 'fractional integration needle',
  167. time_from: '2026-07-24T00:00:00.12300001Z',
  168. })
  169. expect(lowerBound.isError).toBe(false)
  170. const lowerText = lowerBound.content.map(block => block.type === 'text' ? block.text : '').join('\n')
  171. expect(lowerText).toContain('seq 1')
  172. expect(lowerText).not.toContain('seq 0')
  173. const upperBound = await execute({
  174. session_id: persisted,
  175. query: 'fractional integration needle',
  176. time_to: '2026-07-24T08:00:00.1239999+08:00',
  177. })
  178. expect(upperBound.isError).toBe(false)
  179. const upperText = upperBound.content.map(block => block.type === 'text' ? block.text : '').join('\n')
  180. expect(upperText).toContain('seq 0')
  181. expect(upperText).not.toContain('seq 1')
  182. const emptySameMillisecond = await execute({
  183. session_id: persisted,
  184. query: 'fractional integration needle',
  185. time_from: '2026-07-24T00:00:00.12300001Z',
  186. time_to: '2026-07-24T08:00:00.1239999+08:00',
  187. })
  188. expect(emptySameMillisecond.isError).toBe(false)
  189. expect(emptySameMillisecond.content.map(block => block.type === 'text' ? block.text : '').join('\n'))
  190. .toContain('No prior event matches found.')
  191. const preEpochLower = await execute({
  192. session_id: persisted,
  193. query: 'pre-epoch fractional needle',
  194. time_from: '1969-12-31T23:59:59.87600001Z',
  195. })
  196. expect(preEpochLower.isError).toBe(false)
  197. const preEpochLowerText = preEpochLower.content
  198. .map(block => block.type === 'text' ? block.text : '').join('\n')
  199. expect(preEpochLowerText).toContain('seq 3')
  200. expect(preEpochLowerText).not.toContain('seq 2')
  201. const preEpochUpper = await execute({
  202. session_id: persisted,
  203. query: 'pre-epoch fractional needle',
  204. time_to: '1969-12-31T19:59:59.8769999-04:00',
  205. })
  206. expect(preEpochUpper.isError).toBe(false)
  207. const preEpochUpperText = preEpochUpper.content
  208. .map(block => block.type === 'text' ? block.text : '').join('\n')
  209. expect(preEpochUpperText).toContain('seq 2')
  210. expect(preEpochUpperText).not.toContain('seq 3')
  211. })
  212. })