kimaki 0.4.89 → 0.4.91
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/dist/agent-model.e2e.test.js +82 -3
- package/dist/anthropic-auth-plugin.js +246 -195
- package/dist/anthropic-auth-plugin.test.js +125 -0
- package/dist/anthropic-auth-state.js +231 -0
- package/dist/bin.js +6 -3
- package/dist/cli-parsing.test.js +23 -0
- package/dist/cli-send-thread.e2e.test.js +4 -3
- package/dist/cli.js +76 -46
- package/dist/commands/btw.js +7 -2
- package/dist/commands/merge-worktree.js +6 -3
- package/dist/commands/new-worktree.js +18 -7
- package/dist/commands/worktrees.js +71 -7
- package/dist/context-awareness-plugin.js +52 -50
- package/dist/context-awareness-plugin.test.js +68 -1
- package/dist/discord-bot.js +131 -58
- package/dist/discord-utils.test.js +19 -0
- package/dist/errors.js +0 -5
- package/dist/event-stream-real-capture.e2e.test.js +2 -1
- package/dist/exec-async.js +26 -0
- package/dist/external-opencode-sync.js +33 -72
- package/dist/forum-sync/config.js +2 -2
- package/dist/forum-sync/markdown.js +4 -8
- package/dist/gateway-proxy.e2e.test.js +2 -1
- package/dist/hrana-server.js +11 -3
- package/dist/image-optimizer-plugin.js +153 -0
- package/dist/ipc-tools-plugin.js +11 -4
- package/dist/kimaki-digital-twin.e2e.test.js +2 -1
- package/dist/kimaki-opencode-plugin.js +1 -0
- package/dist/logger.js +0 -1
- package/dist/markdown.js +2 -2
- package/dist/markdown.test.js +5 -6
- package/dist/message-finish-field.e2e.test.js +2 -1
- package/dist/message-preprocessing.js +100 -16
- package/dist/onboarding-tutorial.js +1 -1
- package/dist/opencode-command-detection.js +70 -0
- package/dist/opencode-command-detection.test.js +210 -0
- package/dist/opencode-interrupt-plugin.js +64 -8
- package/dist/opencode-interrupt-plugin.test.js +23 -39
- package/dist/opencode.js +34 -32
- package/dist/pkce.js +23 -0
- package/dist/plugin-logger.js +59 -0
- package/dist/queue-advanced-abort.e2e.test.js +14 -15
- package/dist/queue-advanced-e2e-setup.js +2 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +26 -24
- package/dist/queue-advanced-question.e2e.test.js +149 -82
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -5
- package/dist/queue-question-select-drain.e2e.test.js +30 -27
- package/dist/runtime-lifecycle.e2e.test.js +2 -1
- package/dist/sentry.js +7 -114
- package/dist/session-handler/event-stream-state.js +1 -1
- package/dist/session-handler/thread-runtime-state.js +9 -0
- package/dist/session-handler/thread-session-runtime.js +210 -53
- package/dist/session-title-rename.test.js +80 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +1 -2
- package/dist/system-message.js +105 -49
- package/dist/system-message.test.js +598 -15
- package/dist/task-runner.js +7 -4
- package/dist/task-schedule.js +2 -0
- package/dist/test-utils.js +20 -0
- package/dist/thread-message-queue.e2e.test.js +34 -41
- package/dist/unnest-code-blocks.js +11 -1
- package/dist/unnest-code-blocks.test.js +32 -0
- package/dist/voice-handler.js +15 -5
- package/dist/voice-message.e2e.test.js +2 -1
- package/dist/voice.js +53 -23
- package/dist/voice.test.js +2 -0
- package/dist/worktree-lifecycle.e2e.test.js +1 -1
- package/dist/worktrees.js +111 -120
- package/dist/worktrees.test.js +2 -2
- package/package.json +18 -22
- package/skills/lintcn/SKILL.md +6 -1
- package/skills/new-skill/SKILL.md +211 -0
- package/skills/npm-package/SKILL.md +3 -2
- package/skills/spiceflow/SKILL.md +1 -1
- package/skills/usecomputer/SKILL.md +174 -249
- package/src/agent-model.e2e.test.ts +97 -2
- package/src/anthropic-auth-plugin.test.ts +159 -0
- package/src/anthropic-auth-plugin.ts +474 -403
- package/src/anthropic-auth-state.ts +282 -0
- package/src/bin.ts +6 -3
- package/src/cli-parsing.test.ts +32 -0
- package/src/cli-send-thread.e2e.test.ts +4 -2
- package/src/cli.ts +101 -63
- package/src/commands/btw.ts +8 -2
- package/src/commands/merge-worktree.ts +8 -3
- package/src/commands/new-worktree.ts +22 -10
- package/src/commands/worktrees.ts +86 -5
- package/src/context-awareness-plugin.test.ts +77 -1
- package/src/context-awareness-plugin.ts +85 -64
- package/src/discord-bot.ts +142 -60
- package/src/discord-utils.test.ts +21 -0
- package/src/errors.ts +0 -6
- package/src/event-stream-real-capture.e2e.test.ts +2 -1
- package/src/exec-async.ts +35 -0
- package/src/external-opencode-sync.ts +39 -85
- package/src/forum-sync/config.ts +2 -2
- package/src/forum-sync/markdown.ts +5 -9
- package/src/gateway-proxy.e2e.test.ts +2 -0
- package/src/hrana-server.ts +15 -3
- package/src/image-optimizer-plugin.ts +194 -0
- package/src/ipc-tools-plugin.ts +16 -8
- package/src/kimaki-digital-twin.e2e.test.ts +2 -1
- package/src/kimaki-opencode-plugin.ts +1 -0
- package/src/logger.ts +0 -1
- package/src/markdown.test.ts +4 -5
- package/src/markdown.ts +2 -2
- package/src/message-finish-field.e2e.test.ts +2 -1
- package/src/message-preprocessing.ts +117 -16
- package/src/onboarding-tutorial.ts +1 -1
- package/src/opencode-command-detection.test.ts +268 -0
- package/src/opencode-command-detection.ts +79 -0
- package/src/opencode-interrupt-plugin.test.ts +93 -50
- package/src/opencode-interrupt-plugin.ts +86 -9
- package/src/opencode.ts +34 -34
- package/src/plugin-logger.ts +68 -0
- package/src/queue-advanced-abort.e2e.test.ts +14 -15
- package/src/queue-advanced-e2e-setup.ts +2 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +34 -24
- package/src/queue-advanced-question.e2e.test.ts +243 -179
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +13 -5
- package/src/queue-question-select-drain.e2e.test.ts +31 -28
- package/src/runtime-lifecycle.e2e.test.ts +2 -0
- package/src/sentry.ts +7 -120
- package/src/session-handler/event-stream-state.ts +1 -1
- package/src/session-handler/thread-runtime-state.ts +17 -0
- package/src/session-handler/thread-session-runtime.ts +254 -52
- package/src/session-title-rename.test.ts +112 -0
- package/src/startup-time.e2e.test.ts +2 -1
- package/src/store.ts +3 -8
- package/src/system-message.test.ts +612 -0
- package/src/system-message.ts +136 -63
- package/src/task-runner.ts +7 -4
- package/src/task-schedule.ts +3 -0
- package/src/test-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +38 -43
- package/src/undici.d.ts +12 -0
- package/src/unnest-code-blocks.test.ts +34 -0
- package/src/unnest-code-blocks.ts +18 -1
- package/src/voice-handler.ts +18 -4
- package/src/voice-message.e2e.test.ts +2 -0
- package/src/voice.test.ts +2 -0
- package/src/voice.ts +68 -23
- package/src/worktree-lifecycle.e2e.test.ts +1 -1
- package/src/worktrees.test.ts +2 -2
- package/src/worktrees.ts +152 -156
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* cd ~/.config/opencode
|
|
8
8
|
* bun init -y
|
|
9
|
-
* bun add
|
|
9
|
+
* bun add proper-lockfile
|
|
10
10
|
*
|
|
11
|
-
* Handles
|
|
11
|
+
* Handles three concerns:
|
|
12
12
|
* 1. OAuth login + token refresh (PKCE flow against claude.ai)
|
|
13
13
|
* 2. Request/response rewriting (tool names, system prompt, beta headers)
|
|
14
14
|
* so the Anthropic API treats requests as Claude Code CLI requests.
|
|
15
|
+
* 3. Multi-account OAuth rotation after Anthropic rate-limit/auth failures.
|
|
15
16
|
*
|
|
16
17
|
* Login mode is chosen from environment:
|
|
17
18
|
* - `KIMAKI` set: remote-first pasted callback URL/raw code flow
|
|
@@ -21,53 +22,67 @@
|
|
|
21
22
|
* - https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/anthropic.ts
|
|
22
23
|
* - https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts
|
|
23
24
|
*/
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
import { loadAccountStore, rememberAnthropicOAuth, rotateAnthropicAccount, saveAccountStore, setAnthropicAuth, shouldRotateAuth, upsertAccount, withAuthStateLock, } from './anthropic-auth-state.js';
|
|
26
|
+
// PKCE (Proof Key for Code Exchange) using Web Crypto API.
|
|
27
|
+
// Reference: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/pkce.ts
|
|
28
|
+
function base64urlEncode(bytes) {
|
|
29
|
+
let binary = '';
|
|
30
|
+
for (const byte of bytes) {
|
|
31
|
+
binary += String.fromCharCode(byte);
|
|
32
|
+
}
|
|
33
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
34
|
+
}
|
|
35
|
+
async function generatePKCE() {
|
|
36
|
+
const verifierBytes = new Uint8Array(32);
|
|
37
|
+
crypto.getRandomValues(verifierBytes);
|
|
38
|
+
const verifier = base64urlEncode(verifierBytes);
|
|
39
|
+
const data = new TextEncoder().encode(verifier);
|
|
40
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
41
|
+
const challenge = base64urlEncode(new Uint8Array(hashBuffer));
|
|
42
|
+
return { verifier, challenge };
|
|
43
|
+
}
|
|
44
|
+
import { spawn } from 'node:child_process';
|
|
45
|
+
import { createServer } from 'node:http';
|
|
31
46
|
// --- Constants ---
|
|
32
47
|
const CLIENT_ID = (() => {
|
|
33
|
-
const encoded =
|
|
34
|
-
return typeof atob ===
|
|
48
|
+
const encoded = 'OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl';
|
|
49
|
+
return typeof atob === 'function'
|
|
35
50
|
? atob(encoded)
|
|
36
|
-
: Buffer.from(encoded,
|
|
51
|
+
: Buffer.from(encoded, 'base64').toString('utf8');
|
|
37
52
|
})();
|
|
38
|
-
const TOKEN_URL =
|
|
39
|
-
const CREATE_API_KEY_URL =
|
|
53
|
+
const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
54
|
+
const CREATE_API_KEY_URL = 'https://api.anthropic.com/api/oauth/claude_cli/create_api_key';
|
|
40
55
|
const CALLBACK_PORT = 53692;
|
|
41
|
-
const CALLBACK_PATH =
|
|
56
|
+
const CALLBACK_PATH = '/callback';
|
|
42
57
|
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
43
|
-
const SCOPES =
|
|
58
|
+
const SCOPES = 'org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload';
|
|
44
59
|
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
45
|
-
const CLAUDE_CODE_VERSION =
|
|
60
|
+
const CLAUDE_CODE_VERSION = '2.1.75';
|
|
46
61
|
const CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
47
|
-
const OPENCODE_IDENTITY =
|
|
48
|
-
const CLAUDE_CODE_BETA =
|
|
49
|
-
const OAUTH_BETA =
|
|
50
|
-
const FINE_GRAINED_TOOL_STREAMING_BETA =
|
|
51
|
-
const INTERLEAVED_THINKING_BETA =
|
|
62
|
+
const OPENCODE_IDENTITY = 'You are OpenCode, the best coding agent on the planet.';
|
|
63
|
+
const CLAUDE_CODE_BETA = 'claude-code-20250219';
|
|
64
|
+
const OAUTH_BETA = 'oauth-2025-04-20';
|
|
65
|
+
const FINE_GRAINED_TOOL_STREAMING_BETA = 'fine-grained-tool-streaming-2025-05-14';
|
|
66
|
+
const INTERLEAVED_THINKING_BETA = 'interleaved-thinking-2025-05-14';
|
|
52
67
|
const ANTHROPIC_HOSTS = new Set([
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
'api.anthropic.com',
|
|
69
|
+
'claude.ai',
|
|
70
|
+
'console.anthropic.com',
|
|
71
|
+
'platform.claude.com',
|
|
57
72
|
]);
|
|
58
73
|
const OPENCODE_TO_CLAUDE_CODE_TOOL_NAME = {
|
|
59
|
-
bash:
|
|
60
|
-
edit:
|
|
61
|
-
glob:
|
|
62
|
-
grep:
|
|
63
|
-
question:
|
|
64
|
-
read:
|
|
65
|
-
skill:
|
|
66
|
-
task:
|
|
67
|
-
todowrite:
|
|
68
|
-
webfetch:
|
|
69
|
-
websearch:
|
|
70
|
-
write:
|
|
74
|
+
bash: 'Bash',
|
|
75
|
+
edit: 'Edit',
|
|
76
|
+
glob: 'Glob',
|
|
77
|
+
grep: 'Grep',
|
|
78
|
+
question: 'AskUserQuestion',
|
|
79
|
+
read: 'Read',
|
|
80
|
+
skill: 'Skill',
|
|
81
|
+
task: 'Task',
|
|
82
|
+
todowrite: 'TodoWrite',
|
|
83
|
+
webfetch: 'WebFetch',
|
|
84
|
+
websearch: 'WebSearch',
|
|
85
|
+
write: 'Write',
|
|
71
86
|
};
|
|
72
87
|
// --- HTTP helpers ---
|
|
73
88
|
// Claude OAuth token exchange can 429 when this runs inside the opencode auth
|
|
@@ -82,7 +97,9 @@ async function requestText(urlString, options) {
|
|
|
82
97
|
method: options.method,
|
|
83
98
|
url: urlString,
|
|
84
99
|
});
|
|
85
|
-
const child = spawn(
|
|
100
|
+
const child = spawn('node', [
|
|
101
|
+
'-e',
|
|
102
|
+
`
|
|
86
103
|
const input = JSON.parse(process.argv[1]);
|
|
87
104
|
(async () => {
|
|
88
105
|
const response = await fetch(input.url, {
|
|
@@ -100,33 +117,35 @@ const input = JSON.parse(process.argv[1]);
|
|
|
100
117
|
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
101
118
|
process.exit(1);
|
|
102
119
|
});
|
|
103
|
-
`.trim(),
|
|
104
|
-
|
|
120
|
+
`.trim(),
|
|
121
|
+
payload,
|
|
122
|
+
], {
|
|
123
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
105
124
|
});
|
|
106
|
-
let stdout =
|
|
107
|
-
let stderr =
|
|
125
|
+
let stdout = '';
|
|
126
|
+
let stderr = '';
|
|
108
127
|
const timeout = setTimeout(() => {
|
|
109
128
|
child.kill();
|
|
110
129
|
reject(new Error(`Request timed out. url=${urlString}`));
|
|
111
130
|
}, 30_000);
|
|
112
|
-
child.stdout.on(
|
|
131
|
+
child.stdout.on('data', (chunk) => {
|
|
113
132
|
stdout += String(chunk);
|
|
114
133
|
});
|
|
115
|
-
child.stderr.on(
|
|
134
|
+
child.stderr.on('data', (chunk) => {
|
|
116
135
|
stderr += String(chunk);
|
|
117
136
|
});
|
|
118
|
-
child.on(
|
|
137
|
+
child.on('error', (error) => {
|
|
119
138
|
clearTimeout(timeout);
|
|
120
139
|
reject(error);
|
|
121
140
|
});
|
|
122
|
-
child.on(
|
|
141
|
+
child.on('close', (code) => {
|
|
123
142
|
clearTimeout(timeout);
|
|
124
143
|
if (code !== 0) {
|
|
125
144
|
let details = stderr.trim();
|
|
126
145
|
try {
|
|
127
146
|
const parsed = JSON.parse(details);
|
|
128
|
-
if (typeof parsed.status ===
|
|
129
|
-
reject(new Error(`HTTP ${parsed.status} from ${urlString}: ${parsed.body ??
|
|
147
|
+
if (typeof parsed.status === 'number') {
|
|
148
|
+
reject(new Error(`HTTP ${parsed.status} from ${urlString}: ${parsed.body ?? ''}`));
|
|
130
149
|
return;
|
|
131
150
|
}
|
|
132
151
|
}
|
|
@@ -143,42 +162,17 @@ const input = JSON.parse(process.argv[1]);
|
|
|
143
162
|
async function postJson(url, body) {
|
|
144
163
|
const requestBody = JSON.stringify(body);
|
|
145
164
|
const responseText = await requestText(url, {
|
|
146
|
-
method:
|
|
165
|
+
method: 'POST',
|
|
147
166
|
headers: {
|
|
148
|
-
Accept:
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
Accept: 'application/json',
|
|
168
|
+
'Content-Length': String(Buffer.byteLength(requestBody)),
|
|
169
|
+
'Content-Type': 'application/json',
|
|
151
170
|
},
|
|
152
171
|
body: requestBody,
|
|
153
172
|
});
|
|
154
173
|
return JSON.parse(responseText);
|
|
155
174
|
}
|
|
156
|
-
|
|
157
|
-
let pendingRefresh;
|
|
158
|
-
function authFilePath() {
|
|
159
|
-
if (process.env.XDG_DATA_HOME) {
|
|
160
|
-
return path.join(process.env.XDG_DATA_HOME, "opencode", "auth.json");
|
|
161
|
-
}
|
|
162
|
-
return path.join(homedir(), ".local", "share", "opencode", "auth.json");
|
|
163
|
-
}
|
|
164
|
-
async function withAuthRefreshLock(fn) {
|
|
165
|
-
const file = authFilePath();
|
|
166
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
167
|
-
await fs.appendFile(file, "");
|
|
168
|
-
const release = await lockfile.lock(file, {
|
|
169
|
-
realpath: false,
|
|
170
|
-
stale: 30_000,
|
|
171
|
-
update: 15_000,
|
|
172
|
-
retries: { factor: 1.3, forever: true, maxTimeout: 1_000, minTimeout: 100 },
|
|
173
|
-
onCompromised: () => { },
|
|
174
|
-
});
|
|
175
|
-
try {
|
|
176
|
-
return await fn();
|
|
177
|
-
}
|
|
178
|
-
finally {
|
|
179
|
-
await release().catch(() => { });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
175
|
+
const pendingRefresh = new Map();
|
|
182
176
|
// --- OAuth token exchange & refresh ---
|
|
183
177
|
function parseTokenResponse(json) {
|
|
184
178
|
const data = json;
|
|
@@ -192,7 +186,7 @@ function tokenExpiry(expiresIn) {
|
|
|
192
186
|
}
|
|
193
187
|
async function exchangeAuthorizationCode(code, state, verifier, redirectUri) {
|
|
194
188
|
const json = await postJson(TOKEN_URL, {
|
|
195
|
-
grant_type:
|
|
189
|
+
grant_type: 'authorization_code',
|
|
196
190
|
client_id: CLIENT_ID,
|
|
197
191
|
code,
|
|
198
192
|
state,
|
|
@@ -201,7 +195,7 @@ async function exchangeAuthorizationCode(code, state, verifier, redirectUri) {
|
|
|
201
195
|
});
|
|
202
196
|
const data = parseTokenResponse(json);
|
|
203
197
|
return {
|
|
204
|
-
type:
|
|
198
|
+
type: 'success',
|
|
205
199
|
refresh: data.refresh_token,
|
|
206
200
|
access: data.access_token,
|
|
207
201
|
expires: tokenExpiry(data.expires_in),
|
|
@@ -209,13 +203,13 @@ async function exchangeAuthorizationCode(code, state, verifier, redirectUri) {
|
|
|
209
203
|
}
|
|
210
204
|
async function refreshAnthropicToken(refreshToken) {
|
|
211
205
|
const json = await postJson(TOKEN_URL, {
|
|
212
|
-
grant_type:
|
|
206
|
+
grant_type: 'refresh_token',
|
|
213
207
|
client_id: CLIENT_ID,
|
|
214
208
|
refresh_token: refreshToken,
|
|
215
209
|
});
|
|
216
210
|
const data = parseTokenResponse(json);
|
|
217
211
|
return {
|
|
218
|
-
type:
|
|
212
|
+
type: 'oauth',
|
|
219
213
|
refresh: data.refresh_token,
|
|
220
214
|
access: data.access_token,
|
|
221
215
|
expires: tokenExpiry(data.expires_in),
|
|
@@ -223,15 +217,15 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
223
217
|
}
|
|
224
218
|
async function createApiKey(accessToken) {
|
|
225
219
|
const responseText = await requestText(CREATE_API_KEY_URL, {
|
|
226
|
-
method:
|
|
220
|
+
method: 'POST',
|
|
227
221
|
headers: {
|
|
228
|
-
Accept:
|
|
222
|
+
Accept: 'application/json',
|
|
229
223
|
authorization: `Bearer ${accessToken}`,
|
|
230
|
-
|
|
224
|
+
'Content-Type': 'application/json',
|
|
231
225
|
},
|
|
232
226
|
});
|
|
233
227
|
const json = JSON.parse(responseText);
|
|
234
|
-
return { type:
|
|
228
|
+
return { type: 'success', key: json.raw_key };
|
|
235
229
|
}
|
|
236
230
|
async function startCallbackServer(expectedState) {
|
|
237
231
|
return new Promise((resolve, reject) => {
|
|
@@ -247,37 +241,45 @@ async function startCallbackServer(expectedState) {
|
|
|
247
241
|
});
|
|
248
242
|
const server = createServer((req, res) => {
|
|
249
243
|
try {
|
|
250
|
-
const url = new URL(req.url ||
|
|
244
|
+
const url = new URL(req.url || '', 'http://localhost');
|
|
251
245
|
if (url.pathname !== CALLBACK_PATH) {
|
|
252
|
-
res.writeHead(404).end(
|
|
246
|
+
res.writeHead(404).end('Not found');
|
|
253
247
|
return;
|
|
254
248
|
}
|
|
255
|
-
const code = url.searchParams.get(
|
|
256
|
-
const state = url.searchParams.get(
|
|
257
|
-
const error = url.searchParams.get(
|
|
249
|
+
const code = url.searchParams.get('code');
|
|
250
|
+
const state = url.searchParams.get('state');
|
|
251
|
+
const error = url.searchParams.get('error');
|
|
258
252
|
if (error || !code || !state || state !== expectedState) {
|
|
259
|
-
res.writeHead(400).end(
|
|
253
|
+
res.writeHead(400).end('Authentication failed: ' + (error || 'missing code/state'));
|
|
260
254
|
return;
|
|
261
255
|
}
|
|
262
|
-
res
|
|
256
|
+
res
|
|
257
|
+
.writeHead(200, { 'Content-Type': 'text/plain' })
|
|
258
|
+
.end('Authentication successful. You can close this window.');
|
|
263
259
|
settle?.({ code, state });
|
|
264
260
|
}
|
|
265
261
|
catch {
|
|
266
|
-
res.writeHead(500).end(
|
|
262
|
+
res.writeHead(500).end('Internal error');
|
|
267
263
|
}
|
|
268
264
|
});
|
|
269
|
-
server.once(
|
|
270
|
-
server.listen(CALLBACK_PORT,
|
|
265
|
+
server.once('error', reject);
|
|
266
|
+
server.listen(CALLBACK_PORT, '127.0.0.1', () => {
|
|
271
267
|
resolve({
|
|
272
268
|
server,
|
|
273
|
-
cancelWait: () => {
|
|
269
|
+
cancelWait: () => {
|
|
270
|
+
settle?.(null);
|
|
271
|
+
},
|
|
274
272
|
waitForCode: () => waitPromise,
|
|
275
273
|
});
|
|
276
274
|
});
|
|
277
275
|
});
|
|
278
276
|
}
|
|
279
277
|
function closeServer(server) {
|
|
280
|
-
return new Promise((resolve) => {
|
|
278
|
+
return new Promise((resolve) => {
|
|
279
|
+
server.close(() => {
|
|
280
|
+
resolve();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
281
283
|
}
|
|
282
284
|
// --- Authorization flow ---
|
|
283
285
|
// Unified flow: beginAuthorizationFlow starts PKCE + callback server,
|
|
@@ -286,13 +288,13 @@ async function beginAuthorizationFlow() {
|
|
|
286
288
|
const pkce = await generatePKCE();
|
|
287
289
|
const callbackServer = await startCallbackServer(pkce.verifier);
|
|
288
290
|
const authParams = new URLSearchParams({
|
|
289
|
-
code:
|
|
291
|
+
code: 'true',
|
|
290
292
|
client_id: CLIENT_ID,
|
|
291
|
-
response_type:
|
|
293
|
+
response_type: 'code',
|
|
292
294
|
redirect_uri: REDIRECT_URI,
|
|
293
295
|
scope: SCOPES,
|
|
294
296
|
code_challenge: pkce.challenge,
|
|
295
|
-
code_challenge_method:
|
|
297
|
+
code_challenge_method: 'S256',
|
|
296
298
|
state: pkce.verifier,
|
|
297
299
|
});
|
|
298
300
|
return {
|
|
@@ -306,7 +308,11 @@ async function waitForCallback(callbackServer, manualInput) {
|
|
|
306
308
|
// Try localhost callback first (instant check)
|
|
307
309
|
const quick = await Promise.race([
|
|
308
310
|
callbackServer.waitForCode(),
|
|
309
|
-
new Promise((r) => {
|
|
311
|
+
new Promise((r) => {
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
r(null);
|
|
314
|
+
}, 50);
|
|
315
|
+
}),
|
|
310
316
|
]);
|
|
311
317
|
if (quick?.code)
|
|
312
318
|
return quick;
|
|
@@ -318,10 +324,14 @@ async function waitForCallback(callbackServer, manualInput) {
|
|
|
318
324
|
// Wait for localhost callback with timeout
|
|
319
325
|
const result = await Promise.race([
|
|
320
326
|
callbackServer.waitForCode(),
|
|
321
|
-
new Promise((r) => {
|
|
327
|
+
new Promise((r) => {
|
|
328
|
+
setTimeout(() => {
|
|
329
|
+
r(null);
|
|
330
|
+
}, OAUTH_TIMEOUT_MS);
|
|
331
|
+
}),
|
|
322
332
|
]);
|
|
323
333
|
if (!result?.code) {
|
|
324
|
-
throw new Error(
|
|
334
|
+
throw new Error('Timed out waiting for OAuth callback');
|
|
325
335
|
}
|
|
326
336
|
return result;
|
|
327
337
|
}
|
|
@@ -333,25 +343,25 @@ async function waitForCallback(callbackServer, manualInput) {
|
|
|
333
343
|
function parseManualInput(input) {
|
|
334
344
|
try {
|
|
335
345
|
const url = new URL(input);
|
|
336
|
-
const code = url.searchParams.get(
|
|
337
|
-
const state = url.searchParams.get(
|
|
346
|
+
const code = url.searchParams.get('code');
|
|
347
|
+
const state = url.searchParams.get('state');
|
|
338
348
|
if (code)
|
|
339
|
-
return { code, state: state ||
|
|
349
|
+
return { code, state: state || '' };
|
|
340
350
|
}
|
|
341
351
|
catch {
|
|
342
352
|
// not a URL
|
|
343
353
|
}
|
|
344
|
-
if (input.includes(
|
|
345
|
-
const [code =
|
|
354
|
+
if (input.includes('#')) {
|
|
355
|
+
const [code = '', state = ''] = input.split('#', 2);
|
|
346
356
|
return { code, state };
|
|
347
357
|
}
|
|
348
|
-
if (input.includes(
|
|
358
|
+
if (input.includes('code=')) {
|
|
349
359
|
const params = new URLSearchParams(input);
|
|
350
|
-
const code = params.get(
|
|
360
|
+
const code = params.get('code');
|
|
351
361
|
if (code)
|
|
352
|
-
return { code, state: params.get(
|
|
362
|
+
return { code, state: params.get('state') || '' };
|
|
353
363
|
}
|
|
354
|
-
return { code: input, state:
|
|
364
|
+
return { code: input, state: '' };
|
|
355
365
|
}
|
|
356
366
|
// Unified authorize handler: returns either OAuth tokens or an API key,
|
|
357
367
|
// for both auto and remote-first modes.
|
|
@@ -363,16 +373,22 @@ function buildAuthorizeHandler(mode) {
|
|
|
363
373
|
const finalize = async (result) => {
|
|
364
374
|
const verifier = auth.verifier;
|
|
365
375
|
const creds = await exchangeAuthorizationCode(result.code, result.state || verifier, verifier, REDIRECT_URI);
|
|
366
|
-
if (mode ===
|
|
376
|
+
if (mode === 'apikey') {
|
|
367
377
|
return createApiKey(creds.access);
|
|
368
378
|
}
|
|
379
|
+
await rememberAnthropicOAuth({
|
|
380
|
+
type: 'oauth',
|
|
381
|
+
refresh: creds.refresh,
|
|
382
|
+
access: creds.access,
|
|
383
|
+
expires: creds.expires,
|
|
384
|
+
});
|
|
369
385
|
return creds;
|
|
370
386
|
};
|
|
371
387
|
if (!isRemote) {
|
|
372
388
|
return {
|
|
373
389
|
url: auth.url,
|
|
374
|
-
instructions:
|
|
375
|
-
method:
|
|
390
|
+
instructions: 'Complete login in your browser on this machine. OpenCode will catch the localhost callback automatically.',
|
|
391
|
+
method: 'auto',
|
|
376
392
|
callback: async () => {
|
|
377
393
|
pendingAuthResult ??= (async () => {
|
|
378
394
|
try {
|
|
@@ -381,7 +397,7 @@ function buildAuthorizeHandler(mode) {
|
|
|
381
397
|
}
|
|
382
398
|
catch (error) {
|
|
383
399
|
console.error(`[anthropic-auth] ${error}`);
|
|
384
|
-
return { type:
|
|
400
|
+
return { type: 'failed' };
|
|
385
401
|
}
|
|
386
402
|
})();
|
|
387
403
|
return pendingAuthResult;
|
|
@@ -390,8 +406,8 @@ function buildAuthorizeHandler(mode) {
|
|
|
390
406
|
}
|
|
391
407
|
return {
|
|
392
408
|
url: auth.url,
|
|
393
|
-
instructions:
|
|
394
|
-
method:
|
|
409
|
+
instructions: 'Complete login in your browser, then paste the final redirect URL from the address bar here. Pasting just the authorization code also works.',
|
|
410
|
+
method: 'code',
|
|
395
411
|
callback: async (input) => {
|
|
396
412
|
pendingAuthResult ??= (async () => {
|
|
397
413
|
try {
|
|
@@ -400,7 +416,7 @@ function buildAuthorizeHandler(mode) {
|
|
|
400
416
|
}
|
|
401
417
|
catch (error) {
|
|
402
418
|
console.error(`[anthropic-auth] ${error}`);
|
|
403
|
-
return { type:
|
|
419
|
+
return { type: 'failed' };
|
|
404
420
|
}
|
|
405
421
|
})();
|
|
406
422
|
return pendingAuthResult;
|
|
@@ -418,23 +434,23 @@ function sanitizeSystemText(text) {
|
|
|
418
434
|
return text.replaceAll(OPENCODE_IDENTITY, CLAUDE_CODE_IDENTITY);
|
|
419
435
|
}
|
|
420
436
|
function prependClaudeCodeIdentity(system) {
|
|
421
|
-
const identityBlock = { type:
|
|
422
|
-
if (typeof system ===
|
|
437
|
+
const identityBlock = { type: 'text', text: CLAUDE_CODE_IDENTITY };
|
|
438
|
+
if (typeof system === 'undefined')
|
|
423
439
|
return [identityBlock];
|
|
424
|
-
if (typeof system ===
|
|
440
|
+
if (typeof system === 'string') {
|
|
425
441
|
const sanitized = sanitizeSystemText(system);
|
|
426
442
|
if (sanitized === CLAUDE_CODE_IDENTITY)
|
|
427
443
|
return [identityBlock];
|
|
428
|
-
return [identityBlock, { type:
|
|
444
|
+
return [identityBlock, { type: 'text', text: sanitized }];
|
|
429
445
|
}
|
|
430
446
|
if (!Array.isArray(system))
|
|
431
447
|
return [identityBlock, system];
|
|
432
448
|
const sanitized = system.map((item) => {
|
|
433
|
-
if (typeof item ===
|
|
434
|
-
return { type:
|
|
435
|
-
if (item && typeof item ===
|
|
449
|
+
if (typeof item === 'string')
|
|
450
|
+
return { type: 'text', text: sanitizeSystemText(item) };
|
|
451
|
+
if (item && typeof item === 'object' && item.type === 'text') {
|
|
436
452
|
const text = item.text;
|
|
437
|
-
if (typeof text ===
|
|
453
|
+
if (typeof text === 'string') {
|
|
438
454
|
return { ...item, text: sanitizeSystemText(text) };
|
|
439
455
|
}
|
|
440
456
|
}
|
|
@@ -442,8 +458,8 @@ function prependClaudeCodeIdentity(system) {
|
|
|
442
458
|
});
|
|
443
459
|
const first = sanitized[0];
|
|
444
460
|
if (first &&
|
|
445
|
-
typeof first ===
|
|
446
|
-
first.type ===
|
|
461
|
+
typeof first === 'object' &&
|
|
462
|
+
first.type === 'text' &&
|
|
447
463
|
first.text === CLAUDE_CODE_IDENTITY) {
|
|
448
464
|
return sanitized;
|
|
449
465
|
}
|
|
@@ -455,14 +471,14 @@ function rewriteRequestPayload(body) {
|
|
|
455
471
|
try {
|
|
456
472
|
const payload = JSON.parse(body);
|
|
457
473
|
const reverseToolNameMap = new Map();
|
|
458
|
-
const modelId = typeof payload.model ===
|
|
474
|
+
const modelId = typeof payload.model === 'string' ? payload.model : undefined;
|
|
459
475
|
// Build reverse map and rename tools
|
|
460
476
|
if (Array.isArray(payload.tools)) {
|
|
461
477
|
payload.tools = payload.tools.map((tool) => {
|
|
462
|
-
if (!tool || typeof tool !==
|
|
478
|
+
if (!tool || typeof tool !== 'object')
|
|
463
479
|
return tool;
|
|
464
480
|
const name = tool.name;
|
|
465
|
-
if (typeof name !==
|
|
481
|
+
if (typeof name !== 'string')
|
|
466
482
|
return tool;
|
|
467
483
|
const mapped = toClaudeCodeToolName(name);
|
|
468
484
|
reverseToolNameMap.set(mapped, name);
|
|
@@ -473,10 +489,10 @@ function rewriteRequestPayload(body) {
|
|
|
473
489
|
payload.system = prependClaudeCodeIdentity(payload.system);
|
|
474
490
|
// Rename tool_choice
|
|
475
491
|
if (payload.tool_choice &&
|
|
476
|
-
typeof payload.tool_choice ===
|
|
477
|
-
payload.tool_choice.type ===
|
|
492
|
+
typeof payload.tool_choice === 'object' &&
|
|
493
|
+
payload.tool_choice.type === 'tool') {
|
|
478
494
|
const name = payload.tool_choice.name;
|
|
479
|
-
if (typeof name ===
|
|
495
|
+
if (typeof name === 'string') {
|
|
480
496
|
payload.tool_choice = {
|
|
481
497
|
...payload.tool_choice,
|
|
482
498
|
name: toClaudeCodeToolName(name),
|
|
@@ -486,7 +502,7 @@ function rewriteRequestPayload(body) {
|
|
|
486
502
|
// Rename tool_use blocks in messages
|
|
487
503
|
if (Array.isArray(payload.messages)) {
|
|
488
504
|
payload.messages = payload.messages.map((message) => {
|
|
489
|
-
if (!message || typeof message !==
|
|
505
|
+
if (!message || typeof message !== 'object')
|
|
490
506
|
return message;
|
|
491
507
|
const content = message.content;
|
|
492
508
|
if (!Array.isArray(content))
|
|
@@ -494,10 +510,10 @@ function rewriteRequestPayload(body) {
|
|
|
494
510
|
return {
|
|
495
511
|
...message,
|
|
496
512
|
content: content.map((block) => {
|
|
497
|
-
if (!block || typeof block !==
|
|
513
|
+
if (!block || typeof block !== 'object')
|
|
498
514
|
return block;
|
|
499
515
|
const b = block;
|
|
500
|
-
if (b.type !==
|
|
516
|
+
if (b.type !== 'tool_use' || typeof b.name !== 'string')
|
|
501
517
|
return block;
|
|
502
518
|
return { ...block, name: toClaudeCodeToolName(b.name) };
|
|
503
519
|
}),
|
|
@@ -516,7 +532,7 @@ function wrapResponseStream(response, reverseToolNameMap) {
|
|
|
516
532
|
const reader = response.body.getReader();
|
|
517
533
|
const decoder = new TextDecoder();
|
|
518
534
|
const encoder = new TextEncoder();
|
|
519
|
-
let carry =
|
|
535
|
+
let carry = '';
|
|
520
536
|
const transform = (text) => {
|
|
521
537
|
return text.replace(/"name"\s*:\s*"([^"]+)"/g, (full, name) => {
|
|
522
538
|
const original = reverseToolNameMap.get(name);
|
|
@@ -554,10 +570,10 @@ function wrapResponseStream(response, reverseToolNameMap) {
|
|
|
554
570
|
// --- Beta headers ---
|
|
555
571
|
function getRequiredBetas(modelId) {
|
|
556
572
|
const betas = [CLAUDE_CODE_BETA, OAUTH_BETA, FINE_GRAINED_TOOL_STREAMING_BETA];
|
|
557
|
-
const isAdaptive = modelId?.includes(
|
|
558
|
-
modelId?.includes(
|
|
559
|
-
modelId?.includes(
|
|
560
|
-
modelId?.includes(
|
|
573
|
+
const isAdaptive = modelId?.includes('opus-4-6') ||
|
|
574
|
+
modelId?.includes('opus-4.6') ||
|
|
575
|
+
modelId?.includes('sonnet-4-6') ||
|
|
576
|
+
modelId?.includes('sonnet-4.6');
|
|
561
577
|
if (!isAdaptive)
|
|
562
578
|
betas.push(INTERLEAVED_THINKING_BETA);
|
|
563
579
|
return betas;
|
|
@@ -566,13 +582,16 @@ function mergeBetas(existing, required) {
|
|
|
566
582
|
return [
|
|
567
583
|
...new Set([
|
|
568
584
|
...required,
|
|
569
|
-
...(existing ||
|
|
585
|
+
...(existing || '')
|
|
586
|
+
.split(',')
|
|
587
|
+
.map((s) => s.trim())
|
|
588
|
+
.filter(Boolean),
|
|
570
589
|
]),
|
|
571
|
-
].join(
|
|
590
|
+
].join(',');
|
|
572
591
|
}
|
|
573
592
|
// --- Token refresh with dedup ---
|
|
574
593
|
function isOAuthStored(auth) {
|
|
575
|
-
return auth.type ===
|
|
594
|
+
return auth.type === 'oauth';
|
|
576
595
|
}
|
|
577
596
|
async function getFreshOAuth(getAuth, client) {
|
|
578
597
|
const auth = await getAuth();
|
|
@@ -580,38 +599,46 @@ async function getFreshOAuth(getAuth, client) {
|
|
|
580
599
|
return undefined;
|
|
581
600
|
if (auth.access && auth.expires > Date.now())
|
|
582
601
|
return auth;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (!isOAuthStored(latest)) {
|
|
587
|
-
throw new Error("Anthropic OAuth credentials disappeared during refresh");
|
|
588
|
-
}
|
|
589
|
-
if (latest.access && latest.expires > Date.now())
|
|
590
|
-
return latest;
|
|
591
|
-
const refreshed = await refreshAnthropicToken(latest.refresh);
|
|
592
|
-
await client.auth.set({ path: { id: "anthropic" }, body: refreshed });
|
|
593
|
-
return refreshed;
|
|
594
|
-
}).finally(() => {
|
|
595
|
-
pendingRefresh = undefined;
|
|
596
|
-
});
|
|
602
|
+
const pending = pendingRefresh.get(auth.refresh);
|
|
603
|
+
if (pending) {
|
|
604
|
+
return pending;
|
|
597
605
|
}
|
|
598
|
-
|
|
606
|
+
const refreshPromise = withAuthStateLock(async () => {
|
|
607
|
+
const latest = await getAuth();
|
|
608
|
+
if (!isOAuthStored(latest)) {
|
|
609
|
+
throw new Error('Anthropic OAuth credentials disappeared during refresh');
|
|
610
|
+
}
|
|
611
|
+
if (latest.access && latest.expires > Date.now())
|
|
612
|
+
return latest;
|
|
613
|
+
const refreshed = await refreshAnthropicToken(latest.refresh);
|
|
614
|
+
await setAnthropicAuth(refreshed, client);
|
|
615
|
+
const store = await loadAccountStore();
|
|
616
|
+
if (store.accounts.length > 0) {
|
|
617
|
+
upsertAccount(store, refreshed);
|
|
618
|
+
await saveAccountStore(store);
|
|
619
|
+
}
|
|
620
|
+
return refreshed;
|
|
621
|
+
});
|
|
622
|
+
pendingRefresh.set(auth.refresh, refreshPromise);
|
|
623
|
+
return refreshPromise.finally(() => {
|
|
624
|
+
pendingRefresh.delete(auth.refresh);
|
|
625
|
+
});
|
|
599
626
|
}
|
|
600
627
|
// --- Plugin export ---
|
|
601
628
|
const AnthropicAuthPlugin = async ({ client }) => {
|
|
602
629
|
return {
|
|
603
630
|
auth: {
|
|
604
|
-
provider:
|
|
631
|
+
provider: 'anthropic',
|
|
605
632
|
async loader(getAuth, provider) {
|
|
606
633
|
const auth = await getAuth();
|
|
607
|
-
if (auth.type !==
|
|
634
|
+
if (auth.type !== 'oauth')
|
|
608
635
|
return {};
|
|
609
636
|
// Zero out costs for OAuth users (Claude Pro/Max subscription)
|
|
610
637
|
for (const model of Object.values(provider.models)) {
|
|
611
638
|
model.cost = { input: 0, output: 0, cache: { read: 0, write: 0 } };
|
|
612
639
|
}
|
|
613
640
|
return {
|
|
614
|
-
apiKey:
|
|
641
|
+
apiKey: '',
|
|
615
642
|
async fetch(input, init) {
|
|
616
643
|
const url = (() => {
|
|
617
644
|
try {
|
|
@@ -623,55 +650,79 @@ const AnthropicAuthPlugin = async ({ client }) => {
|
|
|
623
650
|
})();
|
|
624
651
|
if (!url || !ANTHROPIC_HOSTS.has(url.hostname))
|
|
625
652
|
return fetch(input, init);
|
|
626
|
-
const
|
|
627
|
-
if (!freshAuth)
|
|
628
|
-
return fetch(input, init);
|
|
629
|
-
const originalBody = typeof init?.body === "string"
|
|
653
|
+
const originalBody = typeof init?.body === 'string'
|
|
630
654
|
? init.body
|
|
631
655
|
: input instanceof Request
|
|
632
|
-
? await input
|
|
656
|
+
? await input
|
|
657
|
+
.clone()
|
|
658
|
+
.text()
|
|
659
|
+
.catch(() => undefined)
|
|
633
660
|
: undefined;
|
|
634
661
|
const rewritten = rewriteRequestPayload(originalBody);
|
|
635
662
|
const headers = new Headers(init?.headers);
|
|
636
663
|
if (input instanceof Request) {
|
|
637
|
-
input.headers.forEach((v, k) => {
|
|
638
|
-
headers.
|
|
664
|
+
input.headers.forEach((v, k) => {
|
|
665
|
+
if (!headers.has(k))
|
|
666
|
+
headers.set(k, v);
|
|
667
|
+
});
|
|
639
668
|
}
|
|
640
669
|
const betas = getRequiredBetas(rewritten.modelId);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
670
|
+
const runRequest = async (auth) => {
|
|
671
|
+
const requestHeaders = new Headers(headers);
|
|
672
|
+
requestHeaders.set('accept', 'application/json');
|
|
673
|
+
requestHeaders.set('anthropic-beta', mergeBetas(requestHeaders.get('anthropic-beta'), betas));
|
|
674
|
+
requestHeaders.set('anthropic-dangerous-direct-browser-access', 'true');
|
|
675
|
+
requestHeaders.set('authorization', `Bearer ${auth.access}`);
|
|
676
|
+
requestHeaders.set('user-agent', process.env.OPENCODE_ANTHROPIC_USER_AGENT || `claude-cli/${CLAUDE_CODE_VERSION}`);
|
|
677
|
+
requestHeaders.set('x-app', 'cli');
|
|
678
|
+
requestHeaders.delete('x-api-key');
|
|
679
|
+
return fetch(input, {
|
|
680
|
+
...(init ?? {}),
|
|
681
|
+
body: rewritten.body,
|
|
682
|
+
headers: requestHeaders,
|
|
683
|
+
});
|
|
684
|
+
};
|
|
685
|
+
const freshAuth = await getFreshOAuth(getAuth, client);
|
|
686
|
+
if (!freshAuth)
|
|
687
|
+
return fetch(input, init);
|
|
688
|
+
let response = await runRequest(freshAuth);
|
|
689
|
+
if (!response.ok) {
|
|
690
|
+
const bodyText = await response
|
|
691
|
+
.clone()
|
|
692
|
+
.text()
|
|
693
|
+
.catch(() => '');
|
|
694
|
+
if (shouldRotateAuth(response.status, bodyText)) {
|
|
695
|
+
const rotated = await rotateAnthropicAccount(freshAuth, client);
|
|
696
|
+
if (rotated) {
|
|
697
|
+
const retryAuth = await getFreshOAuth(getAuth, client);
|
|
698
|
+
if (retryAuth) {
|
|
699
|
+
response = await runRequest(retryAuth);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
653
704
|
return wrapResponseStream(response, rewritten.reverseToolNameMap);
|
|
654
705
|
},
|
|
655
706
|
};
|
|
656
707
|
},
|
|
657
708
|
methods: [
|
|
658
709
|
{
|
|
659
|
-
label:
|
|
660
|
-
type:
|
|
661
|
-
authorize: buildAuthorizeHandler(
|
|
710
|
+
label: 'Claude Pro/Max',
|
|
711
|
+
type: 'oauth',
|
|
712
|
+
authorize: buildAuthorizeHandler('oauth'),
|
|
662
713
|
},
|
|
663
714
|
{
|
|
664
|
-
label:
|
|
665
|
-
type:
|
|
666
|
-
authorize: buildAuthorizeHandler(
|
|
715
|
+
label: 'Create an API Key',
|
|
716
|
+
type: 'oauth',
|
|
717
|
+
authorize: buildAuthorizeHandler('apikey'),
|
|
667
718
|
},
|
|
668
719
|
{
|
|
669
|
-
provider:
|
|
670
|
-
label:
|
|
671
|
-
type:
|
|
720
|
+
provider: 'anthropic',
|
|
721
|
+
label: 'Manually enter API Key',
|
|
722
|
+
type: 'api',
|
|
672
723
|
},
|
|
673
724
|
],
|
|
674
725
|
},
|
|
675
726
|
};
|
|
676
727
|
};
|
|
677
|
-
export { AnthropicAuthPlugin as anthropicAuthPlugin };
|
|
728
|
+
export { AnthropicAuthPlugin as anthropicAuthPlugin, };
|