fix(attachment): distinguish admission from storage failures
This commit is contained in:
@@ -18,6 +18,7 @@ import type { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { z } from 'zod'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { isImageAdmissionError } from '@deepseek-ai/dsh-attachment'
|
||||
import type { AttachmentStore, ImageAttachmentRef, ImageMediaType, SaveImageAttachment } from '@deepseek-ai/dsh-attachment'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { ToolDefinition, ToolExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
|
||||
@@ -474,10 +475,13 @@ async function prepareImageProjection(
|
||||
type: 'image',
|
||||
attachment: byIndex.get(index) as ImageAttachmentRef,
|
||||
}))
|
||||
} catch {
|
||||
} catch (error: unknown) {
|
||||
const reason = isImageAdmissionError(error)
|
||||
? `image admission rejected the result: ${error.message}`
|
||||
: 'durable image storage rejected the result'
|
||||
return projectContent(content, toolName, block => ({
|
||||
type: 'text',
|
||||
text: imageDiagnostic(block, 'durable image storage rejected the result'),
|
||||
text: imageDiagnostic(block, reason),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import AttachmentStore, { AttachmentId } from '@deepseek-ai/dsh-attachment'
|
||||
import AttachmentStore, { AttachmentError, AttachmentId } from '@deepseek-ai/dsh-attachment'
|
||||
import type { ImageAttachmentLimits, ImageAttachmentRef, SaveImageAttachment, StoredImageAttachment } from '@deepseek-ai/dsh-attachment'
|
||||
import { CallId, LlmAdapter, LlmService } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
@@ -676,6 +676,29 @@ describe('tool execution', () => {
|
||||
expect(textAt(result.content)).toContain('durable image storage rejected the result')
|
||||
})
|
||||
|
||||
it('reports attachment policy rejection as image admission rather than storage failure', async () => {
|
||||
const rich = await mountRichRegistry()
|
||||
vi.spyOn(rich.attachments, 'saveImages').mockRejectedValueOnce(
|
||||
new AttachmentError('too many images', 'TOO_MANY_IMAGES'),
|
||||
)
|
||||
const client = createMockClient(
|
||||
[{ name: 'img', inputSchema: { type: 'object' } }],
|
||||
{ content: [{ type: 'image', mimeType: 'image/png', data: 'AQ==' }] },
|
||||
)
|
||||
|
||||
await syncTools(client as never, rich.ctx, defaultOpts, new Map())
|
||||
const result = await rich.ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: CallId('policy-rejected'),
|
||||
name: 'mcp__srv__img',
|
||||
arguments: {},
|
||||
agent: agentOn() as never,
|
||||
})
|
||||
|
||||
expect(textAt(result.content)).toContain('image admission rejected the result: too many images')
|
||||
expect(textAt(result.content)).not.toContain('storage rejected')
|
||||
})
|
||||
|
||||
it('lets post-execute replacement win over a prepared image projection', async () => {
|
||||
const rich = await mountRichRegistry()
|
||||
rich.ctx.on('tools/post-execute', async (): Promise<PostToolDecision> => ({
|
||||
|
||||
Reference in New Issue
Block a user