mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 22:39:45 +00:00
fix(batch): enforce delivery boundaries and normalize thinking overrides
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
16e0b67b7a
commit
8560fe95f2
4 changed files with 88 additions and 24 deletions
|
|
@ -163,6 +163,20 @@ describe('assembleRequests', () => {
|
|||
'enable_thinking',
|
||||
);
|
||||
});
|
||||
it('enables tiered thinking when the frozen model settings disabled it', () => {
|
||||
const request = freezeRequest({ reasoning: false }, 'qwen3.8-max');
|
||||
const body = assembleRequests(
|
||||
{ ...plan, enableThinking: true },
|
||||
[item()],
|
||||
1,
|
||||
root,
|
||||
'qwen3.8-max',
|
||||
request,
|
||||
)[0].line['body'];
|
||||
expect(body).not.toHaveProperty('reasoning_effort');
|
||||
expect(body).not.toHaveProperty('enable_thinking');
|
||||
expect(request.params).toEqual({ reasoning_effort: 'none' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('freezeRequest', () => {
|
||||
|
|
@ -212,6 +226,18 @@ describe('freezeRequest', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('removes a disabling effort from thinking-mandatory models', () => {
|
||||
const request = freezeRequest(
|
||||
{
|
||||
thinkingMandatory: true,
|
||||
extra_body: { reasoning_effort: 'none' },
|
||||
},
|
||||
'qwen3.8-max',
|
||||
);
|
||||
expect(request.params).not.toHaveProperty('reasoning_effort');
|
||||
expect(request.notes.join()).toMatch(/cannot be disabled/);
|
||||
});
|
||||
|
||||
it('resolves the thinking switch with the realtime precedence', () => {
|
||||
// extra_body is merged last on the realtime wire, so it wins.
|
||||
expect(
|
||||
|
|
@ -501,6 +527,26 @@ describe('deliverResult', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('holds a visible symlink into a hidden directory before creating children', () => {
|
||||
fs.mkdirSync(path.join(root, '.qwen'));
|
||||
fs.symlinkSync(
|
||||
path.join(root, '.qwen'),
|
||||
path.join(root, 'output'),
|
||||
'junction',
|
||||
);
|
||||
const outcome = deliverResult(
|
||||
item({ target: 'output/new/settings.json' }),
|
||||
content,
|
||||
root,
|
||||
sourceHash,
|
||||
);
|
||||
expect(outcome).toMatchObject({
|
||||
kind: 'held',
|
||||
reason: expect.stringContaining('hidden path'),
|
||||
});
|
||||
expect(fs.existsSync(path.join(root, '.qwen', 'new'))).toBe(false);
|
||||
});
|
||||
|
||||
it('holds without creating directories outside a symlinked parent', () => {
|
||||
// `linked/new/` does not exist yet: containment must be proven at the
|
||||
// nearest existing ancestor before mkdir creates anything outside.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '@qwen-code/qwen-code-core/core/modalityDefaults.js';
|
||||
import { isWithinRoot } from '../config/path-comparison.js';
|
||||
import type { BatchPlan, TaskItem } from './batch-task.js';
|
||||
import { customIdOf } from './batch-task.js';
|
||||
import { customIdOf, targetProblem } from './batch-task.js';
|
||||
|
||||
export const sha256 = (text: string) =>
|
||||
crypto.createHash('sha256').update(text).digest('hex');
|
||||
|
|
@ -86,20 +86,26 @@ export const outputBudgetKey = (params: Record<string, unknown> = {}) =>
|
|||
/**
|
||||
* The Qwen-on-DashScope wire shape for "thinking off", as realtime emits it:
|
||||
* the tiered family reads `reasoning_effort`, the rest `enable_thinking`.
|
||||
* Other model families have their own knobs, so they get nothing here.
|
||||
* Enabling a tiered model removes the disable override, preserving its default
|
||||
* or an explicitly configured effort. Other model families get nothing here.
|
||||
*/
|
||||
function disableThinking(
|
||||
export function setThinking(
|
||||
params: Record<string, unknown>,
|
||||
model: string | undefined,
|
||||
enabled: boolean,
|
||||
): boolean {
|
||||
if (isTieredEffortWireModel(model)) {
|
||||
delete params['enable_thinking'];
|
||||
delete params['thinking_budget'];
|
||||
params['reasoning_effort'] = 'none';
|
||||
if (!enabled) params['reasoning_effort'] = 'none';
|
||||
else if (params['reasoning_effort'] === 'none')
|
||||
delete params['reasoning_effort'];
|
||||
return true;
|
||||
}
|
||||
if (isQwenFamilyWireModel(model)) {
|
||||
params['enable_thinking'] = false;
|
||||
if (enabled && params['reasoning_effort'] === 'none')
|
||||
delete params['reasoning_effort'];
|
||||
params['enable_thinking'] = enabled;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -124,7 +130,7 @@ export function freezeRequest(
|
|||
!config.thinkingMandatory &&
|
||||
params['enable_thinking'] === undefined &&
|
||||
params['reasoning_effort'] === undefined &&
|
||||
!disableThinking(params, model)
|
||||
!setThinking(params, model, false)
|
||||
) {
|
||||
notes.push(
|
||||
`disabled reasoning is not reproduced for ${model ?? 'this model'} in Batch requests; the provider default thinking mode applies`,
|
||||
|
|
@ -132,9 +138,13 @@ export function freezeRequest(
|
|||
}
|
||||
if (
|
||||
config?.thinkingMandatory &&
|
||||
(config.reasoning === false || params['enable_thinking'] === false)
|
||||
(config.reasoning === false ||
|
||||
params['enable_thinking'] === false ||
|
||||
params['reasoning_effort'] === 'none')
|
||||
) {
|
||||
delete params['enable_thinking'];
|
||||
if (params['enable_thinking'] === false) delete params['enable_thinking'];
|
||||
if (params['reasoning_effort'] === 'none')
|
||||
delete params['reasoning_effort'];
|
||||
notes.push('thinking cannot be disabled for this model; left on');
|
||||
}
|
||||
if (
|
||||
|
|
@ -228,10 +238,8 @@ export function assembleRequests(
|
|||
if (plan.maxOutputTokens !== undefined) {
|
||||
body[outputBudgetKey(request?.params)] = plan.maxOutputTokens;
|
||||
}
|
||||
if (plan.enableThinking === true) {
|
||||
body['enable_thinking'] = true;
|
||||
} else if (plan.enableThinking === false) {
|
||||
disableThinking(body, model);
|
||||
if (plan.enableThinking !== undefined) {
|
||||
setThinking(body, model, plan.enableThinking);
|
||||
}
|
||||
const inputTokens = estimateTokens(JSON.stringify(messages));
|
||||
requests.push({
|
||||
|
|
@ -441,6 +449,15 @@ export function deliverResult(
|
|||
reason: `target directory "${item.target}" resolves outside the project root`,
|
||||
};
|
||||
}
|
||||
const ancestorProblem = targetProblem(
|
||||
path.relative(realRoot, fs.realpathSync(ancestor)),
|
||||
);
|
||||
if (ancestorProblem) {
|
||||
return {
|
||||
kind: 'held',
|
||||
reason: `target directory "${item.target}" ${ancestorProblem}`,
|
||||
};
|
||||
}
|
||||
fs.mkdirSync(parent, { recursive: true });
|
||||
// Revalidate the created parent: the chain must really live under the
|
||||
// project at delivery time, not just before the mkdir.
|
||||
|
|
@ -451,6 +468,13 @@ export function deliverResult(
|
|||
reason: `target directory "${item.target}" resolves outside the project root`,
|
||||
};
|
||||
}
|
||||
const parentProblem = targetProblem(path.relative(realRoot, realParent));
|
||||
if (parentProblem) {
|
||||
return {
|
||||
kind: 'held',
|
||||
reason: `target directory "${item.target}" ${parentProblem}`,
|
||||
};
|
||||
}
|
||||
if (fs.existsSync(targetPath)) {
|
||||
const existing = fs.readFileSync(targetPath, 'utf8');
|
||||
if (existing === content) {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export type BatchPlan = z.infer<typeof batchPlanSchema>;
|
|||
* hidden path — at any depth, since `.git/hooks`, `.github/workflows` and
|
||||
* `.qwen/settings.json` configure tools or run code in nested projects too.
|
||||
*/
|
||||
function targetProblem(target: string): string | undefined {
|
||||
export function targetProblem(target: string): string | undefined {
|
||||
const normalized = path.normalize(target);
|
||||
const segments = normalized.split(/[\\/]/);
|
||||
if (path.isAbsolute(target) || segments[0] === '..') {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import {
|
|||
deliverResult,
|
||||
sha256,
|
||||
freezeRequest,
|
||||
setThinking,
|
||||
describeThinking,
|
||||
outputBudgetKey,
|
||||
} from './batch-docs.js';
|
||||
|
|
@ -527,17 +528,10 @@ export async function runPlan(
|
|||
? `preview: ${task.items.length} item(s), window ${task.completionWindow} — nothing uploaded, nothing billed`
|
||||
: `task ${task.id}: ${task.items.length} item(s), window ${task.completionWindow}`,
|
||||
);
|
||||
const effective = {
|
||||
...request,
|
||||
params: {
|
||||
...request.params,
|
||||
...(plan.enableThinking === undefined
|
||||
? {}
|
||||
: plan.enableThinking
|
||||
? { enable_thinking: true }
|
||||
: { enable_thinking: false, reasoning_effort: 'none' }),
|
||||
},
|
||||
};
|
||||
const effective = { ...request, params: { ...request.params } };
|
||||
if (plan.enableThinking !== undefined) {
|
||||
setThinking(effective.params, task.model, plan.enableThinking);
|
||||
}
|
||||
const limit = outputLimitOf(task, attempt.maxOutputTokens);
|
||||
deps.out(
|
||||
`model ${task.model}, ${describeThinking(effective)}, ` +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue