Przeglądaj źródła

fix(client): satisfy upload package gates

creatixchu 1 miesiąc temu
rodzic
commit
7a99e70372

+ 2 - 2
packages/client/file-upload/README.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 packages/client/file-upload/README.md
-README.md: d7cac384c1cef4f52f4f7c09838697d53bba7c4d
-README.zh.md: 6ced413d1813c1808daef10e0cf283cd973eff68
+README.md: 9abd4106dbd7cd1cb9671f8f830952f6faff11c6
+README.zh.md: 4ad14ca5a91acd482f54cab74fb974208a264a5a

+ 2 - 0
packages/client/file-upload/README.md

@@ -52,6 +52,8 @@ The Client plugin provides one inherited `ctx.fileUpload` service. Its provider
 
 </details>
 
+**Runtime invariant:** No companion is published. Each request uses exactly one selected carrier, and fixture or unsupported-browser requests fail before the body is sent.
+
 -----
 
 <a id="further-exploration"></a>

+ 2 - 0
packages/client/file-upload/README.zh.md

@@ -52,6 +52,8 @@ Client 插件提供可被各级上下文继承的 `ctx.fileUpload` 服务。其
 
 </details>
 
+**运行时不变式:** 不发布伴生入口。每个请求只使用一个已选定载体;fixture 或不支持的浏览器请求会在发送请求体前失败。
+
 -----
 
 <a id="further-exploration"></a>

+ 4 - 3
packages/client/file-upload/tests/file-upload.client.spec.ts

