api-proxy-skills-cold.spec.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import { Context } from '@deepseek-ai/cordis'
  2. import AgentRegistry from '@deepseek-ai/dsh-agent'
  3. import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
  4. import { SessionQueryError, type SessionObservation } from '@deepseek-ai/dsh-session-query'
  5. import type {} from '@deepseek-ai/dsh-skill'
  6. import { describe, expect, it, vi } from 'vitest'
  7. import { createApiProxy } from '../src/api-proxy.ts'
  8. import { RpcId } from '../src/api/rpc.ts'
  9. describe('skill catalog Session inspection', () => {
  10. it('reads a detached Session without resuming its Agent', async () => {
  11. const ctx = new Context()
  12. await ctx.plugin(SessionStore)
  13. await ctx.plugin(AgentRegistry)
  14. const sessionId = SessionId('cold-skills')
  15. const resolveAgent = vi.fn()
  16. const dispose = vi.fn()
  17. const observeSession = vi.fn(() => Promise.resolve({
  18. source: 'live',
  19. header: { version: 0 as const, id: sessionId, createdAt: 1, cwd: '/cold/project' },
  20. events: [],
  21. cursor: -1,
  22. projections: { asOfSeq: -1, values: {} },
  23. retain: () => { throw new Error('not retained') },
  24. [Symbol.dispose]: dispose,
  25. } satisfies SessionObservation))
  26. ctx.provide('sessionQuery', { observeSession } as never)
  27. ctx.provide('sessionController', { resolveAgent } as never)
  28. const list = vi.fn(() => Promise.resolve([{
  29. name: 'review',
  30. description: 'Review the current change.',
  31. invocation: { modelInvocable: true, userInvocable: true },
  32. }]))
  33. ctx.provide('skills', { list } as never)
  34. const api = createApiProxy(ctx, {
  35. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  36. cwd: '/default',
  37. })
  38. const response = await api.skills.list({ rpcId: RpcId('cold-skills'), payload: { sessionId } })
  39. expect(response.result).toEqual({
  40. ok: true,
  41. value: {
  42. skills: [{
  43. name: 'review',
  44. description: 'Review the current change.',
  45. modelInvocable: true,
  46. }],
  47. },
  48. })
  49. expect(observeSession).toHaveBeenCalledWith(sessionId)
  50. expect(dispose).toHaveBeenCalledOnce()
  51. expect(resolveAgent).not.toHaveBeenCalled()
  52. expect(list).toHaveBeenCalledWith({ cwd: '/cold/project', scope: undefined })
  53. })
  54. it('preserves missing and failed cold inspection as distinct API errors', async () => {
  55. const sessionId = SessionId('missing-skills')
  56. for (const fixture of [
  57. {
  58. error: new SessionQueryError(
  59. 'session "missing-skills" not found',
  60. 'SESSION_QUERY_SESSION_NOT_FOUND',
  61. ),
  62. code: 'session-not-found',
  63. },
  64. { error: new Error('storage offline'), code: 'internal' },
  65. ] as const) {
  66. const ctx = new Context()
  67. await ctx.plugin(SessionStore)
  68. await ctx.plugin(AgentRegistry)
  69. ctx.provide('sessionQuery', {
  70. observeSession: () => Promise.reject(fixture.error),
  71. } as never)
  72. ctx.provide('skills', { list: vi.fn() } as never)
  73. const api = createApiProxy(ctx, {
  74. defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
  75. cwd: '/default',
  76. })
  77. const response = await api.skills.list({ rpcId: RpcId(fixture.code), payload: { sessionId } })
  78. expect(response.result).toMatchObject({ ok: false, error: { code: fixture.code } })
  79. }
  80. })
  81. })