@suncreation/opencode-claude-patch 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/README.md +105 -0
- package/package.json +36 -0
- package/src/helpers.d.ts +29 -0
- package/src/helpers.js +260 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# opencode-anthropic-oauth-patch
|
|
2
|
+
|
|
3
|
+
Installable OpenCode plugin for sanitizing Anthropic OAuth / Claude request shape, with helper exports for lower-level integrations.
|
|
4
|
+
|
|
5
|
+
This is meant for OpenCode/oh-my-opencode-style integrations where Claude OAuth requests break because extra Anthropic-incompatible fields are injected into the request path.
|
|
6
|
+
|
|
7
|
+
## What it patches automatically as an OpenCode plugin
|
|
8
|
+
|
|
9
|
+
- Removes `thinking`, `output_config`, and related Anthropic-problematic option fields from OpenCode provider options
|
|
10
|
+
- Rewrites OpenCode system prompt strings into a single Claude-Code-compatible system string
|
|
11
|
+
- Sets Claude-Code-compatible Anthropic headers by default
|
|
12
|
+
- Installs a targeted global `fetch` patch that sanitizes outbound Anthropic OAuth message bodies, including final `system[*].cache_control` removal
|
|
13
|
+
|
|
14
|
+
## How the automatic patch works
|
|
15
|
+
|
|
16
|
+
The root plugin uses two layers:
|
|
17
|
+
|
|
18
|
+
1. OpenCode hooks (`chat.params`, `chat.headers`, `experimental.chat.system.transform`) for public-ABI-safe fixes
|
|
19
|
+
2. a narrowly targeted `globalThis.fetch` wrapper that only patches `https://api.anthropic.com/v1/messages...` requests carrying `Authorization: Bearer ...`
|
|
20
|
+
|
|
21
|
+
That second layer is what makes the package automatic for the `cache_control` problem too.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Add the package to your `opencode.json` plugin list:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"$schema": "https://opencode.ai/config.json",
|
|
30
|
+
"plugin": [
|
|
31
|
+
"@suncreation/opencode-claude-patch"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install it from this repository:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install /Users/admin/Projects/subscription-llm/npm/opencode-anthropic-oauth-patch
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If you later publish it, keep the same `plugin` entry and install with:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @suncreation/opencode-claude-patch
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
OpenCode will auto-install npm plugins referenced in `opencode.json` at startup.
|
|
49
|
+
|
|
50
|
+
## What the root package export does
|
|
51
|
+
|
|
52
|
+
The root package export is an OpenCode plugin function. Once installed and listed in `opencode.json`, it auto-applies:
|
|
53
|
+
|
|
54
|
+
- `chat.params` patching for Anthropic/Claude models
|
|
55
|
+
- `chat.headers` patching for Claude-compatible headers
|
|
56
|
+
- `experimental.chat.system.transform` patching for a single Claude-Code-style system prompt
|
|
57
|
+
- a targeted `globalThis.fetch` patch for final Anthropic OAuth request-body cleanup
|
|
58
|
+
|
|
59
|
+
No extra wiring is needed.
|
|
60
|
+
|
|
61
|
+
## Optional environment toggles
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
OPENCODE_ANTHROPIC_OAUTH_SET_HEADERS=0
|
|
65
|
+
OPENCODE_ANTHROPIC_OAUTH_SYSTEM_PREFIX="You are Claude Code, Anthropic's official CLI for Claude."
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Helper subpath for lower-level integrations
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import {
|
|
72
|
+
createAnthropicOAuthPatchedFetch,
|
|
73
|
+
mergePatchHeaders,
|
|
74
|
+
normalizeAnthropicSystemStrings,
|
|
75
|
+
sanitizeAnthropicOptions
|
|
76
|
+
} from "@suncreation/opencode-claude-patch/helpers";
|
|
77
|
+
|
|
78
|
+
// if published:
|
|
79
|
+
// import { ... } from "@suncreation/opencode-anthropic-oauth-patch/helpers";
|
|
80
|
+
|
|
81
|
+
const options = sanitizeAnthropicOptions({
|
|
82
|
+
thinking: { type: "adaptive" },
|
|
83
|
+
output_config: { effort: "max" }
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const headers = mergePatchHeaders({});
|
|
87
|
+
const system = normalizeAnthropicSystemStrings([
|
|
88
|
+
"Be concise.",
|
|
89
|
+
"Answer in Korean."
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const patchedFetch = createAnthropicOAuthPatchedFetch(fetch, {
|
|
93
|
+
ensureClaudeCodeSystem: true,
|
|
94
|
+
normalizeSystemToUserMessage: true
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(options, headers, system, typeof patchedFetch);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Notes
|
|
101
|
+
|
|
102
|
+
- The root export is safe for `opencode.json -> plugin` installation.
|
|
103
|
+
- The `./helpers` subpath is for deeper interceptors or forks.
|
|
104
|
+
- It does not mint OAuth tokens or bypass account-level/provider policy restrictions.
|
|
105
|
+
- This package is targeted at Claude OAuth / oh-my-opencode-style setups. If you use Anthropic API keys and want native reasoning features, do not enable it globally.
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@suncreation/opencode-claude-patch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable Anthropic OAuth request sanitizer for OpenCode and similar tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./helpers": {
|
|
14
|
+
"types": "./src/helpers.d.ts",
|
|
15
|
+
"import": "./src/helpers.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"anthropic",
|
|
27
|
+
"oauth",
|
|
28
|
+
"opencode",
|
|
29
|
+
"claude",
|
|
30
|
+
"patch"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
,"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/helpers.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface PatchOptions {
|
|
2
|
+
ensureClaudeCodeSystem?: boolean;
|
|
3
|
+
normalizeSystemToUserMessage?: boolean;
|
|
4
|
+
systemPrefix?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export declare const DEFAULT_SYSTEM_PREFIX: string;
|
|
8
|
+
export declare const DEFAULT_PATCH_HEADERS: Record<string, string>;
|
|
9
|
+
|
|
10
|
+
export declare function sanitizeAnthropicOAuthBody<T>(body: T, options?: PatchOptions): T;
|
|
11
|
+
export declare function sanitizeAnthropicOptions<T>(options: T): T;
|
|
12
|
+
export declare function normalizeAnthropicSystemStrings(system: unknown, options?: PatchOptions): string[];
|
|
13
|
+
export declare function mergePatchHeaders(
|
|
14
|
+
headers?: Record<string, string>,
|
|
15
|
+
overrides?: Record<string, string>
|
|
16
|
+
): Record<string, string>;
|
|
17
|
+
export declare function isAnthropicClaudeModel(model: unknown): boolean;
|
|
18
|
+
export declare function isOpenCodeAnthropicPatchTarget(input: unknown): boolean;
|
|
19
|
+
export declare function shouldPatchAnthropicOAuthRequest(url: string, init?: RequestInit): boolean;
|
|
20
|
+
export declare function patchAnthropicOAuthRequest(
|
|
21
|
+
url: string,
|
|
22
|
+
init?: RequestInit,
|
|
23
|
+
options?: PatchOptions
|
|
24
|
+
): { url: string; init: RequestInit; patched: boolean; body?: Record<string, unknown> };
|
|
25
|
+
export declare function createAnthropicOAuthPatchedFetch(
|
|
26
|
+
fetchImpl: typeof fetch,
|
|
27
|
+
options?: PatchOptions
|
|
28
|
+
): typeof fetch;
|
|
29
|
+
export declare function installGlobalAnthropicOAuthFetchPatch(options?: PatchOptions): boolean;
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const DEFAULT_SYSTEM_PREFIX = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PATCH_HEADERS = {
|
|
4
|
+
"anthropic-beta": "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219",
|
|
5
|
+
"anthropic-version": "2023-06-01",
|
|
6
|
+
"user-agent": "claude-cli/2.1.2 (external, cli)"
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const FETCH_PATCH_MARKER = Symbol.for("opencode.anthropic.oauth.patch.fetch");
|
|
10
|
+
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function deepClone(value) {
|
|
16
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isAnthropicMessagesUrl(url) {
|
|
20
|
+
if (typeof url !== "string") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return /^https:\/\/api\.anthropic\.com\/v1\/messages(?:\?|$)/.test(url);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasOAuthBearer(headers) {
|
|
28
|
+
if (!headers) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
33
|
+
const authorization = headers.get("authorization");
|
|
34
|
+
return typeof authorization === "string" && authorization.toLowerCase().startsWith("bearer ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(headers)) {
|
|
38
|
+
for (const [key, value] of headers) {
|
|
39
|
+
if (String(key).toLowerCase() === "authorization") {
|
|
40
|
+
return String(value).toLowerCase().startsWith("bearer ");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (isPlainObject(headers)) {
|
|
47
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
48
|
+
if (key.toLowerCase() === "authorization") {
|
|
49
|
+
return typeof value === "string" && value.toLowerCase().startsWith("bearer ");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractBody(init) {
|
|
58
|
+
if (!init || !("body" in init)) {
|
|
59
|
+
return { body: undefined, bodyType: "missing" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof init.body === "string") {
|
|
63
|
+
return { body: JSON.parse(init.body), bodyType: "json-string" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isPlainObject(init.body)) {
|
|
67
|
+
return { body: deepClone(init.body), bodyType: "object" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { body: undefined, bodyType: "unsupported" };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stripKnownAnthropicProblemFields(node) {
|
|
74
|
+
if (Array.isArray(node)) {
|
|
75
|
+
return node.map(stripKnownAnthropicProblemFields);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!isPlainObject(node)) {
|
|
79
|
+
return node;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const next = {};
|
|
83
|
+
for (const [key, value] of Object.entries(node)) {
|
|
84
|
+
if (
|
|
85
|
+
key === "thinking" ||
|
|
86
|
+
key === "output_config" ||
|
|
87
|
+
key === "outputConfig" ||
|
|
88
|
+
key === "cache_control" ||
|
|
89
|
+
key === "cacheControl"
|
|
90
|
+
) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (key === "effort" && isPlainObject(node) && ("thinking" in node || "output_config" in node || "outputConfig" in node)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
next[key] = stripKnownAnthropicProblemFields(value);
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function mergeSystemTextIntoFirstUserMessage(messages, text) {
|
|
104
|
+
if (!text) {
|
|
105
|
+
return messages;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const injected = `<SYSTEM_INSTRUCTIONS>\n${text}\n</SYSTEM_INSTRUCTIONS>`;
|
|
109
|
+
const nextMessages = Array.isArray(messages) ? messages.map((message) => ({ ...message })) : [];
|
|
110
|
+
|
|
111
|
+
if (nextMessages.length > 0 && nextMessages[0].role === "user" && typeof nextMessages[0].content === "string") {
|
|
112
|
+
nextMessages[0].content = `${injected}\n\n${nextMessages[0].content}`;
|
|
113
|
+
return nextMessages;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
nextMessages.unshift({ role: "user", content: injected });
|
|
117
|
+
return nextMessages;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function sanitizeAnthropicOAuthBody(body, options = {}) {
|
|
121
|
+
if (!isPlainObject(body)) {
|
|
122
|
+
return body;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const {
|
|
126
|
+
ensureClaudeCodeSystem = false,
|
|
127
|
+
normalizeSystemToUserMessage = false,
|
|
128
|
+
systemPrefix = DEFAULT_SYSTEM_PREFIX
|
|
129
|
+
} = options;
|
|
130
|
+
|
|
131
|
+
const nextBody = stripKnownAnthropicProblemFields(deepClone(body));
|
|
132
|
+
|
|
133
|
+
if (ensureClaudeCodeSystem || normalizeSystemToUserMessage) {
|
|
134
|
+
const systemText = [];
|
|
135
|
+
|
|
136
|
+
if (Array.isArray(nextBody.system)) {
|
|
137
|
+
for (const entry of nextBody.system) {
|
|
138
|
+
if (isPlainObject(entry) && typeof entry.text === "string" && entry.text !== systemPrefix) {
|
|
139
|
+
systemText.push(entry.text);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
nextBody.system = [{ type: "text", text: systemPrefix }];
|
|
145
|
+
|
|
146
|
+
if (normalizeSystemToUserMessage) {
|
|
147
|
+
nextBody.messages = mergeSystemTextIntoFirstUserMessage(nextBody.messages, systemText.join("\n\n"));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return nextBody;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function sanitizeAnthropicOptions(options) {
|
|
155
|
+
if (!isPlainObject(options)) {
|
|
156
|
+
return options;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return stripKnownAnthropicProblemFields(deepClone(options));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function normalizeAnthropicSystemStrings(system, options = {}) {
|
|
163
|
+
const { systemPrefix = DEFAULT_SYSTEM_PREFIX } = options;
|
|
164
|
+
const values = Array.isArray(system) ? system.filter((entry) => typeof entry === "string" && entry.trim()) : [];
|
|
165
|
+
const extra = values.filter((entry) => entry !== systemPrefix).join("\n\n");
|
|
166
|
+
|
|
167
|
+
if (!extra) {
|
|
168
|
+
return [systemPrefix];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [`${systemPrefix}\n\n<SYSTEM_INSTRUCTIONS>\n${extra}\n</SYSTEM_INSTRUCTIONS>`];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function mergePatchHeaders(headers = {}, overrides = {}) {
|
|
175
|
+
return {
|
|
176
|
+
...headers,
|
|
177
|
+
...DEFAULT_PATCH_HEADERS,
|
|
178
|
+
...overrides
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function isAnthropicClaudeModel(model) {
|
|
183
|
+
if (!isPlainObject(model)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const providerID = typeof model.providerID === "string" ? model.providerID.toLowerCase() : "";
|
|
188
|
+
const modelID = typeof model.id === "string" ? model.id.toLowerCase() : "";
|
|
189
|
+
const api = isPlainObject(model.api) ? model.api : {};
|
|
190
|
+
const apiID = typeof api.id === "string" ? api.id.toLowerCase() : "";
|
|
191
|
+
const apiNpm = typeof api.npm === "string" ? api.npm.toLowerCase() : "";
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
providerID.includes("anthropic") ||
|
|
195
|
+
modelID.includes("claude") ||
|
|
196
|
+
apiID.includes("claude") ||
|
|
197
|
+
apiID.includes("anthropic") ||
|
|
198
|
+
apiNpm === "@ai-sdk/anthropic" ||
|
|
199
|
+
apiNpm === "@ai-sdk/google-vertex/anthropic"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function isOpenCodeAnthropicPatchTarget(input) {
|
|
204
|
+
return isPlainObject(input) && isAnthropicClaudeModel(input.model);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function shouldPatchAnthropicOAuthRequest(url, init = {}) {
|
|
208
|
+
return isAnthropicMessagesUrl(url) && hasOAuthBearer(init.headers);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function patchAnthropicOAuthRequest(url, init = {}, options = {}) {
|
|
212
|
+
if (!shouldPatchAnthropicOAuthRequest(url, init)) {
|
|
213
|
+
return { url, init, patched: false };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const { body, bodyType } = extractBody(init);
|
|
217
|
+
if (!body || bodyType === "unsupported") {
|
|
218
|
+
return { url, init, patched: false };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const sanitizedBody = sanitizeAnthropicOAuthBody(body, options);
|
|
222
|
+
const nextInit = { ...init };
|
|
223
|
+
|
|
224
|
+
if (bodyType === "json-string") {
|
|
225
|
+
nextInit.body = JSON.stringify(sanitizedBody);
|
|
226
|
+
} else {
|
|
227
|
+
nextInit.body = sanitizedBody;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { url, init: nextInit, patched: true, body: sanitizedBody };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function createAnthropicOAuthPatchedFetch(fetchImpl, options = {}) {
|
|
234
|
+
if (typeof fetchImpl !== "function") {
|
|
235
|
+
throw new TypeError("fetchImpl must be a function");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const patchedFetch = async function patchedFetch(url, init = {}) {
|
|
239
|
+
const patched = patchAnthropicOAuthRequest(url, init, options);
|
|
240
|
+
return fetchImpl(patched.url, patched.init);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
patchedFetch[FETCH_PATCH_MARKER] = true;
|
|
244
|
+
return patchedFetch;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function installGlobalAnthropicOAuthFetchPatch(options = {}) {
|
|
248
|
+
if (typeof globalThis.fetch !== "function") {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (globalThis.fetch[FETCH_PATCH_MARKER]) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
globalThis.fetch = createAnthropicOAuthPatchedFetch(globalThis.fetch.bind(globalThis), options);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { DEFAULT_PATCH_HEADERS, DEFAULT_SYSTEM_PREFIX, FETCH_PATCH_MARKER };
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SYSTEM_PREFIX,
|
|
3
|
+
installGlobalAnthropicOAuthFetchPatch,
|
|
4
|
+
isOpenCodeAnthropicPatchTarget,
|
|
5
|
+
mergePatchHeaders,
|
|
6
|
+
normalizeAnthropicSystemStrings,
|
|
7
|
+
sanitizeAnthropicOptions
|
|
8
|
+
} from "./helpers.js";
|
|
9
|
+
|
|
10
|
+
function getPluginOptions() {
|
|
11
|
+
return {
|
|
12
|
+
systemPrefix: process.env.OPENCODE_ANTHROPIC_OAUTH_SYSTEM_PREFIX || DEFAULT_SYSTEM_PREFIX,
|
|
13
|
+
setClaudeHeaders: process.env.OPENCODE_ANTHROPIC_OAUTH_SET_HEADERS !== "0"
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function OpenCodeAnthropicOAuthPatch() {
|
|
18
|
+
const pluginOptions = getPluginOptions();
|
|
19
|
+
installGlobalAnthropicOAuthFetchPatch({
|
|
20
|
+
ensureClaudeCodeSystem: true,
|
|
21
|
+
normalizeSystemToUserMessage: true,
|
|
22
|
+
systemPrefix: pluginOptions.systemPrefix
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
async "chat.params"(input, output) {
|
|
27
|
+
if (!isOpenCodeAnthropicPatchTarget(input)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
output.options = sanitizeAnthropicOptions(output.options);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async "chat.headers"(input, output) {
|
|
35
|
+
if (!isOpenCodeAnthropicPatchTarget(input) || !pluginOptions.setClaudeHeaders) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
output.headers = mergePatchHeaders(output.headers);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async "experimental.chat.system.transform"(input, output) {
|
|
43
|
+
if (!isOpenCodeAnthropicPatchTarget(input)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nextSystem = normalizeAnthropicSystemStrings(output.system, {
|
|
48
|
+
systemPrefix: pluginOptions.systemPrefix
|
|
49
|
+
});
|
|
50
|
+
output.system.length = 0;
|
|
51
|
+
output.system.push(...nextSystem);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|