feat(apiproxy): host session-log download surface
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.
This commit is contained in:
22
packages/host/apiproxy/src/api/downloads.schema.ts
Normal file
22
packages/host/apiproxy/src/api/downloads.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* downloads domain zod schemas. The GET download surface has no wire
|
||||
* envelope: the request arrives as query parameters (all strings), so its
|
||||
* request schema parses the raw query-parameter object into the method's
|
||||
* exact request shape. SessionId brand cast point: sessionIdSchema, and only
|
||||
* there (hosted in sessions.schema like every other cast).
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import type { DownloadsApi } from './downloads.ts'
|
||||
import { sessionIdSchema } from './sessions.schema.ts'
|
||||
|
||||
/** session.export query params → the sessionLog request. */
|
||||
export const sessionLogQuerySchema = z
|
||||
.object({
|
||||
sessionId: sessionIdSchema,
|
||||
includeDescendants: z.string().optional(),
|
||||
})
|
||||
.transform(query => ({
|
||||
sessionId: query.sessionId,
|
||||
...(query.includeDescendants === 'true' ? { includeDescendants: true } : {}),
|
||||
})) satisfies z.ZodType<Parameters<DownloadsApi['sessionLog']>[0]>
|
||||
25
packages/host/apiproxy/src/api/downloads.ts
Normal file
25
packages/host/apiproxy/src/api/downloads.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* downloads domain contract: host-only download surfaces — the GET-download
|
||||
* channel family, the mirror of the SSE-stream `events` domain. No wire
|
||||
* envelope: the carrier's GET routes answer these directly, and the browser
|
||||
* `IApiClient` never exposes them.
|
||||
*/
|
||||
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
|
||||
/** Host-only download surfaces (no wire envelope; absent from IApiClient). */
|
||||
export interface DownloadsApi {
|
||||
/**
|
||||
* Stream one session-log ZIP — the root artifact verbatim plus each subagent
|
||||
* descendant's — as an attachment response. The carrier's GET route answers
|
||||
* this directly; the browser never calls it.
|
||||
* @param request - the root session id and whether to include descendants.
|
||||
* @param signal - cancellation for the underlying reads.
|
||||
* @returns the ZIP attachment response; missing services answer 500 and a
|
||||
* missing root session 404 before any byte is produced.
|
||||
*/
|
||||
sessionLog(
|
||||
request: { sessionId: SessionId; includeDescendants?: boolean },
|
||||
signal: AbortSignal,
|
||||
): Promise<Response>
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import type { GoalsApi } from './goals.ts'
|
||||
import type { SettingsApi } from './settings.ts'
|
||||
import type { CredentialsApi } from './credentials.ts'
|
||||
import type { LlmApi } from './llm.ts'
|
||||
import type { DownloadsApi } from './downloads.ts'
|
||||
import type { ClientResponse, RpcReceipt } from './rpc.ts'
|
||||
|
||||
/** Root interface of the unified API surface. New client-request domain = one new file pair + one field here + one map row. */
|
||||
@@ -32,6 +33,8 @@ export interface ApiProxy {
|
||||
settings: SettingsApi
|
||||
credentials: CredentialsApi
|
||||
llm: LlmApi
|
||||
/** Host-only download surfaces (GET, no wire envelope); absent from IApiClient. */
|
||||
downloads: DownloadsApi
|
||||
/** Response entry for server-requests (client-response, echoing their rpcId); not a domain method (four-quadrant model). */
|
||||
respond(message: ClientResponse): Promise<RpcReceipt>
|
||||
}
|
||||
@@ -39,9 +42,8 @@ export interface ApiProxy {
|
||||
// ---- Domain interfaces and payload entities ----
|
||||
export type {
|
||||
HistoryEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
||||
ModelReasoningEffort, ModelSelection, PromptContentPart, QueueAction, SessionModels, SessionProjectionsBlock,
|
||||
SessionSearchItem,
|
||||
SessionsApi, SessionSummary,
|
||||
ModelReasoningEffort, ModelSelection, PromptContentPart, QueueAction, SessionModels,
|
||||
SessionProjectionsBlock, SessionSearchItem, SessionsApi, SessionSummary,
|
||||
} from './sessions.ts'
|
||||
export type { DirectoryEntry, DirectoryListing, HostApi } from './host.ts'
|
||||
export type {
|
||||
@@ -57,6 +59,7 @@ export type { GoalsApi, GoalId, GoalRef } from './goals.ts'
|
||||
export type { SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView } from './settings.ts'
|
||||
export type { CredentialsApi, CredentialView } from './credentials.ts'
|
||||
export type { ConfigurableProviderView, DiscoveredModelView, LlmApi } from './llm.ts'
|
||||
export type { DownloadsApi } from './downloads.ts'
|
||||
export type { ApprovalResponsePayload } from './approvals.ts'
|
||||
|
||||
export type { QuestionResponsePayload } from './questions.ts'
|
||||
|
||||
Reference in New Issue
Block a user