@vacbo/opencode-anthropic-fix 0.0.44 → 0.1.1
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 +19 -0
- package/dist/bun-proxy.mjs +282 -55
- package/dist/opencode-anthropic-auth-cli.mjs +194 -55
- package/dist/opencode-anthropic-auth-plugin.js +1816 -594
- package/package.json +1 -1
- package/src/__tests__/billing-edge-cases.test.ts +84 -0
- package/src/__tests__/bun-proxy.parallel.test.ts +460 -0
- package/src/__tests__/debug-gating.test.ts +76 -0
- package/src/__tests__/decomposition-smoke.test.ts +92 -0
- package/src/__tests__/fingerprint-regression.test.ts +1 -1
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +338 -0
- package/src/__tests__/helpers/conversation-history.ts +376 -0
- package/src/__tests__/helpers/deferred.smoke.test.ts +161 -0
- package/src/__tests__/helpers/deferred.ts +122 -0
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +166 -0
- package/src/__tests__/helpers/in-memory-storage.ts +152 -0
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +92 -0
- package/src/__tests__/helpers/mock-bun-proxy.ts +229 -0
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +337 -0
- package/src/__tests__/helpers/plugin-fetch-harness.ts +401 -0
- package/src/__tests__/helpers/sse.smoke.test.ts +243 -0
- package/src/__tests__/helpers/sse.ts +288 -0
- package/src/__tests__/index.parallel.test.ts +711 -0
- package/src/__tests__/sanitization-regex.test.ts +65 -0
- package/src/__tests__/state-bounds.test.ts +110 -0
- package/src/account-identity.test.ts +213 -0
- package/src/account-identity.ts +108 -0
- package/src/accounts.dedup.test.ts +696 -0
- package/src/accounts.test.ts +2 -1
- package/src/accounts.ts +485 -191
- package/src/bun-fetch.test.ts +379 -0
- package/src/bun-fetch.ts +447 -174
- package/src/bun-proxy.ts +289 -57
- package/src/circuit-breaker.test.ts +274 -0
- package/src/circuit-breaker.ts +235 -0
- package/src/cli.test.ts +1 -0
- package/src/cli.ts +37 -18
- package/src/commands/router.ts +25 -5
- package/src/env.ts +1 -0
- package/src/headers/billing.ts +31 -13
- package/src/index.ts +224 -247
- package/src/oauth.ts +7 -1
- package/src/parent-pid-watcher.test.ts +219 -0
- package/src/parent-pid-watcher.ts +99 -0
- package/src/plugin-helpers.ts +112 -0
- package/src/refresh-helpers.ts +169 -0
- package/src/refresh-lock.test.ts +36 -9
- package/src/refresh-lock.ts +2 -2
- package/src/request/body.history.test.ts +398 -0
- package/src/request/body.ts +200 -13
- package/src/request/metadata.ts +6 -2
- package/src/response/index.ts +1 -1
- package/src/response/mcp.ts +60 -31
- package/src/response/streaming.test.ts +382 -0
- package/src/response/streaming.ts +403 -76
- package/src/storage.test.ts +127 -104
- package/src/storage.ts +152 -62
- package/src/system-prompt/builder.ts +33 -3
- package/src/system-prompt/sanitize.ts +12 -2
- package/src/token-refresh.test.ts +84 -1
- package/src/token-refresh.ts +14 -8
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decomposition smoke tests (Tasks 6-7 from quality-refactor plan)
|
|
3
|
+
*
|
|
4
|
+
* Verifies that extracted refresh-helpers and plugin-helpers modules
|
|
5
|
+
* export their factory functions and produce the expected API surface.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi } from "vitest";
|
|
9
|
+
|
|
10
|
+
import { DEFAULT_CONFIG } from "../config.js";
|
|
11
|
+
import { createRefreshHelpers } from "../refresh-helpers.js";
|
|
12
|
+
import { createPluginHelpers } from "../plugin-helpers.js";
|
|
13
|
+
|
|
14
|
+
describe("refresh-helpers module", () => {
|
|
15
|
+
it("exports createRefreshHelpers as a function", () => {
|
|
16
|
+
expect(typeof createRefreshHelpers).toBe("function");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("factory returns an object when called with valid deps", () => {
|
|
20
|
+
const stubClient = {
|
|
21
|
+
tui: { showToast: vi.fn() },
|
|
22
|
+
command: { prompt: vi.fn() },
|
|
23
|
+
session: { prompt: vi.fn() },
|
|
24
|
+
};
|
|
25
|
+
const helpers = createRefreshHelpers({
|
|
26
|
+
client: stubClient as any,
|
|
27
|
+
config: { ...DEFAULT_CONFIG } as any,
|
|
28
|
+
getAccountManager: () => null,
|
|
29
|
+
debugLog: vi.fn(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(helpers).toBeDefined();
|
|
33
|
+
expect(typeof helpers).toBe("object");
|
|
34
|
+
expect(helpers).not.toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("factory reads idle_refresh config fields at construction", () => {
|
|
38
|
+
const config = {
|
|
39
|
+
...DEFAULT_CONFIG,
|
|
40
|
+
idle_refresh: { enabled: false, window_minutes: 10, min_interval_minutes: 5 },
|
|
41
|
+
};
|
|
42
|
+
expect(() =>
|
|
43
|
+
createRefreshHelpers({
|
|
44
|
+
client: {} as any,
|
|
45
|
+
config: config as any,
|
|
46
|
+
getAccountManager: () => null,
|
|
47
|
+
debugLog: vi.fn(),
|
|
48
|
+
}),
|
|
49
|
+
).not.toThrow();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("plugin-helpers module", () => {
|
|
54
|
+
it("exports createPluginHelpers as a function", () => {
|
|
55
|
+
expect(typeof createPluginHelpers).toBe("function");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("factory returns an object when called with valid deps", () => {
|
|
59
|
+
const stubClient = {
|
|
60
|
+
tui: { showToast: vi.fn() },
|
|
61
|
+
command: { prompt: vi.fn() },
|
|
62
|
+
session: { prompt: vi.fn() },
|
|
63
|
+
};
|
|
64
|
+
const helpers = createPluginHelpers({
|
|
65
|
+
client: stubClient as any,
|
|
66
|
+
config: { ...DEFAULT_CONFIG } as any,
|
|
67
|
+
debugLog: vi.fn(),
|
|
68
|
+
getAccountManager: () => null,
|
|
69
|
+
setAccountManager: vi.fn(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(helpers).toBeDefined();
|
|
73
|
+
expect(typeof helpers).toBe("object");
|
|
74
|
+
expect(helpers).not.toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("factory accepts quiet toast config without throwing", () => {
|
|
78
|
+
const config = {
|
|
79
|
+
...DEFAULT_CONFIG,
|
|
80
|
+
toasts: { quiet: true, debounce_seconds: 60 },
|
|
81
|
+
};
|
|
82
|
+
expect(() =>
|
|
83
|
+
createPluginHelpers({
|
|
84
|
+
client: {} as any,
|
|
85
|
+
config: config as any,
|
|
86
|
+
debugLog: vi.fn(),
|
|
87
|
+
getAccountManager: () => null,
|
|
88
|
+
setAccountManager: vi.fn(),
|
|
89
|
+
}),
|
|
90
|
+
).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -207,7 +207,7 @@ describe("CC 2.1.98 — Billing header", () => {
|
|
|
207
207
|
const messages = [{ role: "user", content: "Hello world from a test" }];
|
|
208
208
|
const header = buildAnthropicBillingHeader(CC_VERSION, messages);
|
|
209
209
|
|
|
210
|
-
// Replicate the
|
|
210
|
+
// Replicate the current billing-header algorithm: SHA-256(salt + chars[4,7,20] + version)
|
|
211
211
|
const text = "Hello world from a test";
|
|
212
212
|
const salt = "59cf53e54c78";
|
|
213
213
|
const picked = [4, 7, 20].map((i) => (i < text.length ? text[i] : "0")).join("");
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests for conversation-history helper.
|
|
3
|
+
*
|
|
4
|
+
* Validates factory functions create valid Anthropic Messages API structures
|
|
5
|
+
* and that tool_use/tool_result pairing works correctly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it, beforeEach } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
makeConversation,
|
|
11
|
+
makeMessage,
|
|
12
|
+
makeToolUse,
|
|
13
|
+
makeToolResult,
|
|
14
|
+
makeTextBlock,
|
|
15
|
+
makeToolExchange,
|
|
16
|
+
makeToolConversation,
|
|
17
|
+
validateToolPair,
|
|
18
|
+
findToolResult,
|
|
19
|
+
validateConversationTools,
|
|
20
|
+
generateToolUseId,
|
|
21
|
+
resetIdCounter,
|
|
22
|
+
type Conversation,
|
|
23
|
+
type Message,
|
|
24
|
+
type ToolUseBlock,
|
|
25
|
+
type ToolResultBlock,
|
|
26
|
+
} from "./conversation-history.js";
|
|
27
|
+
|
|
28
|
+
describe("conversation-history factories", () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
resetIdCounter();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("makeConversation", () => {
|
|
34
|
+
it("creates empty conversation by default", () => {
|
|
35
|
+
const conv = makeConversation();
|
|
36
|
+
|
|
37
|
+
expect(conv.messages).toEqual([]);
|
|
38
|
+
expect(conv.metadata).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("creates conversation with messages", () => {
|
|
42
|
+
const messages = [makeMessage({ role: "user", content: "Hello" })];
|
|
43
|
+
const conv = makeConversation({ messages });
|
|
44
|
+
|
|
45
|
+
expect(conv.messages).toHaveLength(1);
|
|
46
|
+
expect(conv.messages[0].role).toBe("user");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("creates conversation with metadata", () => {
|
|
50
|
+
const conv = makeConversation({
|
|
51
|
+
metadata: { sessionId: "test-123", model: "claude-sonnet" },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(conv.metadata).toEqual({ sessionId: "test-123", model: "claude-sonnet" });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("makeMessage", () => {
|
|
59
|
+
it("creates user message by default", () => {
|
|
60
|
+
const msg = makeMessage();
|
|
61
|
+
|
|
62
|
+
expect(msg.role).toBe("user");
|
|
63
|
+
expect(msg.content).toBe("");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("creates message with string content", () => {
|
|
67
|
+
const msg = makeMessage({ role: "user", content: "Hello Claude" });
|
|
68
|
+
|
|
69
|
+
expect(msg.role).toBe("user");
|
|
70
|
+
expect(msg.content).toBe("Hello Claude");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("creates assistant message with content blocks", () => {
|
|
74
|
+
const msg = makeMessage({
|
|
75
|
+
role: "assistant",
|
|
76
|
+
content: [makeTextBlock("Hello!")],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(msg.role).toBe("assistant");
|
|
80
|
+
expect(Array.isArray(msg.content)).toBe(true);
|
|
81
|
+
expect((msg.content as Array<{ type: string }>)[0].type).toBe("text");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("makeTextBlock", () => {
|
|
86
|
+
it("creates text block with correct type", () => {
|
|
87
|
+
const block = makeTextBlock("Hello world");
|
|
88
|
+
|
|
89
|
+
expect(block.type).toBe("text");
|
|
90
|
+
expect(block.text).toBe("Hello world");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("makeToolUse", () => {
|
|
95
|
+
it("creates tool_use with auto-generated ID", () => {
|
|
96
|
+
const tool = makeToolUse();
|
|
97
|
+
|
|
98
|
+
expect(tool.type).toBe("tool_use");
|
|
99
|
+
expect(tool.id).toMatch(/^tu_[a-f0-9]+_\d+$/);
|
|
100
|
+
expect(tool.name).toBe("unnamed_tool");
|
|
101
|
+
expect(tool.input).toEqual({});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("creates tool_use with custom properties", () => {
|
|
105
|
+
const tool = makeToolUse({
|
|
106
|
+
id: "custom_id",
|
|
107
|
+
name: "read_file",
|
|
108
|
+
input: { path: "test.txt", offset: 0 },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(tool.id).toBe("custom_id");
|
|
112
|
+
expect(tool.name).toBe("read_file");
|
|
113
|
+
expect(tool.input).toEqual({ path: "test.txt", offset: 0 });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("generates unique IDs for multiple tools", () => {
|
|
117
|
+
const tool1 = makeToolUse();
|
|
118
|
+
const tool2 = makeToolUse();
|
|
119
|
+
|
|
120
|
+
expect(tool1.id).not.toBe(tool2.id);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("makeToolResult", () => {
|
|
125
|
+
it("creates tool_result with auto-generated tool_use_id", () => {
|
|
126
|
+
const result = makeToolResult();
|
|
127
|
+
|
|
128
|
+
expect(result.type).toBe("tool_result");
|
|
129
|
+
expect(result.tool_use_id).toMatch(/^tr_[a-f0-9]+_\d+$/);
|
|
130
|
+
expect(result.content).toBe("");
|
|
131
|
+
expect(result.is_error).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("creates tool_result with custom properties", () => {
|
|
135
|
+
const result = makeToolResult({
|
|
136
|
+
toolUseId: "tu_abc123",
|
|
137
|
+
content: "File contents here",
|
|
138
|
+
isError: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result.tool_use_id).toBe("tu_abc123");
|
|
142
|
+
expect(result.content).toBe("File contents here");
|
|
143
|
+
expect(result.is_error).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("creates tool_result with content blocks", () => {
|
|
147
|
+
const result = makeToolResult({
|
|
148
|
+
toolUseId: "tu_123",
|
|
149
|
+
content: [makeTextBlock("Result text")],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
153
|
+
expect((result.content as Array<{ type: string }>)[0].type).toBe("text");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("tool pairing validation", () => {
|
|
158
|
+
it("validates matching tool_use and tool_result pair", () => {
|
|
159
|
+
const toolUse = makeToolUse({ id: "tu_test123", name: "read_file" });
|
|
160
|
+
const toolResult = makeToolResult({ toolUseId: "tu_test123", content: "data" });
|
|
161
|
+
|
|
162
|
+
expect(validateToolPair(toolUse, toolResult)).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("rejects mismatched tool pair", () => {
|
|
166
|
+
const toolUse = makeToolUse({ id: "tu_abc" });
|
|
167
|
+
const toolResult = makeToolResult({ toolUseId: "tu_xyz" });
|
|
168
|
+
|
|
169
|
+
expect(validateToolPair(toolUse, toolResult)).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("finds tool_result by ID in message array", () => {
|
|
173
|
+
const toolUse = makeToolUse({ id: "tu_findme" });
|
|
174
|
+
const toolResult = makeToolResult({ toolUseId: "tu_findme", content: "found" });
|
|
175
|
+
|
|
176
|
+
const messages: Message[] = [
|
|
177
|
+
makeMessage({ role: "assistant", content: [toolUse] }),
|
|
178
|
+
makeMessage({ role: "user", content: [toolResult] }),
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const found = findToolResult(messages, "tu_findme");
|
|
182
|
+
expect(found).toBeDefined();
|
|
183
|
+
expect(found?.content).toBe("found");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("returns undefined when tool_result not found", () => {
|
|
187
|
+
const messages: Message[] = [makeMessage({ role: "user", content: "Hello" })];
|
|
188
|
+
|
|
189
|
+
const found = findToolResult(messages, "tu_missing");
|
|
190
|
+
expect(found).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("conversation tool validation", () => {
|
|
195
|
+
it("validates conversation with complete tool pairs", () => {
|
|
196
|
+
const toolUse = makeToolUse({ id: "tu_complete" });
|
|
197
|
+
const toolResult = makeToolResult({ toolUseId: "tu_complete" });
|
|
198
|
+
|
|
199
|
+
const conv = makeConversation({
|
|
200
|
+
messages: [
|
|
201
|
+
makeMessage({ role: "user", content: "Use tool" }),
|
|
202
|
+
makeMessage({ role: "assistant", content: [toolUse] }),
|
|
203
|
+
makeMessage({ role: "user", content: [toolResult] }),
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const validation = validateConversationTools(conv);
|
|
208
|
+
expect(validation.valid).toBe(true);
|
|
209
|
+
expect(validation.unmatchedToolUses).toHaveLength(0);
|
|
210
|
+
expect(validation.unmatchedToolResults).toHaveLength(0);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("detects unmatched tool_use blocks", () => {
|
|
214
|
+
const toolUse = makeToolUse({ id: "tu_unmatched" });
|
|
215
|
+
|
|
216
|
+
const conv = makeConversation({
|
|
217
|
+
messages: [makeMessage({ role: "assistant", content: [toolUse] })],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const validation = validateConversationTools(conv);
|
|
221
|
+
expect(validation.valid).toBe(false);
|
|
222
|
+
expect(validation.unmatchedToolUses).toHaveLength(1);
|
|
223
|
+
expect(validation.unmatchedToolResults).toHaveLength(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("detects unmatched tool_result blocks", () => {
|
|
227
|
+
const toolResult = makeToolResult({ toolUseId: "tu_missing" });
|
|
228
|
+
|
|
229
|
+
const conv = makeConversation({
|
|
230
|
+
messages: [makeMessage({ role: "user", content: [toolResult] })],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const validation = validateConversationTools(conv);
|
|
234
|
+
expect(validation.valid).toBe(false);
|
|
235
|
+
expect(validation.unmatchedToolUses).toHaveLength(0);
|
|
236
|
+
expect(validation.unmatchedToolResults).toHaveLength(1);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("makeToolExchange", () => {
|
|
241
|
+
it("creates paired tool_use and tool_result", () => {
|
|
242
|
+
const [toolUse, toolResult] = makeToolExchange("read_file", { path: "test.txt" }, "file contents");
|
|
243
|
+
|
|
244
|
+
expect(toolUse.type).toBe("tool_use");
|
|
245
|
+
expect(toolUse.name).toBe("read_file");
|
|
246
|
+
expect(toolUse.input).toEqual({ path: "test.txt" });
|
|
247
|
+
|
|
248
|
+
expect(toolResult.type).toBe("tool_result");
|
|
249
|
+
expect(toolResult.tool_use_id).toBe(toolUse.id);
|
|
250
|
+
expect(toolResult.content).toBe("file contents");
|
|
251
|
+
|
|
252
|
+
expect(validateToolPair(toolUse, toolResult)).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("makeToolConversation", () => {
|
|
257
|
+
it("creates complete tool conversation flow", () => {
|
|
258
|
+
const conv = makeToolConversation(
|
|
259
|
+
"Read the config file",
|
|
260
|
+
"read_file",
|
|
261
|
+
{ path: ".config" },
|
|
262
|
+
'{ "setting": true }',
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(conv.messages).toHaveLength(3);
|
|
266
|
+
|
|
267
|
+
// User request
|
|
268
|
+
expect(conv.messages[0].role).toBe("user");
|
|
269
|
+
expect(conv.messages[0].content).toBe("Read the config file");
|
|
270
|
+
|
|
271
|
+
// Assistant tool use
|
|
272
|
+
expect(conv.messages[1].role).toBe("assistant");
|
|
273
|
+
const assistantContent = conv.messages[1].content as Array<{ type: string; name?: string }>;
|
|
274
|
+
expect(assistantContent[0].type).toBe("tool_use");
|
|
275
|
+
expect(assistantContent[0].name).toBe("read_file");
|
|
276
|
+
|
|
277
|
+
// User tool result
|
|
278
|
+
expect(conv.messages[2].role).toBe("user");
|
|
279
|
+
const userContent = conv.messages[2].content as Array<{ type: string }>;
|
|
280
|
+
expect(userContent[0].type).toBe("tool_result");
|
|
281
|
+
|
|
282
|
+
// Validate pairing
|
|
283
|
+
const validation = validateConversationTools(conv);
|
|
284
|
+
expect(validation.valid).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("ID generation", () => {
|
|
289
|
+
it("generates unique IDs with different prefixes", () => {
|
|
290
|
+
const id1 = generateToolUseId("tu");
|
|
291
|
+
const id2 = generateToolUseId("tr");
|
|
292
|
+
|
|
293
|
+
expect(id1.startsWith("tu_")).toBe(true);
|
|
294
|
+
expect(id2.startsWith("tr_")).toBe(true);
|
|
295
|
+
expect(id1).not.toBe(id2);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("resets counter for deterministic tests", () => {
|
|
299
|
+
const tool1 = makeToolUse();
|
|
300
|
+
const counter1 = parseInt(tool1.id.split("_").pop() || "0", 10);
|
|
301
|
+
|
|
302
|
+
resetIdCounter();
|
|
303
|
+
|
|
304
|
+
const tool2 = makeToolUse();
|
|
305
|
+
const counter2 = parseInt(tool2.id.split("_").pop() || "0", 10);
|
|
306
|
+
|
|
307
|
+
expect(counter2).toBe(1);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("complex conversation scenarios", () => {
|
|
312
|
+
it("handles mixed content types", () => {
|
|
313
|
+
const imageBlock = {
|
|
314
|
+
type: "image" as const,
|
|
315
|
+
source: { type: "base64" as const, media_type: "image/png", data: "abc123" },
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const conv = makeConversation({
|
|
319
|
+
messages: [
|
|
320
|
+
makeMessage({
|
|
321
|
+
role: "user",
|
|
322
|
+
content: [makeTextBlock("Please analyze this:"), imageBlock],
|
|
323
|
+
}),
|
|
324
|
+
makeMessage({
|
|
325
|
+
role: "assistant",
|
|
326
|
+
content: [makeToolUse({ name: "analyze_image" })],
|
|
327
|
+
}),
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
expect(conv.messages[0].role).toBe("user");
|
|
332
|
+
const userContent = conv.messages[0].content as Array<{ type: string }>;
|
|
333
|
+
expect(userContent).toHaveLength(2);
|
|
334
|
+
expect(userContent[0].type).toBe("text");
|
|
335
|
+
expect(userContent[1].type).toBe("image");
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|