@lostgradient/cinder 0.9.0 → 0.12.0

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 (169) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/components.json +82 -4
  3. package/dist/components/access-gate/index.js +381 -239
  4. package/dist/components/chat/builders.d.ts +17 -8
  5. package/dist/components/chat/conversation-model.d.ts +1 -1
  6. package/dist/components/chat/index.d.ts +1 -1
  7. package/dist/components/chat/index.js +300 -17
  8. package/dist/components/chat/utilities/conversation.d.ts +6 -6
  9. package/dist/components/chat-conversation-header/index.js +19 -6
  10. package/dist/components/chat-conversation-list/index.js +19 -6
  11. package/dist/components/code-block/code-block.css +15 -9
  12. package/dist/components/code-block/code-block.variables.js +8 -2
  13. package/dist/components/confirm-dialog/confirm-dialog.css +2 -0
  14. package/dist/components/confirm-dialog/confirm-dialog.schema.js +10 -1
  15. package/dist/components/confirm-dialog/confirm-dialog.types.d.ts +7 -0
  16. package/dist/components/confirm-dialog/index.js +478 -106
  17. package/dist/components/connection-indicator/connection-indicator.css +136 -0
  18. package/dist/components/connection-indicator/connection-indicator.css.d.ts +1 -0
  19. package/dist/components/connection-indicator/connection-indicator.schema.d.ts +3 -0
  20. package/dist/components/connection-indicator/connection-indicator.schema.js +89 -0
  21. package/dist/components/connection-indicator/connection-indicator.svelte.d.ts +21 -0
  22. package/dist/components/connection-indicator/connection-indicator.types.d.ts +46 -0
  23. package/dist/components/connection-indicator/connection-indicator.variables.d.ts +2 -0
  24. package/dist/components/connection-indicator/connection-indicator.variables.js +61 -0
  25. package/dist/components/connection-indicator/index.d.ts +5 -0
  26. package/dist/components/connection-indicator/index.js +409 -0
  27. package/dist/components/container/container.schema.js +4 -4
  28. package/dist/components/faceted-filter-bar/faceted-filter-bar.schema.js +5 -1
  29. package/dist/components/faceted-filter-bar/faceted-filter-bar.types.d.ts +2 -0
  30. package/dist/components/faceted-filter-bar/index.js +40 -31
  31. package/dist/components/invocation-rule-builder/index.d.ts +1 -1
  32. package/dist/components/invocation-rule-builder/index.js +305 -150
  33. package/dist/components/invocation-rule-builder/invocation-rule-builder.css +28 -0
  34. package/dist/components/invocation-rule-builder/invocation-rule-builder.schema.js +89 -5
  35. package/dist/components/invocation-rule-builder/invocation-rule-builder.svelte.d.ts +2 -1
  36. package/dist/components/invocation-rule-builder/invocation-rule-builder.types.d.ts +114 -16
  37. package/dist/components/navigation-bar/index.js +11 -17
  38. package/dist/components/run-step-timeline/index.d.ts +1 -1
  39. package/dist/components/run-step-timeline/index.js +1052 -560
  40. package/dist/components/run-step-timeline/run-step-branch-disclosure.svelte.d.ts +17 -0
  41. package/dist/components/run-step-timeline/run-step-timeline.css +124 -0
  42. package/dist/components/run-step-timeline/run-step-timeline.schema.js +873 -350
  43. package/dist/components/run-step-timeline/run-step-timeline.svelte.d.ts +1 -15
  44. package/dist/components/run-step-timeline/run-step-timeline.types.d.ts +182 -5
  45. package/dist/components/run-step-timeline/run-step-timeline.utilities.d.ts +60 -0
  46. package/dist/components/schedule-builder/index.d.ts +5 -0
  47. package/dist/components/schedule-builder/index.js +3176 -0
  48. package/dist/components/schedule-builder/schedule-builder.css +121 -0
  49. package/dist/components/schedule-builder/schedule-builder.css.d.ts +1 -0
  50. package/dist/components/schedule-builder/schedule-builder.schema.d.ts +3 -0
  51. package/dist/components/schedule-builder/schedule-builder.schema.js +141 -0
  52. package/dist/components/schedule-builder/schedule-builder.svelte.d.ts +22 -0
  53. package/dist/components/schedule-builder/schedule-builder.types.d.ts +130 -0
  54. package/dist/components/schedule-builder/schedule-builder.utilities.d.ts +78 -0
  55. package/dist/components/schedule-builder/schedule-builder.variables.d.ts +2 -0
  56. package/dist/components/schedule-builder/schedule-builder.variables.js +61 -0
  57. package/dist/components/tag-input/index.js +22 -4
  58. package/dist/components/tag-input/tag-input.schema.js +5 -1
  59. package/dist/components/tag-input/tag-input.types.d.ts +4 -0
  60. package/dist/index.d.ts +6 -2
  61. package/dist/index.js +23069 -20833
  62. package/dist/server/components/access-gate/index.js +2 -1
  63. package/dist/server/components/chat/index.js +4 -4
  64. package/dist/server/components/chat-conversation-header/index.js +5 -5
  65. package/dist/server/components/chat-conversation-list/index.js +3 -3
  66. package/dist/server/components/color-field/index.js +2 -2
  67. package/dist/server/components/confirm-dialog/index.js +7 -2
  68. package/dist/server/components/connection-indicator/index.js +16 -0
  69. package/dist/server/components/data-table/index.js +3 -3
  70. package/dist/server/components/diff-viewer/index.js +4 -3
  71. package/dist/server/components/faceted-filter-bar/index.js +1 -1
  72. package/dist/server/components/invocation-rule-builder/index.js +1 -1
  73. package/dist/server/components/json-schema-editor/index.js +4 -3
  74. package/dist/server/components/navigation-bar/index.js +1 -1
  75. package/dist/server/components/review-editor/index.js +4 -3
  76. package/dist/server/components/run-step-timeline/index.js +1 -1
  77. package/dist/server/components/schedule-builder/index.js +30 -0
  78. package/dist/server/components/side-navigation/index.js +2 -2
  79. package/dist/server/components/table/index.js +3 -3
  80. package/dist/server/components/tag-input/index.js +1 -1
  81. package/dist/server/components/time-field/index.js +5 -3
  82. package/dist/server/index.js +105 -92
  83. package/dist/server/index.server-0sx92swg.js +9 -0
  84. package/dist/server/{index.server-mzzb7dnm.js → index.server-2bbwajb2.js} +1 -1
  85. package/dist/server/{index.server-q7w4jw89.js → index.server-2s5h1d05.js} +2 -2
  86. package/dist/server/{index.server-dg83ke3k.js → index.server-2zdbz3rj.js} +1 -1
  87. package/dist/server/{index.server-0jhra85x.js → index.server-3w72dxz7.js} +4 -4
  88. package/dist/server/{index.server-5tf22d6e.js → index.server-4wx0qven.js} +3 -3
  89. package/dist/server/{index.server-qh9qzd5t.js → index.server-4x4hgvqe.js} +283 -13
  90. package/dist/server/{index.server-c6pe4kaf.js → index.server-5gzyqv78.js} +1 -1
  91. package/dist/server/{index.server-mqt1ngqw.js → index.server-6hnfzc7f.js} +19 -6
  92. package/dist/server/{index.server-894t97kv.js → index.server-7yvpb6z7.js} +4 -4
  93. package/dist/server/index.server-be9bpqeh.js +93 -0
  94. package/dist/server/{index.server-f3z7tpxr.js → index.server-c42b462p.js} +2 -4
  95. package/dist/server/{index.server-7vyzen5x.js → index.server-c5qv3p61.js} +171 -63
  96. package/dist/server/index.server-cch0eed5.js +144 -0
  97. package/dist/server/{index.server-btgb3d56.js → index.server-d5a5ccz7.js} +18 -12
  98. package/dist/server/index.server-g5jgq3jp.js +793 -0
  99. package/dist/server/index.server-gmh5ar16.js +2 -0
  100. package/dist/server/{index.server-0atyznp7.js → index.server-h89zk5ev.js} +7 -7
  101. package/dist/server/{index.server-jxg42jpz.js → index.server-jdcq5zza.js} +7 -7
  102. package/dist/server/{index.server-2dga5yzr.js → index.server-jfrssta6.js} +1 -1
  103. package/dist/server/{index.server-fy068yne.js → index.server-jj76374p.js} +1 -1
  104. package/dist/server/{index.server-39ne5cd1.js → index.server-pxq277t5.js} +32 -1
  105. package/dist/server/index.server-qbkxz7r2.js +750 -0
  106. package/dist/server/{index.server-marjz8ps.js → index.server-tcay903b.js} +3 -2
  107. package/dist/server/{index.server-yz4k36cw.js → index.server-z7hxtqs1.js} +1 -1
  108. package/package.json +87 -97
  109. package/src/components/chat/builders.ts +445 -14
  110. package/src/components/chat/conversation-model.ts +1 -0
  111. package/src/components/chat/index.ts +1 -0
  112. package/src/components/chat/utilities/conversation.ts +33 -15
  113. package/src/components/code-block/code-block.css +15 -9
  114. package/src/components/code-block/code-block.variables.json +7 -1
  115. package/src/components/code-block/code-block.variables.ts +7 -1
  116. package/src/components/confirm-dialog/confirm-dialog.css +2 -0
  117. package/src/components/confirm-dialog/confirm-dialog.schema.json +8 -0
  118. package/src/components/confirm-dialog/confirm-dialog.schema.ts +10 -0
  119. package/src/components/confirm-dialog/confirm-dialog.svelte +31 -3
  120. package/src/components/confirm-dialog/confirm-dialog.types.ts +7 -0
  121. package/src/components/connection-indicator/connection-indicator.css +136 -0
  122. package/src/components/connection-indicator/connection-indicator.examples.json +31 -0
  123. package/src/components/connection-indicator/connection-indicator.schema.json +29 -0
  124. package/src/components/connection-indicator/connection-indicator.schema.ts +36 -0
  125. package/src/components/connection-indicator/connection-indicator.svelte +63 -0
  126. package/src/components/connection-indicator/connection-indicator.types.ts +58 -0
  127. package/src/components/connection-indicator/connection-indicator.variables.json +1 -0
  128. package/src/components/connection-indicator/connection-indicator.variables.ts +3 -0
  129. package/src/components/connection-indicator/index.ts +10 -0
  130. package/src/components/container/container.schema.json +3 -3
  131. package/src/components/container/container.schema.ts +3 -3
  132. package/src/components/faceted-filter-bar/faceted-filter-bar.schema.json +4 -0
  133. package/src/components/faceted-filter-bar/faceted-filter-bar.schema.ts +4 -0
  134. package/src/components/faceted-filter-bar/faceted-filter-bar.svelte +13 -11
  135. package/src/components/faceted-filter-bar/faceted-filter-bar.types.ts +2 -0
  136. package/src/components/invocation-rule-builder/index.ts +4 -0
  137. package/src/components/invocation-rule-builder/invocation-rule-builder.css +28 -0
  138. package/src/components/invocation-rule-builder/invocation-rule-builder.examples.json +6 -0
  139. package/src/components/invocation-rule-builder/invocation-rule-builder.schema.json +76 -3
  140. package/src/components/invocation-rule-builder/invocation-rule-builder.schema.ts +83 -3
  141. package/src/components/invocation-rule-builder/invocation-rule-builder.svelte +326 -113
  142. package/src/components/invocation-rule-builder/invocation-rule-builder.types.ts +157 -43
  143. package/src/components/navigation-bar/navigation-bar.svelte +2 -2
  144. package/src/components/run-step-timeline/index.ts +8 -0
  145. package/src/components/run-step-timeline/run-step-branch-disclosure.svelte +16 -0
  146. package/src/components/run-step-timeline/run-step-timeline.css +124 -0
  147. package/src/components/run-step-timeline/run-step-timeline.examples.json +18 -0
  148. package/src/components/run-step-timeline/run-step-timeline.schema.json +872 -349
  149. package/src/components/run-step-timeline/run-step-timeline.schema.ts +931 -349
  150. package/src/components/run-step-timeline/run-step-timeline.svelte +388 -269
  151. package/src/components/run-step-timeline/run-step-timeline.types.ts +192 -5
  152. package/src/components/run-step-timeline/run-step-timeline.utilities.ts +310 -0
  153. package/src/components/schedule-builder/index.ts +14 -0
  154. package/src/components/schedule-builder/schedule-builder.css +121 -0
  155. package/src/components/schedule-builder/schedule-builder.examples.json +31 -0
  156. package/src/components/schedule-builder/schedule-builder.schema.json +78 -0
  157. package/src/components/schedule-builder/schedule-builder.schema.ts +86 -0
  158. package/src/components/schedule-builder/schedule-builder.svelte +629 -0
  159. package/src/components/schedule-builder/schedule-builder.types.ts +137 -0
  160. package/src/components/schedule-builder/schedule-builder.utilities.ts +344 -0
  161. package/src/components/schedule-builder/schedule-builder.variables.json +1 -0
  162. package/src/components/schedule-builder/schedule-builder.variables.ts +3 -0
  163. package/src/components/tag-input/tag-input.schema.json +4 -0
  164. package/src/components/tag-input/tag-input.schema.ts +4 -0
  165. package/src/components/tag-input/tag-input.svelte +20 -3
  166. package/src/components/tag-input/tag-input.types.ts +4 -0
  167. package/src/index.ts +23 -0
  168. package/src/styles/components.css +2 -0
  169. package/dist/server/index.server-qk1h0n02.js +0 -393
