Skip to content

DX-2548: add configurable box sizes (small, medium, large)#115

Open
CahidArda wants to merge 12 commits intomainfrom
DX-2548
Open

DX-2548: add configurable box sizes (small, medium, large)#115
CahidArda wants to merge 12 commits intomainfrom
DX-2548

Conversation

@CahidArda
Copy link
Copy Markdown
Collaborator

@CahidArda CahidArda commented Mar 27, 2026

New Box API Endpoints — SDK Support

Progress

  • Box Sizessize option on Box.create() and Box.fromSnapshot()
  • Delete Multiple BoxesBox.delete() and Box.deleteAll() static methods + BoxConnectionOptions refactor
  • Agent Options — Type-safe agentOptions on RunOptions / StreamOptions with Box<TProvider> generic
  • Skills APIbox.skills.add(), box.skills.remove(), box.skills.list() + enabled_skills on BoxData
  • Multi-Modal Prompt Filesfiles field on run/stream with multipart form data support
  • Check if ephemeral box allows size option and update accordingly
  • Make agent options camel case
  • Get Box by ID or Name — skipped (already works transparently)
  • CLAUDE.md & Skills Support — skipped (server-side behavior, no SDK change needed)
  • fromSnapshot new fieldsnetwork_policy and attach_headers already sent; size covered by Box Sizes

Box Sizes

Choose CPU and memory allocation at creation time. Defaults to "small".

const box = await Box.create({
  size: "large", // 8 cores, 16 GB
  agent: { provider: Agent.ClaudeCode, model: ClaudeCode.Sonnet_4_6 },
});

console.log(box.size); // "large"

Also works with snapshots:

const box = await Box.fromSnapshot("snap_abc123", { size: "medium" });

Implementation details

  • New BoxSize type ("small" | "medium" | "large") added to BoxConfig, BoxData, and exported from the package
  • box.size is a readonly property on Box instances, defaulting to "small" when the API response omits it
  • Box.create() and Box.fromSnapshot() send size in the request body when provided
  • New "Box Sizes" section added to README with pricing table and examples
  • Unit tests on create/fromSnapshot for sending and omitting size
  • Integration test creates boxes with all three sizes and verifies via Box.get()

Delete Multiple Boxes

Delete specific boxes by ID, or wipe all at once.

// Delete one
await Box.delete({ boxIds: "box-abc", apiKey: "..." });

// Delete several
await Box.delete({ boxIds: ["box-1", "box-2", "box-3"], apiKey: "..." });

// Delete all
await Box.deleteAll({ apiKey: "..." });

Both methods are also available on EphemeralBox:

await EphemeralBox.delete({ boxIds: "box-abc", apiKey: "..." });
await EphemeralBox.deleteAll({ apiKey: "..." });

Implementation details

  • New BoxConnectionOptions base interface (apiKey, baseUrl) — BoxConfig, EphemeralBoxConfig, ListOptions, and BoxGetOptions now extend it instead of redeclaring those fields
  • Box.delete() sends DELETE /v2/box with { ids: [...] } JSON body
  • Box.deleteAll() sends DELETE /v2/box without a body
  • EphemeralBox.delete and EphemeralBox.deleteAll delegate to the Box static methods
  • Unit tests for single/multiple ID deletion, deleteAll, missing apiKey, env var fallback, and API errors on both Box and EphemeralBox
  • Integration test creates ephemeral boxes and verifies deletion via Box.list()

Agent Options

Pass SDK-specific options to the underlying agent. The options are type-checked based on the provider.

// Claude Code — typed options
const box = await Box.create<Agent.ClaudeCode>({
  agent: { provider: Agent.ClaudeCode, model: ClaudeCode.Sonnet_4_6 },
});

const run = await box.agent.run({
  prompt: "Refactor auth.ts",
  agentOptions: {
    maxTurns: 5,
    effort: "max",
    thinking: { type: "enabled", budgetTokens: 16000 },
  },
});
// Codex — different options surface
const box = await Box.create<Agent.Codex>({
  agent: { provider: Agent.Codex, model: OpenAICodex.GPT_5_3_Codex },
});

await box.agent.run({
  prompt: "Fix the tests",
  agentOptions: {
    model_reasoning_effort: "high",
    personality: "pragmatic",
    web_search: true,
  },
});
// OpenCode
const box = await Box.create<Agent.OpenCode>({
  agent: { provider: Agent.OpenCode, model: OpenCodeModel.Claude_Sonnet_4_6 },
});

await box.agent.stream({
  prompt: "Add logging",
  agentOptions: {
    reasoningEffort: "high",
    textVerbosity: "low",
  },
});

Also works with webhooks:

await box.agent.run({
  prompt: "Deploy",
  agentOptions: { maxTurns: 3 },
  webhook: { url: "https://example.com/hook" },
});

Implementation details

  • ClaudeCodeAgentOptionsmaxTurns, maxBudgetUsd, effort, thinking, disallowedTools, agents, promptSuggestions, fallbackModel, systemPrompt
  • CodexAgentOptionsmodel_reasoning_effort, model_reasoning_summary, personality, web_search
  • OpenCodeAgentOptionsreasoningEffort, textVerbosity, reasoningSummary, thinking
  • AgentOptions<TProvider> conditional type resolves to the correct interface based on the provider
  • Box<TProvider> — the Box class is now generic. Box.create<TProvider>(), Box.get<TProvider>(), and Box.fromSnapshot<TProvider>() propagate the provider so agentOptions in run() and stream() is type-checked
  • agentOptions is serialized as agent_options in the JSON request body for all run paths (streaming, non-streaming, webhook)
  • Unit tests per provider (ClaudeCode, Codex, OpenCode) for both run and stream
  • Integration test with Claude Code agent options

