Conversation
…sweep expired files The /v1/files routes now answer in Anthropic's FileMetadata shape when the caller sends anthropic-version (its SDK always does) and in the OpenAI shape otherwise, and the listing is cursor-paged (limit, after/after_id, order, has_more). Uploads referenced by a request that runs otari_code_execution are seeded into the sandbox session with PutFile; container_upload blocks are staged and replaced by a marker for the model. Files a run produces are fetched with GetFile, stored as code_execution_output files owned by the same user and workspace, and named with their file_id in the tool result. A bare input_file or input_image item at the top level of a Responses input is now normalized too. A background sweep reclaims the bytes and rows of expired and deleted files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks 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 |
|
This PR has been automatically closed because the required template sections were not restored within 24 hours. Please create a new PR using the template and complete the checklist. The template helps maintainers review your contribution. |
HareeshBahuleyan
left a comment
There was a problem hiding this comment.
Review by ChatGPT (AI assistant in pi), posted with user approval. Five findings from the PR/base-relative review are attached inline.
| response = await self._client.get( | ||
| f"{self._sandbox_url}/sessions/{self._session_id}/files", | ||
| params={"path": ref.filename}, | ||
| ) | ||
| response.raise_for_status() | ||
| except httpx.HTTPError as exc: | ||
| logger.warning("sandbox output %r could not be fetched: %s", ref.filename, exc) | ||
| continue | ||
| data = response.content | ||
| if not data or len(data) > self._files.max_output_bytes: |
There was a problem hiding this comment.
[P1] Bound sandbox downloads before buffering
httpx.get() downloads the entire output before checking max_output_bytes. Large sandbox files can exhaust gateway memory despite the configured limit. A 4-byte-limit probe consumed all 40 bytes.
Fix: Stream downloads and abort when the limit is exceeded.
| f"{self._sandbox_url}/sessions/{self._session_id}/files", | ||
| files={"file": (staged.filename, data, staged.mime_type)}, | ||
| data={"path": staged.filename}, |
There was a problem hiding this comment.
[P2] Prevent same-name attachments from overwriting
Distinct file IDs named data.csv are uploaded to the same sandbox path. Reproduction left only the second file’s contents, silently corrupting multi-file analysis.
Fix: Allocate unique paths and expose those paths consistently to the model.
| cursor_id = after or after_id | ||
| if cursor_id is not None: | ||
| cursor = await fetch_file(db, cursor_id, user_id, workspace_id=scope) | ||
| if cursor is None: | ||
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found") |
There was a problem hiding this comment.
[P2] Keep pagination and cursor expiry rules consistent
Listings include expired files, but cursor lookup rejects them. Reproduction returned an expired last_id with has_more=true; following that cursor returned 404, preventing access to subsequent live files.
Fix: Exclude expired files from listings and define consistent cursor-expiry behavior.
| if isinstance(item, dict) and item.get("type") == "input_text": | ||
| return {"role": "user", "content": [item]} | ||
| return item |
There was a problem hiding this comment.
[P2] Wrap native Responses content parts in messages
The new bare-input handling wraps extracted text only. Native PDFs/images remain top-level input_file/input_image items, which fail OpenAI’s ResponseInputItemParam schema validation. Wrapping the same result in a user message validates.
Fix: Wrap all supported bare content parts, including native passthrough results.
| "title": "Workspace Id" | ||
| } | ||
| }, | ||
| { |
There was a problem hiding this comment.
[P2] Regenerate the dashboard API client
The spec adds pagination parameters, but web/src/client/schema.ts remains unchanged. Regeneration produces a diff, failing the dashboard’s explicit client-drift gate.
Fix: Run pnpm --dir web client:generate.
|
This PR has been automatically closed because the required template sections were not restored within 24 hours. Please create a new PR using the template and complete the checklist. The template helps maintainers review your contribution. |
|
Continued in #1364: GitHub refuses to reopen a PR after its branch is force-pushed, and the branch was rebased onto current main. |
Description
Octonous talks to provider files APIs only through the official Anthropic and OpenAI SDKs pointed at a base URL, references uploads with
image/documentfilesources,container_upload,input_file, and expects code execution to see attachments and hand back downloadable files. Otari's files API already stored and resolved files, but only in the OpenAI shape, never into the sandbox, and never out of it. This closes those gaps so an open-source model behind Otari can take the same requests a frontier model does.anthropic-version(the SDK sends it on every call) getsFileMetadata(type,size_bytes,mime_type,downloadable, RFC 3339created_at) andfile_deletedon delete. OpenAI callers are unchanged.limit,after/after_id,order,has_more,first_id,last_id(part of Four list endpoints have no pagination bound #786).otari_code_executionin the request, every referenced upload is seeded into the session via the protocol'sPutFile.container_uploadblocks are staged and replaced by a marker for the model; without a sandbox they are read as documents. A refused seed fails the request rather than running over a missing input.GetFile, stored ascode_execution_outputfiles owned by the same user and workspace, and named with theirfile_idin the tool result. Storage failures never fail the run.inputitems (input_file,input_imageoutside a message) are now normalized.files_sweep_interval_sec, hourly,0disables). It is one entry in the lifespan worker registry, and commits through the Unit of Work like every other service.Docs (
docs/files.md, protocol doc), OpenAPI spec, and Postman collection are regenerated.Not in this PR
cfile_) and code-interpreter annotations on the Responses wire: needs the Anthropic-content-block lift the sandbox backend already notes.otari-sandbox-containerdoes not populate the file-reference list yet, so output collection is wired but idle until it does.How to test it locally
GET /api/v1/files?limit=2pages withhas_moreandlast_id.otari_code_executionand afile_idreference against a sandbox; the file is on disk in the session, and any file the run writes comes back as afile_idin the tool result and is fetchable from the files API.files_retention_hours: 1, wait past expiry (or setfiles_sweep_interval_seclow), and watch the sweep log line reclaim the bytes and rows.Covered by
tests/integration/test_files_endpoint.py(Anthropic shapes, paging, sweep),tests/unit/test_sandbox_backend.py(seeding and output collection),tests/unit/test_content_normalizer.py(bare Responses items), andtests/unit/test_gateway_lifespan_shutdown.py(the sweep worker starts and can be switched off).make lint,make typecheck, the unit suite, the OSS smoke gate, and the files, messages-dispatch, hybrid-chat and settings integration tests pass locally.PR Type
Relevant issues
Part of #786.
Checklist
tests/unit,tests/integration).make lint,make typecheck,make test).uv run python scripts/generate_openapi.py).ARCHITECTURE.mdorscripts/check_architecture.py, the description names the rule and says why.AI Usage
AI Model/Tool used: Claude Code
Any additional AI details you'd like to share:
Rebased onto main after the models package split, the lifespan worker registry, the derived settings view, and the Unit of Work transaction rule landed; the change was adapted to each.
🤖 Generated with Claude Code