sessions.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /**
  2. * sessions domain contract. Method signatures are the source of truth:
  3. * unary methods take the RpcRequest<P> narrow form and the impl echoes rpcId; everything
  4. * else references RequestPayload<'session.*'> / ResponseValue<'session.*'>.
  5. */
  6. import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
  7. import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session/types'
  8. // The pure-type outlet: api/ is browser-importable, and the package root's
  9. // cordis Context merge (via dsh-agent) must not enter client aggregates.
  10. import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/types'
  11. import type { RpcId, RpcRequest, RpcResponse } from './rpc.ts'
  12. import type { ToolEventView } from './events.ts'
  13. import type { WorkspaceId } from './workspace.ts'
  14. declare module '@deepseek-ai/dsh-llm' {
  15. interface MessageSourceMap {
  16. /**
  17. * The prompt's rpcId is passed through MessageSource into the `user/message` event
  18. * (the client uses it to reconcile the optimistically
  19. * echoed provisional message with the event stream). kind stays `'user'` — the model face
  20. * carries no transport vocabulary; rpcId is an extra durable-JSON field passed back to the client with the event.
  21. */
  22. 'user-rpc': { kind: 'user'; rpcId: RpcId }
  23. }
  24. }
  25. /**
  26. * One history page entry: the raw event plus the optional host-computed render
  27. * intent (same semantics as the mux frame's `view` slot — a pagination-time
  28. * derivation, never persisted).
  29. */
  30. export interface HistoryEntry {
  31. event: SessionEvent
  32. view?: ToolEventView
  33. }
  34. /**
  35. * The projection baseline riding the history tail page: one synchronous cut
  36. * over every registered projection unit, read from the registry's watermark
  37. * cache. `asOfSeq` is the seq of the last committed event every value
  38. * reflects — the window tail event seq (`-1` for an empty log, mirroring
  39. * `session/subscribed.lastSeq`), directly comparable with
  40. * `session/projection` frame seqs under the client's higher-seq-wins rule. A
  41. * key absent from `values` means the capability is absent (its domain plugin
  42. * is unmounted).
  43. */
  44. export interface SessionProjectionsBlock {
  45. /** Seq of the last event the values reflect; -1 for an empty log. */
  46. asOfSeq: number
  47. /** Whole current value per registered projection key. */
  48. values: Partial<SessionProjectionMap>
  49. }
  50. /** Complete model target selected for one session. */
  51. export interface ModelTarget {
  52. /** Registered provider route. */
  53. provider: string
  54. /** Provider-owned model id. */
  55. model: string
  56. /** Adapter-owned reasoning effort; absence preserves adapter/provider default behavior. */
  57. reasoningEffort?: string
  58. }
  59. /** One adapter-owned reasoning effort displayed for an exact model route. */
  60. export interface ModelReasoningEffort {
  61. /** Opaque value submitted back to the owning adapter. */
  62. id: string
  63. /** Adapter-supplied display name. */
  64. name: string
  65. /** Optional adapter-supplied description. */
  66. description?: string
  67. }
  68. /** Selectable reasoning metadata for one exact model route. */
  69. export interface ModelReasoning {
  70. /** Efforts in adapter-preferred display order. */
  71. efforts: ModelReasoningEffort[]
  72. /** Adapter-configured default; absence preserves the provider default. */
  73. defaultEffort?: string
  74. }
  75. /** One model displayed inside its provider group. */
  76. export interface ModelCatalogModel {
  77. /** Provider-owned model id. */
  78. id: string
  79. /** Provider-supplied display name. */
  80. name: string
  81. /** Optional provider-supplied description. */
  82. description?: string
  83. /** The current model was inserted because the advisory catalog omitted it. */
  84. unlisted?: true
  85. /** Exact-route reasoning metadata when the adapter exposes it. */
  86. reasoning?: ModelReasoning
  87. }
  88. /** One provider and the models it advertised successfully. */
  89. export interface ModelProviderGroup {
  90. /** Provider route id used for requests. */
  91. id: string
  92. /** Provider display name. */
  93. name: string
  94. /** Models in provider-preferred order. */
  95. models: ModelCatalogModel[]
  96. }
  97. /** A provider whose asynchronous catalog lookup failed. */
  98. export interface ModelCatalogFailure {
  99. /** Provider route id. */
  100. id: string
  101. /** Provider display name. */
  102. name: string
  103. /** Lookup failure diagnostic. */
  104. message: string
  105. }
  106. /** Detached model-directory snapshot for one session. */
  107. export interface SessionModels {
  108. /** Target selected for the session's next assembled step. */
  109. current: ModelTarget
  110. /** Successfully loaded provider groups. */
  111. groups: ModelProviderGroup[]
  112. /** Provider-local failures; successful groups remain usable. */
  113. failures: ModelCatalogFailure[]
  114. }
  115. /** Session list entry (v1 builds no index: list does readdir+stat). */
  116. export interface SessionSummary {
  117. sessionId: SessionId
  118. /** Persisted file mtime. */
  119. updatedAt: number
  120. /** Status of the attached agent; always false for cold (unattached) sessions. */
  121. running: boolean
  122. /**
  123. * Derived conversation-not-started bit: true while no turn has run (no
  124. * prompt was accepted yet). Standalone plugin events — command lifecycle
  125. * records, plan/mode, titles, goals — do not open a turn and therefore do
  126. * not clear it. Clients hide blank sessions from lists and reuse them for
  127. * New Session on the same workspace. Always false for cold sessions —
  128. * lazy persistence keeps a never-appended session out of the store, and a
  129. * listed cold session's log holds its turns.
  130. */
  131. blank: boolean
  132. /** fork/spawn lineage (session.header.parentSession passthrough); absent for root sessions. */
  133. parentSessionId?: SessionId
  134. /** Session working directory (header.cwd passthrough); absent when unrecorded. */
  135. cwd?: string
  136. /**
  137. * Projection baseline for this row, with zero log loads: attached sessions
  138. * read the registry's live watermark cut; cold sessions read the persisted
  139. * projection cache's stored rows — as stale as that session's last durable
  140. * checkpoint (`asOfSeq` says exactly how stale), never wrong, and directly
  141. * seedable into the client's per-session value store under its
  142. * higher-seq-wins rule (a list baseline can never overwrite a newer push
  143. * frame). Absent when no value is available (no registry, no cache row for
  144. * a cold session, or a fail-soft cache read miss); a listing client treats
  145. * absence as "no title yet", exactly like a blank session.
  146. */
  147. projections?: SessionProjectionsBlock
  148. }
  149. /** Session-domain unary methods (the map keys session.* of RpcMethodMap). */
  150. export interface SessionsApi {
  151. /** Lists persisted sessions (updatedAt descending). v1 returns everything; cursor is a reserved seat, unimplemented. */
  152. list(request: RpcRequest<{ cursor?: string }>): Promise<RpcResponse<{ items: SessionSummary[] }>>
  153. /**
  154. * Creates a real session and its idle agent. At most one of `workspaceId` /
  155. * `cwd` is accepted; an omitted project uses the Host cwd. A caller may
  156. * preallocate `sessionId`: retries with the same id and cwd return the same
  157. * session, while a different cwd fails with `session-conflict`. Workspace
  158. * creation attaches the session after publication; an attach failure
  159. * returns `workspace-attach-failed` with the published session id.
  160. */
  161. create(request: RpcRequest<{ workspaceId?: WorkspaceId; cwd?: string; sessionId?: SessionId }>):
  162. Promise<RpcResponse<{ sessionId: SessionId }>>
  163. /**
  164. * Reads a window of history events; page boundaries align to append-origin human-message
  165. * boundaries: one page = all raw events owned by a whole number of such messages (including
  166. * their chunk / tool events), never cut mid-message. Model-only replacement copies consume no
  167. * `maxMessages`, so a compaction's provenance stays on the page of its replacement. The tail
  168. * page (beforeSeq absent) additionally carries the in-flight
  169. * partial — chunk events already emitted for the last unfinalized message.
  170. * Each entry pairs the raw SessionEvent with the host-computed view (tool events whose
  171. * presenter produced one, evaluated against the registry at pagination time); the client
  172. * rebuilds the surface from the events with the shared fold.
  173. * The tail page — and only the tail page — additionally carries `projections`
  174. * when the deployment mounts the session-projection registry: every moment
  175. * the client needs a fresh baseline already pulls the tail page, and
  176. * loadOlder (the only beforeSeq path) is the only path that never needs one.
  177. * A deployment without the registry serves histories without the block.
  178. */
  179. history(request: RpcRequest<{ sessionId: SessionId; beforeSeq?: number; maxMessages?: number }>):
  180. Promise<RpcResponse<{ events: HistoryEntry[]; hasMore: boolean; projections?: SessionProjectionsBlock }>>
  181. /** Reads a fresh advisory model directory for this session. Provider lookups run independently. */
  182. models(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<SessionModels>>
  183. /**
  184. * Selects the complete target for this session. Exact model metadata
  185. * validates an optional reasoning effort, while catalog membership remains
  186. * advisory.
  187. */
  188. selectModel(request: RpcRequest<{
  189. sessionId: SessionId
  190. provider: string
  191. model: string
  192. reasoningEffort?: string
  193. }>):
  194. Promise<RpcResponse<{ selected: ModelTarget }>>
  195. /**
  196. * Sends a message. content is core's ContentBlock[] verbatim; mode maps 1:1 — queue→send, steer→steer.
  197. * A prompt whose content is exactly one text block starting with '/' is a slash command: the host
  198. * executes it through the command registry (mode-agnostic) and it is never sent to the model. A
  199. * successful command returns ok with the command slot (its success text, when the command produced
  200. * one — carried for future rendering; the state change is the feedback). A usage/state error is an
  201. * RPC error with code command-error; an unrecognized name is an RPC error with code unknown-command.
  202. */
  203. prompt(request: RpcRequest<{ sessionId: SessionId; mode: 'queue' | 'steer'; content: ContentBlock[] }>):
  204. Promise<RpcResponse<{ accepted: true; command?: { kind: 'success'; text?: string } }>>
  205. /** Stops: clears both FIFOs + aborts the current step (1:1 with agent.cancel). */
  206. cancel(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<{ accepted: true }>>
  207. }