opencode-anthropic-fix 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +17 -2
  2. package/index.mjs +232 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -46,6 +46,8 @@ The [original plugin](https://github.com/anomalyco/opencode-anthropic-auth) prov
46
46
  - **Adaptive thinking for Opus 4.6** — automatically uses `adaptive-thinking-2026-01-28` and maps `budgetTokens` to effort levels (`low`/`medium`/`high`)
47
47
  - **1M context limit override** — patches `model.limit.context` so OpenCode compacts at the right threshold while `models.dev` catches up
48
48
  - **Runtime config + custom betas** — `/anthropic set`, `/anthropic config`, and `/anthropic betas` slash commands for live feature toggling without restarting OpenCode
49
+ - **Files API integration** — upload, list, download, and manage files via `/anthropic files` with auto-included `files-api-2025-04-14` beta
50
+ - **Code execution support** — auto-includes `code-execution-2025-08-25` beta for sandbox code execution (non-Haiku models)
49
51
 
50
52
  ## Installation
51
53
 
@@ -279,8 +281,6 @@ Add or remove beta flags that get included in every `anthropic-beta` header. Per
279
281
  | `context-management-2025-06-27` | Server-side auto-summarization when context fills |
280
282
  | `structured-outputs-2025-12-15` | Strict JSON schema output enforcement |
281
283
  | `tool-examples-2025-10-29` | Input/output examples in tool definitions |
282
- | `code-execution-2025-08-25` | Python sandbox execution by the model |
283
- | `files-api-2025-04-14` | Upload once, reuse file ID across requests |
284
284
  | `compact-2026-01-12` | Conversation compaction endpoint |
285
285
  | `mcp-servers-2025-12-04` | MCP servers in API request payload |
286
286
  | `web-search-2025-03-05` | Web search (Vertex/Foundry only) |
@@ -293,6 +293,21 @@ Example workflow:
293
293
  /anthropic betas # verify
294
294
  ```
295
295
 
296
+ ### Files API management
297
+
298
+ Upload, list, download, and delete files via the Anthropic Files API. The `files-api-2025-04-14` beta is auto-included in every request.
299
+
300
+ ```text
301
+ /anthropic files # list uploaded files
302
+ /anthropic files upload ./report.pdf # upload a file
303
+ /anthropic files get file_abc123 # get file metadata
304
+ /anthropic files download file_abc123 # download to current directory
305
+ /anthropic files download file_abc123 ./out.pdf # download to specific path
306
+ /anthropic files delete file_abc123 # delete a file
307
+ ```
308
+
309
+ Supported formats: PDF, DOCX, TXT, CSV, Excel, Markdown, images (max 350 MB per file). Uploaded files can be referenced by `file_id` in Messages API requests.
310
+
296
311
  ### OAuth flows from slash command
297
312
 
298
313
  Login and reauth are two-step flows in slash mode:
package/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { createInterface } from "node:readline/promises";
2
2
  import { stdin, stdout } from "node:process";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { join } from "node:path";
5
+ import { join, resolve, basename } from "node:path";
6
6
  import { AccountManager } from "./lib/accounts.mjs";
7
7
  import { authorize, exchange, refreshToken } from "./lib/oauth.mjs";
8
8
  import { loadConfig, loadConfigFresh, saveConfig, CLIENT_ID, getConfigDir } from "./lib/config.mjs";
@@ -119,6 +119,8 @@ const BEDROCK_UNSUPPORTED_BETAS = new Set([
119
119
  "context-1m-2025-08-07",
120
120
  "tool-search-tool-2025-10-19",
121
121
  "tool-examples-2025-10-29",
122
+ "code-execution-2025-08-25",
123
+ "files-api-2025-04-14",
122
124
  ]);
123
125
  const STAINLESS_HELPER_KEYS = [
124
126
  "x_stainless_helper",
@@ -680,8 +682,12 @@ function buildAnthropicBetaHeader(incomingBeta, signatureEnabled, model, provide
680
682
 
681
683
  if (!haiku) {
682
684
  betas.push(CLAUDE_CODE_BETA_FLAG);
685
+ betas.push("code-execution-2025-08-25");
683
686
  }
684
687
 
688
+ // Files API is model-independent; enables /v1/files endpoint and file_id references.
689
+ betas.push("files-api-2025-04-14");
690
+
685
691
  if (isOpus46Model(model)) {
686
692
  // Opus 4.6 uses adaptive thinking — model decides how much to think based on task complexity.
687
693
  // The effort parameter guides it; budgetTokens/interleaved-thinking are for older models.
@@ -1978,7 +1984,9 @@ export async function AnthropicAuthPlugin({ client }) {
1978
1984
  "Preset betas (auto-computed per model/provider):",
1979
1985
  " oauth-2025-04-20, claude-code-20250219,",
1980
1986
  " interleaved-thinking-2025-05-14 OR adaptive-thinking-2026-01-28,",
1981
- " fine-grained-tool-streaming-2025-05-14",
1987
+ " fine-grained-tool-streaming-2025-05-14,",
1988
+ " code-execution-2025-08-25 (non-Haiku),",
1989
+ " files-api-2025-04-14",
1982
1990
  "",
1983
1991
  `Custom betas: ${fresh.custom_betas.length ? fresh.custom_betas.join(", ") : "(none)"}`,
1984
1992
  "",
@@ -1988,10 +1996,8 @@ export async function AnthropicAuthPlugin({ client }) {
1988
1996
  " /anthropic betas add prompt-caching-scope-2026-01-05",
1989
1997
  " /anthropic betas add tool-examples-2025-10-29",
1990
1998
  " /anthropic betas add web-search-2025-03-05",
1991
- " /anthropic betas add code-execution-2025-08-25",
1992
1999
  " /anthropic betas add compact-2026-01-12",
1993
2000
  " /anthropic betas add mcp-servers-2025-12-04",
1994
- " /anthropic betas add files-api-2025-04-14",
1995
2001
  "",
1996
2002
  "Remove: /anthropic betas remove <beta>",
1997
2003
  ];
@@ -2039,6 +2045,228 @@ export async function AnthropicAuthPlugin({ client }) {
2039
2045
  return;
2040
2046
  }
2041
2047
 
2048
+ // /anthropic files [list|upload|get|delete|download] — Files API management
2049
+ if (primary === "files") {
2050
+ const action = (args[1] || "").toLowerCase();
2051
+
2052
+ // Get current account and ensure valid token
2053
+ const account = accountManager?.getCurrentAccount();
2054
+ if (!account) {
2055
+ await sendCommandMessage(
2056
+ input.sessionID,
2057
+ "▣ Anthropic Files (error)\n\nNo accounts configured. Use /anthropic login first.",
2058
+ );
2059
+ return;
2060
+ }
2061
+
2062
+ let token = account.access;
2063
+ if (!token || !account.expires || account.expires < Date.now()) {
2064
+ try {
2065
+ token = await refreshAccountTokenSingleFlight(account);
2066
+ } catch (err) {
2067
+ await sendCommandMessage(
2068
+ input.sessionID,
2069
+ `▣ Anthropic Files (error)\n\nToken refresh failed: ${err.message}`,
2070
+ );
2071
+ return;
2072
+ }
2073
+ }
2074
+
2075
+ const apiBase = "https://api.anthropic.com";
2076
+ const authHeaders = {
2077
+ authorization: `Bearer ${token}`,
2078
+ "anthropic-beta": "files-api-2025-04-14",
2079
+ };
2080
+
2081
+ try {
2082
+ // /anthropic files list — list all uploaded files
2083
+ if (!action || action === "list") {
2084
+ const res = await fetch(`${apiBase}/v1/files`, { headers: authHeaders });
2085
+ if (!res.ok) {
2086
+ const errBody = await res.text();
2087
+ await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\nHTTP ${res.status}: ${errBody}`);
2088
+ return;
2089
+ }
2090
+ const data = await res.json();
2091
+ const files = data.data || [];
2092
+ if (files.length === 0) {
2093
+ await sendCommandMessage(input.sessionID, "▣ Anthropic Files\n\nNo files uploaded.");
2094
+ return;
2095
+ }
2096
+ const lines = ["▣ Anthropic Files", "", `${files.length} file(s):`, ""];
2097
+ for (const f of files) {
2098
+ const sizeKB = (f.size / 1024).toFixed(1);
2099
+ lines.push(` ${f.id} ${f.filename} (${sizeKB} KB, ${f.purpose})`);
2100
+ }
2101
+ await sendCommandMessage(input.sessionID, lines.join("\n"));
2102
+ return;
2103
+ }
2104
+
2105
+ // /anthropic files upload <path> — upload a file
2106
+ if (action === "upload") {
2107
+ const filePath = args.slice(2).join(" ").trim();
2108
+ if (!filePath) {
2109
+ await sendCommandMessage(input.sessionID, "▣ Anthropic Files\n\nUsage: /anthropic files upload <path>");
2110
+ return;
2111
+ }
2112
+ const resolvedPath = resolve(filePath);
2113
+ if (!existsSync(resolvedPath)) {
2114
+ await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\nFile not found: ${resolvedPath}`);
2115
+ return;
2116
+ }
2117
+ const content = readFileSync(resolvedPath);
2118
+ const filename = basename(resolvedPath);
2119
+ const blob = new Blob([content]);
2120
+ const form = new FormData();
2121
+ form.append("file", blob, filename);
2122
+ form.append("purpose", "assistants");
2123
+
2124
+ const res = await fetch(`${apiBase}/v1/files`, {
2125
+ method: "POST",
2126
+ headers: {
2127
+ authorization: `Bearer ${token}`,
2128
+ "anthropic-beta": "files-api-2025-04-14",
2129
+ },
2130
+ body: form,
2131
+ });
2132
+ if (!res.ok) {
2133
+ const errBody = await res.text();
2134
+ await sendCommandMessage(
2135
+ input.sessionID,
2136
+ `▣ Anthropic Files (error)\n\nUpload failed (HTTP ${res.status}): ${errBody}`,
2137
+ );
2138
+ return;
2139
+ }
2140
+ const file = await res.json();
2141
+ const sizeKB = ((file.size || 0) / 1024).toFixed(1);
2142
+ await sendCommandMessage(
2143
+ input.sessionID,
2144
+ `▣ Anthropic Files\n\nUploaded: ${file.id}\n Filename: ${file.filename}\n Size: ${sizeKB} KB`,
2145
+ );
2146
+ return;
2147
+ }
2148
+
2149
+ // /anthropic files get <file_id> — get file metadata
2150
+ if (action === "get" || action === "info") {
2151
+ const fileId = args[2]?.trim();
2152
+ if (!fileId) {
2153
+ await sendCommandMessage(input.sessionID, "▣ Anthropic Files\n\nUsage: /anthropic files get <file_id>");
2154
+ return;
2155
+ }
2156
+ const res = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}`, { headers: authHeaders });
2157
+ if (!res.ok) {
2158
+ const errBody = await res.text();
2159
+ await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\nHTTP ${res.status}: ${errBody}`);
2160
+ return;
2161
+ }
2162
+ const file = await res.json();
2163
+ const lines = [
2164
+ "▣ Anthropic Files",
2165
+ "",
2166
+ ` ID: ${file.id}`,
2167
+ ` Filename: ${file.filename}`,
2168
+ ` Purpose: ${file.purpose}`,
2169
+ ` Size: ${((file.size || 0) / 1024).toFixed(1)} KB`,
2170
+ ` Type: ${file.mime_type || "unknown"}`,
2171
+ ` Created: ${file.created_at || "unknown"}`,
2172
+ ];
2173
+ await sendCommandMessage(input.sessionID, lines.join("\n"));
2174
+ return;
2175
+ }
2176
+
2177
+ // /anthropic files delete <file_id> — delete a file
2178
+ if (action === "delete" || action === "rm") {
2179
+ const fileId = args[2]?.trim();
2180
+ if (!fileId) {
2181
+ await sendCommandMessage(input.sessionID, "▣ Anthropic Files\n\nUsage: /anthropic files delete <file_id>");
2182
+ return;
2183
+ }
2184
+ const res = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}`, {
2185
+ method: "DELETE",
2186
+ headers: authHeaders,
2187
+ });
2188
+ if (!res.ok) {
2189
+ const errBody = await res.text();
2190
+ await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\nHTTP ${res.status}: ${errBody}`);
2191
+ return;
2192
+ }
2193
+ await sendCommandMessage(input.sessionID, `▣ Anthropic Files\n\nDeleted: ${fileId}`);
2194
+ return;
2195
+ }
2196
+
2197
+ // /anthropic files download <file_id> [output_path] — download file content
2198
+ if (action === "download" || action === "dl") {
2199
+ const fileId = args[2]?.trim();
2200
+ if (!fileId) {
2201
+ await sendCommandMessage(
2202
+ input.sessionID,
2203
+ "▣ Anthropic Files\n\nUsage: /anthropic files download <file_id> [output_path]",
2204
+ );
2205
+ return;
2206
+ }
2207
+ const outputPath = args.slice(3).join(" ").trim();
2208
+
2209
+ // Get file metadata first for the filename
2210
+ const metaRes = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}`, {
2211
+ headers: authHeaders,
2212
+ });
2213
+ if (!metaRes.ok) {
2214
+ const errBody = await metaRes.text();
2215
+ await sendCommandMessage(
2216
+ input.sessionID,
2217
+ `▣ Anthropic Files (error)\n\nHTTP ${metaRes.status}: ${errBody}`,
2218
+ );
2219
+ return;
2220
+ }
2221
+ const meta = await metaRes.json();
2222
+ const savePath = outputPath ? resolve(outputPath) : resolve(meta.filename);
2223
+
2224
+ // Download file content
2225
+ const res = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}/content`, {
2226
+ headers: authHeaders,
2227
+ });
2228
+ if (!res.ok) {
2229
+ const errBody = await res.text();
2230
+ await sendCommandMessage(
2231
+ input.sessionID,
2232
+ `▣ Anthropic Files (error)\n\nDownload failed (HTTP ${res.status}): ${errBody}`,
2233
+ );
2234
+ return;
2235
+ }
2236
+ const buffer = Buffer.from(await res.arrayBuffer());
2237
+ writeFileSync(savePath, buffer);
2238
+ const sizeKB = (buffer.length / 1024).toFixed(1);
2239
+ await sendCommandMessage(
2240
+ input.sessionID,
2241
+ `▣ Anthropic Files\n\nDownloaded: ${meta.filename}\n Saved to: ${savePath}\n Size: ${sizeKB} KB`,
2242
+ );
2243
+ return;
2244
+ }
2245
+
2246
+ // Unknown action — show help
2247
+ const helpLines = [
2248
+ "▣ Anthropic Files",
2249
+ "",
2250
+ "Usage: /anthropic files <action>",
2251
+ "",
2252
+ "Actions:",
2253
+ " list List uploaded files",
2254
+ " upload <path> Upload a file (max 350MB)",
2255
+ " get <file_id> Get file metadata",
2256
+ " delete <file_id> Delete a file",
2257
+ " download <file_id> [path] Download file content",
2258
+ "",
2259
+ "Supported formats: PDF, DOCX, TXT, CSV, Excel, Markdown, images",
2260
+ "Files can be referenced by file_id in Messages API requests.",
2261
+ ];
2262
+ await sendCommandMessage(input.sessionID, helpLines.join("\n"));
2263
+ return;
2264
+ } catch (err) {
2265
+ await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\n${err.message}`);
2266
+ return;
2267
+ }
2268
+ }
2269
+
2042
2270
  // Interactive CLI command is not compatible with slash flow.
2043
2271
  if (primary === "manage" || primary === "mg") {
2044
2272
  await sendCommandMessage(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-anthropic-fix",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "main": "./index.mjs",
5
5
  "files": [
6
6
  "index.mjs",