feat: add prompt param to POST /api/sandboxes#231
Conversation
Add optional `prompt` field that converts to `opencode run "<prompt>"` command. Validates that command and prompt cannot be used together. Includes tests for validation and domain logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds optional Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ❌ 1❌ Failed checks (1 warning)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
lib/sandbox/processCreateSandbox.ts (1)
7-10: Consider exportingProcessCreateSandboxInputfor MCP tool callers.The JSDoc on line 15 explicitly notes this function is consumed by both the HTTP handler and the
run_sandbox_commandMCP tool. Keeping the input type unexported means external callers must reconstruct orany-cast the parameter shape. Exporting it costs nothing and improves type safety at all call sites.♻️ Proposed change
-type ProcessCreateSandboxInput = Pick< +export type ProcessCreateSandboxInput = Pick< SandboxBody, "accountId" | "command" | "args" | "cwd" | "prompt" >;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/sandbox/processCreateSandbox.ts` around lines 7 - 10, The type ProcessCreateSandboxInput is currently unexported which forces external callers like the MCP tool to reconstruct or cast the shape; export the type so callers can reuse it safely. Update the declaration of ProcessCreateSandboxInput to be exported (export type ProcessCreateSandboxInput = ...) and ensure any imports/exports where run_sandbox_command or the HTTP handler live reference this exported type; keep the exact property pick (accountId, command, args, cwd, prompt) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/sandbox/validateSandboxBody.ts`:
- Around line 8-18: The schema's refine currently only rejects command+prompt
but allows args+prompt which are dropped downstream; update
sandboxBodySchema.refine predicate to reject any payload where prompt is present
with either command or a non-empty args (e.g. change refine to: data =>
!(data.prompt && (data.command || (data.args && data.args.length > 0)))), and
update the refine message/path accordingly (e.g. message "Cannot specify prompt
with command or args" and path ["prompt"] or ["args"] as appropriate) so callers
get validation errors when supplying args alongside prompt.
- Around line 15-17: The refinement in validateSandboxBody that uses
.refine(..., { message: "Cannot specify both command and prompt", path:
["prompt"] }) incorrectly maps to the response shape's missing_fields; change
the refinement to emit a distinct conflict indicator (e.g., set path to
["conflicting_fields"] or attach a custom issue/code like "conflict") and update
the error-to-response mapping logic that currently populates missing_fields to
instead populate a new conflicting_fields array when that conflict issue/code is
present (include both "command" and "prompt" or at least the conflicting field
names). This keeps the message text, but ensures the response shape
distinguishes conflicts from missing fields and uses identifiers in
validateSandboxBody, the .refine call, and the error-to-response mapping logic
to locate and update the code.
---
Nitpick comments:
In `@lib/sandbox/processCreateSandbox.ts`:
- Around line 7-10: The type ProcessCreateSandboxInput is currently unexported
which forces external callers like the MCP tool to reconstruct or cast the
shape; export the type so callers can reuse it safely. Update the declaration of
ProcessCreateSandboxInput to be exported (export type ProcessCreateSandboxInput
= ...) and ensure any imports/exports where run_sandbox_command or the HTTP
handler live reference this exported type; keep the exact property pick
(accountId, command, args, cwd, prompt) unchanged.
| export const sandboxBodySchema = z | ||
| .object({ | ||
| command: z.string().min(1, "command cannot be empty").optional(), | ||
| args: z.array(z.string()).optional(), | ||
| cwd: z.string().optional(), | ||
| prompt: z.string().min(1, "prompt cannot be empty").optional(), | ||
| }) | ||
| .refine(data => !(data.command && data.prompt), { | ||
| message: "Cannot specify both command and prompt", | ||
| path: ["prompt"], | ||
| }); |
There was a problem hiding this comment.
args + prompt combination passes validation but is silently dropped downstream.
The refinement only guards against command + prompt. A request body of { prompt: "...", args: ["extra"] } passes validation cleanly, but args is silently discarded in processCreateSandbox.ts (line 27: const args = prompt ? ["run", prompt] : input.args). A caller who passes args alongside prompt gets no feedback that their args were ignored.
Extend the refinement to also reject args + prompt:
🛡️ Proposed fix — also block args + prompt together
.refine(data => !(data.command && data.prompt), {
message: "Cannot specify both command and prompt",
path: ["prompt"],
- });
+ })
+ .refine(data => !(data.args && data.prompt), {
+ message: "Cannot specify both args and prompt",
+ path: ["args"],
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const sandboxBodySchema = z | |
| .object({ | |
| command: z.string().min(1, "command cannot be empty").optional(), | |
| args: z.array(z.string()).optional(), | |
| cwd: z.string().optional(), | |
| prompt: z.string().min(1, "prompt cannot be empty").optional(), | |
| }) | |
| .refine(data => !(data.command && data.prompt), { | |
| message: "Cannot specify both command and prompt", | |
| path: ["prompt"], | |
| }); | |
| export const sandboxBodySchema = z | |
| .object({ | |
| command: z.string().min(1, "command cannot be empty").optional(), | |
| args: z.array(z.string()).optional(), | |
| cwd: z.string().optional(), | |
| prompt: z.string().min(1, "prompt cannot be empty").optional(), | |
| }) | |
| .refine(data => !(data.command && data.prompt), { | |
| message: "Cannot specify both command and prompt", | |
| path: ["prompt"], | |
| }) | |
| .refine(data => !(data.args && data.prompt), { | |
| message: "Cannot specify both args and prompt", | |
| path: ["args"], | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/sandbox/validateSandboxBody.ts` around lines 8 - 18, The schema's refine
currently only rejects command+prompt but allows args+prompt which are dropped
downstream; update sandboxBodySchema.refine predicate to reject any payload
where prompt is present with either command or a non-empty args (e.g. change
refine to: data => !(data.prompt && (data.command || (data.args &&
data.args.length > 0)))), and update the refine message/path accordingly (e.g.
message "Cannot specify prompt with command or args" and path ["prompt"] or
["args"] as appropriate) so callers get validation errors when supplying args
alongside prompt.
| .refine(data => !(data.command && data.prompt), { | ||
| message: "Cannot specify both command and prompt", | ||
| path: ["prompt"], |
There was a problem hiding this comment.
missing_fields is semantically incorrect for this refinement error path.
When this refinement fires, line 44 responds with { missing_fields: ["prompt"], error: "Cannot specify both command and prompt" }. The prompt field is not missing — it's conflicting. API consumers receiving missing_fields: ["prompt"] will be misled into thinking they need to add prompt, not remove one of the two conflicting params.
This is a pre-existing naming issue in the error response shape, but the new refinement makes it actively user-hostile because the field literally is present.
💡 Suggested fix — differentiate conflict errors from missing-field errors
if (!result.success) {
const firstError = result.error.issues[0];
+ const isConflict = firstError.code === "custom";
return NextResponse.json(
{
status: "error",
- missing_fields: firstError.path,
+ ...(isConflict
+ ? { conflicting_fields: firstError.path }
+ : { missing_fields: firstError.path }),
error: firstError.message,
},
{
status: 400,
headers: getCorsHeaders(),
},
);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/sandbox/validateSandboxBody.ts` around lines 15 - 17, The refinement in
validateSandboxBody that uses .refine(..., { message: "Cannot specify both
command and prompt", path: ["prompt"] }) incorrectly maps to the response
shape's missing_fields; change the refinement to emit a distinct conflict
indicator (e.g., set path to ["conflicting_fields"] or attach a custom
issue/code like "conflict") and update the error-to-response mapping logic that
currently populates missing_fields to instead populate a new conflicting_fields
array when that conflict issue/code is present (include both "command" and
"prompt" or at least the conflicting field names). This keeps the message text,
but ensures the response shape distinguishes conflicts from missing fields and
uses identifiers in validateSandboxBody, the .refine call, and the
error-to-response mapping logic to locate and update the code.
Summary
promptfield tosandboxBodySchemainvalidateSandboxBody.tscommandandpromptcannot be used together (returns 400)processCreateSandbox, convertsprompttocommand: "opencode", args: ["run", prompt]Test plan
pnpm test lib/sandbox/__tests__/validateSandboxBody.test.ts— 8 tests passpnpm test lib/sandbox/__tests__/processCreateSandbox.test.ts— 8 tests pass🤖 Generated with Claude Code
Summary by CodeRabbit