@@ -1,33 +1,464 @@
1
1
  /**
2
2
  * Conversation builders for the Chat component.
3
3
  *
4
- * Chat renders a {@link ConversationHistory} snapshot. These are thin aliases
5
- * over the published `conversationalist` builders so examples, demos, tests, and
6
- * simple apps construct the same transcript shape Chat consumes.
4
+ * Chat renders a {@link ConversationHistory} snapshot. These helpers construct
5
+ * the published Conversationalist shape without importing the package runtime
6
+ * into Cinder's browser graph.
7
7
  */
8
8
 
9
- import {
10
- appendAssistantMessage as appendConversationalistAssistantMessage,
11
- appendMessages as appendConversationalistMessages,
12
- appendUserMessage as appendConversationalistUserMessage,
13
- createConversationHistory,
14
- } from 'conversationalist';
9
+ import type {
10
+ ConversationEnvironment,
11
+ ConversationHistory,
12
+ ConversationStatus,
13
+ JSONValue,
14
+ Message,
15
+ MessageInput,
16
+ MultiModalContent,
17
+ TokenUsage,
18
+ ToolCall,
19
+ ToolResult,
20
+ } from './conversation-model.ts';
15
21
 
16
- import type { ConversationHistory, MessageInput } from './conversation-model.ts';
22
+ type CreateConversationOptions = {
23
+ id?: string;
24
+ title?: string;
25
+ status?: ConversationStatus;
26
+ metadata?: Record<string, JSONValue>;
27
+ };
28
+
29
+ type ConversationEnvironmentInput = Partial<ConversationEnvironment>;
30
+ type MessageContentInput = MessageInput['content'] | MultiModalContent;
31
+ type LooseMessageInput = Omit<MessageInput, 'content'> & { content: MessageContentInput };
32
+
33
+ const MESSAGE_ROLES = new Set([
34
+ 'user',
35
+ 'assistant',
36
+ 'system',
37
+ 'developer',
38
+ 'tool-call',
39
+ 'tool-result',
40
+ 'snapshot',
41
+ ]);
42
+ const CONVERSATION_STATUSES = new Set<ConversationStatus>(['active', 'archived', 'deleted']);
43
+
44
+ function createId(): string {
45
+ return globalThis.crypto?.randomUUID?.() ?? `message-${Date.now()}-${Math.random()}`;
46
+ }
47
+
48
+ function isRecord(value: unknown): value is Record<string, unknown> {
49
+ return typeof value === 'object' && value !== null;
50
+ }
51
+
52
+ function isJsonObject(value: unknown): value is Record<string, JSONValue> {
53
+ if (!isRecord(value) || Array.isArray(value)) return false;
54
+ const prototype = Object.getPrototypeOf(value);
55
+ return prototype === Object.prototype || prototype === null;
56
+ }
57
+
58
+ function isJsonValue(value: unknown): value is JSONValue {
59
+ if (value === null) return true;
60
+ switch (typeof value) {
61
+ case 'string':
62
+ case 'boolean':
63
+ return true;
64
+ case 'number':
65
+ return Number.isFinite(value);
66
+ case 'object':
67
+ if (Array.isArray(value)) return value.every(isJsonValue);
68
+ return isJsonObject(value) && Object.values(value).every(isJsonValue);
69
+ default:
70
+ return false;
71
+ }
72
+ }
73
+
74
+ function assertJsonObject(
75
+ value: unknown,
76
+ label: string,
77
+ ): asserts value is Record<string, JSONValue> {
78
+ if (!isJsonObject(value) || !Object.values(value).every(isJsonValue)) {
79
+ throw new Error(`${label} must be a JSON-compatible object`);
80
+ }
81
+ }
82
+
83
+ function isConversationEnvironmentParameter(value: unknown): value is ConversationEnvironmentInput {
84
+ if (!isRecord(value) || 'role' in value) return false;
85
+ return (
86
+ typeof value['now'] === 'function' ||
87
+ typeof value['randomId'] === 'function' ||
88
+ typeof value['estimateTokens'] === 'function' ||
89
+ (Array.isArray(value['plugins']) &&
90
+ value['plugins'].every((plugin) => typeof plugin === 'function'))
91
+ );
92
+ }
93
+
94
+ function isRoleHelperEnvironmentParameter(value: unknown): value is ConversationEnvironmentInput {
95
+ if (!isRecord(value) || 'role' in value) return false;
96
+ return (
97
+ typeof value['now'] === 'function' ||
98
+ typeof value['randomId'] === 'function' ||
99
+ typeof value['estimateTokens'] === 'function' ||
100
+ (Array.isArray(value['plugins']) &&
101
+ value['plugins'].length > 0 &&
102
+ value['plugins'].every((plugin) => typeof plugin === 'function'))
103
+ );
104
+ }
105
+
106
+ function isMultiModalContentPart(value: unknown): value is MultiModalContent {
107
+ if (!isJsonObject(value) || typeof value['type'] !== 'string') return false;
108
+ if (!Object.values(value).every(isJsonValue)) return false;
109
+ switch (value['type']) {
110
+ case 'text':
111
+ return typeof value['text'] === 'string';
112
+ case 'image':
113
+ return typeof value['url'] === 'string';
114
+ case 'thinking':
115
+ return typeof value['thinking'] === 'string';
116
+ case 'redacted_thinking':
117
+ return typeof value['data'] === 'string';
118
+ case 'server_tool_use':
119
+ return typeof value['id'] === 'string' && typeof value['name'] === 'string';
120
+ case 'web_search_tool_result':
121
+ return typeof value['tool_use_id'] === 'string' && 'content' in value;
122
+ case 'container_upload':
123
+ return typeof value['file_id'] === 'string';
124
+ default:
125
+ return false;
126
+ }
127
+ }
128
+
129
+ function isMessageContentInput(value: unknown): value is MessageContentInput {
130
+ return (
131
+ typeof value === 'string' ||
132
+ (Array.isArray(value) && value.every(isMultiModalContentPart)) ||
133
+ isMultiModalContentPart(value)
134
+ );
135
+ }
136
+
137
+ function isLooseMessageInput(value: unknown): value is LooseMessageInput {
138
+ return (
139
+ isRecord(value) &&
140
+ typeof value['role'] === 'string' &&
141
+ MESSAGE_ROLES.has(value['role']) &&
142
+ isMessageContentInput(value['content']) &&
143
+ (value['hidden'] === undefined || typeof value['hidden'] === 'boolean') &&
144
+ (value['goalCompleted'] === undefined || typeof value['goalCompleted'] === 'boolean')
145
+ );
146
+ }
147
+
148
+ function isMetadataRecord(value: unknown): value is Record<string, JSONValue> {
149
+ return isJsonObject(value);
150
+ }
151
+
152
+ function resolveEnvironment(environment?: ConversationEnvironmentInput): {
153
+ now: () => string;
154
+ randomId: () => string;
155
+ plugins: NonNullable<ConversationEnvironmentInput['plugins']>;
156
+ } {
157
+ const plugins = environment?.plugins ?? [];
158
+ if (!Array.isArray(plugins) || plugins.some((plugin) => typeof plugin !== 'function')) {
159
+ throw new Error('conversation environment plugins must be functions');
160
+ }
161
+ return {
162
+ now: environment?.now ?? (() => new Date().toISOString()),
163
+ randomId: environment?.randomId ?? createId,
164
+ plugins: [...plugins],
165
+ };
166
+ }
167
+
168
+ function cloneStructuredValue<T>(value: T): T {
169
+ return structuredClone(value);
170
+ }
171
+
172
+ function cloneMetadata(metadata: Record<string, JSONValue> | undefined): Record<string, JSONValue> {
173
+ if (metadata === undefined) return {};
174
+ assertJsonObject(metadata, 'metadata');
175
+ return cloneStructuredValue(metadata);
176
+ }
177
+
178
+ function normalizeContent(content: MessageContentInput): MessageInput['content'] {
179
+ if (typeof content === 'string') return content;
180
+ return Array.isArray(content) ? cloneStructuredValue(content) : [cloneStructuredValue(content)];
181
+ }
182
+
183
+ function cloneTokenUsage(tokenUsage: TokenUsage): TokenUsage {
184
+ assertJsonObject(tokenUsage, 'tokenUsage');
185
+ const values = Object.values(tokenUsage);
186
+ if (values.some((value) => typeof value !== 'number')) {
187
+ throw new Error('tokenUsage values must be numbers');
188
+ }
189
+ if (
190
+ values.some((value) => typeof value === 'number' && (!Number.isInteger(value) || value < 0))
191
+ ) {
192
+ throw new Error('tokenUsage values must be non-negative integers');
193
+ }
194
+ return cloneStructuredValue(tokenUsage);
195
+ }
196
+
197
+ function cloneToolCall(toolCall: ToolCall): ToolCall {
198
+ assertJsonObject(toolCall, 'toolCall');
199
+ if (
200
+ typeof toolCall.id !== 'string' ||
201
+ typeof toolCall.name !== 'string' ||
202
+ !('arguments' in toolCall) ||
203
+ !isJsonValue(toolCall.arguments)
204
+ ) {
205
+ throw new Error('toolCall must include string id, string name, and JSON arguments');
206
+ }
207
+ return cloneStructuredValue(toolCall);
208
+ }
209
+
210
+ function cloneToolResult(toolResult: ToolResult): ToolResult {
211
+ assertJsonObject(toolResult, 'toolResult');
212
+ if (
213
+ typeof toolResult.callId !== 'string' ||
214
+ !['success', 'error', 'action_required'].includes(String(toolResult.outcome)) ||
215
+ !('content' in toolResult) ||
216
+ !isJsonValue(toolResult.content)
217
+ ) {
218
+ throw new Error('toolResult must include string callId, valid outcome, and JSON content');
219
+ }
220
+ return cloneStructuredValue(toolResult);
221
+ }
222
+
223
+ function getOrderedMessages(conversation: ConversationHistory): Message[] {
224
+ return conversation.ids
225
+ .map((id) => conversation.messages[id])
226
+ .filter((message): message is Message => message !== undefined);
227
+ }
228
+
229
+ function buildToolCallIds(messages: ReadonlyArray<Message>): Set<string> {
230
+ const toolCallIds = new Set<string>();
231
+ for (const message of messages) {
232
+ if (message.role === 'tool-call' && message.toolCall !== undefined) {
233
+ if (toolCallIds.has(message.toolCall.id)) {
234
+ throw new Error(`duplicate toolCall.id in conversation: ${message.toolCall.id}`);
235
+ }
236
+ toolCallIds.add(message.toolCall.id);
237
+ }
238
+ }
239
+ return toolCallIds;
240
+ }
241
+
242
+ function assertValidToolMessage(input: LooseMessageInput, toolCallIds: Set<string>): void {
243
+ if (input.role === 'tool-result' && input.toolResult !== undefined) {
244
+ if (!toolCallIds.has(input.toolResult.callId)) {
245
+ throw new Error(`tool result references non-existent tool-call: ${input.toolResult.callId}`);
246
+ }
247
+ }
248
+ if (input.role === 'tool-call' && input.toolCall !== undefined) {
249
+ if (toolCallIds.has(input.toolCall.id)) {
250
+ throw new Error(`duplicate toolCall.id in conversation: ${input.toolCall.id}`);
251
+ }
252
+ toolCallIds.add(input.toolCall.id);
253
+ }
254
+ }
17
255
 
