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.
- package/README.md +17 -2
- package/index.mjs +232 -4
- 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(
|