mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-12 04:01:20 +00:00
refactor(client): extract background file upload service
This commit is contained in:
@@ -92,14 +92,14 @@ export interface ISession {
|
||||
/**
|
||||
* Persist one browser file verbatim and stage it for a later prompt on this
|
||||
* session. The returned opaque receipt is what a prompt file part cites.
|
||||
* @param data - browser Blob or exact file bytes.
|
||||
* @param data - browser Blob, exact file bytes, or a one-shot byte stream.
|
||||
* @param name - optional display name; the host sanitizes the stored leaf name.
|
||||
* @param signal - optional cancellation for the active upload.
|
||||
* @param onProgress - optional byte-progress observer for background Blob uploads.
|
||||
* @param onProgress - optional byte-progress observer for background Blob or stream uploads.
|
||||
* @returns the staged-upload receipt and durable file reference, or the business error.
|
||||
*/
|
||||
uploadFile(
|
||||
data: Blob | Uint8Array,
|
||||
data: Blob | Uint8Array | ReadableStream<Uint8Array>,
|
||||
name?: string,
|
||||
signal?: AbortSignal,
|
||||
onProgress?: (progress: { readonly loaded: number; readonly total?: number }) => void,
|
||||
|
||||
@@ -31,10 +31,22 @@ export interface PendingSubmissionImage {
|
||||
readonly height?: number
|
||||
}
|
||||
|
||||
/** Image branch of a local submission echo attachment. */
|
||||
export interface PendingSubmissionImageAttachment {
|
||||
readonly type: 'image'
|
||||
readonly value: PendingSubmissionImage
|
||||
}
|
||||
|
||||
/** File branch of a local submission echo attachment. */
|
||||
export interface PendingSubmissionFileAttachment {
|
||||
readonly type: 'file'
|
||||
readonly value: FileAttachmentRef
|
||||
}
|
||||
|
||||
/** One attachment displayed by a local submission echo, in prompt order. */
|
||||
export type PendingSubmissionAttachment =
|
||||
| ({ readonly type: 'image' } & PendingSubmissionImage)
|
||||
| { readonly type: 'file'; readonly attachment: FileAttachmentRef }
|
||||
| PendingSubmissionImageAttachment
|
||||
| PendingSubmissionFileAttachment
|
||||
|
||||
/** Client surface selected when a local submission begins. */
|
||||
export type PendingSubmissionPlacement = 'transcript' | 'queued' | 'steering'
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type {} from '@deepseek-ai/dsh-agent/types'
|
||||
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-file-upload/client'
|
||||
import { createSessionControlStream } from './transport.ts'
|
||||
import { ClientSessions } from './sessions/service.ts'
|
||||
import type { SessionRemotes } from './sessions/remotes.ts'
|
||||
@@ -61,7 +62,9 @@ export type {
|
||||
OpenState,
|
||||
PendingSubmission,
|
||||
PendingSubmissionAttachment,
|
||||
PendingSubmissionFileAttachment,
|
||||
PendingSubmissionImage,
|
||||
PendingSubmissionImageAttachment,
|
||||
PendingSubmissionPlacement,
|
||||
PromptError,
|
||||
QueuedMessage,
|
||||
@@ -78,6 +81,7 @@ declare module '@deepseek-ai/cordis' {
|
||||
/** Required Remote and Context projection services. */
|
||||
export const inject = [
|
||||
'connection',
|
||||
'fileUpload',
|
||||
'typert',
|
||||
'remote',
|
||||
'remote.commands',
|
||||
@@ -90,9 +94,8 @@ export const inject = [
|
||||
* @param ctx - Client Cordis context.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
const connection = ctx.get('connection') as ConnectionHandle
|
||||
const remotes = ctx.remote as unknown as SessionRemotes
|
||||
const sessions = new ClientSessions(ctx, remotes, connection.backgroundUploads)
|
||||
const sessions = new ClientSessions(ctx, remotes)
|
||||
ctx.remote.$on('api-session/added', (summary) => { sessions.handleSessionAdded(summary) })
|
||||
ctx.remote.$on('api-session/removed', (sessionId) => { sessions.handleSessionRemoved(sessionId) })
|
||||
ctx.remote.$on('api-session/status', (sessionId, running) => {
|
||||
|
||||
@@ -25,7 +25,6 @@ import { Notifier } from './notifier.ts'
|
||||
import { ProjectionValueStore } from './projection-store.ts'
|
||||
import { Session } from './session.ts'
|
||||
import type { SessionRemotes } from './remotes.ts'
|
||||
import type { BackgroundUploadTransport } from '@deepseek-ai/dsh-client-connection/client'
|
||||
|
||||
function sessionSeqCursor(value: number): SessionSeqCursor {
|
||||
return value === -1 ? -1 : SessionSeq(value)
|
||||
@@ -154,7 +153,6 @@ export class SessionManager {
|
||||
private readonly remote: SessionRemotes,
|
||||
restoredSelection?: SessionId,
|
||||
restoredAddress?: SubagentAddress,
|
||||
private readonly backgroundUploads?: BackgroundUploadTransport,
|
||||
) {
|
||||
this.selected = restoredSelection
|
||||
if (restoredAddress !== undefined) this.addresses.set(restoredAddress.childSessionId, restoredAddress)
|
||||
@@ -334,7 +332,6 @@ export class SessionManager {
|
||||
this.recordMutation({ kind: 'engaged', sessionId: engaged.sessionId })
|
||||
},
|
||||
projections: this.projectionStore(sessionId),
|
||||
...(this.backgroundUploads === undefined ? {} : { backgroundUploads: this.backgroundUploads }),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import type { AgentContext, ISessions } from '../contract/sessions.ts'
|
||||
import { createScope, scopeOf as scopeTagOf } from '../scope.ts'
|
||||
import { SessionManager } from './manager.ts'
|
||||
import type { SessionRemotes } from './remotes.ts'
|
||||
import type { BackgroundUploadTransport } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { SessionListPhase, SessionSearchResultItem, SubagentCatalogSnapshot } from './manager.ts'
|
||||
import type { Session } from './session.ts'
|
||||
|
||||
@@ -222,7 +221,6 @@ export class ClientSessions implements ISessions {
|
||||
constructor(
|
||||
private readonly rootCtx: Context,
|
||||
remote: SessionRemotes,
|
||||
backgroundUploads?: BackgroundUploadTransport,
|
||||
) {
|
||||
this.selection = createSnapshotStore<SessionSelection>(
|
||||
{},
|
||||
@@ -232,7 +230,6 @@ export class ClientSessions implements ISessions {
|
||||
remote,
|
||||
restored.sessionId,
|
||||
restored.subagentAddress,
|
||||
backgroundUploads,
|
||||
)
|
||||
this.list = createSnapshotStore<SessionListState>({
|
||||
ids: [], byId: {}, current: undefined, phase: 'pending',
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { bytesToBase64, randomUUID } from '@deepseek-ai/dsh-util-crypto'
|
||||
import type {
|
||||
BackgroundUploadProgress, BackgroundUploadTransport,
|
||||
} from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { FileUploadProgress } from '@deepseek-ai/dsh-client-file-upload/client'
|
||||
import type { AttachmentIdType, FileAttachmentRef, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
||||
import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
@@ -76,8 +74,6 @@ export interface SessionOptions {
|
||||
* private store (bare object-layer construction).
|
||||
*/
|
||||
projections?: ProjectionValueStore
|
||||
/** Physical large-body carrier supplied by the active browser Connection. */
|
||||
backgroundUploads?: BackgroundUploadTransport
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,15 +298,17 @@ export class Session implements SessionFace {
|
||||
/**
|
||||
* Persist one browser file verbatim and stage it for a later prompt on this
|
||||
* session (ordinary sessions only; subagent conversations refuse).
|
||||
* @param data - exact file bytes.
|
||||
* @param data - exact bytes, a browser Blob, or a one-shot byte stream.
|
||||
* @param name - optional display name; the host sanitizes the stored leaf name.
|
||||
* @param signal - optional cancellation for the active upload.
|
||||
* @param onProgress - optional byte-progress observer for background bodies.
|
||||
* @returns the staged-upload receipt and durable file reference, or the business error.
|
||||
*/
|
||||
async uploadFile(
|
||||
data: Blob | Uint8Array,
|
||||
data: Blob | Uint8Array | ReadableStream<Uint8Array>,
|
||||
name?: string,
|
||||
signal?: AbortSignal,
|
||||
onProgress?: (progress: BackgroundUploadProgress) => void,
|
||||
onProgress?: (progress: FileUploadProgress) => void,
|
||||
): Promise<RemoteResult<SessionUploadFileValue>> {
|
||||
if (this.address !== undefined) {
|
||||
return {
|
||||
@@ -322,10 +320,10 @@ export class Session implements SessionFace {
|
||||
),
|
||||
}
|
||||
}
|
||||
if (data instanceof Blob && this.options.backgroundUploads !== undefined) {
|
||||
if (!(data instanceof Uint8Array) && this.actx?.fileUpload.available === true) {
|
||||
const query = new URLSearchParams({ sessionId: this.sessionId })
|
||||
if (name !== undefined) query.set('name', name)
|
||||
const response = await this.options.backgroundUploads.post({
|
||||
const response = await this.actx.fileUpload.post({
|
||||
path: `${SESSION_FILE_UPLOAD_PATH}?${query.toString()}`,
|
||||
body: data,
|
||||
headers: { 'content-type': 'application/octet-stream' },
|
||||
@@ -337,6 +335,9 @@ export class Session implements SessionFace {
|
||||
}
|
||||
return parseFileUploadResult(response.body)
|
||||
}
|
||||
if (!(data instanceof Uint8Array) && !(data instanceof Blob)) {
|
||||
throw new Error('stream file upload requires a bound Client file-upload service')
|
||||
}
|
||||
const bytes = data instanceof Uint8Array ? data : new Uint8Array(await data.arrayBuffer())
|
||||
return this.remote.session.uploadFile({
|
||||
sessionId: this.sessionId,
|
||||
|
||||
Reference in New Issue
Block a user