@ex-machina/opencode-anthropic-auth 0.0.17 → 0.1.0
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/LICENSE +1 -1
- package/README.md +1 -7
- package/dist/auth.d.ts +13 -0
- package/dist/auth.js +47 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +136 -0
- package/dist/transform.d.ts +36 -0
- package/dist/transform.js +155 -0
- package/package.json +18 -3
- package/.github/workflows/publish.yml +0 -31
- package/.idea/inspectionProfiles/Project_Default.xml +0 -104
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/opencode-anthropic-auth.iml +0 -9
- package/.idea/vcs.xml +0 -7
- package/bun.lock +0 -43
- package/images/renaming.jpg +0 -0
- package/index.mjs +0 -349
- package/script/bump.ts +0 -43
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenCode Anthropic Auth Plugin
|
|
2
2
|
|
|
3
3
|
An [OpenCode](https://github.com/anomalyco/opencode) plugin that provides Anthropic OAuth authentication, enabling Claude Pro/Max users to use their subscription directly with OpenCode.
|
|
4
4
|
|
|
@@ -8,12 +8,6 @@ An [OpenCode](https://github.com/anomalyco/opencode) plugin that provides Anthro
|
|
|
8
8
|

|
|
9
9
|
</details>
|
|
10
10
|
|
|
11
|
-
## Installation
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm install @ex-machina/opencode-anthropic-auth
|
|
15
|
-
```
|
|
16
|
-
|
|
17
11
|
## Usage
|
|
18
12
|
|
|
19
13
|
Add the plugin to your OpenCode configuration:
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function authorize(mode: 'max' | 'console'): Promise<{
|
|
2
|
+
url: string;
|
|
3
|
+
verifier: string;
|
|
4
|
+
}>;
|
|
5
|
+
export type ExchangeResult = {
|
|
6
|
+
type: 'success';
|
|
7
|
+
refresh: string;
|
|
8
|
+
access: string;
|
|
9
|
+
expires: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'failed';
|
|
12
|
+
};
|
|
13
|
+
export declare function exchange(code: string, verifier: string): Promise<ExchangeResult>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { generatePKCE } from '@openauthjs/openauth/pkce';
|
|
2
|
+
import { CLIENT_ID } from './constants';
|
|
3
|
+
export async function authorize(mode) {
|
|
4
|
+
const pkce = await generatePKCE();
|
|
5
|
+
const url = new URL(`https://${mode === 'console' ? 'console.anthropic.com' : 'claude.ai'}/oauth/authorize`, import.meta.url);
|
|
6
|
+
url.searchParams.set('code', 'true');
|
|
7
|
+
url.searchParams.set('client_id', CLIENT_ID);
|
|
8
|
+
url.searchParams.set('response_type', 'code');
|
|
9
|
+
url.searchParams.set('redirect_uri', 'https://console.anthropic.com/oauth/code/callback');
|
|
10
|
+
url.searchParams.set('scope', 'org:create_api_key user:profile user:inference');
|
|
11
|
+
url.searchParams.set('code_challenge', pkce.challenge);
|
|
12
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
13
|
+
url.searchParams.set('state', pkce.verifier);
|
|
14
|
+
return {
|
|
15
|
+
url: url.toString(),
|
|
16
|
+
verifier: pkce.verifier,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function exchange(code, verifier) {
|
|
20
|
+
const splits = code.split('#');
|
|
21
|
+
const result = await fetch('https://console.anthropic.com/v1/oauth/token', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
code: splits[0],
|
|
28
|
+
state: splits[1],
|
|
29
|
+
grant_type: 'authorization_code',
|
|
30
|
+
client_id: CLIENT_ID,
|
|
31
|
+
redirect_uri: 'https://console.anthropic.com/oauth/code/callback',
|
|
32
|
+
code_verifier: verifier,
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
if (!result.ok) {
|
|
36
|
+
return {
|
|
37
|
+
type: 'failed',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const json = (await result.json());
|
|
41
|
+
return {
|
|
42
|
+
type: 'success',
|
|
43
|
+
refresh: json.refresh_token,
|
|
44
|
+
access: json.access_token,
|
|
45
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
46
|
+
};
|
|
47
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { authorize, exchange } from './auth';
|
|
2
|
+
import { CLIENT_ID } from './constants';
|
|
3
|
+
import { createStrippedStream, mergeHeaders, prefixToolNames, rewriteUrl, setOAuthHeaders, } from './transform';
|
|
4
|
+
export const AnthropicAuthPlugin = async ({ client }) => {
|
|
5
|
+
return {
|
|
6
|
+
'experimental.chat.system.transform': (input, output) => {
|
|
7
|
+
const prefix = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
8
|
+
if (input.model?.providerID === 'anthropic') {
|
|
9
|
+
output.system.unshift(prefix);
|
|
10
|
+
if (output.system[1])
|
|
11
|
+
output.system[1] = `${prefix}\n\n${output.system[1]}`;
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
auth: {
|
|
15
|
+
provider: 'anthropic',
|
|
16
|
+
async loader(getAuth, provider) {
|
|
17
|
+
const auth = await getAuth();
|
|
18
|
+
if (auth.type === 'oauth') {
|
|
19
|
+
// zero out cost for max plan
|
|
20
|
+
for (const model of Object.values(provider.models)) {
|
|
21
|
+
model.cost = {
|
|
22
|
+
input: 0,
|
|
23
|
+
output: 0,
|
|
24
|
+
cache: {
|
|
25
|
+
read: 0,
|
|
26
|
+
write: 0,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
apiKey: '',
|
|
32
|
+
async fetch(input, init) {
|
|
33
|
+
const auth = await getAuth();
|
|
34
|
+
if (auth.type !== 'oauth')
|
|
35
|
+
return fetch(input, init);
|
|
36
|
+
if (!auth.access || !auth.expires || auth.expires < Date.now()) {
|
|
37
|
+
const response = await fetch('https://console.anthropic.com/v1/oauth/token', {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
grant_type: 'refresh_token',
|
|
44
|
+
refresh_token: auth.refresh,
|
|
45
|
+
client_id: CLIENT_ID,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`Token refresh failed: ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
const json = (await response.json());
|
|
52
|
+
// biome-ignore lint/suspicious/noExplicitAny: SDK types don't expose auth.set
|
|
53
|
+
await client.auth.set({
|
|
54
|
+
path: {
|
|
55
|
+
id: 'anthropic',
|
|
56
|
+
},
|
|
57
|
+
body: {
|
|
58
|
+
type: 'oauth',
|
|
59
|
+
refresh: json.refresh_token,
|
|
60
|
+
access: json.access_token,
|
|
61
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
auth.access = json.access_token;
|
|
65
|
+
}
|
|
66
|
+
const requestHeaders = mergeHeaders(input, init);
|
|
67
|
+
// biome-ignore lint/style/noNonNullAssertion: access is guaranteed set above
|
|
68
|
+
setOAuthHeaders(requestHeaders, auth.access);
|
|
69
|
+
let body = init?.body;
|
|
70
|
+
if (body && typeof body === 'string') {
|
|
71
|
+
body = prefixToolNames(body);
|
|
72
|
+
}
|
|
73
|
+
const rewritten = rewriteUrl(input);
|
|
74
|
+
const response = await fetch(rewritten.input, {
|
|
75
|
+
...init,
|
|
76
|
+
body,
|
|
77
|
+
headers: requestHeaders,
|
|
78
|
+
});
|
|
79
|
+
return createStrippedStream(response);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {};
|
|
84
|
+
},
|
|
85
|
+
methods: [
|
|
86
|
+
{
|
|
87
|
+
label: 'Claude Pro/Max',
|
|
88
|
+
type: 'oauth',
|
|
89
|
+
authorize: async () => {
|
|
90
|
+
const { url, verifier } = await authorize('max');
|
|
91
|
+
return {
|
|
92
|
+
url: url,
|
|
93
|
+
instructions: 'Paste the authorization code here: ',
|
|
94
|
+
method: 'code',
|
|
95
|
+
callback: async (code) => {
|
|
96
|
+
const credentials = await exchange(code, verifier);
|
|
97
|
+
return credentials;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: 'Create an API Key',
|
|
104
|
+
type: 'oauth',
|
|
105
|
+
authorize: async () => {
|
|
106
|
+
const { url, verifier } = await authorize('console');
|
|
107
|
+
return {
|
|
108
|
+
url: url,
|
|
109
|
+
instructions: 'Paste the authorization code here: ',
|
|
110
|
+
method: 'code',
|
|
111
|
+
callback: async (code) => {
|
|
112
|
+
const credentials = await exchange(code, verifier);
|
|
113
|
+
if (credentials.type === 'failed')
|
|
114
|
+
return credentials;
|
|
115
|
+
const result = await fetch(`https://api.anthropic.com/api/oauth/claude_cli/create_api_key`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
authorization: `Bearer ${credentials.access}`,
|
|
120
|
+
},
|
|
121
|
+
}).then((r) => r.json());
|
|
122
|
+
return { type: 'success', key: result.raw_key };
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
provider: 'anthropic',
|
|
129
|
+
label: 'Manually enter API Key',
|
|
130
|
+
type: 'api',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
// biome-ignore lint/suspicious/noExplicitAny: Plugin type doesn't include undocumented auth/hooks
|
|
135
|
+
};
|
|
136
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type FetchInput = string | URL | Request;
|
|
2
|
+
/**
|
|
3
|
+
* Merge headers from a Request object and/or a RequestInit headers value
|
|
4
|
+
* into a single Headers instance.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mergeHeaders(input: FetchInput, init?: RequestInit): Headers;
|
|
7
|
+
/**
|
|
8
|
+
* Merge incoming beta headers with the required OAuth betas, deduplicating.
|
|
9
|
+
*/
|
|
10
|
+
export declare function mergeBetaHeaders(headers: Headers): string;
|
|
11
|
+
/**
|
|
12
|
+
* Set OAuth-required headers on the request: authorization, beta, user-agent.
|
|
13
|
+
* Removes x-api-key since we're using OAuth.
|
|
14
|
+
*/
|
|
15
|
+
export declare function setOAuthHeaders(headers: Headers, accessToken: string): Headers;
|
|
16
|
+
/**
|
|
17
|
+
* Add TOOL_PREFIX to tool names in the request body.
|
|
18
|
+
* Prefixes both tool definitions and tool_use blocks in messages.
|
|
19
|
+
*/
|
|
20
|
+
export declare function prefixToolNames(body: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Strip TOOL_PREFIX from tool names in streaming response text.
|
|
23
|
+
*/
|
|
24
|
+
export declare function stripToolPrefix(text: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Rewrite the request URL to add ?beta=true for /v1/messages requests.
|
|
27
|
+
* Returns the modified input and URL (if applicable).
|
|
28
|
+
*/
|
|
29
|
+
export declare function rewriteUrl(input: FetchInput): {
|
|
30
|
+
input: FetchInput;
|
|
31
|
+
url: URL | null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Create a streaming response that strips the tool prefix from tool names.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createStrippedStream(response: Response): Response;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { REQUIRED_BETAS, TOOL_PREFIX } from './constants';
|
|
2
|
+
/**
|
|
3
|
+
* Merge headers from a Request object and/or a RequestInit headers value
|
|
4
|
+
* into a single Headers instance.
|
|
5
|
+
*/
|
|
6
|
+
export function mergeHeaders(input, init) {
|
|
7
|
+
const headers = new Headers();
|
|
8
|
+
if (input instanceof Request) {
|
|
9
|
+
input.headers.forEach((value, key) => {
|
|
10
|
+
headers.set(key, value);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const initHeaders = init?.headers;
|
|
14
|
+
if (initHeaders) {
|
|
15
|
+
if (initHeaders instanceof Headers) {
|
|
16
|
+
initHeaders.forEach((value, key) => {
|
|
17
|
+
headers.set(key, value);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else if (Array.isArray(initHeaders)) {
|
|
21
|
+
for (const entry of initHeaders) {
|
|
22
|
+
const [key, value] = entry;
|
|
23
|
+
if (typeof value !== 'undefined') {
|
|
24
|
+
headers.set(key, String(value));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
for (const [key, value] of Object.entries(initHeaders)) {
|
|
30
|
+
if (typeof value !== 'undefined') {
|
|
31
|
+
headers.set(key, String(value));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return headers;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Merge incoming beta headers with the required OAuth betas, deduplicating.
|
|
40
|
+
*/
|
|
41
|
+
export function mergeBetaHeaders(headers) {
|
|
42
|
+
const incomingBeta = headers.get('anthropic-beta') || '';
|
|
43
|
+
const incomingBetasList = incomingBeta
|
|
44
|
+
.split(',')
|
|
45
|
+
.map((b) => b.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
return [...new Set([...REQUIRED_BETAS, ...incomingBetasList])].join(',');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Set OAuth-required headers on the request: authorization, beta, user-agent.
|
|
51
|
+
* Removes x-api-key since we're using OAuth.
|
|
52
|
+
*/
|
|
53
|
+
export function setOAuthHeaders(headers, accessToken) {
|
|
54
|
+
headers.set('authorization', `Bearer ${accessToken}`);
|
|
55
|
+
headers.set('anthropic-beta', mergeBetaHeaders(headers));
|
|
56
|
+
headers.set('user-agent', 'claude-cli/2.1.2 (external, cli)');
|
|
57
|
+
headers.delete('x-api-key');
|
|
58
|
+
return headers;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Add TOOL_PREFIX to tool names in the request body.
|
|
62
|
+
* Prefixes both tool definitions and tool_use blocks in messages.
|
|
63
|
+
*/
|
|
64
|
+
export function prefixToolNames(body) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(body);
|
|
67
|
+
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
68
|
+
parsed.tools = parsed.tools.map((tool) => ({
|
|
69
|
+
...tool,
|
|
70
|
+
name: tool.name ? `${TOOL_PREFIX}${tool.name}` : tool.name,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
74
|
+
parsed.messages = parsed.messages.map((msg) => {
|
|
75
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
76
|
+
msg.content = msg.content.map((block) => {
|
|
77
|
+
if (block.type === 'tool_use' && block.name) {
|
|
78
|
+
return {
|
|
79
|
+
...block,
|
|
80
|
+
name: `${TOOL_PREFIX}${block.name}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return block;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return msg;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify(parsed);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Strip TOOL_PREFIX from tool names in streaming response text.
|
|
97
|
+
*/
|
|
98
|
+
export function stripToolPrefix(text) {
|
|
99
|
+
return text.replace(/"name"\s*:\s*"mcp_([^"]+)"/g, '"name": "$1"');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Rewrite the request URL to add ?beta=true for /v1/messages requests.
|
|
103
|
+
* Returns the modified input and URL (if applicable).
|
|
104
|
+
*/
|
|
105
|
+
export function rewriteUrl(input) {
|
|
106
|
+
let requestUrl = null;
|
|
107
|
+
try {
|
|
108
|
+
if (typeof input === 'string' || input instanceof URL) {
|
|
109
|
+
requestUrl = new URL(input.toString());
|
|
110
|
+
}
|
|
111
|
+
else if (input instanceof Request) {
|
|
112
|
+
requestUrl = new URL(input.url);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
requestUrl = null;
|
|
117
|
+
}
|
|
118
|
+
if (requestUrl &&
|
|
119
|
+
requestUrl.pathname === '/v1/messages' &&
|
|
120
|
+
!requestUrl.searchParams.has('beta')) {
|
|
121
|
+
requestUrl.searchParams.set('beta', 'true');
|
|
122
|
+
const newInput = input instanceof Request
|
|
123
|
+
? new Request(requestUrl.toString(), input)
|
|
124
|
+
: requestUrl;
|
|
125
|
+
return { input: newInput, url: requestUrl };
|
|
126
|
+
}
|
|
127
|
+
return { input, url: requestUrl };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a streaming response that strips the tool prefix from tool names.
|
|
131
|
+
*/
|
|
132
|
+
export function createStrippedStream(response) {
|
|
133
|
+
if (!response.body)
|
|
134
|
+
return response;
|
|
135
|
+
const reader = response.body.getReader();
|
|
136
|
+
const decoder = new TextDecoder();
|
|
137
|
+
const encoder = new TextEncoder();
|
|
138
|
+
const stream = new ReadableStream({
|
|
139
|
+
async pull(controller) {
|
|
140
|
+
const { done, value } = await reader.read();
|
|
141
|
+
if (done) {
|
|
142
|
+
controller.close();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
let text = decoder.decode(value, { stream: true });
|
|
146
|
+
text = stripToolPrefix(text);
|
|
147
|
+
controller.enqueue(encoder.encode(text));
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
return new Response(stream, {
|
|
151
|
+
status: response.status,
|
|
152
|
+
statusText: response.statusText,
|
|
153
|
+
headers: response.headers,
|
|
154
|
+
});
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ex-machina/opencode-anthropic-auth",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"main": "./index.
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
5
9
|
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.build.json",
|
|
11
|
+
"test": "bun test",
|
|
12
|
+
"types": "tsc",
|
|
13
|
+
"format": "biome format --write .",
|
|
14
|
+
"format:check": "biome format .",
|
|
15
|
+
"lint": "biome lint .",
|
|
6
16
|
"bump": "bun script/bump.ts"
|
|
7
17
|
},
|
|
8
18
|
"repository": {
|
|
@@ -10,7 +20,12 @@
|
|
|
10
20
|
"url": "https://github.com/ex-machina-co/opencode-anthropic-auth"
|
|
11
21
|
},
|
|
12
22
|
"devDependencies": {
|
|
13
|
-
"@
|
|
23
|
+
"@biomejs/biome": "^2.4.7",
|
|
24
|
+
"@opencode-ai/plugin": "^0.4.45",
|
|
25
|
+
"@tsconfig/bun": "^1.0.10",
|
|
26
|
+
"@types/bun": "^1.3.10",
|
|
27
|
+
"lefthook": "^2.1.4",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
14
29
|
},
|
|
15
30
|
"dependencies": {
|
|
16
31
|
"@openauthjs/openauth": "^0.4.3"
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
paths:
|
|
8
|
-
- "package.json"
|
|
9
|
-
|
|
10
|
-
permissions:
|
|
11
|
-
id-token: write
|
|
12
|
-
contents: read
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
publish:
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
steps:
|
|
18
|
-
- name: Checkout
|
|
19
|
-
uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- name: Setup Node
|
|
22
|
-
uses: actions/setup-node@v4
|
|
23
|
-
with:
|
|
24
|
-
node-version: "24"
|
|
25
|
-
registry-url: "https://registry.npmjs.org"
|
|
26
|
-
|
|
27
|
-
- name: Install dependencies
|
|
28
|
-
run: npm install
|
|
29
|
-
|
|
30
|
-
- name: Publish
|
|
31
|
-
run: npm publish --access public --provenance
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
<component name="InspectionProjectProfileManager">
|
|
2
|
-
<profile version="1.0">
|
|
3
|
-
<option name="myName" value="Project Default" />
|
|
4
|
-
<inspection_tool class="ES6PreferShortImport" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
|
5
|
-
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
6
|
-
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
|
7
|
-
<option name="myValues">
|
|
8
|
-
<value>
|
|
9
|
-
<list size="1">
|
|
10
|
-
<item index="0" class="java.lang.String" itemvalue="css" />
|
|
11
|
-
</list>
|
|
12
|
-
</value>
|
|
13
|
-
</option>
|
|
14
|
-
<option name="myCustomValuesEnabled" value="true" />
|
|
15
|
-
</inspection_tool>
|
|
16
|
-
<inspection_tool class="HtmlUnknownTag" enabled="true" level="INFORMATION" enabled_by_default="true">
|
|
17
|
-
<option name="myValues">
|
|
18
|
-
<value>
|
|
19
|
-
<list size="6">
|
|
20
|
-
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
|
21
|
-
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
|
22
|
-
<item index="2" class="java.lang.String" itemvalue="comment" />
|
|
23
|
-
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
|
24
|
-
<item index="4" class="java.lang.String" itemvalue="embed" />
|
|
25
|
-
<item index="5" class="java.lang.String" itemvalue="script" />
|
|
26
|
-
</list>
|
|
27
|
-
</value>
|
|
28
|
-
</option>
|
|
29
|
-
<option name="myCustomValuesEnabled" value="true" />
|
|
30
|
-
</inspection_tool>
|
|
31
|
-
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
32
|
-
<option name="ignoredUrls">
|
|
33
|
-
<list>
|
|
34
|
-
<option value="http://localhost" />
|
|
35
|
-
<option value="http://127.0.0.1" />
|
|
36
|
-
<option value="http://0.0.0.0" />
|
|
37
|
-
<option value="http://www.w3.org/" />
|
|
38
|
-
<option value="http://json-schema.org/draft" />
|
|
39
|
-
<option value="http://java.sun.com/" />
|
|
40
|
-
<option value="http://xmlns.jcp.org/" />
|
|
41
|
-
<option value="http://javafx.com/javafx/" />
|
|
42
|
-
<option value="http://javafx.com/fxml" />
|
|
43
|
-
<option value="http://maven.apache.org/xsd/" />
|
|
44
|
-
<option value="http://maven.apache.org/POM/" />
|
|
45
|
-
<option value="http://www.springframework.org/schema/" />
|
|
46
|
-
<option value="http://www.springframework.org/tags" />
|
|
47
|
-
<option value="http://www.springframework.org/security/tags" />
|
|
48
|
-
<option value="http://www.thymeleaf.org" />
|
|
49
|
-
<option value="http://www.jboss.org/j2ee/schema/" />
|
|
50
|
-
<option value="http://www.jboss.com/xml/ns/" />
|
|
51
|
-
<option value="http://www.ibm.com/webservices/xsd" />
|
|
52
|
-
<option value="http://activemq.apache.org/schema/" />
|
|
53
|
-
<option value="http://schema.cloudfoundry.org/spring/" />
|
|
54
|
-
<option value="http://schemas.xmlsoap.org/" />
|
|
55
|
-
<option value="http://cxf.apache.org/schemas/" />
|
|
56
|
-
<option value="http://primefaces.org/ui" />
|
|
57
|
-
<option value="http://tiles.apache.org/" />
|
|
58
|
-
<option value="http://broker" />
|
|
59
|
-
</list>
|
|
60
|
-
</option>
|
|
61
|
-
</inspection_tool>
|
|
62
|
-
<inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="INFORMATION" enabled_by_default="true" editorAttributes="INFORMATION_ATTRIBUTES" />
|
|
63
|
-
<inspection_tool class="JSXUnresolvedComponent" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
|
64
|
-
<inspection_tool class="MarkdownIncorrectTableFormatting" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
|
65
|
-
<inspection_tool class="MarkdownIncorrectlyNumberedListItem" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
|
66
|
-
<inspection_tool class="PyArgumentListInspection" enabled="true" level="TEXT ATTRIBUTES" enabled_by_default="true" editorAttributes="TEXT_STYLE_SUGGESTION" />
|
|
67
|
-
<inspection_tool class="PyClassVarInspection" enabled="true" level="TEXT ATTRIBUTES" enabled_by_default="true" editorAttributes="CONSIDERATION_ATTRIBUTES" />
|
|
68
|
-
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
69
|
-
<option name="ourVersions">
|
|
70
|
-
<value>
|
|
71
|
-
<list size="3">
|
|
72
|
-
<item index="0" class="java.lang.String" itemvalue="3.12" />
|
|
73
|
-
<item index="1" class="java.lang.String" itemvalue="3.13" />
|
|
74
|
-
<item index="2" class="java.lang.String" itemvalue="3.14" />
|
|
75
|
-
</list>
|
|
76
|
-
</value>
|
|
77
|
-
</option>
|
|
78
|
-
</inspection_tool>
|
|
79
|
-
<inspection_tool class="PyMethodParametersInspection" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES" />
|
|
80
|
-
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
81
|
-
<option name="ignoredErrors">
|
|
82
|
-
<list>
|
|
83
|
-
<option value="N806" />
|
|
84
|
-
</list>
|
|
85
|
-
</option>
|
|
86
|
-
</inspection_tool>
|
|
87
|
-
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
88
|
-
<option name="ignoredNames">
|
|
89
|
-
<list>
|
|
90
|
-
<option value="id" />
|
|
91
|
-
<option value="input" />
|
|
92
|
-
</list>
|
|
93
|
-
</option>
|
|
94
|
-
</inspection_tool>
|
|
95
|
-
<inspection_tool class="PyShadowingNamesInspection" enabled="true" level="INFORMATION" enabled_by_default="true" editorAttributes="CONSIDERATION_ATTRIBUTES" />
|
|
96
|
-
<inspection_tool class="PyTypeHintsInspection" enabled="true" level="TEXT ATTRIBUTES" enabled_by_default="true" editorAttributes="CONSIDERATION_ATTRIBUTES" />
|
|
97
|
-
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
|
98
|
-
<inspection_tool class="TypeScriptValidateJSTypes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
99
|
-
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="INFORMATION" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES">
|
|
100
|
-
<option name="m_ignoreImmediatelyReturnedVariables" value="false" />
|
|
101
|
-
<option name="m_ignoreAnnotatedVariables" value="false" />
|
|
102
|
-
</inspection_tool>
|
|
103
|
-
</profile>
|
|
104
|
-
</component>
|
package/.idea/misc.xml
DELETED
package/.idea/modules.xml
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="ProjectModuleManager">
|
|
4
|
-
<modules>
|
|
5
|
-
<module fileurl="file://$PROJECT_DIR$/.idea/opencode-anthropic-auth.iml" filepath="$PROJECT_DIR$/.idea/opencode-anthropic-auth.iml" />
|
|
6
|
-
</modules>
|
|
7
|
-
</component>
|
|
8
|
-
</project>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="JAVA_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
-
<exclude-output />
|
|
5
|
-
<content url="file://$MODULE_DIR$" />
|
|
6
|
-
<orderEntry type="inheritedJdk" />
|
|
7
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
-
</component>
|
|
9
|
-
</module>
|
package/.idea/vcs.xml
DELETED
package/bun.lock
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"workspaces": {
|
|
4
|
-
"": {
|
|
5
|
-
"name": "opencode-anthropic-auth",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"@openauthjs/openauth": "^0.4.3",
|
|
8
|
-
},
|
|
9
|
-
"devDependencies": {
|
|
10
|
-
"@opencode-ai/plugin": "^0.4.45",
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
"packages": {
|
|
15
|
-
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
|
16
|
-
|
|
17
|
-
"@opencode-ai/plugin": ["@opencode-ai/plugin@0.4.45", "", { "dependencies": { "@opencode-ai/sdk": "0.4.19" } }, "sha512-TuD+FNmA6vN+/B82qayCvOyTXRuAtfvU0U95UKSZoNrYgIBhpD/sW/oS65iUv5QwQqzO8BxI4DYWjmLIqCz8uw=="],
|
|
18
|
-
|
|
19
|
-
"@opencode-ai/sdk": ["@opencode-ai/sdk@0.4.19", "", {}, "sha512-7V+wDR1+m+TQZAraAh/bOSObiA/uysG1YIXZVe6gl1sQAXDtkG2FYCzs0gTZ/ORdkUKEnr3vyQIk895Mu0CC/w=="],
|
|
20
|
-
|
|
21
|
-
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
|
22
|
-
|
|
23
|
-
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
|
|
24
|
-
|
|
25
|
-
"@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
|
|
26
|
-
|
|
27
|
-
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
|
28
|
-
|
|
29
|
-
"@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
|
|
30
|
-
|
|
31
|
-
"@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="],
|
|
32
|
-
|
|
33
|
-
"arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
|
|
34
|
-
|
|
35
|
-
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
|
36
|
-
|
|
37
|
-
"hono": ["hono@4.9.1", "", {}, "sha512-qfvdJ42t6CQE0N/iSCa8KsW8SQqYD67YB+TYbwPHlnALvX+s7ynh8otR1NEk5jXtUg73gpV/B82OSufDmwtX3w=="],
|
|
38
|
-
|
|
39
|
-
"jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
|
40
|
-
|
|
41
|
-
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
|
42
|
-
}
|
|
43
|
-
}
|
package/images/renaming.jpg
DELETED
|
Binary file
|
package/index.mjs
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import { generatePKCE } from "@openauthjs/openauth/pkce";
|
|
2
|
-
|
|
3
|
-
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {"max" | "console"} mode
|
|
7
|
-
*/
|
|
8
|
-
async function authorize(mode) {
|
|
9
|
-
const pkce = await generatePKCE();
|
|
10
|
-
|
|
11
|
-
const url = new URL(
|
|
12
|
-
`https://${mode === "console" ? "console.anthropic.com" : "claude.ai"}/oauth/authorize`,
|
|
13
|
-
import.meta.url,
|
|
14
|
-
);
|
|
15
|
-
url.searchParams.set("code", "true");
|
|
16
|
-
url.searchParams.set("client_id", CLIENT_ID);
|
|
17
|
-
url.searchParams.set("response_type", "code");
|
|
18
|
-
url.searchParams.set(
|
|
19
|
-
"redirect_uri",
|
|
20
|
-
"https://console.anthropic.com/oauth/code/callback",
|
|
21
|
-
);
|
|
22
|
-
url.searchParams.set(
|
|
23
|
-
"scope",
|
|
24
|
-
"org:create_api_key user:profile user:inference",
|
|
25
|
-
);
|
|
26
|
-
url.searchParams.set("code_challenge", pkce.challenge);
|
|
27
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
28
|
-
url.searchParams.set("state", pkce.verifier);
|
|
29
|
-
return {
|
|
30
|
-
url: url.toString(),
|
|
31
|
-
verifier: pkce.verifier,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param {string} code
|
|
37
|
-
* @param {string} verifier
|
|
38
|
-
*/
|
|
39
|
-
async function exchange(code, verifier) {
|
|
40
|
-
const splits = code.split("#");
|
|
41
|
-
const result = await fetch("https://console.anthropic.com/v1/oauth/token", {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: {
|
|
44
|
-
"Content-Type": "application/json",
|
|
45
|
-
},
|
|
46
|
-
body: JSON.stringify({
|
|
47
|
-
code: splits[0],
|
|
48
|
-
state: splits[1],
|
|
49
|
-
grant_type: "authorization_code",
|
|
50
|
-
client_id: CLIENT_ID,
|
|
51
|
-
redirect_uri: "https://console.anthropic.com/oauth/code/callback",
|
|
52
|
-
code_verifier: verifier,
|
|
53
|
-
}),
|
|
54
|
-
});
|
|
55
|
-
if (!result.ok)
|
|
56
|
-
return {
|
|
57
|
-
type: "failed",
|
|
58
|
-
};
|
|
59
|
-
const json = await result.json();
|
|
60
|
-
return {
|
|
61
|
-
type: "success",
|
|
62
|
-
refresh: json.refresh_token,
|
|
63
|
-
access: json.access_token,
|
|
64
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @type {import('@opencode-ai/plugin').Plugin}
|
|
70
|
-
*/
|
|
71
|
-
export async function AnthropicAuthPlugin({ client }) {
|
|
72
|
-
return {
|
|
73
|
-
"experimental.chat.system.transform": (input, output) => {
|
|
74
|
-
const prefix =
|
|
75
|
-
"You are Claude Code, Anthropic's official CLI for Claude.";
|
|
76
|
-
if (input.model?.providerID === "anthropic") {
|
|
77
|
-
output.system.unshift(prefix);
|
|
78
|
-
if (output.system[1])
|
|
79
|
-
output.system[1] = prefix + "\n\n" + output.system[1];
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
auth: {
|
|
83
|
-
provider: "anthropic",
|
|
84
|
-
async loader(getAuth, provider) {
|
|
85
|
-
const auth = await getAuth();
|
|
86
|
-
if (auth.type === "oauth") {
|
|
87
|
-
// zero out cost for max plan
|
|
88
|
-
for (const model of Object.values(provider.models)) {
|
|
89
|
-
model.cost = {
|
|
90
|
-
input: 0,
|
|
91
|
-
output: 0,
|
|
92
|
-
cache: {
|
|
93
|
-
read: 0,
|
|
94
|
-
write: 0,
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
apiKey: "",
|
|
100
|
-
/**
|
|
101
|
-
* @param {any} input
|
|
102
|
-
* @param {any} init
|
|
103
|
-
*/
|
|
104
|
-
async fetch(input, init) {
|
|
105
|
-
const auth = await getAuth();
|
|
106
|
-
if (auth.type !== "oauth") return fetch(input, init);
|
|
107
|
-
if (!auth.access || auth.expires < Date.now()) {
|
|
108
|
-
const response = await fetch(
|
|
109
|
-
"https://console.anthropic.com/v1/oauth/token",
|
|
110
|
-
{
|
|
111
|
-
method: "POST",
|
|
112
|
-
headers: {
|
|
113
|
-
"Content-Type": "application/json",
|
|
114
|
-
},
|
|
115
|
-
body: JSON.stringify({
|
|
116
|
-
grant_type: "refresh_token",
|
|
117
|
-
refresh_token: auth.refresh,
|
|
118
|
-
client_id: CLIENT_ID,
|
|
119
|
-
}),
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
if (!response.ok) {
|
|
123
|
-
throw new Error(`Token refresh failed: ${response.status}`);
|
|
124
|
-
}
|
|
125
|
-
const json = await response.json();
|
|
126
|
-
await client.auth.set({
|
|
127
|
-
path: {
|
|
128
|
-
id: "anthropic",
|
|
129
|
-
},
|
|
130
|
-
body: {
|
|
131
|
-
type: "oauth",
|
|
132
|
-
refresh: json.refresh_token,
|
|
133
|
-
access: json.access_token,
|
|
134
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
auth.access = json.access_token;
|
|
138
|
-
}
|
|
139
|
-
const requestInit = init ?? {};
|
|
140
|
-
|
|
141
|
-
const requestHeaders = new Headers();
|
|
142
|
-
if (input instanceof Request) {
|
|
143
|
-
input.headers.forEach((value, key) => {
|
|
144
|
-
requestHeaders.set(key, value);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
if (requestInit.headers) {
|
|
148
|
-
if (requestInit.headers instanceof Headers) {
|
|
149
|
-
requestInit.headers.forEach((value, key) => {
|
|
150
|
-
requestHeaders.set(key, value);
|
|
151
|
-
});
|
|
152
|
-
} else if (Array.isArray(requestInit.headers)) {
|
|
153
|
-
for (const [key, value] of requestInit.headers) {
|
|
154
|
-
if (typeof value !== "undefined") {
|
|
155
|
-
requestHeaders.set(key, String(value));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
for (const [key, value] of Object.entries(
|
|
160
|
-
requestInit.headers,
|
|
161
|
-
)) {
|
|
162
|
-
if (typeof value !== "undefined") {
|
|
163
|
-
requestHeaders.set(key, String(value));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Preserve all incoming beta headers while ensuring OAuth requirements
|
|
170
|
-
const incomingBeta = requestHeaders.get("anthropic-beta") || "";
|
|
171
|
-
const incomingBetasList = incomingBeta
|
|
172
|
-
.split(",")
|
|
173
|
-
.map((b) => b.trim())
|
|
174
|
-
.filter(Boolean);
|
|
175
|
-
|
|
176
|
-
const requiredBetas = [
|
|
177
|
-
"oauth-2025-04-20",
|
|
178
|
-
"interleaved-thinking-2025-05-14",
|
|
179
|
-
];
|
|
180
|
-
const mergedBetas = [
|
|
181
|
-
...new Set([...requiredBetas, ...incomingBetasList]),
|
|
182
|
-
].join(",");
|
|
183
|
-
|
|
184
|
-
requestHeaders.set("authorization", `Bearer ${auth.access}`);
|
|
185
|
-
requestHeaders.set("anthropic-beta", mergedBetas);
|
|
186
|
-
requestHeaders.set(
|
|
187
|
-
"user-agent",
|
|
188
|
-
"claude-cli/2.1.2 (external, cli)",
|
|
189
|
-
);
|
|
190
|
-
requestHeaders.delete("x-api-key");
|
|
191
|
-
|
|
192
|
-
const TOOL_PREFIX = "mcp_";
|
|
193
|
-
let body = requestInit.body;
|
|
194
|
-
if (body && typeof body === "string") {
|
|
195
|
-
try {
|
|
196
|
-
const parsed = JSON.parse(body);
|
|
197
|
-
|
|
198
|
-
// Add prefix to tools definitions
|
|
199
|
-
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
200
|
-
parsed.tools = parsed.tools.map((tool) => ({
|
|
201
|
-
...tool,
|
|
202
|
-
name: tool.name
|
|
203
|
-
? `${TOOL_PREFIX}${tool.name}`
|
|
204
|
-
: tool.name,
|
|
205
|
-
}));
|
|
206
|
-
}
|
|
207
|
-
// Add prefix to tool_use blocks in messages
|
|
208
|
-
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
209
|
-
parsed.messages = parsed.messages.map((msg) => {
|
|
210
|
-
if (msg.content && Array.isArray(msg.content)) {
|
|
211
|
-
msg.content = msg.content.map((block) => {
|
|
212
|
-
if (block.type === "tool_use" && block.name) {
|
|
213
|
-
return {
|
|
214
|
-
...block,
|
|
215
|
-
name: `${TOOL_PREFIX}${block.name}`,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
return block;
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
return msg;
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
body = JSON.stringify(parsed);
|
|
225
|
-
} catch (e) {
|
|
226
|
-
// ignore parse errors
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
let requestInput = input;
|
|
231
|
-
let requestUrl = null;
|
|
232
|
-
try {
|
|
233
|
-
if (typeof input === "string" || input instanceof URL) {
|
|
234
|
-
requestUrl = new URL(input.toString());
|
|
235
|
-
} else if (input instanceof Request) {
|
|
236
|
-
requestUrl = new URL(input.url);
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
requestUrl = null;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
requestUrl &&
|
|
244
|
-
requestUrl.pathname === "/v1/messages" &&
|
|
245
|
-
!requestUrl.searchParams.has("beta")
|
|
246
|
-
) {
|
|
247
|
-
requestUrl.searchParams.set("beta", "true");
|
|
248
|
-
requestInput =
|
|
249
|
-
input instanceof Request
|
|
250
|
-
? new Request(requestUrl.toString(), input)
|
|
251
|
-
: requestUrl;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const response = await fetch(requestInput, {
|
|
255
|
-
...requestInit,
|
|
256
|
-
body,
|
|
257
|
-
headers: requestHeaders,
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Transform streaming response to rename tools back
|
|
261
|
-
if (response.body) {
|
|
262
|
-
const reader = response.body.getReader();
|
|
263
|
-
const decoder = new TextDecoder();
|
|
264
|
-
const encoder = new TextEncoder();
|
|
265
|
-
|
|
266
|
-
const stream = new ReadableStream({
|
|
267
|
-
async pull(controller) {
|
|
268
|
-
const { done, value } = await reader.read();
|
|
269
|
-
if (done) {
|
|
270
|
-
controller.close();
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
let text = decoder.decode(value, { stream: true });
|
|
275
|
-
text = text.replace(
|
|
276
|
-
/"name"\s*:\s*"mcp_([^"]+)"/g,
|
|
277
|
-
'"name": "$1"',
|
|
278
|
-
);
|
|
279
|
-
controller.enqueue(encoder.encode(text));
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
return new Response(stream, {
|
|
284
|
-
status: response.status,
|
|
285
|
-
statusText: response.statusText,
|
|
286
|
-
headers: response.headers,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return response;
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return {};
|
|
296
|
-
},
|
|
297
|
-
methods: [
|
|
298
|
-
{
|
|
299
|
-
label: "Claude Pro/Max",
|
|
300
|
-
type: "oauth",
|
|
301
|
-
authorize: async () => {
|
|
302
|
-
const { url, verifier } = await authorize("max");
|
|
303
|
-
return {
|
|
304
|
-
url: url,
|
|
305
|
-
instructions: "Paste the authorization code here: ",
|
|
306
|
-
method: "code",
|
|
307
|
-
callback: async (code) => {
|
|
308
|
-
const credentials = await exchange(code, verifier);
|
|
309
|
-
return credentials;
|
|
310
|
-
},
|
|
311
|
-
};
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
label: "Create an API Key",
|
|
316
|
-
type: "oauth",
|
|
317
|
-
authorize: async () => {
|
|
318
|
-
const { url, verifier } = await authorize("console");
|
|
319
|
-
return {
|
|
320
|
-
url: url,
|
|
321
|
-
instructions: "Paste the authorization code here: ",
|
|
322
|
-
method: "code",
|
|
323
|
-
callback: async (code) => {
|
|
324
|
-
const credentials = await exchange(code, verifier);
|
|
325
|
-
if (credentials.type === "failed") return credentials;
|
|
326
|
-
const result = await fetch(
|
|
327
|
-
`https://api.anthropic.com/api/oauth/claude_cli/create_api_key`,
|
|
328
|
-
{
|
|
329
|
-
method: "POST",
|
|
330
|
-
headers: {
|
|
331
|
-
"Content-Type": "application/json",
|
|
332
|
-
authorization: `Bearer ${credentials.access}`,
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
).then((r) => r.json());
|
|
336
|
-
return { type: "success", key: result.raw_key };
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
provider: "anthropic",
|
|
343
|
-
label: "Manually enter API Key",
|
|
344
|
-
type: "api",
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
},
|
|
348
|
-
};
|
|
349
|
-
}
|
package/script/bump.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Bumps the version in package.json, commits, and pushes.
|
|
4
|
-
* Pushing the version change to main triggers the CI publish workflow.
|
|
5
|
-
*
|
|
6
|
-
* Usage: bun script/bump.ts [patch|minor|major] [--dry-run]
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { $ } from "bun";
|
|
10
|
-
|
|
11
|
-
const dir = new URL("..", import.meta.url).pathname;
|
|
12
|
-
process.chdir(dir);
|
|
13
|
-
|
|
14
|
-
const args = process.argv.slice(2).filter((a) => !a.startsWith("--"));
|
|
15
|
-
const bumpType = args[0] || "patch";
|
|
16
|
-
const dry = process.argv.includes("--dry-run");
|
|
17
|
-
|
|
18
|
-
const log = (msg: string) => console.log(dry ? `[DRY RUN] ${msg}` : msg);
|
|
19
|
-
|
|
20
|
-
// Read current version
|
|
21
|
-
const currentPkg = await Bun.file("./package.json").json();
|
|
22
|
-
log(`Current version: ${currentPkg.version}`);
|
|
23
|
-
log(`Bumping ${bumpType}...`);
|
|
24
|
-
|
|
25
|
-
// Bump version in package.json
|
|
26
|
-
if (!dry) {
|
|
27
|
-
await $`npm version ${bumpType} --no-git-tag-version`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Read the new version
|
|
31
|
-
const pkg = await Bun.file("./package.json").json();
|
|
32
|
-
const version = pkg.version;
|
|
33
|
-
|
|
34
|
-
log(`New version: ${version}`);
|
|
35
|
-
|
|
36
|
-
// Commit and push — CI will handle npm publish
|
|
37
|
-
if (!dry) {
|
|
38
|
-
await $`git add package.json`;
|
|
39
|
-
await $`git commit -m ${"chore: bump to " + version}`;
|
|
40
|
-
await $`git push`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
log(`Done!`);
|