workspace.client.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. /**
  2. * The Remote side of one Workspace registry under test: default answers for
  3. * every `workspace/*` command a `ClientWorkspaceModel` calls, builders for the
  4. * rows and frames of the `workspace/follow` stream, and a script that hands
  5. * each physical generation of that stream to its own script. The stream's
  6. * boot-time opening baseline lives in `remoteDefaultResponses`.
  7. */
  8. import { ok, type RemoteTable, type StreamScript } from '@deepseek-ai/dsh-remote-mock'
  9. import type { RemoteFailure, RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
  10. import type {
  11. WorkspaceArchiveSessionRequest,
  12. WorkspaceArchiveValue,
  13. WorkspaceCreateRequest,
  14. WorkspaceCreateValue,
  15. WorkspaceDeleteRequest,
  16. WorkspaceDeleteValue,
  17. WorkspaceFollowFrame,
  18. WorkspaceId,
  19. WorkspaceInsertBeforeRequest,
  20. WorkspaceInsertSessionBeforeRequest,
  21. WorkspaceOrderValue,
  22. WorkspaceRenameRequest,
  23. WorkspaceValue,
  24. WorkspaceView,
  25. } from '../../src/types.ts'
  26. /** The Workspace state stream endpoint. */
  27. export const FOLLOW = 'workspace/follow'
  28. /** The opening frame of one follow generation. */
  29. export type WorkspaceBaselineFrame = Extract<WorkspaceFollowFrame, { type: 'baseline' }>
  30. /**
  31. * The failure branch of a Remote result.
  32. * @param error - the owner-declared failure.
  33. * @returns the result.
  34. */
  35. export function err<T>(error: RemoteFailure): RemoteResult<T> {
  36. return { ok: false, error }
  37. }
  38. /**
  39. * One Workspace row whose id doubles as its title and path segment.
  40. * @param id - Workspace id.
  41. * @param overrides - fields replacing the derived ones.
  42. * @returns the row.
  43. */
  44. export function workspace(id: string, overrides: Partial<WorkspaceView> = {}): WorkspaceView {
  45. return {
  46. workspaceId: id as WorkspaceId,
  47. path: `/work/${id}`,
  48. title: id,
  49. sessionIds: [],
  50. createdAt: '2026-01-01T00:00:00.000Z',
  51. updatedAt: '2026-01-01T00:00:00.000Z',
  52. ...overrides,
  53. }
  54. }
  55. /**
  56. * A baseline frame holding the named Workspaces and no archived Sessions.
  57. * @param ids - Workspace ids in registry order.
  58. * @returns the frame.
  59. */
  60. export function baseline(...ids: readonly string[]): WorkspaceBaselineFrame {
  61. return { type: 'baseline', value: { items: ids.map(id => workspace(id)), archivedSessionIds: [] } }
  62. }
  63. /**
  64. * `workspace/follow` script running the n-th open on the n-th script; an open
  65. * past the last script fails the stream.
  66. * @param generations - one script per physical generation, in open order.
  67. * @returns the script.
  68. */
  69. export function followGenerations(generations: readonly StreamScript[]): StreamScript {
  70. let opened = 0
  71. return (args, stream) => {
  72. const generation = generations[opened++]
  73. if (generation === undefined) throw new Error('no scripted Workspace follow generation')
  74. return generation(args, stream)
  75. }
  76. }
  77. /** Default answers: every command accepted and echoed back as the row or set it names. */
  78. export const workspaceWorld: RemoteTable = {
  79. unary: {
  80. 'workspace/create': (request: WorkspaceCreateRequest): RemoteResult<WorkspaceCreateValue> => ok({
  81. workspace: workspace('created', { path: request.path }), created: true,
  82. }),
  83. 'workspace/rename': (request: WorkspaceRenameRequest): RemoteResult<WorkspaceValue> => ok({
  84. workspace: workspace(String(request.workspaceId), { title: request.title }),
  85. }),
  86. 'workspace/delete': (_request: WorkspaceDeleteRequest): RemoteResult<WorkspaceDeleteValue> => ok({ deleted: true }),
  87. 'workspace/insertBefore': (request: WorkspaceInsertBeforeRequest): RemoteResult<WorkspaceOrderValue> => ok({ workspaceIds: [request.workspaceId] }),
  88. 'workspace/insertSessionBefore': (request: WorkspaceInsertSessionBeforeRequest): RemoteResult<WorkspaceValue> => ok({
  89. workspace: workspace(String(request.workspaceId), { sessionIds: [request.sessionId] }),
  90. }),
  91. 'workspace/archiveSession': (request: WorkspaceArchiveSessionRequest): RemoteResult<WorkspaceArchiveValue> => ok({ archivedSessionIds: [request.sessionId] }),
  92. },
  93. }