@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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 +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
|
@@ -7,328 +7,328 @@
|
|
|
7
7
|
|
|
8
8
|
import { describe, expect, it, beforeEach } from "vitest";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 Message,
|
|
23
23
|
} from "./conversation-history.js";
|
|
24
24
|
|
|
25
25
|
describe("conversation-history factories", () => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("makeConversation", () => {
|
|
31
|
-
it("creates empty conversation by default", () => {
|
|
32
|
-
const conv = makeConversation();
|
|
33
|
-
|
|
34
|
-
expect(conv.messages).toEqual([]);
|
|
35
|
-
expect(conv.metadata).toBeUndefined();
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
resetIdCounter();
|
|
36
28
|
});
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
describe("makeConversation", () => {
|
|
31
|
+
it("creates empty conversation by default", () => {
|
|
32
|
+
const conv = makeConversation();
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
expect(conv.messages).toEqual([]);
|
|
35
|
+
expect(conv.metadata).toBeUndefined();
|
|
36
|
+
});
|
|
45
37
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
38
|
+
it("creates conversation with messages", () => {
|
|
39
|
+
const messages = [makeMessage({ role: "user", content: "Hello" })];
|
|
40
|
+
const conv = makeConversation({ messages });
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
expect(conv.messages).toHaveLength(1);
|
|
43
|
+
expect(conv.messages[0].role).toBe("user");
|
|
44
|
+
});
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
it("creates conversation with metadata", () => {
|
|
47
|
+
const conv = makeConversation({
|
|
48
|
+
metadata: { sessionId: "test-123", model: "claude-sonnet" },
|
|
49
|
+
});
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
expect(conv.metadata).toEqual({ sessionId: "test-123", model: "claude-sonnet" });
|
|
52
|
+
});
|
|
61
53
|
});
|
|
62
54
|
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
describe("makeMessage", () => {
|
|
56
|
+
it("creates user message by default", () => {
|
|
57
|
+
const msg = makeMessage();
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
expect(msg.role).toBe("user");
|
|
60
|
+
expect(msg.content).toBe("");
|
|
61
|
+
});
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
role: "assistant",
|
|
73
|
-
content: [makeTextBlock("Hello!")],
|
|
74
|
-
});
|
|
63
|
+
it("creates message with string content", () => {
|
|
64
|
+
const msg = makeMessage({ role: "user", content: "Hello Claude" });
|
|
75
65
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
});
|
|
66
|
+
expect(msg.role).toBe("user");
|
|
67
|
+
expect(msg.content).toBe("Hello Claude");
|
|
68
|
+
});
|
|
81
69
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
it("creates assistant message with content blocks", () => {
|
|
71
|
+
const msg = makeMessage({
|
|
72
|
+
role: "assistant",
|
|
73
|
+
content: [makeTextBlock("Hello!")],
|
|
74
|
+
});
|
|
85
75
|
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
expect(msg.role).toBe("assistant");
|
|
77
|
+
expect(Array.isArray(msg.content)).toBe(true);
|
|
78
|
+
expect((msg.content as Array<{ type: string }>)[0].type).toBe("text");
|
|
79
|
+
});
|
|
88
80
|
});
|
|
89
|
-
});
|
|
90
81
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
describe("makeTextBlock", () => {
|
|
83
|
+
it("creates text block with correct type", () => {
|
|
84
|
+
const block = makeTextBlock("Hello world");
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
expect(tool.input).toEqual({});
|
|
86
|
+
expect(block.type).toBe("text");
|
|
87
|
+
expect(block.text).toBe("Hello world");
|
|
88
|
+
});
|
|
99
89
|
});
|
|
100
90
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
91
|
+
describe("makeToolUse", () => {
|
|
92
|
+
it("creates tool_use with auto-generated ID", () => {
|
|
93
|
+
const tool = makeToolUse();
|
|
94
|
+
|
|
95
|
+
expect(tool.type).toBe("tool_use");
|
|
96
|
+
expect(tool.id).toMatch(/^tu_[a-f0-9]+_\d+$/);
|
|
97
|
+
expect(tool.name).toBe("unnamed_tool");
|
|
98
|
+
expect(tool.input).toEqual({});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("creates tool_use with custom properties", () => {
|
|
102
|
+
const tool = makeToolUse({
|
|
103
|
+
id: "custom_id",
|
|
104
|
+
name: "read_file",
|
|
105
|
+
input: { path: "test.txt", offset: 0 },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(tool.id).toBe("custom_id");
|
|
109
|
+
expect(tool.name).toBe("read_file");
|
|
110
|
+
expect(tool.input).toEqual({ path: "test.txt", offset: 0 });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("generates unique IDs for multiple tools", () => {
|
|
114
|
+
const tool1 = makeToolUse();
|
|
115
|
+
const tool2 = makeToolUse();
|
|
116
|
+
|
|
117
|
+
expect(tool1.id).not.toBe(tool2.id);
|
|
118
|
+
});
|
|
111
119
|
});
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
describe("makeToolResult", () => {
|
|
122
|
+
it("creates tool_result with auto-generated tool_use_id", () => {
|
|
123
|
+
const result = makeToolResult();
|
|
124
|
+
|
|
125
|
+
expect(result.type).toBe("tool_result");
|
|
126
|
+
expect(result.tool_use_id).toMatch(/^tr_[a-f0-9]+_\d+$/);
|
|
127
|
+
expect(result.content).toBe("");
|
|
128
|
+
expect(result.is_error).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("creates tool_result with custom properties", () => {
|
|
132
|
+
const result = makeToolResult({
|
|
133
|
+
toolUseId: "tu_abc123",
|
|
134
|
+
content: "File contents here",
|
|
135
|
+
isError: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result.tool_use_id).toBe("tu_abc123");
|
|
139
|
+
expect(result.content).toBe("File contents here");
|
|
140
|
+
expect(result.is_error).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("creates tool_result with content blocks", () => {
|
|
144
|
+
const result = makeToolResult({
|
|
145
|
+
toolUseId: "tu_123",
|
|
146
|
+
content: [makeTextBlock("Result text")],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
150
|
+
expect((result.content as Array<{ type: string }>)[0].type).toBe("text");
|
|
151
|
+
});
|
|
118
152
|
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe("makeToolResult", () => {
|
|
122
|
-
it("creates tool_result with auto-generated tool_use_id", () => {
|
|
123
|
-
const result = makeToolResult();
|
|
124
153
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
154
|
+
describe("tool pairing validation", () => {
|
|
155
|
+
it("validates matching tool_use and tool_result pair", () => {
|
|
156
|
+
const toolUse = makeToolUse({ id: "tu_test123", name: "read_file" });
|
|
157
|
+
const toolResult = makeToolResult({ toolUseId: "tu_test123", content: "data" });
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
toolUseId: "tu_abc123",
|
|
134
|
-
content: "File contents here",
|
|
135
|
-
isError: true,
|
|
136
|
-
});
|
|
159
|
+
expect(validateToolPair(toolUse, toolResult)).toBe(true);
|
|
160
|
+
});
|
|
137
161
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
162
|
+
it("rejects mismatched tool pair", () => {
|
|
163
|
+
const toolUse = makeToolUse({ id: "tu_abc" });
|
|
164
|
+
const toolResult = makeToolResult({ toolUseId: "tu_xyz" });
|
|
142
165
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
toolUseId: "tu_123",
|
|
146
|
-
content: [makeTextBlock("Result text")],
|
|
147
|
-
});
|
|
166
|
+
expect(validateToolPair(toolUse, toolResult)).toBe(false);
|
|
167
|
+
});
|
|
148
168
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
169
|
+
it("finds tool_result by ID in message array", () => {
|
|
170
|
+
const toolUse = makeToolUse({ id: "tu_findme" });
|
|
171
|
+
const toolResult = makeToolResult({ toolUseId: "tu_findme", content: "found" });
|
|
153
172
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
const messages: Message[] = [
|
|
174
|
+
makeMessage({ role: "assistant", content: [toolUse] }),
|
|
175
|
+
makeMessage({ role: "user", content: [toolResult] }),
|
|
176
|
+
];
|
|
158
177
|
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
const found = findToolResult(messages, "tu_findme");
|
|
179
|
+
expect(found).toBeDefined();
|
|
180
|
+
expect(found?.content).toBe("found");
|
|
181
|
+
});
|
|
161
182
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const toolResult = makeToolResult({ toolUseId: "tu_xyz" });
|
|
183
|
+
it("returns undefined when tool_result not found", () => {
|
|
184
|
+
const messages: Message[] = [makeMessage({ role: "user", content: "Hello" })];
|
|
165
185
|
|
|
166
|
-
|
|
186
|
+
const found = findToolResult(messages, "tu_missing");
|
|
187
|
+
expect(found).toBeUndefined();
|
|
188
|
+
});
|
|
167
189
|
});
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
191
|
+
describe("conversation tool validation", () => {
|
|
192
|
+
it("validates conversation with complete tool pairs", () => {
|
|
193
|
+
const toolUse = makeToolUse({ id: "tu_complete" });
|
|
194
|
+
const toolResult = makeToolResult({ toolUseId: "tu_complete" });
|
|
195
|
+
|
|
196
|
+
const conv = makeConversation({
|
|
197
|
+
messages: [
|
|
198
|
+
makeMessage({ role: "user", content: "Use tool" }),
|
|
199
|
+
makeMessage({ role: "assistant", content: [toolUse] }),
|
|
200
|
+
makeMessage({ role: "user", content: [toolResult] }),
|
|
201
|
+
],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const validation = validateConversationTools(conv);
|
|
205
|
+
expect(validation.valid).toBe(true);
|
|
206
|
+
expect(validation.unmatchedToolUses).toHaveLength(0);
|
|
207
|
+
expect(validation.unmatchedToolResults).toHaveLength(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("detects unmatched tool_use blocks", () => {
|
|
211
|
+
const toolUse = makeToolUse({ id: "tu_unmatched" });
|
|
212
|
+
|
|
213
|
+
const conv = makeConversation({
|
|
214
|
+
messages: [makeMessage({ role: "assistant", content: [toolUse] })],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const validation = validateConversationTools(conv);
|
|
218
|
+
expect(validation.valid).toBe(false);
|
|
219
|
+
expect(validation.unmatchedToolUses).toHaveLength(1);
|
|
220
|
+
expect(validation.unmatchedToolResults).toHaveLength(0);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("detects unmatched tool_result blocks", () => {
|
|
224
|
+
const toolResult = makeToolResult({ toolUseId: "tu_missing" });
|
|
225
|
+
|
|
226
|
+
const conv = makeConversation({
|
|
227
|
+
messages: [makeMessage({ role: "user", content: [toolResult] })],
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const validation = validateConversationTools(conv);
|
|
231
|
+
expect(validation.valid).toBe(false);
|
|
232
|
+
expect(validation.unmatchedToolUses).toHaveLength(0);
|
|
233
|
+
expect(validation.unmatchedToolResults).toHaveLength(1);
|
|
234
|
+
});
|
|
181
235
|
});
|
|
182
236
|
|
|
183
|
-
|
|
184
|
-
|
|
237
|
+
describe("makeToolExchange", () => {
|
|
238
|
+
it("creates paired tool_use and tool_result", () => {
|
|
239
|
+
const [toolUse, toolResult] = makeToolExchange("read_file", { path: "test.txt" }, "file contents");
|
|
185
240
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe("conversation tool validation", () => {
|
|
192
|
-
it("validates conversation with complete tool pairs", () => {
|
|
193
|
-
const toolUse = makeToolUse({ id: "tu_complete" });
|
|
194
|
-
const toolResult = makeToolResult({ toolUseId: "tu_complete" });
|
|
195
|
-
|
|
196
|
-
const conv = makeConversation({
|
|
197
|
-
messages: [
|
|
198
|
-
makeMessage({ role: "user", content: "Use tool" }),
|
|
199
|
-
makeMessage({ role: "assistant", content: [toolUse] }),
|
|
200
|
-
makeMessage({ role: "user", content: [toolResult] }),
|
|
201
|
-
],
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const validation = validateConversationTools(conv);
|
|
205
|
-
expect(validation.valid).toBe(true);
|
|
206
|
-
expect(validation.unmatchedToolUses).toHaveLength(0);
|
|
207
|
-
expect(validation.unmatchedToolResults).toHaveLength(0);
|
|
208
|
-
});
|
|
241
|
+
expect(toolUse.type).toBe("tool_use");
|
|
242
|
+
expect(toolUse.name).toBe("read_file");
|
|
243
|
+
expect(toolUse.input).toEqual({ path: "test.txt" });
|
|
209
244
|
|
|
210
|
-
|
|
211
|
-
|
|
245
|
+
expect(toolResult.type).toBe("tool_result");
|
|
246
|
+
expect(toolResult.tool_use_id).toBe(toolUse.id);
|
|
247
|
+
expect(toolResult.content).toBe("file contents");
|
|
212
248
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const validation = validateConversationTools(conv);
|
|
218
|
-
expect(validation.valid).toBe(false);
|
|
219
|
-
expect(validation.unmatchedToolUses).toHaveLength(1);
|
|
220
|
-
expect(validation.unmatchedToolResults).toHaveLength(0);
|
|
249
|
+
expect(validateToolPair(toolUse, toolResult)).toBe(true);
|
|
250
|
+
});
|
|
221
251
|
});
|
|
222
252
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
253
|
+
describe("makeToolConversation", () => {
|
|
254
|
+
it("creates complete tool conversation flow", () => {
|
|
255
|
+
const conv = makeToolConversation(
|
|
256
|
+
"Read the config file",
|
|
257
|
+
"read_file",
|
|
258
|
+
{ path: ".config" },
|
|
259
|
+
'{ "setting": true }',
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
expect(conv.messages).toHaveLength(3);
|
|
263
|
+
|
|
264
|
+
// User request
|
|
265
|
+
expect(conv.messages[0].role).toBe("user");
|
|
266
|
+
expect(conv.messages[0].content).toBe("Read the config file");
|
|
267
|
+
|
|
268
|
+
// Assistant tool use
|
|
269
|
+
expect(conv.messages[1].role).toBe("assistant");
|
|
270
|
+
const assistantContent = conv.messages[1].content as Array<{ type: string; name?: string }>;
|
|
271
|
+
expect(assistantContent[0].type).toBe("tool_use");
|
|
272
|
+
expect(assistantContent[0].name).toBe("read_file");
|
|
273
|
+
|
|
274
|
+
// User tool result
|
|
275
|
+
expect(conv.messages[2].role).toBe("user");
|
|
276
|
+
const userContent = conv.messages[2].content as Array<{ type: string }>;
|
|
277
|
+
expect(userContent[0].type).toBe("tool_result");
|
|
278
|
+
|
|
279
|
+
// Validate pairing
|
|
280
|
+
const validation = validateConversationTools(conv);
|
|
281
|
+
expect(validation.valid).toBe(true);
|
|
282
|
+
});
|
|
234
283
|
});
|
|
235
|
-
});
|
|
236
284
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
285
|
+
describe("ID generation", () => {
|
|
286
|
+
it("generates unique IDs with different prefixes", () => {
|
|
287
|
+
const id1 = generateToolUseId("tu");
|
|
288
|
+
const id2 = generateToolUseId("tr");
|
|
240
289
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
290
|
+
expect(id1.startsWith("tu_")).toBe(true);
|
|
291
|
+
expect(id2.startsWith("tr_")).toBe(true);
|
|
292
|
+
expect(id1).not.toBe(id2);
|
|
293
|
+
});
|
|
244
294
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
expect(toolResult.content).toBe("file contents");
|
|
295
|
+
it("resets counter for deterministic tests", () => {
|
|
296
|
+
makeToolUse();
|
|
248
297
|
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe("makeToolConversation", () => {
|
|
254
|
-
it("creates complete tool conversation flow", () => {
|
|
255
|
-
const conv = makeToolConversation(
|
|
256
|
-
"Read the config file",
|
|
257
|
-
"read_file",
|
|
258
|
-
{ path: ".config" },
|
|
259
|
-
'{ "setting": true }',
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
expect(conv.messages).toHaveLength(3);
|
|
263
|
-
|
|
264
|
-
// User request
|
|
265
|
-
expect(conv.messages[0].role).toBe("user");
|
|
266
|
-
expect(conv.messages[0].content).toBe("Read the config file");
|
|
267
|
-
|
|
268
|
-
// Assistant tool use
|
|
269
|
-
expect(conv.messages[1].role).toBe("assistant");
|
|
270
|
-
const assistantContent = conv.messages[1].content as Array<{ type: string; name?: string }>;
|
|
271
|
-
expect(assistantContent[0].type).toBe("tool_use");
|
|
272
|
-
expect(assistantContent[0].name).toBe("read_file");
|
|
273
|
-
|
|
274
|
-
// User tool result
|
|
275
|
-
expect(conv.messages[2].role).toBe("user");
|
|
276
|
-
const userContent = conv.messages[2].content as Array<{ type: string }>;
|
|
277
|
-
expect(userContent[0].type).toBe("tool_result");
|
|
278
|
-
|
|
279
|
-
// Validate pairing
|
|
280
|
-
const validation = validateConversationTools(conv);
|
|
281
|
-
expect(validation.valid).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
298
|
+
resetIdCounter();
|
|
284
299
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const id1 = generateToolUseId("tu");
|
|
288
|
-
const id2 = generateToolUseId("tr");
|
|
300
|
+
const tool2 = makeToolUse();
|
|
301
|
+
const counter2 = parseInt(tool2.id.split("_").pop() || "0", 10);
|
|
289
302
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
expect(id1).not.toBe(id2);
|
|
303
|
+
expect(counter2).toBe(1);
|
|
304
|
+
});
|
|
293
305
|
});
|
|
294
306
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
role: "assistant",
|
|
322
|
-
content: [makeToolUse({ name: "analyze_image" })],
|
|
323
|
-
}),
|
|
324
|
-
],
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
expect(conv.messages[0].role).toBe("user");
|
|
328
|
-
const userContent = conv.messages[0].content as Array<{ type: string }>;
|
|
329
|
-
expect(userContent).toHaveLength(2);
|
|
330
|
-
expect(userContent[0].type).toBe("text");
|
|
331
|
-
expect(userContent[1].type).toBe("image");
|
|
307
|
+
describe("complex conversation scenarios", () => {
|
|
308
|
+
it("handles mixed content types", () => {
|
|
309
|
+
const imageBlock = {
|
|
310
|
+
type: "image" as const,
|
|
311
|
+
source: { type: "base64" as const, media_type: "image/png", data: "abc123" },
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const conv = makeConversation({
|
|
315
|
+
messages: [
|
|
316
|
+
makeMessage({
|
|
317
|
+
role: "user",
|
|
318
|
+
content: [makeTextBlock("Please analyze this:"), imageBlock],
|
|
319
|
+
}),
|
|
320
|
+
makeMessage({
|
|
321
|
+
role: "assistant",
|
|
322
|
+
content: [makeToolUse({ name: "analyze_image" })],
|
|
323
|
+
}),
|
|
324
|
+
],
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
expect(conv.messages[0].role).toBe("user");
|
|
328
|
+
const userContent = conv.messages[0].content as Array<{ type: string }>;
|
|
329
|
+
expect(userContent).toHaveLength(2);
|
|
330
|
+
expect(userContent[0].type).toBe("text");
|
|
331
|
+
expect(userContent[1].type).toBe("image");
|
|
332
|
+
});
|
|
332
333
|
});
|
|
333
|
-
});
|
|
334
334
|
});
|