mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(daemon): grok-build — pass prompt inline as -p value, drop stdin (#2259)
* fix(daemon): grok-build runtime — pass prompt inline as -p value, drop stdin Grok Build CLI 0.1.212 enforces `-p, --single <PROMPT>` as a value-requiring flag — invoking with bare `-p` and piping the prompt to stdin now fails with: error: a value is required for '--single <PROMPT>' but none was supplied The previous runtime def used `promptViaStdin: true` + `buildArgs` returning `['-p']`, which only worked against earlier grok builds that read the prompt from stdin when `-p` had no inline value. This change inlines the prompt as the `-p` argument value and flips `promptViaStdin: false`. Linux `MAX_ARG_STRLEN` (128 KB) is enough headroom for typical Open Design prompts; if we ever hit `E2BIG` on a very large brief, a follow-up could shell out to `--prompt-file <tempfile>`. Verified against grok 0.1.212 (b7b8204a4) — single-turn invocations now return clean text replies instead of exit 2. * fix(daemon): declare grok-build argv prompt budget + regression coverage @mrcfps' review on #2259 flagged that moving the Grok Build adapter from the (no-longer-working) stdin path to argv would regress oversized composed prompts from the actionable AGENT_PROMPT_TOO_LARGE error we already emit for DeepSeek to a raw spawn ENAMETOOLONG / E2BIG instead. Fixed by mirroring the DeepSeek argv-budget shape: - grok-build.ts: `maxPromptArgBytes: 30_000` (same headroom as DeepSeek, ~2.7 KB under the Windows CreateProcess 32_767-char cap) so `checkPromptArgvBudget` pre-flights composed prompts (system + history + skills + design-system content + user message) before spawn. - prompt-budget.ts: Grok-Build-specific message — names the `-p / --single` flag, the xAI CLI 0.1.212+ behavior change, and points the user at stdin-capable adapters (claude / codex / hermes) when they need to ship large local context. - Tests: 3 new vitest cases in prompt-budget.test.ts — pin the budget field, exercise the strict-overrun + at-limit + CJK byte-count guards exactly like the DeepSeek regression set, and assert the Grok-named diagnostic copy. New `grokBuild` + `grokBuildMaxPromptArgBytes` helpers exported alongside the existing `deepseek*` ones. All 23 prompt-budget tests pass locally (`pnpm exec vitest run tests/runtimes/prompt-budget.test.ts`). --------- Co-authored-by: Sriram Sivakumar <sriram155@gmail.com> Co-authored-by: Siri-Ray <2667192167@qq.com>
This commit is contained in:
parent
73b2dc853f
commit
0bd07b2a3d
4 changed files with 92 additions and 7 deletions
|
|
@ -49,11 +49,10 @@ export const grokBuildAgentDef = {
|
||||||
label: 'grok-4.20-multi-agent (xAI · orchestration)',
|
label: 'grok-4.20-multi-agent (xAI · orchestration)',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Prompt delivered via stdin so Windows `spawn ENAMETOOLONG` and Linux
|
// Grok Build CLI v0.1.212 enforces `-p, --single <PROMPT>` as value-
|
||||||
// `spawn E2BIG` can't truncate large composed prompts. `grok -p` with
|
// required — stdin piping no longer satisfies it. Inline the prompt.
|
||||||
// no positional argument reads from piped stdin.
|
buildArgs: (prompt, _imagePaths, _extra = [], options = {}) => {
|
||||||
buildArgs: (_prompt, _imagePaths, _extra = [], options = {}) => {
|
const args = ['-p', prompt];
|
||||||
const args = ['-p'];
|
|
||||||
if (options.model && options.model !== DEFAULT_MODEL_OPTION.id) {
|
if (options.model && options.model !== DEFAULT_MODEL_OPTION.id) {
|
||||||
args.push('--model', options.model);
|
args.push('--model', options.model);
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +68,21 @@ export const grokBuildAgentDef = {
|
||||||
{ id: 'xhigh', label: 'xhigh' },
|
{ id: 'xhigh', label: 'xhigh' },
|
||||||
{ id: 'max', label: 'max' },
|
{ id: 'max', label: 'max' },
|
||||||
],
|
],
|
||||||
promptViaStdin: true,
|
promptViaStdin: false,
|
||||||
|
// Guard against prompts that would blow Windows' ~32 KB CreateProcess
|
||||||
|
// limit (or Linux MAX_ARG_STRLEN on extreme edges) before spawn. Same
|
||||||
|
// shape as the DeepSeek adapter — the previous stdin path is gone (CLI
|
||||||
|
// 0.1.212 enforces `-p <value>`), so the composed prompt now rides
|
||||||
|
// argv and a sufficiently large one — system text + history + skills/
|
||||||
|
// design-system content + user message — could surface as a generic
|
||||||
|
// spawn ENAMETOOLONG / E2BIG instead of a Grok-specific, user-
|
||||||
|
// actionable message. The /api/chat spawn path checks this byte
|
||||||
|
// budget against the composed prompt and emits AGENT_PROMPT_TOO_LARGE
|
||||||
|
// ("reduce skills/design-system context, or pick an adapter with
|
||||||
|
// stdin support") before calling `spawn`. 30_000 bytes leaves ~2.7 KB
|
||||||
|
// of argv headroom under the Windows command-line limit for `-p
|
||||||
|
// --model <id> --effort <level>` and internal quoting.
|
||||||
|
maxPromptArgBytes: 30_000,
|
||||||
streamFormat: 'plain',
|
streamFormat: 'plain',
|
||||||
installUrl: 'https://x.ai/cli',
|
installUrl: 'https://x.ai/cli',
|
||||||
docsUrl: 'https://x.ai/cli',
|
docsUrl: 'https://x.ai/cli',
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ function promptArgvBudgetMessage(
|
||||||
'Reduce the selected skills/design-system context or conversation length, or use DeepSeek through an API/provider model connection for large contexts. Pick a stdin-capable adapter when the prompt must include large local context.'
|
'Reduce the selected skills/design-system context or conversation length, or use DeepSeek through an API/provider model connection for large contexts. Pick a stdin-capable adapter when the prompt must include large local context.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (def.id === 'grok-build') {
|
||||||
|
return (
|
||||||
|
`${def.name} requires the prompt as the value of -p / --single (xAI CLI 0.1.212+ no longer reads piped stdin), and this run's composed prompt exceeds the safe size (${bytes} > ${def.maxPromptArgBytes} bytes). ` +
|
||||||
|
'Reduce the selected skills/design-system context or conversation length, or pick an adapter with stdin support (e.g. claude, codex, hermes) when the prompt must include large local context.'
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
`${def.name} requires the prompt as a command-line argument and this run's composed prompt exceeds the safe size (${bytes} > ${def.maxPromptArgBytes} bytes). ` +
|
`${def.name} requires the prompt as a command-line argument and this run's composed prompt exceeds the safe size (${bytes} > ${def.maxPromptArgBytes} bytes). ` +
|
||||||
'Reduce the selected skills/design-system context, shorten the conversation, or pick an adapter with stdin support.'
|
'Reduce the selected skills/design-system context, shorten the conversation, or pick an adapter with stdin support.'
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ export const gemini = requireAgent('gemini');
|
||||||
export const qoder = requireAgent('qoder');
|
export const qoder = requireAgent('qoder');
|
||||||
export const qwen = requireAgent('qwen');
|
export const qwen = requireAgent('qwen');
|
||||||
export const opencode = requireAgent('opencode');
|
export const opencode = requireAgent('opencode');
|
||||||
|
export const grokBuild = requireAgent('grok-build');
|
||||||
export const aider = requireAgent('aider');
|
export const aider = requireAgent('aider');
|
||||||
export const antigravity = requireAgent('antigravity');
|
export const antigravity = requireAgent('antigravity');
|
||||||
export const deepseekMaxPromptArgBytes = (() => {
|
export const deepseekMaxPromptArgBytes = (() => {
|
||||||
|
|
@ -95,6 +96,13 @@ export const deepseekMaxPromptArgBytes = (() => {
|
||||||
);
|
);
|
||||||
return deepseek.maxPromptArgBytes;
|
return deepseek.maxPromptArgBytes;
|
||||||
})();
|
})();
|
||||||
|
export const grokBuildMaxPromptArgBytes = (() => {
|
||||||
|
assert.ok(
|
||||||
|
grokBuild.maxPromptArgBytes !== undefined,
|
||||||
|
'grok-build must define maxPromptArgBytes for argv budget tests',
|
||||||
|
);
|
||||||
|
return grokBuild.maxPromptArgBytes;
|
||||||
|
})();
|
||||||
const originalDisablePlugins = process.env.OD_CODEX_DISABLE_PLUGINS;
|
const originalDisablePlugins = process.env.OD_CODEX_DISABLE_PLUGINS;
|
||||||
const originalPath = process.env.PATH;
|
const originalPath = process.env.PATH;
|
||||||
const originalHome = process.env.HOME;
|
const originalHome = process.env.HOME;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { test } from 'vitest';
|
import { test } from 'vitest';
|
||||||
import {
|
import {
|
||||||
assert, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, claude, deepseek, deepseekMaxPromptArgBytes, vibe,
|
assert, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, claude, deepseek, deepseekMaxPromptArgBytes, grokBuild, grokBuildMaxPromptArgBytes, vibe,
|
||||||
} from './helpers/test-helpers.js';
|
} from './helpers/test-helpers.js';
|
||||||
import type { TestAgentDef } from './helpers/test-helpers.js';
|
import type { TestAgentDef } from './helpers/test-helpers.js';
|
||||||
|
|
||||||
|
|
@ -107,6 +107,64 @@ test('checkPromptArgvBudget gives DeepSeek-specific guidance for large contexts'
|
||||||
assert.match(flagged.message, /stdin-capable adapter/);
|
assert.match(flagged.message, /stdin-capable adapter/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Grok Build CLI 0.1.212+ enforces `-p, --single <PROMPT>` as value-
|
||||||
|
// required, so the prompt rides argv just like DeepSeek. Pin the budget
|
||||||
|
// field and the byte-vs-codepoint guard so a future runtime-def edit
|
||||||
|
// can't silently drop the guard or let it drift over the Windows
|
||||||
|
// CreateProcess limit.
|
||||||
|
test('grok-build declares a conservative argv-byte budget for the prompt', () => {
|
||||||
|
assert.equal(
|
||||||
|
typeof grokBuildMaxPromptArgBytes,
|
||||||
|
'number',
|
||||||
|
'grok-build must set maxPromptArgBytes so the spawn path can pre-flight oversized prompts before hitting CreateProcess / E2BIG',
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
grokBuildMaxPromptArgBytes > 0 && grokBuildMaxPromptArgBytes < 32_768,
|
||||||
|
`grokBuildMaxPromptArgBytes must stay strictly under the Windows CreateProcess limit (~32 KB); got ${grokBuildMaxPromptArgBytes}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checkPromptArgvBudget flags oversized Grok Build prompts and lets short prompts through', () => {
|
||||||
|
const oversized = 'x'.repeat(grokBuildMaxPromptArgBytes + 1);
|
||||||
|
const flagged = checkPromptArgvBudget(grokBuild, oversized);
|
||||||
|
assert.ok(flagged, 'oversized prompts must trip the argv-byte guard');
|
||||||
|
assert.equal(flagged.code, 'AGENT_PROMPT_TOO_LARGE');
|
||||||
|
assert.equal(flagged.limit, grokBuildMaxPromptArgBytes);
|
||||||
|
assert.equal(flagged.bytes, grokBuildMaxPromptArgBytes + 1);
|
||||||
|
assert.match(flagged.message, /Grok Build/);
|
||||||
|
assert.match(flagged.message, /-p \/ --single/);
|
||||||
|
assert.match(flagged.message, /stdin/);
|
||||||
|
|
||||||
|
// Happy path: chat must keep working for normal-sized prompts.
|
||||||
|
assert.equal(checkPromptArgvBudget(grokBuild, 'hello'), null);
|
||||||
|
|
||||||
|
// Exact-budget edge: at-limit prompts pass; guard fires only on strict
|
||||||
|
// overrun.
|
||||||
|
const atLimit = 'x'.repeat(grokBuildMaxPromptArgBytes);
|
||||||
|
assert.equal(checkPromptArgvBudget(grokBuild, atLimit), null);
|
||||||
|
|
||||||
|
// Multi-byte UTF-8 (CJK = 3 bytes) must be byte-counted, not code-
|
||||||
|
// point-counted — mirrors the DeepSeek byte-count regression guard.
|
||||||
|
const cjkOversized = '汉'.repeat(
|
||||||
|
Math.ceil(grokBuildMaxPromptArgBytes / 3) + 1,
|
||||||
|
);
|
||||||
|
const cjkFlagged = checkPromptArgvBudget(grokBuild, cjkOversized);
|
||||||
|
assert.ok(cjkFlagged, 'byte-counted UTF-8 prompts must also trip the guard');
|
||||||
|
assert.equal(cjkFlagged.code, 'AGENT_PROMPT_TOO_LARGE');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checkPromptArgvBudget gives Grok-Build-specific guidance for large contexts', () => {
|
||||||
|
const oversized = 'x'.repeat(grokBuildMaxPromptArgBytes + 1);
|
||||||
|
const flagged = checkPromptArgvBudget(grokBuild, oversized);
|
||||||
|
|
||||||
|
assert.ok(flagged, 'oversized Grok Build prompts must return a diagnostic');
|
||||||
|
assert.match(flagged.message, /Grok Build/);
|
||||||
|
assert.match(flagged.message, /-p \/ --single/);
|
||||||
|
assert.match(flagged.message, /xAI CLI 0\.1\.212\+/);
|
||||||
|
assert.match(flagged.message, /no longer reads piped stdin/);
|
||||||
|
assert.match(flagged.message, /stdin support/);
|
||||||
|
});
|
||||||
|
|
||||||
// Adapters that ship the prompt over stdin (every other code agent
|
// Adapters that ship the prompt over stdin (every other code agent
|
||||||
// today) don't declare `maxPromptArgBytes` and must skip the guard
|
// today) don't declare `maxPromptArgBytes` and must skip the guard
|
||||||
// entirely — applying it to them would refuse perfectly valid huge
|
// entirely — applying it to them would refuse perfectly valid huge
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue