GitNexus/gitnexus-web/test/unit/agent-abort.test.ts
Octopus 0fa547ccdc
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Skill copy sync / shipped skills drift guard (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
feat: refresh MiniMax model and endpoint configuration (#2780)
2026-08-11 18:11:47 +00:00

128 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { streamAgentResponse, type AgentMessage } from '../../src/core/llm/agent';
describe('streamAgentResponse abort', () => {
const userMessage: AgentMessage[] = [{ role: 'user', content: 'hello' }];
it('yields cancelled when the LangGraph stream throws AbortError', async () => {
const agent = {
stream: async () => {
throw new DOMException('The operation was aborted', 'AbortError');
},
};
const chunks = [];
for await (const chunk of streamAgentResponse(agent as any, userMessage, {
signal: new AbortController().signal,
})) {
chunks.push(chunk);
}
expect(chunks).toEqual([{ type: 'cancelled' }]);
});
it('yields cancelled when the abort signal is set mid-stream', async () => {
const controller = new AbortController();
const agent = {
stream: async function* () {
yield ['values', { messages: [] }];
controller.abort();
for (let i = 0; i < 100; i++) {
yield ['messages', [{ _getType: () => 'ai', content: 'still going' }]];
}
},
};
const chunks = [];
for await (const chunk of streamAgentResponse(agent as any, userMessage, {
signal: controller.signal,
})) {
chunks.push(chunk);
if (chunk.type === 'cancelled') break;
}
expect(chunks[chunks.length - 1]).toEqual({ type: 'cancelled' });
expect(chunks.filter((c) => c.type === 'error')).toEqual([]);
});
it('passes AbortSignal to agent.stream config', async () => {
const controller = new AbortController();
let capturedConfig: Record<string, unknown> | undefined;
const agent = {
stream: async (_input: unknown, config: Record<string, unknown>) => {
capturedConfig = config;
throw new DOMException('aborted', 'AbortError');
},
};
for await (const _chunk of streamAgentResponse(agent as any, userMessage, {
signal: controller.signal,
})) {
// drain
}
expect(capturedConfig?.signal).toBe(controller.signal);
});
it('yields cancelled for a plain Error with name AbortError', async () => {
const agent = {
stream: async () => {
throw Object.assign(new Error('aborted'), { name: 'AbortError' });
},
};
const chunks = [];
for await (const chunk of streamAgentResponse(agent as any, userMessage)) {
chunks.push(chunk);
}
expect(chunks).toEqual([{ type: 'cancelled' }]);
});
it('does not treat unrelated errors mentioning abort as cancellation', async () => {
const agent = {
stream: async () => {
throw new Error('Cannot abort the current transaction');
},
};
const chunks = [];
for await (const chunk of streamAgentResponse(agent as any, userMessage)) {
chunks.push(chunk);
}
expect(chunks).toEqual([{ type: 'error', error: 'Cannot abort the current transaction' }]);
});
});
describe('streamAgentResponse content blocks', () => {
const userMessage: AgentMessage[] = [{ role: 'user', content: 'hello' }];
it('emits thinking blocks as reasoning', async () => {
const agent = {
stream: async function* () {
yield [
'messages',
[
{
_getType: () => 'ai',
content: [{ type: 'thinking', thinking: 'Reviewing the repository context.' }],
tool_calls: [],
},
],
];
},
};
const chunks = [];
for await (const chunk of streamAgentResponse(agent as any, userMessage)) {
chunks.push(chunk);
}
expect(chunks).toEqual([
{ type: 'reasoning', reasoning: 'Reviewing the repository context.' },
{ type: 'done', historyMessages: undefined },
]);
});
});