18
256
  /** Creates a new empty conversation snapshot. */
19
- const createConversation = createConversationHistory;
257
+ function createConversation(
258
+ options: CreateConversationOptions = {},
259
+ environment?: ConversationEnvironmentInput,
260
+ ): ConversationHistory {
261
+ const resolvedEnvironment = resolveEnvironment(environment);
262
+ const createdAt = resolvedEnvironment.now();
263
+ const conversationId = options.id ?? resolvedEnvironment.randomId();
264
+ if (typeof conversationId !== 'string') {
265
+ throw new Error('conversation id must be a string');
266
+ }
267
+ if (typeof createdAt !== 'string') {
268
+ throw new Error('conversation timestamp must be a string');
269
+ }
270
+ if (options.title !== undefined && typeof options.title !== 'string') {
271
+ throw new Error('conversation title must be a string');
272
+ }
273
+ if (options.status !== undefined && !CONVERSATION_STATUSES.has(options.status)) {
274
+ throw new Error('conversation status must be active, archived, or deleted');
275
+ }
276
+ return {
277
+ schemaVersion: 4,
278
+ id: conversationId,
279
+ ...(options.title !== undefined ? { title: options.title } : {}),
280
+ status: options.status ?? 'active',
281
+ metadata: cloneMetadata(options.metadata),
282
+ ids: [],
283
+ messages: {},
284
+ createdAt,
285
+ updatedAt: createdAt,
286
+ };
287
+ }
288
+
289
+ function materializeMessage(
290
+ input: LooseMessageInput,
291
+ position: number,
292
+ createdAt: string,
293
+ id: string,
294
+ ): Message {
295
+ return {
296
+ id,
297
+ role: input.role,
298
+ content: normalizeContent(input.content),
299
+ position,
300
+ createdAt,
301
+ metadata: cloneMetadata(input.metadata),
302
+ hidden: input.hidden ?? false,
303
+ ...(input.toolCall !== undefined ? { toolCall: cloneToolCall(input.toolCall) } : {}),
304
+ ...(input.toolResult !== undefined ? { toolResult: cloneToolResult(input.toolResult) } : {}),
305
+ ...(input.tokenUsage !== undefined ? { tokenUsage: cloneTokenUsage(input.tokenUsage) } : {}),
306
+ ...(input.role === 'assistant' && input.goalCompleted !== undefined
307
+ ? { goalCompleted: input.goalCompleted }
308
+ : {}),
309
+ };
310
+ }
311
+
312
+ function partitionAppendArguments(args: unknown[]): {
313
+ inputs: LooseMessageInput[];
314
+ environment?: ConversationEnvironmentInput;
315
+ } {
316
+ const filteredArguments = args.at(-1) === undefined ? args.slice(0, -1) : args;
317
+ if (filteredArguments.some((argument) => argument === undefined)) {
318
+ throw new Error(
319
+ 'appendMessages expected MessageInput arguments before the optional environment',
320
+ );
321
+ }
322
+ const lastArgument = filteredArguments.at(-1);
323
+ const inputArguments = isConversationEnvironmentParameter(lastArgument)
324
+ ? filteredArguments.slice(0, -1)
325
+ : filteredArguments;
326
+ const inputs: LooseMessageInput[] = [];
327
+ for (const argument of inputArguments) {
328
+ if (!isLooseMessageInput(argument)) {
329
+ throw new Error(
330
+ 'appendMessages expected MessageInput arguments before the optional environment',
331
+ );
332
+ }
333
+ inputs.push(argument);
334
+ }
335
+ if (isConversationEnvironmentParameter(lastArgument)) {
336
+ return { inputs, environment: lastArgument };
337
+ }
338
+ return { inputs };
339
+ }
20
340
 
