conversationalist 0.0.7 → 0.0.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 (97) hide show
  1. package/README.md +158 -104
  2. package/dist/adapters/anthropic/index.d.ts +11 -6
  3. package/dist/adapters/anthropic/index.d.ts.map +1 -1
  4. package/dist/adapters/anthropic/index.js +16 -3
  5. package/dist/adapters/anthropic/index.js.map +5 -4
  6. package/dist/adapters/gemini/index.d.ts +2 -2
  7. package/dist/adapters/gemini/index.d.ts.map +1 -1
  8. package/dist/adapters/gemini/index.js +52 -9
  9. package/dist/adapters/gemini/index.js.map +5 -4
  10. package/dist/adapters/openai/index.d.ts +32 -5
  11. package/dist/adapters/openai/index.d.ts.map +1 -1
  12. package/dist/adapters/openai/index.js +30 -8
  13. package/dist/adapters/openai/index.js.map +5 -4
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/conversation/append.d.ts +4 -4
  16. package/dist/conversation/append.d.ts.map +1 -1
  17. package/dist/conversation/create.d.ts +2 -3
  18. package/dist/conversation/create.d.ts.map +1 -1
  19. package/dist/conversation/index.d.ts +2 -2
  20. package/dist/conversation/index.d.ts.map +1 -1
  21. package/dist/conversation/modify.d.ts.map +1 -1
  22. package/dist/conversation/query.d.ts +9 -5
  23. package/dist/conversation/query.d.ts.map +1 -1
  24. package/dist/conversation/serialization.d.ts +6 -28
  25. package/dist/conversation/serialization.d.ts.map +1 -1
  26. package/dist/conversation/system-messages.d.ts +3 -3
  27. package/dist/conversation/system-messages.d.ts.map +1 -1
  28. package/dist/conversation/transform.d.ts.map +1 -1
  29. package/dist/conversation.d.ts +74 -18
  30. package/dist/conversation.d.ts.map +1 -1
  31. package/dist/export/index.d.ts +7 -0
  32. package/dist/export/index.d.ts.map +1 -0
  33. package/dist/export/index.js +3762 -0
  34. package/dist/export/index.js.map +62 -0
  35. package/dist/history.d.ts +102 -24
  36. package/dist/history.d.ts.map +1 -1
  37. package/dist/index.d.ts +7 -8
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +392 -4075
  40. package/dist/index.js.map +15 -60
  41. package/dist/markdown/index.d.ts +15 -0
  42. package/dist/markdown/index.d.ts.map +1 -0
  43. package/dist/markdown/index.js +4969 -0
  44. package/dist/markdown/index.js.map +69 -0
  45. package/dist/message.d.ts +1 -1
  46. package/dist/message.d.ts.map +1 -1
  47. package/dist/multi-modal.d.ts +3 -0
  48. package/dist/multi-modal.d.ts.map +1 -1
  49. package/dist/plugins/index.d.ts +1 -1
  50. package/dist/plugins/index.d.ts.map +1 -1
  51. package/dist/plugins/index.js +59 -0
  52. package/dist/plugins/index.js.map +10 -0
  53. package/dist/plugins/pii-redaction.d.ts +10 -1
  54. package/dist/plugins/pii-redaction.d.ts.map +1 -1
  55. package/dist/redaction/index.d.ts +2 -0
  56. package/dist/redaction/index.d.ts.map +1 -0
  57. package/dist/redaction/index.js +59 -0
  58. package/dist/redaction/index.js.map +10 -0
  59. package/dist/schemas/index.d.ts +2 -0
  60. package/dist/schemas/index.d.ts.map +1 -0
  61. package/dist/schemas/index.js +114 -0
  62. package/dist/schemas/index.js.map +10 -0
  63. package/dist/schemas.d.ts +324 -15
  64. package/dist/schemas.d.ts.map +1 -1
  65. package/dist/sort/index.d.ts +2 -0
  66. package/dist/sort/index.d.ts.map +1 -0
  67. package/dist/sort/index.js +32 -0
  68. package/dist/sort/index.js.map +10 -0
  69. package/dist/streaming.d.ts +3 -3
  70. package/dist/streaming.d.ts.map +1 -1
  71. package/dist/types.d.ts +72 -50
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/utilities/index.d.ts +1 -3
  74. package/dist/utilities/index.d.ts.map +1 -1
  75. package/dist/utilities/line-endings.d.ts +5 -0
  76. package/dist/utilities/line-endings.d.ts.map +1 -0
  77. package/dist/utilities/markdown.d.ts +1 -1
  78. package/dist/utilities/markdown.d.ts.map +1 -1
  79. package/dist/utilities/message-store.d.ts +6 -0
  80. package/dist/utilities/message-store.d.ts.map +1 -0
  81. package/dist/utilities/message.d.ts +9 -3
  82. package/dist/utilities/message.d.ts.map +1 -1
  83. package/dist/utilities/tool-calls.d.ts +4 -4
  84. package/dist/utilities/tool-calls.d.ts.map +1 -1
  85. package/dist/utilities/tool-results.d.ts +10 -0
  86. package/dist/utilities/tool-results.d.ts.map +1 -0
  87. package/dist/utilities/transient.d.ts +2 -2
  88. package/dist/utilities/transient.d.ts.map +1 -1
  89. package/dist/utilities.d.ts +3 -5
  90. package/dist/utilities.d.ts.map +1 -1
  91. package/dist/versioning/index.d.ts +3 -0
  92. package/dist/versioning/index.d.ts.map +1 -0
  93. package/dist/versioning/index.js +58 -0
  94. package/dist/versioning/index.js.map +11 -0
  95. package/dist/with-conversation.d.ts +8 -8
  96. package/dist/with-conversation.d.ts.map +1 -1
  97. package/package.json +26 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ In a modern AI application, a conversation is more than just a list of strings.
