Address the review of this pull request: - A failed image upload keeps the Host's attachment refusal reason. The thumbnail of a refused image names the reason instead of offering a retry that cannot succeed, and Enter while uploads hold the send names the first failure; a transport failure still offers retry, and an image without a file name uses the pending-image label. - An image upload stops reading at the smaller of the per-image and per-message byte limits. - A carried image draft moving to a Session that sends images inline drops its upload instead of staging it again. - The image upload ready state no longer carries an unread image ref. - Docs describe stored-image admission and the command image path, and the attachment rail test builds typed props instead of casting through unknown.
6.3 KiB
description, kind
| description | kind |
|---|---|
| Session-addressed browser file uploads with streaming intake, progress, cancellation, and staged receipts for later prompts. | package-reference |
@deepseek-ai/dsh-client-file-upload
English | 中文
Summary
This package lets browser features store a Blob, exact bytes, or a ReadableStream<Uint8Array> for one Session and receive an opaque receipt for a later prompt. Served pages send Blob and stream bodies without aggregating their bytes on the page thread; pages whose Host runs in another execution context supply a Fetch-shaped carrier before Cordis boots. Callers can observe consumed bytes and cancel an active operation. A stream body is consumed once and transfers ownership when it crosses a Worker boundary. An image upload stages a normalized prompt image that a later prompt cites by receipt.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
Mount the package before a consumer that injects fileUpload, then call ctx.fileUpload.upload(sessionId, body, name, signal, onProgress), or ctx.fileUpload.uploadImage(sessionId, blob, mediaType, name, signal, onProgress) for a prompt image. The Session identity addresses both the raw route and generated Remote fallback; callers do not assemble either request.
- id: file-upload
name: '@deepseek-ai/dsh-client-file-upload'
The package has no Cordis configuration fields. A Blob uses XMLHttpRequest inside a dedicated Worker so the service can report browser upload progress, including the total when the browser provides it. A ReadableStream transfers to that Worker and feeds Fetch incrementally; progress reports consumed bytes without a total. An AbortSignal terminates the dedicated Worker or reaches a page-owned carrier. Exact bytes use the generated Remote.
Understand the implementation
Implementation internals — click to expand
The Client plugin provides ctx.fileUpload. Its upload() method receives the owning Session identity, assembles the raw route request, and invokes the generated Remote for exact bytes. The provider reads the optional pre-Cordis __DSH_FILE_UPLOAD__ hook once. Without a hook, each Blob or stream request owns a short-lived Worker and releases it after completion, failure, or cancellation. With the hook, the service sends the body through the page-owned Fetch carrier; the Web Worker runtime transfers stream bodies through its request frame and exposes them to the Host HTTP bridge as backpressured chunks.
The Host plugin provides ctx.fileUploads. It owns the authenticated streaming route, encoded Remote fallback, command receipt resolver, and staged-receipt lifecycle; encoded admission, attachment-error recognition, and byte storage stay behind ctx.attachments. A raw upload with a mediaType query parameter reads at most the smaller of the deployment's per-image and per-message image byte limits and stores the image through ctx.attachments.saveImages; its receipt records the upload's byte length and resolves to that image during prompt admission, where those bytes count toward the per-message image byte limit, and commands accept only file receipts. A cancelled image upload receives no receipt: cancellation before storage begins stores nothing, and an image whose storage already began stays stored. Host callers supply an Agent with its runtime-owned context; receipt tables use that Agent's exact Session object as their key. The Session Controller registers the resolver that can resume a cold ordinary Agent and consumes receipts during prompt admission. Prompt delivery holds each receipt binding in a disposable transaction: disposal restores the previous binding until successful delivery commits it, and queue or history observation then retires the committed receipt.
| File | Role |
|---|---|
src/index.ts |
Host streaming route, attachment-service admission, and Session-owned receipt lifecycle |
src/types.ts |
encoded request, receipt, and durable result types |
src/client/contract.ts |
Client upload, progress, and page-hook types |
src/client/runtime.ts |
Dedicated Worker and page-owned carrier implementations |
src/client/index.ts |
Client plugin registration and ctx.fileUpload declaration |
Each upload receipt belongs to one exact Session, and each request uses one selected carrier. Unsupported stream carriers fail before the body is sent.
Further Exploration
- Connection — authenticated RPC, exact Host routes, and connection generations.
- Session Controller — prompt admission that consumes staged receipts.
- Web Worker runtime — the page-to-Host Worker request tunnel.
- Client group map — browser services and UI feature packages.
Model Experience
None, as this package transfers browser request bodies and contributes no model input.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
These limits apply to the transport operation itself.
- Uploads are not resumable — a failed or cancelled retry starts from the first byte.
- Stream bodies are one-shot — transferring a
ReadableStreamlocks the caller's object, so retry requires a newly created stream. - Stream progress has no total — callers receive consumed-byte counts because the stream API carries no byte length.
- The browser Worker is self-contained — its source is emitted from a function string. Adding runtime imports requires moving it to a standalone Worker entry bundled by tsdown.
Dev Note
Working context for maintainers — click to expand
None.