loader-composition.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { mkdtemp, rm, writeFile } from 'node:fs/promises'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { pathToFileURL } from 'node:url'
  5. import { afterEach, describe, expect, it } from 'vitest'
  6. import { Context } from '@deepseek-ai/cordis'
  7. import Loader from '@deepseek-ai/cordis-plugin-loader'
  8. import Include from '@deepseek-ai/cordis-plugin-include'
  9. import type { Agent } from '@deepseek-ai/dsh-agent'
  10. import CommandRuntime from '@deepseek-ai/dsh-commands'
  11. import {
  12. CompactionId,
  13. CompactionEngine,
  14. type CompactionAgentContext,
  15. type CompactionResult,
  16. type CompactionTrigger,
  17. type ManualCompactAgentContext,
  18. } from '@deepseek-ai/dsh-compaction'
  19. import * as commandCompact from '@deepseek-ai/dsh-command-compact'
  20. import { Session, SessionId, SessionSeq } from '@deepseek-ai/dsh-session'
  21. const COMPACTION_ID = CompactionId('loader-command-compact-test')
  22. const RESULT: CompactionResult = {
  23. compactionId: COMPACTION_ID,
  24. startSeq: SessionSeq(1),
  25. summarySeq: SessionSeq(2),
  26. endSeq: SessionSeq(3),
  27. summary: [{ type: 'text', text: 'loader summary' }],
  28. shadowedRange: { start: SessionSeq(3), end: SessionSeq(8) },
  29. shadowedSeqs: [SessionSeq(3), SessionSeq(5), SessionSeq(8)],
  30. shadowedTokenCount: 99,
  31. }
  32. class LoaderCompactionEngine extends CompactionEngine {
  33. override compactIfNeeded(
  34. _agent: CompactionAgentContext,
  35. _trigger: CompactionTrigger,
  36. _signal: AbortSignal,
  37. ): Promise<CompactionResult | null> {
  38. return Promise.resolve(null)
  39. }
  40. override compactRegion(): Promise<CompactionResult> {
  41. return Promise.resolve(RESULT)
  42. }
  43. override compactNow(
  44. agent: ManualCompactAgentContext,
  45. _signal: AbortSignal,
  46. sourceCommandId?: Parameters<CompactionEngine['compactNow']>[2],
  47. ): Promise<CompactionResult | null> {
  48. const provenance = {
  49. compactionId: RESULT.compactionId,
  50. ...sourceCommandId === undefined ? {} : { sourceCommandId },
  51. }
  52. agent.session.append('compaction/start', { ...provenance, turn: null })
  53. agent.session.append('compaction/summary', {
  54. ...provenance,
  55. summary: RESULT.summary,
  56. shadowedRange: RESULT.shadowedRange,
  57. shadowedSeqs: RESULT.shadowedSeqs,
  58. shadowedTokenCount: RESULT.shadowedTokenCount,
  59. provider: 'loader-test',
  60. model: 'loader-test',
  61. })
  62. agent.session.append('compaction/end', { ...provenance, turn: null })
  63. return Promise.resolve({ ...RESULT, ...provenance })
  64. }
  65. }
  66. let root: string | undefined
  67. let context: Context | undefined
  68. afterEach(async () => {
  69. await context?.fiber.dispose()
  70. context = undefined
  71. if (root !== undefined) await rm(root, { recursive: true, force: true })
  72. root = undefined
  73. })
  74. describe('command-compact real Loader composition', () => {
  75. it('discovers and executes /compact through the assembled command plane', async () => {
  76. root = await mkdtemp(join(tmpdir(), 'dsh-command-compact-loader-'))
  77. const configPath = join(root, 'cordis.yml')
  78. await writeFile(configPath, [
  79. "- name: '@deepseek-ai/dsh-commands'",
  80. "- name: '@test/compact-backend'",
  81. "- name: '@deepseek-ai/dsh-command-compact'",
  82. '',
  83. ].join('\n'))
  84. context = new Context()
  85. context.baseUrl = pathToFileURL(root).href + '/'
  86. await context.plugin(Loader)
  87. context.loader.builtins.include = Include
  88. const modules = new Map<string, unknown>([
  89. ['@deepseek-ai/dsh-commands', CommandRuntime],
  90. ['@test/compact-backend', LoaderCompactionEngine],
  91. ['@deepseek-ai/dsh-command-compact', commandCompact],
  92. ])
  93. context.loader.internal = {
  94. version: 'v2',
  95. async import(specifier: string) {
  96. if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`)
  97. return modules.get(specifier)
  98. },
  99. } as unknown as NonNullable<typeof context.loader.internal>
  100. await context.loader.create({
  101. name: 'cordis:include',
  102. config: { path: pathToFileURL(configPath).href },
  103. })
  104. await context.loader.await()
  105. const session = Session.create(SessionId('loader-command-compact'))
  106. const agent = {
  107. session,
  108. status: 'idle',
  109. options: {},
  110. reserveTurnAdmission: () => () => undefined,
  111. } as unknown as Agent
  112. expect(context.commands.list(agent)).toContainEqual({
  113. name: 'compact',
  114. description: 'Compact older conversation history',
  115. })
  116. const execution = await context.commands.execute(agent, '/compact', [], new AbortController().signal)
  117. if (execution === undefined) throw new Error('Loader composition did not resolve /compact')
  118. expect(execution.result).toEqual({
  119. kind: 'success',
  120. text: 'Compacted 3 history items (~99 tokens).',
  121. sourceEventSeq: RESULT.summarySeq,
  122. })
  123. expect(session.snapshotEvents().map(event => ({ type: event.type, data: event.data }))).toEqual([
  124. {
  125. type: 'command/run',
  126. data: {
  127. commandId: execution.commandId,
  128. name: 'compact',
  129. args: '',
  130. source: { kind: 'user' },
  131. },
  132. },
  133. {
  134. type: 'compaction/start',
  135. data: {
  136. compactionId: COMPACTION_ID,
  137. sourceCommandId: execution.commandId,
  138. turn: null,
  139. },
  140. },
  141. {
  142. type: 'compaction/summary',
  143. data: {
  144. compactionId: COMPACTION_ID,
  145. sourceCommandId: execution.commandId,
  146. summary: RESULT.summary,
  147. shadowedRange: RESULT.shadowedRange,
  148. shadowedSeqs: RESULT.shadowedSeqs,
  149. shadowedTokenCount: RESULT.shadowedTokenCount,
  150. provider: 'loader-test',
  151. model: 'loader-test',
  152. },
  153. },
  154. {
  155. type: 'compaction/end',
  156. data: {
  157. compactionId: COMPACTION_ID,
  158. sourceCommandId: execution.commandId,
  159. turn: null,
  160. },
  161. },
  162. {
  163. type: 'command/done',
  164. data: {
  165. commandId: execution.commandId,
  166. kind: 'success',
  167. text: 'Compacted 3 history items (~99 tokens).',
  168. sourceEventSeq: RESULT.summarySeq,
  169. },
  170. },
  171. ])
  172. expect(session.surface.nodes).toEqual([])
  173. expect(session.deriveMessages()).toEqual([])
  174. })
  175. })