ded90bffba
Streams one ZIP of the root session artifact plus each subagent descendant verbatim (the persistence readRaw bytes) from GET /api/session.export as a host-only download — no wire envelope, absent from IApiClient. The downloads domain owns the query schema, the fetch handler answers the GET alongside the SSE routes, and compression runs on the host with fflate's streaming Zip API.
106 lines
4.0 KiB
TypeScript
106 lines
4.0 KiB
TypeScript
/**
|
|
* @deepseek-ai/dsh-host-apiproxy — the API gateway every client shape shares:
|
|
* the ApiProxy contract (api/: types + zod schemas, browser-safe), the fetch
|
|
* carrier pair (fetch/: toFetchHandler on the host side, AbstractApiClient +
|
|
* platform subclasses on the client side), and the host-side implementation
|
|
* (api-proxy.ts: createApiProxy + the ApiProxyService gateway plugin providing
|
|
* `ctx.apiProxy`). Transport-agnostic by design: this package registers no
|
|
* routes — physical carriers wrap `ctx.apiProxy` themselves.
|
|
*
|
|
* The gateway consumes `ctx.agentDefaultModel`, the transport-independent default
|
|
* shared with direct entry points. Switching models persists through that
|
|
* service; sessions that have already logged a selection remain unchanged.
|
|
*/
|
|
|
|
import { Context, Service } from 'cordis'
|
|
import z from 'schemastery'
|
|
import type {} from '@deepseek-ai/dsh-agent-default-model'
|
|
import type { ApiProxy } from './api/index.ts'
|
|
import { createApiProxy } from './api-proxy.ts'
|
|
|
|
export type * from './api/index.ts'
|
|
export { RpcId } from './api/rpc.ts'
|
|
export { toFetchHandler } from './fetch/handler.ts'
|
|
export { AbstractApiClient, InProcessApiClient } from './fetch/client.ts'
|
|
export type { IApiClient } from './fetch/client.ts'
|
|
export { createApiProxy } from './api-proxy.ts'
|
|
export type { ApiProxyDefaults } from './api-proxy.ts'
|
|
|
|
declare module 'cordis' {
|
|
interface Context {
|
|
/** The host-side ApiProxy implementation (the transport-agnostic gateway face). */
|
|
apiProxy: ApiProxy
|
|
}
|
|
}
|
|
|
|
/** Gateway plugin config for native Host integration. */
|
|
export interface Config {
|
|
/**
|
|
* Whether this deployment can hand paths to a native desktop opener —
|
|
* the `hasDocument` capability the agent-preset roster reports. Absent,
|
|
* the platform is asked (macOS/Windows/WSL yes; Linux only with a display
|
|
* server); set it explicitly where detection misleads, e.g. `false` in a
|
|
* container whose DISPLAY points nowhere a user can see.
|
|
*/
|
|
nativeOpen?: boolean
|
|
}
|
|
|
|
/**
|
|
* The API gateway service: implements the ApiProxy contract over the composed
|
|
* host context and provides it as `ctx.apiProxy`. The Host cwd is the default
|
|
* project directory.
|
|
*/
|
|
export class ApiProxyService extends Service implements ApiProxy {
|
|
static inject = [
|
|
'agentDefaultModel', 'agents', 'attachments', 'directoryPicker', 'llm', 'sessions', 'subagents', 'sessionQuery',
|
|
'tools', 'userInteraction', 'workspace',
|
|
]
|
|
|
|
static Config: z<Config> = z.object({
|
|
nativeOpen: z.boolean(),
|
|
})
|
|
|
|
readonly sessions: ApiProxy['sessions']
|
|
readonly subagents: ApiProxy['subagents']
|
|
readonly workspace: ApiProxy['workspace']
|
|
readonly host: ApiProxy['host']
|
|
readonly commands: ApiProxy['commands']
|
|
readonly goals: ApiProxy['goals']
|
|
readonly skills: ApiProxy['skills']
|
|
readonly agentPresets: ApiProxy['agentPresets']
|
|
readonly settings: ApiProxy['settings']
|
|
readonly credentials: ApiProxy['credentials']
|
|
readonly llm: ApiProxy['llm']
|
|
readonly events: ApiProxy['events']
|
|
readonly downloads: ApiProxy['downloads']
|
|
readonly respond: ApiProxy['respond']
|
|
|
|
constructor(ctx: Context, config: Config) {
|
|
super(ctx, 'apiProxy')
|
|
const api = createApiProxy(ctx, {
|
|
defaultModelSelection: () => ctx.agentDefaultModel.currentSelection(),
|
|
saveDefaultModelSelection: selection => ctx.agentDefaultModel.saveSelection(selection),
|
|
cwd: process.cwd(),
|
|
...config.nativeOpen === undefined ? {} : { canOpenPath: () => config.nativeOpen as boolean },
|
|
})
|
|
this.sessions = api.sessions
|
|
this.subagents = api.subagents
|
|
this.workspace = api.workspace
|
|
this.host = api.host
|
|
this.commands = api.commands
|
|
this.goals = api.goals
|
|
this.skills = api.skills
|
|
this.agentPresets = api.agentPresets
|
|
this.settings = api.settings
|
|
this.credentials = api.credentials
|
|
this.llm = api.llm
|
|
this.events = api.events
|
|
this.downloads = api.downloads
|
|
// createApiProxy returns closures (no `this` capture), so the bind is
|
|
// behavior-neutral.
|
|
this.respond = api.respond.bind(api)
|
|
}
|
|
}
|
|
|
|
export default ApiProxyService
|