Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 37 additions & 62 deletions integration-tests/interactive/protocol-tags-interactive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,89 +10,64 @@ import {
startFakeOpenAIServer,
type FakeOpenAIServer,
} from '../fake-openai-server.js';
import { TestRig, type } from '../test-helper.js';

const SANDBOX_MODE = process.env['QWEN_SANDBOX']?.toLowerCase().trim();
const IS_CONTAINER_SANDBOX =
SANDBOX_MODE === 'docker' || SANDBOX_MODE === 'podman';
import {
TestRig,
type,
applyContainerSandboxNoProxy,
fakeServerHostOptions,
} from '../test-helper.js';

describe('Interactive protocol tag retry guard', () => {
let fakeServer: FakeOpenAIServer | undefined;
let rig: TestRig;
let savedNoProxy: string | undefined;
let savedNoProxyLower: string | undefined;
let restoreNoProxy: () => void;

beforeEach(() => {
rig = new TestRig();
if (IS_CONTAINER_SANDBOX) {
savedNoProxy = process.env['NO_PROXY'];
savedNoProxyLower = process.env['no_proxy'];
const noProxy = '127.0.0.1,localhost,host.docker.internal';
process.env['NO_PROXY'] = noProxy;
process.env['no_proxy'] = noProxy;
}
restoreNoProxy = applyContainerSandboxNoProxy();
});

afterEach(async () => {
await fakeServer?.close();
fakeServer = undefined;
if (IS_CONTAINER_SANDBOX) {
if (savedNoProxy !== undefined) {
process.env['NO_PROXY'] = savedNoProxy;
} else {
delete process.env['NO_PROXY'];
}
if (savedNoProxyLower !== undefined) {
process.env['no_proxy'] = savedNoProxyLower;
} else {
delete process.env['no_proxy'];
}
}
restoreNoProxy();
await rig.cleanup();
});

it.skipIf(process.platform === 'win32')(
'retries protocol leaks across SSE disconnect and completed streams',
async () => {
fakeServer = await startFakeOpenAIServer(
({ requestIndex }) => {
if (requestIndex === 0) {
return {
contentChunks: [
'<analysis>hidden before disconnect',
'</analysis><summary>WRONG_FIRST_ATTEMPT</summary>',
],
disconnectAfterContentChunks: 1,
};
}

if (requestIndex === 1) {
return {
contentChunks: [
'<ana',
'lysis>hidden completed attempt</analysis>',
'<sum',
'mary>WRONG_COMPLETED_SUMMARY</summary>',
],
};
}
fakeServer = await startFakeOpenAIServer(({ requestIndex }) => {
if (requestIndex === 0) {
return {
contentChunks: [
'<analysis>hidden before disconnect',
'</analysis><summary>WRONG_FIRST_ATTEMPT</summary>',
],
disconnectAfterContentChunks: 1,
};
}

if (requestIndex === 1) {
return {
contentChunks: ['VISIBLE_TMUX_RETRY_RESPONSE_DONE'],
usage: {
prompt_tokens: 20,
completion_tokens: 8,
total_tokens: 28,
},
contentChunks: [
'<ana',
'lysis>hidden completed attempt</analysis>',
'<sum',
'mary>WRONG_COMPLETED_SUMMARY</summary>',
],
};
},
IS_CONTAINER_SANDBOX
? {
listenHost: '0.0.0.0',
baseUrlHost: 'host.docker.internal',
}
: undefined,
);
}

return {
contentChunks: ['VISIBLE_TMUX_RETRY_RESPONSE_DONE'],
usage: {
prompt_tokens: 20,
completion_tokens: 8,
total_tokens: 28,
},
};
}, fakeServerHostOptions());

await rig.setup('interactive-protocol-tag-filtering-http-retry', {
settings: {
Expand Down
36 changes: 23 additions & 13 deletions integration-tests/interactive/submitted-prompt-provenance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
startFakeOpenAIServer,
type FakeOpenAIServer,
} from '../fake-openai-server.js';
import { TestRig, type } from '../test-helper.js';
import {
TestRig,
type,
applyContainerSandboxNoProxy,
fakeServerHostOptions,
} from '../test-helper.js';

interface CapturedHookInput {
hook_event_name?: unknown;
Expand All @@ -24,16 +29,19 @@ describe('submitted prompt provenance', () => {
let rig: TestRig;
let savedQwenHome: string | undefined;
let savedTrustedFoldersPath: string | undefined;
let restoreNoProxy: () => void;

beforeEach(() => {
rig = new TestRig();
savedQwenHome = process.env['QWEN_HOME'];
savedTrustedFoldersPath = process.env['QWEN_CODE_TRUSTED_FOLDERS_PATH'];
restoreNoProxy = applyContainerSandboxNoProxy();
});

afterEach(async () => {
await fakeServer?.close();
fakeServer = undefined;
restoreNoProxy();
await rig.cleanup();
if (savedQwenHome === undefined) {
delete process.env['QWEN_HOME'];
Expand Down Expand Up @@ -104,18 +112,20 @@ describe('submitted prompt provenance', () => {
].join('\n'),
);

fakeServer = await startFakeOpenAIServer(({ requestIndex }) =>
requestIndex === 0
? {
toolCalls: [
fakeToolCall(
'read_file',
{ file_path: toolFile },
'call_submitted_prompt_e2e',
),
],
}
: { content: 'PROVENANCE_E2E_DONE' },
fakeServer = await startFakeOpenAIServer(
({ requestIndex }) =>
requestIndex === 0
? {
toolCalls: [
fakeToolCall(
'read_file',
{ file_path: toolFile },
'call_submitted_prompt_e2e',
),
],
}
: { content: 'PROVENANCE_E2E_DONE' },
fakeServerHostOptions(),
);

const { ptyProcess, promise } = rig.runInteractive(
Expand Down
14 changes: 7 additions & 7 deletions integration-tests/sdk-typescript/tool-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ import {
createSharedTestOptions,
createResultWaiter,
} from './test-helper.js';
import {
IS_CONTAINER_SANDBOX,
CONTAINER_SANDBOX_NO_PROXY,
fakeServerHostOptions,
} from '../test-helper.js';

const SHARED_TEST_OPTIONS = createSharedTestOptions();
const TEST_TIMEOUT = 60000;
const SANDBOX_MODE = process.env['QWEN_SANDBOX']?.toLowerCase().trim();
const IS_CONTAINER_SANDBOX =
SANDBOX_MODE === 'docker' || SANDBOX_MODE === 'podman';
const LOCAL_OPENAI_NO_PROXY = IS_CONTAINER_SANDBOX
? '127.0.0.1,localhost,host.docker.internal'
? CONTAINER_SANDBOX_NO_PROXY
: '127.0.0.1,localhost';
const FAKE_SERVER_OPTIONS = IS_CONTAINER_SANDBOX
? { listenHost: '0.0.0.0' as const, baseUrlHost: 'host.docker.internal' }
: undefined;
const FAKE_SERVER_OPTIONS = fakeServerHostOptions();

function fakeModelOptions(baseUrl: string) {
return {
Expand Down
39 changes: 39 additions & 0 deletions integration-tests/test-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import fs from 'node:fs';
import { EOL } from 'node:os';
import * as pty from '@lydell/node-pty';
import stripAnsi from 'strip-ansi';
import type { FakeOpenAIServerOptions } from './fake-openai-server.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

Expand Down Expand Up @@ -122,6 +123,44 @@ export async function type(ptyProcess: pty.IPty, text: string) {
}
}

const SANDBOX_MODE = process.env['QWEN_SANDBOX']?.toLowerCase().trim();

export const IS_CONTAINER_SANDBOX =
SANDBOX_MODE === 'docker' || SANDBOX_MODE === 'podman';

export const CONTAINER_SANDBOX_NO_PROXY =
'127.0.0.1,localhost,host.docker.internal';

export function fakeServerHostOptions(): FakeOpenAIServerOptions | undefined {
return IS_CONTAINER_SANDBOX
? { listenHost: '0.0.0.0', baseUrlHost: 'host.docker.internal' }
: undefined;
}

// Sets NO_PROXY so a containerized CLI reaches the host-side fake server, and
// returns a restorer for the previous values. No-op outside a container sandbox.
export function applyContainerSandboxNoProxy(): () => void {
if (!IS_CONTAINER_SANDBOX) {
return () => {};
}
const savedNoProxy = process.env['NO_PROXY'];
const savedNoProxyLower = process.env['no_proxy'];
process.env['NO_PROXY'] = CONTAINER_SANDBOX_NO_PROXY;
process.env['no_proxy'] = CONTAINER_SANDBOX_NO_PROXY;
return () => {
if (savedNoProxy === undefined) {
delete process.env['NO_PROXY'];
} else {
process.env['NO_PROXY'] = savedNoProxy;
}
if (savedNoProxyLower === undefined) {
delete process.env['no_proxy'];
} else {
process.env['no_proxy'] = savedNoProxyLower;
}
};
}

interface ParsedLog {
attributes?: {
'event.name'?: string;
Expand Down
Loading