Просмотр исходного кода

refactor(file-upload): keep storage helpers and the HTTP route package-private

Stop re-exporting the verbatim file-store helpers from attachment-local, move
the raw-byte upload route handler into a non-entry module of file-upload, and
drop the redundant hook type re-exports from the Client entry. The route test
moves next to the module it exercises.
creatixchu 1 месяц назад
Родитель
Сommit
58ec1f0867

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: 5dcec11452e12400442d5dd539b6bc7e2491ed87
-config-catalog.zh.md: cde0be16f3f433cc24076029b671fde3783e6648
+config-catalog.md: af72b832723423745817fd340022e65c0c066d36
+config-catalog.zh.md: 6d961f22b3c29fbc4355e99c8de55823e014b74c

+ 1 - 1
docs/config-catalog.md

@@ -262,7 +262,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/attachment/attachment-local/src/index.ts:64`](../packages/attachment/attachment-local/src/index.ts)
+Source: [`packages/attachment/attachment-local/src/index.ts:61`](../packages/attachment/attachment-local/src/index.ts)
 
 <a id="deepseek-aidsh-bash-local"></a>
 

+ 1 - 1
docs/config-catalog.zh.md

@@ -264,7 +264,7 @@ export interface Config {
 }
 ```
 
-来源:[`packages/attachment/attachment-local/src/index.ts:64`](../packages/attachment/attachment-local/src/index.ts)
+来源:[`packages/attachment/attachment-local/src/index.ts:61`](../packages/attachment/attachment-local/src/index.ts)
 
 <a id="deepseek-aidsh-bash-local"></a>
 

+ 0 - 3
packages/attachment/attachment-local/src/index.ts

@@ -28,9 +28,6 @@ export { canPassThroughNormalization, normalizeImage } from './normalization.ts'
 export type { NormalizedImage, NormalizationPolicy } from './normalization.ts'
 export { commitPreparedImageFile, prepareImageFile, readImageFile, saveImageFile, validateImageFile } from './store.ts'
 export type { PreparedImageFile } from './store.ts'
-export {
-  fileLeafName, readFileStreamVerbatim, saveFileStreamVerbatim, saveFileVerbatim, storedFilePath,
-} from './file-store.ts'
 export { readRequestImageFile, requestImageVariantId } from './request-image.ts'
 
 /** Default maximum encoded bytes for one submitted image; oversized sources are refused, not shrunk. */

+ 0 - 2
packages/client/file-upload/src/client/contract.ts

@@ -33,5 +33,3 @@ export interface FileUploadService {
     onProgress?: (progress: FileUploadProgress) => void,
   ): Promise<RemoteResult<FileUploadValue>>
 }
-
-export type { ClientFileUploadHooks, FileUploadFetch } from '../types.ts'

+ 1 - 7
packages/client/file-upload/src/client/index.ts

@@ -4,13 +4,7 @@ import type { Context } from '@deepseek-ai/cordis'
 import { FileUploadRuntime } from './runtime.ts'
 import type { FileUploadService } from './contract.ts'
 
