conversationalist 0.0.10 → 0.0.11

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 (67) hide show
  1. package/README.md +89 -15
  2. package/dist/adapters/anthropic/index.d.ts.map +1 -1
  3. package/dist/adapters/anthropic/index.js +255 -2
  4. package/dist/adapters/anthropic/index.js.map +8 -4
  5. package/dist/adapters/gemini/index.d.ts.map +1 -1
  6. package/dist/adapters/gemini/index.js +255 -3
  7. package/dist/adapters/gemini/index.js.map +8 -4
  8. package/dist/adapters/openai/index.d.ts.map +1 -1
  9. package/dist/adapters/openai/index.js +247 -3
  10. package/dist/adapters/openai/index.js.map +8 -4
  11. package/dist/context.d.ts +6 -0
  12. package/dist/context.d.ts.map +1 -1
  13. package/dist/conversation/append.d.ts +5 -0
  14. package/dist/conversation/append.d.ts.map +1 -1
  15. package/dist/conversation/create.d.ts +9 -0
  16. package/dist/conversation/create.d.ts.map +1 -1
  17. package/dist/conversation/index.d.ts +8 -3
  18. package/dist/conversation/index.d.ts.map +1 -1
  19. package/dist/conversation/integrity.d.ts +16 -0
  20. package/dist/conversation/integrity.d.ts.map +1 -0
  21. package/dist/conversation/modify.d.ts +8 -2
  22. package/dist/conversation/modify.d.ts.map +1 -1
  23. package/dist/conversation/serialization.d.ts +0 -17
  24. package/dist/conversation/serialization.d.ts.map +1 -1
  25. package/dist/conversation/system-messages.d.ts.map +1 -1
  26. package/dist/conversation/tool-interactions.d.ts +45 -0
  27. package/dist/conversation/tool-interactions.d.ts.map +1 -0
  28. package/dist/conversation/transform.d.ts.map +1 -1
  29. package/dist/conversation/validation.d.ts +8 -0
  30. package/dist/conversation/validation.d.ts.map +1 -0
  31. package/dist/errors.d.ts +6 -1
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/export/index.js +249 -12
  34. package/dist/export/index.js.map +10 -6
  35. package/dist/history.d.ts +4 -1
  36. package/dist/history.d.ts.map +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +604 -231
  40. package/dist/index.js.map +24 -13
  41. package/dist/markdown/index.js +595 -215
  42. package/dist/markdown/index.js.map +22 -12
  43. package/dist/schemas/index.js +35 -19
  44. package/dist/schemas/index.js.map +3 -3
  45. package/dist/schemas.d.ts +15 -35
  46. package/dist/schemas.d.ts.map +1 -1
  47. package/dist/streaming.d.ts.map +1 -1
  48. package/dist/types.d.ts +0 -4
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/utilities/index.d.ts +0 -1
  51. package/dist/utilities/index.d.ts.map +1 -1
  52. package/dist/utilities/markdown.d.ts.map +1 -1
  53. package/dist/utilities/tool-calls.d.ts +2 -6
  54. package/dist/utilities/tool-calls.d.ts.map +1 -1
  55. package/dist/utilities/tool-results.d.ts.map +1 -1
  56. package/dist/utilities/transient.d.ts.map +1 -1
  57. package/dist/utilities.d.ts +0 -1
  58. package/dist/utilities.d.ts.map +1 -1
  59. package/dist/versioning/index.d.ts +0 -1
  60. package/dist/versioning/index.d.ts.map +1 -1
  61. package/dist/versioning/index.js +1 -52
  62. package/dist/versioning/index.js.map +4 -5
  63. package/dist/with-conversation.d.ts +4 -1
  64. package/dist/with-conversation.d.ts.map +1 -1
  65. package/package.json +6 -4
  66. package/dist/conversation.d.ts +0 -109
  67. package/dist/conversation.d.ts.map +0 -1
@@ -5001,6 +5001,263 @@ class EventEmission {
5001
5001
  }
5002
5002
  }
5003
5003
 
