|
|
@@ -13,7 +13,7 @@ function createMockClient(overrides?: {
|
|
|
promptAsyncImpl?: (args: unknown) => Promise<unknown>;
|
|
|
abortImpl?: () => Promise<unknown>;
|
|
|
includePromptAsync?: boolean;
|
|
|
- messagesData?: Array<{ info: { role: string }; parts: unknown[] }>;
|
|
|
+ messagesData?: unknown[];
|
|
|
}) {
|
|
|
const promptAsync = mock(async (args: unknown) => {
|
|
|
if (overrides?.promptAsyncImpl) return overrides.promptAsyncImpl(args);
|
|
|
@@ -174,6 +174,50 @@ describe('ForegroundFallbackManager session.error', () => {
|
|
|
expect(call[0].body.model.modelID).toBe('gpt-4o');
|
|
|
});
|
|
|
|
|
|
+ test('skips malformed messages without info when locating the last user message', async () => {
|
|
|
+ // OpenCode may return partial/streaming messages whose `info` is undefined;
|
|
|
+ // the fallback must ignore those rather than crash, and still re-submit the
|
|
|
+ // real last user message.
|
|
|
+ ({ client, mocks } = createMockClient({
|
|
|
+ messagesData: [
|
|
|
+ {},
|
|
|
+ { info: { role: 'assistant' }, parts: [] },
|
|
|
+ { parts: [{ type: 'text', text: 'no info' }] },
|
|
|
+ {
|
|
|
+ info: { role: 'user' },
|
|
|
+ parts: [{ type: 'text', text: 'real prompt' }],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }));
|
|
|
+ mgr = new ForegroundFallbackManager(client, makeChains(), true);
|
|
|
+
|
|
|
+ await mgr.handleEvent({
|
|
|
+ type: 'message.updated',
|
|
|
+ properties: {
|
|
|
+ info: {
|
|
|
+ sessionID: 'sess-1',
|
|
|
+ providerID: 'anthropic',
|
|
|
+ modelID: 'claude-opus-4-5',
|
|
|
+ role: 'assistant',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ await mgr.handleEvent({
|
|
|
+ type: 'session.error',
|
|
|
+ properties: {
|
|
|
+ sessionID: 'sess-1',
|
|
|
+ error: { message: 'Rate limit exceeded' },
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(mocks.promptAsync).toHaveBeenCalledTimes(1);
|
|
|
+ const call = mocks.promptAsync.mock.calls[0] as [
|
|
|
+ { body: { parts: Array<{ text?: string }> } },
|
|
|
+ ];
|
|
|
+ expect(call[0].body.parts[0]?.text).toBe('real prompt');
|
|
|
+ });
|
|
|
+
|
|
|
test('does nothing when error is not a rate limit', async () => {
|
|
|
await mgr.handleEvent({
|
|
|
type: 'session.error',
|