conversationalist 0.0.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +369 -0
  3. package/dist/adapters/anthropic/index.d.ts +76 -0
  4. package/dist/adapters/anthropic/index.d.ts.map +1 -0
  5. package/dist/adapters/anthropic/index.js +147 -0
  6. package/dist/adapters/anthropic/index.js.map +10 -0
  7. package/dist/adapters/gemini/index.d.ts +79 -0
  8. package/dist/adapters/gemini/index.d.ts.map +1 -0
  9. package/dist/adapters/gemini/index.js +148 -0
  10. package/dist/adapters/gemini/index.js.map +10 -0
  11. package/dist/adapters/openai/index.d.ts +65 -0
  12. package/dist/adapters/openai/index.d.ts.map +1 -0
  13. package/dist/adapters/openai/index.js +127 -0
  14. package/dist/adapters/openai/index.js.map +10 -0
  15. package/dist/context.d.ts +35 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/conversation/append.d.ts +25 -0
  18. package/dist/conversation/append.d.ts.map +1 -0
  19. package/dist/conversation/create.d.ts +14 -0
  20. package/dist/conversation/create.d.ts.map +1 -0
  21. package/dist/conversation/index.d.ts +9 -0
  22. package/dist/conversation/index.d.ts.map +1 -0
  23. package/dist/conversation/modify.d.ts +9 -0
  24. package/dist/conversation/modify.d.ts.map +1 -0
  25. package/dist/conversation/query.d.ts +34 -0
  26. package/dist/conversation/query.d.ts.map +1 -0
  27. package/dist/conversation/serialization.d.ts +13 -0
  28. package/dist/conversation/serialization.d.ts.map +1 -0
  29. package/dist/conversation/system-messages.d.ts +31 -0
  30. package/dist/conversation/system-messages.d.ts.map +1 -0
  31. package/dist/conversation/tool-tracking.d.ts +26 -0
  32. package/dist/conversation/tool-tracking.d.ts.map +1 -0
  33. package/dist/conversation/transform.d.ts +9 -0
  34. package/dist/conversation/transform.d.ts.map +1 -0
  35. package/dist/conversation.d.ts +39 -0
  36. package/dist/conversation.d.ts.map +1 -0
  37. package/dist/environment.d.ts +23 -0
  38. package/dist/environment.d.ts.map +1 -0
  39. package/dist/errors.d.ts +67 -0
  40. package/dist/errors.d.ts.map +1 -0
  41. package/dist/index.d.ts +17 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +14378 -0
  44. package/dist/index.js.map +87 -0
  45. package/dist/message.d.ts +2 -0
  46. package/dist/message.d.ts.map +1 -0
  47. package/dist/multi-modal.d.ts +27 -0
  48. package/dist/multi-modal.d.ts.map +1 -0
  49. package/dist/schemas.d.ts +30 -0
  50. package/dist/schemas.d.ts.map +1 -0
  51. package/dist/streaming.d.ts +37 -0
  52. package/dist/streaming.d.ts.map +1 -0
  53. package/dist/types.d.ts +78 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/utilities.d.ts +79 -0
  56. package/dist/utilities.d.ts.map +1 -0
  57. package/dist/with-conversation.d.ts +125 -0
  58. package/dist/with-conversation.d.ts.map +1 -0
  59. package/package.json +113 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Steve Kinney
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,369 @@
1
+ # Conversationalist
2
+
3
+ A TypeScript library for managing LLM conversation state with immutable data structures and type-safe APIs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add conversationalist
9
+ npm add conversationalist
10
+ ```
11
+
12
+ ## Core Concepts
13
+
14
+ ### Conversation
15
+
16
+ A conversation is an immutable data structure containing messages, metadata, and timestamps:
17
+
18
+ ```typescript
19
+ import {
20
+ createConversation,
21
+ appendUserMessage,
22
+ appendAssistantMessage,
23
+ } from 'conversationalist';
24
+
25
+ let conversation = createConversation({ title: 'My Chat' });
26
+
27
+ conversation = appendUserMessage(conversation, 'Hello!');
28
+ conversation = appendAssistantMessage(conversation, 'Hi there!');
29
+ ```
30
+
31
+ ### Messages
32
+
33
+ Messages have roles (`user`, `assistant`, `system`, `tool-use`, `tool-result`, `developer`, `snapshot`) and can contain text or multi-modal content:
34
+
35
+ ```typescript
36
+ import { appendMessages } from 'conversationalist';
37
+
38
+ // Text message
39
+ conversation = appendMessages(conversation, {
40
+ role: 'user',
41
+ content: 'What is in this image?',
42
+ });
43
+
44
+ // Multi-modal message with image
45
+ conversation = appendMessages(conversation, {
46
+ role: 'user',
47
+ content: [
48
+ { type: 'text', text: 'Describe this:' },
49
+ { type: 'image', url: 'https://example.com/image.png' },
50
+ ],
51
+ });
52
+ ```
53
+
54
+ ### Tool Calls
55
+
56
+ Tool use is supported with linked tool calls and results:
57
+
58
+ ```typescript
59
+ conversation = appendMessages(
60
+ conversation,
61
+ {
62
+ role: 'tool-use',
63
+ content: '',
64
+ toolCall: { id: 'call_123', name: 'get_weather', arguments: '{"city":"NYC"}' },
65
+ },
66
+ {
67
+ role: 'tool-result',
68
+ content: '',
69
+ toolResult: { callId: 'call_123', outcome: 'success', content: '72°F, sunny' },
70
+ },
71
+ );
72
+ ```
73
+
74
+ ## API Reference
75
+
76
+ ### Creating Conversations
77
+
78
+ ```typescript
79
+ createConversation(options?: {
80
+ id?: string;
81
+ title?: string;
82
+ status?: 'active' | 'archived' | 'deleted';
83
+ metadata?: Record<string, unknown>;
84
+ tags?: string[];
85
+ }): Conversation
86
+ ```
87
+
88
+ ### Appending Messages
89
+
90
+ ```typescript
91
+ appendMessages(conversation: Conversation, ...inputs: MessageInput[]): Conversation
92
+ appendUserMessage(conversation: Conversation, content: string | MultiModalContent[]): Conversation
93
+ appendAssistantMessage(conversation: Conversation, content: string | MultiModalContent[]): Conversation
94
+ appendSystemMessage(conversation: Conversation, content: string): Conversation
95
+ prependSystemMessage(conversation: Conversation, content: string): Conversation
96
+ replaceSystemMessage(conversation: Conversation, content: string): Conversation
97
+ ```
98
+
99
+ ### Querying Messages
100
+
101
+ ```typescript
102
+ getConversationMessages(conversation: Conversation, options?: { includeHidden?: boolean }): Message[]
103
+ getMessageAtPosition(conversation: Conversation, position: number): Message | undefined
104
+ getMessageByIdentifier(conversation: Conversation, id: string): Message | undefined
105
+ searchConversationMessages(conversation: Conversation, predicate: (m: Message) => boolean): Message[]
106
+ ```
107
+
108
+ ### System Messages
109
+
110
+ ```typescript
111
+ hasSystemMessage(conversation: Conversation): boolean
112
+ getFirstSystemMessage(conversation: Conversation): Message | undefined
113
+ getSystemMessages(conversation: Conversation): Message[]
114
+ collapseSystemMessages(conversation: Conversation): Conversation
115
+ ```
116
+
117
+ ### Utilities
118
+
119
+ ```typescript
120
+ computeConversationStatistics(conversation: Conversation): {
121
+ total: number;
122
+ byRole: Record<string, number>;
123
+ hidden: number;
124
+ withImages: number;
125
+ }
126
+
127
+ redactMessageAtPosition(conversation: Conversation, position: number, placeholder?: string): Conversation
128
+ ```
129
+
130
+ ### Serialization
131
+
132
+ ```typescript
133
+ serializeConversation(conversation: Conversation): ConversationJSON
134
+ deserializeConversation(json: ConversationJSON): Conversation
135
+ toChatMessages(conversation: Conversation): ExternalMessage[]
136
+ ```
137
+
138
+ ### Builder Pattern
139
+
140
+ For fluent API style:
141
+
142
+ ```typescript
143
+ import { createConversation } from 'conversationalist';
144
+ import { withConversation, pipeConversation } from 'conversationalist';
145
+ import { simpleTokenEstimator } from 'conversationalist';
146
+
147
+ // Draft pattern with callback
148
+ const conversation = withConversation(createConversation(), (draft) => {
149
+ draft
150
+ .appendSystemMessage('You are a helpful assistant.')
151
+ .appendUserMessage('Hello!')
152
+ .appendAssistantMessage('Hi there!');
153
+ });
154
+
155
+ // Streaming with the draft pattern
156
+ const streamedConversation = withConversation(createConversation(), (draft) => {
157
+ const { draft: d, messageId } = draft.appendStreamingMessage('assistant');
158
+ d.updateStreamingMessage(messageId, 'Partial response...').finalizeStreamingMessage(
159
+ messageId,
160
+ { tokenUsage: { prompt: 10, completion: 5, total: 15 } },
161
+ );
162
+ });
163
+
164
+ // Context window management with the draft pattern
165
+ const truncatedConversation = withConversation(conversation, (draft) => {
166
+ draft.truncateToTokenLimit(4000, simpleTokenEstimator, { preserveLastN: 2 });
167
+ });
168
+
169
+ // Pipe pattern
170
+ const conversation = pipeConversation(
171
+ createConversation(),
172
+ (c) => appendSystemMessage(c, 'You are helpful.'),
173
+ (c) => appendUserMessage(c, 'Hello!'),
174
+ );
175
+ ```
176
+
177
+ The `ConversationDraft` provides all conversation manipulation methods:
178
+
179
+ - **Message appending**: `appendMessages`, `appendUserMessage`, `appendAssistantMessage`, `appendSystemMessage`
180
+ - **System messages**: `prependSystemMessage`, `replaceSystemMessage`, `collapseSystemMessages`
181
+ - **Modification**: `redactMessageAtPosition`
182
+ - **Streaming**: `appendStreamingMessage`, `updateStreamingMessage`, `finalizeStreamingMessage`, `cancelStreamingMessage`
183
+ - **Context window**: `truncateFromPosition`, `truncateToTokenLimit`
184
+
185
+ ### Tool Call Pairing
186
+
187
+ ```typescript
188
+ import { pairToolCallsWithResults } from 'conversationalist';
189
+
190
+ const pairs = pairToolCallsWithResults(conversation.messages);
191
+ // Returns: [{ call: ToolCall, result?: ToolResult }, ...]
192
+ ```
193
+
194
+ ## Provider Adapters
195
+
196
+ Convert conversations to provider-specific formats using subpath exports:
197
+
198
+ ### OpenAI
199
+
200
+ ```typescript
201
+ import { toOpenAIMessages } from 'conversationalist/openai';
202
+
203
+ const messages = toOpenAIMessages(conversation);
204
+ const response = await openai.chat.completions.create({
205
+ model: 'gpt-4',
206
+ messages,
207
+ });
208
+ ```
209
+
210
+ ### Anthropic
211
+
212
+ ```typescript
213
+ import { toAnthropicMessages } from 'conversationalist/anthropic';
214
+
215
+ const { system, messages } = toAnthropicMessages(conversation);
216
+ const response = await anthropic.messages.create({
217
+ model: 'claude-3-opus-20240229',
218
+ system,
219
+ messages,
220
+ });
221
+ ```
222
+
223
+ ### Google Gemini
224
+
225
+ ```typescript
226
+ import { toGeminiMessages } from 'conversationalist/gemini';
227
+
228
+ const { systemInstruction, contents } = toGeminiMessages(conversation);
229
+ const response = await model.generateContent({
230
+ systemInstruction,
231
+ contents,
232
+ });
233
+ ```
234
+
235
+ ## Streaming Support
236
+
237
+ Handle streaming responses with pending message utilities:
238
+
239
+ ```typescript
240
+ import {
241
+ appendStreamingMessage,
242
+ updateStreamingMessage,
243
+ finalizeStreamingMessage,
244
+ cancelStreamingMessage,
245
+ isStreamingMessage,
246
+ getStreamingMessage,
247
+ } from 'conversationalist';
248
+
249
+ // Start a streaming message
250
+ let { conversation, messageId } = appendStreamingMessage(conversation, 'assistant');
251
+
252
+ // Update content as chunks arrive
253
+ for await (const chunk of stream) {
254
+ accumulatedContent += chunk;
255
+ conversation = updateStreamingMessage(conversation, messageId, accumulatedContent);
256
+ }
257
+
258
+ // Finalize when complete
259
+ conversation = finalizeStreamingMessage(conversation, messageId, {
260
+ tokenUsage: { prompt: 100, completion: 50, total: 150 },
261
+ });
262
+
263
+ // Or cancel if needed
264
+ conversation = cancelStreamingMessage(conversation, messageId);
265
+ ```
266
+
267
+ ## Context Window Utilities
268
+
269
+ Manage token limits and message truncation:
270
+
271
+ ```typescript
272
+ import {
273
+ getRecentMessages,
274
+ truncateFromPosition,
275
+ truncateToTokenLimit,
276
+ estimateConversationTokens,
277
+ simpleTokenEstimator,
278
+ } from 'conversationalist';
279
+
280
+ // Get last N messages (excluding system by default)
281
+ const recent = getRecentMessages(conversation, 10);
282
+
283
+ // Truncate to messages from position onwards
284
+ const truncated = truncateFromPosition(conversation, 5);
285
+
286
+ // Truncate to fit token limit
287
+ const fitted = truncateToTokenLimit(conversation, 4000, simpleTokenEstimator, {
288
+ preserveSystemMessages: true,
289
+ preserveLastN: 2,
290
+ });
291
+
292
+ // Estimate total tokens
293
+ const tokens = estimateConversationTokens(conversation, simpleTokenEstimator);
294
+ ```
295
+
296
+ ## Types
297
+
298
+ ```typescript
299
+ import type {
300
+ Conversation,
301
+ ConversationJSON,
302
+ ConversationStatus,
303
+ Message,
304
+ MessageInput,
305
+ MessageJSON,
306
+ MessageRole,
307
+ TokenUsage,
308
+ ToolCall,
309
+ ToolResult,
310
+ MultiModalContent,
311
+ TextContent,
312
+ ImageContent,
313
+ } from 'conversationalist';
314
+ ```
315
+
316
+ ## Validation Schemas
317
+
318
+ Zod schemas are exported for runtime validation:
319
+
320
+ ```typescript
321
+ import {
322
+ conversationSchema,
323
+ messageInputSchema,
324
+ messageJSONSchema,
325
+ messageRoleSchema,
326
+ multiModalContentSchema,
327
+ tokenUsageSchema,
328
+ toolCallSchema,
329
+ toolResultSchema,
330
+ } from 'conversationalist';
331
+
332
+ const result = messageInputSchema.safeParse(data);
333
+ ```
334
+
335
+ ## Error Handling
336
+
337
+ Custom error types with codes:
338
+
339
+ ```typescript
340
+ import {
341
+ ConversationalistError,
342
+ createInvalidInputError,
343
+ createInvalidPositionError,
344
+ createNotFoundError,
345
+ createValidationError,
346
+ } from 'conversationalist';
347
+
348
+ try {
349
+ // ...
350
+ } catch (error) {
351
+ if (error instanceof ConversationalistError) {
352
+ console.log(error.code); // e.g., 'INVALID_POSITION'
353
+ }
354
+ }
355
+ ```
356
+
357
+ ## Development
358
+
359
+ ```bash
360
+ bun install
361
+ bun test
362
+ bun run typecheck
363
+ bun run lint
364
+ bun run build
365
+ ```
366
+
367
+ ## License
368
+
369
+ MIT
@@ -0,0 +1,76 @@
1
+ import type { Conversation } from '../../types';
2
+ /**
3
+ * Anthropic text content block.
4
+ */
5
+ export interface AnthropicTextBlock {
6
+ type: 'text';
7
+ text: string;
8
+ }
9
+ /**
10
+ * Anthropic image content block.
11
+ */
12
+ export interface AnthropicImageBlock {
13
+ type: 'image';
14
+ source: {
15
+ type: 'base64' | 'url';
16
+ media_type?: string;
17
+ data?: string;
18
+ url?: string;
19
+ };
20
+ }
21
+ /**
22
+ * Anthropic tool use content block.
23
+ */
24
+ export interface AnthropicToolUseBlock {
25
+ type: 'tool_use';
26
+ id: string;
27
+ name: string;
28
+ input: unknown;
29
+ }
30
+ /**
31
+ * Anthropic tool result content block.
32
+ */
33
+ export interface AnthropicToolResultBlock {
34
+ type: 'tool_result';
35
+ tool_use_id: string;
36
+ content: string;
37
+ is_error?: boolean;
38
+ }
39
+ /**
40
+ * Anthropic content block union type.
41
+ */
42
+ export type AnthropicContentBlock = AnthropicTextBlock | AnthropicImageBlock | AnthropicToolUseBlock | AnthropicToolResultBlock;
43
+ /**
44
+ * Anthropic message format for the Messages API.
45
+ */
46
+ export interface AnthropicMessage {
47
+ role: 'user' | 'assistant';
48
+ content: string | AnthropicContentBlock[];
49
+ }
50
+ /**
51
+ * Result of converting a conversation to Anthropic format.
52
+ * System messages are extracted separately since Anthropic uses a top-level system parameter.
53
+ */
54
+ export interface AnthropicConversation {
55
+ system?: string;
56
+ messages: AnthropicMessage[];
57
+ }
58
+ /**
59
+ * Converts a conversation to Anthropic Messages API format.
60
+ * System messages are extracted to the top-level `system` field.
61
+ * Tool calls become tool_use blocks, tool results become tool_result blocks.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { toAnthropicMessages } from 'conversationalist/anthropic';
66
+ *
67
+ * const { system, messages } = toAnthropicMessages(conversation);
68
+ * const response = await anthropic.messages.create({
69
+ * model: 'claude-3-opus-20240229',
70
+ * system,
71
+ * messages,
72
+ * });
73
+ * ```
74
+ */
75
+ export declare function toAnthropicMessages(conversation: Conversation): AnthropicConversation;
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/anthropic/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAiC,MAAM,aAAa,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAC7B,kBAAkB,GAClB,mBAAmB,GACnB,qBAAqB,GACrB,wBAAwB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AA+GD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,GAAG,qBAAqB,CAiFrF"}
@@ -0,0 +1,147 @@
1
+ // src/adapters/anthropic/index.ts
2
+ function toAnthropicContent(content) {
3
+ if (typeof content === "string") {
4
+ return content;
5
+ }
6
+ const blocks = [];
7
+ for (const part of content) {
8
+ if (part.type === "text") {
9
+ blocks.push({ type: "text", text: part.text ?? "" });
10
+ } else if (part.type === "image") {
11
+ const url = part.url ?? "";
12
+ if (url.startsWith("data:")) {
13
+ const matches = url.match(/^data:([^;]+);base64,(.+)$/);
14
+ if (matches && matches[1] && matches[2]) {
15
+ blocks.push({
16
+ type: "image",
17
+ source: {
18
+ type: "base64",
19
+ media_type: matches[1],
20
+ data: matches[2]
21
+ }
22
+ });
23
+ }
24
+ } else {
25
+ blocks.push({
26
+ type: "image",
27
+ source: {
28
+ type: "url",
29
+ url
30
+ }
31
+ });
32
+ }
33
+ }
34
+ }
35
+ return blocks.length === 1 && blocks[0]?.type === "text" ? blocks[0].text : blocks;
36
+ }
37
+ function toToolUseBlock(toolCall) {
38
+ return {
39
+ type: "tool_use",
40
+ id: toolCall.id,
41
+ name: toolCall.name,
42
+ input: typeof toolCall.arguments === "string" ? JSON.parse(toolCall.arguments) : toolCall.arguments
43
+ };
44
+ }
45
+ function toToolResultBlock(toolResult) {
46
+ const result = {
47
+ type: "tool_result",
48
+ tool_use_id: toolResult.callId,
49
+ content: typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content)
50
+ };
51
+ if (toolResult.outcome === "error") {
52
+ result.is_error = true;
53
+ }
54
+ return result;
55
+ }
56
+ function extractSystemContent(messages) {
57
+ const systemMessages = messages.filter((m) => (m.role === "system" || m.role === "developer") && !m.hidden);
58
+ if (systemMessages.length === 0) {
59
+ return;
60
+ }
61
+ const parts = [];
62
+ for (const msg of systemMessages) {
63
+ if (typeof msg.content === "string") {
64
+ parts.push(msg.content);
65
+ } else {
66
+ for (const part of msg.content) {
67
+ if (part.type === "text") {
68
+ parts.push(part.text ?? "");
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return parts.join(`
74
+
75
+ `);
76
+ }
77
+ function toAnthropicMessages(conversation) {
78
+ const system = extractSystemContent(conversation.messages);
79
+ const messages = [];
80
+ let currentRole = null;
81
+ let currentBlocks = [];
82
+ const flushCurrent = () => {
83
+ if (currentRole && currentBlocks.length > 0) {
84
+ messages.push({
85
+ role: currentRole,
86
+ content: currentBlocks.length === 1 && currentBlocks[0]?.type === "text" ? currentBlocks[0].text : currentBlocks
87
+ });
88
+ currentBlocks = [];
89
+ }
90
+ currentRole = null;
91
+ };
92
+ for (const message of conversation.messages) {
93
+ if (message.hidden)
94
+ continue;
95
+ if (message.role === "system" || message.role === "developer") {
96
+ continue;
97
+ }
98
+ if (message.role === "snapshot") {
99
+ continue;
100
+ }
101
+ let targetRole;
102
+ let blocks = [];
103
+ if (message.role === "user") {
104
+ targetRole = "user";
105
+ const content = toAnthropicContent(message.content);
106
+ if (typeof content === "string") {
107
+ blocks = [{ type: "text", text: content }];
108
+ } else {
109
+ blocks = content;
110
+ }
111
+ } else if (message.role === "assistant") {
112
+ targetRole = "assistant";
113
+ const content = toAnthropicContent(message.content);
114
+ if (typeof content === "string") {
115
+ blocks = [{ type: "text", text: content }];
116
+ } else {
117
+ blocks = content;
118
+ }
119
+ } else if (message.role === "tool-use" && message.toolCall) {
120
+ targetRole = "assistant";
121
+ blocks = [toToolUseBlock(message.toolCall)];
122
+ } else if (message.role === "tool-result" && message.toolResult) {
123
+ targetRole = "user";
124
+ blocks = [toToolResultBlock(message.toolResult)];
125
+ } else {
126
+ continue;
127
+ }
128
+ if (currentRole === targetRole) {
129
+ currentBlocks.push(...blocks);
130
+ } else {
131
+ flushCurrent();
132
+ currentRole = targetRole;
133
+ currentBlocks = blocks;
134
+ }
135
+ }
136
+ flushCurrent();
137
+ const result = { messages };
138
+ if (system !== undefined) {
139
+ result.system = system;
140
+ }
141
+ return result;
142
+ }
143
+ export {
144
+ toAnthropicMessages
145
+ };
146
+
147
+ //# debugId=46DDFFB8D921BA7364756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/adapters/anthropic/index.ts"],
4
+ "sourcesContent": [
5
+ "import type { MultiModalContent } from '@lasercat/homogenaize';\n\nimport type { Conversation, Message, ToolCall, ToolResult } from '../../types';\n\n/**\n * Anthropic text content block.\n */\nexport interface AnthropicTextBlock {\n type: 'text';\n text: string;\n}\n\n/**\n * Anthropic image content block.\n */\nexport interface AnthropicImageBlock {\n type: 'image';\n source: {\n type: 'base64' | 'url';\n media_type?: string;\n data?: string;\n url?: string;\n };\n}\n\n/**\n * Anthropic tool use content block.\n */\nexport interface AnthropicToolUseBlock {\n type: 'tool_use';\n id: string;\n name: string;\n input: unknown;\n}\n\n/**\n * Anthropic tool result content block.\n */\nexport interface AnthropicToolResultBlock {\n type: 'tool_result';\n tool_use_id: string;\n content: string;\n is_error?: boolean;\n}\n\n/**\n * Anthropic content block union type.\n */\nexport type AnthropicContentBlock =\n | AnthropicTextBlock\n | AnthropicImageBlock\n | AnthropicToolUseBlock\n | AnthropicToolResultBlock;\n\n/**\n * Anthropic message format for the Messages API.\n */\nexport interface AnthropicMessage {\n role: 'user' | 'assistant';\n content: string | AnthropicContentBlock[];\n}\n\n/**\n * Result of converting a conversation to Anthropic format.\n * System messages are extracted separately since Anthropic uses a top-level system parameter.\n */\nexport interface AnthropicConversation {\n system?: string;\n messages: AnthropicMessage[];\n}\n\n/**\n * Converts internal multi-modal content to Anthropic content blocks.\n */\nfunction toAnthropicContent(\n content: string | ReadonlyArray<MultiModalContent>,\n): string | AnthropicContentBlock[] {\n if (typeof content === 'string') {\n return content;\n }\n\n const blocks: AnthropicContentBlock[] = [];\n for (const part of content) {\n if (part.type === 'text') {\n blocks.push({ type: 'text', text: part.text ?? '' });\n } else if (part.type === 'image') {\n // Anthropic supports both URL and base64\n const url = part.url ?? '';\n if (url.startsWith('data:')) {\n // Base64 data URL\n const matches = url.match(/^data:([^;]+);base64,(.+)$/);\n if (matches && matches[1] && matches[2]) {\n blocks.push({\n type: 'image',\n source: {\n type: 'base64',\n media_type: matches[1],\n data: matches[2],\n },\n });\n }\n } else {\n // Regular URL\n blocks.push({\n type: 'image',\n source: {\n type: 'url',\n url,\n },\n });\n }\n }\n }\n\n return blocks.length === 1 && blocks[0]?.type === 'text' ? blocks[0].text : blocks;\n}\n\n/**\n * Converts an internal ToolCall to Anthropic tool_use block.\n */\nfunction toToolUseBlock(toolCall: ToolCall): AnthropicToolUseBlock {\n return {\n type: 'tool_use',\n id: toolCall.id,\n name: toolCall.name,\n input:\n typeof toolCall.arguments === 'string'\n ? JSON.parse(toolCall.arguments)\n : toolCall.arguments,\n };\n}\n\n/**\n * Converts an internal ToolResult to Anthropic tool_result block.\n */\nfunction toToolResultBlock(toolResult: ToolResult): AnthropicToolResultBlock {\n const result: AnthropicToolResultBlock = {\n type: 'tool_result',\n tool_use_id: toolResult.callId,\n content:\n typeof toolResult.content === 'string'\n ? toolResult.content\n : JSON.stringify(toolResult.content),\n };\n\n if (toolResult.outcome === 'error') {\n result.is_error = true;\n }\n\n return result;\n}\n\n/**\n * Collects system message content from a conversation.\n */\nfunction extractSystemContent(messages: ReadonlyArray<Message>): string | undefined {\n const systemMessages = messages.filter(\n (m) => (m.role === 'system' || m.role === 'developer') && !m.hidden,\n );\n\n if (systemMessages.length === 0) {\n return undefined;\n }\n\n const parts: string[] = [];\n for (const msg of systemMessages) {\n if (typeof msg.content === 'string') {\n parts.push(msg.content);\n } else {\n for (const part of msg.content) {\n if (part.type === 'text') {\n parts.push(part.text ?? '');\n }\n }\n }\n }\n\n return parts.join('\\n\\n');\n}\n\n/**\n * Converts a conversation to Anthropic Messages API format.\n * System messages are extracted to the top-level `system` field.\n * Tool calls become tool_use blocks, tool results become tool_result blocks.\n *\n * @example\n * ```ts\n * import { toAnthropicMessages } from 'conversationalist/anthropic';\n *\n * const { system, messages } = toAnthropicMessages(conversation);\n * const response = await anthropic.messages.create({\n * model: 'claude-3-opus-20240229',\n * system,\n * messages,\n * });\n * ```\n */\nexport function toAnthropicMessages(conversation: Conversation): AnthropicConversation {\n const system = extractSystemContent(conversation.messages);\n const messages: AnthropicMessage[] = [];\n\n // Track pending content blocks to merge consecutive same-role messages\n let currentRole: 'user' | 'assistant' | null = null;\n let currentBlocks: AnthropicContentBlock[] = [];\n\n const flushCurrent = () => {\n if (currentRole && currentBlocks.length > 0) {\n messages.push({\n role: currentRole,\n content:\n currentBlocks.length === 1 && currentBlocks[0]?.type === 'text'\n ? currentBlocks[0].text\n : currentBlocks,\n });\n currentBlocks = [];\n }\n currentRole = null;\n };\n\n for (const message of conversation.messages) {\n if (message.hidden) continue;\n\n // Skip system messages (already extracted)\n if (message.role === 'system' || message.role === 'developer') {\n continue;\n }\n\n // Skip snapshots\n if (message.role === 'snapshot') {\n continue;\n }\n\n let targetRole: 'user' | 'assistant';\n let blocks: AnthropicContentBlock[] = [];\n\n if (message.role === 'user') {\n targetRole = 'user';\n const content = toAnthropicContent(message.content);\n if (typeof content === 'string') {\n blocks = [{ type: 'text', text: content }];\n } else {\n blocks = content;\n }\n } else if (message.role === 'assistant') {\n targetRole = 'assistant';\n const content = toAnthropicContent(message.content);\n if (typeof content === 'string') {\n blocks = [{ type: 'text', text: content }];\n } else {\n blocks = content;\n }\n } else if (message.role === 'tool-use' && message.toolCall) {\n targetRole = 'assistant';\n blocks = [toToolUseBlock(message.toolCall)];\n } else if (message.role === 'tool-result' && message.toolResult) {\n targetRole = 'user';\n blocks = [toToolResultBlock(message.toolResult)];\n } else {\n continue;\n }\n\n // Merge with current or start new\n if (currentRole === targetRole) {\n currentBlocks.push(...blocks);\n } else {\n flushCurrent();\n currentRole = targetRole;\n currentBlocks = blocks;\n }\n }\n\n flushCurrent();\n\n const result: AnthropicConversation = { messages };\n if (system !== undefined) {\n result.system = system;\n }\n return result;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "46DDFFB8D921BA7364756E2164756E21",
9
+ "names": []
10
+ }