opencode-anthropic-fix 0.3.0 → 0.5.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/index.mjs +33 -14
- package/lib/mimicry/adapter-input.mjs +23 -9
- package/lib/mimicry/wire-compat.mjs +203 -13
- package/lib/request-headers.mjs +9 -1
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
SUBAGENT_CC_ANCHOR,
|
|
78
78
|
} from "./lib/mimicry/system-prompt.mjs";
|
|
79
79
|
import { buildAdapterTransport, resolveAdapterEnv } from "./lib/mimicry/adapter-input.mjs";
|
|
80
|
-
import { buildWireCompatibleRequest } from "./lib/mimicry/wire-compat.mjs";
|
|
80
|
+
import { buildWireCompatibleRequest, buildWireCompatibleCountTokensRequest } from "./lib/mimicry/wire-compat.mjs";
|
|
81
81
|
import {
|
|
82
82
|
hasOneMillionContext,
|
|
83
83
|
isEligibleFor1MContext,
|
|
@@ -3010,20 +3010,29 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
3010
3010
|
// 1. signature emulation on — with it off the plugin emits only
|
|
3011
3011
|
// 3 headers and a minimal beta set, while the package always
|
|
3012
3012
|
// emits the full Claude Code set;
|
|
3013
|
-
// 2.
|
|
3014
|
-
// `
|
|
3015
|
-
//
|
|
3016
|
-
//
|
|
3013
|
+
// 2. a messages endpoint the package has a surface for. Both
|
|
3014
|
+
// qualify now: `buildClaudeCodeRequest` for /v1/messages and
|
|
3015
|
+
// `buildClaudeCodeCountTokensRequest` for
|
|
3016
|
+
// /v1/messages/count_tokens, each pinning its OWN endpoint.
|
|
3017
|
+
// The endpoint pin is inert either way — `built.url` is never
|
|
3018
|
+
// adopted (see the consumption comment below), so the URL on
|
|
3019
|
+
// the wire stays the one transformRequestUrl produced and a
|
|
3020
|
+
// custom ANTHROPIC_BASE_URL survives.
|
|
3017
3021
|
//
|
|
3018
3022
|
// The former first-party check is now structural: the plugin only
|
|
3019
3023
|
// speaks to first-party Anthropic, so it is always satisfied.
|
|
3020
|
-
// 3. a JSON body is actually present
|
|
3021
|
-
// `model` and `max_tokens
|
|
3022
|
-
//
|
|
3024
|
+
// 3. a JSON body is actually present. The main-turn surface
|
|
3025
|
+
// requires `model` and `max_tokens`; the count surface
|
|
3026
|
+
// requires `model` and `messages` and accepts no
|
|
3027
|
+
// `max_tokens` at all. Either way a bodiless request must
|
|
3028
|
+
// keep using the legacy path rather than throwing
|
|
3029
|
+
// INVALID_INPUT.
|
|
3023
3030
|
const _adapterPathname = requestUrl?.pathname;
|
|
3031
|
+
const _isCountTokens =
|
|
3032
|
+
_adapterPathname === "/v1/messages/count_tokens" || _adapterPathname === "/messages/count_tokens";
|
|
3024
3033
|
const _useAdapter =
|
|
3025
3034
|
getSignatureEmulationEnabled() &&
|
|
3026
|
-
(_adapterPathname === "/v1/messages" || _adapterPathname === "/messages") &&
|
|
3035
|
+
(_adapterPathname === "/v1/messages" || _adapterPathname === "/messages" || _isCountTokens) &&
|
|
3027
3036
|
typeof requestInit.body === "string" &&
|
|
3028
3037
|
requestInit.body.length > 0;
|
|
3029
3038
|
|
|
@@ -3106,12 +3115,17 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
3106
3115
|
//
|
|
3107
3116
|
// Adapter path: the shared package composes the headers AND
|
|
3108
3117
|
// rebuilds the body (canonical system prefix, metadata, beta
|
|
3109
|
-
// header).
|
|
3118
|
+
// header). On the count-tokens surface it composes headers and a
|
|
3119
|
+
// body that deliberately carries NO system, metadata or
|
|
3120
|
+
// max_tokens. Legacy path: unchanged, byte for byte.
|
|
3110
3121
|
//
|
|
3111
3122
|
// The URL deliberately stays whatever transformRequestUrl already
|
|
3112
|
-
// produced. `built.url` is the package's pinned
|
|
3113
|
-
// `https://api.anthropic.com/v1/messages?beta=true`,
|
|
3114
|
-
//
|
|
3123
|
+
// produced. `built.url` is the package's pinned endpoint
|
|
3124
|
+
// (`https://api.anthropic.com/v1/messages?beta=true`, or
|
|
3125
|
+
// `.../v1/messages/count_tokens?beta=true` on the count surface),
|
|
3126
|
+
// which would override a custom ANTHROPIC_BASE_URL or proxy
|
|
3127
|
+
// endpoint. transformRequestUrl already appends `?beta=true` to
|
|
3128
|
+
// both endpoints itself, so discarding `built.url` costs nothing.
|
|
3115
3129
|
const _adapterSignature = {
|
|
3116
3130
|
enabled: getSignatureEmulationEnabled(),
|
|
3117
3131
|
claudeCliVersion,
|
|
@@ -3159,7 +3173,12 @@ export async function AnthropicAuthPlugin({ client }) {
|
|
|
3159
3173
|
nodeVersion: process.version,
|
|
3160
3174
|
});
|
|
3161
3175
|
if (_adapterResult.applicable) {
|
|
3162
|
-
|
|
3176
|
+
// Two package surfaces, one transport: the count-tokens
|
|
3177
|
+
// builder consumes a strict subset of the same input, so the
|
|
3178
|
+
// transport object needs no count-specific mode.
|
|
3179
|
+
const built = _isCountTokens
|
|
3180
|
+
? await buildWireCompatibleCountTokensRequest(body, _adapterResult.transport)
|
|
3181
|
+
: await buildWireCompatibleRequest(body, _adapterResult.transport);
|
|
3163
3182
|
requestHeaders = built.headers;
|
|
3164
3183
|
adapterBody = built.body;
|
|
3165
3184
|
} else {
|
|
@@ -131,12 +131,26 @@ export const ADAPTER_ENV_KEYS = Object.freeze([
|
|
|
131
131
|
]);
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* Protocol identity of the
|
|
135
|
-
*
|
|
136
|
-
* `resolveProfileOverride` for the
|
|
134
|
+
* Protocol identity of the profile this adapter composes against
|
|
135
|
+
* (`claude-code-2.1.233-sdk-0.112.1`). Used to decide whether a
|
|
136
|
+
* `profileOverride` is needed at all — see `resolveProfileOverride` for the
|
|
137
|
+
* user-agent convergence argument.
|
|
138
|
+
*
|
|
139
|
+
* PROFILE INHERITANCE POLICY. The adapter never passes an explicit `profile`
|
|
140
|
+
* argument to the package's entry points; omitting it IS the mechanism by which
|
|
141
|
+
* the plugin inherits whatever `DEFAULT_PROFILE` the installed
|
|
142
|
+
* `@tormentalabs/claude-code-wire-compat` declares. The dependency is tracked at
|
|
143
|
+
* the `latest` dist-tag, so each package release can advance the default profile
|
|
144
|
+
* and the plugin follows without a code change to the composition path.
|
|
145
|
+
*
|
|
146
|
+
* These two constants are the ONLY thing that has to move in lockstep: they
|
|
147
|
+
* mirror the default profile's `cliVersion` / `userAgent` so that
|
|
148
|
+
* `resolveProfileOverride` stays silent in the common case. Keep them in sync
|
|
149
|
+
* with `FALLBACK_CLAUDE_CLI_VERSION` (lib/request-headers.mjs) on every package
|
|
150
|
+
* bump that changes the default profile.
|
|
137
151
|
*/
|
|
138
|
-
export const PROFILE_CLI_VERSION = "2.1.
|
|
139
|
-
export const PROFILE_USER_AGENT = "claude-cli/2.1.
|
|
152
|
+
export const PROFILE_CLI_VERSION = "2.1.233";
|
|
153
|
+
export const PROFILE_USER_AGENT = "claude-cli/2.1.233 (external, cli)";
|
|
140
154
|
|
|
141
155
|
// ---------------------------------------------------------------------------
|
|
142
156
|
// Environment snapshot
|
|
@@ -179,12 +193,12 @@ export function buildAdapterUserAgent(version, env) {
|
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
/**
|
|
182
|
-
* The
|
|
183
|
-
* (`"claude-cli/2.1.
|
|
196
|
+
* The inherited default profile carries a FIXED `userAgent`
|
|
197
|
+
* (`"claude-cli/2.1.233 (external, cli)"`) while the plugin builds one
|
|
184
198
|
* dynamically from entrypoint / agent-sdk version / client-app. In the common
|
|
185
199
|
* case — no `CLAUDE_CODE_ENTRYPOINT`, no `CLAUDE_AGENT_SDK_VERSION`, no
|
|
186
|
-
* `CLAUDE_AGENT_SDK_CLIENT_APP`, detected CLI version `2.1.
|
|
187
|
-
* produce the byte-identical `"claude-cli/2.1.
|
|
200
|
+
* `CLAUDE_AGENT_SDK_CLIENT_APP`, detected CLI version `2.1.233` — both sides
|
|
201
|
+
* produce the byte-identical `"claude-cli/2.1.233 (external, cli)"`, so no
|
|
188
202
|
* override is emitted and the request stays byte-identical to the profile.
|
|
189
203
|
*
|
|
190
204
|
* When they diverge, `userAgent` and `cliVersion` are validated TOGETHER by the
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* NOT promised because the returned HeaderPair array is converted to Headers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { buildClaudeCodeRequest } from "@tormentalabs/claude-code-wire-compat";
|
|
7
|
+
import { buildClaudeCodeRequest, buildClaudeCodeCountTokensRequest } from "@tormentalabs/claude-code-wire-compat";
|
|
8
8
|
import { stripStainlessHelperMarkers } from "./headers.mjs";
|
|
9
9
|
|
|
10
10
|
const PROFILE_OVERRIDE_ENV = "OPENCODE_ANTHROPIC_PROFILE_OVERRIDE";
|
|
@@ -288,18 +288,208 @@ export async function buildWireCompatibleRequest(body, transport) {
|
|
|
288
288
|
try {
|
|
289
289
|
built = await buildClaudeCodeRequest(toClaudeCodeRequestInput(parsed, transport));
|
|
290
290
|
} catch (error) {
|
|
291
|
-
|
|
292
|
-
// carries the useful part in `safeDetails`, which nothing surfaces. Fold it
|
|
293
|
-
// into the message so a rejection is diagnosable from a stack trace alone.
|
|
294
|
-
// The error object itself is preserved, so `code`, `safeDetails` and
|
|
295
|
-
// `instanceof` all still hold for callers that inspect them.
|
|
296
|
-
if (error instanceof Error && "safeDetails" in error) {
|
|
297
|
-
const detail = Object.entries(error.safeDetails ?? {})
|
|
298
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
299
|
-
.join(" ");
|
|
300
|
-
if (detail) error.message = `${error.message} (${detail})`;
|
|
301
|
-
}
|
|
302
|
-
throw error;
|
|
291
|
+
throw foldSafeDetailsIntoMessage(error);
|
|
303
292
|
}
|
|
304
293
|
return { ...built, headers: new Headers(built.headers) };
|
|
305
294
|
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The subset of `ClaudeCodeRequestInput` keys that `ClaudeCodeCountTokensInput`
|
|
298
|
+
* picks. Kept as data so the pick below stays a single source of truth.
|
|
299
|
+
*/
|
|
300
|
+
const COUNT_TOKENS_INPUT_FIELDS = new Set([
|
|
301
|
+
"accessToken",
|
|
302
|
+
"model",
|
|
303
|
+
"messages",
|
|
304
|
+
"tools",
|
|
305
|
+
"runtime",
|
|
306
|
+
"clientRequestId",
|
|
307
|
+
"profileOverride",
|
|
308
|
+
"crypto",
|
|
309
|
+
"app",
|
|
310
|
+
"stainlessRetryCount",
|
|
311
|
+
"stainlessHelper",
|
|
312
|
+
"claudeRemoteContainerId",
|
|
313
|
+
"claudeRemoteSessionId",
|
|
314
|
+
"clientApp",
|
|
315
|
+
"anthropicAdditionalProtection",
|
|
316
|
+
"extraHeaders",
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The header names the package composes itself, mirrored from `HEADER_NAMES`
|
|
321
|
+
* (src/headers.ts). Exported so `wire-compat.test.mjs` can pin the mirror
|
|
322
|
+
* against the package's real behaviour rather than trusting this copy.
|
|
323
|
+
*/
|
|
324
|
+
export const PACKAGE_CANONICAL_HEADER_NAMES = new Set([
|
|
325
|
+
"anthropic-beta",
|
|
326
|
+
"anthropic-dangerous-direct-browser-access",
|
|
327
|
+
"anthropic-version",
|
|
328
|
+
"authorization",
|
|
329
|
+
"content-type",
|
|
330
|
+
"user-agent",
|
|
331
|
+
"x-app",
|
|
332
|
+
"x-claude-code-session-id",
|
|
333
|
+
"x-client-request-id",
|
|
334
|
+
"x-stainless-arch",
|
|
335
|
+
"x-stainless-lang",
|
|
336
|
+
"x-stainless-os",
|
|
337
|
+
"x-stainless-package-version",
|
|
338
|
+
"x-stainless-retry-count",
|
|
339
|
+
"x-stainless-runtime",
|
|
340
|
+
"x-stainless-runtime-version",
|
|
341
|
+
"x-stainless-timeout",
|
|
342
|
+
"x-stainless-helper",
|
|
343
|
+
"x-claude-remote-container-id",
|
|
344
|
+
"x-claude-remote-session-id",
|
|
345
|
+
"x-client-app",
|
|
346
|
+
"x-anthropic-additional-protection",
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
/** Mirror of the package's `FORBIDDEN_HEADER_NAMES` (src/headers.ts). */
|
|
350
|
+
export const PACKAGE_FORBIDDEN_HEADER_NAMES = new Set([
|
|
351
|
+
"x-api-key",
|
|
352
|
+
"cookie",
|
|
353
|
+
"set-cookie",
|
|
354
|
+
"forwarded",
|
|
355
|
+
"content-length",
|
|
356
|
+
"host",
|
|
357
|
+
"connection",
|
|
358
|
+
"transfer-encoding",
|
|
359
|
+
"te",
|
|
360
|
+
"upgrade",
|
|
361
|
+
"keep-alive",
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
/** Mirror of the package's `isForbiddenHeader` prefix rules. */
|
|
365
|
+
export const PACKAGE_FORBIDDEN_HEADER_PREFIXES = Object.freeze(["proxy-", "x-forwarded-"]);
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Apply the package's `dropConflicting` extra-header policy PLUGIN-SIDE.
|
|
369
|
+
*
|
|
370
|
+
* WHY THIS EXISTS. `extraHeaderPolicy` is not one of the sixteen keys
|
|
371
|
+
* `ClaudeCodeCountTokensInput` picks, and `validateCountTokensInput` runs
|
|
372
|
+
* `assertExactKeys`, so passing it is `INVALID_INPUT` rather than a no-op. The
|
|
373
|
+
* count surface therefore always resolves extra headers under `strict`, where
|
|
374
|
+
* the FIRST host header the package owns raises `DUPLICATE_HEADER` (canonical
|
|
375
|
+
* name) or `FORBIDDEN_HEADER` (hop-by-hop / credential name) and nothing
|
|
376
|
+
* reaches the wire. The plugin forwards a heterogeneous host header map — the
|
|
377
|
+
* opencode SDK alone sends `content-type` and `accept` — so without this filter
|
|
378
|
+
* every count turn would throw and fall back to nothing at all.
|
|
379
|
+
*
|
|
380
|
+
* The main turn gets the same outcome for free through
|
|
381
|
+
* `extraHeaderPolicy: "dropConflicting"` on the transport, which the count pick
|
|
382
|
+
* discards; this reproduces it. Only OWNERSHIP conflicts are dropped, exactly
|
|
383
|
+
* as the package does: header syntax is still validated package-side and a
|
|
384
|
+
* caller duplicating one of its OWN extra headers still raises
|
|
385
|
+
* `DUPLICATE_HEADER`, because that is a caller bug and not ours to paper over.
|
|
386
|
+
*
|
|
387
|
+
* @param {readonly (readonly [string, string])[] | undefined} extraHeaders
|
|
388
|
+
* @returns {readonly (readonly [string, string])[] | undefined}
|
|
389
|
+
*/
|
|
390
|
+
function dropConflictingExtraHeaders(extraHeaders) {
|
|
391
|
+
if (extraHeaders === undefined) return undefined;
|
|
392
|
+
const kept = extraHeaders.filter(([name]) => {
|
|
393
|
+
const normalized = String(name).toLowerCase();
|
|
394
|
+
if (PACKAGE_CANONICAL_HEADER_NAMES.has(normalized)) return false;
|
|
395
|
+
if (PACKAGE_FORBIDDEN_HEADER_NAMES.has(normalized)) return false;
|
|
396
|
+
return !PACKAGE_FORBIDDEN_HEADER_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
397
|
+
});
|
|
398
|
+
return kept.length > 0 ? kept : undefined;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Map the host body to the shared package's count-tokens input.
|
|
403
|
+
*
|
|
404
|
+
* WHY THIS DELEGATES TO THE FULL MAPPER AND PICKS. The package declares
|
|
405
|
+
* `ClaudeCodeCountTokensInput = Pick<ClaudeCodeRequestInput, ...>` — literally
|
|
406
|
+
* the same field names carrying the same types — so every value the full mapper
|
|
407
|
+
* produces for a retained key is already built under the exact contract the
|
|
408
|
+
* count-tokens surface expects. Picking is therefore safe by construction, and
|
|
409
|
+
* it keeps the host tolerances that DO apply in one place instead of forking
|
|
410
|
+
* them: the dotted->dashed model rewrite, `tools` raising on a malformed value
|
|
411
|
+
* instead of silently dropping it, the stainless marker/transport passthrough,
|
|
412
|
+
* and the profile-override resolution all carry over unchanged.
|
|
413
|
+
*
|
|
414
|
+
* The tolerances that do NOT apply are dropped by the pick rather than reaching
|
|
415
|
+
* the package: upstream derives the count-tokens beta set from the model alone
|
|
416
|
+
* and the count body carries no `system`, `metadata` or `max_tokens`. `system`
|
|
417
|
+
* is stripped BEFORE delegating, because `normalizeSystem` throws on a
|
|
418
|
+
* malformed value and a field that is contractually absent from the count body
|
|
419
|
+
* must not be able to fail the build.
|
|
420
|
+
*
|
|
421
|
+
* @param {Record<string, import('@tormentalabs/claude-code-wire-compat').JsonValue>} body
|
|
422
|
+
* @param {{
|
|
423
|
+
* accessToken: string,
|
|
424
|
+
* clientRequestId: string,
|
|
425
|
+
* runtime: import('@tormentalabs/claude-code-wire-compat').ClaudeCodeRuntimeIdentity,
|
|
426
|
+
* app?: 'cli' | 'cli-bg',
|
|
427
|
+
* stainlessRetryCount?: number,
|
|
428
|
+
* stainlessHelper?: string,
|
|
429
|
+
* claudeRemoteContainerId?: string,
|
|
430
|
+
* claudeRemoteSessionId?: string,
|
|
431
|
+
* clientApp?: string,
|
|
432
|
+
* anthropicAdditionalProtection?: string,
|
|
433
|
+
* extraHeaders?: readonly import('@tormentalabs/claude-code-wire-compat').HeaderPair[],
|
|
434
|
+
* crypto?: import('@tormentalabs/claude-code-wire-compat').ClaudeCodeRequestInput['crypto'],
|
|
435
|
+
* profileOverride?: import('@tormentalabs/claude-code-wire-compat').ClaudeCodeProfileOverride,
|
|
436
|
+
* }} transport
|
|
437
|
+
* @returns {import('@tormentalabs/claude-code-wire-compat').ClaudeCodeCountTokensInput}
|
|
438
|
+
*/
|
|
439
|
+
export function toClaudeCodeCountTokensInput(body, transport) {
|
|
440
|
+
const { system: _system, ...bodyWithoutSystem } = body;
|
|
441
|
+
const full = toClaudeCodeRequestInput(bodyWithoutSystem, transport);
|
|
442
|
+
const input = Object.fromEntries(Object.entries(full).filter(([name]) => COUNT_TOKENS_INPUT_FIELDS.has(name)));
|
|
443
|
+
const extraHeaders = dropConflictingExtraHeaders(input.extraHeaders);
|
|
444
|
+
if (extraHeaders === undefined) delete input.extraHeaders;
|
|
445
|
+
else input.extraHeaders = extraHeaders;
|
|
446
|
+
if (transport.crypto !== undefined) input.crypto = transport.crypto;
|
|
447
|
+
return input;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Build a Claude Code `/v1/messages/count_tokens` request from the JSON body
|
|
452
|
+
* received from the host. Mirrors `buildWireCompatibleRequest`, including the
|
|
453
|
+
* deliberate absence of an explicit `profile` argument so the count turn
|
|
454
|
+
* inherits the package's `DEFAULT_PROFILE` exactly like the main turn does.
|
|
455
|
+
*
|
|
456
|
+
* @param {string | undefined} body
|
|
457
|
+
* @param {Parameters<typeof toClaudeCodeCountTokensInput>[1]} transport
|
|
458
|
+
* @returns {Promise<Omit<import('@tormentalabs/claude-code-wire-compat').BuiltClaudeCodeCountTokensRequest, 'headers'> & {headers: Headers}>}
|
|
459
|
+
*/
|
|
460
|
+
export async function buildWireCompatibleCountTokensRequest(body, transport) {
|
|
461
|
+
if (!body || typeof body !== "string") throw new TypeError("Expected a JSON request body");
|
|
462
|
+
const parsed = JSON.parse(body);
|
|
463
|
+
// Same rationale as the main turn: `buildAdapterTransport` already derived
|
|
464
|
+
// `transport.stainlessHelper` from these markers, the API has never known the
|
|
465
|
+
// keys, and the package rejects them with INVALID_INPUT. `parsed` is a local
|
|
466
|
+
// object owned by this call, so the strip is in place.
|
|
467
|
+
stripStainlessHelperMarkers(parsed?.tools, parsed?.messages);
|
|
468
|
+
let built;
|
|
469
|
+
try {
|
|
470
|
+
built = await buildClaudeCodeCountTokensRequest(toClaudeCodeCountTokensInput(parsed, transport));
|
|
471
|
+
} catch (error) {
|
|
472
|
+
throw foldSafeDetailsIntoMessage(error);
|
|
473
|
+
}
|
|
474
|
+
return { ...built, headers: new Headers(built.headers) };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* The package deliberately sanitizes its errors down to a bare code, and carries
|
|
479
|
+
* the useful part in `safeDetails`, which nothing surfaces. Fold it into the
|
|
480
|
+
* message so a rejection is diagnosable from a stack trace alone. The error
|
|
481
|
+
* object itself is preserved, so `code`, `safeDetails` and `instanceof` all
|
|
482
|
+
* still hold for callers that inspect them.
|
|
483
|
+
*
|
|
484
|
+
* @param {unknown} error
|
|
485
|
+
* @returns {unknown}
|
|
486
|
+
*/
|
|
487
|
+
function foldSafeDetailsIntoMessage(error) {
|
|
488
|
+
if (error instanceof Error && "safeDetails" in error) {
|
|
489
|
+
const detail = Object.entries(error.safeDetails ?? {})
|
|
490
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
491
|
+
.join(" ");
|
|
492
|
+
if (detail) error.message = `${error.message} (${detail})`;
|
|
493
|
+
}
|
|
494
|
+
return error;
|
|
495
|
+
}
|
package/lib/request-headers.mjs
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
// Version / build / SDK constants
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// Tracks the CLI version of the wire-compat package's DEFAULT_PROFILE (see
|
|
20
|
+
// lib/mimicry/adapter-input.mjs PROFILE_CLI_VERSION). Bump both together when
|
|
21
|
+
// the package ships a newer default profile.
|
|
22
|
+
export const FALLBACK_CLAUDE_CLI_VERSION = "2.1.233";
|
|
20
23
|
export const CLAUDE_CODE_NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
|
|
21
24
|
// Real build markers extracted from the 2.1.195 native binary
|
|
22
25
|
// (Bun-embedded JS): `BUILD_TIME:"2026-06-26T01:00:56Z"`,
|
|
@@ -38,6 +41,11 @@ export const ANTHROPIC_SDK_VERSION = "0.94.0";
|
|
|
38
41
|
|
|
39
42
|
// Map of CLI version to bundled SDK version (update when CLI version changes)
|
|
40
43
|
export const CLI_TO_SDK_VERSION = new Map([
|
|
44
|
+
// 2.1.233: the wire-compat package's DEFAULT_PROFILE
|
|
45
|
+
// (`claude-code-2.1.233-sdk-0.112.1`) declares sdkVersion 0.112.1, so the
|
|
46
|
+
// legacy header path stays consistent with the adapter path when the tracked
|
|
47
|
+
// CLI version is the profile baseline.
|
|
48
|
+
["2.1.233", "0.112.1"],
|
|
41
49
|
// 2.1.160-2.1.195: refusal-fallback feature (server-side-fallback /
|
|
42
50
|
// fallback-credit betas) is opt-in/GrowthBook-gated and never emitted on a
|
|
43
51
|
// default /v1/messages turn; no wire SDK bump (binary-verified:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-anthropic-fix",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "GPL-3.0-or-later",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
"vitest": "^4.0.18"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@tormentalabs/claude-code-wire-compat": "
|
|
58
|
+
"@tormentalabs/claude-code-wire-compat": "latest"
|
|
59
59
|
}
|
|
60
60
|
}
|