loader-composition.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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. definitionId: '@deepseek-ai/dsh-command-compact',
  114. name: 'compact',
  115. description: 'Compact older conversation history',
  116. })
  117. const execution = await context.commands.execute(agent, '/compact', [], new AbortController().signal)
  118. if (execution === undefined) throw new Error('Loader composition did not resolve /compact')
  119. expect(execution.result).toEqual({
  120. kind: 'success',
  121. text: 'Compacted 3 history items (~99 tokens).',
  122. sourceEventSeq: RESULT.summarySeq,
  123. })
  124. expect(session.snapshotEvents().map(event => ({ type: event.type, data: event.data }))).toEqual([
  125. {
  126. type: 'command/run',
  127. data: {
  128. commandId: execution.commandId,
  129. name: 'compact',
  130. args: '',
  131. source: { kind: 'user' },
  132. },
  133. },
  134. {
  135. type: 'compaction/start',
  136. data: {
  137. compactionId: COMPACTION_ID,
  138. sourceCommandId: execution.commandId,
  139. turn: null,
  140. },
  141. },
  142. {
  143. type: 'compaction/summary',
  144. data: {
  145. compactionId: COMPACTION_ID,
  146. sourceCommandId: execution.commandId,
  147. summary: RESULT.summary,
  148. shadowedRange: RESULT.shadowedRange,
  149. shadowedSeqs: RESULT.shadowedSeqs,
  150. shadowedTokenCount: RESULT.shadowedTokenCount,
  151. provider: 'loader-test',
  152. model: 'loader-test',
  153. },
  154. },
  155. {
  156. type: 'compaction/end',
  157. data: {
  158. compactionId: COMPACTION_ID,
  159. sourceCommandId: execution.commandId,
  160. turn: null,
  161. },
  162. },
  163. {
  164. type: 'command/done',
  165. data: {
  166. commandId: execution.commandId,
  167. kind: 'success',
  168. text: 'Compacted 3 history items (~99 tokens).',
  169. sourceEventSeq: RESULT.summarySeq,
  170. },
  171. },
  172. ])
  173. expect(session.surface.nodes).toEqual([])
  174. expect(session.deriveMessages()).toEqual([])
  175. })
  176. })