catalog.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /**
  2. * Parent-owned durable subagent catalog events and their chunked projection.
  3. *
  4. * @module @deepseek-ai/dsh-subagent/catalog
  5. */
  6. import { z } from 'zod'
  7. import { appendChunkedList, chunkedListSchema, iterateChunkedList } from '@deepseek-ai/dsh-chunked-list'
  8. import type { ChunkedList } from '@deepseek-ai/dsh-chunked-list'
  9. import type {
  10. Session,
  11. SessionEvent,
  12. SessionHeader,
  13. SessionId,
  14. SessionLogOffset,
  15. } from '@deepseek-ai/dsh-session'
  16. import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection'
  17. import type { SubagentCatalogEntry } from './projection-types.ts'
  18. /** Current payload version for `subagent/catalog` events. */
  19. export const SUBAGENT_CATALOG_VERSION = 0
  20. /** One complete parent-owned catalog fact. */
  21. export type SubagentCatalogEvent =
  22. & {
  23. readonly version: 0
  24. readonly childId: SessionId
  25. readonly childCreatedAt: number
  26. } & (
  27. | { readonly mode: 'one-shot'; readonly label?: string }
  28. | { readonly mode: 'continuable'; readonly label: string }
  29. )
  30. declare module '@deepseek-ai/dsh-session/types' {
  31. interface SessionEventMap {
  32. /**
  33. * A direct child's complete discovery fact.
  34. * @param data - versioned parent-owned catalog entry.
  35. */
  36. 'subagent/catalog': SubagentCatalogEvent
  37. }
  38. }
  39. /** Host fold state for one parent catalog. */
  40. export interface SubagentCatalogState {
  41. readonly inheritedEventCount: SessionLogOffset
  42. readonly head?: ChunkedList<SubagentCatalogEvent> | undefined
  43. }
  44. const sessionIdSchema = z.string() as unknown as z.ZodType<SessionId>
  45. const oneShotCatalogSchema = z.object({
  46. version: z.literal(SUBAGENT_CATALOG_VERSION),
  47. childId: sessionIdSchema,
  48. childCreatedAt: z.number().int().nonnegative(),
  49. mode: z.literal('one-shot'),
  50. label: z.string().optional(),
  51. }).strict()
  52. const continuableCatalogSchema = z.object({
  53. version: z.literal(SUBAGENT_CATALOG_VERSION),
  54. childId: sessionIdSchema,
  55. childCreatedAt: z.number().int().nonnegative(),
  56. mode: z.literal('continuable'),
  57. label: z.string(),
  58. }).strict()
  59. const eventDataSchema = z.union([
  60. oneShotCatalogSchema,
  61. continuableCatalogSchema,
  62. ]) as unknown as z.ZodType<SubagentCatalogEvent>
  63. const viewSchema = z.array(z.union([
  64. oneShotCatalogSchema.omit({ version: true, childId: true, childCreatedAt: true }).extend({
  65. id: sessionIdSchema,
  66. createdAt: oneShotCatalogSchema.shape.childCreatedAt,
  67. }),
  68. continuableCatalogSchema.omit({ version: true, childId: true, childCreatedAt: true }).extend({
  69. id: sessionIdSchema,
  70. createdAt: continuableCatalogSchema.shape.childCreatedAt,
  71. }),
  72. ])) as unknown as z.ZodType<SubagentCatalogEntry[]>
  73. const stateSchema: z.ZodType<SubagentCatalogState> = z.object({
  74. inheritedEventCount: z.number().int().nonnegative() as unknown as z.ZodType<SessionLogOffset>,
  75. head: chunkedListSchema(eventDataSchema).optional(),
  76. }).strict()
  77. declare module '@deepseek-ai/dsh-session-projection/types' {
  78. interface SessionProjectionStateMap {
  79. subagentCatalog: SubagentCatalogState
  80. }
  81. }
  82. /**
  83. * Materialize direct children from their parent's successful creation facts.
  84. * @param state - parent catalog fold state.
  85. * @returns current direct-child rows in parent catalog event order.
  86. */
  87. function subagentCatalogEntries(state: SubagentCatalogState): SubagentCatalogEntry[] {
  88. const entries: SubagentCatalogEntry[] = []
  89. for (const data of iterateChunkedList(state.head)) {
  90. entries.push(data.mode === 'one-shot'
  91. ? {
  92. id: data.childId,
  93. createdAt: data.childCreatedAt,
  94. mode: data.mode,
  95. ...data.label === undefined ? {} : { label: data.label },
  96. }
  97. : {
  98. id: data.childId,
  99. createdAt: data.childCreatedAt,
  100. mode: data.mode,
  101. label: data.label,
  102. })
  103. }
  104. return entries
  105. }
  106. /** Parent-owned direct-child catalog projection; invalid own facts reject restoration. */
  107. export const subagentCatalogProjectionDefinition = {
  108. key: 'subagentCatalog',
  109. stateSchema,
  110. init: (_header: SessionHeader, inheritedEventCount: SessionLogOffset) => ({ inheritedEventCount }),
  111. apply: (state, event: SessionEvent) => {
  112. if (event.type !== 'subagent/catalog' || event.seq < state.inheritedEventCount) return state
  113. return { ...state, head: appendChunkedList(state.head, eventDataSchema.parse(event.data)) }
  114. },
  115. stateVersion: 2,
  116. wire: { viewSchema, view: subagentCatalogEntries },
  117. } satisfies ProjectionDefinition<'subagentCatalog', SubagentCatalogState>
  118. /**
  119. * Append a complete direct-child discovery fact to its parent Session.
  120. * @param parent - durable direct parent receiving the discovery fact.
  121. * @param child - established child's immutable Session metadata.
  122. * @param descriptor - mode-discriminated creation label frozen with the child.
  123. */
  124. export function establishCatalogChild(
  125. parent: Session,
  126. child: SessionHeader,
  127. descriptor:
  128. | { readonly mode: 'one-shot'; readonly label?: string }
  129. | { readonly mode: 'continuable'; readonly label: string },
  130. ): void {
  131. parent.append('subagent/catalog', descriptor.mode === 'one-shot'
  132. ? {
  133. version: SUBAGENT_CATALOG_VERSION,
  134. childId: child.id,
  135. childCreatedAt: child.createdAt,
  136. mode: descriptor.mode,
  137. ...descriptor.label === undefined ? {} : { label: descriptor.label },
  138. }
  139. : {
  140. version: SUBAGENT_CATALOG_VERSION,
  141. childId: child.id,
  142. childCreatedAt: child.createdAt,
  143. mode: descriptor.mode,
  144. label: descriptor.label,
  145. })
  146. }