@@ -124,7 +124,7 @@ describe('file upload worker body', () => {
     fileUploadWorker(
       scope,
       () => { throw new Error('unused') },
-      () => Promise.reject('offline'),
+      () => Promise.reject(new Error('offline')),
     )
     scope.onmessage?.({ data: { url: '/upload', body, headers: {} } } as never)
     await vi.waitFor(() => {
@@ -190,9 +190,10 @@ describe('file upload service', () => {
     const ctx = new Context()
     const fiber = ctx.plugin({ apply })
     await fiber
-    await ctx.fileUpload.post({ path: '/fallback', body: new Blob() })
+    const body = new Blob()
+    await ctx.fileUpload.post({ path: '/fallback', body })
     expect(fetch).toHaveBeenCalledWith(new URL('http://dsh.internal/fallback'), {
-      method: 'POST', body: expect.any(Blob),
+      method: 'POST', body,
     })
     await fiber.dispose()
   })

+ 40 - 23
packages/client/ui-conversation/tests/service-orchestration.client.spec.ts

@@ -6,7 +6,9 @@
 import { Context } from '@deepseek-ai/cordis'
 import { describe, expect, it, vi } from 'vitest'
 import { makeTranslate, RemoteError, SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
-import type { QueuedMessage } from '@deepseek-ai/dsh-api-session-controller/client'
+import type {
+  BeginSubmissionInput, PendingSubmissionRetirement, QueuedMessage,
+} from '@deepseek-ai/dsh-api-session-controller/client'
 import { ComposerBlockRegistry } from '../src/client/input/blocks.ts'
 import { InputHub } from '../src/client/input/hub.ts'
 import { ConversationController } from '../src/client/service.ts'
@@ -453,9 +455,9 @@ describe('sendSession submission echo', () => {
   /** Bench with an observable beginSubmission on the session face. */
   async function echoBench() {
     const b = await bench()
-    const retire: { onRetire?: ((retirement: unknown) => void) | undefined } = {}
+    const retire: { onRetire?: ((retirement: PendingSubmissionRetirement) => void) | undefined } = {}
     const abandon = vi.fn()
-    const beginSubmission = vi.fn((input: { onRetire?: (retirement: unknown) => void }) => {
+    const beginSubmission = vi.fn((input: BeginSubmissionInput) => {
       retire.onRetire = input.onRetire
       return { requestId: 'req-echo' as never, abandon }
     })
@@ -480,14 +482,12 @@ describe('sendSession submission echo', () => {
       const session = b.runtime.sessions.binding('s1')!.session
       const sending = b.root.sendSession(session, '带图', [attachment!.id], 'queue')
       // Synchronous: the echo is registered before any encoding starts.
-      expect(b.beginSubmission).toHaveBeenCalledWith(expect.objectContaining({
-        mode: 'queue',
-        text: '带图',
-        attachments: [{
-          type: 'image',
-          value: expect.objectContaining({ previewUrl: 'blob:echo-1', name: 'a.png' }),
-        }],
-      }))
+      const echo = b.beginSubmission.mock.calls[0]?.[0]
+      expect(echo?.mode).toBe('queue')
+      expect(echo?.text).toBe('带图')
+      expect(echo?.attachments).toHaveLength(1)
+      expect(echo?.attachments[0]?.type).toBe('image')
+      expect(echo?.attachments[0]?.value).toMatchObject({ previewUrl: 'blob:echo-1', name: 'a.png' })
       expect(b.prompt).not.toHaveBeenCalled()
       await vi.waitFor(() => { expect(b.prompt).toHaveBeenCalledOnce() })
       expect(b.prompt).toHaveBeenCalledWith(
@@ -531,14 +531,15 @@ describe('sendSession submission echo', () => {
         expect(b.root.fileUploads.getSnapshot()[drafts[1]!.id]?.status).toBe('ready')
       })
       const sending = b.root.sendSession(session, 'ordered', drafts.map(draft => draft.id), 'steer')
-      expect(b.beginSubmission).toHaveBeenCalledWith(expect.objectContaining({
-        mode: 'steer',
-        attachments: [
-          { type: 'image', value: expect.objectContaining({ name: 'first.png' }) },
-          { type: 'file', value: { attachmentId: 'mixed-file', name: 'notes.txt', bytes: 1 } },
-          { type: 'image', value: expect.objectContaining({ name: 'last.png' }) },
-        ],
-      }))
+      const echo = b.beginSubmission.mock.calls[0]?.[0]
+      expect(echo?.mode).toBe('steer')
+      expect(echo?.attachments.map(attachment => attachment.type === 'image'
+        ? { type: attachment.type, name: attachment.value.name }
+        : { type: attachment.type, value: attachment.value })).toEqual([
+        { type: 'image', name: 'first.png' },
+        { type: 'file', value: { attachmentId: 'mixed-file', name: 'notes.txt', bytes: 1 } },
+        { type: 'image', name: 'last.png' },
+      ])
       await vi.waitFor(() => { expect(b.prompt).toHaveBeenCalledOnce() })
       expect(b.prompt.mock.calls[0]?.[0]).toEqual([
         { type: 'image', mediaType: 'image/png', data: expect.any(String) as string, name: 'first.png' },
@@ -549,9 +550,21 @@ describe('sendSession submission echo', () => {
       b.retire.onRetire?.({
         reason: 'observed',
         attachments: [
-          { attachmentId: 'image-first', mediaType: 'image/png' },
-          { attachmentId: 'mixed-file', name: 'notes.txt', bytes: 1 },
-          { attachmentId: 'image-last', mediaType: 'image/png' },
+          {
+            attachmentId: 'image-first' as never,
+            mediaType: 'image/png',
+            bytes: 1,
+            width: 1,
+            height: 1,
+          },
+          { attachmentId: 'mixed-file' as never, name: 'notes.txt', bytes: 1 },
+          {
+            attachmentId: 'image-last' as never,
+            mediaType: 'image/png',
+            bytes: 1,
+            width: 1,
+            height: 1,
+          },
         ],
       })
       await expect(sending).resolves.toEqual({ kind: 'success' })
@@ -598,7 +611,11 @@ describe('sendSession submission echo', () => {
       const sending = b.root.sendSession(session, '', [attachment!.id], 'queue')
       await vi.waitFor(() => { expect(b.prompt).toHaveBeenCalledOnce() })
       const ref = {
-        attachmentId: 'att-1', mediaType: 'image/png', bytes: 1, width: 1, height: 1,
+        attachmentId: 'att-1' as never,
+        mediaType: 'image/png' as const,
+        bytes: 1,
+        width: 1,
+        height: 1,
       }
       b.retire.onRetire?.({ reason: 'observed', attachments: [ref] })
       await expect(sending).resolves.toEqual({ kind: 'success' })