mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
Merge pinned master 84bde6c7 into V3 readiness repair
This commit is contained in:
@@ -133,6 +133,14 @@ describe('Session queue commands', () => {
|
||||
}],
|
||||
},
|
||||
})), 'session/attachment-invalid')
|
||||
for (const content of [[], [{ type: 'text' as const, text: ' \t\n' }]]) {
|
||||
await expectFailure(Promise.resolve().then(() => controller.updateQueue({
|
||||
sessionId: agent.id,
|
||||
itemId: queued.id,
|
||||
action: { kind: 'edit', content },
|
||||
})), 'gateway/bad-request')
|
||||
}
|
||||
expect(inbox.nextTurn[0]?.content).toEqual([{ type: 'text', text: 'queued' }])
|
||||
await expectFailure(Promise.resolve().then(() => controller.updateQueue({
|
||||
sessionId: SessionId('missing'), itemId: queued.id, action: { kind: 'remove' },
|
||||
})), 'session/queue-item-not-found')
|
||||
|
||||
@@ -17,6 +17,7 @@ import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
|
||||
import { createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import type { Agent, Inbox } from '@deepseek-ai/dsh-agent'
|
||||
import type { SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import AttachmentStore from '@deepseek-ai/dsh-attachment'
|
||||
import type { SessionPromptRequest, SessionRequestId } from '../src/types.ts'
|
||||
import {
|
||||
SessionPersistenceRevision,
|
||||
@@ -678,6 +679,101 @@ describe('degenerate composition (no persistence, no factory)', () => {
|
||||
})
|
||||
|
||||
describe('sessions.prompt synchronous rejection', () => {
|
||||
it('rejects content without non-whitespace text or an attachment before delivery or Session events', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const session = ctx.sessions.create(sid('session-empty-prompt'))
|
||||
const followup = vi.fn()
|
||||
const steer = vi.fn()
|
||||
ctx.agents.register({
|
||||
id: session.id,
|
||||
session,
|
||||
inbox: inboxFor(),
|
||||
status: 'idle',
|
||||
ctx,
|
||||
followup,
|
||||
steer,
|
||||
} as unknown as Agent)
|
||||
const savedImage = {
|
||||
attachmentId: 'accepted-image',
|
||||
mediaType: 'image/png' as const,
|
||||
bytes: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
}
|
||||
const saveImages = vi.fn(() => Promise.resolve([savedImage]))
|
||||
ctx.provide('attachments', Object.setPrototypeOf(
|
||||
{ saveImages },
|
||||
AttachmentStore.prototype,
|
||||
) as never)
|
||||
ctx.provide('llm', {
|
||||
listProviders: () => [{ id: 'p', name: 'Provider' }],
|
||||
resolveModelInfo: () => Promise.resolve({
|
||||
provider: 'p', id: 'm', name: 'Model', inputModalities: ['text', 'image'],
|
||||
}),
|
||||
} as never)
|
||||
const remote = createSessionTestRemote(ctx, {
|
||||
defaultModelSelection: () => ({ provider: 'p', model: 'm' }),
|
||||
cwd: '/tmp',
|
||||
})
|
||||
const initialEvents = session.snapshotEvents()
|
||||
const rejectedContent: readonly SessionPromptRequest['content'][] = [
|
||||
[],
|
||||
[{ type: 'text', text: '' }],
|
||||
[{ type: 'text', text: ' \t\n' }, { type: 'text', text: '' }],
|
||||
]
|
||||
|
||||
for (const [index, content] of rejectedContent.entries()) {
|
||||
const response = await remote.prompt(promptRequest({
|
||||
sessionId: session.id,
|
||||
mode: index === 1 ? 'steer' : 'queue',
|
||||
content,
|
||||
}))
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'gateway/bad-request',
|
||||
message: 'prompt content must include non-whitespace text or an attachment',
|
||||
details: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
expect(followup).not.toHaveBeenCalled()
|
||||
expect(steer).not.toHaveBeenCalled()
|
||||
expect(session.snapshotEvents()).toEqual(initialEvents)
|
||||
|
||||
const queued = await remote.prompt(promptRequest({
|
||||
sessionId: session.id,
|
||||
mode: 'queue',
|
||||
content: [{ type: 'text', text: ' queued ' }],
|
||||
}))
|
||||
const steered = await remote.prompt(promptRequest({
|
||||
sessionId: session.id,
|
||||
mode: 'steer',
|
||||
content: [{ type: 'text', text: 'steered' }],
|
||||
}))
|
||||
const imageQueued = await remote.prompt(promptRequest({
|
||||
sessionId: session.id,
|
||||
mode: 'queue',
|
||||
content: [{ type: 'image', mediaType: 'image/png', data: 'AQ==' }],
|
||||
}))
|
||||
expect(queued).toMatchObject({ ok: true, value: { accepted: true } })
|
||||
expect(steered).toMatchObject({ ok: true, value: { accepted: true } })
|
||||
expect(imageQueued).toMatchObject({ ok: true, value: { accepted: true } })
|
||||
expect(followup).toHaveBeenCalledWith(expect.objectContaining({
|
||||
content: [{ type: 'text', text: ' queued ' }],
|
||||
}))
|
||||
expect(steer).toHaveBeenCalledWith(expect.objectContaining({
|
||||
content: [{ type: 'text', text: 'steered' }],
|
||||
}))
|
||||
expect(saveImages).toHaveBeenCalledOnce()
|
||||
expect(followup).toHaveBeenCalledWith(expect.objectContaining({
|
||||
content: [{ type: 'image', attachment: savedImage }],
|
||||
}))
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('maps a synchronous send throw (disposed/invalid input) to agent-busy with the reason attached', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
|
||||
Reference in New Issue
Block a user