@sahiljassal/opencode-anthropic-auth 2.5.0 → 2.6.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 +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/transform.js +89 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Additional behaviours:
|
|
|
40
40
|
|
|
41
41
|
- **System tail coalescing** — plugin-added system blocks beyond the primary prompt are merged into one block before placing the system anchor, preventing cache busts when block layout changes between requests
|
|
42
42
|
- **Trailing assistant strip** — assistant messages at the tail of the request are removed before forwarding (OAuth rejects assistant prefill)
|
|
43
|
+
- **Tool pair repair** — `/compact` and `/undo` can leave a `tool_use` and its `tool_result` matched by id but no longer adjacent. Orphaned `tool_result` blocks are dropped; orphaned `tool_use` blocks are never deleted (Anthropic rejects edits to `thinking`/`redacted_thinking` blocks in the latest assistant message) — instead a placeholder `tool_result` is synthesized to restore adjacency
|
|
43
44
|
- **Thinking block guard** — `thinking` and `redacted_thinking` blocks are excluded from cache anchor placement; messages containing only thinking blocks receive no `cache_control` (avoids Anthropic 400)
|
|
44
45
|
- **SSE retryable errors** — transient server errors (`api_error`, `overloaded_error`, `server_error`) emitted inside HTTP 200 streams are detected and thrown as connection-reset errors so OpenCode auto-retries
|
|
45
46
|
- **Buffered stream rewriting** — tool name stripping buffers partial `"name"` tokens across chunk boundaries to avoid corruption
|
package/dist/constants.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export declare const CODE_CALLBACK_URL = "https://platform.claude.com/oauth/code
|
|
|
7
7
|
export declare const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
8
8
|
export declare const OAUTH_SCOPES: string[];
|
|
9
9
|
export declare const TOOL_PREFIX = "mcp_";
|
|
10
|
+
/**
|
|
11
|
+
* Content for a synthesized tool_result whose real output was removed by a
|
|
12
|
+
* /compact or /undo summary insertion (see repairOrphanedToolPairs).
|
|
13
|
+
*/
|
|
14
|
+
export declare const TOOL_RESULT_PLACEHOLDER = "Tool result unavailable (removed during context compaction).";
|
|
10
15
|
/**
|
|
11
16
|
* Anthropic's cache lookback window size. Each explicit breakpoint scans at
|
|
12
17
|
* most this many content blocks backward (counting the breakpoint block as
|
package/dist/constants.js
CHANGED
|
@@ -14,6 +14,11 @@ export const OAUTH_SCOPES = [
|
|
|
14
14
|
'user:file_upload',
|
|
15
15
|
];
|
|
16
16
|
export const TOOL_PREFIX = 'mcp_';
|
|
17
|
+
/**
|
|
18
|
+
* Content for a synthesized tool_result whose real output was removed by a
|
|
19
|
+
* /compact or /undo summary insertion (see repairOrphanedToolPairs).
|
|
20
|
+
*/
|
|
21
|
+
export const TOOL_RESULT_PLACEHOLDER = 'Tool result unavailable (removed during context compaction).';
|
|
17
22
|
/**
|
|
18
23
|
* Anthropic's cache lookback window size. Each explicit breakpoint scans at
|
|
19
24
|
* most this many content blocks backward (counting the breakpoint block as
|
package/dist/transform.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ADAPTIVE_THINKING_MODEL_PATTERN, ANTHROPIC_CACHE_LOOKBACK_BLOCKS, CLAUDE_CODE_IDENTITY, OPENCODE_IDENTITY_PREFIX, PARAGRAPH_REMOVAL_ANCHORS, REQUIRED_BETAS, TEXT_REPLACEMENTS, TOOL_PREFIX, USER_AGENT, } from "./constants.js";
|
|
1
|
+
import { ADAPTIVE_THINKING_MODEL_PATTERN, ANTHROPIC_CACHE_LOOKBACK_BLOCKS, CLAUDE_CODE_IDENTITY, OPENCODE_IDENTITY_PREFIX, PARAGRAPH_REMOVAL_ANCHORS, REQUIRED_BETAS, TEXT_REPLACEMENTS, TOOL_PREFIX, TOOL_RESULT_PLACEHOLDER, USER_AGENT, } from "./constants.js";
|
|
2
2
|
function prefixName(name) {
|
|
3
3
|
return `${TOOL_PREFIX}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
4
4
|
}
|
|
@@ -499,60 +499,104 @@ function stripRestrictedSamplingParams(parsed) {
|
|
|
499
499
|
delete parsed.top_p;
|
|
500
500
|
delete parsed.top_k;
|
|
501
501
|
}
|
|
502
|
+
function toolUseIdOf(block) {
|
|
503
|
+
return isRecord(block) &&
|
|
504
|
+
block.type === 'tool_use' &&
|
|
505
|
+
typeof block.id === 'string'
|
|
506
|
+
? block.id
|
|
507
|
+
: undefined;
|
|
508
|
+
}
|
|
509
|
+
function toolResultIdOf(block) {
|
|
510
|
+
return isRecord(block) &&
|
|
511
|
+
block.type === 'tool_result' &&
|
|
512
|
+
typeof block.tool_use_id === 'string'
|
|
513
|
+
? block.tool_use_id
|
|
514
|
+
: undefined;
|
|
515
|
+
}
|
|
502
516
|
/**
|
|
503
|
-
*
|
|
504
|
-
* requires a tool_result to be the first
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
517
|
+
* Reconcile tool_use/tool_result adjacency broken by a /compact or /undo
|
|
518
|
+
* summary insertion. Anthropic requires a tool_result to be the first
|
|
519
|
+
* content in the message immediately following its tool_use, and rejects
|
|
520
|
+
* partial edits to an assistant message that holds thinking/redacted_thinking
|
|
521
|
+
* blocks ("thinking blocks in the latest assistant message cannot be
|
|
522
|
+
* modified") — so orphaned tool_use blocks can't simply be deleted. Two
|
|
523
|
+
* passes:
|
|
524
|
+
*
|
|
525
|
+
* 1. Remove tool_result blocks with no adjacent preceding tool_use (these
|
|
526
|
+
* only live in user turns, so no thinking block is affected).
|
|
527
|
+
* 2. Synthesize a placeholder tool_result, adjacent, for every tool_use
|
|
528
|
+
* that still lacks one — assistant message content is never rewritten.
|
|
508
529
|
*/
|
|
509
530
|
function repairOrphanedToolPairs(parsed) {
|
|
510
531
|
if (!Array.isArray(parsed.messages))
|
|
511
532
|
return;
|
|
512
533
|
const messages = parsed.messages;
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
for (const block of msg.content) {
|
|
519
|
-
if (!isRecord(block))
|
|
520
|
-
continue;
|
|
521
|
-
if (block.type === 'tool_use' &&
|
|
522
|
-
typeof block.id === 'string' &&
|
|
523
|
-
!useMsgIndex.has(block.id)) {
|
|
524
|
-
useMsgIndex.set(block.id, index);
|
|
525
|
-
}
|
|
526
|
-
else if (block.type === 'tool_result' &&
|
|
527
|
-
typeof block.tool_use_id === 'string' &&
|
|
528
|
-
!resultMsgIndex.has(block.tool_use_id)) {
|
|
529
|
-
resultMsgIndex.set(block.tool_use_id, index);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
const isAdjacentPair = (id) => {
|
|
534
|
-
const useIndex = useMsgIndex.get(id);
|
|
535
|
-
return useIndex !== undefined && resultMsgIndex.get(id) === useIndex + 1;
|
|
534
|
+
const hasAdjacentUse = (index, id) => {
|
|
535
|
+
const prev = messages[index - 1];
|
|
536
|
+
return (isRecord(prev) &&
|
|
537
|
+
Array.isArray(prev.content) &&
|
|
538
|
+
prev.content.some((block) => toolUseIdOf(block) === id));
|
|
536
539
|
};
|
|
537
|
-
|
|
540
|
+
const pass1 = messages.flatMap((msg, index) => {
|
|
538
541
|
if (!isRecord(msg) || !Array.isArray(msg.content))
|
|
539
|
-
return
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (block.type === 'tool_use' && typeof block.id === 'string') {
|
|
544
|
-
return isAdjacentPair(block.id) && useMsgIndex.get(block.id) === index;
|
|
545
|
-
}
|
|
546
|
-
if (block.type === 'tool_result' &&
|
|
547
|
-
typeof block.tool_use_id === 'string') {
|
|
548
|
-
return (isAdjacentPair(block.tool_use_id) &&
|
|
549
|
-
resultMsgIndex.get(block.tool_use_id) === index);
|
|
550
|
-
}
|
|
551
|
-
return true;
|
|
542
|
+
return [msg];
|
|
543
|
+
const filtered = msg.content.filter((block) => {
|
|
544
|
+
const resultId = toolResultIdOf(block);
|
|
545
|
+
return resultId === undefined || hasAdjacentUse(index, resultId);
|
|
552
546
|
});
|
|
553
|
-
msg.content
|
|
554
|
-
|
|
547
|
+
if (filtered.length === 0 && msg.content.length > 0)
|
|
548
|
+
return [];
|
|
549
|
+
return [
|
|
550
|
+
filtered.length === msg.content.length
|
|
551
|
+
? msg
|
|
552
|
+
: { ...msg, content: filtered },
|
|
553
|
+
];
|
|
555
554
|
});
|
|
555
|
+
const out = [];
|
|
556
|
+
for (let i = 0; i < pass1.length; i++) {
|
|
557
|
+
const msg = pass1[i];
|
|
558
|
+
out.push(msg);
|
|
559
|
+
if (!isRecord(msg) || !Array.isArray(msg.content))
|
|
560
|
+
continue;
|
|
561
|
+
const useIds = msg.content
|
|
562
|
+
.map(toolUseIdOf)
|
|
563
|
+
.filter((id) => id !== undefined);
|
|
564
|
+
if (useIds.length === 0)
|
|
565
|
+
continue;
|
|
566
|
+
const next = pass1[i + 1];
|
|
567
|
+
const presentIds = new Set(isRecord(next) && Array.isArray(next.content)
|
|
568
|
+
? next.content
|
|
569
|
+
.map(toolResultIdOf)
|
|
570
|
+
.filter((id) => id !== undefined)
|
|
571
|
+
: []);
|
|
572
|
+
const missing = useIds.filter((id) => !presentIds.has(id));
|
|
573
|
+
if (missing.length === 0)
|
|
574
|
+
continue;
|
|
575
|
+
const synthetic = missing.map((id) => ({
|
|
576
|
+
type: 'tool_result',
|
|
577
|
+
tool_use_id: id,
|
|
578
|
+
content: TOOL_RESULT_PLACEHOLDER,
|
|
579
|
+
is_error: true,
|
|
580
|
+
}));
|
|
581
|
+
if (isRecord(next) && next.role === 'user' && Array.isArray(next.content)) {
|
|
582
|
+
out.push({ ...next, content: [...synthetic, ...next.content] });
|
|
583
|
+
i++;
|
|
584
|
+
}
|
|
585
|
+
else if (isRecord(next) &&
|
|
586
|
+
next.role === 'user' &&
|
|
587
|
+
typeof next.content === 'string') {
|
|
588
|
+
const text = next.content;
|
|
589
|
+
out.push({
|
|
590
|
+
...next,
|
|
591
|
+
content: text.length > 0 ? [...synthetic, { type: 'text', text }] : synthetic,
|
|
592
|
+
});
|
|
593
|
+
i++;
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
out.push({ role: 'user', content: synthetic });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
parsed.messages = out;
|
|
556
600
|
}
|
|
557
601
|
/**
|
|
558
602
|
* Anthropic requires tool_result blocks to precede any text in a message
|