5004
+ // src/errors.ts
5005
+ class ConversationalistError extends Error {
5006
+ code;
5007
+ context;
5008
+ cause;
5009
+ constructor(code, message, options2) {
5010
+ super(message);
5011
+ this.name = "ConversationalistError";
5012
+ this.code = code;
5013
+ this.context = options2?.context;
5014
+ this.cause = options2?.cause;
5015
+ if (Error.captureStackTrace) {
5016
+ Error.captureStackTrace(this, ConversationalistError);
5017
+ }
5018
+ }
5019
+ toDetailedString() {
5020
+ const parts = [`[${this.code}] ${this.message}`];
5021
+ if (this.context && Object.keys(this.context).length > 0) {
5022
+ parts.push(`Context: ${JSON.stringify(this.context, null, 2)}`);
5023
+ }
5024
+ if (this.cause) {
5025
+ parts.push(`Caused by: ${this.cause.message}`);
5026
+ }
5027
+ return parts.join(`
5028
+ `);
5029
+ }
5030
+ }
5031
+ function createInvalidPositionError(expected, actual) {
5032
+ return new ConversationalistError("error:invalid-position", `invalid position: expected ${expected}, got ${actual}`, { context: { expected, actual } });
5033
+ }
5034
+ function createInvalidToolReferenceError(callId) {
5035
+ return new ConversationalistError("error:invalid-tool-reference", `tool result references non-existent tool-use: ${callId}`, { context: { callId } });
5036
+ }
5037
+ function createSerializationError(message, cause) {
5038
+ return new ConversationalistError("error:serialization", message, { cause });
5039
+ }
5040
+ function createValidationError(message, context, cause) {
5041
+ return new ConversationalistError("error:validation", message, { context, cause });
5042
+ }
5043
+ function createIntegrityError(message, context) {
5044
+ return new ConversationalistError("error:integrity", message, { context });
5045
+ }
5046
+
5047
+ // src/schemas.ts
5048
+ import { z } from "zod";
5049
+ var isPlainObject = (value) => {
5050
+ if (!value || typeof value !== "object")
5051
+ return false;
5052
+ const prototype = Reflect.getPrototypeOf(value);
5053
+ return prototype === Object.prototype || prototype === null;
5054
+ };
5055
+ function toMultiModalContent(value) {
5056
+ const result = { type: value.type };
5057
+ if (value.text !== undefined)
5058
+ result.text = value.text;
5059
+ if (value.url !== undefined)
5060
+ result.url = value.url;
5061
+ if (value.mimeType !== undefined)
5062
+ result.mimeType = value.mimeType;
5063
+ return result;
5064
+ }
5065
+ var jsonValueSchema = z.lazy(() => {
5066
+ const jsonObjectSchema = z.preprocess((value, ctx) => {
5067
+ if (!isPlainObject(value)) {
5068
+ ctx.addIssue({
5069
+ code: z.ZodIssueCode.custom,
5070
+ message: "expected a plain object"
5071
+ });
5072
+ return z.NEVER;
5073
+ }
5074
+ return value;
5075
+ }, z.record(z.string(), jsonValueSchema));
5076
+ return z.union([
5077
+ z.string(),
5078
+ z.number().refine((value) => Number.isFinite(value), {
5079
+ message: "expected a finite number"
5080
+ }),
5081
+ z.boolean(),
5082
+ z.null(),
5083
+ z.array(jsonValueSchema),
5084
+ jsonObjectSchema
5085
+ ]);
5086
+ });
5087
+ var multiModalContentSchema = z.discriminatedUnion("type", [
5088
+ z.object({
5089
+ type: z.literal("text"),
5090
+ text: z.string()
5091
+ }),
5092
+ z.object({
5093
+ type: z.literal("image"),
5094
+ url: z.string().url(),
5095
+ mimeType: z.string().optional(),
5096
+ text: z.string().optional()
5097
+ })
5098
+ ]).transform(toMultiModalContent);
5099
+ var messageRoleSchema = z.enum([
5100
+ "user",
5101
+ "assistant",
5102
+ "system",
5103
+ "developer",
5104
+ "tool-use",
5105
+ "tool-result",
5106
+ "snapshot"
5107
+ ]);
5108
+ var toolCallSchema = z.object({
5109
+ id: z.string(),
5110
+ name: z.string(),
5111
+ arguments: jsonValueSchema
5112
+ }).strict();
5113
+ var toolResultSchema = z.object({
5114
+ callId: z.string(),
5115
+ outcome: z.enum(["success", "error"]),
5116
+ content: jsonValueSchema
5117
+ }).strict();
5118
+ var tokenUsageSchema = z.object({
5119
+ prompt: z.number().int().min(0),
5120
+ completion: z.number().int().min(0),
5121
+ total: z.number().int().min(0)
5122
+ });
5123
+ var messageInputSchema = z.object({
5124
+ role: messageRoleSchema,
5125
+ content: z.union([z.string(), z.array(multiModalContentSchema)]),
5126
+ metadata: z.record(z.string(), jsonValueSchema).optional(),
5127
+ hidden: z.boolean().optional(),
5128
+ toolCall: toolCallSchema.optional(),
5129
+ toolResult: toolResultSchema.optional(),
5130
+ tokenUsage: tokenUsageSchema.optional(),
5131
+ goalCompleted: z.boolean().optional()
5132
+ }).strict();
5133
+ var messageSchema = z.object({
5134
+ id: z.string(),
5135
+ role: messageRoleSchema,
5136
+ content: z.union([z.string(), z.array(multiModalContentSchema)]),
5137
+ position: z.number().int().min(0),
5138
+ createdAt: z.string(),
5139
+ metadata: z.record(z.string(), jsonValueSchema),
5140
+ hidden: z.boolean(),
5141
+ toolCall: toolCallSchema.optional(),
5142
+ toolResult: toolResultSchema.optional(),
5143
+ tokenUsage: tokenUsageSchema.optional(),
5144
+ goalCompleted: z.boolean().optional()
5145
+ }).strict();
5146
+ var conversationStatusSchema = z.enum([
5147
+ "active",
5148
+ "archived",
5149
+ "deleted"
5150
+ ]);
5151
+ var conversationShape = {
5152
+ schemaVersion: z.number().int().min(1),
5153
+ id: z.string(),
5154
+ title: z.string().optional(),
5155
+ status: conversationStatusSchema,
5156
+ metadata: z.record(z.string(), jsonValueSchema),
5157
+ ids: z.array(z.string()),
5158
+ messages: z.record(z.string(), messageSchema),
5159
+ createdAt: z.string(),
5160
+ updatedAt: z.string()
5161
+ };
5162
+ var conversationSchema = z.object(conversationShape).strict();
5163
+
5164
+ // src/conversation/integrity.ts
5165
+ function validateConversationIntegrity(conversation) {
5166
+ const issues = [];
5167
+ const seenIds = new Set;
5168
+ conversation.ids.forEach((id, index) => {
5169
+ if (seenIds.has(id)) {
5170
+ issues.push({
5171
+ code: "integrity:duplicate-message-id",
5172
+ message: `duplicate message id in ids: ${id}`,
5173
+ data: { id, position: index }
5174
+ });
5175
+ } else {
5176
+ seenIds.add(id);
5177
+ }
5178
+ if (!conversation.messages[id]) {
5179
+ issues.push({
5180
+ code: "integrity:missing-message",
5181
+ message: `missing message for id ${id}`,
5182
+ data: { id, position: index }
5183
+ });
5184
+ }
5185
+ });
5186
+ for (const id of Object.keys(conversation.messages)) {
5187
+ if (!seenIds.has(id)) {
5188
+ issues.push({
5189
+ code: "integrity:unlisted-message",
5190
+ message: `message ${id} is not listed in ids`,
5191
+ data: { id }
5192
+ });
5193
+ }
5194
+ }
5195
+ const toolUses = new Map;
5196
+ conversation.ids.forEach((id, index) => {
5197
+ const message = conversation.messages[id];
5198
+ if (!message)
5199
+ return;
5200
+ if (message.role === "tool-use" && message.toolCall) {
5201
+ if (toolUses.has(message.toolCall.id)) {
5202
+ issues.push({
5203
+ code: "integrity:duplicate-tool-call",
5204
+ message: `duplicate toolCall.id ${message.toolCall.id}`,
5205
+ data: { toolCallId: message.toolCall.id, messageId: message.id }
5206
+ });
5207
+ } else {
5208
+ toolUses.set(message.toolCall.id, { position: index, messageId: message.id });
5209
+ }
5210
+ }
5211
+ });
5212
+ conversation.ids.forEach((id, index) => {
5213
+ const message = conversation.messages[id];
5214
+ if (!message)
5215
+ return;
5216
+ if (message.role === "tool-result" && message.toolResult) {
5217
+ const toolUse = toolUses.get(message.toolResult.callId);
5218
+ if (!toolUse) {
5219
+ issues.push({
5220
+ code: "integrity:orphan-tool-result",
5221
+ message: `tool-result references missing tool-use ${message.toolResult.callId}`,
5222
+ data: { callId: message.toolResult.callId, messageId: message.id }
5223
+ });
5224
+ } else if (toolUse.position >= index) {
5225
+ issues.push({
5226
+ code: "integrity:tool-result-before-call",
5227
+ message: `tool-result ${message.toolResult.callId} occurs before tool-use`,
5228
+ data: {
5229
+ callId: message.toolResult.callId,
5230
+ messageId: message.id,
5231
+ toolUseMessageId: toolUse.messageId
5232
+ }
5233
+ });
5234
+ }
5235
+ }
5236
+ });
5237
+ return issues;
5238
+ }
5239
+ function assertConversationIntegrity(conversation) {
5240
+ const issues = validateConversationIntegrity(conversation);
5241
+ if (issues.length === 0)
5242
+ return;
5243
+ throw createIntegrityError("conversation integrity check failed", { issues });
5244
+ }
5245
+
5246
+ // src/conversation/validation.ts
5247
+ function assertConversationSafe(conversation) {
5248
+ const parsed = conversationSchema.safeParse(conversation);
5249
+ if (!parsed.success) {
5250
+ throw createValidationError("conversation failed schema validation", {
5251
+ issues: parsed.error.issues
5252
+ });
5253
+ }
5254
+ assertConversationIntegrity(conversation);
5255
+ }
5256
+ function ensureConversationSafe(conversation) {
5257
+ assertConversationSafe(conversation);
5258
+ return conversation;
5259
+ }
5260
+
5004
5261
  // src/utilities/content.ts
5005
5262
  function normalizeContent(content) {
5006
5263
  if (content === undefined)
@@ -5165,6 +5422,92 @@ var cloneMessageWithPosition = (message, position, content) => {
5165
5422
  }
5166
5423
  return createMessage(baseMessage);
5167
5424
  };
