Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/core-data-structures/llm-streaming.i18n.yaml # docs/core-data-structures/llm-streaming.md # docs/core-data-structures/llm-streaming.zh.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/subsystems/attachment.i18n.yaml # docs/subsystems/attachment.md # docs/subsystems/attachment.zh.md # docs/subsystems/core.i18n.yaml # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/README.i18n.yaml # packages/README.md # packages/README.zh.md # packages/client/runtime/package.json # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx # packages/client/ui-conversation/src/client/chat/ChatView.tsx # packages/client/ui-conversation/src/client/chat/MessageItem.tsx # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/tests/input-bar.spec.tsx # packages/compact/compact-basic/README.i18n.yaml # packages/compact/compact-basic/README.md # packages/compact/compact-basic/README.zh.md # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/index.ts # packages/host/apiproxy/src/api/sessions.ts # packages/host/apiproxy/src/index.ts # packages/host/apiproxy/tests/api-proxy-models.spec.ts # packages/self-modification/tool-cordis/src/api-catalog.ts # pnpm-lock.yaml # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -548,6 +548,40 @@ describe('DeepSeekAdapter against a mock server', () => {
|
||||
fetchSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps an idle provider read alive through SSE comments', async () => {
|
||||
vi.useFakeTimers()
|
||||
const encoder = new TextEncoder()
|
||||
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
setTimeout(() => { controller.enqueue(encoder.encode(': keep-alive\n\n')) }, 75)
|
||||
setTimeout(() => { controller.enqueue(encoder.encode(': keep-alive\n\n')) }, 150)
|
||||
setTimeout(() => {
|
||||
controller.enqueue(encoder.encode(textEvents.map(event => `data: ${event}\n\n`).join('')))
|
||||
controller.close()
|
||||
}, 225)
|
||||
},
|
||||
})
|
||||
return Promise.resolve(new Response(body, { status: 200 }))
|
||||
})
|
||||
const adapter = adapterOf({ baseURL: 'https://example.invalid', streamIdleTimeoutMs: 100 })
|
||||
try {
|
||||
const chunks: string[] = []
|
||||
const drain = (async () => {
|
||||
for await (const chunk of adapter.stream({ provider: 'deepseek-official', model: 'm', messages: [] })) {
|
||||
chunks.push(chunk.type)
|
||||
}
|
||||
})()
|
||||
await vi.advanceTimersByTimeAsync(75)
|
||||
await vi.advanceTimersByTimeAsync(75)
|
||||
await vi.advanceTimersByTimeAsync(75)
|
||||
await expect(drain).resolves.toBeUndefined()
|
||||
expect(chunks).toEqual(['block-start', 'text-delta', 'block-end', 'usage', 'finish'])
|
||||
} finally {
|
||||
fetchSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin registration and config', () => {
|
||||
|
||||
@@ -132,7 +132,7 @@ describe('llm-deepseek real dynamic composition', () => {
|
||||
})
|
||||
|
||||
it('keeps a stored key writable and rotatable across a real restart', async () => {
|
||||
// No ambient DEEPSEEK_API_KEY: the shipped surfaces no longer hoist
|
||||
// No ambient DEEPSEEK_API_KEY: the shipped surfaces do not hoist
|
||||
// the credentials document into process.env, so a stored key must stay file-sourced.
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', '')
|
||||
const first = await mockServer([{ kind: 'sse', events: textEvents }])
|
||||
|
||||
@@ -31,6 +31,16 @@ describe('parseSse', () => {
|
||||
expect(events).toEqual(['{"a":1}', DONE])
|
||||
})
|
||||
|
||||
it('reports comments out of band without yielding them', async () => {
|
||||
const comments: string[] = []
|
||||
const events = await collect(parseSse(
|
||||
bytes(': keep-alive\n\ndata: {"a":1}\n\ndata: [DONE]\n\n'),
|
||||
(comment) => { comments.push(comment) },
|
||||
))
|
||||
expect(comments).toEqual(['keep-alive'])
|
||||
expect(events).toEqual(['{"a":1}', DONE])
|
||||
})
|
||||
|
||||
it('stops yielding after DONE even when more data follows', async () => {
|
||||
const events = await collect(parseSse(bytes('data: [DONE]\n\ndata: {"late":1}\n\n')))
|
||||
expect(events).toEqual([DONE])
|
||||
|
||||
Reference in New Issue
Block a user