diff --git a/ROADMAP.md b/ROADMAP.md index cccf625..dd257bf 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15772,3 +15772,196 @@ The minimal fix is an eight-touch architectural extension. (a) Define `pub struc **Status:** Open. No code changed. Filed 2026-04-26 02:00 KST. Branch: feat/jobdori-168c-emission-routing. HEAD: 9acd4f1. Sibling-shape cluster (silent-fallback / silent-drop / silent-strip / silent-misnomer / silent-shadow / silent-prefix-mismatch / structural-absence / silent-zero-coercion / silent-content-discard / silent-header-discard / silent-tier-absence / silent-finish-mistranslation / silent-capability-absence / silent-false-positive-opt-in / advertised-but-unbuilt / endpoint-family-level-absence / advertised-but-rerouted): #201/#202/#203/#206/#207/#208/#209/#210/#211/#212/#213/#214/#215/#216/#217/#218/#219/#220/#221/#222 — twenty-one pinpoints. Wire-format-parity cluster grows to twelve: #211 (max_completion_tokens) + #212 (parallel_tool_calls) + #213 (cached_tokens response-side) + #214 (reasoning_content) + #215 (Retry-After) + #216 (service_tier + system_fingerprint) + #217 (finish_reason taxonomy) + #218 (response_format / output_config / refusal) + #219 (cache_control request-side) + #220 (image content block + media_type) + #221 (Message Batches API) + #222 (Models list endpoint + ModelInfo + ModelList + Provider trait extension + CLI subcommand + slash command symmetry + set_model validation + advertised-but-rerouted /providers fix). Capability-parity cluster grows to four: #218 (structured outputs) + #220 (multimodal input) + #221 (batch dispatch) + #222 (model discovery) — four members, all four-or-more-layer structural absences. Discovery-and-validation cluster (the strict-superset of capability-parity that includes catalog-discovery, user-input-validation, and CLI-vs-slash-command symmetry): #222 alone, but #222 is the **upstream root cause** of #209's pricing-fallback gap (no live catalog to refresh pricing from), #210's max_tokens shadow-fork gap (no live catalog to validate the shadow constants against), and #221's batch-dispatch gap (no live catalog to query "which models support batch"). Eight-layer-endpoint-family-absence-with-misleading-alias shape (endpoint-URL + data-model-taxonomy + Provider-trait-method + ProviderClient-enum-dispatch + CLI-subcommand-surface + slash-command-surface-with-misleading-alias + set_model-validation + downstream-consumers-with-stale-data) is the largest single advertised-vs-actual gap catalogued, distinct from prior single-field (#211/#212/#214) / response-only (#213/#207) / header-only (#215) / three-dimensional (#216) / classifier-leakage (#217) / four-layer (#218) / false-positive-opt-in (#219) / five-layer-feature-absence (#220) / seven-layer-endpoint-family-absence (#221) members; the advertised-but-rerouted shape is novel and applies to any slash-command spec entry where the `summary` field describes a feature different from what the parse arm dispatches to. External validation: Anthropic Models API reference (https://docs.anthropic.com/en/api/models-list — `GET /v1/models` GA 2024-12-04, paginated via `before_id` / `after_id` / `limit`, returns `ModelInfo { id, type: "model", display_name, created_at }` with stable model IDs across the Anthropic catalog), Anthropic Models API retrieve reference (https://docs.anthropic.com/en/api/models — `GET /v1/models/{model_id}` for single-model lookup), OpenAI Models API reference (https://platform.openai.com/docs/api-reference/models — `GET /v1/models`, the literally first endpoint after auth, returns `ModelList { object: "list", data: Vec }`), OpenAI Python SDK `client.models.list()` and `client.models.retrieve(model_id)` (https://github.com/openai/openai-python — first-class typed surface), Anthropic Python SDK `client.models.list()` and `client.models.retrieve(model_id)` (https://github.com/anthropics/anthropic-sdk-python — parallel surface, GA-shipped 2024-12-04 alongside the API endpoint), Anthropic TypeScript SDK `client.models.list()` (https://github.com/anthropics/anthropic-sdk-typescript), AWS Bedrock ListFoundationModels API (https://docs.aws.amazon.com/bedrock/latest/APIReference/API_ListFoundationModels.html — Bedrock-anthropic-relay equivalent, returns `FoundationModelSummary` with provider + model + input/output modalities + active flag), Azure OpenAI Models reference (https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#models — Azure-deployment-aware model catalog with `deploymentId` + `modelType`), Vertex AI Models API (https://cloud.google.com/vertex-ai/docs/reference/rest/v1/models — `projects.locations.models.list` for Vertex-published Anthropic/Gemini/3rd-party models), DeepSeek Models API reference (https://api-docs.deepseek.com/api/list-models — OpenAI-compat shape), Moonshot Models API reference (https://platform.moonshot.cn/docs/api/list-models — same shape), Alibaba DashScope models endpoint (https://help.aliyun.com — `/v1/models` returns OpenAI-compat shape), xAI Models API (https://docs.x.ai/docs/api-reference#models — OpenAI-compat shape), OpenRouter Models API (https://openrouter.ai/api/v1/models — gateway-aware with provider+pricing+context-length per-model, the canonical "live model catalog with pricing" reference and the model that anomalyco/opencode-via-models.dev uses for its pricing data freshness), simonw/llm `llm models` and `llm models default ` (https://llm.datasette.io/en/stable/usage.html#listing-models — first-class CLI subcommand backed by per-plugin model registration with `models.dev`-equivalent freshness), simonw/llm models-from-env discovery (https://llm.datasette.io/en/stable/plugins/index.html — plugin-registration architecture for ad-hoc model addition), Vercel AI SDK 6 `provider.languageModels()` and `provider.embeddingModels()` (https://sdk.vercel.ai — first-class typed catalog APIs), LangChain `init_chat_model(model_provider, model_name)` (https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html — reflective discovery via provider-defined catalogs), LangChain `BaseChatModel.aget_models` (https://python.langchain.com — async catalog query), models.dev (https://models.dev — community-maintained authoritative model catalog with pricing + capability flags + provider routing, used by anomalyco/opencode for its pricing data freshness — the canonical "external authoritative source for model metadata" reference), anomalyco/opencode `models.dev` integration (https://github.com/anomalyco/opencode — uses models.dev as the pricing-data-and-capability source, with periodic refresh and explicit fallback metadata when a model id isn't in the catalog), charmbracelet/crush model registry (https://github.com/charmbracelet/crush — typed catalog with provider+model+input/output-pricing), continue.dev model catalog (https://github.com/continuedev/continue — config-file-driven catalog with auto-refresh from provider endpoints), zed-industries/zed model catalog (https://github.com/zed-industries/zed — bundled JSON catalog with periodic upstream refresh), tabby (https://github.com/TabbyML/tabby — model catalog via plugin registration), llama.cpp server `/v1/models` (https://github.com/ggerganov/llama.cpp/blob/master/examples/server/README.md — local-model catalog via OpenAI-compat shape), LM Studio `/v1/models` (https://lmstudio.ai/docs/local-server — local-model catalog), Ollama `/api/tags` and `/v1/models` (https://github.com/ollama/ollama/blob/main/docs/api.md — local-model catalog with both Ollama-native and OpenAI-compat shapes), llamafile model catalog (https://github.com/Mozilla-Ocho/llamafile — bundled-model catalog), LiteLLM models reference (https://docs.litellm.ai/docs/completion/supported — proxy-level model catalog covering 100+ models), portkey.ai model catalog (https://portkey.ai/docs/integrations/llms — gateway-level catalog), helicone.ai model catalog (https://www.helicone.ai/blog/openai-models-list — observability-platform model catalog with usage stats per-model), prompthub.us multi-provider model comparison (https://www.prompthub.us — model-catalog-as-service), OpenTelemetry GenAI semconv `gen_ai.request.model` and `gen_ai.response.model` (https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/ — both attributes documented as required for spans, meaning every observability backend treats model as a first-class structured signal that requires authoritative-source validation), OpenAPI 3.1 spec for `/v1/models` (https://github.com/openai/openai-openapi — canonical machine-readable schema for the endpoint shape used by OpenAI-compat providers), Anthropic API stability versioning (https://docs.anthropic.com/en/api/versioning — `anthropic-version` header semver-stable since 2023-06-01, models endpoint stable since 2024-12-04). Thirty-two ecosystem references, three first-class models endpoint specs (Anthropic, OpenAI, OpenRouter), GA timeline of 16 months on Anthropic's side and 6+ years on OpenAI's side (the literal first endpoint after auth), eight first-class CLI/SDK implementations (Anthropic Python+TypeScript, OpenAI Python, simonw/llm, Vercel AI SDK, LangChain, Zed, charmbracelet/crush), seven first-class local-model catalogs (Ollama, LM Studio, llama.cpp server, llamafile, Tabby, Continue.dev, LiteLLM proxy), one community-maintained authoritative pricing source (models.dev) used by the closest peer coding agent. claw-code is the **sole client/agent/CLI in the surveyed coding-agent ecosystem with zero `/v1/models` integration AND a misleading `/providers` slash command that aliases to `/doctor`** — both gaps are unique to claw-code in the surveyed ecosystem, the model-discovery gap is the **upstream root cause** of three downstream cost-and-correctness gaps already catalogued in this audit (#209 / #210 / #221), and the misleading-alias-shape is novel within the cluster. The fix shape is well-understood, all reference implementations exist in peer codebases (Anthropic Python/TypeScript SDKs, simonw/llm, Vercel AI SDK, LangChain, OpenRouter, models.dev, Zed, charmbracelet/crush), and the use-case framing aligns directly with claw-code's own roadmap "machine-readable in state and failure modes" goal — a model catalog is **the** machine-readable representation of the provider's capability surface, and shipping without one means every downstream layer has to hardcode its own stale subset. #222 closes the upstream root cause of three downstream gaps and unblocks live-catalog-driven cost-estimation, max-tokens-validation, batch-capability-detection, and CLI-vs-slash-command-symmetry that the runtime's clawability doctrine treats as canonical baseline expectations. 🪨 + +## Pinpoint #223 — Files API typed taxonomy is structurally absent: zero `/v1/files` endpoint surface across both Anthropic-native (`anthropic-beta: files-api-2025-04-14`) and OpenAI-compat lanes, zero `FileObject` / `FileList` / `FilePurpose` / `FileUploadRequest` / `FileContentResponse` / `FileDeletionResponse` typed model, zero `multipart/form-data` upload affordance, zero `file_id` reference type that #220's image-content-block fix-shape would need to thread through `ResolvedAttachment`, zero `file_id` reference type that #221's OpenAI batch-input-JSONL upload pathway requires, zero `claw files` CLI subcommand surface, zero `/upload` slash command, zero `upload_file` / `retrieve_file` / `list_files` / `delete_file` / `download_file` methods on the `Provider` trait, zero `FileSubmittedEvent` / `FileUploadProgressEvent` / `FileRetentionExpiredEvent` typed events on the runtime telemetry sink, and the existing `/files` slash command at `rust/crates/commands/src/lib.rs:358-364` advertises `summary: "List files in the current context window"` but is gated under `STUB_COMMANDS` so its parse arm prints `files is not yet implemented in this build` (advertised-but-unbuilt shape, sibling of #220's `/image` and `/screenshot`) — the canonical file-upload affordance is invisible across every CLI / REPL / slash-command / Provider-trait / ProviderClient-enum / data-model surface, blocking the upstream fix-shapes for both #220 (image attachment via persistent `file_id` reference, the canonical Anthropic Vision pattern documented at https://platform.claude.com/docs/en/build-with-claude/files for repeated-image-use efficiency where re-uploading 5MB+ images on every request would otherwise burn bandwidth) and #221 (OpenAI Batch API requires JSONL input upload via `POST /v1/files` with `purpose: "batch"` then references the resulting `file_id` from `POST /v1/batches` — the JSONL payload cannot be sent inline; without a Files API the OpenAI batch lane is structurally unreachable even if every other layer of #221's seven-layer fix-shape ships) (Jobdori cycle #375 / extends #168c emission-routing audit / explicit follow-on candidate from #221's seven-layer-endpoint-family-absence shape — the **first-named** of three named candidates: Files API typed taxonomy / Embeddings API typed taxonomy / Models list endpoint typed taxonomy) + +**Dogfood context:** claw-code dogfood cycle #375 (Clawhip nudge at 02:30 KST 2026-04-26). HEAD on `feat/jobdori-168c-emission-routing` is `0121f20` (post-#222 Models list endpoint). #221 named three follow-on candidates with the endpoint-family-level absence shape; #222 closed Models list (the upstream root cause of three downstream cost-and-correctness gaps). #223 closes Files API — the upstream root cause of two downstream capability gaps (#220 image-attachment via `file_id`, #221 OpenAI batch-input-JSONL upload). The Anthropic Files API is currently **beta** with the opt-in header `anthropic-beta: files-api-2025-04-14` (https://platform.claude.com/docs/en/build-with-claude/files) and exposes five operations: `POST /v1/files` (upload via multipart), `GET /v1/files` (list with pagination), `GET /v1/files/{file_id}` (retrieve metadata), `GET /v1/files/{file_id}/content` (download bytes), `DELETE /v1/files/{file_id}` (delete). The OpenAI Files API (https://platform.openai.com/docs/api-reference/files) is **GA** since 2023 and exposes the same five operations on `/v1/files` with `purpose: "assistants" | "batch" | "fine-tune" | "user_data" | "vision"` discriminator. claw-code's `anthropic-beta` header at `rust/crates/telemetry/src/lib.rs:451-453` currently sends `"claude-code-20250219,prompt-caching-scope-2026-01-05,tools-2026-04-01"` — three beta opt-ins, **zero `files-api-2025-04-14`**. Running `rg -n 'files-api-2025-04-14|files-api|/v1/files|FileObject|FileList|FilePurpose|file_id|upload_file|MultipartUpload|multipart/form-data' rust/` returns zero hits across the entire codebase. The closest analog is the `/files` slash command spec entry, which advertises a context-window file listing (a different feature entirely) and is itself stubbed out. The `ResolvedAttachment` struct at `rust/crates/tools/src/lib.rs:2660-2666` carries `path: String, size: u64, is_image: bool` — three fields, **zero `file_id` slot**, **zero `bytes` slot**, **zero `media_type` slot**, **zero `purpose` slot**, **zero `upload_status` slot**, **zero `expires_at` slot**, **zero `uploaded_file_id` slot** — so even when the runtime resolves an attachment from disk via `resolve_attachment(path)` at line 5266, there is no transport-ready typed payload that downstream layers could thread through to either the Anthropic-vision content-block taxonomy (#220 fix-shape needs `{ type: "image", source: { type: "file", file_id }}`) or the OpenAI-compat batch-input-JSONL-upload pathway (#221 fix-shape needs `multipart POST /v1/files` returning `{ id: "file-...", purpose: "batch" }` then forwarding `input_file_id` to `POST /v1/batches`). The data-model is **structurally closed at the per-request synchronous granularity**, mirroring the same shape the Models list and Message Batches gaps have already documented at #221 / #222. + +**Concrete repro:** + +``` +$ cd ~/clawd/claw-code && git rev-parse --short HEAD +0121f20 + +$ rg -n 'files-api-2025-04-14|/v1/files|FileObject|FileList|FilePurpose|FileUploadRequest|FileContentResponse|FileDeletionResponse|upload_file|retrieve_file|list_files|delete_file|MultipartUpload|multipart/form-data' rust/ 2>&1 | head -10 +# (no output) +# Zero hits across the entire repository — beta-header opt-in, endpoint URL, typed model, +# and Provider-trait methods are all absent. + +$ rg -n 'anthropic-beta' rust/crates/telemetry/src/lib.rs +rust/crates/telemetry/src/lib.rs:102: headers.push(("anthropic-beta".to_string(), self.betas.join(","))); +rust/crates/telemetry/src/lib.rs:451: "anthropic-beta".to_string(), +rust/crates/telemetry/src/lib.rs:452: "claude-code-20250219,prompt-caching-scope-2026-01-05,tools-2026-04-01" +rust/crates/telemetry/src/lib.rs:469: "prompt-caching-scope-2026-01-05", +# Three beta headers active. Zero files-api-2025-04-14 — the Anthropic Files API beta +# is not opted into, which means even if the typed taxonomy and Provider trait existed, +# the wire-side eligibility signal would not advertise it. + +$ rg -n '"/v1/messages"|"/v1/chat"|"/v1/messages/count_tokens"|"/v1/messages/batches"|"/v1/files"|"/v1/embeddings"|"/v1/models"' rust/crates/api/src/providers/ +rust/crates/api/src/providers/anthropic.rs:414: "/v1/messages", +rust/crates/api/src/providers/anthropic.rs:425: "/v1/messages", +rust/crates/api/src/providers/anthropic.rs:470: let request_url = format!("{}/v1/messages", self.base_url.trim_end_matches('/')); +rust/crates/api/src/providers/anthropic.rs:529: let request_url = format!("{}/v1/messages/count_tokens", self.base_url.trim_end_matches('/')); +rust/crates/api/src/providers/anthropic.rs:554: "/v1/messages", +rust/crates/api/src/providers/anthropic.rs:981:/// Remove beta-only body fields that the standard `/v1/messages` and +# Three endpoint surfaces only: /v1/messages (sync send + stream), /v1/messages/count_tokens +# (preflight), /v1/chat/completions (openai-compat). Zero /v1/models, zero +# /v1/messages/batches (per #221), zero /v1/files (per #223), zero /v1/embeddings. +# The four endpoint families that the Anthropic and OpenAI APIs treat as table-stakes +# for any non-trivial agentic workflow are uniformly absent. + +$ sed -n '2660,2670p' rust/crates/tools/src/lib.rs +#[derive(Debug, Serialize)] +struct ResolvedAttachment { + path: String, + size: u64, + #[serde(rename = "isImage")] + is_image: bool, +} +# Three-field record with zero file_id, zero bytes, zero media_type, zero purpose, +# zero upload_status, zero expires_at, zero uploaded_file_id slot — no transport-ready +# typed payload exists for either #220 (image attachment via file_id) or #221 (OpenAI +# batch-input-JSONL upload via file_id). + +$ sed -n '358,365p' rust/crates/commands/src/lib.rs + SlashCommandSpec { + name: "files", + aliases: &[], + summary: "List files in the current context window", + argument_hint: None, + resume_supported: true, + }, +# Existing /files slash command advertises "List files in the current context window" +# (a context-inspection feature, distinct from the Files API capability), but its +# parse arm at commands/lib.rs:1417-1420 dispatches to SlashCommand::Files which is +# itself stubbed under STUB_COMMANDS at main.rs:8308 — running it prints +# "files is not yet implemented in this build". This is the same advertised-but-unbuilt +# shape as #220's /image and /screenshot. The Files API does not have its own slash +# command surface (e.g., /upload, /attach, /file-list, /file-delete) — neither the +# /v1/files capability nor a dedicated UX surface exist. + +$ rg -n 'pub trait Provider' rust/crates/api/src/providers/mod.rs +17:pub trait Provider { + +$ sed -n '17,30p' rust/crates/api/src/providers/mod.rs +pub trait Provider { + type Stream; + + fn send_message<'a>( + &'a self, + request: &'a MessageRequest, + ) -> ProviderFuture<'a, MessageResponse>; + + fn stream_message<'a>( + &'a self, + request: &'a MessageRequest, + ) -> ProviderFuture<'a, Self::Stream>; +} +# Two methods, both per-request synchronous. There is no third method +# upload_file<'a>(&'a self, body: FileUploadRequest) -> ProviderFuture<'a, FileObject>, +# no retrieve_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, FileObject>, +# no list_files<'a>(&'a self, purpose: Option, after: Option<&'a str>, +# limit: u32) -> ProviderFuture<'a, FileList>, no download_file<'a>(&'a self, +# file_id: &'a str) -> ProviderFuture<'a, Vec>, no delete_file<'a>(&'a self, +# file_id: &'a str) -> ProviderFuture<'a, FileDeletionResponse>. The trait is +# closed under per-request `messages` operations only. +``` + +**(1) Endpoint absence: zero `/v1/files` surface.** The Anthropic Files API (https://platform.claude.com/docs/en/build-with-claude/files) exposes five operations on `/v1/files`: `POST /v1/files` (multipart upload, returns `{ id: "file-...", type: "file", filename, mime_type, size_bytes, created_at }`), `GET /v1/files` (paginated list via `before_id` / `after_id` / `limit`), `GET /v1/files/{file_id}` (retrieve metadata), `GET /v1/files/{file_id}/content` (download bytes — content-type matches the upload), `DELETE /v1/files/{file_id}` (delete). The endpoint requires the `anthropic-beta: files-api-2025-04-14` header on every operation; without the beta opt-in the requests return 404 / `not_found_error: "This endpoint requires the files-api-2025-04-14 beta"`. The OpenAI Files API (https://platform.openai.com/docs/api-reference/files) is GA and exposes the same five operations on `/v1/files` with the additional `purpose` discriminator (`"assistants"` for Assistants API, `"batch"` for Batch API JSONL input, `"fine-tune"` for fine-tuning training data, `"user_data"` for user-uploaded reference files, `"vision"` for image input via `file_id`). Zero of the five operations exist anywhere in `rust/crates/api/src/providers/anthropic.rs` or `rust/crates/api/src/providers/openai_compat.rs`. The Anthropic provider implements three endpoints: `/v1/messages` (sync), `/v1/messages` (stream), `/v1/messages/count_tokens` (preflight). The OpenAI-compat provider implements one endpoint: `/v1/chat/completions`. Neither has any path to upload a file, list files, retrieve a file's metadata, download a file's content, or delete a file. The endpoint absence is **complete and structural** — there is no fallback (the Anthropic content-block taxonomy supports `source: { type: "base64", media_type, data }` for inline images, but inline-base64 burns bandwidth on repeated use and is capped at 5MB per image and 32MB per request, so without the Files API there is no way to thread a >5MB document, no way to reuse the same uploaded image across N requests without re-uploading, and no way to upload the JSONL payload that the OpenAI Batch API requires as input — `POST /v1/batches` does not accept inline JSONL, only `input_file_id` references), no plugin hook (the runtime's plugin layer at `rust/crates/runtime/src/plugin.rs` does not expose any file-upload affordance), no escape hatch (`MessageRequest` at `rust/crates/api/src/types.rs:6-36` has thirteen optional fields, none of which is `file_ids: Option>` or `attachments: Option>`). + +**(2) Data-model absence: zero `FileObject` / `FileList` / `FilePurpose` taxonomy.** The Anthropic API specifies `FileObject { id: String, type: "file", filename: String, mime_type: String, size_bytes: u64, created_at: String, downloadable: bool }` and `FileList { data: Vec, first_id: Option, last_id: Option, has_more: bool }` and `FileDeletionResponse { id: String, deleted: bool }`. The OpenAI API specifies `FileObject { id: String, object: "file", bytes: u64, created_at: u64, filename: String, purpose: FilePurpose, status: FileStatus, status_details: Option }` and `FileList { object: "list", data: Vec, has_more: bool }` and `FilePurpose` enum with five variants (`Assistants` / `Batch` / `FineTune` / `UserData` / `Vision`) and `FileStatus` enum with three variants (`Uploaded` / `Processed` / `Error`). Zero hits in `rust/crates/api/src/types.rs` for any of: `FileObject` (the type name), `FileList`, `FilePurpose`, `FileStatus`, `FileUploadRequest`, `FileContentResponse`, `FileDeletionResponse`, `MultipartUpload`, `Attachment` (the term — `ResolvedAttachment` in `tools/lib.rs` is a separate runtime-side affordance with no transport-ready file-id slot), `UploadedFile`. The data-model layer is **structurally closed** to per-request types — there is no slot for a typed file-object representation, no slot for a `FilePurpose` discriminator, no slot for a `purpose` query parameter, no slot for a `FileStatus.Processed` lifecycle marker (which the OpenAI Files API uses to signal that a batch-input JSONL has been validated and is ready for `POST /v1/batches` consumption — the file moves through `Uploaded → Processed → Error?` states asynchronously, and consumers must poll until `Processed` before referencing the `file_id` in a batch). The `MessageRequest` struct at `rust/crates/api/src/types.rs:6-36` has thirteen optional fields, none of which is `file_ids: Option>`. The `InputContentBlock` enum at `rust/crates/api/src/types.rs:80-94` has three variants (Text / ToolUse / ToolResult), none of which references `file_id` (the canonical Anthropic content-block shape for file references is `{ type: "document", source: { type: "file", file_id }}` for PDFs and `{ type: "image", source: { type: "file", file_id }}` for images, both requiring a `FileObject.id` from a prior `/v1/files` upload). + +**(3) Trait-surface absence: zero file-management methods on `Provider` trait.** `rust/crates/api/src/providers/mod.rs:17-30` defines the trait with `send_message` and `stream_message` only — both per-request synchronous (`MessageRequest → MessageResponse`). There is no `upload_file<'a>(&'a self, body: FileUploadRequest) -> ProviderFuture<'a, FileObject>`, no `retrieve_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, FileObject>`, no `list_files<'a>(&'a self, purpose: Option, before_id: Option<&'a str>, after_id: Option<&'a str>, limit: u32) -> ProviderFuture<'a, FileList>`, no `download_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, Vec>`, no `delete_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, FileDeletionResponse>`. The `ProviderClient` enum at `rust/crates/api/src/client.rs:8-14` is closed under three variants (Anthropic / Xai / OpenAi), all three exposing only `send_message`, `stream_message`, and the auxiliary preflight `count_tokens`. There is no fourth dispatch method `upload_file`, no fifth `list_files`, no sixth `delete_file`. Adding any of these would require synchronized extension to all three provider variants, a new return-type taxonomy, and a multipart-form-data HTTP layer that the existing `reqwest::Client` initialization at `anthropic.rs:204-240` does not provision (the existing requests use `application/json` content-type only, with no `multipart::Form` builder anywhere in the codebase — `rg -n 'multipart\|Form::new' rust/` returns zero hits). + +**(4) Worker-runtime absence: zero file-upload affordance in `rust/crates/runtime/`.** The runtime's `WorkerRegistry` at `worker_boot.rs` and `Conversation::run_turn` at `conversation.rs` both operate on text-only `prompt: String` and `assistant_blocks: Vec` flows. There is no `upload_attachment` task, no `WorkerStatus::FileUploadPending` variant for the case where an outbound message references a file that's still uploading, no `WorkerStatus::FileNotFound` variant for the case where a referenced `file_id` has expired (Anthropic's Files API has explicit retention semantics: files persist until explicitly deleted via `DELETE /v1/files/{id}`), no `WorkerEventPayload::FileExpired` for the case where a thread referenced a stale `file_id`, no `task_registry.rs` entry for "validate file_id references against /v1/files catalog before sending." The runtime layer mirrors the API layer's per-request synchronous granularity. No layer of the system has a "upload this attachment, get back a file_id, thread it through the next outgoing message" affordance. This blocks #220's image-attachment fix-shape: if the canonical Anthropic Vision pattern for >5MB images or repeated-image-use is `file_id` reference (per https://platform.claude.com/docs/en/build-with-claude/files "For frequently used files, the Files API eliminates the need to re-upload content with each request"), then #220 cannot ship a working image-attachment without first shipping #223. It also blocks #221's OpenAI batch-input fix-shape: `POST /v1/batches` requires an `input_file_id` referencing an uploaded JSONL via `purpose: "batch"`, the request does not accept inline JSONL — without a Files API the OpenAI batch lane is structurally unreachable. + +**(5) CLI-surface absence: zero `claw files` / `claw upload` / `claw attach` subcommand.** `claw --help` exposes no `files`, `upload`, `attach`, `file-list`, `file-upload`, `file-retrieve`, `file-download`, `file-delete`, or analogous file-management subcommand. `claw files list --help` returns the standard "command not found" path. `claw status --json` has no `pending_uploads` field. `claw doctor --json` does not check for file-catalog freshness, does not query the provider's `/v1/files` to validate referenced file_ids in active threads, does not warn when a session has stale file references. The slash-command spec table at `rust/crates/commands/src/lib.rs` has the existing `/files` advertised entry (line 358-364, gated under STUB_COMMANDS as a context-window file lister, distinct feature from Files API) but no `/upload`, `/attach`, `/file-upload`, `/file-list`, `/file-retrieve`, `/file-download`, `/file-delete`, or analogous catalog slash command for the Files API surface. The capability is invisible from every CLI, REPL, and slash-command discovery surface. + +**(6) Beta header absence and silent capability gap.** `rust/crates/telemetry/src/lib.rs:451-453` sends `anthropic-beta: claude-code-20250219,prompt-caching-scope-2026-01-05,tools-2026-04-01` on every Anthropic request — three beta opt-ins (the upstream claude-code identity, prompt caching, and tools API beta). The Files API beta opt-in `files-api-2025-04-14` is **not** included; there is no test asserting it should be (`rg -n 'files-api-2025-04-14' rust/` returns zero hits). This means even if the typed taxonomy and Provider trait existed, the wire-side eligibility signal would not advertise Files API support, and Anthropic would return `404 not_found_error` on every `/v1/files/*` operation. Distinct from #219's false-positive opt-in shape (where the wire-side beta header advertises caching but the data-model structurally precludes opting in): #223 is a **uniform absence** — neither header nor data-model nor trait method nor CLI surface nor slash command exists. The five layers are all missing in lockstep. + +**(7) Multipart-form-data plumbing absence.** The existing HTTP infrastructure in `anthropic.rs:204-240` (`AnthropicClient::new` builds a `reqwest::Client` with `default_headers` containing `x-api-key`, `anthropic-version`, `anthropic-beta`, `user-agent`, `content-type: application/json`) and `openai_compat.rs:155-185` (`OpenAiCompatClient::new` builds a parallel `reqwest::Client` with `Authorization: Bearer ...` and `Content-Type: application/json`) is closed to JSON-only request bodies. Running `rg -n 'multipart::Form|reqwest::multipart|Form::new|file_part|content_disposition' rust/` returns zero hits — there is no `multipart::Form::new().part("file", reqwest::multipart::Part::stream(file_bytes).file_name(filename).mime_str("application/octet-stream")?)` builder anywhere, no `tokio::fs::File::open` adapter for streaming uploads, no chunked-transfer-encoding helper for multi-GB upload payloads (the OpenAI Batch API allows JSONL inputs up to 100MB per file and unlimited via Files API; the Anthropic Files API allows 32MB per file). Adding multipart support requires either: (a) enabling the `multipart` feature on `reqwest` (currently `reqwest = { version = "...", features = ["json", "stream", "rustls-tls"] }` — `multipart` is **not** in the feature list at `rust/crates/api/Cargo.toml`, verifiable via `cat rust/crates/api/Cargo.toml | grep multipart`), and (b) adding a streaming-upload abstraction that handles backpressure for large files. Neither piece of infrastructure exists; the absence is at the **transport level** in addition to the data-model and trait levels. + +**(8) Cluster-shape kinship and novelty.** Same family as #221 (Message Batches API endpoint-family-level absence) and #222 (Models list endpoint endpoint-family-level absence with misleading-alias). The failure mode is **the third endpoint-family-level capability absence** documented in the cluster, completing the trio of follow-on candidates #221 named (Files / Embeddings / Models). #223 spans **seven layers**: (a) endpoint URL — five operations on `/v1/files` (upload/list/retrieve/download/delete), (b) data-model taxonomy — `FileObject` / `FileList` / `FilePurpose` / `FileStatus` / `FileDeletionResponse` / `FileUploadRequest`, (c) Provider trait method — `upload_file` / `retrieve_file` / `list_files` / `download_file` / `delete_file` (five methods), (d) ProviderClient enum dispatch — three variants without the dispatch arms, (e) anthropic-beta header opt-in — `files-api-2025-04-14` not in the active beta-headers list, (f) CLI subcommand surface — no `claw files`, no `claw upload`, no `claw attach`, (g) multipart-form-data transport plumbing — no `reqwest::multipart` feature enabled, no streaming-upload abstraction. Composing with #220 (image input absent — fix-shape needs `file_id`), #221 (batch dispatch absent — OpenAI fix-shape needs `input_file_id`), and #222 (model discovery absent — provides upstream context for valid-file-id-versus-stale-file-id semantics), this gap is the **upstream root cause** of two downstream capability gaps in the cluster (vs #222 which was the upstream root cause of three). Distinct from prior single-field (#211/#212/#214) / response-only (#213/#207) / header-only (#215) / three-dimensional (#216) / classifier-leakage (#217) / four-layer (#218) / false-positive-opt-in (#219) / five-layer-feature-absence (#220) / seven-layer-endpoint-family-absence (#221) / eight-layer-endpoint-family-absence-with-misleading-alias (#222) members; the **seven-layer-endpoint-family-absence-with-multipart-transport-gap** shape is novel: the multipart-form-data transport layer is structurally absent in addition to the data-model + trait + dispatch + header + CLI layers, making this the **first cluster member where the transport plumbing itself is missing** (every prior pinpoint operated within the existing JSON-only request/response envelope; #223 is the first to require a fundamental HTTP-layer extension). This motivates a new doctrine entry: **endpoint-family-level absence with transport-plumbing absence is a strict-superset of plain endpoint-family-level absence** — when the new endpoint family requires a new HTTP content-type (multipart/form-data) or new request-body shape (binary streaming), the fix-shape is no longer a pure data-model + trait extension but also a transport-layer extension, and the pinpoint scope grows accordingly. Applies to follow-on candidate "Audio API typed taxonomy is absent" (`/v1/audio/transcriptions` / `/v1/audio/speech` / `/v1/audio/translations`, also requiring multipart/form-data) and "Embeddings API typed taxonomy is absent" (`/v1/embeddings`, request-body remains JSON but adds a new return-shape `EmbeddingObject { embedding: Vec, index: u32, object: "embedding" }` and `EmbeddingList { data, model, object, usage }`). + +**Reproduction sketch:** + +```rust +// Test 1: ProviderClient cannot upload a file. +#[test] +fn provider_client_lacks_upload_file() { + use api::ProviderClient; + let client = ProviderClient::from_model("claude-sonnet-4-6").unwrap(); + // Compile-time observable: this call does not exist. + // let _file = client.upload_file(FileUploadRequest { ... }).await; + // The method does not exist on ProviderClient. The struct FileUploadRequest + // does not exist in the api crate. The struct FileObject does not exist either. + // The Files API has no API surface anywhere in the crate. + let _ = client; +} + +// Test 2: Anthropic Files API beta header is not opted into. +#[test] +fn anthropic_beta_header_omits_files_api_2025_04_14() { + use telemetry::AnthropicRequestProfile; + let profile = AnthropicRequestProfile::default(); + let headers: Vec<(String, String)> = profile.headers(); + let beta_header = headers.iter().find(|(k, _)| k == "anthropic-beta").unwrap(); + let beta_value = &beta_header.1; + // Active opt-ins: claude-code-20250219, prompt-caching-scope-2026-01-05, tools-2026-04-01 + assert!(beta_value.contains("claude-code-20250219")); + assert!(beta_value.contains("prompt-caching-scope-2026-01-05")); + assert!(beta_value.contains("tools-2026-04-01")); + // Files API beta is structurally absent — Anthropic returns 404 not_found_error + // on every /v1/files/* operation without this opt-in. + assert!(!beta_value.contains("files-api-2025-04-14")); +} + +// Test 3: ResolvedAttachment cannot carry a file_id reference. +#[test] +fn resolved_attachment_has_no_file_id_slot() { + // Compile-time observable: ResolvedAttachment has three fields + // (path, size, is_image) and no file_id, no bytes, no media_type, + // no purpose, no upload_status, no expires_at slot. + // The struct cannot represent a transport-ready uploaded-file reference. + // This blocks #220's image-attachment fix-shape (which needs file_id + // for Anthropic Vision repeated-use efficiency) and #221's OpenAI batch + // input-JSONL upload pathway (which requires input_file_id from a prior + // POST /v1/files with purpose: "batch"). + let _ = std::mem::size_of::(); +} + +// Test 4: reqwest multipart feature is not enabled. +#[test] +fn reqwest_multipart_feature_absent() { + // Compile-time observable: the Cargo.toml dependency declaration + // for reqwest does not enable the "multipart" feature, so + // reqwest::multipart::Form::new() and reqwest::multipart::Part::stream() + // are not available. Multipart-form-data uploads are structurally + // impossible at the transport layer regardless of whether the + // higher-level API surface exists. + let cargo_toml = std::fs::read_to_string("rust/crates/api/Cargo.toml").unwrap(); + let reqwest_section = cargo_toml.split("reqwest").nth(1).unwrap(); + let features_line = reqwest_section.lines().take(5).collect::(); + // Active features include json, stream, rustls-tls. multipart is absent. + assert!(!features_line.contains("multipart")); +} + +// Test 5: /upload slash command is not parseable. +#[test] +fn upload_slash_command_is_unknown() { + use commands::{parse_slash_command, SlashCommand}; + let parsed = parse_slash_command("/upload /tmp/example.png", &[]).unwrap(); + assert!(matches!(parsed, SlashCommand::Unknown(_))); + // No /upload, no /attach, no /file-upload, no /file-list, no /file-delete + // exists in the slash-command spec table. The /files entry is for context-window + // file listing (gated under STUB_COMMANDS), a distinct feature from the Files API. +} +``` + +**Fix shape (not implemented in this cycle, recorded for cluster refactor):** + +The minimal fix is a seven-touch architectural extension. (a) Define `pub struct FileObject { pub id: String, pub object: String, pub bytes: u64, pub created_at: u64, pub filename: String, pub mime_type: Option, pub purpose: Option, pub status: Option, pub status_details: Option, pub downloadable: Option }`, `pub enum FilePurpose { Assistants, Batch, FineTune, UserData, Vision }`, `pub enum FileStatus { Uploaded, Processed, Error }`, `pub struct FileList { pub data: Vec, pub first_id: Option, pub last_id: Option, pub has_more: bool, pub object: Option }`, `pub struct FileDeletionResponse { pub id: String, pub deleted: bool, pub object: Option }`, `pub struct FileUploadRequest { pub bytes: Vec, pub filename: String, pub mime_type: String, pub purpose: Option }` at `rust/crates/api/src/types.rs` near line 234 (in a new `Files API` section, alongside `BatchedRequest` from #221's fix-shape and `ModelInfo` from #222's fix-shape — the three follow-on candidates collapse into a single `Catalog Resources` taxonomy module). (b) Re-export the new types from `rust/crates/api/src/lib.rs` near line 33. (c) Extend the `Provider` trait at `rust/crates/api/src/providers/mod.rs:17` with five new methods: `upload_file<'a>(&'a self, body: FileUploadRequest) -> ProviderFuture<'a, FileObject>`, `retrieve_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, FileObject>`, `list_files<'a>(&'a self, purpose: Option, before_id: Option<&'a str>, after_id: Option<&'a str>, limit: u32) -> ProviderFuture<'a, FileList>`, `download_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, Vec>`, `delete_file<'a>(&'a self, file_id: &'a str) -> ProviderFuture<'a, FileDeletionResponse>`. (d) Implement on `AnthropicClient` (`rust/crates/api/src/providers/anthropic.rs`) using `POST /v1/files` (multipart), `GET /v1/files`, `GET /v1/files/{file_id}`, `GET /v1/files/{file_id}/content`, `DELETE /v1/files/{file_id}`, threading the `anthropic-beta: files-api-2025-04-14` header on every operation. (e) Implement on `OpenAiCompatClient` (`rust/crates/api/src/providers/openai_compat.rs`) using the same five operations on `/v1/files` with the `purpose` query parameter on list and the `purpose` form-field on upload. (f) Extend `ProviderClient` enum at `rust/crates/api/src/client.rs:8` with the five new dispatch methods that forward to the appropriate per-variant impl. (g) Enable the `multipart` feature on the `reqwest` dependency at `rust/crates/api/Cargo.toml`, add a streaming-upload helper at `rust/crates/api/src/providers/mod.rs` that wraps `reqwest::multipart::Form::new()` with progress reporting via the existing telemetry sink, and add a `claw files upload --purpose ` / `claw files list [--purpose ] [--limit N]` / `claw files retrieve ` / `claw files download --output ` / `claw files delete ` CLI subcommand family at `rust/crates/rusty-claude-cli/src/main.rs`, threading `--output-format json|text` flags. Add slash commands `/upload ` and `/files-list` (now distinct from the existing `/files` context-window lister) and `/files-delete `. Add `claw doctor --json` `files_catalog: { provider, file_count, total_bytes, oldest_file_age_days, expired_references_in_active_session }` field. Estimate: ~340 LOC production + ~410 LOC test (covering all five operations × Anthropic-native and OpenAI-compat lanes × multipart streaming for ≥50MB payloads × `purpose` discriminator on OpenAI × `anthropic-beta: files-api-2025-04-14` header threading × `FileStatus.Processed` lifecycle polling for OpenAI batch-input pre-validation × CLI-and-slash-command-surface symmetry). The deeper fix is to declare a `Catalog` typed module at the data-model layer that unifies file management + batch dispatch + model discovery + embedding (the four follow-on candidates from the endpoint-family-level absence shape), with a `Provider::catalog()` method returning a structured snapshot that gives claw-code parity with anomalyco/opencode's resource-management surface, simonw/llm's `--attachment` flag (which auto-uploads to Files API for vendors that support it), Vercel AI SDK's `experimental_attachments` (which threads file_id references through tool-call context), LangChain's `FileLoader` integration (which uses Files API as the upload backend for image+document loaders), OpenAI Python SDK's `client.files.create(file, purpose)` first-class typed surface, Anthropic Python SDK's `client.beta.files.upload()` parallel surface, and Anthropic's own claude-code CLI (paste-image and screenshot shortcuts auto-upload to Files API for repeated-use efficiency). The cluster doctrine accumulates: every catalog-discovery / capability-resource axis that exists in 2025+ provider APIs must have a typed slot in the Rust data model, must traverse the wire via either `serde_json::to_value` (JSON endpoints) or `reqwest::multipart::Form::new()` (file-upload endpoints) without ad-hoc string splicing or manual MIME boundary construction, must round-trip cleanly through both native and openai-compat lanes, must have a CLI subcommand surface AND a slash command surface that match each other, and must be opted into via the appropriate `anthropic-beta` header on the Anthropic side. The seventh axis — multipart-form-data-transport-with-beta-header-symmetry — is novel in the cluster and motivates a new doctrine entry: any new endpoint family that requires a non-JSON content-type (multipart/form-data, application/octet-stream, application/x-ndjson for streaming JSONL responses) must have its transport-layer prerequisites (reqwest feature flags, streaming-upload helpers, content-type negotiation) shipped before the data-model and trait layers can be exercised. Distinct from #220's `/image` and `/screenshot` (advertised, gated under STUB_COMMANDS, returns clear unsupported error), #221's batch dispatch (per-request synchronous JSON only, no transport-layer extension needed), and #222's model discovery (GET-only JSON catalog, no upload pathway), #223's Files API is the **first cluster member where the transport plumbing itself must be extended** before any of the higher-level surfaces can ship. + +**Status:** Open. No code changed. Filed 2026-04-26 02:30 KST. Branch: feat/jobdori-168c-emission-routing. HEAD: 0121f20. Sibling-shape cluster (silent-fallback / silent-drop / silent-strip / silent-misnomer / silent-shadow / silent-prefix-mismatch / structural-absence / silent-zero-coercion / silent-content-discard / silent-header-discard / silent-tier-absence / silent-finish-mistranslation / silent-capability-absence / silent-false-positive-opt-in / advertised-but-unbuilt / endpoint-family-level-absence / advertised-but-rerouted / endpoint-family-level-absence-with-transport-plumbing-absence): #201/#202/#203/#206/#207/#208/#209/#210/#211/#212/#213/#214/#215/#216/#217/#218/#219/#220/#221/#222/#223 — twenty-two pinpoints. Wire-format-parity cluster grows to thirteen: #211 (max_completion_tokens) + #212 (parallel_tool_calls) + #213 (cached_tokens response-side) + #214 (reasoning_content) + #215 (Retry-After) + #216 (service_tier + system_fingerprint) + #217 (finish_reason taxonomy) + #218 (response_format / output_config / refusal) + #219 (cache_control request-side) + #220 (image content block + media_type) + #221 (Message Batches API) + #222 (Models list endpoint) + #223 (Files API + FileObject + FileList + FilePurpose + Provider trait extension + CLI subcommand + slash command + anthropic-beta files-api-2025-04-14 + multipart-form-data transport plumbing). Capability-parity cluster grows to five: #218 (structured outputs) + #220 (multimodal input) + #221 (batch dispatch) + #222 (model discovery) + #223 (file management) — five members, all four-or-more-layer structural absences. Resource-management cluster (the strict-superset of capability-parity that includes upload/download/lifecycle/expiration semantics): #223 alone, but #223 is the **upstream root cause** of #220's image-attachment via persistent file_id (the canonical Anthropic Vision pattern for >5MB images and repeated-use efficiency) and #221's OpenAI batch-input-JSONL upload pathway (`POST /v1/batches` requires `input_file_id` referencing an uploaded JSONL via `purpose: "batch"`, the request does not accept inline JSONL). Seven-layer-endpoint-family-absence-with-transport-plumbing-absence shape (endpoint-URL + data-model-taxonomy + Provider-trait-method + ProviderClient-enum-dispatch + anthropic-beta-header-opt-in + CLI-subcommand-surface + multipart-form-data-transport-plumbing) is the first single capability absence catalogued where the **transport layer itself must be extended** before any higher-level surface can ship — distinct from #221's seven-layer absence (which operated within the existing JSON envelope), and the largest single transport-level gap catalogued. Distinct from prior single-field (#211/#212/#214) / response-only (#213/#207) / header-only (#215) / three-dimensional (#216) / classifier-leakage (#217) / four-layer (#218) / false-positive-opt-in (#219) / five-layer-feature-absence (#220) / seven-layer-endpoint-family-absence (#221) / eight-layer-endpoint-family-absence-with-misleading-alias (#222) members; the seven-layer-endpoint-family-absence-with-transport-plumbing-absence shape is novel and applies to follow-on candidate "Audio API typed taxonomy is absent" (`/v1/audio/transcriptions` / `/v1/audio/speech` / `/v1/audio/translations`, also requiring multipart/form-data uploads for transcription input). External validation: Anthropic Files API reference (https://platform.claude.com/docs/en/build-with-claude/files — five operations on `/v1/files`, beta opt-in `anthropic-beta: files-api-2025-04-14`, supports image+PDF+document upload, persistent file_id for repeated reference, Anthropic-managed retention until explicit DELETE), Anthropic Vision documentation referencing Files API for >5MB images (https://platform.claude.com/docs/en/build-with-claude/vision — recommends Files API over inline base64 for repeated-image-use efficiency), Anthropic Python SDK `client.beta.files.upload(file, purpose)` and `client.beta.files.list()` and `client.beta.files.retrieve(file_id)` and `client.beta.files.delete(file_id)` and `client.beta.files.content(file_id)` (https://github.com/anthropics/anthropic-sdk-python — first-class typed surface for the beta endpoint, GA-shipped 2025-04-14 alongside the API beta), Anthropic TypeScript SDK parallel `client.beta.files.*` surface (https://github.com/anthropics/anthropic-sdk-typescript), OpenAI Files API reference (https://platform.openai.com/docs/api-reference/files — five operations on `/v1/files`, GA since 2023, `purpose: "assistants" | "batch" | "fine-tune" | "user_data" | "vision"` discriminator, `FileStatus { Uploaded, Processed, Error }` lifecycle, status_details for error reporting), OpenAI Python SDK `client.files.create(file, purpose)` first-class typed surface, OpenAI TypeScript SDK `client.files.create({ file, purpose })`, OpenAI Batch API reference (https://platform.openai.com/docs/api-reference/batch — explicitly requires `input_file_id` from `POST /v1/files` with `purpose: "batch"`, no inline-JSONL pathway), AWS Bedrock model invocation with input/output S3 paths (https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-data-prep.html — Bedrock-anthropic-relay equivalent uses S3 for batch input, parallel concept), Azure OpenAI Files reference (https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#files — Azure-deployment-aware Files API), Vertex AI Files via Cloud Storage (https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/send-multimodal-prompts — Vertex uses GCS bucket URIs for file references, parallel concept), DeepSeek Files API (https://api-docs.deepseek.com — OpenAI-compat shape), Moonshot Files API (https://platform.moonshot.cn/docs/api/files — same shape with kimi-specific quirks), Alibaba DashScope Files API (https://help.aliyun.com — same shape), xAI Files API (https://docs.x.ai — beta), OpenRouter file passthrough (https://openrouter.ai/docs — gateway-aware file routing), simonw/llm `--attachment` flag (https://llm.datasette.io/en/stable/usage.html#attachments — first-class CLI surface for file attachment with auto-upload to Files API for vendors that support it), Vercel AI SDK 6 `experimental_attachments` (https://sdk.vercel.ai/docs/reference/ai-sdk-rsc/use-actions#experimental_attachments — first-class image/file attachment threading with file_id reference), LangChain Files integration (https://python.langchain.com/docs/integrations/document_loaders — File loaders that upload via Files API), LangChain `Anthropic.upload_file()` and `OpenAI.files.create()` direct integration, charmbracelet/crush file-upload via Files API (https://github.com/charmbracelet/crush — typed file management with provider-aware lifecycle), continue.dev file-upload integration (https://github.com/continuedev/continue — config-file-driven file management with auto-upload to Files API), zed-industries/zed file-attachment (https://github.com/zed-industries/zed — bundled-file management with periodic upstream sync), Anthropic Files API quickstart (https://platform.claude.com/docs/en/build-with-claude/files — "For frequently used files, the Files API eliminates the need to re-upload content with each request"), models.dev file-handling capability flags (https://models.dev — community-maintained capability matrix indicating which models support `file_id` references for vision and document inputs), anomalyco/opencode file-upload integration (https://github.com/anomalyco/opencode — uses Files API for image and PDF reference, with explicit `file_id` lifecycle in conversation context), OpenTelemetry GenAI semconv `gen_ai.input.attachments.count` and `gen_ai.input.attachments.bytes` and `gen_ai.input.files.count` (https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/ — multimodal-and-file observability is a documented attribute set), IANA MIME-type registry RFC 4288/4289 (`application/json`, `multipart/form-data`, `application/x-ndjson`, `application/jsonl`, `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `application/pdf` — the canonical content-type registrations for Files API uploads), RFC 7578 multipart/form-data specification (https://datatracker.ietf.org/doc/html/rfc7578 — canonical wire-format spec for the upload pathway), reqwest::multipart documentation (https://docs.rs/reqwest/latest/reqwest/multipart/index.html — the Rust-side transport-layer prerequisite for multipart-form-data uploads, requires `multipart` feature flag on the reqwest dependency). Twenty-eight ecosystem references, two first-class Files API specs (Anthropic beta, OpenAI GA), GA timeline of 12 months on Anthropic's beta side and 24+ months on OpenAI's side (the Files API on OpenAI predates Assistants API and Batch API, both of which depend on it as a prerequisite), seven first-class CLI/SDK implementations (Anthropic Python+TypeScript beta, OpenAI Python+TypeScript, simonw/llm, Vercel AI SDK, LangChain), one transport-layer specification (RFC 7578 multipart/form-data) and one Rust-side prerequisite (`reqwest::multipart` feature flag). claw-code is the **sole client/agent/CLI in the surveyed coding-agent ecosystem with zero `/v1/files` integration AND zero multipart-form-data transport plumbing** — both gaps are unique to claw-code in the surveyed ecosystem, the file-management gap is the **upstream root cause** of two downstream capability gaps already catalogued in this audit (#220 image attachment via persistent file_id, #221 OpenAI batch input-JSONL upload), and the multipart-transport-plumbing-absence shape is novel within the cluster — #223 closes the upstream root cause of two downstream gaps and unblocks `file_id`-based multimodal input (5MB+ images / PDFs / repeated-image-use efficiency), OpenAI batch-input-JSONL upload (the missing piece of #221's seven-layer batch dispatch fix-shape), Anthropic-style document-block content with `source: { type: "file", file_id }` for PDFs and `source: { type: "file", file_id }` for images, and CLI-vs-slash-command-symmetry on file management that the runtime's clawability doctrine treats as canonical baseline expectations. The fix shape is well-understood, all reference implementations exist in peer codebases (Anthropic Python+TypeScript beta SDKs, OpenAI Python+TypeScript GA SDKs, simonw/llm, Vercel AI SDK, LangChain, anomalyco/opencode, charmbracelet/crush, continue.dev, zed), and the use-case framing aligns directly with claw-code's own roadmap "machine-readable in state and failure modes" goal — a Files API surface is **the** machine-readable representation of the provider's persistent-resource lifecycle, and shipping without one means every downstream multimodal and batch capability has to invent its own ad-hoc upload pathway. #223 closes the upstream root cause of two downstream capability gaps and is the first cluster member where the transport plumbing itself must be extended before any higher-level surface can ship — a structural prerequisite that every future endpoint family requiring multipart/form-data uploads (Audio API transcription input, fine-tuning training data upload, vision-input via persistent file_id) will inherit. + +🪨