5425
+ var createMessageBlock = (message, estimator) => ({
5426
+ messages: [message],
5427
+ minPosition: message.position,
5428
+ maxPosition: message.position,
5429
+ tokenCount: estimator(message)
5430
+ });
5431
+ var buildMessageBlocks = (messages, estimator, preserveToolPairs) => {
5432
+ if (!preserveToolPairs) {
5433
+ const blocks2 = messages.map((message) => createMessageBlock(message, estimator));
5434
+ const messageToBlock2 = new Map;
5435
+ for (const block of blocks2) {
5436
+ const message = block.messages[0];
5437
+ if (message) {
5438
+ messageToBlock2.set(message.id, block);
5439
+ }
5440
+ }
5441
+ return { blocks: blocks2, messageToBlock: messageToBlock2 };
5442
+ }
5443
+ const blocks = [];
5444
+ const toolUses = new Map;
5445
+ for (const message of messages) {
5446
+ if (message.role === "tool-use" && message.toolCall) {
5447
+ const block = createMessageBlock(message, estimator);
5448
+ toolUses.set(message.toolCall.id, block);
5449
+ blocks.push(block);
5450
+ continue;
5451
+ }
5452
+ if (message.role === "tool-result" && message.toolResult) {
5453
+ const existing = toolUses.get(message.toolResult.callId);
5454
+ if (existing) {
5455
+ existing.messages.push(message);
5456
+ existing.maxPosition = Math.max(existing.maxPosition, message.position);
5457
+ existing.tokenCount += estimator(message);
5458
+ continue;
5459
+ }
5460
+ const orphanBlock = createMessageBlock(message, estimator);
5461
+ orphanBlock.orphanToolResult = true;
5462
+ blocks.push(orphanBlock);
5463
+ continue;
5464
+ }
5465
+ blocks.push(createMessageBlock(message, estimator));
5466
+ }
5467
+ const filteredBlocks = blocks.filter((block) => !block.orphanToolResult);
5468
+ const messageToBlock = new Map;
5469
+ for (const block of filteredBlocks) {
5470
+ for (const message of block.messages) {
5471
+ messageToBlock.set(message.id, block);
5472
+ }
5473
+ }
5474
+ return { blocks: filteredBlocks, messageToBlock };
5475
+ };
5476
+ var collectBlocksForMessages = (messages, messageToBlock) => {
5477
+ const blocks = [];
5478
+ const seen = new Set;
5479
+ for (const message of messages) {
5480
+ const block = messageToBlock.get(message.id);
5481
+ if (block && !seen.has(block)) {
5482
+ seen.add(block);
5483
+ blocks.push(block);
5484
+ }
5485
+ }
5486
+ return blocks;
5487
+ };
5488
+ var collectMessagesFromBlocks = (blocks) => {
5489
+ const messages = [];
5490
+ const seen = new Set;
5491
+ for (const block of blocks) {
5492
+ for (const message of block.messages) {
5493
+ if (!seen.has(message.id)) {
5494
+ seen.add(message.id);
5495
+ messages.push(message);
5496
+ }
5497
+ }
5498
+ }
5499
+ messages.sort((a, b) => a.position - b.position);
5500
+ return messages;
5501
+ };
5502
+ var ensureTruncationSafe = (conversation, preserveToolPairs, operation) => {
5503
+ try {
5504
+ return ensureConversationSafe(conversation);
5505
+ } catch (error2) {
5506
+ if (!preserveToolPairs && error2 instanceof ConversationalistError && error2.code === "error:integrity")
5507
+ throw createIntegrityError(`${operation} produced invalid tool linkage; use preserveToolPairs: true to keep tool interactions intact`, { preserveToolPairs, issues: error2.context?.["issues"] });
5508
+ throw error2;
5509
+ }
5510
+ };
5168
5511
  function estimateConversationTokens(conversation, estimateTokens, environment) {
5169
5512
  let estimator = estimateTokens;
5170
5513
  let env = environment;
@@ -5177,6 +5520,7 @@ function estimateConversationTokens(conversation, estimateTokens, environment) {
5177
5520
  return getOrderedMessages(conversation).reduce((total, message) => total + finalEstimator(message), 0);
5178
5521
  }
5179
5522
  function truncateToTokenLimit(conversation, maxTokens, optionsOrEstimator, environment) {
5523
+ assertConversationSafe(conversation);
5180
5524
  let options2 = {};
5181
5525
  let env = environment;
5182
5526
  if (typeof optionsOrEstimator === "function") {
@@ -5198,54 +5542,56 @@ function truncateToTokenLimit(conversation, maxTokens, optionsOrEstimator, envir
5198
5542
  const estimator = options2.estimateTokens ?? resolvedEnvironment.estimateTokens;
5199
5543
  const preserveSystem = options2.preserveSystemMessages ?? true;
5200
5544
  const preserveLastN = options2.preserveLastN ?? 0;
5545
+ const preserveToolPairs = options2.preserveToolPairs ?? true;
5201
5546
  const currentTokens = estimateConversationTokens(conversation, estimator, resolvedEnvironment);
5202
5547
  if (currentTokens <= maxTokens) {
5203
5548
  return conversation;
5204
5549
  }
5205
5550
  const now = resolvedEnvironment.now();
5206
5551
  const orderedMessages = getOrderedMessages(conversation);
5552
+ const { blocks, messageToBlock } = buildMessageBlocks(orderedMessages, estimator, preserveToolPairs);
5207
5553
  const systemMessages = preserveSystem ? orderedMessages.filter((m) => m.role === "system") : [];
5208
5554
  const nonSystemMessages = orderedMessages.filter((m) => m.role !== "system");
5209
5555
  const protectedMessages = preserveLastN > 0 ? nonSystemMessages.slice(-preserveLastN) : [];
5210
- const removableMessages = preserveLastN > 0 ? nonSystemMessages.slice(0, -preserveLastN) : nonSystemMessages;
5211
- const systemTokens = systemMessages.reduce((sum, m) => sum + estimator(m), 0);
5212
- const protectedTokens = protectedMessages.reduce((sum, m) => sum + estimator(m), 0);
5556
+ const systemBlocks = collectBlocksForMessages(systemMessages, messageToBlock);
5557
+ const protectedBlocks = collectBlocksForMessages(protectedMessages, messageToBlock);
5558
+ const lockedBlocks = new Set([...systemBlocks, ...protectedBlocks]);
5559
+ const removableBlocks = blocks.filter((block) => !lockedBlocks.has(block));
5560
+ const systemTokens = systemBlocks.reduce((sum, block) => sum + block.tokenCount, 0);
5561
+ const protectedTokens = protectedBlocks.reduce((sum, block) => sum + block.tokenCount, 0);
5213
5562
  const availableTokens = maxTokens - systemTokens - protectedTokens;
5563
+ let selectedBlocks = [];
5214
5564
  if (availableTokens <= 0) {
5215
- const allMessages2 = [...systemMessages, ...protectedMessages];
5216
- const renumbered2 = allMessages2.map((message, index) => cloneMessageWithPosition(message, index, copyContent(message.content)));
5217
- return toReadonly({
5218
- ...conversation,
5219
- ids: renumbered2.map((message) => message.id),
5220
- messages: toIdRecord(renumbered2),
5221
- updatedAt: now
5222
- });
5223
- }
5224
- const keptRemovable = [];
5225
- let usedTokens = 0;
5226
- for (let i = removableMessages.length - 1;i >= 0; i--) {
5227
- const message = removableMessages[i];
5228
- const messageTokens = estimator(message);
5229
- if (usedTokens + messageTokens <= availableTokens) {
5230
- keptRemovable.unshift(message);
5231
- usedTokens += messageTokens;
5232
- } else {
5233
- break;
5565
+ selectedBlocks = [...systemBlocks, ...protectedBlocks];
5566
+ } else {
5567
+ const sortedRemovable = [...removableBlocks].sort((a, b) => a.maxPosition - b.maxPosition);
5568
+ const keptRemovable = [];
5569
+ let usedTokens = 0;
5570
+ for (let i = sortedRemovable.length - 1;i >= 0; i--) {
5571
+ const block = sortedRemovable[i];
5572
+ if (usedTokens + block.tokenCount <= availableTokens) {
5573
+ keptRemovable.unshift(block);
5574
+ usedTokens += block.tokenCount;
5575
+ } else {
5576
+ break;
5577
+ }
5234
5578
  }
5579
+ selectedBlocks = [...systemBlocks, ...keptRemovable, ...protectedBlocks];
5235
5580
  }
5236
- const allMessages = [...systemMessages, ...keptRemovable, ...protectedMessages];
5237
- allMessages.sort((a, b) => a.position - b.position);
5581
+ const allMessages = collectMessagesFromBlocks(selectedBlocks);
5238
5582
  const renumbered = allMessages.map((message, index) => cloneMessageWithPosition(message, index, copyContent(message.content)));
5239
- return toReadonly({
5583
+ const next2 = toReadonly({
5240
5584
  ...conversation,
5241
5585
  ids: renumbered.map((message) => message.id),
5242
5586
  messages: toIdRecord(renumbered),
5243
5587
  updatedAt: now
5244
5588
  });
5589
+ return ensureTruncationSafe(next2, preserveToolPairs, "truncateToTokenLimit");
5245
5590
  }
5246
5591
  function getRecentMessages(conversation, count, options2) {
5247
5592
  const includeHidden = options2?.includeHidden ?? false;
5248
5593
  const includeSystem = options2?.includeSystem ?? false;
5594
+ const preserveToolPairs = options2?.preserveToolPairs ?? true;
5249
5595
  const filtered = getOrderedMessages(conversation).filter((m) => {
5250
5596
  if (!includeHidden && m.hidden)
5251
5597
  return false;
@@ -5253,66 +5599,58 @@ function getRecentMessages(conversation, count, options2) {
5253
5599
  return false;
5254
5600
  return true;
5255
5601
  });
5256
- return filtered.slice(-count);
5602
+ if (!preserveToolPairs) {
5603
+ return filtered.slice(-count);
5604
+ }
5605
+ const { messageToBlock } = buildMessageBlocks(filtered, () => 0, preserveToolPairs);
5606
+ const tail = filtered.slice(-count);
5607
+ const blocks = collectBlocksForMessages(tail, messageToBlock);
5608
+ return collectMessagesFromBlocks(blocks);
5257
5609
  }
5258
5610
  function truncateFromPosition(conversation, position, options2, environment) {
5611
+ assertConversationSafe(conversation);
5259
5612
  const preserveSystem = options2?.preserveSystemMessages ?? true;
5613
+ const preserveToolPairs = options2?.preserveToolPairs ?? true;
5260
5614
  const resolvedEnvironment = resolveConversationEnvironment(environment);
5261
5615
  const now = resolvedEnvironment.now();
5262
5616
  const ordered = getOrderedMessages(conversation);
5617
+ const { messageToBlock } = buildMessageBlocks(ordered, () => 0, preserveToolPairs);
5263
5618
  const systemMessages = preserveSystem ? ordered.filter((m) => m.role === "system" && m.position < position) : [];
5264
5619
  const keptMessages = ordered.filter((m) => m.position >= position);
5265
- const allMessages = [...systemMessages, ...keptMessages];
5620
+ const systemBlocks = collectBlocksForMessages(systemMessages, messageToBlock);
5621
+ const keptBlocks = collectBlocksForMessages(keptMessages, messageToBlock);
5622
+ const allMessages = collectMessagesFromBlocks([...systemBlocks, ...keptBlocks]);
5266
5623
  const renumbered = allMessages.map((message, index) => cloneMessageWithPosition(message, index, copyContent(message.content)));
5267
- return toReadonly({
5624
+ const next2 = toReadonly({
5268
5625
  ...conversation,
5269
5626
  ids: renumbered.map((message) => message.id),
5270
5627
  messages: toIdRecord(renumbered),
5271
5628
  updatedAt: now
5272
5629
  });
5273
- }
5274
-
5275
- // src/errors.ts
5276
- class ConversationalistError extends Error {
5277
- code;
5278
- context;
5279
- cause;
5280
- constructor(code, message, options2) {
5281
- super(message);
5282
- this.name = "ConversationalistError";
5283
- this.code = code;
5284
- this.context = options2?.context;
5285
- this.cause = options2?.cause;
5286
- if (Error.captureStackTrace) {
5287
- Error.captureStackTrace(this, ConversationalistError);
5288
- }
5289
- }
5290
- toDetailedString() {
5291
- const parts = [`[${this.code}] ${this.message}`];
5292
- if (this.context && Object.keys(this.context).length > 0) {
5293
- parts.push(`Context: ${JSON.stringify(this.context, null, 2)}`);
5294
- }
5295
- if (this.cause) {
5296
- parts.push(`Caused by: ${this.cause.message}`);
5297
- }
5298
- return parts.join(`
5299
- `);
5300
- }
5301
- }
5302
- function createInvalidPositionError(expected, actual) {
5303
- return new ConversationalistError("error:invalid-position", `invalid position: expected ${expected}, got ${actual}`, { context: { expected, actual } });
5304
- }
5305
- function createInvalidToolReferenceError(callId) {
5306
- return new ConversationalistError("error:invalid-tool-reference", `tool result references non-existent tool-use: ${callId}`, { context: { callId } });
5307
- }
5308
- function createSerializationError(message, cause) {
5309
- return new ConversationalistError("error:serialization", message, { cause });
5630
+ return ensureTruncationSafe(next2, preserveToolPairs, "truncateFromPosition");
5310
5631
  }
5311
5632
 
5312
5633
  // src/types.ts
5313
5634
  var CURRENT_SCHEMA_VERSION = 3;
5314
5635
 
5315
- // src/conversation.ts
5636
+ // src/conversation/create.ts
5637
+ function createConversation(options2, environment) {
5638
+ const resolvedEnvironment = resolveConversationEnvironment(environment);
5639
+ const now = resolvedEnvironment.now();
5640
+ const conv = {
5641
+ schemaVersion: CURRENT_SCHEMA_VERSION,
5642
+ id: options2?.id ?? resolvedEnvironment.randomId(),
5643
+ title: options2?.title,
5644
+ status: options2?.status ?? "active",
5645
+ metadata: { ...options2?.metadata ?? {} },
5646
+ ids: [],
5647
+ messages: {},
5648
+ createdAt: now,
5649
+ updatedAt: now
5650
+ };
5651
+ return ensureConversationSafe(toReadonly(conv));
5652
+ }
5653
+ // src/conversation/tool-tracking.ts
5316
5654
  var buildToolUseIndex = (messages) => messages.reduce((index, message) => {
5317
5655
  if (message.role === "tool-use" && message.toolCall) {
5318
5656
  index.set(message.toolCall.id, { name: message.toolCall.name });
@@ -5329,44 +5667,34 @@ var assertToolReference = (index, callId) => {
5329
5667
  throw createInvalidToolReferenceError(callId);
5330
5668
  }
5331
5669
  };
5670
+
5671
+ // src/conversation/append.ts
5332
5672
  function partitionAppendArgs(args) {
5333
- if (args.length === 0) {
5673
+ const filtered = args.filter((arg) => arg !== undefined);
5674
+ if (filtered.length === 0) {
5334
5675
  return { inputs: [] };
5335
5676
  }
5336
- const last = args[args.length - 1];
5677
+ const last = filtered[filtered.length - 1];
5337
5678
  if (isConversationEnvironmentParameter(last)) {
5338
5679
  return {
5339
- inputs: args.slice(0, -1),
5680
+ inputs: filtered.slice(0, -1),
5340
5681
  environment: last
5341
5682
  };
5342
5683
  }
5343
- return { inputs: args };
5344
- }
5345
- function createConversation(options2, environment) {
5346
- const resolvedEnvironment = resolveConversationEnvironment(environment);
5347
- const now = resolvedEnvironment.now();
5348
- const conv = {
5349
- schemaVersion: CURRENT_SCHEMA_VERSION,
5350
- id: options2?.id ?? resolvedEnvironment.randomId(),
5351
- title: options2?.title,
5352
- status: options2?.status ?? "active",
5353
- metadata: { ...options2?.metadata ?? {} },
5354
- ids: [],
5355
- messages: {},
5356
- createdAt: now,
5357
- updatedAt: now
5358
- };
5359
- return toReadonly(conv);
5684
+ return { inputs: filtered };
5360
5685
  }
5361
5686
  function appendMessages(conversation, ...args) {
5687
+ return appendMessagesInternal(conversation, args, true);
5688
+ }
5689
+ var appendMessagesInternal = (conversation, args, validate) => {
5362
5690
  const { inputs, environment } = partitionAppendArgs(args);
5363
5691
  const resolvedEnvironment = resolveConversationEnvironment(environment);
5364
5692
  const now = resolvedEnvironment.now();
5365
5693
  const startPosition = conversation.ids.length;
5366
- const initialToolUses = buildToolUseIndex(getOrderedMessages(conversation));
5694
+ const initialToolUses = validate ? buildToolUseIndex(getOrderedMessages(conversation)) : new Map;
5367
5695
  const { messages } = inputs.reduce((state, input, index) => {
5368
5696
  const processedInput = resolvedEnvironment.plugins.reduce((acc, plugin) => plugin(acc), input);
5369
- if (processedInput.role === "tool-result" && processedInput.toolResult) {
5697
+ if (validate && processedInput.role === "tool-result" && processedInput.toolResult) {
5370
5698
  assertToolReference(state.toolUses, processedInput.toolResult.callId);
5371
5699
  }
5372
5700
  const normalizedContent = normalizeContent(processedInput.content);
@@ -5393,7 +5721,16 @@ function appendMessages(conversation, ...args) {
5393
5721
  } else {
5394
5722
  message = createMessage(baseMessage);
5395
5723
  }
5396
- const toolUses = processedInput.role === "tool-use" && processedInput.toolCall ? registerToolUse(state.toolUses, processedInput.toolCall) : state.toolUses;
5724
+ let toolUses = state.toolUses;
5725
+ if (processedInput.role === "tool-use" && processedInput.toolCall) {
5726
+ if (validate && state.toolUses.has(processedInput.toolCall.id)) {
5727
+ throw createIntegrityError("duplicate toolCall.id in conversation", {
5728
+ toolCallId: processedInput.toolCall.id,
5729
+ messageId: baseMessage.id
5730
+ });
5731
+ }
5732
+ toolUses = validate ? registerToolUse(state.toolUses, processedInput.toolCall) : state.toolUses;
5733
+ }
5397
5734
  return {
5398
5735
  toolUses,
5399
5736
  messages: [...state.messages, message]
@@ -5406,17 +5743,25 @@ function appendMessages(conversation, ...args) {
5406
5743
  messages: { ...conversation.messages, ...toIdRecord(messages) },
5407
5744
  updatedAt: now
5408
5745
  };
5409
- return toReadonly(next2);
5410
- }
5746
+ const readonly = toReadonly(next2);
5747
+ return validate ? ensureConversationSafe(readonly) : readonly;
5748
+ };
5411
5749
  function appendUserMessage(conversation, content, metadata, environment) {
5412
- return environment ? appendMessages(conversation, { role: "user", content, metadata }, environment) : appendMessages(conversation, { role: "user", content, metadata });
5750
+ const resolvedEnvironment = isConversationEnvironmentParameter(metadata) ? metadata : environment;
5751
+ const resolvedMetadata = isConversationEnvironmentParameter(metadata) ? undefined : metadata;
5752
+ return appendMessages(conversation, { role: "user", content, metadata: resolvedMetadata }, resolvedEnvironment);
5413
5753
  }
5414
5754
  function appendAssistantMessage(conversation, content, metadata, environment) {
5415
- return environment ? appendMessages(conversation, { role: "assistant", content, metadata }, environment) : appendMessages(conversation, { role: "assistant", content, metadata });
5755
+ const resolvedEnvironment = isConversationEnvironmentParameter(metadata) ? metadata : environment;
5756
+ const resolvedMetadata = isConversationEnvironmentParameter(metadata) ? undefined : metadata;
5757
+ return appendMessages(conversation, { role: "assistant", content, metadata: resolvedMetadata }, resolvedEnvironment);
5416
5758
  }
5417
5759
  function appendSystemMessage(conversation, content, metadata, environment) {
5418
- return environment ? appendMessages(conversation, { role: "system", content, metadata }, environment) : appendMessages(conversation, { role: "system", content, metadata });
5760
+ const resolvedEnvironment = isConversationEnvironmentParameter(metadata) ? metadata : environment;
5761
+ const resolvedMetadata = isConversationEnvironmentParameter(metadata) ? undefined : metadata;
5762
+ return appendMessages(conversation, { role: "system", content, metadata: resolvedMetadata }, resolvedEnvironment);
5419
5763
  }
5764
+ // src/conversation/query.ts
5420
5765
  function getMessages(conversation, options2) {
5421
5766
  const includeHidden = options2?.includeHidden ?? false;
5422
5767
  const ordered = getOrderedMessages(conversation);
@@ -5450,6 +5795,7 @@ function getStatistics(conversation) {
5450
5795
  }, { byRole: {}, hidden: 0, withImages: 0 });
5451
5796
  return { total: ordered.length, ...stats };
5452
5797
  }
5798
+ // src/conversation/system-messages.ts
5453
5799
  function hasSystemMessage(conversation) {
5454
5800
  return getOrderedMessages(conversation).some((m) => m.role === "system");
5455
5801
  }
@@ -5460,7 +5806,8 @@ function getSystemMessages(conversation) {
5460
5806
  return getOrderedMessages(conversation).filter((m) => m.role === "system");
5461
5807
  }
5462
5808
  function prependSystemMessage(conversation, content, metadata, environment) {
5463
- const resolvedEnvironment = resolveConversationEnvironment(environment);
5809
+ const resolvedEnvironment = resolveConversationEnvironment(isConversationEnvironmentParameter(metadata) ? metadata : environment);
5810
+ const resolvedMetadata = isConversationEnvironmentParameter(metadata) ? undefined : metadata;
5464
5811
  const now = resolvedEnvironment.now();
5465
5812
  const newMessage = createMessage({
5466
5813
  id: resolvedEnvironment.randomId(),
@@ -5468,7 +5815,7 @@ function prependSystemMessage(conversation, content, metadata, environment) {
5468
5815
  content,
5469
5816
  position: 0,
5470
5817
  createdAt: now,
5471
- metadata: { ...metadata ?? {} },
5818
+ metadata: { ...resolvedMetadata ?? {} },
5472
5819
  hidden: false,
5473
5820
  toolCall: undefined,
5474
5821
  toolResult: undefined,
@@ -5487,20 +5834,21 @@ function prependSystemMessage(conversation, content, metadata, environment) {
5487
5834
  toolResult: message.toolResult,
5488
5835
  tokenUsage: message.tokenUsage
5489
5836
  }));
5490
- return toReadonly({
5837
+ return ensureConversationSafe(toReadonly({
5491
5838
  ...conversation,
5492
5839
  ids: [newMessage.id, ...ordered.map((message) => message.id)],
5493
5840
  messages: toIdRecord([newMessage, ...renumberedMessages]),
5494
5841
  updatedAt: now
5495
- });
5842
+ }));
5496
5843
  }
5497
5844
  function replaceSystemMessage(conversation, content, metadata, environment) {
5498
- const resolvedEnvironment = resolveConversationEnvironment(environment);
5845
+ const resolvedEnvironment = resolveConversationEnvironment(isConversationEnvironmentParameter(metadata) ? metadata : environment);
5846
+ const resolvedMetadata = isConversationEnvironmentParameter(metadata) ? undefined : metadata;
5499
5847
  const now = resolvedEnvironment.now();
5500
5848
  const ordered = getOrderedMessages(conversation);
5501
5849
  const firstSystemIndex = ordered.findIndex((m) => m.role === "system");
5502
5850
  if (firstSystemIndex === -1) {
5503
- return prependSystemMessage(conversation, content, metadata, resolvedEnvironment);
5851
+ return prependSystemMessage(conversation, content, resolvedMetadata, resolvedEnvironment);
5504
5852
  }
5505
5853
  const original = ordered[firstSystemIndex];
5506
5854
  const replaced = createMessage({
@@ -5509,7 +5857,7 @@ function replaceSystemMessage(conversation, content, metadata, environment) {
5509
5857
  content,
5510
5858
  position: original.position,
5511
5859
  createdAt: original.createdAt,
5512
- metadata: { ...metadata ?? original.metadata },
5860
+ metadata: { ...resolvedMetadata ?? original.metadata },
5513
5861
  hidden: original.hidden,
5514
5862
  toolCall: undefined,
5515
5863
  toolResult: undefined,
@@ -5521,13 +5869,13 @@ function replaceSystemMessage(conversation, content, metadata, environment) {
5521
5869
  messages: { ...conversation.messages, [replaced.id]: replaced },
5522
5870
  updatedAt: now
5523
5871
  };
5524
- return toReadonly(next2);
5872
+ return ensureConversationSafe(toReadonly(next2));
5525
5873
  }
5526
5874
  function collapseSystemMessages(conversation, environment) {
5527
5875
  const ordered = getOrderedMessages(conversation);
5528
5876
  const systemMessages = ordered.filter((m) => m.role === "system");
5529
5877
  if (systemMessages.length <= 1) {
5530
- return conversation;
5878
+ return ensureConversationSafe(conversation);
5531
5879
  }
5532
5880
  const resolvedEnvironment = resolveConversationEnvironment(environment);
5533
5881
  const now = resolvedEnvironment.now();
@@ -5583,9 +5931,42 @@ function collapseSystemMessages(conversation, environment) {
5583
5931
  messages: toIdRecord(renumbered),
5584
5932
  updatedAt: now
5585
5933
  };
5586
- return toReadonly(next2);
5934
+ return ensureConversationSafe(toReadonly(next2));
5587
5935
  }
5588
- function redactMessageAtPosition(conversation, position, placeholder = "[REDACTED]", environment) {
5936
+ // src/utilities/tool-results.ts
5937
+ function copyToolResult(toolResult) {
5938
+ return { ...toolResult };
5939
+ }
5940
+ function redactToolResult(toolResult, placeholder) {
5941
+ return { ...toolResult, content: placeholder };
5942
+ }
5943
+
5944
+ // src/conversation/modify.ts
5945
+ var isRedactMessageOptions = (value) => {
5946
+ if (!value || typeof value !== "object")
5947
+ return false;
5948
+ const candidate = value;
5949
+ return "placeholder" in candidate || "redactToolArguments" in candidate || "redactToolResults" in candidate || "clearToolMetadata" in candidate;
5950
+ };
5951
+ function redactMessageAtPosition(conversation, position, placeholderOrOptions, environment) {
5952
+ let placeholder = "[REDACTED]";
5953
+ let options2 = {};
5954
+ let env = environment;
5955
+ if (typeof placeholderOrOptions === "string") {
5956
+ placeholder = placeholderOrOptions;
5957
+ } else if (placeholderOrOptions) {
5958
+ if (!environment && isConversationEnvironmentParameter(placeholderOrOptions)) {
5959
+ env = placeholderOrOptions;
5960
+ } else if (isRedactMessageOptions(placeholderOrOptions)) {
5961
+ options2 = placeholderOrOptions;
5962
+ if (options2.placeholder) {
5963
+ placeholder = options2.placeholder;
5964
+ }
5965
+ }
5966
+ }
5967
+ const redactToolArguments = options2.redactToolArguments ?? true;
5968
+ const redactToolResults = options2.redactToolResults ?? true;
5969
+ const clearToolMetadata = options2.clearToolMetadata ?? false;
5589
5970
  if (position < 0 || position >= conversation.ids.length) {
5590
5971
  throw createInvalidPositionError(conversation.ids.length - 1, position);
5591
5972
  }
@@ -5594,6 +5975,22 @@ function redactMessageAtPosition(conversation, position, placeholder = "[REDACTE
5594
5975
  if (!original) {
5595
5976
  throw createInvalidPositionError(conversation.ids.length - 1, position);
5596
5977
  }
5978
+ let toolCall = original.toolCall ? { ...original.toolCall } : undefined;
5979
+ let toolResult = original.toolResult ? { ...original.toolResult } : undefined;
5980
+ if (clearToolMetadata) {
5981
+ toolCall = undefined;
5982
+ toolResult = undefined;
5983
+ } else {
5984
+ if (original.role === "tool-use" && toolCall) {
5985
+ toolCall = {
5986
+ ...toolCall,
5987
+ arguments: redactToolArguments ? placeholder : toolCall.arguments
5988
+ };
5989
+ }
5990
+ if (original.role === "tool-result" && toolResult) {
5991
+ toolResult = redactToolResults ? redactToolResult(toolResult, placeholder) : { ...toolResult };
5992
+ }
5993
+ }
5597
5994
  const redacted = createMessage({
5598
5995
  id: original.id,
5599
5996
  role: original.role,
@@ -5602,11 +5999,11 @@ function redactMessageAtPosition(conversation, position, placeholder = "[REDACTE
5602
5999
  createdAt: original.createdAt,
5603
6000
  metadata: { ...original.metadata },
5604
6001
  hidden: original.hidden,
5605
- toolCall: undefined,
5606
- toolResult: undefined,
5607
- tokenUsage: undefined
6002
+ toolCall,
6003
+ toolResult,
6004
+ tokenUsage: original.tokenUsage ? { ...original.tokenUsage } : undefined
5608
6005
  });
5609
- const resolvedEnvironment = resolveConversationEnvironment(environment);
6006
+ const resolvedEnvironment = resolveConversationEnvironment(env);
5610
6007
  const now = resolvedEnvironment.now();
5611
6008
  const next2 = {
5612
6009
  ...conversation,
@@ -5614,70 +6011,62 @@ function redactMessageAtPosition(conversation, position, placeholder = "[REDACTE
5614
6011
  messages: { ...conversation.messages, [redacted.id]: redacted },
5615
6012
  updatedAt: now
5616
6013
  };
5617
- return toReadonly(next2);
6014
+ return ensureConversationSafe(toReadonly(next2));
5618
6015
  }
5619
- function migrateConversation(json) {
5620
- if (typeof json !== "object" || json === null || Array.isArray(json)) {
5621
- return {
5622
- schemaVersion: CURRENT_SCHEMA_VERSION,
5623
- id: "",
5624
- status: "active",
5625
- metadata: {},
5626
- ids: [],
5627
- messages: {},
5628
- createdAt: new Date().toISOString(),
5629
- updatedAt: new Date().toISOString()
5630
- };
5631
- }
5632
- const data = json;
5633
- const rawMessages = data.messages;
5634
- let messages = {};
5635
- let ids = [];
5636
- const rawIds = data.ids;
5637
- const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
5638
- if (Array.isArray(rawMessages)) {
5639
- const rawMessageArray = rawMessages;
5640
- ids = rawMessageArray.map((message) => message.id);
5641
- messages = Object.fromEntries(rawMessageArray.map((message) => [message.id, message]));
5642
- } else if (rawMessages && typeof rawMessages === "object") {
5643
- messages = { ...rawMessages };
5644
- if (isStringArray(rawIds) && rawIds.length > 0) {
5645
- ids = [...rawIds];
5646
- } else {
5647
- ids = Object.values(messages).sort((a, b) => a.position - b.position).map((message) => message.id);
5648
- }
5649
- }
5650
- if (ids.length > 0) {
5651
- ids = ids.filter((id) => (id in messages));
5652
- const missing = Object.keys(messages).filter((id) => !ids.includes(id));
5653
- if (missing.length > 0) {
5654
- const sortedMissing = missing.sort((a, b) => (messages[a]?.position ?? 0) - (messages[b]?.position ?? 0));
5655
- ids = [...ids, ...sortedMissing];
5656
- }
5657
- }
5658
- if (!("schemaVersion" in json)) {
6016
+ // src/conversation/serialization.ts
6017
+ function normalizeToolResult(toolResult) {
6018
+ if (!toolResult)
6019
+ return;
6020
+ return {
6021
+ callId: toolResult.callId,
6022
+ outcome: toolResult.outcome,
6023
+ content: toolResult.content
6024
+ };
6025
+ }
6026
+ function normalizeMessage(message) {
6027
+ const base = {
6028
+ id: message.id,
6029
+ role: message.role,
6030
+ content: message.content,
6031
+ position: message.position,
6032
+ createdAt: message.createdAt,
6033
+ metadata: message.metadata,
6034
+ hidden: message.hidden,
6035
+ toolCall: message.toolCall ? { ...message.toolCall } : undefined,
6036
+ toolResult: normalizeToolResult(message.toolResult),
6037
+ tokenUsage: message.tokenUsage ? { ...message.tokenUsage } : undefined
6038
+ };
6039
+ if (isAssistantMessage(message)) {
5659
6040
  return {
5660
- ...data,
5661
- schemaVersion: CURRENT_SCHEMA_VERSION,
5662
- ids,
5663
- messages
6041
+ ...base,
6042
+ role: "assistant",
6043
+ goalCompleted: message.goalCompleted
5664
6044
  };
5665
6045
  }
5666
- return { ...data, ids, messages };
6046
+ return base;
5667
6047
  }
5668
6048
  function deserializeConversation(json) {
5669
- const migrated = migrateConversation(json);
6049
+ const parsed = conversationSchema.safeParse(json);
6050
+ if (!parsed.success) {
6051
+ throw createSerializationError("failed to deserialize conversation: invalid data");
6052
+ }
6053
+ const data = parsed.data;
5670
6054
  try {
5671
- const orderedMessages = migrated.ids.map((id, index) => {
5672
- const message = migrated.messages[id];
6055
+ const messageIds = new Set(Object.keys(data.messages));
6056
+ const orderedMessages = data.ids.map((id, index) => {
6057
+ const message = data.messages[id];
5673
6058
  if (!message) {
5674
6059
  throw createSerializationError(`missing message for id ${id}`);
5675
6060
  }
5676
6061
  if (message.position !== index) {
5677
6062
  throw createInvalidPositionError(index, message.position);
5678
6063
  }
5679
- return message;
6064
+ messageIds.delete(id);
6065
+ return normalizeMessage(message);
5680
6066
  });
6067
+ if (messageIds.size > 0) {
6068
+ throw createSerializationError(`messages not listed in ids: ${[...messageIds].join(", ")}`);
6069
+ }
5681
6070
  orderedMessages.reduce((state, message) => {
5682
6071
  if (message.role === "tool-use" && message.toolCall) {
5683
6072
  return {
@@ -5689,24 +6078,28 @@ function deserializeConversation(json) {
5689
6078
  }
5690
6079
  return state;
5691
6080
  }, { toolUses: new Map });
5692
- const messageInstances = orderedMessages.map((m) => createMessage(m));
6081
+ const messageInstances = orderedMessages.map((message) => createMessage(message));
5693
6082
  const conv = {
5694
- schemaVersion: migrated.schemaVersion,
5695
- id: migrated.id,
5696
- title: migrated.title,
5697
- status: migrated.status,
5698
- metadata: { ...migrated.metadata },
6083
+ schemaVersion: data.schemaVersion,
6084
+ id: data.id,
6085
+ title: data.title,
6086
+ status: data.status,
6087
+ metadata: { ...data.metadata },
5699
6088
  ids: orderedMessages.map((message) => message.id),
5700
6089
  messages: toIdRecord(messageInstances),
5701
- createdAt: migrated.createdAt,
5702
- updatedAt: migrated.updatedAt
6090
+ createdAt: data.createdAt,
6091
+ updatedAt: data.updatedAt
5703
6092
  };
5704
- return toReadonly(conv);
6093
+ const readonly = toReadonly(conv);
6094
+ assertConversationIntegrity(readonly);
6095
+ return readonly;
5705
6096
  } catch (error2) {
5706
6097
  throw createSerializationError(`failed to deserialize conversation: ${error2 instanceof Error ? error2.message : String(error2)}`, error2);
5707
6098
  }
5708
6099
  }
6100
+ // src/conversation/transform.ts
5709
6101
  function toChatMessages(conversation) {
6102
+ assertConversationSafe(conversation);
5710
6103
  const roleMap = {
5711
6104
  user: "user",
5712
6105
  assistant: "assistant",
@@ -5728,7 +6121,6 @@ function toChatMessages(conversation) {
5728
6121
  }
5729
6122
  return result;
5730
6123
  }
5731
-
5732
6124
  // src/streaming.ts
5733
6125
  var STREAMING_KEY = "__streaming";
5734
6126
  var cloneMessage = (original, overrides = {}) => {
@@ -5761,7 +6153,8 @@ function getStreamingMessage(conversation) {
5761
6153
  return getOrderedMessages(conversation).find(isStreamingMessage);
5762
6154
  }
5763
6155
  function appendStreamingMessage(conversation, role, metadata, environment) {
5764
- const resolvedEnvironment = resolveConversationEnvironment(environment);
6156
+ const resolvedEnvironment = resolveConversationEnvironment(isConversationEnvironmentParameter(metadata) ? metadata : environment);
6157
+ const resolvedMetadata = isConversationEnvironmentParameter(metadata) ? undefined : metadata;
5765
6158
  const now = resolvedEnvironment.now();
5766
6159
  const messageId = resolvedEnvironment.randomId();
5767
6160
  const newMessage = createMessage({
@@ -5770,7 +6163,7 @@ function appendStreamingMessage(conversation, role, metadata, environment) {
5770
6163
  content: "",
5771
6164
  position: conversation.ids.length,
5772
6165
  createdAt: now,
5773
- metadata: { ...metadata ?? {}, [STREAMING_KEY]: true },
6166
+ metadata: { ...resolvedMetadata ?? {}, [STREAMING_KEY]: true },
5774
6167
  hidden: false,
5775
6168
  toolCall: undefined,
5776
6169
  toolResult: undefined,
@@ -5782,14 +6175,14 @@ function appendStreamingMessage(conversation, role, metadata, environment) {
5782
6175
  messages: { ...conversation.messages, [messageId]: newMessage },
5783
6176
  updatedAt: now
5784
6177
  });
5785
- return { conversation: updatedConversation, messageId };
6178
+ return { conversation: ensureConversationSafe(updatedConversation), messageId };
5786
6179
  }
5787
6180
  function updateStreamingMessage(conversation, messageId, content, environment) {
5788
6181
  const resolvedEnvironment = resolveConversationEnvironment(environment);
5789
6182
  const now = resolvedEnvironment.now();
5790
6183
  const original = conversation.messages[messageId];
5791
6184
  if (!original) {
5792
- return conversation;
6185
+ return ensureConversationSafe(conversation);
5793
6186
  }
5794
6187
  const overrides = {
5795
6188
  content: typeof content === "string" ? content : [...content]
@@ -5798,44 +6191,45 @@ function updateStreamingMessage(conversation, messageId, content, environment) {
5798
6191
  overrides.tokenUsage = { ...original.tokenUsage };
5799
6192
  }
5800
6193
  const updated = cloneMessage(original, overrides);
5801
- return toReadonly({
6194
+ return ensureConversationSafe(toReadonly({
5802
6195
  ...conversation,
5803
6196
  ids: [...conversation.ids],
5804
6197
  messages: { ...conversation.messages, [updated.id]: updated },
5805
6198
  updatedAt: now
5806
- });
6199
+ }));
5807
6200
  }
5808
6201
  function finalizeStreamingMessage(conversation, messageId, options2, environment) {
5809
- const resolvedEnvironment = resolveConversationEnvironment(environment);
6202
+ const resolvedEnvironment = resolveConversationEnvironment(isConversationEnvironmentParameter(options2) ? options2 : environment);
6203
+ const resolvedOptions = isConversationEnvironmentParameter(options2) ? undefined : options2;
5810
6204
  const now = resolvedEnvironment.now();
5811
6205
  const original = conversation.messages[messageId];
5812
6206
  if (!original) {
5813
- return conversation;
6207
+ return ensureConversationSafe(conversation);
5814
6208
  }
5815
6209
  const { [STREAMING_KEY]: _, ...restMetadata } = original.metadata;
5816
6210
  const finalMetadata = {
5817
6211
  ...restMetadata,
5818
- ...options2?.metadata ?? {}
6212
+ ...resolvedOptions?.metadata ?? {}
5819
6213
  };
5820
6214
  const finalizeOverrides = {
5821
6215
  metadata: finalMetadata
5822
6216
  };
5823
- if (options2?.tokenUsage) {
5824
- finalizeOverrides.tokenUsage = { ...options2.tokenUsage };
6217
+ if (resolvedOptions?.tokenUsage) {
6218
+ finalizeOverrides.tokenUsage = { ...resolvedOptions.tokenUsage };
5825
6219
  }
5826
6220
  const updated = cloneMessage(original, finalizeOverrides);
5827
- return toReadonly({
6221
+ return ensureConversationSafe(toReadonly({
5828
6222
  ...conversation,
5829
6223
  ids: [...conversation.ids],
5830
6224
  messages: { ...conversation.messages, [updated.id]: updated },
5831
6225
  updatedAt: now
5832
- });
6226
+ }));
5833
6227
  }
5834
6228
  function cancelStreamingMessage(conversation, messageId, environment) {
5835
6229
  const resolvedEnvironment = resolveConversationEnvironment(environment);
5836
6230
  const now = resolvedEnvironment.now();
5837
6231
  if (!conversation.messages[messageId]) {
5838
- return conversation;
6232
+ return ensureConversationSafe(conversation);
5839
6233
  }
5840
6234
  const messages = getOrderedMessages(conversation).filter((m) => m.id !== messageId).map((message, index) => message.position === index ? message : (() => {
5841
6235
  const overrides = {
@@ -5846,12 +6240,12 @@ function cancelStreamingMessage(conversation, messageId, environment) {
5846
6240
  }
5847
6241
  return cloneMessage(message, overrides);
5848
6242
  })());
5849
- return toReadonly({
6243
+ return ensureConversationSafe(toReadonly({
5850
6244
  ...conversation,
5851
6245
  ids: messages.map((message) => message.id),
5852
6246
  messages: toIdRecord(messages),
5853
6247
  updatedAt: now
5854
- });
6248
+ }));
5855
6249
  }
5856
6250
 
5857
6251
  // src/history.ts
@@ -5863,8 +6257,9 @@ class ConversationHistory extends EventTarget {
5863
6257
  super();
5864
6258
  this.environment = resolveConversationEnvironment(environment);
5865
6259
  this.events = createEventTarget();
6260
+ const safeInitial = ensureConversationSafe(initial);
5866
6261
  this.currentNode = {
5867
- conversation: initial,
6262
+ conversation: safeInitial,
5868
6263
  parent: null,
5869
6264
  children: []
5870
6265
  };
@@ -6062,8 +6457,8 @@ class ConversationHistory extends EventTarget {
6062
6457
  collapseSystemMessages() {
6063
6458
  this.push(collapseSystemMessages(this.current, this.env));
6064
6459
  }
6065
- redactMessageAtPosition(position, placeholder) {
6066
- this.push(redactMessageAtPosition(this.current, position, placeholder, this.env));
6460
+ redactMessageAtPosition(position, placeholderOrOptions) {
6461
+ this.push(redactMessageAtPosition(this.current, position, placeholderOrOptions, this.env));
6067
6462
  }
6068
6463
  truncateFromPosition(position, options2) {
6069
6464
  this.push(truncateFromPosition(this.current, position, options2, this.env));
@@ -6169,23 +6564,6 @@ function isConversation(value) {
6169
6564
 
6170
6565
  // src/utilities/markdown.ts
6171
6566
  var import_gray_matter = __toESM(require_gray_matter(), 1);
6172
-
6173
- // src/utilities/tool-results.ts
6174
- function copyToolResult(toolResult) {
6175
- return { ...toolResult };
6176
- }
6177
- function redactToolResult(toolResult, placeholder) {
6178
- const result = { ...toolResult, content: placeholder };
6179
- if (result.result !== undefined) {
6180
- result.result = placeholder;
6181
- }
6182
- if (result.error !== undefined) {
6183
- result.error = placeholder;
6184
- }
6185
- return result;
6186
- }
6187
-
6188
- // src/utilities/markdown.ts
6189
6567
  var ROLE_LABELS = {
6190
6568
  user: "User",
6191
6569
  assistant: "Assistant",
@@ -6195,7 +6573,6 @@ var ROLE_LABELS = {
6195
6573
  "tool-result": "Tool Result",
6196
6574
  snapshot: "Snapshot"
6197
6575
  };
6198
- var ROLE_DISPLAY_NAMES = ROLE_LABELS;
6199
6576
  var LABEL_TO_ROLE = {
6200
6577
  User: "user",
6201
6578
  Assistant: "assistant",
@@ -6205,7 +6582,6 @@ var LABEL_TO_ROLE = {
6205
6582
  "Tool Result": "tool-result",
6206
6583
  Snapshot: "snapshot"
6207
6584
  };
6208
- var DISPLAY_NAME_TO_ROLE = LABEL_TO_ROLE;
6209
6585
  function getRoleLabel(role) {
6210
6586
  return ROLE_LABELS[role];
6211
6587
  }
@@ -6288,6 +6664,7 @@ function formatMessageContent(message) {
6288
6664
  `);
6289
6665
  }
6290
6666
  function toMarkdown(conversation, options2 = {}) {
6667
+ assertConversationSafe(conversation);
6291
6668
  const resolved = resolveMarkdownOptions(options2);
6292
6669
  const prepared = prepareConversationForMarkdown(conversation, resolved);
6293
6670
  if (resolved.includeMetadata) {
@@ -6298,7 +6675,7 @@ function toMarkdown(conversation, options2 = {}) {
6298
6675
  function toMarkdownSimple(conversation) {
6299
6676
  const sections = [];
6300
6677
  for (const message of getOrderedMessages(conversation)) {
6301
- const roleName = ROLE_DISPLAY_NAMES[message.role];
6678
+ const roleName = ROLE_LABELS[message.role];
6302
6679
  const header = `### ${roleName}`;
6303
6680
  const content = formatMessageContent(message);
6304
6681
  sections.push(`${header}
@@ -6349,7 +6726,7 @@ function toMarkdownWithMetadata(conversation, _options) {
6349
6726
  }
6350
6727
  const messageSections = [];
6351
6728
  for (const message of getOrderedMessages(conversation)) {
6352
- const roleName = ROLE_DISPLAY_NAMES[message.role];
6729
+ const roleName = ROLE_LABELS[message.role];
6353
6730
  const header = `### ${roleName} (${message.id})`;
6354
6731
  const content = formatMessageContent(message);
6355
6732
  messageSections.push(`${header}
@@ -6374,10 +6751,13 @@ function generateId() {
6374
6751
  function fromMarkdown(markdown) {
6375
6752
  const trimmed = markdown.trim();
6376
6753
  const hasFrontmatter = trimmed.startsWith("---");
6377
- if (hasFrontmatter) {
6378
- return parseMarkdownWithMetadata(trimmed);
6754
+ const conversation = hasFrontmatter ? parseMarkdownWithMetadata(trimmed) : parseMarkdownSimple(trimmed);
6755
+ try {
6756
+ assertConversationSafe(conversation);
6757
+ } catch (error2) {
6758
+ throw new MarkdownParseError(`Invalid markdown conversation: ${error2 instanceof Error ? error2.message : String(error2)}`);
6379
6759
  }
6380
- return parseMarkdownSimple(trimmed);
6760
+ return conversation;
6381
6761
  }
6382
6762
  function parseMarkdownWithMetadata(trimmed) {
6383
6763
  let parsed;
@@ -6396,7 +6776,7 @@ function parseMarkdownWithMetadata(trimmed) {
6396
6776
  let match;
6397
6777
  while ((match = messagePattern.exec(body)) !== null) {
6398
6778
  const [, roleDisplay, messageId, contentBody] = match;
6399
- const role = DISPLAY_NAME_TO_ROLE[roleDisplay];
6779
+ const role = LABEL_TO_ROLE[roleDisplay];
6400
6780
  if (!role) {
6401
6781
  throw new MarkdownParseError(`Unknown role: ${roleDisplay}`);
6402
6782
  }
@@ -6453,7 +6833,7 @@ function parseMarkdownSimple(body) {
6453
6833
  let position = 0;
6454
6834
  while ((match = messagePattern.exec(body)) !== null) {
6455
6835
  const [, roleDisplay, contentBody] = match;
6456
- const role = DISPLAY_NAME_TO_ROLE[roleDisplay];
6836
+ const role = LABEL_TO_ROLE[roleDisplay];
6457
6837
  if (!role) {
6458
6838
  throw new MarkdownParseError(`Unknown role: ${roleDisplay}`);
6459
6839
  }
@@ -6502,4 +6882,4 @@ export {
6502
6882
  LABEL_TO_ROLE
6503
6883
  };
6504
6884
 
6505
- //# debugId=C64E71F05A5A314564756E2164756E21
6885
+ //# debugId=C9F3624DCBE2A76564756E2164756E21