fix(attachment): distinguish admission from storage failures

This commit is contained in:
Tianyi Cui
2026-08-11 17:45:09 +08:00
parent 32c584561a
commit 57fc6bc539
10 changed files with 80 additions and 13 deletions

View File

@@ -2,7 +2,7 @@
import type { ContentBlock as AcpContentBlock } from '@agentclientprotocol/sdk'
import type { Context } from '@deepseek-ai/cordis'
import { AttachmentError } from '@deepseek-ai/dsh-attachment'
import { isImageAdmissionError } from '@deepseek-ai/dsh-attachment'
import type { ImageAttachmentRef, ImageMediaType, SaveImageAttachment } from '@deepseek-ai/dsh-attachment'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
@@ -72,7 +72,7 @@ async function assertImageRoute(ctx: Context, agent: Agent, signal: AbortSignal)
try {
info = await llm.resolveModelInfo(provider, model, signal)
} catch (error: unknown) {
throw new AcpContentError('the current model route could not be verified for image input', 'invalid', { cause: error })
throw new AcpContentError('the current model route could not be verified for image input', 'internal', { cause: error })
}
if (info.inputModalities === undefined || !info.inputModalities.includes('image')) {
throw new AcpContentError(`model "${model}" does not declare image input`, 'invalid')
@@ -157,7 +157,7 @@ export async function admitAcpPrompt(
try {
refs = await attachments.saveImages(images)
} catch (error: unknown) {
if (error instanceof AttachmentError && error.code !== 'ATTACHMENT_WRITE_FAILED') {
if (isImageAdmissionError(error)) {
throw new AcpContentError(error.message, 'invalid', { cause: error })
}
throw new AcpContentError('unable to persist the prompt image batch', 'internal', { cause: error })

View File

@@ -133,8 +133,9 @@ describe('ACP rich content codec', () => {
const broken = admissionFixture()
broken.resolveModelInfo.mockRejectedValueOnce(new Error('catalog down'))
await expect(admitAcpPrompt(broken.ctx, broken.agent, prompt, true, new AbortController().signal))
.rejects.toThrow(/route could not be verified/)
const routeFailure = admitAcpPrompt(broken.ctx, broken.agent, prompt, true, new AbortController().signal)
await expect(routeFailure).rejects.toMatchObject({ kind: 'internal' })
await expect(routeFailure).rejects.toThrow(/route could not be verified/)
const unknown = admissionFixture()
unknown.resolveModelInfo.mockResolvedValueOnce({ provider: 'mock', id: 'vision', name: 'vision' })
await expect(admitAcpPrompt(unknown.ctx, unknown.agent, prompt, true, new AbortController().signal))
@@ -158,6 +159,9 @@ describe('ACP rich content codec', () => {
await expect(admitAcpPrompt(fixture.ctx, fixture.agent, prompt, true, new AbortController().signal))
.rejects.toMatchObject({ kind: 'invalid', message: 'too many' })
fixture.saveImages.mockRejectedValueOnce(new AttachmentError('disk failed', 'ATTACHMENT_WRITE_FAILED'))
await expect(admitAcpPrompt(fixture.ctx, fixture.agent, prompt, true, new AbortController().signal))
.rejects.toMatchObject({ kind: 'internal', message: 'unable to persist the prompt image batch' })
fixture.saveImages.mockRejectedValueOnce(new AttachmentError('corrupt object', 'ATTACHMENT_CORRUPT'))
await expect(admitAcpPrompt(fixture.ctx, fixture.agent, prompt, true, new AbortController().signal))
.rejects.toMatchObject({ kind: 'internal', message: 'unable to persist the prompt image batch' })
fixture.saveImages.mockRejectedValueOnce(new Error('unknown store failure'))