index.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /**
  2. * @deepseek-ai/dsh-host-apiproxy — the API gateway every client shape shares:
  3. * the ApiProxy contract (api/: types + zod schemas, browser-safe), the fetch
  4. * carrier pair (fetch/: toFetchHandler on the host side, AbstractApiClient +
  5. * platform subclasses on the client side), and the host-side implementation
  6. * (api-proxy.ts: createApiProxy + the ApiProxyService gateway plugin providing
  7. * `ctx.apiProxy`). Transport-agnostic by design: this package registers no
  8. * routes — physical carriers wrap `ctx.apiProxy` themselves.
  9. *
  10. * The gateway consumes `ctx.agentDefaultModel`, the transport-independent default
  11. * shared with direct entry points. Switching models persists through that
  12. * service; sessions that have already logged a selection remain unchanged.
  13. */
  14. import { Context, Service } from '@deepseek-ai/cordis'
  15. import z from '@deepseek-ai/schemastery'
  16. import type {} from '@deepseek-ai/dsh-agent-default-model'
  17. import type { ApiProxy } from './api/index.ts'
  18. import { createApiProxy } from './api-proxy.ts'
  19. import {
  20. DEFAULT_SESSION_LOG_COMPRESSION_LEVEL,
  21. type SessionLogCompressionLevel,
  22. } from './session-export.ts'
  23. export type * from './api/index.ts'
  24. export { RpcId } from './api/rpc.ts'
  25. export { toFetchHandler } from './fetch/handler.ts'
  26. export { AbstractApiClient, InProcessApiClient } from './fetch/client.ts'
  27. export type { IApiClient } from './fetch/client.ts'
  28. export { createApiProxy } from './api-proxy.ts'
  29. export type { ApiProxyDefaults } from './api-proxy.ts'
  30. declare module '@deepseek-ai/cordis' {
  31. interface Context {
  32. /** The host-side ApiProxy implementation (the transport-agnostic gateway face). */
  33. apiProxy: ApiProxy
  34. }
  35. }
  36. /** Gateway plugin configuration. */
  37. export interface Config {
  38. /**
  39. * Whether this deployment can hand paths to a native desktop opener —
  40. * the `hasDocument` capability the agent-preset roster reports. Absent,
  41. * the platform is asked (macOS/Windows/WSL yes; Linux only with a display
  42. * server); set it explicitly where detection misleads, e.g. `false` in a
  43. * container whose DISPLAY points nowhere a user can see.
  44. */
  45. nativeOpen?: boolean
  46. /**
  47. * DEFLATE level for every session-log ZIP entry: `0` stores without
  48. * compression, `1` favors CPU/latency, and `9` favors archive size.
  49. * @default 6
  50. */
  51. sessionExportCompressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
  52. }
  53. /**
  54. * The API gateway service: implements the ApiProxy contract over the composed
  55. * host context and provides it as `ctx.apiProxy`. The Host cwd is the default
  56. * project directory.
  57. */
  58. export class ApiProxyService extends Service implements ApiProxy {
  59. static inject = [
  60. 'agentDefaultModel', 'agents', 'attachments', 'directoryPicker', 'llm', 'sessions', 'subagents', 'sessionQuery',
  61. 'tools', 'userQuestions', 'workspaceRegistry',
  62. ]
  63. static Config: z<Config> = z.object({
  64. nativeOpen: z.boolean(),
  65. sessionExportCompressionLevel: z.number().step(1).min(0).max(9)
  66. .default(DEFAULT_SESSION_LOG_COMPRESSION_LEVEL) as z<SessionLogCompressionLevel>,
  67. })
  68. readonly sessions: ApiProxy['sessions']
  69. readonly subagents: ApiProxy['subagents']
  70. readonly workspace: ApiProxy['workspace']
  71. readonly host: ApiProxy['host']
  72. readonly goals: ApiProxy['goals']
  73. readonly skills: ApiProxy['skills']
  74. readonly agentPresets: ApiProxy['agentPresets']
  75. readonly settings: ApiProxy['settings']
  76. readonly credentials: ApiProxy['credentials']
  77. readonly llm: ApiProxy['llm']
  78. readonly events: ApiProxy['events']
  79. readonly downloads: ApiProxy['downloads']
  80. readonly respond: ApiProxy['respond']
  81. constructor(ctx: Context, config: Config) {
  82. super(ctx, 'apiProxy')
  83. const api = createApiProxy(ctx, {
  84. defaultModelSelection: () => ctx.agentDefaultModel.currentSelection(),
  85. saveDefaultModelSelection: selection => ctx.agentDefaultModel.saveSelection(selection),
  86. cwd: process.cwd(),
  87. ...config.nativeOpen === undefined ? {} : { canOpenPath: () => config.nativeOpen as boolean },
  88. ...(config.sessionExportCompressionLevel === undefined
  89. ? {}
  90. : { sessionExportCompressionLevel: config.sessionExportCompressionLevel }),
  91. })
  92. this.sessions = api.sessions
  93. this.subagents = api.subagents
  94. this.workspace = api.workspace
  95. this.host = api.host
  96. this.goals = api.goals
  97. this.skills = api.skills
  98. this.agentPresets = api.agentPresets
  99. this.settings = api.settings
  100. this.credentials = api.credentials
  101. this.llm = api.llm
  102. this.events = api.events
  103. this.downloads = api.downloads
  104. // createApiProxy returns closures (no `this` capture), so the bind is
  105. // behavior-neutral.
  106. this.respond = api.respond.bind(api)
  107. }
  108. }
  109. export default ApiProxyService