21
341
  /** Appends one or more messages, preserving the previous no-op identity contract. */
22
342
  function appendMessages(
23
343
  conversation: ConversationHistory,
24
344
  ...inputs: MessageInput[]
345
+ ): ConversationHistory;
346
+ function appendMessages(
347
+ conversation: ConversationHistory,
348
+ ...inputsAndEnvironment: [...MessageInput[], ConversationEnvironmentInput | undefined]
349
+ ): ConversationHistory;
350
+ function appendMessages(
351
+ conversation: ConversationHistory,
352
+ ...args: unknown[]
25
353
  ): ConversationHistory {
354
+ const { inputs, environment } = partitionAppendArguments(args);
26
355
  if (inputs.length === 0) return conversation;
27
- return appendConversationalistMessages(conversation, ...inputs);
356
+ const resolvedEnvironment = resolveEnvironment(environment);
357
+ const updatedAt = resolvedEnvironment.now();
358
+ if (typeof updatedAt !== 'string') {
359
+ throw new Error('conversation timestamp must be a string');
360
+ }
361
+ const nextIds = [...conversation.ids];
362
+ const nextMessages = { ...conversation.messages };
363
+ const toolCallIds = buildToolCallIds(getOrderedMessages(conversation));
364
+
365
+ inputs.forEach((input, index) => {
366
+ const normalizedInput: MessageInput = { ...input, content: normalizeContent(input.content) };
367
+ const processedInput = resolvedEnvironment.plugins.reduce(
368
+ (current, plugin) => plugin(current),
369
+ normalizedInput,
370
+ );
371
+ if (!isLooseMessageInput(processedInput)) {
372
+ throw new Error('conversation plugin returned an invalid MessageInput');
373
+ }
374
+ assertValidToolMessage(processedInput, toolCallIds);
375
+ const messageId = resolvedEnvironment.randomId();
376
+ if (typeof messageId !== 'string') {
377
+ throw new Error('generated message id must be a string');
378
+ }
379
+ if (nextMessages[messageId] !== undefined || nextIds.includes(messageId)) {
380
+ throw new Error(`duplicate message id in conversation: ${messageId}`);
381
+ }
382
+ const message = materializeMessage(
383
+ processedInput,
384
+ conversation.ids.length + index,
385
+ updatedAt,
386
+ messageId,
387
+ );
388
+ nextIds.push(message.id);
389
+ nextMessages[message.id] = message;
390
+ });
391
+
392
+ return {
393
+ ...conversation,
394
+ ids: nextIds,
395
+ messages: nextMessages,
396
+ updatedAt,
397
+ };
28
398
  }
