mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 22:39:45 +00:00
fix(agents): retain provider details from prompt rejection
This commit is contained in:
parent
8d7faadd32
commit
8e5cd79cdd
2 changed files with 81 additions and 8 deletions
|
|
@ -9,6 +9,7 @@ import * as fs from 'node:fs/promises';
|
|||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { HostRunResult } from '@qwen-code/qwen-code-core';
|
||||
import type { AcpSessionBridge } from './acp-session-bridge.js';
|
||||
import { isRevocation, startAgentHostConnection } from './agent-host-client.js';
|
||||
|
||||
|
|
@ -44,6 +45,83 @@ describe('isRevocation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('returns the provider detail when sendPrompt rejects with a JSON-RPC error', async () => {
|
||||
const workspaceCwd = await fs.mkdtemp(path.join(os.tmpdir(), 'pr12582-f7-'));
|
||||
let result: HostRunResult | undefined;
|
||||
let pickups = 0;
|
||||
const bridge = {
|
||||
listWorkspaceSessions: () => [],
|
||||
spawnOrAttach: vi.fn().mockResolvedValue({}),
|
||||
async *subscribeEvents() {},
|
||||
getSessionStatsStatus: vi.fn().mockResolvedValue({ models: {} }),
|
||||
sendPrompt: vi.fn().mockRejectedValue({
|
||||
code: -32603,
|
||||
message: 'Internal error',
|
||||
data: { details: '400 PR12582_PROVIDER_400_READABLE_CAUSE' },
|
||||
}),
|
||||
closeSession: vi.fn().mockResolvedValue({}),
|
||||
} as unknown as AcpSessionBridge;
|
||||
vi.stubEnv('QWEN_HOME', workspaceCwd);
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async (url: string, init?: RequestInit) => {
|
||||
if (url.endsWith('/enroll')) {
|
||||
return Response.json({ host: { id: 'host-test' }, secret: 'secret' });
|
||||
}
|
||||
if (url.endsWith('/pickup')) {
|
||||
if (++pickups > 1) {
|
||||
return Response.json(
|
||||
{ error: 'Invalid Agent Host credential.' },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
return Response.json({
|
||||
assignment: {
|
||||
agent: { id: 'agent-test', name: 'test' },
|
||||
threadId: 'thread-test',
|
||||
runId: 'run-test',
|
||||
attempt: 1,
|
||||
lease: { leaseId: 'lease-test' },
|
||||
prompt: 'Read the fixture.',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url.endsWith('/result')) {
|
||||
result = JSON.parse(init?.body as string) as HostRunResult;
|
||||
}
|
||||
if (url.endsWith('/heartbeat')) {
|
||||
return Response.json({ lease: { leaseId: 'lease-test' } });
|
||||
}
|
||||
return Response.json({ ok: true });
|
||||
}),
|
||||
);
|
||||
try {
|
||||
await startAgentHostConnection({
|
||||
bridge,
|
||||
serverUrl: 'http://127.0.0.1:18583',
|
||||
workspaceId: 'ws-test',
|
||||
workspaceCwd,
|
||||
enrollmentToken: 'test-token',
|
||||
});
|
||||
await vi.waitFor(() => expect(pickups).toBe(2));
|
||||
expect(result).toMatchObject({
|
||||
status: 'failed',
|
||||
error: '400 PR12582_PROVIDER_400_READABLE_CAUSE',
|
||||
});
|
||||
expect(bridge.sendPrompt).toHaveBeenCalledOnce();
|
||||
expect(bridge.closeSession).toHaveBeenCalledOnce();
|
||||
await vi.waitFor(async () => {
|
||||
expect(await fs.readdir(path.join(workspaceCwd, 'agent-hosts'))).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
vi.unstubAllEnvs();
|
||||
await fs.rm(workspaceCwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('retains a saved credential when rejoining sees only bare bearer 401s', async () => {
|
||||
const qwenDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pr12582-f1-'));
|
||||
const serverUrl = 'http://127.0.0.1:18582';
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import * as fs from 'node:fs/promises';
|
|||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { extractErrorMessage } from '@qwen-code/acp-bridge/bridge';
|
||||
import type {
|
||||
HostRunAssignment,
|
||||
HostRunResult,
|
||||
|
|
@ -491,14 +492,8 @@ async function executeAssignment(
|
|||
);
|
||||
if (turn?.promptId === promptId) {
|
||||
if (turn.state === 'error' || turn.state === 'cancelled') {
|
||||
const message = turn.error?.message;
|
||||
// A provider failure can arrive as the JSON-RPC error object
|
||||
// itself; stringifying the field keeps the real cause on the wire
|
||||
// instead of "[object Object]".
|
||||
throw new Error(
|
||||
typeof message === 'string' && message
|
||||
? message
|
||||
: (JSON.stringify(turn.error) ?? 'Managed Agent cancelled.'),
|
||||
extractErrorMessage(turn.error ?? 'Managed Agent cancelled.'),
|
||||
);
|
||||
}
|
||||
if (turn.state === 'completed') {
|
||||
|
|
@ -843,7 +838,7 @@ async function connectAgentHost(
|
|||
error instanceof Error && error.message === 'not_leasable'
|
||||
? 'cancelled'
|
||||
: 'failed',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: extractErrorMessage(error),
|
||||
};
|
||||
}
|
||||
await returnResult(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue