@contractspec/module.ai-chat 4.3.17 → 4.3.18

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.
@@ -1,687 +1,40 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/core/agent-adapter.ts
5
- function createChatAgentAdapter(agent) {
6
- return {
7
- async generate({ prompt, signal }) {
8
- const result = await agent.generate({ prompt, signal });
9
- return {
10
- text: result.text,
11
- toolCalls: result.toolCalls,
12
- toolResults: result.toolResults,
13
- usage: result.usage
14
- };
15
- }
16
- };
17
- }
18
- // src/core/agent-tools-adapter.ts
19
- import { tool } from "ai";
20
- import { z } from "zod";
21
- function getInputSchema(_schema) {
22
- return z.object({}).passthrough();
23
- }
24
- function agentToolConfigsToToolSet(configs, handlers) {
25
- const result = {};
26
- for (const config of configs) {
27
- const handler = handlers?.[config.name];
28
- const inputSchema = getInputSchema(config.schema);
29
- result[config.name] = tool({
30
- description: config.description ?? config.name,
31
- inputSchema,
32
- execute: async (input) => {
33
- if (!handler) {
34
- return {
35
- status: "unimplemented",
36
- message: "Wire handler in host",
37
- toolName: config.name
38
- };
39
- }
40
- try {
41
- const output = await Promise.resolve(handler(input));
42
- return typeof output === "string" ? output : output;
43
- } catch (err) {
44
- return {
45
- status: "error",
46
- error: err instanceof Error ? err.message : String(err),
47
- toolName: config.name
48
- };
49
- }
50
- }
51
- });
52
- }
53
- return result;
54
- }
55
- // src/core/chat-service.ts
56
- import { generateText, streamText } from "ai";
57
- import { compilePlannerPrompt } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
58
-
59
- // src/core/contracts-context.ts
60
- function buildContractsContextPrompt(config) {
61
- const parts = [];
62
- if (!config.agentSpecs?.length && !config.dataViewSpecs?.length && !config.formSpecs?.length && !config.presentationSpecs?.length && !config.operationRefs?.length) {
63
- return "";
64
- }
65
- parts.push(`
66
-
67
- ## Available resources`);
68
- if (config.agentSpecs?.length) {
69
- parts.push(`
70
- ### Agent tools`);
71
- for (const agent of config.agentSpecs) {
72
- const toolNames = agent.tools?.map((t) => t.name).join(", ") ?? "none";
73
- parts.push(`- **${agent.key}**: tools: ${toolNames}`);
74
- }
75
- }
76
- if (config.dataViewSpecs?.length) {
77
- parts.push(`
78
- ### Data views`);
79
- for (const dv of config.dataViewSpecs) {
80
- parts.push(`- **${dv.key}**: ${dv.meta.title ?? dv.key}`);
81
- }
82
- }
83
- if (config.formSpecs?.length) {
84
- parts.push(`
85
- ### Forms`);
86
- for (const form of config.formSpecs) {
87
- parts.push(`- **${form.key}**: ${form.meta.title ?? form.key}`);
88
- }
89
- }
90
- if (config.presentationSpecs?.length) {
91
- parts.push(`
92
- ### Presentations`);
93
- for (const pres of config.presentationSpecs) {
94
- parts.push(`- **${pres.key}**: ${pres.meta.title ?? pres.key} (targets: ${pres.targets?.join(", ") ?? "react"})`);
95
- }
96
- }
97
- if (config.operationRefs?.length) {
98
- parts.push(`
99
- ### Operations`);
100
- for (const op of config.operationRefs) {
101
- parts.push(`- **${op.key}@${op.version}**`);
102
- }
103
- }
104
- parts.push(`
105
- Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`);
106
- return parts.join(`
107
- `);
108
- }
109
-
110
- // src/core/conversation-store.ts
111
- function generateId(prefix) {
112
- return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
113
- }
114
-
115
- class InMemoryConversationStore {
116
- conversations = new Map;
117
- async get(conversationId) {
118
- return this.conversations.get(conversationId) ?? null;
119
- }
120
- async create(conversation) {
121
- const now = new Date;
122
- const fullConversation = {
123
- ...conversation,
124
- id: generateId("conv"),
125
- createdAt: now,
126
- updatedAt: now
127
- };
128
- this.conversations.set(fullConversation.id, fullConversation);
129
- return fullConversation;
130
- }
131
- async update(conversationId, updates) {
132
- const conversation = this.conversations.get(conversationId);
133
- if (!conversation)
134
- return null;
135
- const updated = {
136
- ...conversation,
137
- ...updates,
138
- updatedAt: new Date
139
- };
140
- this.conversations.set(conversationId, updated);
141
- return updated;
142
- }
143
- async appendMessage(conversationId, message) {
144
- const conversation = this.conversations.get(conversationId);
145
- if (!conversation) {
146
- throw new Error(`Conversation ${conversationId} not found`);
147
- }
148
- const now = new Date;
149
- const fullMessage = {
150
- ...message,
151
- id: generateId("msg"),
152
- conversationId,
153
- createdAt: now,
154
- updatedAt: now
155
- };
156
- conversation.messages.push(fullMessage);
157
- conversation.updatedAt = now;
158
- return fullMessage;
159
- }
160
- async updateMessage(conversationId, messageId, updates) {
161
- const conversation = this.conversations.get(conversationId);
162
- if (!conversation)
163
- return null;
164
- const messageIndex = conversation.messages.findIndex((m) => m.id === messageId);
165
- if (messageIndex === -1)
166
- return null;
167
- const message = conversation.messages[messageIndex];
168
- if (!message)
169
- return null;
170
- const updated = {
171
- ...message,
172
- ...updates,
173
- updatedAt: new Date
174
- };
175
- conversation.messages[messageIndex] = updated;
176
- conversation.updatedAt = new Date;
177
- return updated;
178
- }
179
- async delete(conversationId) {
180
- return this.conversations.delete(conversationId);
181
- }
182
- async list(options) {
183
- let results = Array.from(this.conversations.values());
184
- if (options?.status) {
185
- results = results.filter((c) => c.status === options.status);
186
- }
187
- if (options?.projectId) {
188
- results = results.filter((c) => c.projectId === options.projectId);
189
- }
190
- if (options?.tags && options.tags.length > 0) {
191
- const tagSet = new Set(options.tags);
192
- results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
193
- }
194
- results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
195
- const offset = options?.offset ?? 0;
196
- const limit = options?.limit ?? 100;
197
- return results.slice(offset, offset + limit);
198
- }
199
- async fork(conversationId, upToMessageId) {
200
- const source = this.conversations.get(conversationId);
201
- if (!source) {
202
- throw new Error(`Conversation ${conversationId} not found`);
203
- }
204
- let messagesToCopy = source.messages;
205
- if (upToMessageId) {
206
- const idx = source.messages.findIndex((m) => m.id === upToMessageId);
207
- if (idx === -1) {
208
- throw new Error(`Message ${upToMessageId} not found`);
209
- }
210
- messagesToCopy = source.messages.slice(0, idx + 1);
211
- }
212
- const now = new Date;
213
- const forkedMessages = messagesToCopy.map((m) => ({
214
- ...m,
215
- id: generateId("msg"),
216
- conversationId: "",
217
- createdAt: new Date(m.createdAt),
218
- updatedAt: new Date(m.updatedAt)
219
- }));
220
- const forked = {
221
- ...source,
222
- id: generateId("conv"),
223
- title: source.title ? `${source.title} (fork)` : undefined,
224
- forkedFromId: source.id,
225
- createdAt: now,
226
- updatedAt: now,
227
- messages: forkedMessages
228
- };
229
- for (const m of forked.messages) {
230
- m.conversationId = forked.id;
231
- }
232
- this.conversations.set(forked.id, forked);
233
- return forked;
234
- }
235
- async truncateAfter(conversationId, messageId) {
236
- const conv = this.conversations.get(conversationId);
237
- if (!conv)
238
- return null;
239
- const idx = conv.messages.findIndex((m) => m.id === messageId);
240
- if (idx === -1)
241
- return null;
242
- conv.messages = conv.messages.slice(0, idx + 1);
243
- conv.updatedAt = new Date;
244
- return conv;
245
- }
246
- async search(query, limit = 20) {
247
- const lowerQuery = query.toLowerCase();
248
- const results = [];
249
- for (const conversation of this.conversations.values()) {
250
- if (conversation.title?.toLowerCase().includes(lowerQuery)) {
251
- results.push(conversation);
252
- continue;
253
- }
254
- const hasMatch = conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery));
255
- if (hasMatch) {
256
- results.push(conversation);
257
- }
258
- if (results.length >= limit)
259
- break;
260
- }
261
- return results;
262
- }
263
- clear() {
264
- this.conversations.clear();
265
- }
266
- }
267
- function createInMemoryConversationStore() {
268
- return new InMemoryConversationStore;
269
- }
270
-
271
- // src/core/surface-planner-tools.ts
272
- import { buildSurfacePatchProposal } from "@contractspec/lib.surface-runtime/runtime/planner-tools";
273
- import {
274
- validatePatchProposal
275
- } from "@contractspec/lib.surface-runtime/spec/validate-surface-patch";
276
- import { tool as tool2 } from "ai";
277
- import { z as z2 } from "zod";
278
- var VALID_OPS = [
279
- "insert-node",
280
- "replace-node",
281
- "remove-node",
282
- "move-node",
283
- "resize-panel",
284
- "set-layout",
285
- "reveal-field",
286
- "hide-field",
287
- "promote-action",
288
- "set-focus"
289
- ];
290
- var DEFAULT_NODE_KINDS = [
291
- "entity-section",
292
- "entity-card",
293
- "data-view",
294
- "assistant-panel",
295
- "chat-thread",
296
- "action-bar",
297
- "timeline",
298
- "table",
299
- "rich-doc",
300
- "form",
301
- "chart",
302
- "custom-widget"
303
- ];
304
- function collectSlotIdsFromRegion(node) {
305
- const ids = [];
306
- if (node.type === "slot") {
307
- ids.push(node.slotId);
308
- }
309
- if (node.type === "panel-group" || node.type === "stack") {
310
- for (const child of node.children) {
311
- ids.push(...collectSlotIdsFromRegion(child));
312
- }
313
- }
314
- if (node.type === "tabs") {
315
- for (const tab of node.tabs) {
316
- ids.push(...collectSlotIdsFromRegion(tab.child));
317
- }
318
- }
319
- if (node.type === "floating") {
320
- ids.push(node.anchorSlotId);
321
- ids.push(...collectSlotIdsFromRegion(node.child));
322
- }
323
- return ids;
324
- }
325
- function deriveConstraints(plan) {
326
- const slotIds = collectSlotIdsFromRegion(plan.layoutRoot);
327
- const uniqueSlots = [...new Set(slotIds)];
328
- return {
329
- allowedOps: VALID_OPS,
330
- allowedSlots: uniqueSlots.length > 0 ? uniqueSlots : ["assistant", "primary"],
331
- allowedNodeKinds: DEFAULT_NODE_KINDS
332
- };
333
- }
334
- var ProposePatchInputSchema = z2.object({
335
- proposalId: z2.string().describe("Unique proposal identifier"),
336
- ops: z2.array(z2.object({
337
- op: z2.enum([
338
- "insert-node",
339
- "replace-node",
340
- "remove-node",
341
- "move-node",
342
- "resize-panel",
343
- "set-layout",
344
- "reveal-field",
345
- "hide-field",
346
- "promote-action",
347
- "set-focus"
348
- ]),
349
- slotId: z2.string().optional(),
350
- nodeId: z2.string().optional(),
351
- toSlotId: z2.string().optional(),
352
- index: z2.number().optional(),
353
- node: z2.object({
354
- nodeId: z2.string(),
355
- kind: z2.string(),
356
- title: z2.string().optional(),
357
- props: z2.record(z2.string(), z2.unknown()).optional(),
358
- children: z2.array(z2.unknown()).optional()
359
- }).optional(),
360
- persistKey: z2.string().optional(),
361
- sizes: z2.array(z2.number()).optional(),
362
- layoutId: z2.string().optional(),
363
- fieldId: z2.string().optional(),
364
- actionId: z2.string().optional(),
365
- placement: z2.enum(["header", "inline", "context", "assistant"]).optional(),
366
- targetId: z2.string().optional()
367
- }))
368
- });
369
- function createSurfacePlannerTools(config) {
370
- const { plan, constraints, onPatchProposal } = config;
371
- const resolvedConstraints = constraints ?? deriveConstraints(plan);
372
- const proposePatchTool = tool2({
373
- description: "Propose surface patches (layout changes, node insertions, etc.) for user approval. " + "Only use allowed ops, slots, and node kinds from the planner context.",
374
- inputSchema: ProposePatchInputSchema,
375
- execute: async (input) => {
376
- const ops = input.ops;
377
- try {
378
- validatePatchProposal(ops, resolvedConstraints);
379
- const proposal = buildSurfacePatchProposal(input.proposalId, ops);
380
- onPatchProposal?.(proposal);
381
- return {
382
- success: true,
383
- proposalId: proposal.proposalId,
384
- opsCount: proposal.ops.length,
385
- message: "Patch proposal validated; awaiting user approval"
386
- };
387
- } catch (err) {
388
- return {
389
- success: false,
390
- error: err instanceof Error ? err.message : String(err),
391
- proposalId: input.proposalId
392
- };
393
- }
394
- }
395
- });
396
- return {
397
- "propose-patch": proposePatchTool
398
- };
399
- }
400
- function buildPlannerPromptInput(plan) {
401
- const constraints = deriveConstraints(plan);
402
- return {
403
- bundleMeta: {
404
- key: plan.bundleKey,
405
- version: "0.0.0",
406
- title: plan.bundleKey
407
- },
408
- surfaceId: plan.surfaceId,
409
- allowedPatchOps: constraints.allowedOps,
410
- allowedSlots: [...constraints.allowedSlots],
411
- allowedNodeKinds: [...constraints.allowedNodeKinds],
412
- actions: plan.actions.map((a) => ({
413
- actionId: a.actionId,
414
- title: a.title
415
- })),
416
- preferences: {
417
- guidance: "hints",
418
- density: "standard",
419
- dataDepth: "detailed",
420
- control: "standard",
421
- media: "text",
422
- pace: "balanced",
423
- narrative: "top-down"
424
- }
425
- };
426
- }
427
-
428
- // src/core/thinking-levels.ts
429
- var THINKING_LEVEL_LABELS = {
430
- instant: "Instant",
431
- thinking: "Thinking",
432
- extra_thinking: "Extra Thinking",
433
- max: "Max"
434
- };
435
- var THINKING_LEVEL_DESCRIPTIONS = {
436
- instant: "Fast responses, minimal reasoning",
437
- thinking: "Standard reasoning depth",
438
- extra_thinking: "More thorough reasoning",
439
- max: "Maximum reasoning depth"
440
- };
441
- function getProviderOptions(level, providerName) {
442
- if (!level || level === "instant") {
443
- return {};
444
- }
445
- switch (providerName) {
446
- case "anthropic": {
447
- const budgetMap = {
448
- thinking: 8000,
449
- extra_thinking: 16000,
450
- max: 32000
451
- };
452
- return {
453
- anthropic: {
454
- thinking: { type: "enabled", budgetTokens: budgetMap[level] }
455
- }
456
- };
457
- }
458
- case "openai": {
459
- const effortMap = {
460
- thinking: "low",
461
- extra_thinking: "medium",
462
- max: "high"
463
- };
464
- return {
465
- openai: {
466
- reasoningEffort: effortMap[level]
467
- }
468
- };
469
- }
470
- case "ollama":
471
- case "mistral":
472
- case "gemini":
473
- return {};
474
- default:
475
- return {};
476
- }
477
- }
478
-
479
- // src/core/workflow-tools.ts
480
- import {
481
- validateExtension,
482
- WorkflowComposer
483
- } from "@contractspec/lib.workflow-composer";
484
- import { tool as tool3 } from "ai";
485
- import { z as z3 } from "zod";
486
- var StepTypeSchema = z3.enum(["human", "automation", "decision"]);
487
- var StepActionSchema = z3.object({
488
- operation: z3.object({
489
- name: z3.string(),
490
- version: z3.number()
491
- }).optional(),
492
- form: z3.object({
493
- key: z3.string(),
494
- version: z3.number()
495
- }).optional()
496
- }).optional();
497
- var StepSchema = z3.object({
498
- id: z3.string(),
499
- type: StepTypeSchema,
500
- label: z3.string(),
501
- description: z3.string().optional(),
502
- action: StepActionSchema
503
- });
504
- var StepInjectionSchema = z3.object({
505
- after: z3.string().optional(),
506
- before: z3.string().optional(),
507
- inject: StepSchema,
508
- transitionTo: z3.string().optional(),
509
- transitionFrom: z3.string().optional(),
510
- when: z3.string().optional()
511
- });
512
- var WorkflowExtensionInputSchema = z3.object({
513
- workflow: z3.string(),
514
- tenantId: z3.string().optional(),
515
- role: z3.string().optional(),
516
- priority: z3.number().optional(),
517
- customSteps: z3.array(StepInjectionSchema).optional(),
518
- hiddenSteps: z3.array(z3.string()).optional()
519
- });
520
- function createWorkflowTools(config) {
521
- const { baseWorkflows, composer } = config;
522
- const baseByKey = new Map(baseWorkflows.map((b) => [b.meta.key, b]));
523
- const createWorkflowExtensionTool = tool3({
524
- description: "Create or validate a workflow extension. Use when the user asks to add steps, modify a workflow, or create a tenant-specific extension. The extension targets an existing base workflow.",
525
- inputSchema: WorkflowExtensionInputSchema,
526
- execute: async (input) => {
527
- const extension = {
528
- workflow: input.workflow,
529
- tenantId: input.tenantId,
530
- role: input.role,
531
- priority: input.priority,
532
- customSteps: input.customSteps,
533
- hiddenSteps: input.hiddenSteps
534
- };
535
- const base = baseByKey.get(input.workflow);
536
- if (!base) {
537
- return {
538
- success: false,
539
- error: `Base workflow "${input.workflow}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
540
- extension
541
- };
542
- }
543
- try {
544
- validateExtension(extension, base);
545
- return {
546
- success: true,
547
- message: "Extension validated successfully",
548
- extension
549
- };
550
- } catch (err) {
551
- return {
552
- success: false,
553
- error: err instanceof Error ? err.message : String(err),
554
- extension
555
- };
556
- }
557
- }
558
- });
559
- const composeWorkflowInputSchema = z3.object({
560
- workflowKey: z3.string().describe("Base workflow meta.key"),
561
- tenantId: z3.string().optional(),
562
- role: z3.string().optional(),
563
- extensions: z3.array(WorkflowExtensionInputSchema).optional().describe("Extensions to register before composing")
564
- });
565
- const composeWorkflowTool = tool3({
566
- description: "Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",
567
- inputSchema: composeWorkflowInputSchema,
568
- execute: async (input) => {
569
- const base = baseByKey.get(input.workflowKey);
570
- if (!base) {
571
- return {
572
- success: false,
573
- error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`
574
- };
575
- }
576
- const comp = composer ?? new WorkflowComposer;
577
- if (input.extensions?.length) {
578
- for (const ext of input.extensions) {
579
- comp.register({
580
- workflow: ext.workflow,
581
- tenantId: ext.tenantId,
582
- role: ext.role,
583
- priority: ext.priority,
584
- customSteps: ext.customSteps,
585
- hiddenSteps: ext.hiddenSteps
586
- });
587
- }
588
- }
589
- try {
590
- const composed = comp.compose({
591
- base,
592
- tenantId: input.tenantId,
593
- role: input.role
594
- });
595
- return {
596
- success: true,
597
- workflow: composed,
598
- meta: composed.meta,
599
- stepIds: composed.definition.steps.map((s) => s.id)
600
- };
601
- } catch (err) {
602
- return {
603
- success: false,
604
- error: err instanceof Error ? err.message : String(err)
605
- };
606
- }
607
- }
608
- });
609
- const generateWorkflowSpecCodeInputSchema = z3.object({
610
- workflowKey: z3.string().describe("Workflow meta.key"),
611
- composedSteps: z3.array(z3.object({
612
- id: z3.string(),
613
- type: z3.enum(["human", "automation", "decision"]),
614
- label: z3.string(),
615
- description: z3.string().optional()
616
- })).optional().describe("Steps to include; if omitted, uses the base workflow")
617
- });
618
- const generateWorkflowSpecCodeTool = tool3({
619
- description: "Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",
620
- inputSchema: generateWorkflowSpecCodeInputSchema,
621
- execute: async (input) => {
622
- const base = baseByKey.get(input.workflowKey);
623
- if (!base) {
624
- return {
625
- success: false,
626
- error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
627
- code: null
628
- };
629
- }
630
- const steps = input.composedSteps ?? base.definition.steps;
631
- const specVarName = toPascalCase((base.meta.key.split(".").pop() ?? "Workflow") + "") + "Workflow";
632
- const stepsCode = steps.map((s) => ` {
633
- id: '${s.id}',
634
- type: '${s.type}',
635
- label: '${escapeString(s.label)}',${s.description ? `
636
- description: '${escapeString(s.description)}',` : ""}
1
+ import{createRequire as g}from"node:module";var CX=g(import.meta.url);function IX(X){return{async generate({prompt:Z,signal:$}){let j=await X.generate({prompt:Z,signal:$});return{text:j.text,toolCalls:j.toolCalls,toolResults:j.toolResults,usage:j.usage}}}}import{tool as p}from"ai";import{z as c}from"zod";function i(X){return c.object({}).passthrough()}function b(X,Z){let $={};for(let j of X){let H=Z?.[j.name],V=i(j.schema);$[j.name]=p({description:j.description??j.name,inputSchema:V,execute:async(G)=>{if(!H)return{status:"unimplemented",message:"Wire handler in host",toolName:j.name};try{let J=await Promise.resolve(H(G));return typeof J==="string"?J:J}catch(J){return{status:"error",error:J instanceof Error?J.message:String(J),toolName:j.name}}}})}return $}import{generateText as GX,streamText as JX}from"ai";import{compilePlannerPrompt as YX}from"@contractspec/lib.surface-runtime/runtime/planner-prompt";function y(X){let Z=[];if(!X.agentSpecs?.length&&!X.dataViewSpecs?.length&&!X.formSpecs?.length&&!X.presentationSpecs?.length&&!X.operationRefs?.length)return"";if(Z.push(`
2
+
3
+ ## Available resources`),X.agentSpecs?.length){Z.push(`
4
+ ### Agent tools`);for(let $ of X.agentSpecs){let j=$.tools?.map((H)=>H.name).join(", ")??"none";Z.push(`- **${$.key}**: tools: ${j}`)}}if(X.dataViewSpecs?.length){Z.push(`
5
+ ### Data views`);for(let $ of X.dataViewSpecs)Z.push(`- **${$.key}**: ${$.meta.title??$.key}`)}if(X.formSpecs?.length){Z.push(`
6
+ ### Forms`);for(let $ of X.formSpecs)Z.push(`- **${$.key}**: ${$.meta.title??$.key}`)}if(X.presentationSpecs?.length){Z.push(`
7
+ ### Presentations`);for(let $ of X.presentationSpecs)Z.push(`- **${$.key}**: ${$.meta.title??$.key} (targets: ${$.targets?.join(", ")??"react"})`)}if(X.operationRefs?.length){Z.push(`
8
+ ### Operations`);for(let $ of X.operationRefs)Z.push(`- **${$.key}@${$.version}**`)}return Z.push(`
9
+ Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`),Z.join(`
10
+ `)}function z(X){return`${X}_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}class P{conversations=new Map;async get(X){return this.conversations.get(X)??null}async create(X){let Z=new Date,$={...X,id:z("conv"),createdAt:Z,updatedAt:Z};return this.conversations.set($.id,$),$}async update(X,Z){let $=this.conversations.get(X);if(!$)return null;let j={...$,...Z,updatedAt:new Date};return this.conversations.set(X,j),j}async appendMessage(X,Z){let $=this.conversations.get(X);if(!$)throw Error(`Conversation ${X} not found`);let j=new Date,H={...Z,id:z("msg"),conversationId:X,createdAt:j,updatedAt:j};return $.messages.push(H),$.updatedAt=j,H}async updateMessage(X,Z,$){let j=this.conversations.get(X);if(!j)return null;let H=j.messages.findIndex((J)=>J.id===Z);if(H===-1)return null;let V=j.messages[H];if(!V)return null;let G={...V,...$,updatedAt:new Date};return j.messages[H]=G,j.updatedAt=new Date,G}async delete(X){return this.conversations.delete(X)}async list(X){let Z=Array.from(this.conversations.values());if(X?.status)Z=Z.filter((H)=>H.status===X.status);if(X?.projectId)Z=Z.filter((H)=>H.projectId===X.projectId);if(X?.tags&&X.tags.length>0){let H=new Set(X.tags);Z=Z.filter((V)=>V.tags&&V.tags.some((G)=>H.has(G)))}Z.sort((H,V)=>V.updatedAt.getTime()-H.updatedAt.getTime());let $=X?.offset??0,j=X?.limit??100;return Z.slice($,$+j)}async fork(X,Z){let $=this.conversations.get(X);if(!$)throw Error(`Conversation ${X} not found`);let j=$.messages;if(Z){let J=$.messages.findIndex((Q)=>Q.id===Z);if(J===-1)throw Error(`Message ${Z} not found`);j=$.messages.slice(0,J+1)}let H=new Date,V=j.map((J)=>({...J,id:z("msg"),conversationId:"",createdAt:new Date(J.createdAt),updatedAt:new Date(J.updatedAt)})),G={...$,id:z("conv"),title:$.title?`${$.title} (fork)`:void 0,forkedFromId:$.id,createdAt:H,updatedAt:H,messages:V};for(let J of G.messages)J.conversationId=G.id;return this.conversations.set(G.id,G),G}async truncateAfter(X,Z){let $=this.conversations.get(X);if(!$)return null;let j=$.messages.findIndex((H)=>H.id===Z);if(j===-1)return null;return $.messages=$.messages.slice(0,j+1),$.updatedAt=new Date,$}async search(X,Z=20){let $=X.toLowerCase(),j=[];for(let H of this.conversations.values()){if(H.title?.toLowerCase().includes($)){j.push(H);continue}if(H.messages.some((G)=>G.content.toLowerCase().includes($)))j.push(H);if(j.length>=Z)break}return j}clear(){this.conversations.clear()}}function lX(){return new P}import{buildSurfacePatchProposal as r}from"@contractspec/lib.surface-runtime/runtime/planner-tools";import{validatePatchProposal as a}from"@contractspec/lib.surface-runtime/spec/validate-surface-patch";import{tool as t}from"ai";import{z as F}from"zod";var n=["insert-node","replace-node","remove-node","move-node","resize-panel","set-layout","reveal-field","hide-field","promote-action","set-focus"],s=["entity-section","entity-card","data-view","assistant-panel","chat-thread","action-bar","timeline","table","rich-doc","form","chart","custom-widget"];function k(X){let Z=[];if(X.type==="slot")Z.push(X.slotId);if(X.type==="panel-group"||X.type==="stack")for(let $ of X.children)Z.push(...k($));if(X.type==="tabs")for(let $ of X.tabs)Z.push(...k($.child));if(X.type==="floating")Z.push(X.anchorSlotId),Z.push(...k(X.child));return Z}function C(X){let Z=k(X.layoutRoot),$=[...new Set(Z)];return{allowedOps:n,allowedSlots:$.length>0?$:["assistant","primary"],allowedNodeKinds:s}}var e=F.object({proposalId:F.string().describe("Unique proposal identifier"),ops:F.array(F.object({op:F.enum(["insert-node","replace-node","remove-node","move-node","resize-panel","set-layout","reveal-field","hide-field","promote-action","set-focus"]),slotId:F.string().optional(),nodeId:F.string().optional(),toSlotId:F.string().optional(),index:F.number().optional(),node:F.object({nodeId:F.string(),kind:F.string(),title:F.string().optional(),props:F.record(F.string(),F.unknown()).optional(),children:F.array(F.unknown()).optional()}).optional(),persistKey:F.string().optional(),sizes:F.array(F.number()).optional(),layoutId:F.string().optional(),fieldId:F.string().optional(),actionId:F.string().optional(),placement:F.enum(["header","inline","context","assistant"]).optional(),targetId:F.string().optional()}))});function T(X){let{plan:Z,constraints:$,onPatchProposal:j}=X,H=$??C(Z);return{"propose-patch":t({description:"Propose surface patches (layout changes, node insertions, etc.) for user approval. Only use allowed ops, slots, and node kinds from the planner context.",inputSchema:e,execute:async(G)=>{let J=G.ops;try{a(J,H);let Q=r(G.proposalId,J);return j?.(Q),{success:!0,proposalId:Q.proposalId,opsCount:Q.ops.length,message:"Patch proposal validated; awaiting user approval"}}catch(Q){return{success:!1,error:Q instanceof Error?Q.message:String(Q),proposalId:G.proposalId}}}})}}function I(X){let Z=C(X);return{bundleMeta:{key:X.bundleKey,version:"0.0.0",title:X.bundleKey},surfaceId:X.surfaceId,allowedPatchOps:Z.allowedOps,allowedSlots:[...Z.allowedSlots],allowedNodeKinds:[...Z.allowedNodeKinds],actions:X.actions.map(($)=>({actionId:$.actionId,title:$.title})),preferences:{guidance:"hints",density:"standard",dataDepth:"detailed",control:"standard",media:"text",pace:"balanced",narrative:"top-down"}}}var tX={instant:"Instant",thinking:"Thinking",extra_thinking:"Extra Thinking",max:"Max"},nX={instant:"Fast responses, minimal reasoning",thinking:"Standard reasoning depth",extra_thinking:"More thorough reasoning",max:"Maximum reasoning depth"};function A(X,Z){if(!X||X==="instant")return{};switch(Z){case"anthropic":return{anthropic:{thinking:{type:"enabled",budgetTokens:{thinking:8000,extra_thinking:16000,max:32000}[X]}}};case"openai":return{openai:{reasoningEffort:{thinking:"low",extra_thinking:"medium",max:"high"}[X]}};case"ollama":case"mistral":case"gemini":return{};default:return{}}}import{validateExtension as o,WorkflowComposer as XX}from"@contractspec/lib.workflow-composer";import{tool as x}from"ai";import{z as Y}from"zod";var ZX=Y.enum(["human","automation","decision"]),$X=Y.object({operation:Y.object({name:Y.string(),version:Y.number()}).optional(),form:Y.object({key:Y.string(),version:Y.number()}).optional()}).optional(),jX=Y.object({id:Y.string(),type:ZX,label:Y.string(),description:Y.string().optional(),action:$X}),HX=Y.object({after:Y.string().optional(),before:Y.string().optional(),inject:jX,transitionTo:Y.string().optional(),transitionFrom:Y.string().optional(),when:Y.string().optional()}),f=Y.object({workflow:Y.string(),tenantId:Y.string().optional(),role:Y.string().optional(),priority:Y.number().optional(),customSteps:Y.array(HX).optional(),hiddenSteps:Y.array(Y.string()).optional()});function v(X){let{baseWorkflows:Z,composer:$}=X,j=new Map(Z.map((D)=>[D.meta.key,D])),H=x({description:"Create or validate a workflow extension. Use when the user asks to add steps, modify a workflow, or create a tenant-specific extension. The extension targets an existing base workflow.",inputSchema:f,execute:async(D)=>{let B={workflow:D.workflow,tenantId:D.tenantId,role:D.role,priority:D.priority,customSteps:D.customSteps,hiddenSteps:D.hiddenSteps},_=j.get(D.workflow);if(!_)return{success:!1,error:`Base workflow "${D.workflow}" not found. Available: ${Array.from(j.keys()).join(", ")}`,extension:B};try{return o(B,_),{success:!0,message:"Extension validated successfully",extension:B}}catch(U){return{success:!1,error:U instanceof Error?U.message:String(U),extension:B}}}}),V=Y.object({workflowKey:Y.string().describe("Base workflow meta.key"),tenantId:Y.string().optional(),role:Y.string().optional(),extensions:Y.array(f).optional().describe("Extensions to register before composing")}),G=x({description:"Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",inputSchema:V,execute:async(D)=>{let B=j.get(D.workflowKey);if(!B)return{success:!1,error:`Base workflow "${D.workflowKey}" not found. Available: ${Array.from(j.keys()).join(", ")}`};let _=$??new XX;if(D.extensions?.length)for(let U of D.extensions)_.register({workflow:U.workflow,tenantId:U.tenantId,role:U.role,priority:U.priority,customSteps:U.customSteps,hiddenSteps:U.hiddenSteps});try{let U=_.compose({base:B,tenantId:D.tenantId,role:D.role});return{success:!0,workflow:U,meta:U.meta,stepIds:U.definition.steps.map((q)=>q.id)}}catch(U){return{success:!1,error:U instanceof Error?U.message:String(U)}}}}),J=Y.object({workflowKey:Y.string().describe("Workflow meta.key"),composedSteps:Y.array(Y.object({id:Y.string(),type:Y.enum(["human","automation","decision"]),label:Y.string(),description:Y.string().optional()})).optional().describe("Steps to include; if omitted, uses the base workflow")}),Q=x({description:"Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",inputSchema:J,execute:async(D)=>{let B=j.get(D.workflowKey);if(!B)return{success:!1,error:`Base workflow "${D.workflowKey}" not found. Available: ${Array.from(j.keys()).join(", ")}`,code:null};let _=D.composedSteps??B.definition.steps,U=VX((B.meta.key.split(".").pop()??"Workflow")+"")+"Workflow",q=_.map((N)=>` {
11
+ id: '${N.id}',
12
+ type: '${N.type}',
13
+ label: '${w(N.label)}',${N.description?`
14
+ description: '${w(N.description)}',`:""}
637
15
  }`).join(`,
638
- `);
639
- const meta = base.meta;
640
- const transitionsJson = JSON.stringify(base.definition.transitions, null, 6);
641
- const code = `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow/spec';
16
+ `),K=B.meta,E=JSON.stringify(B.definition.transitions,null,6);return{success:!0,code:`import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow/spec';
642
17
 
643
18
  /**
644
- * Workflow: ${base.meta.key}
19
+ * Workflow: ${B.meta.key}
645
20
  * Generated via AI chat workflow tools.
646
21
  */
647
- export const ${specVarName}: WorkflowSpec = {
22
+ export const ${U}: WorkflowSpec = {
648
23
  meta: {
649
- key: '${base.meta.key}',
650
- version: '${String(base.meta.version)}',
651
- title: '${escapeString(meta.title ?? base.meta.key)}',
652
- description: '${escapeString(meta.description ?? "")}',
24
+ key: '${B.meta.key}',
25
+ version: '${String(B.meta.version)}',
26
+ title: '${w(K.title??B.meta.key)}',
27
+ description: '${w(K.description??"")}',
653
28
  },
654
29
  definition: {
655
- entryStepId: '${base.definition.entryStepId ?? base.definition.steps[0]?.id ?? ""}',
30
+ entryStepId: '${B.definition.entryStepId??B.definition.steps[0]?.id??""}',
656
31
  steps: [
657
- ${stepsCode}
32
+ ${q}
658
33
  ],
659
- transitions: ${transitionsJson},
34
+ transitions: ${E},
660
35
  },
661
36
  };
662
- `;
663
- return {
664
- success: true,
665
- code,
666
- workflowKey: input.workflowKey
667
- };
668
- }
669
- });
670
- return {
671
- create_workflow_extension: createWorkflowExtensionTool,
672
- compose_workflow: composeWorkflowTool,
673
- generate_workflow_spec_code: generateWorkflowSpecCodeTool
674
- };
675
- }
676
- function toPascalCase(value) {
677
- return value.split(/[-_.]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
678
- }
679
- function escapeString(value) {
680
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
681
- }
682
-
683
- // src/core/chat-service.ts
684
- var DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
37
+ `,workflowKey:D.workflowKey}}});return{create_workflow_extension:H,compose_workflow:G,generate_workflow_spec_code:Q}}function VX(X){return X.split(/[-_.]/).filter(Boolean).map((Z)=>Z.charAt(0).toUpperCase()+Z.slice(1)).join("")}function w(X){return X.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}var QX=`You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
685
38
 
686
39
  Your capabilities:
687
40
  - Help users create, modify, and understand ContractSpec specifications
@@ -694,957 +47,64 @@ Guidelines:
694
47
  - Provide code examples when helpful
695
48
  - Reference relevant ContractSpec concepts and patterns
696
49
  - Ask clarifying questions when the user's intent is unclear
697
- - When suggesting code changes, explain the rationale`;
698
- var WORKFLOW_TOOLS_PROMPT = `
699
-
700
- Workflow creation: You can create and modify workflows. Use create_workflow_extension when the user asks to add steps, change a workflow, or create a tenant-specific extension. Use compose_workflow to apply extensions to a base workflow. Use generate_workflow_spec_code to output TypeScript for the user to save.`;
50
+ - When suggesting code changes, explain the rationale`,UX=`
701
51
 
702
- class ChatService {
703
- provider;
704
- context;
705
- store;
706
- systemPrompt;
707
- maxHistoryMessages;
708
- onUsage;
709
- tools;
710
- thinkingLevel;
711
- sendReasoning;
712
- sendSources;
713
- modelSelector;
714
- constructor(config) {
715
- this.provider = config.provider;
716
- this.context = config.context;
717
- this.store = config.store ?? new InMemoryConversationStore;
718
- this.systemPrompt = this.buildSystemPrompt(config);
719
- this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
720
- this.onUsage = config.onUsage;
721
- this.tools = this.mergeTools(config);
722
- this.thinkingLevel = config.thinkingLevel;
723
- this.modelSelector = config.modelSelector;
724
- this.sendReasoning = config.sendReasoning ?? (config.thinkingLevel != null && config.thinkingLevel !== "instant");
725
- this.sendSources = config.sendSources ?? false;
726
- }
727
- buildSystemPrompt(config) {
728
- let base = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
729
- if (config.workflowToolsConfig?.baseWorkflows?.length) {
730
- base += WORKFLOW_TOOLS_PROMPT;
731
- }
732
- const contractsPrompt = buildContractsContextPrompt(config.contractsContext ?? {});
733
- if (contractsPrompt) {
734
- base += contractsPrompt;
735
- }
736
- if (config.surfacePlanConfig?.plan) {
737
- const plannerInput = buildPlannerPromptInput(config.surfacePlanConfig.plan);
738
- base += `
52
+ Workflow creation: You can create and modify workflows. Use create_workflow_extension when the user asks to add steps, change a workflow, or create a tenant-specific extension. Use compose_workflow to apply extensions to a base workflow. Use generate_workflow_spec_code to output TypeScript for the user to save.`;class u{provider;context;store;systemPrompt;maxHistoryMessages;onUsage;tools;thinkingLevel;sendReasoning;sendSources;modelSelector;constructor(X){this.provider=X.provider,this.context=X.context,this.store=X.store??new P,this.systemPrompt=this.buildSystemPrompt(X),this.maxHistoryMessages=X.maxHistoryMessages??20,this.onUsage=X.onUsage,this.tools=this.mergeTools(X),this.thinkingLevel=X.thinkingLevel,this.modelSelector=X.modelSelector,this.sendReasoning=X.sendReasoning??(X.thinkingLevel!=null&&X.thinkingLevel!=="instant"),this.sendSources=X.sendSources??!1}buildSystemPrompt(X){let Z=X.systemPrompt??QX;if(X.workflowToolsConfig?.baseWorkflows?.length)Z+=UX;let $=y(X.contractsContext??{});if($)Z+=$;if(X.surfacePlanConfig?.plan){let j=I(X.surfacePlanConfig.plan);Z+=`
739
53
 
740
- ` + compilePlannerPrompt(plannerInput);
741
- }
742
- return base;
743
- }
744
- mergeTools(config) {
745
- let merged = config.tools ?? {};
746
- const wfConfig = config.workflowToolsConfig;
747
- if (wfConfig?.baseWorkflows?.length) {
748
- const workflowTools = createWorkflowTools({
749
- baseWorkflows: wfConfig.baseWorkflows,
750
- composer: wfConfig.composer
751
- });
752
- merged = { ...merged, ...workflowTools };
753
- }
754
- const contractsCtx = config.contractsContext;
755
- if (contractsCtx?.agentSpecs?.length) {
756
- const allTools = [];
757
- for (const agent of contractsCtx.agentSpecs) {
758
- if (agent.tools?.length)
759
- allTools.push(...agent.tools);
760
- }
761
- if (allTools.length > 0) {
762
- const agentTools = agentToolConfigsToToolSet(allTools);
763
- merged = { ...merged, ...agentTools };
764
- }
765
- }
766
- const surfaceConfig = config.surfacePlanConfig;
767
- if (surfaceConfig?.plan) {
768
- const plannerTools = createSurfacePlannerTools({
769
- plan: surfaceConfig.plan,
770
- onPatchProposal: surfaceConfig.onPatchProposal
771
- });
772
- merged = { ...merged, ...plannerTools };
773
- }
774
- if (config.mcpTools && Object.keys(config.mcpTools).length > 0) {
775
- merged = { ...merged, ...config.mcpTools };
776
- }
777
- return Object.keys(merged).length > 0 ? merged : undefined;
778
- }
779
- async resolveModel() {
780
- if (this.modelSelector) {
781
- const dimension = this.thinkingLevelToDimension(this.thinkingLevel);
782
- const { model, selection } = await this.modelSelector.selectAndCreate({
783
- taskDimension: dimension
784
- });
785
- return { model, providerName: selection.providerKey };
786
- }
787
- return {
788
- model: this.provider.getModel(),
789
- providerName: this.provider.name
790
- };
791
- }
792
- thinkingLevelToDimension(level) {
793
- if (!level || level === "instant")
794
- return "latency";
795
- return "reasoning";
796
- }
797
- async send(options) {
798
- let conversation;
799
- if (options.conversationId) {
800
- const existing = await this.store.get(options.conversationId);
801
- if (!existing) {
802
- throw new Error(`Conversation ${options.conversationId} not found`);
803
- }
804
- conversation = existing;
805
- } else {
806
- conversation = await this.store.create({
807
- status: "active",
808
- provider: this.provider.name,
809
- model: this.provider.model,
810
- messages: [],
811
- workspacePath: this.context?.workspacePath
812
- });
813
- }
814
- if (!options.skipUserAppend) {
815
- await this.store.appendMessage(conversation.id, {
816
- role: "user",
817
- content: options.content,
818
- status: "completed",
819
- attachments: options.attachments
820
- });
821
- }
822
- conversation = await this.store.get(conversation.id) ?? conversation;
823
- const messages = this.buildMessages(conversation, options);
824
- const { model, providerName } = await this.resolveModel();
825
- const providerOptions = getProviderOptions(this.thinkingLevel, providerName);
826
- try {
827
- const result = await generateText({
828
- model,
829
- messages,
830
- system: this.systemPrompt,
831
- tools: this.tools,
832
- providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : undefined
833
- });
834
- const assistantMessage = await this.store.appendMessage(conversation.id, {
835
- role: "assistant",
836
- content: result.text,
837
- status: "completed"
838
- });
839
- const updatedConversation = await this.store.get(conversation.id);
840
- if (!updatedConversation) {
841
- throw new Error("Conversation lost after update");
842
- }
843
- return {
844
- message: assistantMessage,
845
- conversation: updatedConversation
846
- };
847
- } catch (error) {
848
- await this.store.appendMessage(conversation.id, {
849
- role: "assistant",
850
- content: "",
851
- status: "error",
852
- error: {
853
- code: "generation_failed",
854
- message: error instanceof Error ? error.message : String(error)
855
- }
856
- });
857
- throw error;
858
- }
859
- }
860
- async stream(options) {
861
- let conversation;
862
- if (options.conversationId) {
863
- const existing = await this.store.get(options.conversationId);
864
- if (!existing) {
865
- throw new Error(`Conversation ${options.conversationId} not found`);
866
- }
867
- conversation = existing;
868
- } else {
869
- conversation = await this.store.create({
870
- status: "active",
871
- provider: this.provider.name,
872
- model: this.provider.model,
873
- messages: [],
874
- workspacePath: this.context?.workspacePath
875
- });
876
- }
877
- if (!options.skipUserAppend) {
878
- await this.store.appendMessage(conversation.id, {
879
- role: "user",
880
- content: options.content,
881
- status: "completed",
882
- attachments: options.attachments
883
- });
884
- }
885
- conversation = await this.store.get(conversation.id) ?? conversation;
886
- const assistantMessage = await this.store.appendMessage(conversation.id, {
887
- role: "assistant",
888
- content: "",
889
- status: "streaming"
890
- });
891
- const messages = this.buildMessages(conversation, options);
892
- const { model, providerName } = await this.resolveModel();
893
- const systemPrompt = this.systemPrompt;
894
- const tools = this.tools;
895
- const store = this.store;
896
- const onUsage = this.onUsage;
897
- const streamProviderOptions = getProviderOptions(this.thinkingLevel, providerName);
898
- async function* streamGenerator() {
899
- let fullContent = "";
900
- let fullReasoning = "";
901
- const toolCallsMap = new Map;
902
- const sources = [];
903
- try {
904
- const result = streamText({
905
- model,
906
- messages,
907
- system: systemPrompt,
908
- tools,
909
- providerOptions: Object.keys(streamProviderOptions).length > 0 ? streamProviderOptions : undefined
910
- });
911
- for await (const part of result.fullStream) {
912
- if (part.type === "text-delta") {
913
- const text = part.text ?? "";
914
- if (text) {
915
- fullContent += text;
916
- yield { type: "text", content: text };
917
- }
918
- } else if (part.type === "reasoning-delta") {
919
- const text = part.text ?? "";
920
- if (text) {
921
- fullReasoning += text;
922
- yield { type: "reasoning", content: text };
923
- }
924
- } else if (part.type === "source") {
925
- const src = part;
926
- const source = {
927
- id: src.id,
928
- title: src.title ?? "",
929
- url: src.url,
930
- type: "web"
931
- };
932
- sources.push(source);
933
- yield { type: "source", source };
934
- } else if (part.type === "tool-call") {
935
- const toolCall = {
936
- id: part.toolCallId,
937
- name: part.toolName,
938
- args: part.input ?? {},
939
- status: "running"
940
- };
941
- toolCallsMap.set(part.toolCallId, toolCall);
942
- yield { type: "tool_call", toolCall };
943
- } else if (part.type === "tool-result") {
944
- const tc = toolCallsMap.get(part.toolCallId);
945
- if (tc) {
946
- tc.result = part.output;
947
- tc.status = "completed";
948
- }
949
- yield {
950
- type: "tool_result",
951
- toolResult: {
952
- toolCallId: part.toolCallId,
953
- toolName: part.toolName,
954
- result: part.output
955
- }
956
- };
957
- } else if (part.type === "tool-error") {
958
- const tc = toolCallsMap.get(part.toolCallId);
959
- if (tc) {
960
- tc.status = "error";
961
- tc.error = part.error ?? "Tool execution failed";
962
- }
963
- } else if (part.type === "finish") {
964
- const usage = part.usage;
965
- const inputTokens = usage?.inputTokens ?? 0;
966
- const outputTokens = usage?.completionTokens ?? 0;
967
- await store.updateMessage(conversation.id, assistantMessage.id, {
968
- content: fullContent,
969
- status: "completed",
970
- reasoning: fullReasoning || undefined,
971
- sources: sources.length > 0 ? sources : undefined,
972
- toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined,
973
- usage: usage ? { inputTokens, outputTokens } : undefined
974
- });
975
- onUsage?.({ inputTokens, outputTokens });
976
- yield {
977
- type: "done",
978
- usage: usage ? { inputTokens, outputTokens } : undefined
979
- };
980
- return;
981
- }
982
- }
983
- await store.updateMessage(conversation.id, assistantMessage.id, {
984
- content: fullContent,
985
- status: "completed",
986
- reasoning: fullReasoning || undefined,
987
- sources: sources.length > 0 ? sources : undefined,
988
- toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined
989
- });
990
- yield { type: "done" };
991
- } catch (error) {
992
- await store.updateMessage(conversation.id, assistantMessage.id, {
993
- content: fullContent,
994
- status: "error",
995
- error: {
996
- code: "stream_failed",
997
- message: error instanceof Error ? error.message : String(error)
998
- }
999
- });
1000
- yield {
1001
- type: "error",
1002
- error: {
1003
- code: "stream_failed",
1004
- message: error instanceof Error ? error.message : String(error)
1005
- }
1006
- };
1007
- }
1008
- }
1009
- return {
1010
- conversationId: conversation.id,
1011
- messageId: assistantMessage.id,
1012
- stream: streamGenerator()
1013
- };
1014
- }
1015
- async getConversation(conversationId) {
1016
- return this.store.get(conversationId);
1017
- }
1018
- async listConversations(options) {
1019
- return this.store.list({
1020
- status: "active",
1021
- ...options
1022
- });
1023
- }
1024
- async updateConversation(conversationId, updates) {
1025
- return this.store.update(conversationId, updates);
1026
- }
1027
- async forkConversation(conversationId, upToMessageId) {
1028
- return this.store.fork(conversationId, upToMessageId);
1029
- }
1030
- async updateMessage(conversationId, messageId, updates) {
1031
- return this.store.updateMessage(conversationId, messageId, updates);
1032
- }
1033
- async truncateAfter(conversationId, messageId) {
1034
- return this.store.truncateAfter(conversationId, messageId);
1035
- }
1036
- async deleteConversation(conversationId) {
1037
- return this.store.delete(conversationId);
1038
- }
1039
- buildMessages(conversation, _options) {
1040
- const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
1041
- const messages = [];
1042
- for (let i = historyStart;i < conversation.messages.length; i++) {
1043
- const msg = conversation.messages[i];
1044
- if (!msg)
1045
- continue;
1046
- if (msg.role === "user") {
1047
- let content = msg.content;
1048
- if (msg.attachments?.length) {
1049
- const attachmentInfo = msg.attachments.map((a) => {
1050
- if (a.type === "file" || a.type === "code") {
1051
- return `
54
+ `+YX(j)}return Z}mergeTools(X){let Z=X.tools??{},$=X.workflowToolsConfig;if($?.baseWorkflows?.length){let V=v({baseWorkflows:$.baseWorkflows,composer:$.composer});Z={...Z,...V}}let j=X.contractsContext;if(j?.agentSpecs?.length){let V=[];for(let G of j.agentSpecs)if(G.tools?.length)V.push(...G.tools);if(V.length>0){let G=b(V);Z={...Z,...G}}}let H=X.surfacePlanConfig;if(H?.plan){let V=T({plan:H.plan,onPatchProposal:H.onPatchProposal});Z={...Z,...V}}if(X.mcpTools&&Object.keys(X.mcpTools).length>0)Z={...Z,...X.mcpTools};return Object.keys(Z).length>0?Z:void 0}async resolveModel(){if(this.modelSelector){let X=this.thinkingLevelToDimension(this.thinkingLevel),{model:Z,selection:$}=await this.modelSelector.selectAndCreate({taskDimension:X});return{model:Z,providerName:$.providerKey}}return{model:this.provider.getModel(),providerName:this.provider.name}}thinkingLevelToDimension(X){if(!X||X==="instant")return"latency";return"reasoning"}async send(X){let Z;if(X.conversationId){let G=await this.store.get(X.conversationId);if(!G)throw Error(`Conversation ${X.conversationId} not found`);Z=G}else Z=await this.store.create({status:"active",provider:this.provider.name,model:this.provider.model,messages:[],workspacePath:this.context?.workspacePath});if(!X.skipUserAppend)await this.store.appendMessage(Z.id,{role:"user",content:X.content,status:"completed",attachments:X.attachments});Z=await this.store.get(Z.id)??Z;let $=this.buildMessages(Z,X),{model:j,providerName:H}=await this.resolveModel(),V=A(this.thinkingLevel,H);try{let G=await GX({model:j,messages:$,system:this.systemPrompt,tools:this.tools,providerOptions:Object.keys(V).length>0?V:void 0}),J=await this.store.appendMessage(Z.id,{role:"assistant",content:G.text,status:"completed"}),Q=await this.store.get(Z.id);if(!Q)throw Error("Conversation lost after update");return{message:J,conversation:Q}}catch(G){throw await this.store.appendMessage(Z.id,{role:"assistant",content:"",status:"error",error:{code:"generation_failed",message:G instanceof Error?G.message:String(G)}}),G}}async stream(X){let Z;if(X.conversationId){let U=await this.store.get(X.conversationId);if(!U)throw Error(`Conversation ${X.conversationId} not found`);Z=U}else Z=await this.store.create({status:"active",provider:this.provider.name,model:this.provider.model,messages:[],workspacePath:this.context?.workspacePath});if(!X.skipUserAppend)await this.store.appendMessage(Z.id,{role:"user",content:X.content,status:"completed",attachments:X.attachments});Z=await this.store.get(Z.id)??Z;let $=await this.store.appendMessage(Z.id,{role:"assistant",content:"",status:"streaming"}),j=this.buildMessages(Z,X),{model:H,providerName:V}=await this.resolveModel(),G=this.systemPrompt,J=this.tools,Q=this.store,D=this.onUsage,B=A(this.thinkingLevel,V);async function*_(){let U="",q="",K=new Map,E=[];try{let L=JX({model:H,messages:j,system:G,tools:J,providerOptions:Object.keys(B).length>0?B:void 0});for await(let N of L.fullStream)if(N.type==="text-delta"){let W=N.text??"";if(W)U+=W,yield{type:"text",content:W}}else if(N.type==="reasoning-delta"){let W=N.text??"";if(W)q+=W,yield{type:"reasoning",content:W}}else if(N.type==="source"){let W=N,O={id:W.id,title:W.title??"",url:W.url,type:"web"};E.push(O),yield{type:"source",source:O}}else if(N.type==="tool-call"){let W={id:N.toolCallId,name:N.toolName,args:N.input??{},status:"running"};K.set(N.toolCallId,W),yield{type:"tool_call",toolCall:W}}else if(N.type==="tool-result"){let W=K.get(N.toolCallId);if(W)W.result=N.output,W.status="completed";yield{type:"tool_result",toolResult:{toolCallId:N.toolCallId,toolName:N.toolName,result:N.output}}}else if(N.type==="tool-error"){let W=K.get(N.toolCallId);if(W)W.status="error",W.error=N.error??"Tool execution failed"}else if(N.type==="finish"){let W=N.usage,O=W?.inputTokens??0,S=W?.completionTokens??0;await Q.updateMessage(Z.id,$.id,{content:U,status:"completed",reasoning:q||void 0,sources:E.length>0?E:void 0,toolCalls:K.size>0?Array.from(K.values()):void 0,usage:W?{inputTokens:O,outputTokens:S}:void 0}),D?.({inputTokens:O,outputTokens:S}),yield{type:"done",usage:W?{inputTokens:O,outputTokens:S}:void 0};return}await Q.updateMessage(Z.id,$.id,{content:U,status:"completed",reasoning:q||void 0,sources:E.length>0?E:void 0,toolCalls:K.size>0?Array.from(K.values()):void 0}),yield{type:"done"}}catch(L){await Q.updateMessage(Z.id,$.id,{content:U,status:"error",error:{code:"stream_failed",message:L instanceof Error?L.message:String(L)}}),yield{type:"error",error:{code:"stream_failed",message:L instanceof Error?L.message:String(L)}}}}return{conversationId:Z.id,messageId:$.id,stream:_()}}async getConversation(X){return this.store.get(X)}async listConversations(X){return this.store.list({status:"active",...X})}async updateConversation(X,Z){return this.store.update(X,Z)}async forkConversation(X,Z){return this.store.fork(X,Z)}async updateMessage(X,Z,$){return this.store.updateMessage(X,Z,$)}async truncateAfter(X,Z){return this.store.truncateAfter(X,Z)}async deleteConversation(X){return this.store.delete(X)}buildMessages(X,Z){let $=Math.max(0,X.messages.length-this.maxHistoryMessages),j=[];for(let H=$;H<X.messages.length;H++){let V=X.messages[H];if(!V)continue;if(V.role==="user"){let G=V.content;if(V.attachments?.length){let J=V.attachments.map((Q)=>{if(Q.type==="file"||Q.type==="code")return`
1052
55
 
1053
- ### ${a.name}
56
+ ### ${Q.name}
1054
57
  \`\`\`
1055
- ${a.content ?? ""}
1056
- \`\`\``;
1057
- }
1058
- return `
58
+ ${Q.content??""}
59
+ \`\`\``;return`
1059
60
 
1060
- [Attachment: ${a.name}]`;
1061
- }).join("");
1062
- content += attachmentInfo;
1063
- }
1064
- messages.push({ role: "user", content });
1065
- } else if (msg.role === "assistant") {
1066
- if (msg.toolCalls?.length) {
1067
- messages.push({
1068
- role: "assistant",
1069
- content: msg.content || "",
1070
- toolCalls: msg.toolCalls.map((tc) => ({
1071
- type: "tool-call",
1072
- toolCallId: tc.id,
1073
- toolName: tc.name,
1074
- args: tc.args
1075
- }))
1076
- });
1077
- messages.push({
1078
- role: "tool",
1079
- content: msg.toolCalls.map((tc) => ({
1080
- type: "tool-result",
1081
- toolCallId: tc.id,
1082
- toolName: tc.name,
1083
- output: tc.result
1084
- }))
1085
- });
1086
- } else {
1087
- messages.push({ role: "assistant", content: msg.content });
1088
- }
1089
- }
1090
- }
1091
- return messages;
1092
- }
1093
- }
1094
- function createChatService(config) {
1095
- return new ChatService(config);
1096
- }
1097
- // src/core/create-chat-route.ts
1098
- import {
1099
- convertToModelMessages,
1100
- streamText as streamText2
1101
- } from "ai";
1102
- import { z as z4 } from "zod";
1103
- var CHAT_ROUTE_MAX_DURATION = 30;
1104
- var REQUEST_BODY_SCHEMA = z4.object({
1105
- messages: z4.array(z4.unknown()).min(1, "messages array required"),
1106
- thinkingLevel: z4.enum(["instant", "thinking", "extra_thinking", "max"]).optional(),
1107
- model: z4.string().optional()
1108
- });
1109
- var DEFAULT_SYSTEM_PROMPT2 = `You are a helpful AI assistant.`;
1110
- function defaultSendReasoning(thinkingLevel) {
1111
- return Boolean(thinkingLevel && thinkingLevel !== "instant");
1112
- }
1113
- function defaultSendSources() {
1114
- return true;
1115
- }
1116
- function createChatRoute(options) {
1117
- const {
1118
- provider,
1119
- systemPrompt = DEFAULT_SYSTEM_PROMPT2,
1120
- tools,
1121
- thinkingLevel: defaultThinkingLevel,
1122
- sendSources = defaultSendSources(),
1123
- sendReasoning = defaultSendReasoning(defaultThinkingLevel),
1124
- getModel
1125
- } = options;
1126
- return async (req) => {
1127
- if (req.method !== "POST") {
1128
- return new Response("Method not allowed", { status: 405 });
1129
- }
1130
- let rawBody;
1131
- try {
1132
- rawBody = await req.json();
1133
- } catch {
1134
- return new Response(JSON.stringify({ error: "Invalid JSON body" }), {
1135
- status: 400,
1136
- headers: { "Content-Type": "application/json" }
1137
- });
1138
- }
1139
- const parsed = REQUEST_BODY_SCHEMA.safeParse(rawBody);
1140
- if (!parsed.success) {
1141
- const message = parsed.error.issues[0]?.message ?? "Invalid request body";
1142
- return new Response(JSON.stringify({ error: message }), {
1143
- status: 400,
1144
- headers: { "Content-Type": "application/json" }
1145
- });
1146
- }
1147
- const body = parsed.data;
1148
- const thinkingLevel = body.thinkingLevel ?? defaultThinkingLevel;
1149
- const providerOptions = getProviderOptions(thinkingLevel, provider.name);
1150
- const model = getModel ? getModel(body) : provider.getModel();
1151
- try {
1152
- const result = streamText2({
1153
- model,
1154
- messages: await convertToModelMessages(body.messages),
1155
- system: systemPrompt,
1156
- tools,
1157
- providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : undefined
1158
- });
1159
- return result.toUIMessageStreamResponse({
1160
- sendSources,
1161
- sendReasoning
1162
- });
1163
- } catch (error) {
1164
- const message = error instanceof Error ? error.message : "An error occurred";
1165
- return new Response(JSON.stringify({ error: message }), {
1166
- status: 500,
1167
- headers: { "Content-Type": "application/json" }
1168
- });
1169
- }
1170
- };
1171
- }
1172
- // src/core/create-completion-route.ts
1173
- import { streamText as streamText3 } from "ai";
1174
- function createCompletionRoute(options) {
1175
- const { provider, systemPrompt } = options;
1176
- return async (req) => {
1177
- if (req.method !== "POST") {
1178
- return new Response("Method not allowed", { status: 405 });
1179
- }
1180
- let body;
1181
- try {
1182
- body = await req.json();
1183
- } catch {
1184
- return new Response("Invalid JSON body", { status: 400 });
1185
- }
1186
- const prompt = body.prompt ?? "";
1187
- if (!prompt || typeof prompt !== "string") {
1188
- return new Response("prompt string required", { status: 400 });
1189
- }
1190
- const model = provider.getModel();
1191
- const result = streamText3({
1192
- model,
1193
- prompt,
1194
- system: systemPrompt
1195
- });
1196
- return result.toTextStreamResponse();
1197
- };
1198
- }
1199
- // src/core/export-formatters.ts
1200
- function formatTimestamp(date) {
1201
- return date.toLocaleTimeString([], {
1202
- hour: "2-digit",
1203
- minute: "2-digit"
1204
- });
1205
- }
1206
- function toIsoString(date) {
1207
- return date.toISOString();
1208
- }
1209
- function messageToJsonSerializable(msg) {
1210
- return {
1211
- id: msg.id,
1212
- conversationId: msg.conversationId,
1213
- role: msg.role,
1214
- content: msg.content,
1215
- status: msg.status,
1216
- createdAt: toIsoString(msg.createdAt),
1217
- updatedAt: toIsoString(msg.updatedAt),
1218
- ...msg.attachments && { attachments: msg.attachments },
1219
- ...msg.codeBlocks && { codeBlocks: msg.codeBlocks },
1220
- ...msg.toolCalls && { toolCalls: msg.toolCalls },
1221
- ...msg.sources && { sources: msg.sources },
1222
- ...msg.reasoning && { reasoning: msg.reasoning },
1223
- ...msg.usage && { usage: msg.usage },
1224
- ...msg.error && { error: msg.error },
1225
- ...msg.metadata && { metadata: msg.metadata }
1226
- };
1227
- }
1228
- function formatSourcesMarkdown(sources) {
1229
- if (sources.length === 0)
1230
- return "";
1231
- return `
61
+ [Attachment: ${Q.name}]`}).join("");G+=J}j.push({role:"user",content:G})}else if(V.role==="assistant")if(V.toolCalls?.length)j.push({role:"assistant",content:V.content||"",toolCalls:V.toolCalls.map((G)=>({type:"tool-call",toolCallId:G.id,toolName:G.name,args:G.args}))}),j.push({role:"tool",content:V.toolCalls.map((G)=>({type:"tool-result",toolCallId:G.id,toolName:G.name,output:G.result}))});else j.push({role:"assistant",content:V.content})}return j}}function UZ(X){return new u(X)}import{convertToModelMessages as DX,streamText as NX}from"ai";import{z as R}from"zod";var FZ=30,WX=R.object({messages:R.array(R.unknown()).min(1,"messages array required"),thinkingLevel:R.enum(["instant","thinking","extra_thinking","max"]).optional(),model:R.string().optional()}),BX="You are a helpful AI assistant.";function FX(X){return Boolean(X&&X!=="instant")}function _X(){return!0}function _Z(X){let{provider:Z,systemPrompt:$=BX,tools:j,thinkingLevel:H,sendSources:V=_X(),sendReasoning:G=FX(H),getModel:J}=X;return async(Q)=>{if(Q.method!=="POST")return new Response("Method not allowed",{status:405});let D;try{D=await Q.json()}catch{return new Response(JSON.stringify({error:"Invalid JSON body"}),{status:400,headers:{"Content-Type":"application/json"}})}let B=WX.safeParse(D);if(!B.success){let E=B.error.issues[0]?.message??"Invalid request body";return new Response(JSON.stringify({error:E}),{status:400,headers:{"Content-Type":"application/json"}})}let _=B.data,U=_.thinkingLevel??H,q=A(U,Z.name),K=J?J(_):Z.getModel();try{return NX({model:K,messages:await DX(_.messages),system:$,tools:j,providerOptions:Object.keys(q).length>0?q:void 0}).toUIMessageStreamResponse({sendSources:V,sendReasoning:G})}catch(E){let L=E instanceof Error?E.message:"An error occurred";return new Response(JSON.stringify({error:L}),{status:500,headers:{"Content-Type":"application/json"}})}}}import{streamText as EX}from"ai";function LZ(X){let{provider:Z,systemPrompt:$}=X;return async(j)=>{if(j.method!=="POST")return new Response("Method not allowed",{status:405});let H;try{H=await j.json()}catch{return new Response("Invalid JSON body",{status:400})}let V=H.prompt??"";if(!V||typeof V!=="string")return new Response("prompt string required",{status:400});let G=Z.getModel();return EX({model:G,prompt:V,system:$}).toTextStreamResponse()}}function d(X){return X.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function h(X){return X.toISOString()}function KX(X){return{id:X.id,conversationId:X.conversationId,role:X.role,content:X.content,status:X.status,createdAt:h(X.createdAt),updatedAt:h(X.updatedAt),...X.attachments&&{attachments:X.attachments},...X.codeBlocks&&{codeBlocks:X.codeBlocks},...X.toolCalls&&{toolCalls:X.toolCalls},...X.sources&&{sources:X.sources},...X.reasoning&&{reasoning:X.reasoning},...X.usage&&{usage:X.usage},...X.error&&{error:X.error},...X.metadata&&{metadata:X.metadata}}}function LX(X){if(X.length===0)return"";return`
1232
62
 
1233
63
  **Sources:**
1234
- ` + sources.map((s) => `- [${s.title}](${s.url ?? "#"})`).join(`
1235
- `);
1236
- }
1237
- function formatSourcesTxt(sources) {
1238
- if (sources.length === 0)
1239
- return "";
1240
- return `
64
+ `+X.map((Z)=>`- [${Z.title}](${Z.url??"#"})`).join(`
65
+ `)}function qX(X){if(X.length===0)return"";return`
1241
66
 
1242
67
  Sources:
1243
- ` + sources.map((s) => `- ${s.title}${s.url ? ` - ${s.url}` : ""}`).join(`
1244
- `);
1245
- }
1246
- function formatToolCallsMarkdown(toolCalls) {
1247
- if (toolCalls.length === 0)
1248
- return "";
1249
- return `
68
+ `+X.map((Z)=>`- ${Z.title}${Z.url?` - ${Z.url}`:""}`).join(`
69
+ `)}function OX(X){if(X.length===0)return"";return`
1250
70
 
1251
71
  **Tool calls:**
1252
- ` + toolCalls.map((tc) => `**${tc.name}** (${tc.status})
72
+ `+X.map((Z)=>`**${Z.name}** (${Z.status})
1253
73
  \`\`\`json
1254
- ${JSON.stringify(tc.args, null, 2)}
1255
- \`\`\`` + (tc.result !== undefined ? `
74
+ ${JSON.stringify(Z.args,null,2)}
75
+ \`\`\``+(Z.result!==void 0?`
1256
76
  Output:
1257
77
  \`\`\`json
1258
- ${typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)}
1259
- \`\`\`` : "") + (tc.error ? `
1260
- Error: ${tc.error}` : "")).join(`
78
+ ${typeof Z.result==="object"?JSON.stringify(Z.result,null,2):String(Z.result)}
79
+ \`\`\``:"")+(Z.error?`
80
+ Error: ${Z.error}`:"")).join(`
1261
81
 
1262
- `);
1263
- }
1264
- function formatToolCallsTxt(toolCalls) {
1265
- if (toolCalls.length === 0)
1266
- return "";
1267
- return `
82
+ `)}function AX(X){if(X.length===0)return"";return`
1268
83
 
1269
84
  Tool calls:
1270
- ` + toolCalls.map((tc) => `- ${tc.name} (${tc.status}): ${JSON.stringify(tc.args)}` + (tc.result !== undefined ? ` -> ${typeof tc.result === "object" ? JSON.stringify(tc.result) : String(tc.result)}` : "") + (tc.error ? ` [Error: ${tc.error}]` : "")).join(`
1271
- `);
1272
- }
1273
- function formatUsage(usage) {
1274
- const total = usage.inputTokens + usage.outputTokens;
1275
- return ` (${total} tokens)`;
1276
- }
1277
- function formatMessagesAsMarkdown(messages) {
1278
- const parts = [];
1279
- for (const msg of messages) {
1280
- const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
1281
- const header = `## ${roleLabel}`;
1282
- const timestamp = `*${formatTimestamp(msg.createdAt)}*`;
1283
- const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
1284
- const meta = `${timestamp}${usageSuffix}
85
+ `+X.map((Z)=>`- ${Z.name} (${Z.status}): ${JSON.stringify(Z.args)}`+(Z.result!==void 0?` -> ${typeof Z.result==="object"?JSON.stringify(Z.result):String(Z.result)}`:"")+(Z.error?` [Error: ${Z.error}]`:"")).join(`
86
+ `)}function m(X){return` (${X.inputTokens+X.outputTokens} tokens)`}function RX(X){let Z=[];for(let $ of X){let H=`## ${$.role==="user"?"User":$.role==="assistant"?"Assistant":"System"}`,V=`*${d($.createdAt)}*`,G=$.usage?m($.usage):"",J=`${V}${G}
1285
87
 
1286
- `;
1287
- let body = msg.content;
1288
- if (msg.error) {
1289
- body += `
88
+ `,Q=$.content;if($.error)Q+=`
1290
89
 
1291
- **Error:** ${msg.error.code} - ${msg.error.message}`;
1292
- }
1293
- if (msg.reasoning) {
1294
- body += `
90
+ **Error:** ${$.error.code} - ${$.error.message}`;if($.reasoning)Q+=`
1295
91
 
1296
92
  > **Reasoning:**
1297
- > ${msg.reasoning.replace(/\n/g, `
1298
- > `)}`;
1299
- }
1300
- body += formatSourcesMarkdown(msg.sources ?? []);
1301
- body += formatToolCallsMarkdown(msg.toolCalls ?? []);
1302
- parts.push(`${header}
93
+ > ${$.reasoning.replace(/\n/g,`
94
+ > `)}`;Q+=LX($.sources??[]),Q+=OX($.toolCalls??[]),Z.push(`${H}
1303
95
 
1304
- ${meta}${body}`);
1305
- }
1306
- return parts.join(`
96
+ ${J}${Q}`)}return Z.join(`
1307
97
 
1308
98
  ---
1309
99
 
1310
- `);
1311
- }
1312
- function formatMessagesAsTxt(messages) {
1313
- const parts = [];
1314
- for (const msg of messages) {
1315
- const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
1316
- const timestamp = `(${formatTimestamp(msg.createdAt)})`;
1317
- const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
1318
- const header = `[${roleLabel}] ${timestamp}${usageSuffix}
100
+ `)}function zX(X){let Z=[];for(let $ of X){let j=$.role==="user"?"User":$.role==="assistant"?"Assistant":"System",H=`(${d($.createdAt)})`,V=$.usage?m($.usage):"",G=`[${j}] ${H}${V}
1319
101
 
1320
- `;
1321
- let body = msg.content;
1322
- if (msg.error) {
1323
- body += `
102
+ `,J=$.content;if($.error)J+=`
1324
103
 
1325
- Error: ${msg.error.code} - ${msg.error.message}`;
1326
- }
1327
- if (msg.reasoning) {
1328
- body += `
104
+ Error: ${$.error.code} - ${$.error.message}`;if($.reasoning)J+=`
1329
105
 
1330
- Reasoning: ${msg.reasoning}`;
1331
- }
1332
- body += formatSourcesTxt(msg.sources ?? []);
1333
- body += formatToolCallsTxt(msg.toolCalls ?? []);
1334
- parts.push(`${header}${body}`);
1335
- }
1336
- return parts.join(`
106
+ Reasoning: ${$.reasoning}`;J+=qX($.sources??[]),J+=AX($.toolCalls??[]),Z.push(`${G}${J}`)}return Z.join(`
1337
107
 
1338
108
  ---
1339
109
 
1340
- `);
1341
- }
1342
- function formatMessagesAsJson(messages, conversation) {
1343
- const payload = {
1344
- messages: messages.map(messageToJsonSerializable)
1345
- };
1346
- if (conversation) {
1347
- payload.conversation = {
1348
- id: conversation.id,
1349
- title: conversation.title,
1350
- status: conversation.status,
1351
- createdAt: toIsoString(conversation.createdAt),
1352
- updatedAt: toIsoString(conversation.updatedAt),
1353
- provider: conversation.provider,
1354
- model: conversation.model,
1355
- workspacePath: conversation.workspacePath,
1356
- contextFiles: conversation.contextFiles,
1357
- summary: conversation.summary,
1358
- metadata: conversation.metadata
1359
- };
1360
- }
1361
- return JSON.stringify(payload, null, 2);
1362
- }
1363
- function getExportFilename(format, conversation) {
1364
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
1365
- const base = conversation?.title ? conversation.title.replace(/[^a-zA-Z0-9-_]/g, "_").slice(0, 40) : "chat-export";
1366
- const ext = format === "markdown" ? "md" : format === "txt" ? "txt" : "json";
1367
- return `${base}-${timestamp}.${ext}`;
1368
- }
1369
- var MIME_TYPES = {
1370
- markdown: "text/markdown",
1371
- txt: "text/plain",
1372
- json: "application/json"
1373
- };
1374
- function downloadAsFile(content, filename, mimeType) {
1375
- const blob = new Blob([content], { type: mimeType });
1376
- const url = URL.createObjectURL(blob);
1377
- const a = document.createElement("a");
1378
- a.href = url;
1379
- a.download = filename;
1380
- document.body.appendChild(a);
1381
- a.click();
1382
- document.body.removeChild(a);
1383
- URL.revokeObjectURL(url);
1384
- }
1385
- function exportToFile(messages, format, conversation) {
1386
- let content;
1387
- if (format === "markdown") {
1388
- content = formatMessagesAsMarkdown(messages);
1389
- } else if (format === "txt") {
1390
- content = formatMessagesAsTxt(messages);
1391
- } else {
1392
- content = formatMessagesAsJson(messages, conversation);
1393
- }
1394
- const filename = getExportFilename(format, conversation);
1395
- const mimeType = MIME_TYPES[format];
1396
- downloadAsFile(content, filename, mimeType);
1397
- }
1398
- // src/core/local-storage-conversation-store.ts
1399
- var DEFAULT_KEY = "contractspec:ai-chat:conversations";
1400
- function generateId2(prefix) {
1401
- return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
1402
- }
1403
- function toSerializable(conv) {
1404
- return {
1405
- ...conv,
1406
- createdAt: conv.createdAt.toISOString(),
1407
- updatedAt: conv.updatedAt.toISOString(),
1408
- messages: conv.messages.map((m) => ({
1409
- ...m,
1410
- createdAt: m.createdAt.toISOString(),
1411
- updatedAt: m.updatedAt.toISOString()
1412
- }))
1413
- };
1414
- }
1415
- function fromSerializable(raw) {
1416
- const messages = raw.messages?.map((m) => ({
1417
- ...m,
1418
- createdAt: new Date(m.createdAt),
1419
- updatedAt: new Date(m.updatedAt)
1420
- })) ?? [];
1421
- return {
1422
- ...raw,
1423
- createdAt: new Date(raw.createdAt),
1424
- updatedAt: new Date(raw.updatedAt),
1425
- messages
1426
- };
1427
- }
1428
- function loadAll(key) {
1429
- if (typeof window === "undefined")
1430
- return new Map;
1431
- try {
1432
- const raw = window.localStorage.getItem(key);
1433
- if (!raw)
1434
- return new Map;
1435
- const arr = JSON.parse(raw);
1436
- const map = new Map;
1437
- for (const item of arr) {
1438
- const conv = fromSerializable(item);
1439
- map.set(conv.id, conv);
1440
- }
1441
- return map;
1442
- } catch {
1443
- return new Map;
1444
- }
1445
- }
1446
- function saveAll(key, map) {
1447
- if (typeof window === "undefined")
1448
- return;
1449
- try {
1450
- const arr = Array.from(map.values()).map(toSerializable);
1451
- window.localStorage.setItem(key, JSON.stringify(arr));
1452
- } catch {}
1453
- }
1454
-
1455
- class LocalStorageConversationStore {
1456
- key;
1457
- cache = null;
1458
- constructor(storageKey = DEFAULT_KEY) {
1459
- this.key = storageKey;
1460
- }
1461
- getMap() {
1462
- if (!this.cache) {
1463
- this.cache = loadAll(this.key);
1464
- }
1465
- return this.cache;
1466
- }
1467
- persist() {
1468
- saveAll(this.key, this.getMap());
1469
- }
1470
- async get(conversationId) {
1471
- return this.getMap().get(conversationId) ?? null;
1472
- }
1473
- async create(conversation) {
1474
- const now = new Date;
1475
- const full = {
1476
- ...conversation,
1477
- id: generateId2("conv"),
1478
- createdAt: now,
1479
- updatedAt: now
1480
- };
1481
- this.getMap().set(full.id, full);
1482
- this.persist();
1483
- return full;
1484
- }
1485
- async update(conversationId, updates) {
1486
- const conv = this.getMap().get(conversationId);
1487
- if (!conv)
1488
- return null;
1489
- const updated = {
1490
- ...conv,
1491
- ...updates,
1492
- updatedAt: new Date
1493
- };
1494
- this.getMap().set(conversationId, updated);
1495
- this.persist();
1496
- return updated;
1497
- }
1498
- async appendMessage(conversationId, message) {
1499
- const conv = this.getMap().get(conversationId);
1500
- if (!conv)
1501
- throw new Error(`Conversation ${conversationId} not found`);
1502
- const now = new Date;
1503
- const fullMessage = {
1504
- ...message,
1505
- id: generateId2("msg"),
1506
- conversationId,
1507
- createdAt: now,
1508
- updatedAt: now
1509
- };
1510
- conv.messages.push(fullMessage);
1511
- conv.updatedAt = now;
1512
- this.persist();
1513
- return fullMessage;
1514
- }
1515
- async updateMessage(conversationId, messageId, updates) {
1516
- const conv = this.getMap().get(conversationId);
1517
- if (!conv)
1518
- return null;
1519
- const idx = conv.messages.findIndex((m) => m.id === messageId);
1520
- if (idx === -1)
1521
- return null;
1522
- const msg = conv.messages[idx];
1523
- if (!msg)
1524
- return null;
1525
- const updated = {
1526
- ...msg,
1527
- ...updates,
1528
- updatedAt: new Date
1529
- };
1530
- conv.messages[idx] = updated;
1531
- conv.updatedAt = new Date;
1532
- this.persist();
1533
- return updated;
1534
- }
1535
- async delete(conversationId) {
1536
- const deleted = this.getMap().delete(conversationId);
1537
- if (deleted)
1538
- this.persist();
1539
- return deleted;
1540
- }
1541
- async list(options) {
1542
- let results = Array.from(this.getMap().values());
1543
- if (options?.status) {
1544
- results = results.filter((c) => c.status === options.status);
1545
- }
1546
- if (options?.projectId) {
1547
- results = results.filter((c) => c.projectId === options.projectId);
1548
- }
1549
- if (options?.tags && options.tags.length > 0) {
1550
- const tagSet = new Set(options.tags);
1551
- results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
1552
- }
1553
- results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
1554
- const offset = options?.offset ?? 0;
1555
- const limit = options?.limit ?? 100;
1556
- return results.slice(offset, offset + limit);
1557
- }
1558
- async fork(conversationId, upToMessageId) {
1559
- const source = this.getMap().get(conversationId);
1560
- if (!source)
1561
- throw new Error(`Conversation ${conversationId} not found`);
1562
- let messagesToCopy = source.messages;
1563
- if (upToMessageId) {
1564
- const idx = source.messages.findIndex((m) => m.id === upToMessageId);
1565
- if (idx === -1)
1566
- throw new Error(`Message ${upToMessageId} not found`);
1567
- messagesToCopy = source.messages.slice(0, idx + 1);
1568
- }
1569
- const now = new Date;
1570
- const forkedMessages = messagesToCopy.map((m) => ({
1571
- ...m,
1572
- id: generateId2("msg"),
1573
- conversationId: "",
1574
- createdAt: new Date(m.createdAt),
1575
- updatedAt: new Date(m.updatedAt)
1576
- }));
1577
- const forked = {
1578
- ...source,
1579
- id: generateId2("conv"),
1580
- title: source.title ? `${source.title} (fork)` : undefined,
1581
- forkedFromId: source.id,
1582
- createdAt: now,
1583
- updatedAt: now,
1584
- messages: forkedMessages
1585
- };
1586
- for (const m of forked.messages) {
1587
- m.conversationId = forked.id;
1588
- }
1589
- this.getMap().set(forked.id, forked);
1590
- this.persist();
1591
- return forked;
1592
- }
1593
- async truncateAfter(conversationId, messageId) {
1594
- const conv = this.getMap().get(conversationId);
1595
- if (!conv)
1596
- return null;
1597
- const idx = conv.messages.findIndex((m) => m.id === messageId);
1598
- if (idx === -1)
1599
- return null;
1600
- conv.messages = conv.messages.slice(0, idx + 1);
1601
- conv.updatedAt = new Date;
1602
- this.persist();
1603
- return conv;
1604
- }
1605
- async search(query, limit = 20) {
1606
- const lowerQuery = query.toLowerCase();
1607
- const results = [];
1608
- for (const conv of this.getMap().values()) {
1609
- if (conv.title?.toLowerCase().includes(lowerQuery)) {
1610
- results.push(conv);
1611
- continue;
1612
- }
1613
- if (conv.messages.some((m) => m.content.toLowerCase().includes(lowerQuery))) {
1614
- results.push(conv);
1615
- }
1616
- if (results.length >= limit)
1617
- break;
1618
- }
1619
- return results;
1620
- }
1621
- }
1622
- function createLocalStorageConversationStore(storageKey) {
1623
- return new LocalStorageConversationStore(storageKey);
1624
- }
1625
- export {
1626
- getProviderOptions,
1627
- getExportFilename,
1628
- formatMessagesAsTxt,
1629
- formatMessagesAsMarkdown,
1630
- formatMessagesAsJson,
1631
- exportToFile,
1632
- downloadAsFile,
1633
- createWorkflowTools,
1634
- createSurfacePlannerTools,
1635
- createLocalStorageConversationStore,
1636
- createInMemoryConversationStore,
1637
- createCompletionRoute,
1638
- createChatService,
1639
- createChatRoute,
1640
- createChatAgentAdapter,
1641
- buildPlannerPromptInput,
1642
- buildContractsContextPrompt,
1643
- agentToolConfigsToToolSet,
1644
- THINKING_LEVEL_LABELS,
1645
- THINKING_LEVEL_DESCRIPTIONS,
1646
- LocalStorageConversationStore,
1647
- InMemoryConversationStore,
1648
- ChatService,
1649
- CHAT_ROUTE_MAX_DURATION
1650
- };
110
+ `)}function PX(X,Z){let $={messages:X.map(KX)};if(Z)$.conversation={id:Z.id,title:Z.title,status:Z.status,createdAt:h(Z.createdAt),updatedAt:h(Z.updatedAt),provider:Z.provider,model:Z.model,workspacePath:Z.workspacePath,contextFiles:Z.contextFiles,summary:Z.summary,metadata:Z.metadata};return JSON.stringify($,null,2)}function kX(X,Z){let $=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);return`${Z?.title?Z.title.replace(/[^a-zA-Z0-9-_]/g,"_").slice(0,40):"chat-export"}-${$}.${X==="markdown"?"md":X==="txt"?"txt":"json"}`}var wX={markdown:"text/markdown",txt:"text/plain",json:"application/json"};function hX(X,Z,$){let j=new Blob([X],{type:$}),H=URL.createObjectURL(j),V=document.createElement("a");V.href=H,V.download=Z,document.body.appendChild(V),V.click(),document.body.removeChild(V),URL.revokeObjectURL(H)}function OZ(X,Z,$){let j;if(Z==="markdown")j=RX(X);else if(Z==="txt")j=zX(X);else j=PX(X,$);let H=kX(Z,$),V=wX[Z];hX(j,H,V)}function M(X){return`${X}_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function MX(X){return{...X,createdAt:X.createdAt.toISOString(),updatedAt:X.updatedAt.toISOString(),messages:X.messages.map((Z)=>({...Z,createdAt:Z.createdAt.toISOString(),updatedAt:Z.updatedAt.toISOString()}))}}function SX(X){let Z=X.messages?.map(($)=>({...$,createdAt:new Date($.createdAt),updatedAt:new Date($.updatedAt)}))??[];return{...X,createdAt:new Date(X.createdAt),updatedAt:new Date(X.updatedAt),messages:Z}}function xX(X){if(typeof window>"u")return new Map;try{let Z=window.localStorage.getItem(X);if(!Z)return new Map;let $=JSON.parse(Z),j=new Map;for(let H of $){let V=SX(H);j.set(V.id,V)}return j}catch{return new Map}}function bX(X,Z){if(typeof window>"u")return;try{let $=Array.from(Z.values()).map(MX);window.localStorage.setItem(X,JSON.stringify($))}catch{}}class l{key;cache=null;constructor(X="contractspec:ai-chat:conversations"){this.key=X}getMap(){if(!this.cache)this.cache=xX(this.key);return this.cache}persist(){bX(this.key,this.getMap())}async get(X){return this.getMap().get(X)??null}async create(X){let Z=new Date,$={...X,id:M("conv"),createdAt:Z,updatedAt:Z};return this.getMap().set($.id,$),this.persist(),$}async update(X,Z){let $=this.getMap().get(X);if(!$)return null;let j={...$,...Z,updatedAt:new Date};return this.getMap().set(X,j),this.persist(),j}async appendMessage(X,Z){let $=this.getMap().get(X);if(!$)throw Error(`Conversation ${X} not found`);let j=new Date,H={...Z,id:M("msg"),conversationId:X,createdAt:j,updatedAt:j};return $.messages.push(H),$.updatedAt=j,this.persist(),H}async updateMessage(X,Z,$){let j=this.getMap().get(X);if(!j)return null;let H=j.messages.findIndex((J)=>J.id===Z);if(H===-1)return null;let V=j.messages[H];if(!V)return null;let G={...V,...$,updatedAt:new Date};return j.messages[H]=G,j.updatedAt=new Date,this.persist(),G}async delete(X){let Z=this.getMap().delete(X);if(Z)this.persist();return Z}async list(X){let Z=Array.from(this.getMap().values());if(X?.status)Z=Z.filter((H)=>H.status===X.status);if(X?.projectId)Z=Z.filter((H)=>H.projectId===X.projectId);if(X?.tags&&X.tags.length>0){let H=new Set(X.tags);Z=Z.filter((V)=>V.tags&&V.tags.some((G)=>H.has(G)))}Z.sort((H,V)=>V.updatedAt.getTime()-H.updatedAt.getTime());let $=X?.offset??0,j=X?.limit??100;return Z.slice($,$+j)}async fork(X,Z){let $=this.getMap().get(X);if(!$)throw Error(`Conversation ${X} not found`);let j=$.messages;if(Z){let J=$.messages.findIndex((Q)=>Q.id===Z);if(J===-1)throw Error(`Message ${Z} not found`);j=$.messages.slice(0,J+1)}let H=new Date,V=j.map((J)=>({...J,id:M("msg"),conversationId:"",createdAt:new Date(J.createdAt),updatedAt:new Date(J.updatedAt)})),G={...$,id:M("conv"),title:$.title?`${$.title} (fork)`:void 0,forkedFromId:$.id,createdAt:H,updatedAt:H,messages:V};for(let J of G.messages)J.conversationId=G.id;return this.getMap().set(G.id,G),this.persist(),G}async truncateAfter(X,Z){let $=this.getMap().get(X);if(!$)return null;let j=$.messages.findIndex((H)=>H.id===Z);if(j===-1)return null;return $.messages=$.messages.slice(0,j+1),$.updatedAt=new Date,this.persist(),$}async search(X,Z=20){let $=X.toLowerCase(),j=[];for(let H of this.getMap().values()){if(H.title?.toLowerCase().includes($)){j.push(H);continue}if(H.messages.some((V)=>V.content.toLowerCase().includes($)))j.push(H);if(j.length>=Z)break}return j}}function RZ(X){return new l(X)}export{A as getProviderOptions,kX as getExportFilename,zX as formatMessagesAsTxt,RX as formatMessagesAsMarkdown,PX as formatMessagesAsJson,OZ as exportToFile,hX as downloadAsFile,v as createWorkflowTools,T as createSurfacePlannerTools,RZ as createLocalStorageConversationStore,lX as createInMemoryConversationStore,LZ as createCompletionRoute,UZ as createChatService,_Z as createChatRoute,IX as createChatAgentAdapter,I as buildPlannerPromptInput,y as buildContractsContextPrompt,b as agentToolConfigsToToolSet,tX as THINKING_LEVEL_LABELS,nX as THINKING_LEVEL_DESCRIPTIONS,l as LocalStorageConversationStore,P as InMemoryConversationStore,u as ChatService,FZ as CHAT_ROUTE_MAX_DURATION};