opencode-anthropic-fix 0.1.32 → 0.2.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 +2 -0
- package/cli.mjs +42 -35
- package/index.mjs +399 -3591
- package/lib/config.test.mjs +298 -1
- package/lib/diagnose.mjs +184 -0
- package/lib/diagnose.test.mjs +122 -0
- package/lib/env.mjs +19 -0
- package/lib/env.test.mjs +25 -0
- package/lib/mimicry/cache.mjs +62 -0
- package/lib/mimicry/cache.test.mjs +112 -0
- package/lib/mimicry/headers.mjs +530 -0
- package/lib/mimicry/headers.test.mjs +50 -0
- package/lib/mimicry/models.mjs +146 -0
- package/lib/mimicry/models.test.mjs +132 -0
- package/lib/mimicry/request-body.mjs +684 -0
- package/lib/mimicry/request-body.test.mjs +34 -0
- package/lib/mimicry/request-helpers.mjs +243 -0
- package/lib/mimicry/request-helpers.test.mjs +155 -0
- package/lib/mimicry/response-stream.mjs +429 -0
- package/lib/mimicry/response-stream.test.mjs +127 -0
- package/lib/mimicry/system-prompt.mjs +638 -0
- package/lib/mimicry/system-prompt.test.mjs +73 -0
- package/lib/redact.mjs +147 -0
- package/lib/redact.test.mjs +154 -0
- package/lib/refresh-lock.test.mjs +52 -0
- package/lib/retry/overload-loop.mjs +83 -0
- package/lib/retry/overload-loop.test.mjs +107 -0
- package/lib/session-metrics.mjs +75 -0
- package/lib/session-metrics.test.mjs +67 -0
- package/lib/storage.test.mjs +82 -0
- package/lib/token-economy/microcompact.mjs +35 -0
- package/lib/token-economy/microcompact.test.mjs +28 -0
- package/lib/token-economy/transforms.mjs +637 -0
- package/lib/token-economy/transforms.test.mjs +192 -0
- package/lib/tuning.mjs +70 -0
- package/lib/tuning.test.mjs +89 -0
- package/package.json +4 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
applyAdaptiveThinkingZero,
|
|
4
|
+
applyContextHintCompaction,
|
|
5
|
+
applySessionToolResultDedupe,
|
|
6
|
+
applyStableToolOrdering,
|
|
7
|
+
applyToolResultDedupe,
|
|
8
|
+
applyToolSchemaDeferral,
|
|
9
|
+
applyTrailingSummaryTrim,
|
|
10
|
+
estimatePromptTokensFromParsed,
|
|
11
|
+
injectTokenBudgetBlock,
|
|
12
|
+
parseNaturalLanguageBudget,
|
|
13
|
+
} from "./transforms.mjs";
|
|
14
|
+
|
|
15
|
+
describe("token-economy transforms", () => {
|
|
16
|
+
it.each([
|
|
17
|
+
["Use 1 million tokens", 1_000_000],
|
|
18
|
+
["budget: 500k", 500_000],
|
|
19
|
+
["spend 1200", 1_200],
|
|
20
|
+
])("parses natural-language budget %s", (content, expected) => {
|
|
21
|
+
expect(parseNaturalLanguageBudget([{ role: "user", content }])).toBe(expected);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("injects a token budget system block", () => {
|
|
25
|
+
const existing = { type: "text", text: "Existing prompt" };
|
|
26
|
+
const result = injectTokenBudgetBlock([existing], { limit: 10_000, used: 2_500, continuations: 1 }, 0.9);
|
|
27
|
+
|
|
28
|
+
expect(result).toHaveLength(2);
|
|
29
|
+
expect(result[0]).toEqual({
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Token budget: ${(2_500).toLocaleString()}/${(10_000).toLocaleString()} tokens used (25%). Stop generating at ${(9_000).toLocaleString()} tokens. Remaining: ${(7_500).toLocaleString()} tokens.`,
|
|
32
|
+
});
|
|
33
|
+
expect(result[1]).toBe(existing);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("estimates a plausible prompt token count from a parsed body", () => {
|
|
37
|
+
const result = estimatePromptTokensFromParsed({
|
|
38
|
+
system: [{ type: "text", text: "12345678" }],
|
|
39
|
+
messages: [{ role: "user", content: "12345678" }],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(result).toBe(4);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("dedupes repeated reproducible results while preserving stateful results", () => {
|
|
46
|
+
const messages = [
|
|
47
|
+
{
|
|
48
|
+
role: "assistant",
|
|
49
|
+
content: [
|
|
50
|
+
{ type: "tool_use", id: "read-1", name: "read", input: { path: "a.mjs" } },
|
|
51
|
+
{ type: "tool_use", id: "bash-1", name: "bash", input: { command: "date" } },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
role: "user",
|
|
56
|
+
content: [
|
|
57
|
+
{ type: "tool_result", tool_use_id: "read-1", content: "old read" },
|
|
58
|
+
{ type: "tool_result", tool_use_id: "bash-1", content: "old bash" },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
role: "assistant",
|
|
63
|
+
content: [
|
|
64
|
+
{ type: "tool_use", id: "read-2", name: "read", input: { path: "a.mjs" } },
|
|
65
|
+
{ type: "tool_use", id: "bash-2", name: "bash", input: { command: "date" } },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
role: "user",
|
|
70
|
+
content: [
|
|
71
|
+
{ type: "tool_result", tool_use_id: "read-2", content: "latest read" },
|
|
72
|
+
{ type: "tool_result", tool_use_id: "bash-2", content: "latest bash" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const result = applySessionToolResultDedupe(messages);
|
|
78
|
+
|
|
79
|
+
expect(result.changed).toBe(true);
|
|
80
|
+
expect(result.stats.deduped).toBe(1);
|
|
81
|
+
expect(result.messages[1].content[0].content).toBe('[Read of {"path":"a.mjs"} superseded by later read at msg #3]');
|
|
82
|
+
expect(result.messages[3].content[0].content).toBe("latest read");
|
|
83
|
+
expect(result.messages[1].content[1].content).toBe("old bash");
|
|
84
|
+
expect(result.messages[3].content[1].content).toBe("latest bash");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("clears thinking and old tool results while keeping the latest eight", () => {
|
|
88
|
+
const messages = [
|
|
89
|
+
{
|
|
90
|
+
role: "assistant",
|
|
91
|
+
content: [
|
|
92
|
+
{ type: "thinking", thinking: "secret" },
|
|
93
|
+
{ type: "text", text: "answer" },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
...Array.from({ length: 10 }, (_, i) => ({
|
|
97
|
+
role: "user",
|
|
98
|
+
content: [{ type: "tool_result", tool_use_id: `tool-${i}`, content: `result-${i}` }],
|
|
99
|
+
})),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const result = applyContextHintCompaction(messages);
|
|
103
|
+
|
|
104
|
+
expect(result.stats).toEqual({ thinkingCleared: 1, toolResultsCleared: 2 });
|
|
105
|
+
expect(result.messages[0].content).toEqual([{ type: "text", text: "answer" }]);
|
|
106
|
+
expect(result.messages[1].content[0].content).toBe("[Old tool result content cleared]");
|
|
107
|
+
expect(result.messages[2].content[0].content).toBe("[Old tool result content cleared]");
|
|
108
|
+
expect(result.messages[3].content[0].content).toBe("result-2");
|
|
109
|
+
expect(result.messages[10].content[0].content).toBe("result-9");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("trims a trailing summary block from a past assistant message", () => {
|
|
113
|
+
const summary = `Summary: ${"completed the requested implementation. ".repeat(3)}`;
|
|
114
|
+
const messages = [
|
|
115
|
+
{
|
|
116
|
+
role: "assistant",
|
|
117
|
+
content: [
|
|
118
|
+
{ type: "text", text: "Useful detail" },
|
|
119
|
+
{ type: "text", text: summary },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{ role: "user", content: "Continue" },
|
|
123
|
+
{ role: "assistant", content: [{ type: "text", text: "Current response" }] },
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const result = applyTrailingSummaryTrim(messages);
|
|
127
|
+
|
|
128
|
+
expect(result).toMatchObject({ changed: true, trimmed: 1 });
|
|
129
|
+
expect(result.messages[0].content).toEqual([{ type: "text", text: "Useful detail" }]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("dedupes a repeated safe tool result", () => {
|
|
133
|
+
const messages = [
|
|
134
|
+
{
|
|
135
|
+
role: "assistant",
|
|
136
|
+
content: [
|
|
137
|
+
{ type: "tool_use", id: "first", name: "Read", input: { file: "a" } },
|
|
138
|
+
{ type: "tool_use", id: "second", name: "Read", input: { file: "a" } },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
role: "user",
|
|
143
|
+
content: [
|
|
144
|
+
{ type: "tool_result", tool_use_id: "first", content: "one" },
|
|
145
|
+
{ type: "tool_result", tool_use_id: "second", content: "two" },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const result = applyToolResultDedupe(messages, { seen: new Map(), safeTools: new Set(["Read"]) });
|
|
151
|
+
|
|
152
|
+
expect(result).toMatchObject({ changed: true, deduped: 1 });
|
|
153
|
+
expect(result.messages[1].content[1].content).toBe("[Identical to tool_use_id=first]");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("orders tools stably by name", () => {
|
|
157
|
+
const tools = [{ name: "write" }, { name: "grep" }, { name: "read" }];
|
|
158
|
+
expect(applyStableToolOrdering(tools).map((tool) => tool.name)).toEqual(["grep", "read", "write"]);
|
|
159
|
+
expect(tools.map((tool) => tool.name)).toEqual(["write", "grep", "read"]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("defers schemas only for uninvoked configured tools", () => {
|
|
163
|
+
const tools = [
|
|
164
|
+
{ name: "read", input_schema: { type: "object", required: ["path"] } },
|
|
165
|
+
{ name: "grep", input_schema: { type: "object", required: ["pattern"] } },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const result = applyToolSchemaDeferral(tools, {
|
|
169
|
+
deferred: new Set(["read", "grep"]),
|
|
170
|
+
invoked: new Set(["grep"]),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result.deferredCount).toBe(1);
|
|
174
|
+
expect(result.tools[0].input_schema).toEqual({ type: "object", properties: {}, additionalProperties: true });
|
|
175
|
+
expect(result.tools[1]).toBe(tools[1]);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("disables thinking for a simple follow-up", () => {
|
|
179
|
+
const parsed = {
|
|
180
|
+
thinking: { type: "enabled", budget_tokens: 4_096 },
|
|
181
|
+
messages: [
|
|
182
|
+
{ role: "user", content: "Initial request" },
|
|
183
|
+
{ role: "assistant", content: "Done" },
|
|
184
|
+
{ role: "user", content: "Thanks" },
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
expect(applyAdaptiveThinkingZero(parsed)).toEqual({ applied: true, previousBudget: 4_096 });
|
|
189
|
+
expect(parsed).not.toHaveProperty("thinking");
|
|
190
|
+
expect(parsed.temperature).toBe(1);
|
|
191
|
+
});
|
|
192
|
+
});
|
package/lib/tuning.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized tuning values for retry / overload-recovery behavior, wired into
|
|
3
|
+
* runtime code as of Wave 3. The service-wide retry budget and the 529 fallback
|
|
4
|
+
* threshold are consumed directly in index.mjs; the base-delay, backoff, max-delay
|
|
5
|
+
* and jitter constants are consumed by the pure decision helpers in
|
|
6
|
+
* lib/retry/overload-loop.mjs. The OAuth token-refresh timeout and the
|
|
7
|
+
* foreground-refresh expiry buffer remain inline in index.mjs (documented below).
|
|
8
|
+
*
|
|
9
|
+
* Constants that are already centralized remain in their existing homes:
|
|
10
|
+
* retry cooldown constants live in lib/backoff.mjs, while account-selection
|
|
11
|
+
* stickiness and switch thresholds live in lib/rotation.mjs.
|
|
12
|
+
*
|
|
13
|
+
* @module tuning
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Bounds foreground service-wide retries so overload recovery cannot loop indefinitely.
|
|
18
|
+
* Consumed at the index.mjs maxServiceRetries budget (foreground branch).
|
|
19
|
+
* @see lib/retry/overload-loop.mjs
|
|
20
|
+
*/
|
|
21
|
+
export const SERVICE_WIDE_MAX_RETRIES = 2;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Delays model fallback until repeated overloads show that retrying the current model is ineffective.
|
|
25
|
+
* Consumed at the index.mjs consecutive-529 model-fallback gate.
|
|
26
|
+
* @see lib/retry/overload-loop.mjs
|
|
27
|
+
*/
|
|
28
|
+
export const CONSECUTIVE_529_FALLBACK_THRESHOLD = 3;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Starts service retries with a short delay to recover quickly from transient overloads.
|
|
32
|
+
* Consumed by computeServiceRetrySleepMs in lib/retry/overload-loop.mjs.
|
|
33
|
+
* @see lib/retry/overload-loop.mjs
|
|
34
|
+
*/
|
|
35
|
+
export const SERVICE_RETRY_BASE_DELAY_SEC = 0.5;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Doubles each service-retry delay to reduce pressure during a sustained outage.
|
|
39
|
+
* Consumed by computeServiceRetrySleepMs in lib/retry/overload-loop.mjs.
|
|
40
|
+
* @see lib/retry/overload-loop.mjs
|
|
41
|
+
*/
|
|
42
|
+
export const SERVICE_RETRY_BACKOFF_MULTIPLIER = 2;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Caps service-retry latency so overload handling remains responsive.
|
|
46
|
+
* Consumed by computeServiceRetrySleepMs in lib/retry/overload-loop.mjs.
|
|
47
|
+
* @see lib/retry/overload-loop.mjs
|
|
48
|
+
*/
|
|
49
|
+
export const SERVICE_RETRY_MAX_DELAY_SEC = 3;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Spreads service retries across clients while retaining most of the calculated delay.
|
|
53
|
+
* Consumed by computeServiceRetrySleepMs in lib/retry/overload-loop.mjs.
|
|
54
|
+
* @see lib/retry/overload-loop.mjs
|
|
55
|
+
*/
|
|
56
|
+
export const SERVICE_RETRY_JITTER_FRACTION = 0.25;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prevents an unresponsive OAuth refresh request from blocking account recovery indefinitely.
|
|
60
|
+
* Current call-site: index.mjs (inline OAuth refresh AbortSignal.timeout).
|
|
61
|
+
* @see test/conformance/regression.test.mjs Fix #12
|
|
62
|
+
*/
|
|
63
|
+
export const TOKEN_REFRESH_TIMEOUT_MS = 15000;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Refreshes foreground credentials early enough to avoid token expiry during an active request.
|
|
67
|
+
* Current call-site: index.mjs (inline foreground refresh expiry check).
|
|
68
|
+
* @see test/conformance/regression.test.mjs Fix #13
|
|
69
|
+
*/
|
|
70
|
+
export const FOREGROUND_REFRESH_EXPIRY_BUFFER_MS = 300000;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
import * as tuning from "./tuning.mjs";
|
|
5
|
+
|
|
6
|
+
const indexSource = readFileSync(new URL("../index.mjs", import.meta.url), "utf8");
|
|
7
|
+
|
|
8
|
+
const expectedValues = {
|
|
9
|
+
SERVICE_WIDE_MAX_RETRIES: 2,
|
|
10
|
+
CONSECUTIVE_529_FALLBACK_THRESHOLD: 3,
|
|
11
|
+
SERVICE_RETRY_BASE_DELAY_SEC: 0.5,
|
|
12
|
+
SERVICE_RETRY_BACKOFF_MULTIPLIER: 2,
|
|
13
|
+
SERVICE_RETRY_MAX_DELAY_SEC: 3,
|
|
14
|
+
SERVICE_RETRY_JITTER_FRACTION: 0.25,
|
|
15
|
+
TOKEN_REFRESH_TIMEOUT_MS: 15000,
|
|
16
|
+
FOREGROUND_REFRESH_EXPIRY_BUFFER_MS: 300000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {RegExp} pattern
|
|
21
|
+
* @param {number} [group]
|
|
22
|
+
* @returns {number}
|
|
23
|
+
*/
|
|
24
|
+
function extractNumber(pattern, group = 1) {
|
|
25
|
+
const match = indexSource.match(pattern);
|
|
26
|
+
expect(match, `Expected index.mjs to match ${pattern}`).not.toBeNull();
|
|
27
|
+
return Number(match[group].replaceAll("_", ""));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("tuning constants", () => {
|
|
31
|
+
it("exports every documented numeric value", () => {
|
|
32
|
+
for (const [name, expected] of Object.entries(expectedValues)) {
|
|
33
|
+
expect(tuning).toHaveProperty(name);
|
|
34
|
+
expect(typeof tuning[name]).toBe("number");
|
|
35
|
+
expect(tuning[name]).toBe(expected);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("wires the service retry budget and fallback threshold to tuning constants", () => {
|
|
40
|
+
// Wave 3 P3.2 replaced the inline literals with imported constant references.
|
|
41
|
+
expect(indexSource).toMatch(
|
|
42
|
+
/const\s+maxServiceRetries\s*=\s*requestClass\s*===\s*"background"[\s\S]*?:\s*SERVICE_WIDE_MAX_RETRIES\s*;/,
|
|
43
|
+
);
|
|
44
|
+
expect(indexSource).toMatch(/consecutive529Count\s*>=\s*CONSECUTIVE_529_FALLBACK_THRESHOLD/);
|
|
45
|
+
expect(indexSource).toMatch(
|
|
46
|
+
/import\s*\{[^}]*\bSERVICE_WIDE_MAX_RETRIES\b[^}]*\bCONSECUTIVE_529_FALLBACK_THRESHOLD\b[^}]*\}\s*from\s*"\.\/lib\/tuning\.mjs"/,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("consumes the service retry delay constants in the overload-loop helper", () => {
|
|
51
|
+
// The Math.min/jitter delay formula moved out of index.mjs into this helper (P3.2).
|
|
52
|
+
const overloadSource = readFileSync(new URL("./retry/overload-loop.mjs", import.meta.url), "utf8");
|
|
53
|
+
for (const name of [
|
|
54
|
+
"SERVICE_RETRY_BASE_DELAY_SEC",
|
|
55
|
+
"SERVICE_RETRY_BACKOFF_MULTIPLIER",
|
|
56
|
+
"SERVICE_RETRY_MAX_DELAY_SEC",
|
|
57
|
+
"SERVICE_RETRY_JITTER_FRACTION",
|
|
58
|
+
]) {
|
|
59
|
+
expect(overloadSource, `overload-loop.mjs must consume ${name}`).toContain(name);
|
|
60
|
+
}
|
|
61
|
+
expect(overloadSource).toMatch(/from\s*"\.\.\/tuning\.mjs"/);
|
|
62
|
+
// The inline delay formula must no longer live in index.mjs.
|
|
63
|
+
expect(indexSource).not.toMatch(/1\s*-\s*Math\.random\(\)\s*\*\s*0\.25/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("matches the inline token refresh timeout and foreground expiry buffer", () => {
|
|
67
|
+
const tokenRefreshTimeout = extractNumber(
|
|
68
|
+
/refreshToken\(\s*account\.refreshToken\s*,\s*\{\s*signal:\s*AbortSignal\.timeout\(\s*(\d[\d_]*)\s*\)\s*\}\s*\)/,
|
|
69
|
+
);
|
|
70
|
+
const foregroundExpiryBuffer = extractNumber(/account\.expires\s*<\s*Date\.now\(\)\s*\+\s*(\d[\d_]*)/);
|
|
71
|
+
|
|
72
|
+
expect(tokenRefreshTimeout).toBe(tuning.TOKEN_REFRESH_TIMEOUT_MS);
|
|
73
|
+
expect(foregroundExpiryBuffer).toBe(tuning.FOREGROUND_REFRESH_EXPIRY_BUFFER_MS);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("gives every exported constant an adjacent JSDoc @see tag", () => {
|
|
77
|
+
const tuningSource = readFileSync(new URL("./tuning.mjs", import.meta.url), "utf8");
|
|
78
|
+
const exports = [...tuningSource.matchAll(/export const\s+([A-Z0-9_]+)\s*=/g)];
|
|
79
|
+
|
|
80
|
+
expect(exports.map((match) => match[1])).toEqual(Object.keys(expectedValues));
|
|
81
|
+
for (const exported of exports) {
|
|
82
|
+
const prefix = tuningSource.slice(0, exported.index);
|
|
83
|
+
const adjacentJSDoc = prefix.slice(prefix.lastIndexOf("/**"));
|
|
84
|
+
expect(adjacentJSDoc, `${exported[1]} must have an adjacent JSDoc @see tag`).toMatch(
|
|
85
|
+
/^\/\*\*[\s\S]*?@see\s+\S[^\n]*[\s\S]*?\*\/\s*$/,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-anthropic-fix",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "GPL-3.0-or-later",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "node scripts/build.mjs",
|
|
23
|
+
"check:invariants": "node scripts/check-invariants.mjs",
|
|
23
24
|
"test": "vitest run",
|
|
24
25
|
"test:watch": "vitest",
|
|
26
|
+
"coverage": "vitest run --coverage",
|
|
25
27
|
"lint": "eslint .",
|
|
26
28
|
"lint:fix": "eslint --fix .",
|
|
27
29
|
"format": "prettier --write .",
|
|
@@ -43,6 +45,7 @@
|
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@eslint/js": "^10.0.1",
|
|
45
47
|
"@opencode-ai/plugin": "^1.2.27",
|
|
48
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
46
49
|
"esbuild": "^0.27.3",
|
|
47
50
|
"eslint": "^10.0.0",
|
|
48
51
|
"husky": "^9.1.7",
|