Skills API

Add, remove, and list platform skills from the Context7 registry on a box.

// Add a skill
await box.skills.add("vercel/next.js/update-docs");

// List enabled skills
const skills = await box.skills.list();

// Remove a skill
await box.skills.remove("vercel/next.js/update-docs");

Planned implementation

  • enabled_skills?: string[] on BoxData
  • box.skills.add(skillId)POST /v2/box/{id}/config/skills with { skill_id }
  • box.skills.remove(skillId)DELETE /v2/box/{id}/config/skills/{owner}/{repo}/{skill}
  • box.skills.list() — returns enabled_skills from box data

Multi-Modal Prompt Files

Attach files to prompts. Two formats supported:

// Base64 (sent as JSON)
await box.agent.run({
  prompt: "What's in this image?",
  files: [
    { data: "iVBORw0KGgo...", mediaType: "image/png", filename: "screenshot.png" },
  ],
});

// Local file paths (sent as multipart form data)
await box.agent.run({
  prompt: "Analyze this CSV",
  files: ["./data/report.csv", "./data/summary.csv"],
});

Both formats work with box.agent.run() and box.agent.stream():

const run = await box.agent.stream({
  prompt: "What color is this?",
  files: [{ data: tinyPng, mediaType: "image/png" }],
});

for await (const chunk of run) {
  if (chunk.type === "text-delta") process.stdout.write(chunk.text);
}

Testing with curl

# Multipart (file path)
curl -X POST "https://us-east-1.box.upstash.com/v2/box/BOX_ID/run" \
  -H "X-Box-Api-Key: API_KEY" \
  -F 'prompt=How many rows in this CSV?' \
  -F 'files=@./data/report.csv'

# JSON (base64)
curl -X POST "https://us-east-1.box.upstash.com/v2/box/BOX_ID/run" \
  -H "X-Box-Api-Key: API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Describe this image","files":[{"data":"iVBORw0KGgo...","media_type":"image/png"}]}'

Implementation details

  • PromptFiles type: string[] (file paths → multipart) or { data, mediaType, filename? }[] (base64 → JSON)
  • files?: PromptFiles added to both RunOptions and StreamOptions
  • buildRunRequest() helper detects format: file paths build a FormData with binary parts; base64 objects are serialized as files array in JSON with media_type wire format
  • buildMultipartBody() reads files from disk, appends all options as form fields (objects serialized as JSON strings)
  • Unit tests for both formats across run, stream, and webhook paths
  • Integration tests with base64 images and local CSV file uploads

CahidArda and others added 2 commits March 27, 2026 16:06
Boxes can now be created with a `size` option that controls CPU and memory
allocation. Supported in Box.create() and Box.fromSnapshot(). The size is
exposed as a readonly property on the Box instance and defaults to "small".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract BoxConnectionOptions as shared base for BoxConfig, EphemeralBoxConfig,
ListOptions, and BoxGetOptions. Add static delete()/deleteAll() to both Box
and EphemeralBox for bulk deletion of boxes by ID or all at once.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear bot commented Mar 27, 2026

Box is now generic on TProvider (Box<Agent.ClaudeCode>, Box<Agent.Codex>,
Box<Agent.OpenCode>) so agentOptions in run/stream are type-checked per
provider. Options are forwarded as agent_options in the API request body.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@CahidArda CahidArda changed the title DX-2548: add configurable box sizes (small, medium, large) (WIP) DX-2548: add configurable box sizes (small, medium, large) Mar 27, 2026
@CahidArda CahidArda marked this pull request as draft March 27, 2026 15:04
CahidArda and others added 8 commits March 30, 2026 17:47
Add skills.add(), skills.remove(), and skills.list() methods for managing
Context7 registry skills on a box. Skills are added via POST, removed via
DELETE, and listed by fetching the box's enabled_skills from the API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Support attaching files to prompts via two formats:
- Local file paths (string[]) sent as multipart form data
- Base64 data objects sent inline as JSON with media_type wire format

Both formats work with box.agent.run() and box.agent.stream().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Support size parameter in EphemeralBox.create() and
EphemeralBox.fromSnapshot() for configuring CPU and memory allocation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split Box.deleteAll test into its own file and vitest config so it runs
first via a separate vitest invocation, preventing it from wiping boxes
that other parallel integration tests depend on.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Blob was created without a type, causing Node.js to send
Content-Type: application/octet-stream — which the server silently
ignores. Now infers the MIME type from the file extension.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Box.deleteAll/EphemeralBox.deleteAll and all associated tests,
config, and changeset references. Rename agentOptions to options in
RunOptions and StreamOptions interfaces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@CahidArda CahidArda marked this pull request as ready for review March 31, 2026 19:57
@CahidArda CahidArda changed the title (WIP) DX-2548: add configurable box sizes (small, medium, large) DX-2548: add configurable box sizes (small, medium, large) Mar 31, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant