@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.
Files changed (107) hide show
  1. package/README.md +88 -88
  2. package/dist/opencode-anthropic-auth-cli.mjs +804 -507
  3. package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
  4. package/package.json +67 -59
  5. package/src/__tests__/billing-edge-cases.test.ts +59 -59
  6. package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
  7. package/src/__tests__/cc-comparison.test.ts +87 -87
  8. package/src/__tests__/cc-credentials.test.ts +254 -250
  9. package/src/__tests__/cch-drift-checker.test.ts +51 -51
  10. package/src/__tests__/cch-native-style.test.ts +56 -56
  11. package/src/__tests__/debug-gating.test.ts +42 -42
  12. package/src/__tests__/decomposition-smoke.test.ts +68 -68
  13. package/src/__tests__/fingerprint-regression.test.ts +575 -566
  14. package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
  15. package/src/__tests__/helpers/conversation-history.ts +119 -119
  16. package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
  17. package/src/__tests__/helpers/deferred.ts +69 -69
  18. package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
  19. package/src/__tests__/helpers/in-memory-storage.ts +88 -88
  20. package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
  21. package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
  22. package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
  23. package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
  24. package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
  25. package/src/__tests__/helpers/sse.ts +209 -209
  26. package/src/__tests__/index.parallel.test.ts +605 -595
  27. package/src/__tests__/sanitization-regex.test.ts +112 -112
  28. package/src/__tests__/state-bounds.test.ts +90 -90
  29. package/src/account-identity.test.ts +197 -192
  30. package/src/account-identity.ts +69 -67
  31. package/src/account-state.test.ts +86 -86
  32. package/src/account-state.ts +25 -25
  33. package/src/accounts/matching.test.ts +335 -0
  34. package/src/accounts/matching.ts +167 -0
  35. package/src/accounts/persistence.test.ts +345 -0
  36. package/src/accounts/persistence.ts +432 -0
  37. package/src/accounts/repair.test.ts +276 -0
  38. package/src/accounts/repair.ts +407 -0
  39. package/src/accounts.dedup.test.ts +621 -621
  40. package/src/accounts.test.ts +933 -929
  41. package/src/accounts.ts +633 -989
  42. package/src/backoff.test.ts +345 -345
  43. package/src/backoff.ts +219 -219
  44. package/src/betas.ts +124 -124
  45. package/src/bun-fetch.test.ts +345 -342
  46. package/src/bun-fetch.ts +424 -424
  47. package/src/bun-proxy.test.ts +25 -25
  48. package/src/bun-proxy.ts +209 -209
  49. package/src/cc-credentials.ts +111 -111
  50. package/src/circuit-breaker.test.ts +184 -184
  51. package/src/circuit-breaker.ts +169 -169
  52. package/src/cli/commands/auth.ts +963 -0
  53. package/src/cli/commands/config.ts +547 -0
  54. package/src/cli/formatting.test.ts +406 -0
  55. package/src/cli/formatting.ts +219 -0
  56. package/src/cli.ts +255 -2022
  57. package/src/commands/handlers/betas.ts +100 -0
  58. package/src/commands/handlers/config.ts +99 -0
  59. package/src/commands/handlers/files.ts +375 -0
  60. package/src/commands/oauth-flow.ts +181 -166
  61. package/src/commands/prompts.ts +61 -61
  62. package/src/commands/router.test.ts +421 -0
  63. package/src/commands/router.ts +143 -635
  64. package/src/config.test.ts +482 -482
  65. package/src/config.ts +412 -404
  66. package/src/constants.ts +48 -48
  67. package/src/drift/cch-constants.ts +95 -95
  68. package/src/env.ts +111 -105
  69. package/src/headers/billing.ts +33 -33
  70. package/src/headers/builder.ts +130 -130
  71. package/src/headers/cch.ts +75 -75
  72. package/src/headers/stainless.ts +25 -25
  73. package/src/headers/user-agent.ts +23 -23
  74. package/src/index.ts +436 -828
  75. package/src/models.ts +27 -27
  76. package/src/oauth.test.ts +102 -102
  77. package/src/oauth.ts +178 -178
  78. package/src/parent-pid-watcher.test.ts +148 -148
  79. package/src/parent-pid-watcher.ts +69 -69
  80. package/src/plugin-helpers.ts +82 -82
  81. package/src/refresh-helpers.ts +145 -139
  82. package/src/refresh-lock.test.ts +94 -94
  83. package/src/refresh-lock.ts +93 -93
  84. package/src/request/body.history.test.ts +579 -571
  85. package/src/request/body.ts +255 -255
  86. package/src/request/metadata.ts +65 -65
  87. package/src/request/retry.test.ts +156 -156
  88. package/src/request/retry.ts +67 -67
  89. package/src/request/url.ts +21 -21
  90. package/src/request-orchestration-helpers.ts +648 -0
  91. package/src/response/index.ts +5 -5
  92. package/src/response/mcp.ts +58 -58
  93. package/src/response/streaming.test.ts +313 -311
  94. package/src/response/streaming.ts +412 -410
  95. package/src/rotation.test.ts +304 -301
  96. package/src/rotation.ts +205 -205
  97. package/src/storage.test.ts +547 -547
  98. package/src/storage.ts +315 -291
  99. package/src/system-prompt/builder.ts +38 -38
  100. package/src/system-prompt/index.ts +5 -5
  101. package/src/system-prompt/normalize.ts +60 -60
  102. package/src/system-prompt/sanitize.ts +30 -30
  103. package/src/thinking.ts +21 -20
  104. package/src/token-refresh.test.ts +265 -265
  105. package/src/token-refresh.ts +219 -214
  106. package/src/types.ts +30 -30
  107. 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
- 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,
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
- beforeEach(() => {
27
- resetIdCounter();
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
- it("creates conversation with messages", () => {
39
- const messages = [makeMessage({ role: "user", content: "Hello" })];
40
- const conv = makeConversation({ messages });
30
+ describe("makeConversation", () => {
31
+ it("creates empty conversation by default", () => {
32
+ const conv = makeConversation();
41
33
 
42
- expect(conv.messages).toHaveLength(1);
43
- expect(conv.messages[0].role).toBe("user");
44
- });
34
+ expect(conv.messages).toEqual([]);
35
+ expect(conv.metadata).toBeUndefined();
36
+ });
45
37
 
46
- it("creates conversation with metadata", () => {
47
- const conv = makeConversation({
48
- metadata: { sessionId: "test-123", model: "claude-sonnet" },
49
- });
38
+ it("creates conversation with messages", () => {
39
+ const messages = [makeMessage({ role: "user", content: "Hello" })];
40
+ const conv = makeConversation({ messages });
50
41
 
51
- expect(conv.metadata).toEqual({ sessionId: "test-123", model: "claude-sonnet" });
52
- });
53
- });
42
+ expect(conv.messages).toHaveLength(1);
43
+ expect(conv.messages[0].role).toBe("user");
44
+ });
54
45
 
55
- describe("makeMessage", () => {
56
- it("creates user message by default", () => {
57
- const msg = makeMessage();
46
+ it("creates conversation with metadata", () => {
47
+ const conv = makeConversation({
48
+ metadata: { sessionId: "test-123", model: "claude-sonnet" },
49
+ });
58
50
 
59
- expect(msg.role).toBe("user");
60
- expect(msg.content).toBe("");
51
+ expect(conv.metadata).toEqual({ sessionId: "test-123", model: "claude-sonnet" });
52
+ });
61
53
  });
62
54
 
63
- it("creates message with string content", () => {
64
- const msg = makeMessage({ role: "user", content: "Hello Claude" });
55
+ describe("makeMessage", () => {
56
+ it("creates user message by default", () => {
57
+ const msg = makeMessage();
65
58
 
66
- expect(msg.role).toBe("user");
67
- expect(msg.content).toBe("Hello Claude");
68
- });
59
+ expect(msg.role).toBe("user");
60
+ expect(msg.content).toBe("");
61
+ });
69
62
 
70
- it("creates assistant message with content blocks", () => {
71
- const msg = makeMessage({
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
- 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
- });
80
- });
66
+ expect(msg.role).toBe("user");
67
+ expect(msg.content).toBe("Hello Claude");
68
+ });
81
69
 
82
- describe("makeTextBlock", () => {
83
- it("creates text block with correct type", () => {
84
- const block = makeTextBlock("Hello world");
70
+ it("creates assistant message with content blocks", () => {
71
+ const msg = makeMessage({
72
+ role: "assistant",
73
+ content: [makeTextBlock("Hello!")],
74
+ });
85
75
 
86
- expect(block.type).toBe("text");
87
- expect(block.text).toBe("Hello world");
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
- describe("makeToolUse", () => {
92
- it("creates tool_use with auto-generated ID", () => {
93
- const tool = makeToolUse();
82
+ describe("makeTextBlock", () => {
83
+ it("creates text block with correct type", () => {
84
+ const block = makeTextBlock("Hello world");
94
85
 
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({});
86
+ expect(block.type).toBe("text");
87
+ expect(block.text).toBe("Hello world");
88
+ });
99
89
  });
100
90
 
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 });
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
- it("generates unique IDs for multiple tools", () => {
114
- const tool1 = makeToolUse();
115
- const tool2 = makeToolUse();
116
-
117
- expect(tool1.id).not.toBe(tool2.id);
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
- 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
- });
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
- it("creates tool_result with custom properties", () => {
132
- const result = makeToolResult({
133
- toolUseId: "tu_abc123",
134
- content: "File contents here",
135
- isError: true,
136
- });
159
+ expect(validateToolPair(toolUse, toolResult)).toBe(true);
160
+ });
137
161
 
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
- });
162
+ it("rejects mismatched tool pair", () => {
163
+ const toolUse = makeToolUse({ id: "tu_abc" });
164
+ const toolResult = makeToolResult({ toolUseId: "tu_xyz" });
142
165
 
143
- it("creates tool_result with content blocks", () => {
144
- const result = makeToolResult({
145
- toolUseId: "tu_123",
146
- content: [makeTextBlock("Result text")],
147
- });
166
+ expect(validateToolPair(toolUse, toolResult)).toBe(false);
167
+ });
148
168
 
149
- expect(Array.isArray(result.content)).toBe(true);
150
- expect((result.content as Array<{ type: string }>)[0].type).toBe("text");
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
- 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" });
173
+ const messages: Message[] = [
174
+ makeMessage({ role: "assistant", content: [toolUse] }),
175
+ makeMessage({ role: "user", content: [toolResult] }),
176
+ ];
158
177
 
159
- expect(validateToolPair(toolUse, toolResult)).toBe(true);
160
- });
178
+ const found = findToolResult(messages, "tu_findme");
179
+ expect(found).toBeDefined();
180
+ expect(found?.content).toBe("found");
181
+ });
161
182
 
162
- it("rejects mismatched tool pair", () => {
163
- const toolUse = makeToolUse({ id: "tu_abc" });
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
- expect(validateToolPair(toolUse, toolResult)).toBe(false);
186
+ const found = findToolResult(messages, "tu_missing");
187
+ expect(found).toBeUndefined();
188
+ });
167
189
  });
168
190
 
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" });
172
-
173
- const messages: Message[] = [
174
- makeMessage({ role: "assistant", content: [toolUse] }),
175
- makeMessage({ role: "user", content: [toolResult] }),
176
- ];
177
-
178
- const found = findToolResult(messages, "tu_findme");
179
- expect(found).toBeDefined();
180
- expect(found?.content).toBe("found");
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
- it("returns undefined when tool_result not found", () => {
184
- const messages: Message[] = [makeMessage({ role: "user", content: "Hello" })];
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
- const found = findToolResult(messages, "tu_missing");
187
- expect(found).toBeUndefined();
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
- it("detects unmatched tool_use blocks", () => {
211
- const toolUse = makeToolUse({ id: "tu_unmatched" });
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
- 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);
249
+ expect(validateToolPair(toolUse, toolResult)).toBe(true);
250
+ });
221
251
  });
222
252
 
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);
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
- describe("makeToolExchange", () => {
238
- it("creates paired tool_use and tool_result", () => {
239
- const [toolUse, toolResult] = makeToolExchange("read_file", { path: "test.txt" }, "file contents");
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
- expect(toolUse.type).toBe("tool_use");
242
- expect(toolUse.name).toBe("read_file");
243
- expect(toolUse.input).toEqual({ path: "test.txt" });
290
+ expect(id1.startsWith("tu_")).toBe(true);
291
+ expect(id2.startsWith("tr_")).toBe(true);
292
+ expect(id1).not.toBe(id2);
293
+ });
244
294
 
245
- expect(toolResult.type).toBe("tool_result");
246
- expect(toolResult.tool_use_id).toBe(toolUse.id);
247
- expect(toolResult.content).toBe("file contents");
295
+ it("resets counter for deterministic tests", () => {
296
+ makeToolUse();
248
297
 
249
- expect(validateToolPair(toolUse, toolResult)).toBe(true);
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
- describe("ID generation", () => {
286
- it("generates unique IDs with different prefixes", () => {
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
- expect(id1.startsWith("tu_")).toBe(true);
291
- expect(id2.startsWith("tr_")).toBe(true);
292
- expect(id1).not.toBe(id2);
303
+ expect(counter2).toBe(1);
304
+ });
293
305
  });
294
306
 
295
- it("resets counter for deterministic tests", () => {
296
- makeToolUse();
297
-
298
- resetIdCounter();
299
-
300
- const tool2 = makeToolUse();
301
- const counter2 = parseInt(tool2.id.split("_").pop() || "0", 10);
302
-
303
- expect(counter2).toBe(1);
304
- });
305
- });
306
-
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");
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
  });