29
399
 
30
- const appendUserMessage = appendConversationalistUserMessage;
31
- const appendAssistantMessage = appendConversationalistAssistantMessage;
400
+ function appendUserMessage(
401
+ conversation: ConversationHistory,
402
+ content: MessageInput['content'],
403
+ metadata?: Record<string, JSONValue>,
404
+ environment?: ConversationEnvironmentInput,
405
+ ): ConversationHistory;
406
+ function appendUserMessage(
407
+ conversation: ConversationHistory,
408
+ content: MessageInput['content'],
409
+ environment?: ConversationEnvironmentInput,
410
+ ): ConversationHistory;
411
+ function appendUserMessage(
412
+ conversation: ConversationHistory,
413
+ content: MessageInput['content'],
414
+ metadataOrEnvironment?: Record<string, JSONValue> | ConversationEnvironmentInput,
415
+ environment?: ConversationEnvironmentInput,
416
+ ): ConversationHistory {
417
+ const metadata = resolveRoleHelperMetadata(metadataOrEnvironment, environment);
418
+ const resolvedEnvironment =
419
+ environment ??
420
+ (isRoleHelperEnvironmentParameter(metadataOrEnvironment) ? metadataOrEnvironment : undefined);
421
+ return appendMessages(conversation, { role: 'user', content, metadata }, resolvedEnvironment);
422
+ }
423
+
424
+ function appendAssistantMessage(
425
+ conversation: ConversationHistory,
426
+ content: MessageInput['content'],
427
+ metadata?: Record<string, JSONValue>,
428
+ environment?: ConversationEnvironmentInput,
429
+ ): ConversationHistory;
430
+ function appendAssistantMessage(
431
+ conversation: ConversationHistory,
432
+ content: MessageInput['content'],
433
+ environment?: ConversationEnvironmentInput,
434
+ ): ConversationHistory;
435
+ function appendAssistantMessage(
436
+ conversation: ConversationHistory,
437
+ content: MessageInput['content'],
438
+ metadataOrEnvironment?: Record<string, JSONValue> | ConversationEnvironmentInput,
439
+ environment?: ConversationEnvironmentInput,
440
+ ): ConversationHistory {
441
+ const metadata = resolveRoleHelperMetadata(metadataOrEnvironment, environment);
442
+ const resolvedEnvironment =
443
+ environment ??
444
+ (isRoleHelperEnvironmentParameter(metadataOrEnvironment) ? metadataOrEnvironment : undefined);
445
+ return appendMessages(
446
+ conversation,
447
+ { role: 'assistant', content, metadata },
448
+ resolvedEnvironment,
449
+ );
450
+ }
451
+
452
+ function resolveRoleHelperMetadata(
453
+ metadataOrEnvironment: Record<string, JSONValue> | ConversationEnvironmentInput | undefined,
454
+ environment: ConversationEnvironmentInput | undefined,
455
+ ): Record<string, JSONValue> | undefined {
456
+ if (metadataOrEnvironment === undefined) return undefined;
457
+ if (environment === undefined && isRoleHelperEnvironmentParameter(metadataOrEnvironment)) {
458
+ return undefined;
459
+ }
460
+ if (isMetadataRecord(metadataOrEnvironment)) return metadataOrEnvironment;
461
+ throw new Error('metadata must be a JSON-compatible object');
462
+ }
32
463
 