13
13
 
14
14
  - **Tool Use**: Pairing function calls with their results and ensuring they stay in sync.
15
15
  - **Hidden Logic**: Internal "thought" messages or snapshots that should be saved but never sent to the provider.
16
- - **Metadata**: Tracking tags, custom IDs, and tokens across different steps.
16
+ - **Metadata**: Tracking custom IDs and tokens across different steps.
17
17
  - **Streaming**: Gracefully handling partial messages in a UI without messy state transitions.
18
18
 
19
19
  Conversationalist handles these complexities through a robust, type-safe API that treats your conversation as the "Single Source of Truth."
@@ -25,7 +25,7 @@ Managing LLM conversations manually often leads to "provider lock-in" or fragile
25
25
  - **Decoupling Logic from Providers**: Write your business logic once using Conversationalist's message model, and use adapters to talk to OpenAI, Anthropic, or Gemini.
26
26
  - **Built-in Context Management**: Automatically handle context window limits by truncating history while preserving critical system instructions or recent messages.
27
27
  - **Type Safety Out-of-the-Box**: Built with Zod and TypeScript, ensuring that your conversation data is valid at runtime and compile-time.
28
- - **Unified Serialization**: One standard format (`ConversationJSON`) for your database, your frontend, and your backend.
28
+ - **Unified Serialization**: One standard format (`Conversation`) for your database, your frontend, and your backend.
29
29
 
30
30
  ## The Immutable Advantage
31
31
 
@@ -65,7 +65,6 @@ import {
65
65
  appendAssistantMessage,
66
66
  appendUserMessage,
67
67
  createConversation,
68
- serializeConversation,
69
68
  } from 'conversationalist';
70
69
  import { toOpenAIMessages } from 'conversationalist/openai';
71
70
 
@@ -82,17 +81,14 @@ conversation = appendAssistantMessage(conversation, 'Let me check that for you.'
82
81
  // 3. Adapt for a provider
83
82
  const openAIMessages = toOpenAIMessages(conversation);
84
83
  // [{ role: 'user', content: 'Where is my order?' }, ...]
85
-
86
- // 4. Save to your database
87
- const data = serializeConversation(conversation);
88
- // db.save(data.id, JSON.stringify(data));
89
84
  ```
90
85
 
91
86
  ## Core Concepts
92
87
 
93
88
  ### Conversations
94
89
 
95
- A conversation is an immutable record with metadata, tags, timestamps, and ordered messages.
90
+ A conversation is an immutable record with metadata, timestamps, a `messages` record keyed
91
+ by message ID, and an `ids` array that preserves order.
96
92
 
97
93
  ```ts
98
94
  import { createConversation } from 'conversationalist';
@@ -101,17 +97,18 @@ const conversation = createConversation({
101
97
  title: 'My Chat',
102
98
  status: 'active',
103
99
  metadata: { customerId: 'cus_123' },
104
- tags: ['support', 'vip'],
105
100
  });
106
101
  ```
107
102
 
103
+ Conversations track message order via `conversation.ids`. Every mutation keeps `ids` in sync
104
+ with `messages`. Use `getMessages(conversation)` for ordered arrays, or
105
+ `getMessageIds()` if you just need the IDs.
106
+
108
107
  ### Messages
109
108
 
110
- Messages have roles and can contain text or multi-modal content. Optional fields include
111
- `metadata`, `hidden`, `tokenUsage`, `toolCall`, `toolResult`, and `goalCompleted`.
109
+ Messages have roles and can contain text or multi-modal content. Optional fields include `metadata`, `hidden`, `tokenUsage`, `toolCall`, and `toolResult`. Assistant messages can also include `goalCompleted` (see `AssistantMessage`). Use `isAssistantMessage` to narrow when you need `goalCompleted`. Metadata and tool payloads are typed as `JSONValue` so conversations remain JSON-serializable.
112
110
 
113
- **Roles**: `user`, `assistant`, `system`, `developer`, `tool-use`, `tool-result`, `snapshot`.
114
- The `snapshot` role is for internal state and is skipped by adapters.
111
+ **Roles**: `user`, `assistant`, `system`, `developer`, `tool-use`, `tool-result`, `snapshot`. The `snapshot` role is for internal state and is skipped by adapters.
115
112
 
116
113
  ```ts
117
114
  import { appendMessages } from 'conversationalist';
@@ -125,13 +122,11 @@ conversation = appendMessages(conversation, {
125
122
  });
126
123
  ```
127
124
 
128
- **Hidden messages** remain in history but are skipped by default when querying or adapting to
129
- providers. This is perfect for internal logging or "thinking" steps.
125
+ **Hidden messages** remain in history but are skipped by default when querying or adapting to providers. This is perfect for internal logging or "thinking" steps.
130
126
 
131
127
  ### Tool Calls
132
128
 
133
- Tool calls are represented as paired `tool-use` and `tool-result` messages. Tool results
134
- are validated to ensure the referenced call exists.
129
+ Tool calls are represented as paired `tool-use` and `tool-result` messages. Tool results are validated to ensure the referenced call exists.
135
130
 
136
131
  ```ts
137
132
  conversation = appendMessages(
@@ -220,19 +215,15 @@ boundTruncate(4000); // Uses tiktokenEstimator automatically
220
215
 
221
216
  ### Markdown Conversion
222
217
 
223
- Convert conversations to human-readable Markdown format, or parse Markdown back into a conversation object.
218
+ Convert conversations to human-readable Markdown format, or parse Markdown back into a conversation object. These helpers live in `conversationalist/markdown`.
224
219
 
225
220
  #### Basic Usage (Clean Markdown)
226
221
 
227
222
  By default, `toMarkdown` produces clean, readable Markdown without metadata:
228
223
 
229
224
  ```ts
230
- import {
231
- toMarkdown,
232
- fromMarkdown,
233
- createConversation,
234
- appendMessages,
235
- } from 'conversationalist';
225
+ import { appendMessages, createConversation } from 'conversationalist';
226
+ import { fromMarkdown, toMarkdown } from 'conversationalist/markdown';
236
227
 
237
228
  let conversation = createConversation({ id: 'conv-1' });
238
229
  conversation = appendMessages(
@@ -272,7 +263,6 @@ const markdown = toMarkdown(conversation, { includeMetadata: true });
272
263
  // id: conv-1
273
264
  // status: active
274
265
  // metadata: {}
275
- // tags: []
276
266
  // createdAt: '2024-01-15T10:00:00.000Z'
277
267
  // updatedAt: '2024-01-15T10:01:00.000Z'
278
268
  // messages:
@@ -298,7 +288,7 @@ const markdown = toMarkdown(conversation, { includeMetadata: true });
298
288
  // Parse back with all metadata preserved
299
289
  const restored = fromMarkdown(markdown);
300
290
  // restored.id === 'conv-1'
301
- // restored.messages[0].id === 'msg-1'
291
+ // restored.ids[0] === 'msg-1'
302
292
  ```
303
293
 
304
294
  #### Multi-Modal Content
@@ -326,18 +316,15 @@ const md = toMarkdown(conversation);
326
316
 
327
317
  ### PII Redaction Plugin
328
318
 
329
- The library includes a built-in `piiRedactionPlugin` that can automatically redact emails, phone numbers, and common API key patterns.
319
+ The library includes a built-in `redactPii` plugin that can automatically redact emails, phone numbers, and common API key patterns.
330
320
 
331
321
  ```ts
332
- import {
333
- appendUserMessage,
334
- createConversation,
335
- piiRedactionPlugin,
336
- } from 'conversationalist';
322
+ import { appendUserMessage, createConversation, getMessages } from 'conversationalist';
323
+ import { redactPii } from 'conversationalist/redaction';
337
324
 
338
325
  // 1. Enable by adding to your environment
339
326
  const env = {
340
- plugins: [piiRedactionPlugin],
327
+ plugins: [redactPii],
341
328
  };
342
329
 
343
330
  // 2. Use the environment when appending messages
@@ -349,7 +336,7 @@ conversation = appendUserMessage(
349
336
  env,
350
337
  );
351
338
 
352
- console.log(conversation.messages[0].content);
339
+ console.log(getMessages(conversation)[0]?.content);
353
340
  // "Contact me at [EMAIL_REDACTED]"
354
341
  ```
355
342
 
@@ -357,7 +344,7 @@ When using `ConversationHistory`, you only need to provide the plugin once durin
357
344
 
358
345
  ```ts
359
346
  const history = new ConversationHistory(createConversation(), {
360
- plugins: [piiRedactionPlugin],
347
+ plugins: [redactPii],
361
348
  });
362
349
 
363
350
  const appendUser = history.bind(appendUserMessage);
@@ -374,6 +361,7 @@ import { toAnthropicMessages } from 'conversationalist/anthropic';
374
361
  import { toGeminiMessages } from 'conversationalist/gemini';
375
362
  ```
376
363
 
364
+ - Adapter outputs are SDK-compatible (OpenAI `ChatCompletionMessageParam[]`, Anthropic `MessageParam[]`, Gemini `Content[]`).
377
365
  - **OpenAI**: Supports `toOpenAIMessages` and `toOpenAIMessagesGrouped` (which groups consecutive tool calls).
378
366
  - **Anthropic**: Maps system messages and tool blocks to Anthropic's specific format.
379
367
  - **Gemini**: Handles Gemini's unique content/part structure.
@@ -533,11 +521,13 @@ history.truncateToTokenLimit(4000);
533
521
  const messages = history.getMessages();
534
522
  const stats = history.getStatistics();
535
523
  const tokens = history.estimateTokens();
524
+ const ids = history.ids;
525
+ const firstMessage = history.get(ids[0]!);
536
526
  ```
537
527
 
538
528
  ### Event Subscription
539
529
 
540
- `ConversationHistory` implements `EventTarget` and follows the Svelte store contract. You can listen for changes using standard DOM events or the `subscribe` method.
530
+ `ConversationHistory` implements `EventTarget` (and follows the Svelte store contract). You can listen for changes using standard DOM events or the `subscribe` method.
541
531
 
542
532
  #### Using DOM Events
543
533
 
@@ -587,10 +577,10 @@ history.undo();
587
577
  history.appendUserMessage('Path B');
588
578
 
589
579
  console.log(history.branchCount); // 2
590
- console.log(history.current.messages[0].content); // "Path B"
580
+ console.log(history.getMessages()[0]?.content); // "Path B"
591
581
 
592
582
  history.switchToBranch(0);
593
- console.log(history.current.messages[0].content); // "Path A"
583
+ console.log(history.getMessages()[0]?.content); // "Path A"
594
584
  ```
595
585
 
596
586
  ### Serialization
@@ -598,15 +588,15 @@ console.log(history.current.messages[0].content); // "Path A"
598
588
  You can serialize the entire history tree (including all branches) to JSON and reconstruct it later.
599
589
 
600
590
  ```ts
601
- // 1. Save to JSON
602
- const json = history.toJSON();
603
- // localStorage.setItem('chat_history', JSON.stringify(json));
591
+ // 1. Capture a snapshot
592
+ const snapshot = history.snapshot();
593
+ // localStorage.setItem('chat_history', JSON.stringify(snapshot));
604
594
 
605
- // 2. Restore from JSON
606
- const restored = ConversationHistory.from(json);
595
+ // 2. Restore from a snapshot
596
+ const restored = ConversationHistory.from(snapshot);
607
597
 
608
598
  // You can also provide a new environment (e.g. with fresh token counters)
609
- const restoredWithEnv = ConversationHistory.from(json, {
599
+ const restoredWithEnv = ConversationHistory.from(snapshot, {
610
600
  estimateTokens: myNewEstimator,
611
601
  });
612
602
  ```
@@ -615,44 +605,25 @@ const restoredWithEnv = ConversationHistory.from(json, {
615
605
 
616
606
  ### Schema Versioning
617
607
 
618
- Conversations include a `schemaVersion` field for forward compatibility. When loading older data, use `migrateConversationJSON` to upgrade it to the current schema:
608
+ Conversations include a `schemaVersion` field for forward compatibility. When loading older data, use `migrateConversation` to upgrade it to the current schema:
619
609
 
620
610
  ```ts
611
+ import { deserializeConversation } from 'conversationalist';
621
612
  import {
622
- migrateConversationJSON,
623
- deserializeConversation,
613
+ migrateConversation,
624
614
  CURRENT_SCHEMA_VERSION,
625
- } from 'conversationalist';
615
+ } from 'conversationalist/versioning';
626
616
 
627
617
  // Old data without schemaVersion
628
618
  const legacyData = JSON.parse(oldStorage);
629
- const migrated = migrateConversationJSON(legacyData);
619
+ const migrated = migrateConversation(legacyData);
630
620
  // migrated.schemaVersion === CURRENT_SCHEMA_VERSION
631
621
 
632
622
  const conversation = deserializeConversation(migrated);
633
623
  ```
634
624
 
635
- ### Serialization Options
636
-
637
- `serializeConversation` accepts options for controlling the output:
638
-
639
- ```ts
640
- import { serializeConversation } from 'conversationalist';
641
-
642
- const json = serializeConversation(conversation, {
643
- // Sort keys and messages for stable, diff-friendly output
644
- deterministic: true,
645
-
646
- // Remove metadata keys starting with '_' (transient UI state)
647
- stripTransient: true,
648
-
649
- // Replace tool arguments with '[REDACTED]'
650
- redactToolArguments: true,
651
-
652
- // Replace tool result content with '[REDACTED]'
653
- redactToolResults: true,
654
- });
655
- ```
625
+ Conversations are already JSON-serializable; persist them directly and apply utilities
626
+ like `stripTransientMetadata` or `redactMessageAtPosition` when you need to sanitize data.
656
627
 
657
628
  ### Transient Metadata Convention
658
629
 
@@ -677,12 +648,12 @@ stripTransientFromRecord({ _loading: true, source: 'web' });
677
648
  const cleaned = stripTransientMetadata(conversation);
678
649
  ```
679
650
 
680
- ### Deterministic Output
651
+ ### Sort Utilities
681
652
 
682
- For reproducible snapshots or tests, use the deterministic utilities:
653
+ For reproducible snapshots or tests, use the sort utilities:
683
654
 
684
655
  ```ts
685
- import { sortObjectKeys, sortMessagesByPosition } from 'conversationalist';
656
+ import { sortObjectKeys, sortMessagesByPosition } from 'conversationalist/sort';
686
657
 
687
658
  // Sort object keys alphabetically (recursive)
688
659
  const sorted = sortObjectKeys({ z: 1, a: 2, nested: { b: 3, a: 4 } });
@@ -702,7 +673,7 @@ import {
702
673
  LABEL_TO_ROLE,
703
674
  getRoleLabel,
704
675
  getRoleFromLabel,
705
- } from 'conversationalist';
676
+ } from 'conversationalist/markdown';
706
677
 
707
678
  // Get display label for a role
708
679
  getRoleLabel('tool-use'); // 'Tool Use'
@@ -722,8 +693,16 @@ LABEL_TO_ROLE['System']; // 'system'
722
693
  You can also convert a conversation to Markdown format for human-readable storage or export, and restore it later.
723
694
 
724
695
  ```ts
696
+ import { ConversationHistory } from 'conversationalist';
697
+ import {
698
+ conversationHistoryFromMarkdown,
699
+ conversationHistoryToMarkdown,
700
+ } from 'conversationalist/markdown';
701
+
702
+ const history = new ConversationHistory();
703
+
725
704
  // Export to clean, readable Markdown
726
- const markdown = history.toMarkdown();
705
+ const markdown = conversationHistoryToMarkdown(history);
727
706
  // ### User
728
707
  //
729
708
  // Hello!
@@ -733,10 +712,32 @@ const markdown = history.toMarkdown();
733
712
  // Hi there!
734
713
 
735
714
  // Export with full metadata (lossless round-trip)
736
- const markdownWithMetadata = history.toMarkdown({ includeMetadata: true });
715
+ const markdownWithMetadata = conversationHistoryToMarkdown(history, {
716
+ includeMetadata: true,
717
+ });
718
+
719
+ // Export with additional controls (redaction, transient stripping, hidden handling)
720
+ const markdownSafe = conversationHistoryToMarkdown(history, {
721
+ includeMetadata: true,
722
+ stripTransient: true,
723
+ redactToolArguments: true,
724
+ redactToolResults: true,
725
+ includeHidden: false,
726
+ });
737
727
 
738
728
  // Restore from Markdown
739
- const restored = ConversationHistory.fromMarkdown(markdownWithMetadata);
729
+ const restored = conversationHistoryFromMarkdown(markdownWithMetadata);
730
+ ```
731
+
732
+ ### Export Helpers
733
+
734
+ For markdown export workflows, use the built-in helpers:
735
+
736
+ ```ts
737
+ import { exportMarkdown, normalizeLineEndings } from 'conversationalist/export';
738
+
739
+ const normalizedMarkdown = exportMarkdown(conversation, { includeMetadata: true });
740
+ const normalized = normalizeLineEndings('line1\r\nline2');
740
741
  ```
741
742
 
742
743
  ## Integration
@@ -747,7 +748,7 @@ Because **Conversationalist** is immutable, it works perfectly with React's `use
747
748
 
748
749
  ```tsx
749
750
  import { useState } from 'react';
750
- import { createConversation, appendUserMessage } from 'conversationalist';
751
+ import { appendUserMessage, createConversation, getMessages } from 'conversationalist';
751
752
 
752
753
  export function ChatApp() {
753
754
  const [conversation, setConversation] = useState(() => createConversation());
@@ -759,7 +760,7 @@ export function ChatApp() {
759
760
 
760
761
  return (
761
762
  <div>
762
- {conversation.messages.map((m) => (
763
+ {getMessages(conversation).map((m) => (
763
764
  <div key={m.id}>{String(m.content)}</div>
764
765
  ))}
765
766
  <button onClick={() => handleSend('Hello!')}>Send</button>
@@ -774,15 +775,11 @@ For more complex applications, you can wrap the logic into a custom hook. This e
774
775
 
775
776
  ```tsx
776
777
  import { useState, useCallback, useEffect } from 'react';
777
- import { createConversation, ConversationHistory } from 'conversationalist';
778
+ import { ConversationHistory, createConversation, getMessages } from 'conversationalist';
778
779
 
779
- export function useChat(initialTitle?: string) {
780
+ export function useChat() {
780
781
  // 1. Initialize history (this could also come from context or props)
781
- const [history] = useState(() =>
782
- initialTitle
783
- ? new ConversationHistory(createConversation({ title: initialTitle }))
784
- : new ConversationHistory(),
785
- );
782
+ const [history] = useState(() => new ConversationHistory());
786
783
 
787
784
  // 2. Sync history with local state for reactivity
788
785
  const [conversation, setConversation] = useState(history.current);
@@ -818,7 +815,7 @@ export function useChat(initialTitle?: string) {
818
815
 
819
816
  return {
820
817
  conversation,
821
- messages: conversation.messages,
818
+ messages: getMessages(conversation),
822
819
  loading,
823
820
  sendMessage,
824
821
  undo: () => history.undo(),
@@ -862,7 +859,11 @@ In Svelte 5, you can manage conversation state using the `$state` rune. Since **
862
859
 
863
860
  ```svelte
864
861
  <script lang="ts">
865
- import { createConversation, appendUserMessage } from 'conversationalist';
862
+ import {
863
+ appendUserMessage,
864
+ createConversation,
865
+ getMessages,
866
+ } from 'conversationalist';
866
867
 
867
868
  let conversation = $state(createConversation());
868
869
 
@@ -872,7 +873,7 @@ In Svelte 5, you can manage conversation state using the `$state` rune. Since **
872
873
  </script>
873
874
 
874
875
  <div>
875
- {#each conversation.messages as m (m.id)}
876
+ {#each getMessages(conversation) as m (m.id)}
876
877
  <div>{String(m.content)}</div>
877
878
  {/each}
878
879
  <button onclick={() => handleSend('Hello!')}>Send</button>
@@ -885,14 +886,14 @@ Svelte 5's runes pair perfectly with **Conversationalist**. You can use the `Con
885
886
 
886
887
  ```svelte
887
888
  <script lang="ts">
888
- import { ConversationHistory } from 'conversationalist';
889
+ import { ConversationHistory, getMessages } from 'conversationalist';
889
890
 
890
891
  // history implements the Svelte store contract
891
892
  const history = new ConversationHistory();
892
893
  </script>
893
894
 
894
895
  <div>
895
- {#each $history.messages as m (m.id)}
896
+ {#each getMessages($history) as m (m.id)}
896
897
  <div>{String(m.content)}</div>
897
898
  {/each}
898
899
  <button onclick={() => history.appendUserMessage('Hello!')}>
@@ -905,19 +906,72 @@ Svelte 5's runes pair perfectly with **Conversationalist**. You can use the `Con
905
906
 
906
907
  ## API Overview
907
908
 
908
- | Category | Key Functions |
909
- | :---------------- | :------------------------------------------------------------------------------------------------------- |
910
- | **Creation** | `createConversation`, `serializeConversation`, `deserializeConversation`, `migrateConversationJSON` |
911
- | **Appending** | `appendUserMessage`, `appendAssistantMessage`, `appendSystemMessage`, `appendMessages` |
912
- | **Streaming** | `appendStreamingMessage`, `updateStreamingMessage`, `finalizeStreamingMessage`, `cancelStreamingMessage` |
913
- | **Modification** | `redactMessageAtPosition`, `replaceSystemMessage`, `collapseSystemMessages` |
914
- | **Context** | `truncateToTokenLimit`, `getRecentMessages`, `estimateConversationTokens` |
915
- | **Querying** | `getConversationMessages`, `getMessageByIdentifier`, `computeConversationStatistics` |
916
- | **Conversion** | `toMarkdown`, `fromMarkdown`, `toChatMessages`, `pairToolCallsWithResults` |
917
- | **Role Labels** | `ROLE_LABELS`, `LABEL_TO_ROLE`, `getRoleLabel`, `getRoleFromLabel` |
918
- | **Transient** | `isTransientKey`, `stripTransientFromRecord`, `stripTransientMetadata` |
919
- | **Deterministic** | `sortObjectKeys`, `sortMessagesByPosition` |
920
- | **History** | `ConversationHistory`, `bindToConversationHistory` |
909
+ | Category | Key Functions |
910
+ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
911
+ | **Creation** | `createConversation`, `deserializeConversation` |
912
+ | **Appending** | `appendUserMessage`, `appendAssistantMessage`, `appendSystemMessage`, `appendMessages` |
913
+ | **Streaming** | `appendStreamingMessage`, `updateStreamingMessage`, `finalizeStreamingMessage`, `cancelStreamingMessage` |
914
+ | **Modification** | `redactMessageAtPosition`, `replaceSystemMessage`, `collapseSystemMessages` |
915
+ | **Context** | `truncateToTokenLimit`, `getRecentMessages`, `estimateConversationTokens` |
916
+ | **Querying** | `getMessages`, `getMessageIds`, `getMessageById`, `getStatistics` |
917
+ | **Conversion** | `toChatMessages`, `pairToolCallsWithResults` |
918
+ | **Markdown** | `toMarkdown`, `fromMarkdown`, `conversationHistoryToMarkdown`, `conversationHistoryFromMarkdown` (from `conversationalist/markdown`) |
919
+ | **Export** | `exportMarkdown`, `normalizeLineEndings` (from `conversationalist/export`) |
920
+ | **Schemas** | `conversationSchema`, `messageJSONSchema`, `messageInputSchema`, `messageRoleSchema`, `multiModalContentSchema`, `jsonValueSchema`, `toolCallSchema`, `toolResultSchema`, `tokenUsageSchema` (from `conversationalist/schemas`) |
921
+ | **Role Labels** | `ROLE_LABELS`, `LABEL_TO_ROLE`, `getRoleLabel`, `getRoleFromLabel` (from `conversationalist/markdown`) |
922
+ | **Transient** | `isTransientKey`, `stripTransientFromRecord`, `stripTransientMetadata` |
923
+ | **Redaction** | `redactPii`, `createPIIRedactionPlugin`, `createPIIRedaction`, `DEFAULT_PII_RULES` (from `conversationalist/redaction`) |
924
+ | **Versioning** | `migrateConversation`, `CURRENT_SCHEMA_VERSION` (from `conversationalist/versioning`) |
925
+ | **Sort** | `sortObjectKeys`, `sortMessagesByPosition` (from `conversationalist/sort`) |
926
+ | **History** | `ConversationHistory` |
927
+
928
+ ## Standard Schema Compliance
929
+
930
+ All exported Zod schemas implement the [Standard Schema](https://standardschema.dev/) specification via Zod's built-in support. This means they can be used with any Standard Schema-compatible tool without library-specific adapters.
931
+
932
+ ### Exported Schemas
933
+
934
+ | Schema | Purpose |
935
+ | :------------------------ | :---------------------------------- |
936
+ | `conversationSchema` | Complete conversation with metadata |
937
+ | `jsonValueSchema` | JSON-serializable values |
938
+ | `messageJSONSchema` | Serialized message format |
939
+ | `messageInputSchema` | Input for creating messages |
940
+ | `messageRoleSchema` | Valid message roles enum |
941
+ | `multiModalContentSchema` | Text or image content |
942
+ | `toolCallSchema` | Tool function calls |
943
+ | `toolResultSchema` | Tool execution results |
944
+ | `tokenUsageSchema` | Token usage statistics |
945
+
946
+ ### Usage with Standard Schema Consumers
947
+
948
+ ```ts
949
+ import { conversationSchema } from 'conversationalist/schemas';
950
+
951
+ // Access the Standard Schema interface
952
+ const standardSchema = conversationSchema['~standard'];
953
+
954
+ // Use with any Standard Schema consumer
955
+ const result = standardSchema.validate(unknownData);
956
+ if (result.issues) {
957
+ console.error('Validation failed:', result.issues);
958
+ } else {
959
+ console.log('Valid conversation:', result.value);
960
+ }
961
+ ```
962
+
963
+ ### Type Inference
964
+
965
+ Standard Schema preserves type information:
966
+
967
+ ```ts
968
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
969
+ import { conversationSchema } from 'conversationalist/schemas';
970
+
971
+ // Type is inferred correctly
972
+ type ConversationInput = StandardSchemaV1.InferInput<typeof conversationSchema>;
973
+ type ConversationOutput = StandardSchemaV1.InferOutput<typeof conversationSchema>;
974
+ ```
921
975
 
922
976
  ## Deterministic Environments (Testing)
923
977
 
@@ -9,14 +9,19 @@ export interface AnthropicTextBlock {
9
9
  /**
10
10
  * Anthropic image content block.
11
11
  */
12
+ export interface AnthropicBase64ImageSource {
13
+ type: 'base64';
14
+ media_type: string;
15
+ data: string;
16
+ }
17
+ export interface AnthropicUrlImageSource {
18
+ type: 'url';
19
+ url: string;
20
+ }
21
+ export type AnthropicImageSource = AnthropicBase64ImageSource | AnthropicUrlImageSource;
12
22
  export interface AnthropicImageBlock {
13
23
  type: 'image';
14
- source: {
15
- type: 'base64' | 'url';
16
- media_type?: string;
17
- data?: string;
18
- url?: string;
19
- };
24
+ source: AnthropicImageSource;
20
25
  }
21
26
  /**
22
27
  * Anthropic tool use content block.
@@ -1 +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"}
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;AAG/E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,uBAAuB,CAAC;AAExF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,oBAAoB,CAAC;CAC9B;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,CAkFrF"}
@@ -1,3 +1,15 @@
1
+ // src/utilities/message-store.ts
2
+ function getOrderedMessages(conversation) {
3
+ const ordered = [];
4
+ for (const id of conversation.ids) {
5
+ const message = conversation.messages[id];
6
+ if (message) {
7
+ ordered.push(message);
8
+ }
9
+ }
10
+ return ordered;
11
+ }
12
+
1
13
  // src/adapters/anthropic/index.ts
2
14
  function toAnthropicContent(content) {
3
15
  if (typeof content === "string") {
@@ -75,7 +87,8 @@ function extractSystemContent(messages) {
75
87
  `);
76
88
  }
77
89
  function toAnthropicMessages(conversation) {
78
- const system = extractSystemContent(conversation.messages);
90
+ const ordered = getOrderedMessages(conversation);
91
+ const system = extractSystemContent(ordered);
79
92
  const messages = [];
80
93
  let currentRole = null;
81
94
  let currentBlocks = [];
@@ -89,7 +102,7 @@ function toAnthropicMessages(conversation) {
89
102
  }
90
103
  currentRole = null;
91
104
  };
92
- for (const message of conversation.messages) {
105
+ for (const message of ordered) {
93
106
  if (message.hidden)
94
107
  continue;
95
108
  if (message.role === "system" || message.role === "developer") {
@@ -144,4 +157,4 @@ export {
144
157
  toAnthropicMessages
145
158
  };
146
159
 
147
- //# debugId=46DDFFB8D921BA7364756E2164756E21
160
+ //# debugId=77199835081EDC2864756E2164756E21