From d0d8f3e1453eeec12424961e34eba142f56f28e9 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 21 Feb 2026 16:31:33 -0500 Subject: [PATCH] add file cache --- aggregate.py | 37 +- ai_client.py | 2 + config.toml | 10 +- docs/anthropic_api_files.md | 1149 +++++++++++++++++++++++++++++++++++ dpg_layout.ini | 160 +++-- file_cache.py | 193 ++++++ 6 files changed, 1495 insertions(+), 56 deletions(-) create mode 100644 docs/anthropic_api_files.md create mode 100644 file_cache.py diff --git a/aggregate.py b/aggregate.py index bd3cf76..0dcae15 100644 --- a/aggregate.py +++ b/aggregate.py @@ -1,4 +1,4 @@ -# aggregate.py +# aggregate.py import tomllib import re import glob @@ -74,6 +74,36 @@ def build_screenshots_section(base_dir: Path, screenshots: list[str]) -> str: sections.append(f"### `{original}`\n\n![{path.name}]({path.as_posix()})") return "\n\n---\n\n".join(sections) + +def build_file_items(base_dir: Path, files: list[str]) -> list[dict]: + """ + Return a list of dicts describing each file, for use by ai_client when it + wants to upload individual files rather than inline everything as markdown. + + Each dict has: + path : Path (resolved absolute path) + entry : str (original config entry string) + content : str (file text, or error string) + error : bool + """ + items = [] + for entry in files: + paths = resolve_paths(base_dir, entry) + if not paths: + items.append({"path": None, "entry": entry, "content": f"ERROR: no files matched: {entry}", "error": True}) + continue + for path in paths: + try: + content = path.read_text(encoding="utf-8") + error = False + except FileNotFoundError: + content = f"ERROR: file not found: {path}" + error = True + except Exception as e: + content = f"ERROR: {e}" + error = True + items.append({"path": path, "entry": entry, "content": content, "error": error}) + return items def build_markdown(base_dir: Path, files: list[str], screenshot_base_dir: Path, screenshots: list[str], history: list[str]) -> str: parts = [] if history: @@ -98,7 +128,8 @@ def run(config: dict) -> tuple[str, Path]: output_file = output_dir / f"{namespace}_{increment:03d}.md" markdown = build_markdown(base_dir, files, screenshot_base_dir, screenshots, history) output_file.write_text(markdown, encoding="utf-8") - return markdown, output_file + file_items = build_file_items(base_dir, files) + return markdown, output_file, file_items def main(): with open("config.toml", "rb") as f: @@ -109,3 +140,5 @@ def main(): if __name__ == "__main__": main() + + diff --git a/ai_client.py b/ai_client.py index eb41c14..5d2bdd0 100644 --- a/ai_client.py +++ b/ai_client.py @@ -3,6 +3,7 @@ import tomllib import json import datetime from pathlib import Path +import file_cache _provider: str = "gemini" _model: str = "gemini-2.0-flash" @@ -546,3 +547,4 @@ def send(md_content: str, user_message: str, base_dir: str = ".") -> str: elif _provider == "anthropic": return _send_anthropic(md_content, user_message, base_dir) raise ValueError(f"unknown provider: {_provider}") + diff --git a/config.toml b/config.toml index f884e79..26a2f72 100644 --- a/config.toml +++ b/config.toml @@ -14,6 +14,7 @@ paths = [ "MainContext.md", "C:/projects/manual_slop/shell_runner.py", "C:/projects/manual_slop/session_logger.py", + "C:/projects/manual_slop/docs/anthropic_api_files.md", ] [screenshots] @@ -22,12 +23,9 @@ paths = [] [discussion] history = [ - "[User]\nAdd rich visualization for the comms history's json, we shouldn't see the raw text unelss we don't have the json object type detected. Also update MainContext.md with latest state of this codebase.", - "[Claude]\nThe file uses CRLF. I'll use a Python script to do the targeted replacement reliably:", - "[API]\nERROR: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'prompt is too long: 201675 tokens > 200000 maximum'}, 'request_id': 'req_011CYMuPtmZC7akLfcaT7b7r'}", - "[User]\nA _patch_gui.py was left over as an artifact. You were in the middle of making edits before a error reponse occured.\nTo save space on the message I removed the prompt caching documentation from the context.\nWe may have to change how messages are sent or discussion so that its chunked appropriately.", - "[Claude]\n`gui.py` is complete with all the rich rendering code. Now let me delete the artifact and update `MainContext.md`:", - "[User]\nartifact deleted MainContext.md has not been updated though. (Removed anthropic documetantion from context, if you want any docs back you can look up docs in ./docs)", + "[User]\nAdd support for properly handling files that we have in config with anthropics api (your context will have documentation reguarding it). This will allow us to free up the main markdown file were sending as a message.\nWe also want to make sure the messaging of that giant markdown is chunked as anthropic does have an upper limit per messages.", + "[Claude]\nGood. Now the big rewrite of `ai_client.py`. The strategy:\n\n- Add a new `_send_anthropic_files` path that uses `file_cache.get_file_id()` to upload files individually as `document`/`image` content blocks, keeping the context message lean\n- The fallback (and also for oversized content) uses chunked text blocks — split the markdown into ≤180k-char chunks (well under the ~200k token limit with headroom for overhead), each sent as a separate `text` content block with `cache_control` on the last one\n- `send()` now accepts an optional `file_items` parameter from aggregate\n- `reset_session()` also resets the file_cache client", + "[User]\nI will check if this works now....", ] [ai] diff --git a/docs/anthropic_api_files.md b/docs/anthropic_api_files.md new file mode 100644 index 0000000..aec68d4 --- /dev/null +++ b/docs/anthropic_api_files.md @@ -0,0 +1,1149 @@ +# Files API + +--- + +The Files API lets you upload and manage files to use with the Claude API without re-uploading content with each request. This is particularly useful when using the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool) to provide inputs (e.g. datasets and documents) and then download outputs (e.g. charts). You can also use the Files API to prevent having to continually re-upload frequently used documents and images across multiple API calls. You can [explore the API reference directly](/docs/en/api/files-create), in addition to this guide. + + +The Files API is currently in beta. Please reach out through our [feedback form](https://forms.gle/tisHyierGwgN4DUE9) to share your experience with the Files API. + + + +This feature is in beta and is **not** covered by [Zero Data Retention (ZDR)](/docs/en/build-with-claude/zero-data-retention) arrangements. Beta features are excluded from ZDR. + + +## Supported models + +Referencing a `file_id` in a Messages request is supported in all models that support the given file type. For example, [images](/docs/en/build-with-claude/vision) are supported in all Claude 3+ models, [PDFs](/docs/en/build-with-claude/pdf-support) in all Claude 3.5+ models, and [various other file types](/docs/en/agents-and-tools/tool-use/code-execution-tool#supported-file-types) for the code execution tool in Claude Haiku 4.5 plus all Claude 3.7+ models. + +The Files API is currently not supported on Amazon Bedrock or Google Vertex AI. + +## How the Files API works + +The Files API provides a simple create-once, use-many-times approach for working with files: + +- **Upload files** to our secure storage and receive a unique `file_id` +- **Download files** that are created from skills or the code execution tool +- **Reference files** in [Messages](/docs/en/api/messages) requests using the `file_id` instead of re-uploading content +- **Manage your files** with list, retrieve, and delete operations + +## How to use the Files API + + +To use the Files API, you'll need to include the beta feature header: `anthropic-beta: files-api-2025-04-14`. + + +### Uploading a file + +Upload a file to be referenced in future API calls: + + +```bash Shell +curl -X POST https://api.anthropic.com/v1/files \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: files-api-2025-04-14" \ + -F "file=@/path/to/document.pdf" +``` + +```python Python +import anthropic + +client = anthropic.Anthropic() +client.beta.files.upload( + file=("document.pdf", open("/path/to/document.pdf", "rb"), "application/pdf"), +) +``` + +```typescript TypeScript +import Anthropic, { toFile } from "@anthropic-ai/sdk"; +import fs from "fs"; + +const anthropic = new Anthropic(); + +await anthropic.beta.files.upload({ + file: await toFile(fs.createReadStream("/path/to/document.pdf"), undefined, { type: "application/pdf" }) +}, { + betas: ["files-api-2025-04-14"] +}); +``` + +```java Java +import com.anthropic.client.AnthropicClient; +import com.anthropic.client.okhttp.AnthropicOkHttpClient; +import java.nio.file.Path; + +AnthropicClient client = AnthropicOkHttpClient.fromEnv(); + +var file = client.beta().files().upload( + Path.of("/path/to/document.pdf") +); + +System.out.println(file.id()); +``` + +```go Go +package main + +import ( + "context" + "fmt" + "os" + + "github.com/anthropics/anthropic-sdk-go" +) + +func main() { + client := anthropic.NewClient() + + file, _ := os.Open("/path/to/document.pdf") + defer file.Close() + + response, _ := client.Beta.Files.Upload(context.Background(), + anthropic.BetaFileUploadParams{ + File: file, + }) + + fmt.Println(response.ID) +} +``` + +```ruby Ruby +require "anthropic" + +client = Anthropic::Client.new + +file = client.beta.files.upload( + file: File.open("/path/to/document.pdf", "rb") +) + +puts file.id +``` + +```csharp C# +using Anthropic; + +var client = new AnthropicClient(); + +var file = await client.Beta.Files.UploadAsync( + new FileUploadParams + { + File = File.OpenRead("/path/to/document.pdf") + }); + +Console.WriteLine(file.Id); +``` + +```php PHP +beta->files->upload([ + 'file' => fopen('/path/to/document.pdf', 'r') +]); + +echo $file->id; +``` + + +The response from uploading a file will include: + +```json +{ + "id": "file_011CNha8iCJcU1wXNR6q4V8w", + "type": "file", + "filename": "document.pdf", + "mime_type": "application/pdf", + "size_bytes": 1024000, + "created_at": "2025-01-01T00:00:00Z", + "downloadable": false +} +``` + +### Using a file in messages + +Once uploaded, reference the file using its `file_id`: + + +```bash Shell +curl -X POST https://api.anthropic.com/v1/messages \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: files-api-2025-04-14" \ + -H "content-type: application/json" \ + -d '{ + "model": "claude-opus-4-6", + "max_tokens": 1024, + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Please summarize this document for me." + }, + { + "type": "document", + "source": { + "type": "file", + "file_id": "file_011CNha8iCJcU1wXNR6q4V8w" + } + } + ] + } + ] + }' +``` + +```python Python +import anthropic + +client = anthropic.Anthropic() + +response = client.beta.messages.create( + model="claude-opus-4-6", + max_tokens=1024, + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please summarize this document for me."}, + { + "type": "document", + "source": { + "type": "file", + "file_id": "file_011CNha8iCJcU1wXNR6q4V8w", + }, + }, + ], + } + ], + betas=["files-api-2025-04-14"], +) +print(response) +``` + +```typescript TypeScript +import { Anthropic } from "@anthropic-ai/sdk"; + +const anthropic = new Anthropic(); + +const response = await anthropic.beta.messages.create({ + model: "claude-opus-4-6", + max_tokens: 1024, + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: "Please summarize this document for me." + }, + { + type: "document", + source: { + type: "file", + file_id: "file_011CNha8iCJcU1wXNR6q4V8w" + } + } + ] + } + ], + betas: ["files-api-2025-04-14"] +}); + +console.log(response); +``` + +```java Java +import com.anthropic.client.AnthropicClient; +import com.anthropic.client.okhttp.AnthropicOkHttpClient; +import com.anthropic.models.messages.*; + +AnthropicClient client = AnthropicOkHttpClient.fromEnv(); + +MessageCreateParams params = MessageCreateParams.builder() + .model(Model.CLAUDE_OPUS_4_6) + .maxTokens(1024) + .addMessage(MessageParam.builder() + .role(Role.USER) + .content(ContentBlockParam.ofText("Please summarize this document for me.")) + .content(ContentBlockParam.ofDocument(DocumentBlockParam.builder() + .source(DocumentBlockParam.Source.ofFile( + DocumentBlockParam.Source.File.builder() + .fileId("file_011CNha8iCJcU1wXNR6q4V8w") + .build())) + .build())) + .build()) + .build(); + +Message message = client.beta().messages().create(params); +System.out.println(message); +``` + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/anthropics/anthropic-sdk-go" +) + +func main() { + client := anthropic.NewClient() + + response, _ := client.Beta.Messages.New(context.Background(), + anthropic.BetaMessageNewParams{ + Model: anthropic.ModelClaudeOpus4_6, + MaxTokens: 1024, + Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFilesAPI2025_04_14}, + Messages: []anthropic.BetaMessageParam{ + { + Role: "user", + Content: []anthropic.BetaContentBlockParam{ + anthropic.NewBetaTextBlock("Please summarize this document for me."), + { + Type: "document", + Source: &anthropic.BetaDocumentSourceParam{ + Type: "file", + FileID: "file_011CNha8iCJcU1wXNR6q4V8w", + }, + }, + }, + }, + }, + }) + + fmt.Println(response) +} +``` + +```ruby Ruby +require "anthropic" + +client = Anthropic::Client.new + +response = client.beta.messages.create( + model: "claude-opus-4-6", + max_tokens: 1024, + betas: ["files-api-2025-04-14"], + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Please summarize this document for me." }, + { + type: "document", + source: { + type: "file", + file_id: "file_011CNha8iCJcU1wXNR6q4V8w" + } + } + ] + } + ] +) + +puts response +``` + +```csharp C# +using Anthropic; + +var client = new AnthropicClient(); + +var response = await client.Beta.Messages.CreateAsync( + new BetaMessageCreateParams + { + Model = "claude-opus-4-6", + MaxTokens = 1024, + Betas = new[] { "files-api-2025-04-14" }, + Messages = new[] + { + new BetaMessageParam + { + Role = "user", + Content = new object[] + { + new { type = "text", text = "Please summarize this document for me." }, + new + { + type = "document", + source = new + { + type = "file", + file_id = "file_011CNha8iCJcU1wXNR6q4V8w" + } + } + } + } + } + }); + +Console.WriteLine(response); +``` + +```php PHP +beta->messages->create([ + 'model' => 'claude-opus-4-6', + 'max_tokens' => 1024, + 'betas' => ['files-api-2025-04-14'], + 'messages' => [ + [ + 'role' => 'user', + 'content' => [ + ['type' => 'text', 'text' => 'Please summarize this document for me.'], + [ + 'type' => 'document', + 'source' => [ + 'type' => 'file', + 'file_id' => 'file_011CNha8iCJcU1wXNR6q4V8w' + ] + ] + ] + ] + ] +]); + +print_r($response); +``` + + +### File types and content blocks + +The Files API supports different file types that correspond to different content block types: + +| File Type | MIME Type | Content Block Type | Use Case | +| :--- | :--- | :--- | :--- | +| PDF | `application/pdf` | `document` | Text analysis, document processing | +| Plain text | `text/plain` | `document` | Text analysis, processing | +| Images | `image/jpeg`, `image/png`, `image/gif`, `image/webp` | `image` | Image analysis, visual tasks | +| [Datasets, others](/docs/en/agents-and-tools/tool-use/code-execution-tool#supported-file-types) | Varies | `container_upload` | Analyze data, create visualizations | + +### Working with other file formats + +For file types that are not supported as `document` blocks (.csv, .txt, .md, .docx, .xlsx), convert the files to plain text, and include the content directly in your message: + + +```bash Shell +# Example: Reading a text file and sending it as plain text +# Note: For files with special characters, consider base64 encoding +TEXT_CONTENT=$(cat document.txt | jq -Rs .) + +curl https://api.anthropic.com/v1/messages \ + -H "content-type: application/json" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -d @- < + + +For .docx files containing images, convert them to PDF format first, then use [PDF support](/docs/en/build-with-claude/pdf-support) to take advantage of the built-in image parsing. This allows using citations from the PDF document. + + +#### Document blocks + +For PDFs and text files, use the `document` content block: + +```json +{ + "type": "document", + "source": { + "type": "file", + "file_id": "file_011CNha8iCJcU1wXNR6q4V8w" + }, + "title": "Document Title", // Optional + "context": "Context about the document", // Optional + "citations": {"enabled": true} // Optional, enables citations +} +``` + +#### Image blocks + +For images, use the `image` content block: + +```json +{ + "type": "image", + "source": { + "type": "file", + "file_id": "file_011CPMxVD3fHLUhvTqtsQA5w" + } +} +``` + +### Managing files + +#### List files + +Retrieve a list of your uploaded files: + + +```bash Shell +curl https://api.anthropic.com/v1/files \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: files-api-2025-04-14" +``` + +```python Python +import anthropic + +client = anthropic.Anthropic() +files = client.beta.files.list() +``` + +```typescript TypeScript +import { Anthropic } from "@anthropic-ai/sdk"; + +const anthropic = new Anthropic(); +const files = await anthropic.beta.files.list({ + betas: ["files-api-2025-04-14"] +}); +``` + + +#### Get file metadata + +Retrieve information about a specific file: + + +```bash Shell +curl https://api.anthropic.com/v1/files/file_011CNha8iCJcU1wXNR6q4V8w \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: files-api-2025-04-14" +``` + +```python Python +import anthropic + +client = anthropic.Anthropic() +file = client.beta.files.retrieve_metadata("file_011CNha8iCJcU1wXNR6q4V8w") +``` + +```typescript TypeScript +import { Anthropic } from "@anthropic-ai/sdk"; + +const anthropic = new Anthropic(); +const file = await anthropic.beta.files.retrieveMetadata( + "file_011CNha8iCJcU1wXNR6q4V8w", + { betas: ["files-api-2025-04-14"] } +); +``` + + +#### Delete a file + +Remove a file from your workspace: + + +```bash Shell +curl -X DELETE https://api.anthropic.com/v1/files/file_011CNha8iCJcU1wXNR6q4V8w \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: files-api-2025-04-14" +``` + +```python Python +import anthropic + +client = anthropic.Anthropic() +result = client.beta.files.delete("file_011CNha8iCJcU1wXNR6q4V8w") +``` + +```typescript TypeScript +import { Anthropic } from "@anthropic-ai/sdk"; + +const anthropic = new Anthropic(); +const result = await anthropic.beta.files.delete( + "file_011CNha8iCJcU1wXNR6q4V8w", + { betas: ["files-api-2025-04-14"] } +); +``` + + +### Downloading a file + +Download files that have been created by skills or the code execution tool: + + +```bash Shell +curl -X GET "https://api.anthropic.com/v1/files/file_011CNha8iCJcU1wXNR6q4V8w/content" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: files-api-2025-04-14" \ + --output downloaded_file.txt +``` + +```python Python +import anthropic + +client = anthropic.Anthropic() +file_content = client.beta.files.download("file_011CNha8iCJcU1wXNR6q4V8w") + +# Save to file +with open("downloaded_file.txt", "w") as f: + f.write(file_content.decode("utf-8")) +``` + +```typescript TypeScript +import { Anthropic } from "@anthropic-ai/sdk"; +import fs from "fs/promises"; + +const anthropic = new Anthropic(); + +const fileContent = await anthropic.beta.files.download( + "file_011CNha8iCJcU1wXNR6q4V8w", + { betas: ["files-api-2025-04-14"] } +); + +// Save to file +await fs.writeFile("downloaded_file.txt", fileContent); +``` + + + +You can only download files that were created by [skills](/docs/en/build-with-claude/skills-guide) or the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool). Files that you uploaded cannot be downloaded. + + +--- + +## File storage and limits + +### Storage limits + +- **Maximum file size:** 500 MB per file +- **Total storage:** 100 GB per organization + +### File lifecycle + +- Files are scoped to the workspace of the API key. Other API keys can use files created by any other API key associated with the same workspace +- Files persist until you delete them +- Deleted files cannot be recovered +- Files are inaccessible via the API shortly after deletion, but they may persist in active `Messages` API calls and associated tool uses +- Files that users delete will be deleted in accordance with our [data retention policy](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data). + +--- + +## Error handling + +Common errors when using the Files API include: + +- **File not found (404):** The specified `file_id` doesn't exist or you don't have access to it +- **Invalid file type (400):** The file type doesn't match the content block type (e.g., using an image file in a document block) +- **Exceeds context window size (400):** The file is larger than the context window size (e.g. using a 500 MB plaintext file in a `/v1/messages` request) +- **Invalid filename (400):** Filename doesn't meet the length requirements (1-255 characters) or contains forbidden characters (`<`, `>`, `:`, `"`, `|`, `?`, `*`, `\`, `/`, or unicode characters 0-31) +- **File too large (413):** File exceeds the 500 MB limit +- **Storage limit exceeded (403):** Your organization has reached the 100 GB storage limit + +```json +{ + "type": "error", + "error": { + "type": "invalid_request_error", + "message": "File not found: file_011CNha8iCJcU1wXNR6q4V8w" + } +} +``` + +## Usage and billing + +File API operations are **free**: +- Uploading files +- Downloading files +- Listing files +- Getting file metadata +- Deleting files + +File content used in `Messages` requests are priced as input tokens. You can only download files created by [skills](/docs/en/build-with-claude/skills-guide) or the [code execution tool](/docs/en/agents-and-tools/tool-use/code-execution-tool). + +### Rate limits + +During the beta period: +- File-related API calls are limited to approximately 100 requests per minute +- [Contact us](mailto:sales@anthropic.com) if you need higher limits for your use case + +--- + +## Upload + +**post** `/v1/files` + +Upload File + +### Header Parameters + +- `"anthropic-beta": optional array of AnthropicBeta` + + Optional header to specify the beta version(s) you want to use. + + - `UnionMember0 = string` + + - `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 17 more` + + - `"message-batches-2024-09-24"` + + - `"prompt-caching-2024-07-31"` + + - `"computer-use-2024-10-22"` + + - `"computer-use-2025-01-24"` + + - `"pdfs-2024-09-25"` + + - `"token-counting-2024-11-01"` + + - `"token-efficient-tools-2025-02-19"` + + - `"output-128k-2025-02-19"` + + - `"files-api-2025-04-14"` + + - `"mcp-client-2025-04-04"` + + - `"mcp-client-2025-11-20"` + + - `"dev-full-thinking-2025-05-14"` + + - `"interleaved-thinking-2025-05-14"` + + - `"code-execution-2025-05-22"` + + - `"extended-cache-ttl-2025-04-11"` + + - `"context-1m-2025-08-07"` + + - `"context-management-2025-06-27"` + + - `"model-context-window-exceeded-2025-08-26"` + + - `"skills-2025-10-02"` + + - `"fast-mode-2026-02-01"` + +### Returns + +- `FileMetadata = object { id, created_at, filename, 4 more }` + + - `id: string` + + Unique object identifier. + + The format and length of IDs may change over time. + + - `created_at: string` + + RFC 3339 datetime string representing when the file was created. + + - `filename: string` + + Original filename of the uploaded file. + + - `mime_type: string` + + MIME type of the file. + + - `size_bytes: number` + + Size of the file in bytes. + + - `type: "file"` + + Object type. + + For files, this is always `"file"`. + + - `"file"` + + - `downloadable: optional boolean` + + Whether the file can be downloaded. + +### Example + +```http +curl https://api.anthropic.com/v1/files?beta=true \ + -H 'Content-Type: multipart/form-data' \ + -H 'anthropic-version: 2023-06-01' \ + -H 'anthropic-beta: files-api-2025-04-14' \ + -H "X-Api-Key: $ANTHROPIC_API_KEY" \ + -F 'file=@/path/to/file' +``` + +--- + +## List + +**get** `/v1/files` + +List Files + +### Query Parameters + +- `after_id: optional string` + + ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object. + +- `before_id: optional string` + + ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object. + +- `limit: optional number` + + Number of items to return per page. + + Defaults to `20`. Ranges from `1` to `1000`. + +### Header Parameters + +- `"anthropic-beta": optional array of AnthropicBeta` + + Optional header to specify the beta version(s) you want to use. + + - `UnionMember0 = string` + + - `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 17 more` + + - `"message-batches-2024-09-24"` + + - `"prompt-caching-2024-07-31"` + + - `"computer-use-2024-10-22"` + + - `"computer-use-2025-01-24"` + + - `"pdfs-2024-09-25"` + + - `"token-counting-2024-11-01"` + + - `"token-efficient-tools-2025-02-19"` + + - `"output-128k-2025-02-19"` + + - `"files-api-2025-04-14"` + + - `"mcp-client-2025-04-04"` + + - `"mcp-client-2025-11-20"` + + - `"dev-full-thinking-2025-05-14"` + + - `"interleaved-thinking-2025-05-14"` + + - `"code-execution-2025-05-22"` + + - `"extended-cache-ttl-2025-04-11"` + + - `"context-1m-2025-08-07"` + + - `"context-management-2025-06-27"` + + - `"model-context-window-exceeded-2025-08-26"` + + - `"skills-2025-10-02"` + + - `"fast-mode-2026-02-01"` + +### Returns + +- `data: array of FileMetadata` + + List of file metadata objects. + + - `id: string` + + Unique object identifier. + + The format and length of IDs may change over time. + + - `created_at: string` + + RFC 3339 datetime string representing when the file was created. + + - `filename: string` + + Original filename of the uploaded file. + + - `mime_type: string` + + MIME type of the file. + + - `size_bytes: number` + + Size of the file in bytes. + + - `type: "file"` + + Object type. + + For files, this is always `"file"`. + + - `"file"` + + - `downloadable: optional boolean` + + Whether the file can be downloaded. + +- `first_id: optional string` + + ID of the first file in this page of results. + +- `has_more: optional boolean` + + Whether there are more results available. + +- `last_id: optional string` + + ID of the last file in this page of results. + +### Example + +```http +curl https://api.anthropic.com/v1/files?beta=true \ + -H 'anthropic-version: 2023-06-01' \ + -H 'anthropic-beta: files-api-2025-04-14' \ + -H "X-Api-Key: $ANTHROPIC_API_KEY" +``` + +--- + +## Download + +**get** `/v1/files/{file_id}/content` + +Download File + +### Path Parameters + +- `file_id: string` + + ID of the File. + +### Header Parameters + +- `"anthropic-beta": optional array of AnthropicBeta` + + Optional header to specify the beta version(s) you want to use. + + - `UnionMember0 = string` + + - `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 17 more` + + - `"message-batches-2024-09-24"` + + - `"prompt-caching-2024-07-31"` + + - `"computer-use-2024-10-22"` + + - `"computer-use-2025-01-24"` + + - `"pdfs-2024-09-25"` + + - `"token-counting-2024-11-01"` + + - `"token-efficient-tools-2025-02-19"` + + - `"output-128k-2025-02-19"` + + - `"files-api-2025-04-14"` + + - `"mcp-client-2025-04-04"` + + - `"mcp-client-2025-11-20"` + + - `"dev-full-thinking-2025-05-14"` + + - `"interleaved-thinking-2025-05-14"` + + - `"code-execution-2025-05-22"` + + - `"extended-cache-ttl-2025-04-11"` + + - `"context-1m-2025-08-07"` + + - `"context-management-2025-06-27"` + + - `"model-context-window-exceeded-2025-08-26"` + + - `"skills-2025-10-02"` + + - `"fast-mode-2026-02-01"` + +### Example + +```http +curl https://api.anthropic.com/v1/files/$FILE_ID/content?beta=true \ + -H 'anthropic-version: 2023-06-01' \ + -H 'anthropic-beta: files-api-2025-04-14' \ + -H "X-Api-Key: $ANTHROPIC_API_KEY" +``` + +--- + +## Download + +**get** `/v1/files/{file_id}/content` + +Download File + +### Path Parameters + +- `file_id: string` + + ID of the File. + +### Header Parameters + +- `"anthropic-beta": optional array of AnthropicBeta` + + Optional header to specify the beta version(s) you want to use. + + - `UnionMember0 = string` + + - `UnionMember1 = "message-batches-2024-09-24" or "prompt-caching-2024-07-31" or "computer-use-2024-10-22" or 17 more` + + - `"message-batches-2024-09-24"` + + - `"prompt-caching-2024-07-31"` + + - `"computer-use-2024-10-22"` + + - `"computer-use-2025-01-24"` + + - `"pdfs-2024-09-25"` + + - `"token-counting-2024-11-01"` + + - `"token-efficient-tools-2025-02-19"` + + - `"output-128k-2025-02-19"` + + - `"files-api-2025-04-14"` + + - `"mcp-client-2025-04-04"` + + - `"mcp-client-2025-11-20"` + + - `"dev-full-thinking-2025-05-14"` + + - `"interleaved-thinking-2025-05-14"` + + - `"code-execution-2025-05-22"` + + - `"extended-cache-ttl-2025-04-11"` + + - `"context-1m-2025-08-07"` + + - `"context-management-2025-06-27"` + + - `"model-context-window-exceeded-2025-08-26"` + + - `"skills-2025-10-02"` + + - `"fast-mode-2026-02-01"` + +### Example + +```http +curl https://api.anthropic.com/v1/files/$FILE_ID/content?beta=true \ + -H 'anthropic-version: 2023-06-01' \ + -H 'anthropic-beta: files-api-2025-04-14' \ + -H "X-Api-Key: $ANTHROPIC_API_KEY" +``` + diff --git a/dpg_layout.ini b/dpg_layout.ini index 92a0726..959810b 100644 --- a/dpg_layout.ini +++ b/dpg_layout.ini @@ -16,9 +16,9 @@ DockId=0x00000005,0 [Window][###30] Pos=0,654 -Size=549,696 +Size=549,742 Collapsed=0 -DockId=0x00000021,0 +DockId=0x00000025,0 [Window][###66] Pos=0,1491 @@ -42,7 +42,7 @@ DockId=0x0000000D,0 Pos=378,494 Size=829,1643 Collapsed=0 -DockId=0x00000023,0 +DockId=0x0000001F,0 [Window][###103] Pos=1613,1330 @@ -103,13 +103,13 @@ DockId=0x00000014,0 Pos=2531,0 Size=1309,1690 Collapsed=0 -DockId=0x00000023,0 +DockId=0x0000001F,0 [Window][###106] -Pos=366,427 -Size=847,1710 +Pos=551,0 +Size=1544,2137 Collapsed=0 -DockId=0x00000012,0 +DockId=0x00000012,2 [Window][###100] Pos=2622,0 @@ -146,7 +146,7 @@ Collapsed=0 Pos=551,0 Size=1060,2137 Collapsed=0 -DockId=0x00000023,0 +DockId=0x0000001F,0 [Window][###78] Pos=0,1422 @@ -164,7 +164,7 @@ DockId=0x00000015,0 Pos=551,0 Size=1060,2137 Collapsed=0 -DockId=0x00000023,1 +DockId=0x0000001F,1 [Window][###110] Pos=2438,0 @@ -174,9 +174,9 @@ DockId=0x00000016,0 [Window][###112] Pos=551,0 -Size=1060,2137 +Size=1544,2137 Collapsed=0 -DockId=0x00000023,2 +DockId=0x00000012,1 [Window][###145] Pos=1578,868 @@ -224,7 +224,7 @@ DockId=0x00000022,0 Pos=1400,0 Size=1220,1412 Collapsed=0 -DockId=0x00000023,0 +DockId=0x0000001F,0 [Window][###92] Pos=551,0 @@ -284,40 +284,104 @@ Pos=1578,868 Size=700,440 Collapsed=0 -[Docking][Data] -DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size=3840,2137 Split=X Selected=0x40484D8F - DockNode ID=0x00000003 Parent=0x7C6B3D9B SizeRef=549,1161 Split=Y Selected=0xEE087978 - DockNode ID=0x00000005 Parent=0x00000003 SizeRef=235,354 Selected=0xEE087978 - DockNode ID=0x00000006 Parent=0x00000003 SizeRef=235,805 Split=Y Selected=0x5F94F9BD - DockNode ID=0x00000009 Parent=0x00000006 SizeRef=235,453 Split=Y Selected=0x5F94F9BD - DockNode ID=0x0000001D Parent=0x00000009 SizeRef=364,766 Split=Y Selected=0x5F94F9BD - DockNode ID=0x00000021 Parent=0x0000001D SizeRef=549,696 Selected=0x5F94F9BD - DockNode ID=0x00000022 Parent=0x0000001D SizeRef=549,785 Selected=0x0CE534DB - DockNode ID=0x0000001E Parent=0x00000009 SizeRef=364,715 Selected=0xF475F06A - DockNode ID=0x0000000A Parent=0x00000006 SizeRef=235,350 Selected=0x80199DAE - DockNode ID=0x00000004 Parent=0x7C6B3D9B SizeRef=3289,1161 Split=X - DockNode ID=0x00000001 Parent=0x00000004 SizeRef=1060,1161 Split=Y Selected=0x40484D8F - DockNode ID=0x00000007 Parent=0x00000001 SizeRef=595,492 Selected=0xBA13FCDE - DockNode ID=0x00000008 Parent=0x00000001 SizeRef=595,1643 Split=X Selected=0x40484D8F - DockNode ID=0x0000000F Parent=0x00000008 SizeRef=847,2137 Split=Y Selected=0x07E8375F - DockNode ID=0x00000011 Parent=0x0000000F SizeRef=835,425 Selected=0x72F373AE - DockNode ID=0x00000012 Parent=0x0000000F SizeRef=835,1710 Selected=0x8B149E2A - DockNode ID=0x00000010 Parent=0x00000008 SizeRef=2625,2137 Split=Y Selected=0xCE7F911A - DockNode ID=0x00000013 Parent=0x00000010 SizeRef=1967,1690 Split=X Selected=0xCE7F911A - DockNode ID=0x00000017 Parent=0x00000013 SizeRef=1314,1749 Selected=0x4B454E0B - DockNode ID=0x00000018 Parent=0x00000013 SizeRef=1309,1749 Split=Y Selected=0x88A8C2FF - DockNode ID=0x00000019 Parent=0x00000018 SizeRef=2440,1412 Split=X Selected=0x88A8C2FF - DockNode ID=0x00000023 Parent=0x00000019 SizeRef=1220,737 CentralNode=1 Selected=0xFDB3860E - DockNode ID=0x00000024 Parent=0x00000019 SizeRef=1218,737 Selected=0x88A8C2FF - DockNode ID=0x0000001A Parent=0x00000018 SizeRef=2440,723 Selected=0x3A881EEF - DockNode ID=0x00000014 Parent=0x00000010 SizeRef=1967,445 Selected=0xC36FF36B - DockNode ID=0x00000002 Parent=0x00000004 SizeRef=2227,1161 Split=X Selected=0x714F2F7B - DockNode ID=0x0000000B Parent=0x00000002 SizeRef=968,1161 Selected=0xC915D9DA - DockNode ID=0x0000000C Parent=0x00000002 SizeRef=1661,1161 Split=Y Selected=0x714F2F7B - DockNode ID=0x0000000D Parent=0x0000000C SizeRef=396,342 Selected=0x714F2F7B - DockNode ID=0x0000000E Parent=0x0000000C SizeRef=396,817 Split=Y Selected=0xCF08B82F - DockNode ID=0x0000001B Parent=0x0000000E SizeRef=2104,1328 Split=X Selected=0x43F4115A - DockNode ID=0x00000015 Parent=0x0000001B SizeRef=823,1328 Selected=0x052342BF - DockNode ID=0x00000016 Parent=0x0000001B SizeRef=1402,1328 Selected=0x43F4115A - DockNode ID=0x0000001C Parent=0x0000000E SizeRef=2104,807 Selected=0xCF08B82F +[Window][###596] +Pos=1578,868 +Size=700,440 +Collapsed=0 + +[Window][###691] +Pos=1578,868 +Size=700,440 +Collapsed=0 + +[Window][###803] +Pos=1578,868 +Size=700,440 +Collapsed=0 + +[Window][###935] +Pos=1578,868 +Size=700,440 +Collapsed=0 + +[Window][###1073] +Pos=1578,868 +Size=700,440 +Collapsed=0 + +[Window][###1217] +Pos=1578,868 +Size=700,440 +Collapsed=0 + +[Window][###72] +Pos=0,1398 +Size=549,739 +Collapsed=0 +DockId=0x00000026,0 + +[Window][###82] +Pos=2097,0 +Size=1743,1787 +Collapsed=0 +DockId=0x0000001F,1 + +[Window][###89] +Pos=551,0 +Size=1544,2137 +Collapsed=0 +DockId=0x00000012,0 + +[Window][###97] +Pos=2097,1789 +Size=1743,348 +Collapsed=0 +DockId=0x00000020,0 + +[Window][###104] +Pos=2097,0 +Size=1743,1787 +Collapsed=0 +DockId=0x0000001F,0 + +[Docking][Data] +DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size=3840,2137 Split=X Selected=0x40484D8F + DockNode ID=0x00000003 Parent=0x7C6B3D9B SizeRef=549,1161 Split=Y Selected=0xEE087978 + DockNode ID=0x00000005 Parent=0x00000003 SizeRef=235,354 Selected=0xEE087978 + DockNode ID=0x00000006 Parent=0x00000003 SizeRef=235,805 Split=Y Selected=0x5F94F9BD + DockNode ID=0x00000009 Parent=0x00000006 SizeRef=235,453 Split=Y Selected=0x5F94F9BD + DockNode ID=0x0000001D Parent=0x00000009 SizeRef=364,766 Split=Y Selected=0x5F94F9BD + DockNode ID=0x00000021 Parent=0x0000001D SizeRef=549,696 Split=Y Selected=0x5F94F9BD + DockNode ID=0x00000025 Parent=0x00000021 SizeRef=549,742 Selected=0x5F94F9BD + DockNode ID=0x00000026 Parent=0x00000021 SizeRef=549,739 Selected=0xBEC5E8CB + DockNode ID=0x00000022 Parent=0x0000001D SizeRef=549,785 Selected=0x0CE534DB + DockNode ID=0x0000001E Parent=0x00000009 SizeRef=364,715 Selected=0xF475F06A + DockNode ID=0x0000000A Parent=0x00000006 SizeRef=235,350 Selected=0x80199DAE + DockNode ID=0x00000004 Parent=0x7C6B3D9B SizeRef=3289,1161 Split=X + DockNode ID=0x00000001 Parent=0x00000004 SizeRef=1060,1161 Split=Y Selected=0x40484D8F + DockNode ID=0x00000007 Parent=0x00000001 SizeRef=595,492 Selected=0xBA13FCDE + DockNode ID=0x00000008 Parent=0x00000001 SizeRef=595,1643 Split=X Selected=0x40484D8F + DockNode ID=0x0000000F Parent=0x00000008 SizeRef=1544,2137 Split=Y Selected=0x07E8375F + DockNode ID=0x00000011 Parent=0x0000000F SizeRef=835,425 Selected=0x72F373AE + DockNode ID=0x00000012 Parent=0x0000000F SizeRef=835,1710 Selected=0x07E8375F + DockNode ID=0x00000010 Parent=0x00000008 SizeRef=1743,2137 Split=Y Selected=0xCE7F911A + DockNode ID=0x00000013 Parent=0x00000010 SizeRef=1967,1690 Split=X Selected=0xCE7F911A + DockNode ID=0x00000017 Parent=0x00000013 SizeRef=1314,1749 Selected=0x4B454E0B + DockNode ID=0x00000018 Parent=0x00000013 SizeRef=1309,1749 Split=Y Selected=0x88A8C2FF + DockNode ID=0x00000019 Parent=0x00000018 SizeRef=2440,1412 Split=X Selected=0x88A8C2FF + DockNode ID=0x00000023 Parent=0x00000019 SizeRef=1220,737 Split=Y Selected=0x7D28643F + DockNode ID=0x0000001F Parent=0x00000023 SizeRef=2440,1787 CentralNode=1 Selected=0x4F935A1E + DockNode ID=0x00000020 Parent=0x00000023 SizeRef=2440,348 Selected=0x4C2F06CB + DockNode ID=0x00000024 Parent=0x00000019 SizeRef=1218,737 Selected=0x88A8C2FF + DockNode ID=0x0000001A Parent=0x00000018 SizeRef=2440,723 Selected=0x3A881EEF + DockNode ID=0x00000014 Parent=0x00000010 SizeRef=1967,445 Selected=0xC36FF36B + DockNode ID=0x00000002 Parent=0x00000004 SizeRef=2227,1161 Split=X Selected=0x714F2F7B + DockNode ID=0x0000000B Parent=0x00000002 SizeRef=968,1161 Selected=0xC915D9DA + DockNode ID=0x0000000C Parent=0x00000002 SizeRef=1661,1161 Split=Y Selected=0x714F2F7B + DockNode ID=0x0000000D Parent=0x0000000C SizeRef=396,342 Selected=0x714F2F7B + DockNode ID=0x0000000E Parent=0x0000000C SizeRef=396,817 Split=Y Selected=0xCF08B82F + DockNode ID=0x0000001B Parent=0x0000000E SizeRef=2104,1328 Split=X Selected=0x43F4115A + DockNode ID=0x00000015 Parent=0x0000001B SizeRef=823,1328 Selected=0x052342BF + DockNode ID=0x00000016 Parent=0x0000001B SizeRef=1402,1328 Selected=0x43F4115A + DockNode ID=0x0000001C Parent=0x0000000E SizeRef=2104,807 Selected=0xCF08B82F diff --git a/file_cache.py b/file_cache.py new file mode 100644 index 0000000..43492ae --- /dev/null +++ b/file_cache.py @@ -0,0 +1,193 @@ +# file_cache.py +""" +Persistent cache of Anthropic Files API uploads. + +Maps (absolute_path, mtime_ns, size_bytes) -> file_id so we only upload each +file once and reuse the file_id on subsequent sends. If the file has changed +on disk the old file_id is deleted from the API and a new one is uploaded. + +Cache is stored as JSON at ./logs/file_cache.json so it survives restarts. +""" + +import json +import os +import datetime +from pathlib import Path +from typing import Optional + +_CACHE_PATH = Path("./logs/file_cache.json") + +# in-memory dict: abs_path_str -> {"file_id": str, "mtime_ns": int, "size": int} +_cache: dict[str, dict] = {} +_cache_loaded = False + +_anthropic_client = None # set by _ensure_client() + + +# ------------------------------------------------------------------ persistence + +def _load_cache(): + global _cache, _cache_loaded + if _cache_loaded: + return + _cache_loaded = True + if _CACHE_PATH.exists(): + try: + _cache = json.loads(_CACHE_PATH.read_text(encoding="utf-8")) + except Exception: + _cache = {} + else: + _cache = {} + + +def _save_cache(): + _CACHE_PATH.parent.mkdir(parents=True, exist_ok=True) + _CACHE_PATH.write_text(json.dumps(_cache, indent=2, ensure_ascii=False), encoding="utf-8") + + +# ------------------------------------------------------------------ anthropic client + +def _ensure_client(): + global _anthropic_client + if _anthropic_client is not None: + return _anthropic_client + import tomllib + import anthropic + with open("credentials.toml", "rb") as f: + creds = tomllib.load(f) + _anthropic_client = anthropic.Anthropic(api_key=creds["anthropic"]["api_key"]) + return _anthropic_client + + +def reset_client(): + """Called when the main ai_client resets its session.""" + global _anthropic_client + _anthropic_client = None + + +# ------------------------------------------------------------------ MIME helpers + +_MIME_BY_EXT = { + ".pdf": "application/pdf", + ".txt": "text/plain", + ".md": "text/plain", + ".py": "text/plain", + ".toml": "text/plain", + ".json": "text/plain", + ".yaml": "text/plain", + ".yml": "text/plain", + ".ini": "text/plain", + ".ps1": "text/plain", + ".csv": "text/plain", + ".log": "text/plain", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", +} + +# File types that can be sent as 'document' blocks +_DOCUMENT_MIMES = {"application/pdf", "text/plain"} +# File types that can be sent as 'image' blocks +_IMAGE_MIMES = {"image/png", "image/jpeg", "image/gif", "image/webp"} + + +def _mime_for(path: Path) -> str: + return _MIME_BY_EXT.get(path.suffix.lower(), "text/plain") + + +def content_block_type(path: Path) -> str: + """Returns 'document', 'image', or 'unsupported'.""" + mime = _mime_for(path) + if mime in _DOCUMENT_MIMES: + return "document" + if mime in _IMAGE_MIMES: + return "image" + return "unsupported" + + +# ------------------------------------------------------------------ upload / cache logic + +def _stat(path: Path) -> tuple[int, int]: + """Returns (mtime_ns, size_bytes). Raises if file missing.""" + st = path.stat() + return int(st.st_mtime_ns), int(st.st_size) + + +def _delete_remote(file_id: str): + """Best-effort delete of a stale file_id from the API.""" + try: + client = _ensure_client() + client.beta.files.delete(file_id, extra_headers={"anthropic-beta": "files-api-2025-04-14"}) + except Exception: + pass # stale deletes are non-fatal + + +def _upload(path: Path) -> str: + """Upload the file and return its new file_id.""" + client = _ensure_client() + mime = _mime_for(path) + filename = path.name + with open(path, "rb") as fh: + result = client.beta.files.upload( + file=(filename, fh, mime), + extra_headers={"anthropic-beta": "files-api-2025-04-14"}, + ) + return result.id + + +def get_file_id(path: Path) -> Optional[str]: + """ + Return a valid Anthropic file_id for the given path, uploading if needed. + Returns None if the file type is unsupported. + Raises on network / API errors. + """ + _load_cache() + + if content_block_type(path) == "unsupported": + return None + + abs_str = str(path.resolve()) + try: + mtime_ns, size = _stat(path) + except FileNotFoundError: + # File gone - evict from cache + if abs_str in _cache: + _delete_remote(_cache[abs_str]["file_id"]) + del _cache[abs_str] + _save_cache() + return None + + entry = _cache.get(abs_str) + if entry and entry.get("mtime_ns") == mtime_ns and entry.get("size") == size: + # Cache hit - file unchanged + return entry["file_id"] + + # Cache miss or stale - delete old remote if we had one + if entry: + _delete_remote(entry["file_id"]) + + file_id = _upload(path) + _cache[abs_str] = {"file_id": file_id, "mtime_ns": mtime_ns, "size": size} + _save_cache() + return file_id + + +def evict(path: Path): + """Manually evict a path from the cache (e.g. after a tool-call write).""" + _load_cache() + abs_str = str(path.resolve()) + entry = _cache.pop(abs_str, None) + if entry: + _delete_remote(entry["file_id"]) + _save_cache() + + +def list_cached() -> list[dict]: + """Return a snapshot of the current cache for display.""" + _load_cache() + return [ + {"path": k, **v} + for k, v in _cache.items() + ]