33
464
  export { appendAssistantMessage, appendMessages, appendUserMessage, createConversation };
@@ -13,6 +13,7 @@ import type { ToMarkdownOptions as ConversationalistToMarkdownOptions } from 'co
13
13
  export type {
14
14
  AssistantMessage,
15
15
  ContainerUploadContent,
16
+ ConversationEnvironment,
16
17
  ConversationHistory,
17
18
  ConversationStatus,
18
19
  ImageContent,
@@ -46,6 +46,7 @@ export {
46
46
  export type {
47
47
  AssistantMessage,
48
48
  ContainerUploadContent,
49
+ ConversationEnvironment,
49
50
  ConversationHistory,
50
51
  ConversationStatus,
51
52
  ExportOptions,
@@ -1,26 +1,44 @@
1
1
  /**
2
2
  * Conversation-reading helpers for Chat.
3
3
  *
4
- * These delegate to `conversationalist` so message ordering stays aligned with
5
- * the published conversation package. Tool pairing keeps Chat's role gate so an
6
- * incidental tool-shaped field on a non-tool message never renders as a pair.
4
+ * These helpers operate on the Conversationalist transcript shape while keeping
5
+ * Cinder's browser runtime free of the full conversation package.
7
6
  */
8
7
 
9
- import { getMessages } from 'conversationalist';
10
- import { pairToolCallsWithResults as pairConversationalistToolCallsWithResults } from 'conversationalist/utilities';
8
+ import type {
9
+ ConversationHistory,
10
+ Message,
11
+ ToolCallPair,
12
+ ToolResult,
13
+ } from '../conversation-model.ts';
11
14
 
12
- import type { Message, ToolCallPair } from '../conversation-model.ts';
13
-
14
- function hasPairableToolField(message: Message): boolean {
15
- return (
16
- (message.role === 'tool-call' && message.toolCall !== undefined) ||
17
- (message.role === 'tool-result' && message.toolResult !== undefined)
18
- );
15
+ export function getMessages(
16
+ conversation: ConversationHistory,
17
+ options: { includeHidden?: boolean } = {},
18
+ ): Message[] {
19
+ return conversation.ids
20
+ .map((id) => conversation.messages[id])
21
+ .filter((message): message is Message => message !== undefined)
22
+ .filter((message) => options.includeHidden === true || !message.hidden);
19
23
  }
20
24
 
21
25
  /** Pairs tool calls with role-valid tool results from an already-ordered message array. */
22
26
  export function pairToolCallsWithResults(messages: ReadonlyArray<Message>): ToolCallPair[] {
23
- return pairConversationalistToolCallsWithResults(messages.filter(hasPairableToolField));
24
- }
27
+ const resultsByCallId = new Map<string, ToolResult>();
28
+ for (const message of messages) {
29
+ if (message.role === 'tool-result' && message.toolResult !== undefined) {
30
+ resultsByCallId.set(message.toolResult.callId, message.toolResult);
31
+ }
32
+ }
25
33
 
26
- export { getMessages };
34
+ const pairs: ToolCallPair[] = [];
35
+ for (const message of messages) {
36
+ if (message.role === 'tool-call' && message.toolCall !== undefined) {
37
+ pairs.push({
38
+ call: message.toolCall,
39
+ result: resultsByCallId.get(message.toolCall.id),
40
+ });
41
+ }
42
+ }
43
+ return pairs;
44
+ }
@@ -9,12 +9,17 @@
9
9
  * ======================================== */
10
10
 
11
11
  .cinder-code-block {
12
+ --cinder-code-block-background: var(--_cinder-code-block-code-surface);
13
+ --cinder-code-block-font-size: var(--cinder-text-sm);
14
+ --cinder-code-block-height: auto;
15
+ --cinder-code-block-line-height: var(--cinder-leading-relaxed);
16
+ --cinder-code-block-padding: var(--cinder-space-4);
12
17
  --_cinder-code-block-code-surface: light-dark(
13
18
  var(--cinder-surface-raised),
14
19
  var(--cinder-surface-inset)
15
20
  );
16
21
 
17
- background: var(--cinder-surface-inset);
22
+ background: var(--cinder-code-block-background);
18
23
  border: 1px solid var(--cinder-border);
19
24
  border-radius: var(--cinder-radius-md);
20
25
  overflow: hidden;
@@ -44,33 +49,34 @@
44
49
  * inner pre elements can stay metrically identical. */
45
50
  .cinder-code-block__viewport {
46
51
  display: block;
47
- overflow-x: auto;
48
- background: var(--_cinder-code-block-code-surface);
52
+ block-size: var(--cinder-code-block-height);
53
+ overflow: auto;
54
+ background: var(--cinder-code-block-background);
49
55
  }
50
56
 
51
57
  .cinder-code-block__pre,
52
58
  .cinder-code-block :where(pre.shiki) {
53
59
  margin: 0;
54
- padding: var(--cinder-space-4);
60
+ padding: var(--cinder-code-block-padding);
55
61
  width: max-content;
56
62
  min-width: 100%;
57
63
  overflow-x: clip;
58
64
  font-family: var(--cinder-font-mono);
59
- font-size: var(--cinder-text-sm);
60
- line-height: var(--cinder-leading-relaxed);
65
+ font-size: var(--cinder-code-block-font-size);
66
+ line-height: var(--cinder-code-block-line-height);
61
67
  color: var(--cinder-text);
62
68
  /* Use near-white in light mode so github-light token colors (e.g. #d73a49
63
69
  * keyword red) clear 4.5:1 WCAG AA. In dark mode keep the deep inset so
64
70
  * github-dark tokens maintain contrast on a very dark surface. */
65
- background: var(--_cinder-code-block-code-surface);
71
+ background: var(--cinder-code-block-background);
66
72
  }
67
73
 
68
74
  /* Shiki writes inline background styles on its generated <pre>; keep the
69
75
  * highlighted state visually and metrically identical to the plain fallback. */
70
76
  .cinder-code-block :where(pre.shiki) {
71
77
  margin: 0 !important;
72
- padding: var(--cinder-space-4) !important;
73
- background: var(--_cinder-code-block-code-surface) !important;
78
+ padding: var(--cinder-code-block-padding) !important;
79
+ background: var(--cinder-code-block-background) !important;
74
80
  }
75
81
 
76
82
  .cinder-code-block__code,
@@ -1 +1,7 @@
1
- []
1
+ [
2
+ "--cinder-code-block-background",
3
+ "--cinder-code-block-font-size",
4
+ "--cinder-code-block-height",
5
+ "--cinder-code-block-line-height",
6
+ "--cinder-code-block-padding"
7
+ ]
@@ -1,3 +1,9 @@
1
- const variables: readonly string[] = [];
1
+ const variables: readonly string[] = [
2
+ '--cinder-code-block-background',
3
+ '--cinder-code-block-font-size',
4
+ '--cinder-code-block-height',
5
+ '--cinder-code-block-line-height',
6
+ '--cinder-code-block-padding',
7
+ ];
2
8
 
3
9
  export default variables;
@@ -1,4 +1,6 @@
1
1
  @layer cinder.tokens, cinder.foundation, cinder.components, cinder.utilities;
2
+ @import '../input/input.css';
3
+
2
4
  @layer cinder.components {
3
5
  .cinder-confirm-dialog__description {
4
6
  margin: 0;
@@ -26,6 +26,14 @@
26
26
  "type": "boolean",
27
27
  "description": "When true, the confirm button uses variant=\"danger\". The cancel button still\nreceives default focus regardless — color is never the sole destructive signal."
28
28
  },
29
+ "typeToConfirm": {
30
+ "type": "string",
31
+ "description": "When set, renders a labelled text input and disables the confirm button until the\ntrimmed input matches this value case-insensitively."
32
+ },
33
+ "typeToConfirmLabel": {
34
+ "type": "string",
35
+ "description": "Visible label for the typed-confirmation input. Defaults to `Type \"<value>\" to confirm`."
36
+ },
29
37
  "class": {
30
38
  "type": "string",
31
39
  "description": "Optional extra class on the underlying <Modal>. Destructured as `class: className` per repo convention."
@@ -31,6 +31,16 @@ const schema = {
31
31
  description:
32
32
  'When true, the confirm button uses variant="danger". The cancel button still\nreceives default focus regardless — color is never the sole destructive signal.',
33
33
  },
34
+ typeToConfirm: {
35
+ type: 'string',
36
+ description:
37
+ 'When set, renders a labelled text input and disables the confirm button until the\ntrimmed input matches this value case-insensitively.',
38
+ },
39
+ typeToConfirmLabel: {
40
+ type: 'string',
41
+ description:
42
+ 'Visible label for the typed-confirmation input. Defaults to `Type "<value>" to confirm`.',
43
+ },
34
44
  class: {
35
45
  type: 'string',
36
46
  description: