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.
- package/README.md +158 -104
- package/dist/adapters/anthropic/index.d.ts +11 -6
- package/dist/adapters/anthropic/index.d.ts.map +1 -1
- package/dist/adapters/anthropic/index.js +16 -3
- package/dist/adapters/anthropic/index.js.map +5 -4
- package/dist/adapters/gemini/index.d.ts +2 -2
- package/dist/adapters/gemini/index.d.ts.map +1 -1
- package/dist/adapters/gemini/index.js +52 -9
- package/dist/adapters/gemini/index.js.map +5 -4
- package/dist/adapters/openai/index.d.ts +32 -5
- package/dist/adapters/openai/index.d.ts.map +1 -1
- package/dist/adapters/openai/index.js +30 -8
- package/dist/adapters/openai/index.js.map +5 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/conversation/append.d.ts +4 -4
- package/dist/conversation/append.d.ts.map +1 -1
- package/dist/conversation/create.d.ts +2 -3
- package/dist/conversation/create.d.ts.map +1 -1
- package/dist/conversation/index.d.ts +2 -2
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/modify.d.ts.map +1 -1
- package/dist/conversation/query.d.ts +9 -5
- package/dist/conversation/query.d.ts.map +1 -1
- package/dist/conversation/serialization.d.ts +6 -28
- package/dist/conversation/serialization.d.ts.map +1 -1
- package/dist/conversation/system-messages.d.ts +3 -3
- package/dist/conversation/system-messages.d.ts.map +1 -1
- package/dist/conversation/transform.d.ts.map +1 -1
- package/dist/conversation.d.ts +74 -18
- package/dist/conversation.d.ts.map +1 -1
- package/dist/export/index.d.ts +7 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +3762 -0
- package/dist/export/index.js.map +62 -0
- package/dist/history.d.ts +102 -24
- package/dist/history.d.ts.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +392 -4075
- package/dist/index.js.map +15 -60
- package/dist/markdown/index.d.ts +15 -0
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +4969 -0
- package/dist/markdown/index.js.map +69 -0
- package/dist/message.d.ts +1 -1
- package/dist/message.d.ts.map +1 -1
- package/dist/multi-modal.d.ts +3 -0
- package/dist/multi-modal.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +59 -0
- package/dist/plugins/index.js.map +10 -0
- package/dist/plugins/pii-redaction.d.ts +10 -1
- package/dist/plugins/pii-redaction.d.ts.map +1 -1
- package/dist/redaction/index.d.ts +2 -0
- package/dist/redaction/index.d.ts.map +1 -0
- package/dist/redaction/index.js +59 -0
- package/dist/redaction/index.js.map +10 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +114 -0
- package/dist/schemas/index.js.map +10 -0
- package/dist/schemas.d.ts +324 -15
- package/dist/schemas.d.ts.map +1 -1
- package/dist/sort/index.d.ts +2 -0
- package/dist/sort/index.d.ts.map +1 -0
- package/dist/sort/index.js +32 -0
- package/dist/sort/index.js.map +10 -0
- package/dist/streaming.d.ts +3 -3
- package/dist/streaming.d.ts.map +1 -1
- package/dist/types.d.ts +72 -50
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities/index.d.ts +1 -3
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/line-endings.d.ts +5 -0
- package/dist/utilities/line-endings.d.ts.map +1 -0
- package/dist/utilities/markdown.d.ts +1 -1
- package/dist/utilities/markdown.d.ts.map +1 -1
- package/dist/utilities/message-store.d.ts +6 -0
- package/dist/utilities/message-store.d.ts.map +1 -0
- package/dist/utilities/message.d.ts +9 -3
- package/dist/utilities/message.d.ts.map +1 -1
- package/dist/utilities/tool-calls.d.ts +4 -4
- package/dist/utilities/tool-calls.d.ts.map +1 -1
- package/dist/utilities/tool-results.d.ts +10 -0
- package/dist/utilities/tool-results.d.ts.map +1 -0
- package/dist/utilities/transient.d.ts +2 -2
- package/dist/utilities/transient.d.ts.map +1 -1
- package/dist/utilities.d.ts +3 -5
- package/dist/utilities.d.ts.map +1 -1
- package/dist/versioning/index.d.ts +3 -0
- package/dist/versioning/index.d.ts.map +1 -0
- package/dist/versioning/index.js +58 -0
- package/dist/versioning/index.js.map +11 -0
- package/dist/with-conversation.d.ts +8 -8
- package/dist/with-conversation.d.ts.map +1 -1
- 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
|
|
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 (`
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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 `
|
|
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
|
-
|
|
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: [
|
|
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
|
|
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: [
|
|
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.
|
|
580
|
+
console.log(history.getMessages()[0]?.content); // "Path B"
|
|
591
581
|
|
|
592
582
|
history.switchToBranch(0);
|
|
593
|
-
console.log(history.
|
|
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.
|
|
602
|
-
const
|
|
603
|
-
// localStorage.setItem('chat_history', JSON.stringify(
|
|
591
|
+
// 1. Capture a snapshot
|
|
592
|
+
const snapshot = history.snapshot();
|
|
593
|
+
// localStorage.setItem('chat_history', JSON.stringify(snapshot));
|
|
604
594
|
|
|
605
|
-
// 2. Restore from
|
|
606
|
-
const restored = ConversationHistory.from(
|
|
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(
|
|
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 `
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
###
|
|
651
|
+
### Sort Utilities
|
|
681
652
|
|
|
682
|
-
For reproducible snapshots or tests, use the
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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.
|
|
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,
|
|
778
|
+
import { ConversationHistory, createConversation, getMessages } from 'conversationalist';
|
|
778
779
|
|
|
779
|
-
export function useChat(
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
909
|
-
|
|
|
910
|
-
| **Creation**
|
|
911
|
-
| **Appending**
|
|
912
|
-
| **Streaming**
|
|
913
|
-
| **Modification**
|
|
914
|
-
| **Context**
|
|
915
|
-
| **Querying**
|
|
916
|
-
| **Conversion**
|
|
917
|
-
| **
|
|
918
|
-
| **
|
|
919
|
-
| **
|
|
920
|
-
| **
|
|
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;
|
|
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
|
|
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
|
|
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=
|
|
160
|
+
//# debugId=77199835081EDC2864756E2164756E21
|