index.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /** Read-only projection of the current Cordis Loader plugin entries. */
  2. import type { Context, FiberState } from '@deepseek-ai/cordis'
  3. import type {} from '@deepseek-ai/cordis-plugin-loader'
  4. // Type-only: the optional agent-preset roster resolved through `ctx.get`.
  5. import type {} from '@deepseek-ai/dsh-agent-presets'
  6. import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
  7. // Typert-generated ./typert and ./remote artifacts import Zod at runtime.
  8. import type {} from 'zod'
  9. import type {
  10. AgentPresetPluginGroup,
  11. PluginEntryId,
  12. PluginFiberPhase,
  13. PluginInventoryEntry,
  14. PluginInventorySnapshot,
  15. } from './types.ts'
  16. export type * from './types.ts'
  17. /**
  18. * Brand an existing Loader-tree entry id at the owning boundary.
  19. * @param value - the entry id as the Loader tree spells it.
  20. * @returns the same id as the inventory's branded entry id.
  21. */
  22. export function pluginEntryId(value: string): PluginEntryId {
  23. return value as PluginEntryId
  24. }
  25. /** Runtime mirror: FiberState is a cross-package const enum. */
  26. const FIBER_STATE = {
  27. PENDING: 0 as FiberState.PENDING,
  28. LOADING: 1 as FiberState.LOADING,
  29. ACTIVE: 2 as FiberState.ACTIVE,
  30. FAILED: 3 as FiberState.FAILED,
  31. DISPOSED: 4 as FiberState.DISPOSED,
  32. UNLOADING: 5 as FiberState.UNLOADING,
  33. } as const
  34. /** Complete public projection of Cordis Fiber states. */
  35. const FIBER_PHASE = {
  36. [FIBER_STATE.PENDING]: 'pending',
  37. [FIBER_STATE.LOADING]: 'loading',
  38. [FIBER_STATE.ACTIVE]: 'active',
  39. [FIBER_STATE.FAILED]: 'failed',
  40. [FIBER_STATE.DISPOSED]: null,
  41. [FIBER_STATE.UNLOADING]: 'unloading',
  42. } as const satisfies Record<FiberState, PluginFiberPhase>
  43. /** Remote-only service exposing the Loader's current non-group entry state. */
  44. export class PluginInventoryGateway extends TypertRemoteService {
  45. static inject = ['loader']
  46. constructor(ctx: Context) {
  47. super(ctx, 'pluginInventory')
  48. }
  49. /**
  50. * Read the Loader directly on every call. Cordis's internal plugin/status
  51. * events already maintain Entry.fiber and Fiber.state, so a second cache
  52. * would only add another lifecycle truth to keep synchronized.
  53. *
  54. * When an agent-preset roster is composed, the snapshot also carries each
  55. * preset's composition rows, because those rows — not the Loader's own
  56. * entries — are where a deployment that mounts the roster runs its
  57. * model-facing plugins.
  58. * @returns Current non-group Loader entries in Loader order, with per-preset
  59. * compositions when a roster is composed.
  60. */
  61. @Remote('list')
  62. async list(): Promise<PluginInventorySnapshot> {
  63. return readPluginInventory(this.ctx)
  64. }
  65. }
  66. export default PluginInventoryGateway
  67. /** Read current Loader entries and optional preset compositions.
  68. * @param ctx Context with the Loader service.
  69. * @returns Current inventory without a separate runtime cache.
  70. */
  71. export async function readPluginInventory(ctx: Context): Promise<PluginInventorySnapshot> {
  72. const entries: PluginInventoryEntry[] = []
  73. for (const entry of ctx.loader.entries()) {
  74. if (entry.options.group) continue
  75. entries.push({
  76. entryId: pluginEntryId(entry.id),
  77. moduleName: entry.options.name,
  78. enabled: !entry.disabled,
  79. fiberPhase: entry.fiber === undefined ? null : FIBER_PHASE[entry.fiber.state],
  80. })
  81. }
  82. const presets = ctx.get('agentPresets')
  83. const management = ctx.get('pluginManager') === undefined ? {} : { managementAvailable: true }
  84. if (presets === undefined) return { entries, ...management }
  85. const agentPresets: AgentPresetPluginGroup[] = (await presets.compositionInventory()).map(
  86. composition => ({
  87. ...composition,
  88. rows: composition.rows.map(({ fiberState, ...row }) => ({
  89. ...row,
  90. fiberPhase: fiberState === undefined ? null : FIBER_PHASE[fiberState],
  91. })),
  92. }),
  93. )
  94. return { entries, agentPresets, ...management }
  95. }