opencode-anthropic-fix 1.0.0 → 2.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/cli.mjs +77 -24
- package/index.mjs +228 -41
- package/lib/account-state.mjs +48 -0
- package/lib/accounts.mjs +241 -12
- package/lib/betas.mjs +1 -1
- package/lib/config.mjs +90 -12
- package/lib/diagnose.mjs +8 -1
- package/lib/mimicry/adapter-input.mjs +182 -18
- package/lib/mimicry/headers.mjs +1 -1
- package/lib/mimicry/response-stream.mjs +816 -112
- package/lib/mimicry/system-prompt.mjs +20 -10
- package/lib/mimicry/wire-compat.mjs +132 -15
- package/lib/oauth-constants.mjs +109 -0
- package/lib/oauth-env.mjs +187 -0
- package/lib/oauth-scopes.mjs +119 -0
- package/lib/oauth.mjs +270 -91
- package/lib/rolling-summarizer.mjs +20 -5
- package/lib/token-economy/microcompact.mjs +3 -12
- package/lib/unicode-text.mjs +111 -0
- package/package.json +3 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grapheme-safe text truncation.
|
|
3
|
+
*
|
|
4
|
+
* Every plugin-side text budget that ends up on the wire (system prompt
|
|
5
|
+
* compaction, rolling-summary sections/envelope, and any future caller) has
|
|
6
|
+
* historically been enforced with `String.prototype.slice(0, N)`. `slice`
|
|
7
|
+
* counts UTF-16 code units and has no notion of "user-perceived character":
|
|
8
|
+
* it can cut a surrogate pair in half (producing a lone surrogate, which
|
|
9
|
+
* `@tormentalabs/claude-code-wire-compat` rejects with `INVALID_UNICODE`) or
|
|
10
|
+
* cut a multi-codepoint grapheme cluster in half (producing well-formed
|
|
11
|
+
* UTF-16 that nonetheless renders as a broken glyph — a dangling combining
|
|
12
|
+
* mark, half a ZWJ sequence, half a flag, a skin-tone modifier with no base
|
|
13
|
+
* emoji, and so on).
|
|
14
|
+
*
|
|
15
|
+
* `truncateGraphemes` is the single, pure helper that replaces those
|
|
16
|
+
* `slice(0, N)` call sites. It only ever cuts on a grapheme-cluster boundary
|
|
17
|
+
* (per `Intl.Segmenter` with `granularity: "grapheme"`) and never emits a
|
|
18
|
+
* lone surrogate or a split cluster.
|
|
19
|
+
*
|
|
20
|
+
* Budget unit: UTF-16 code units — i.e. `String.prototype.length`. This is
|
|
21
|
+
* the same unit the `slice(0, N)` call sites it replaces already used, so
|
|
22
|
+
* swapping in this helper with the same `N` preserves the legacy budget
|
|
23
|
+
* semantics (it does NOT switch callers to a UTF-8 byte budget or a visual
|
|
24
|
+
* column budget; a caller that needs one of those must convert before and
|
|
25
|
+
* after calling in).
|
|
26
|
+
*
|
|
27
|
+
* @module lib/unicode-text
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Return the longest prefix of `text` that:
|
|
32
|
+
* - ends exactly on a grapheme-cluster boundary (never splits a surrogate
|
|
33
|
+
* pair and never splits a base character from a combining mark, ZWJ
|
|
34
|
+
* sequence, flag, or emoji modifier);
|
|
35
|
+
* - together with `options.suffix` appended, has a UTF-16 length
|
|
36
|
+
* (`.length`) less than or equal to `maxUnits`.
|
|
37
|
+
*
|
|
38
|
+
* Edge-case contract (all explicit; the function never throws):
|
|
39
|
+
* - `text === ""` -> `""`.
|
|
40
|
+
* - `maxUnits` is `NaN`, or not a number at all -> treated as `0` (the
|
|
41
|
+
* most conservative reading: if the budget itself is meaningless,
|
|
42
|
+
* nothing is guaranteed to fit).
|
|
43
|
+
* - `maxUnits < 0` -> treated as `0`.
|
|
44
|
+
* - `maxUnits === Infinity` -> `text` is returned unchanged; there is no
|
|
45
|
+
* ceiling to cut against.
|
|
46
|
+
* - `text.length <= maxUnits` -> `text` is returned unchanged, WITHOUT the
|
|
47
|
+
* suffix appended. Nothing was truncated, so nothing marks that it was —
|
|
48
|
+
* this matches the legacy call sites, where the suffix only ever showed
|
|
49
|
+
* up when a cut actually happened.
|
|
50
|
+
* - The very first grapheme cluster of `text` is already wider than the
|
|
51
|
+
* room left for the prefix (`maxUnits - suffix.length`) -> the prefix is
|
|
52
|
+
* `""`. The cluster is NEVER split to make it fit; if `options.suffix`
|
|
53
|
+
* itself fits within `maxUnits` on its own it is still returned alone,
|
|
54
|
+
* otherwise the overall result is `""`.
|
|
55
|
+
* - Long combining-mark sequences, ZWJ emoji, flags, and skin-tone
|
|
56
|
+
* modifiers are each a single grapheme cluster to `Intl.Segmenter` and
|
|
57
|
+
* are therefore included or excluded as one indivisible unit, however
|
|
58
|
+
* many UTF-16 units or code points they span.
|
|
59
|
+
*
|
|
60
|
+
* Performance: `text` is segmented at most once. If `text.length <=
|
|
61
|
+
* maxUnits`, segmentation is skipped entirely (fast path) — no reason to pay
|
|
62
|
+
* for it when no truncation can possibly be needed. When truncation IS
|
|
63
|
+
* needed, the segments are walked forward exactly once, accumulating length
|
|
64
|
+
* until the next segment would exceed the budget; this is O(n) in the length
|
|
65
|
+
* of `text`, never quadratic, and always terminates because the walk is a
|
|
66
|
+
* single forward pass over a finite iterator.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} text
|
|
69
|
+
* @param {number} maxUnits - budget in UTF-16 code units (`.length`).
|
|
70
|
+
* @param {{suffix?: string}} [options]
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
export function truncateGraphemes(text, maxUnits, options) {
|
|
74
|
+
const suffix = options && typeof options.suffix === "string" ? options.suffix : "";
|
|
75
|
+
const input = typeof text === "string" ? text : String(text ?? "");
|
|
76
|
+
if (input.length === 0) return "";
|
|
77
|
+
|
|
78
|
+
// Normalize the budget: non-numbers and NaN are the most conservative
|
|
79
|
+
// reading (0); negative budgets clamp to 0; Infinity is left as-is and
|
|
80
|
+
// falls through the normal comparisons below (any finite length is <=
|
|
81
|
+
// Infinity, so it hits the "already fits" fast path further down).
|
|
82
|
+
let limit = maxUnits;
|
|
83
|
+
if (typeof limit !== "number" || Number.isNaN(limit)) {
|
|
84
|
+
limit = 0;
|
|
85
|
+
} else if (limit < 0) {
|
|
86
|
+
limit = 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fast path: already within budget. Skip segmenting entirely, and do NOT
|
|
90
|
+
// append the suffix — nothing was truncated.
|
|
91
|
+
if (input.length <= limit) return input;
|
|
92
|
+
|
|
93
|
+
const budgetForPrefix = limit - suffix.length;
|
|
94
|
+
if (budgetForPrefix <= 0) {
|
|
95
|
+
// No room for even one grapheme once the suffix is reserved. Return the
|
|
96
|
+
// suffix alone if it fits the whole budget by itself; otherwise "".
|
|
97
|
+
// Either way, no partial cluster is ever produced.
|
|
98
|
+
return suffix.length <= limit ? suffix : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
102
|
+
let prefix = "";
|
|
103
|
+
let used = 0;
|
|
104
|
+
for (const { segment } of segmenter.segment(input)) {
|
|
105
|
+
const segmentLength = segment.length;
|
|
106
|
+
if (used + segmentLength > budgetForPrefix) break;
|
|
107
|
+
prefix += segment;
|
|
108
|
+
used += segmentLength;
|
|
109
|
+
}
|
|
110
|
+
return prefix + suffix;
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-anthropic-fix",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"license": "GPL-3.0-or-later",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"install:link": "node scripts/install.mjs link",
|
|
33
33
|
"install:copy": "npm run build && node scripts/install.mjs copy",
|
|
34
34
|
"uninstall": "node scripts/install.mjs uninstall",
|
|
35
|
+
"sync:wire-compat": "node scripts/sync-wire-compat.mjs",
|
|
36
|
+
"check:wire-compat-drift": "node scripts/check-wire-compat-drift.mjs",
|
|
35
37
|
"prepare": "husky"
|
|
36
38
|
},
|
|
37
39
|
"lint-staged": {
|