-export type {
-  ClientFileUploadHooks,
-  FileUploadBody,
-  FileUploadFetch,
-  FileUploadProgress,
-  FileUploadService,
-} from './contract.ts'
+export type { FileUploadProgress, FileUploadService } from './contract.ts'
 export type { FileUploadReceiptId, FileUploadValue } from '../types.ts'
 
 declare module '@deepseek-ai/cordis' {

+ 3 - 6
packages/client/file-upload/src/client/runtime.ts

@@ -6,13 +6,10 @@ import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
 import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import { FILE_UPLOAD_PATH } from '../protocol.ts'
-import type { EncodedFileUploadRequest, FileUploadValue } from '../types.ts'
 import type {
-  ClientFileUploadHooks,
-  FileUploadBody,
-  FileUploadFetch,
-  FileUploadService,
-} from './contract.ts'
+  ClientFileUploadHooks, EncodedFileUploadRequest, FileUploadFetch, FileUploadValue,
+} from '../types.ts'
+import type { FileUploadBody, FileUploadService } from './contract.ts'
 
 interface FileUploadRequest {
   readonly path: string

+ 81 - 0
packages/client/file-upload/src/http-route.ts

@@ -0,0 +1,81 @@
+/** Authenticated raw-byte upload route registered on the Connection fetch registry. */
+
+import { brandString } from '@deepseek-ai/dsh-brand'
+import type { SessionId } from '@deepseek-ai/dsh-session'
+import { remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol'
+import type { FileUploads } from './index.ts'
+import type { FileUploadValue } from './types.ts'
+
+type FileUploadHttpResult =
+  | { readonly ok: true; readonly value: FileUploadValue }
+  | {
+    readonly ok: false
+    readonly error: { readonly code: string; readonly message: string; readonly details: object }
+  }
+
+/**
+ * Handle one authenticated raw-byte upload.
+ * @param service - Host upload service receiving streamed bytes.
+ * @param request - authenticated HTTP request from Connection.
+ * @returns JSON result using HTTP status 200 after request validation.
+ */
+export async function handleFileUploadHttp(service: FileUploads, request: Request): Promise<Response> {
+  if (request.method !== 'POST') {
+    return new Response(null, { status: 405, headers: { allow: 'POST' } })
+  }
+  const mediaType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()
+  if (mediaType !== 'application/octet-stream') {
+    return new Response('content type must be application/octet-stream', { status: 415 })
+  }
+  const url = new URL(request.url)
+  const sessionId = url.searchParams.get('sessionId')
+  if (sessionId === null || sessionId === '') {
+    return new Response('sessionId is required', { status: 400 })
+  }
+  const name = url.searchParams.get('name') ?? undefined
+  let result: FileUploadHttpResult
+  try {
+    result = {
+      ok: true,
+      value: await service.uploadStream({
+        sessionId: brandString<SessionId>(sessionId),
+        data: requestBodyChunks(request.body),
+        signal: request.signal,
+        ...(name === undefined ? {} : { name }),
+      }),
+    }
+  } catch (error) {
+    const failure = remoteErrorOf(error)
+    result = {
+      ok: false,
+      error: failure !== undefined
+        ? { code: failure.code, message: failure.message, details: failure.details }
+        : {
+          code: 'gateway/internal',
+          message: error instanceof Error ? error.message : String(error),
+          details: {},
+        },
+    }
+  }
+  return new Response(JSON.stringify(result), {
+    status: 200,
+    headers: {
+      'content-type': 'application/json; charset=utf-8',
+      'cache-control': 'no-store',
+    },
+  })
+}
+
+async function* requestBodyChunks(body: ReadableStream<Uint8Array> | null): AsyncIterable<Uint8Array> {
+  if (body === null) return
+  const reader = body.getReader()
+  try {
+    while (true) {
+      const chunk = await reader.read()
+      if (chunk.done) return
+      yield chunk.value
+    }
+  } finally {
+    reader.releaseLock()
+  }
+}

+ 2 - 77
packages/client/file-upload/src/index.ts

@@ -4,16 +4,15 @@ import { randomUUID } from 'node:crypto'
 import type { Context } from '@deepseek-ai/cordis'
 import type { Agent } from '@deepseek-ai/dsh-agent'
 import type { FileAttachmentRef } from '@deepseek-ai/dsh-attachment'
-import { brandString } from '@deepseek-ai/dsh-brand'
 import type {} from '@deepseek-ai/dsh-client-connection'
 import type { CommandFileReceiptResolver } from '@deepseek-ai/dsh-commands'
 import { scopeOf } from '@deepseek-ai/dsh-scope'
 import type { Session, SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
-import { Remote, RemoteError, TypertRemoteService, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol'
+import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
+import { handleFileUploadHttp } from './http-route.ts'
 import { FILE_UPLOAD_PATH } from './protocol.ts'
 import type { EncodedFileUploadRequest, FileUploadReceiptId, FileUploadValue } from './types.ts'
 
-export { FILE_UPLOAD_PATH } from './protocol.ts'
 export type * from './types.ts'
 
 declare module '@deepseek-ai/cordis' {
@@ -38,13 +37,6 @@ export interface PromptFileBinding extends Disposable {
   commit(): void
 }
 
-type FileUploadHttpResult =
-  | { readonly ok: true; readonly value: FileUploadValue }
-  | {
-    readonly ok: false
-    readonly error: { readonly code: string; readonly message: string; readonly details: object }
-  }
-
 class PromptFileBindingGuard implements PromptFileBinding {
   private settled = false
 
@@ -262,73 +254,6 @@ export class FileUploads extends TypertRemoteService {
   }
 }
 
-/**
- * Handle one authenticated raw-byte upload.
- * @param service - Host upload service receiving streamed bytes.
- * @param request - authenticated HTTP request from Connection.
- * @returns JSON result using HTTP status 200 after request validation.
- */
-export async function handleFileUploadHttp(service: FileUploads, request: Request): Promise<Response> {
-  if (request.method !== 'POST') {
-    return new Response(null, { status: 405, headers: { allow: 'POST' } })
-  }
-  const mediaType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()
-  if (mediaType !== 'application/octet-stream') {
-    return new Response('content type must be application/octet-stream', { status: 415 })
-  }
-  const url = new URL(request.url)
-  const sessionId = url.searchParams.get('sessionId')
-  if (sessionId === null || sessionId === '') {
-    return new Response('sessionId is required', { status: 400 })
-  }
-  const name = url.searchParams.get('name') ?? undefined
-  let result: FileUploadHttpResult
-  try {
-    result = {
-      ok: true,
-      value: await service.uploadStream({
-        sessionId: brandString<SessionId>(sessionId),
-        data: requestBodyChunks(request.body),
-        signal: request.signal,
-        ...(name === undefined ? {} : { name }),
-      }),
-    }
-  } catch (error) {
-    const failure = remoteErrorOf(error)
-    result = {
-      ok: false,
-      error: failure !== undefined
-        ? { code: failure.code, message: failure.message, details: failure.details }
-        : {
-          code: 'gateway/internal',
-          message: error instanceof Error ? error.message : String(error),
-          details: {},
-        },
-    }
-  }
-  return new Response(JSON.stringify(result), {
-    status: 200,
-    headers: {
-      'content-type': 'application/json; charset=utf-8',
-      'cache-control': 'no-store',
-    },
-  })
-}
-
-async function* requestBodyChunks(body: ReadableStream<Uint8Array> | null): AsyncIterable<Uint8Array> {
-  if (body === null) return
-  const reader = body.getReader()
-  try {
-    while (true) {
-      const chunk = await reader.read()
-      if (chunk.done) return
-      yield chunk.value
-    }
-  } finally {
-    reader.releaseLock()
-  }
-}
-
 function fileNotStaged(): RemoteError {
   return new RemoteError(
     'session/attachment-invalid' as never,

+ 2 - 1
packages/api/session-controller/tests/file-upload-http.host.spec.ts → packages/client/file-upload/tests/file-upload-http.host.spec.ts

@@ -2,7 +2,8 @@ import { runInNewContext } from 'node:vm'
 import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
 import { describe, expect, it, vi } from 'vitest'
 import type { Mock } from 'vitest'
-import { FileUploads, handleFileUploadHttp } from '@deepseek-ai/dsh-client-file-upload'
+import { handleFileUploadHttp } from '../src/http-route.ts'
+import type { FileUploads } from '../src/index.ts'
 
 function request(input: {
   method?: string

+ 2 - 1
packages/client/file-upload/tests/file-upload.client.spec.ts

@@ -3,7 +3,8 @@ import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import { apply } from '../src/client/index.ts'
 import { fileUploadWorker, FileUploadRuntime } from '../src/client/runtime.ts'
-import type { ClientFileUploadHooks, FileUploadBody } from '../src/client/contract.ts'
+import type { FileUploadBody } from '../src/client/contract.ts'
+import type { ClientFileUploadHooks } from '../src/types.ts'
 
 interface UploadGlobal {
   __DSH_FILE_UPLOAD__?: ClientFileUploadHooks

+ 1 - 1
packages/client/file-upload/tsconfig.host.json

@@ -5,7 +5,7 @@
     "outDir": "lib/types",
     "tsBuildInfoFile": "lib/tsconfig.host.tsbuildinfo"
   },
-  "files": ["src/index.ts", "src/protocol.ts", "src/types.ts"],
+  "files": ["src/http-route.ts", "src/index.ts", "src/protocol.ts", "src/types.ts"],
   "references": [
     { "path": "../../../vendor/cordis" },
     { "path": "../../util/brand" },