@contractspec/module.ai-chat 4.3.6 → 4.3.10

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 (46) hide show
  1. package/README.md +62 -370
  2. package/dist/adapters/ai-sdk-bundle-adapter.d.ts +1 -1
  3. package/dist/adapters/index.d.ts +1 -1
  4. package/dist/browser/context/index.js +93 -93
  5. package/dist/browser/core/index.js +308 -307
  6. package/dist/browser/index.js +3803 -3801
  7. package/dist/browser/presentation/components/index.js +2617 -2618
  8. package/dist/browser/presentation/hooks/index.js +476 -476
  9. package/dist/browser/presentation/index.js +2474 -2475
  10. package/dist/browser/providers/index.js +7 -7
  11. package/dist/context/index.d.ts +1 -1
  12. package/dist/context/index.js +93 -93
  13. package/dist/core/agent-tools-adapter.d.ts +1 -1
  14. package/dist/core/chat-service.d.ts +4 -4
  15. package/dist/core/create-chat-route.d.ts +1 -1
  16. package/dist/core/create-completion-route.d.ts +15 -0
  17. package/dist/core/export-formatters.d.ts +1 -1
  18. package/dist/core/index.d.ts +6 -6
  19. package/dist/core/index.js +308 -307
  20. package/dist/core/local-storage-conversation-store.d.ts +1 -1
  21. package/dist/core/surface-planner-tools.d.ts +2 -2
  22. package/dist/core/workflow-tools.d.ts +1 -1
  23. package/dist/index.d.ts +8 -8
  24. package/dist/index.js +3803 -3801
  25. package/dist/node/context/index.js +93 -93
  26. package/dist/node/core/index.js +308 -307
  27. package/dist/node/index.js +3803 -3801
  28. package/dist/node/presentation/components/index.js +2617 -2618
  29. package/dist/node/presentation/hooks/index.js +476 -476
  30. package/dist/node/presentation/index.js +2474 -2475
  31. package/dist/node/providers/index.js +7 -7
  32. package/dist/presentation/components/ChainOfThought.d.ts +1 -1
  33. package/dist/presentation/components/ChatExportToolbar.d.ts +1 -1
  34. package/dist/presentation/components/ChatSidebar.d.ts +1 -1
  35. package/dist/presentation/components/ChatWithExport.d.ts +1 -1
  36. package/dist/presentation/components/index.d.ts +11 -11
  37. package/dist/presentation/components/index.js +2617 -2618
  38. package/dist/presentation/hooks/index.d.ts +4 -4
  39. package/dist/presentation/hooks/index.js +476 -476
  40. package/dist/presentation/hooks/useChat.d.ts +6 -6
  41. package/dist/presentation/hooks/useConversations.d.ts +1 -1
  42. package/dist/presentation/hooks/useProviders.d.ts +1 -1
  43. package/dist/presentation/index.js +2474 -2475
  44. package/dist/providers/index.d.ts +2 -2
  45. package/dist/providers/index.js +7 -7
  46. package/package.json +15 -15
@@ -6,13 +6,109 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  throw Error('Dynamic require of "' + x + '" is not supported');
7
7
  });
8
8
 
9
+ // src/presentation/hooks/index.ts
10
+ import { useCompletion } from "@ai-sdk/react";
11
+
9
12
  // src/presentation/hooks/useChat.tsx
10
- import * as React from "react";
13
+ import {
14
+ createProvider
15
+ } from "@contractspec/lib.ai-providers";
11
16
  import { tool as tool4 } from "ai";
17
+ import * as React from "react";
12
18
  import { z as z4 } from "zod";
13
19
 
14
20
  // src/core/chat-service.ts
15
21
  import { generateText, streamText } from "ai";
22
+ import { compilePlannerPrompt } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
23
+
24
+ // src/core/agent-tools-adapter.ts
25
+ import { tool } from "ai";
26
+ import { z } from "zod";
27
+ function getInputSchema(_schema) {
28
+ return z.object({}).passthrough();
29
+ }
30
+ function agentToolConfigsToToolSet(configs, handlers) {
31
+ const result = {};
32
+ for (const config of configs) {
33
+ const handler = handlers?.[config.name];
34
+ const inputSchema = getInputSchema(config.schema);
35
+ result[config.name] = tool({
36
+ description: config.description ?? config.name,
37
+ inputSchema,
38
+ execute: async (input) => {
39
+ if (!handler) {
40
+ return {
41
+ status: "unimplemented",
42
+ message: "Wire handler in host",
43
+ toolName: config.name
44
+ };
45
+ }
46
+ try {
47
+ const output = await Promise.resolve(handler(input));
48
+ return typeof output === "string" ? output : output;
49
+ } catch (err) {
50
+ return {
51
+ status: "error",
52
+ error: err instanceof Error ? err.message : String(err),
53
+ toolName: config.name
54
+ };
55
+ }
56
+ }
57
+ });
58
+ }
59
+ return result;
60
+ }
61
+
62
+ // src/core/contracts-context.ts
63
+ function buildContractsContextPrompt(config) {
64
+ const parts = [];
65
+ if (!config.agentSpecs?.length && !config.dataViewSpecs?.length && !config.formSpecs?.length && !config.presentationSpecs?.length && !config.operationRefs?.length) {
66
+ return "";
67
+ }
68
+ parts.push(`
69
+
70
+ ## Available resources`);
71
+ if (config.agentSpecs?.length) {
72
+ parts.push(`
73
+ ### Agent tools`);
74
+ for (const agent of config.agentSpecs) {
75
+ const toolNames = agent.tools?.map((t) => t.name).join(", ") ?? "none";
76
+ parts.push(`- **${agent.key}**: tools: ${toolNames}`);
77
+ }
78
+ }
79
+ if (config.dataViewSpecs?.length) {
80
+ parts.push(`
81
+ ### Data views`);
82
+ for (const dv of config.dataViewSpecs) {
83
+ parts.push(`- **${dv.key}**: ${dv.meta.title ?? dv.key}`);
84
+ }
85
+ }
86
+ if (config.formSpecs?.length) {
87
+ parts.push(`
88
+ ### Forms`);
89
+ for (const form of config.formSpecs) {
90
+ parts.push(`- **${form.key}**: ${form.meta.title ?? form.key}`);
91
+ }
92
+ }
93
+ if (config.presentationSpecs?.length) {
94
+ parts.push(`
95
+ ### Presentations`);
96
+ for (const pres of config.presentationSpecs) {
97
+ parts.push(`- **${pres.key}**: ${pres.meta.title ?? pres.key} (targets: ${pres.targets?.join(", ") ?? "react"})`);
98
+ }
99
+ }
100
+ if (config.operationRefs?.length) {
101
+ parts.push(`
102
+ ### Operations`);
103
+ for (const op of config.operationRefs) {
104
+ parts.push(`- **${op.key}@${op.version}**`);
105
+ }
106
+ }
107
+ parts.push(`
108
+ Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`);
109
+ return parts.join(`
110
+ `);
111
+ }
16
112
 
17
113
  // src/core/conversation-store.ts
18
114
  function generateId(prefix) {
@@ -175,6 +271,163 @@ function createInMemoryConversationStore() {
175
271
  return new InMemoryConversationStore;
176
272
  }
177
273
 
274
+ // src/core/surface-planner-tools.ts
275
+ import { buildSurfacePatchProposal } from "@contractspec/lib.surface-runtime/runtime/planner-tools";
276
+ import {
277
+ validatePatchProposal
278
+ } from "@contractspec/lib.surface-runtime/spec/validate-surface-patch";
279
+ import { tool as tool2 } from "ai";
280
+ import { z as z2 } from "zod";
281
+ var VALID_OPS = [
282
+ "insert-node",
283
+ "replace-node",
284
+ "remove-node",
285
+ "move-node",
286
+ "resize-panel",
287
+ "set-layout",
288
+ "reveal-field",
289
+ "hide-field",
290
+ "promote-action",
291
+ "set-focus"
292
+ ];
293
+ var DEFAULT_NODE_KINDS = [
294
+ "entity-section",
295
+ "entity-card",
296
+ "data-view",
297
+ "assistant-panel",
298
+ "chat-thread",
299
+ "action-bar",
300
+ "timeline",
301
+ "table",
302
+ "rich-doc",
303
+ "form",
304
+ "chart",
305
+ "custom-widget"
306
+ ];
307
+ function collectSlotIdsFromRegion(node) {
308
+ const ids = [];
309
+ if (node.type === "slot") {
310
+ ids.push(node.slotId);
311
+ }
312
+ if (node.type === "panel-group" || node.type === "stack") {
313
+ for (const child of node.children) {
314
+ ids.push(...collectSlotIdsFromRegion(child));
315
+ }
316
+ }
317
+ if (node.type === "tabs") {
318
+ for (const tab of node.tabs) {
319
+ ids.push(...collectSlotIdsFromRegion(tab.child));
320
+ }
321
+ }
322
+ if (node.type === "floating") {
323
+ ids.push(node.anchorSlotId);
324
+ ids.push(...collectSlotIdsFromRegion(node.child));
325
+ }
326
+ return ids;
327
+ }
328
+ function deriveConstraints(plan) {
329
+ const slotIds = collectSlotIdsFromRegion(plan.layoutRoot);
330
+ const uniqueSlots = [...new Set(slotIds)];
331
+ return {
332
+ allowedOps: VALID_OPS,
333
+ allowedSlots: uniqueSlots.length > 0 ? uniqueSlots : ["assistant", "primary"],
334
+ allowedNodeKinds: DEFAULT_NODE_KINDS
335
+ };
336
+ }
337
+ var ProposePatchInputSchema = z2.object({
338
+ proposalId: z2.string().describe("Unique proposal identifier"),
339
+ ops: z2.array(z2.object({
340
+ op: z2.enum([
341
+ "insert-node",
342
+ "replace-node",
343
+ "remove-node",
344
+ "move-node",
345
+ "resize-panel",
346
+ "set-layout",
347
+ "reveal-field",
348
+ "hide-field",
349
+ "promote-action",
350
+ "set-focus"
351
+ ]),
352
+ slotId: z2.string().optional(),
353
+ nodeId: z2.string().optional(),
354
+ toSlotId: z2.string().optional(),
355
+ index: z2.number().optional(),
356
+ node: z2.object({
357
+ nodeId: z2.string(),
358
+ kind: z2.string(),
359
+ title: z2.string().optional(),
360
+ props: z2.record(z2.string(), z2.unknown()).optional(),
361
+ children: z2.array(z2.unknown()).optional()
362
+ }).optional(),
363
+ persistKey: z2.string().optional(),
364
+ sizes: z2.array(z2.number()).optional(),
365
+ layoutId: z2.string().optional(),
366
+ fieldId: z2.string().optional(),
367
+ actionId: z2.string().optional(),
368
+ placement: z2.enum(["header", "inline", "context", "assistant"]).optional(),
369
+ targetId: z2.string().optional()
370
+ }))
371
+ });
372
+ function createSurfacePlannerTools(config) {
373
+ const { plan, constraints, onPatchProposal } = config;
374
+ const resolvedConstraints = constraints ?? deriveConstraints(plan);
375
+ const proposePatchTool = tool2({
376
+ description: "Propose surface patches (layout changes, node insertions, etc.) for user approval. " + "Only use allowed ops, slots, and node kinds from the planner context.",
377
+ inputSchema: ProposePatchInputSchema,
378
+ execute: async (input) => {
379
+ const ops = input.ops;
380
+ try {
381
+ validatePatchProposal(ops, resolvedConstraints);
382
+ const proposal = buildSurfacePatchProposal(input.proposalId, ops);
383
+ onPatchProposal?.(proposal);
384
+ return {
385
+ success: true,
386
+ proposalId: proposal.proposalId,
387
+ opsCount: proposal.ops.length,
388
+ message: "Patch proposal validated; awaiting user approval"
389
+ };
390
+ } catch (err) {
391
+ return {
392
+ success: false,
393
+ error: err instanceof Error ? err.message : String(err),
394
+ proposalId: input.proposalId
395
+ };
396
+ }
397
+ }
398
+ });
399
+ return {
400
+ "propose-patch": proposePatchTool
401
+ };
402
+ }
403
+ function buildPlannerPromptInput(plan) {
404
+ const constraints = deriveConstraints(plan);
405
+ return {
406
+ bundleMeta: {
407
+ key: plan.bundleKey,
408
+ version: "0.0.0",
409
+ title: plan.bundleKey
410
+ },
411
+ surfaceId: plan.surfaceId,
412
+ allowedPatchOps: constraints.allowedOps,
413
+ allowedSlots: [...constraints.allowedSlots],
414
+ allowedNodeKinds: [...constraints.allowedNodeKinds],
415
+ actions: plan.actions.map((a) => ({
416
+ actionId: a.actionId,
417
+ title: a.title
418
+ })),
419
+ preferences: {
420
+ guidance: "hints",
421
+ density: "standard",
422
+ dataDepth: "detailed",
423
+ control: "standard",
424
+ media: "text",
425
+ pace: "balanced",
426
+ narrative: "top-down"
427
+ }
428
+ };
429
+ }
430
+
178
431
  // src/core/thinking-levels.ts
179
432
  var THINKING_LEVEL_LABELS = {
180
433
  instant: "Instant",
@@ -227,457 +480,210 @@ function getProviderOptions(level, providerName) {
227
480
  }
228
481
 
229
482
  // src/core/workflow-tools.ts
230
- import { tool } from "ai";
231
- import { z } from "zod";
232
483
  import {
233
- WorkflowComposer,
234
- validateExtension
484
+ validateExtension,
485
+ WorkflowComposer
235
486
  } from "@contractspec/lib.workflow-composer";
236
- var StepTypeSchema = z.enum(["human", "automation", "decision"]);
237
- var StepActionSchema = z.object({
238
- operation: z.object({
239
- name: z.string(),
240
- version: z.number()
487
+ import { tool as tool3 } from "ai";
488
+ import { z as z3 } from "zod";
489
+ var StepTypeSchema = z3.enum(["human", "automation", "decision"]);
490
+ var StepActionSchema = z3.object({
491
+ operation: z3.object({
492
+ name: z3.string(),
493
+ version: z3.number()
241
494
  }).optional(),
242
- form: z.object({
243
- key: z.string(),
244
- version: z.number()
495
+ form: z3.object({
496
+ key: z3.string(),
497
+ version: z3.number()
245
498
  }).optional()
246
499
  }).optional();
247
- var StepSchema = z.object({
248
- id: z.string(),
500
+ var StepSchema = z3.object({
501
+ id: z3.string(),
249
502
  type: StepTypeSchema,
250
- label: z.string(),
251
- description: z.string().optional(),
503
+ label: z3.string(),
504
+ description: z3.string().optional(),
252
505
  action: StepActionSchema
253
506
  });
254
- var StepInjectionSchema = z.object({
255
- after: z.string().optional(),
256
- before: z.string().optional(),
507
+ var StepInjectionSchema = z3.object({
508
+ after: z3.string().optional(),
509
+ before: z3.string().optional(),
257
510
  inject: StepSchema,
258
- transitionTo: z.string().optional(),
259
- transitionFrom: z.string().optional(),
260
- when: z.string().optional()
511
+ transitionTo: z3.string().optional(),
512
+ transitionFrom: z3.string().optional(),
513
+ when: z3.string().optional()
261
514
  });
262
- var WorkflowExtensionInputSchema = z.object({
263
- workflow: z.string(),
264
- tenantId: z.string().optional(),
265
- role: z.string().optional(),
266
- priority: z.number().optional(),
267
- customSteps: z.array(StepInjectionSchema).optional(),
268
- hiddenSteps: z.array(z.string()).optional()
515
+ var WorkflowExtensionInputSchema = z3.object({
516
+ workflow: z3.string(),
517
+ tenantId: z3.string().optional(),
518
+ role: z3.string().optional(),
519
+ priority: z3.number().optional(),
520
+ customSteps: z3.array(StepInjectionSchema).optional(),
521
+ hiddenSteps: z3.array(z3.string()).optional()
269
522
  });
270
523
  function createWorkflowTools(config) {
271
524
  const { baseWorkflows, composer } = config;
272
525
  const baseByKey = new Map(baseWorkflows.map((b) => [b.meta.key, b]));
273
- const createWorkflowExtensionTool = tool({
526
+ const createWorkflowExtensionTool = tool3({
274
527
  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.",
275
- inputSchema: WorkflowExtensionInputSchema,
276
- execute: async (input) => {
277
- const extension = {
278
- workflow: input.workflow,
279
- tenantId: input.tenantId,
280
- role: input.role,
281
- priority: input.priority,
282
- customSteps: input.customSteps,
283
- hiddenSteps: input.hiddenSteps
284
- };
285
- const base = baseByKey.get(input.workflow);
286
- if (!base) {
287
- return {
288
- success: false,
289
- error: `Base workflow "${input.workflow}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
290
- extension
291
- };
292
- }
293
- try {
294
- validateExtension(extension, base);
295
- return {
296
- success: true,
297
- message: "Extension validated successfully",
298
- extension
299
- };
300
- } catch (err) {
301
- return {
302
- success: false,
303
- error: err instanceof Error ? err.message : String(err),
304
- extension
305
- };
306
- }
307
- }
308
- });
309
- const composeWorkflowInputSchema = z.object({
310
- workflowKey: z.string().describe("Base workflow meta.key"),
311
- tenantId: z.string().optional(),
312
- role: z.string().optional(),
313
- extensions: z.array(WorkflowExtensionInputSchema).optional().describe("Extensions to register before composing")
314
- });
315
- const composeWorkflowTool = tool({
316
- description: "Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",
317
- inputSchema: composeWorkflowInputSchema,
318
- execute: async (input) => {
319
- const base = baseByKey.get(input.workflowKey);
320
- if (!base) {
321
- return {
322
- success: false,
323
- error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`
324
- };
325
- }
326
- const comp = composer ?? new WorkflowComposer;
327
- if (input.extensions?.length) {
328
- for (const ext of input.extensions) {
329
- comp.register({
330
- workflow: ext.workflow,
331
- tenantId: ext.tenantId,
332
- role: ext.role,
333
- priority: ext.priority,
334
- customSteps: ext.customSteps,
335
- hiddenSteps: ext.hiddenSteps
336
- });
337
- }
338
- }
339
- try {
340
- const composed = comp.compose({
341
- base,
342
- tenantId: input.tenantId,
343
- role: input.role
344
- });
345
- return {
346
- success: true,
347
- workflow: composed,
348
- meta: composed.meta,
349
- stepIds: composed.definition.steps.map((s) => s.id)
350
- };
351
- } catch (err) {
352
- return {
353
- success: false,
354
- error: err instanceof Error ? err.message : String(err)
355
- };
356
- }
357
- }
358
- });
359
- const generateWorkflowSpecCodeInputSchema = z.object({
360
- workflowKey: z.string().describe("Workflow meta.key"),
361
- composedSteps: z.array(z.object({
362
- id: z.string(),
363
- type: z.enum(["human", "automation", "decision"]),
364
- label: z.string(),
365
- description: z.string().optional()
366
- })).optional().describe("Steps to include; if omitted, uses the base workflow")
367
- });
368
- const generateWorkflowSpecCodeTool = tool({
369
- description: "Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",
370
- inputSchema: generateWorkflowSpecCodeInputSchema,
371
- execute: async (input) => {
372
- const base = baseByKey.get(input.workflowKey);
373
- if (!base) {
374
- return {
375
- success: false,
376
- error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
377
- code: null
378
- };
379
- }
380
- const steps = input.composedSteps ?? base.definition.steps;
381
- const specVarName = toPascalCase((base.meta.key.split(".").pop() ?? "Workflow") + "") + "Workflow";
382
- const stepsCode = steps.map((s) => ` {
383
- id: '${s.id}',
384
- type: '${s.type}',
385
- label: '${escapeString(s.label)}',${s.description ? `
386
- description: '${escapeString(s.description)}',` : ""}
387
- }`).join(`,
388
- `);
389
- const meta = base.meta;
390
- const transitionsJson = JSON.stringify(base.definition.transitions, null, 6);
391
- const code = `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow';
392
-
393
- /**
394
- * Workflow: ${base.meta.key}
395
- * Generated via AI chat workflow tools.
396
- */
397
- export const ${specVarName}: WorkflowSpec = {
398
- meta: {
399
- key: '${base.meta.key}',
400
- version: '${String(base.meta.version)}',
401
- title: '${escapeString(meta.title ?? base.meta.key)}',
402
- description: '${escapeString(meta.description ?? "")}',
403
- },
404
- definition: {
405
- entryStepId: '${base.definition.entryStepId ?? base.definition.steps[0]?.id ?? ""}',
406
- steps: [
407
- ${stepsCode}
408
- ],
409
- transitions: ${transitionsJson},
410
- },
411
- };
412
- `;
413
- return {
414
- success: true,
415
- code,
416
- workflowKey: input.workflowKey
417
- };
418
- }
419
- });
420
- return {
421
- create_workflow_extension: createWorkflowExtensionTool,
422
- compose_workflow: composeWorkflowTool,
423
- generate_workflow_spec_code: generateWorkflowSpecCodeTool
424
- };
425
- }
426
- function toPascalCase(value) {
427
- return value.split(/[-_.]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
428
- }
429
- function escapeString(value) {
430
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
431
- }
432
-
433
- // src/core/contracts-context.ts
434
- function buildContractsContextPrompt(config) {
435
- const parts = [];
436
- if (!config.agentSpecs?.length && !config.dataViewSpecs?.length && !config.formSpecs?.length && !config.presentationSpecs?.length && !config.operationRefs?.length) {
437
- return "";
438
- }
439
- parts.push(`
440
-
441
- ## Available resources`);
442
- if (config.agentSpecs?.length) {
443
- parts.push(`
444
- ### Agent tools`);
445
- for (const agent of config.agentSpecs) {
446
- const toolNames = agent.tools?.map((t) => t.name).join(", ") ?? "none";
447
- parts.push(`- **${agent.key}**: tools: ${toolNames}`);
448
- }
449
- }
450
- if (config.dataViewSpecs?.length) {
451
- parts.push(`
452
- ### Data views`);
453
- for (const dv of config.dataViewSpecs) {
454
- parts.push(`- **${dv.key}**: ${dv.meta.title ?? dv.key}`);
455
- }
456
- }
457
- if (config.formSpecs?.length) {
458
- parts.push(`
459
- ### Forms`);
460
- for (const form of config.formSpecs) {
461
- parts.push(`- **${form.key}**: ${form.meta.title ?? form.key}`);
462
- }
463
- }
464
- if (config.presentationSpecs?.length) {
465
- parts.push(`
466
- ### Presentations`);
467
- for (const pres of config.presentationSpecs) {
468
- parts.push(`- **${pres.key}**: ${pres.meta.title ?? pres.key} (targets: ${pres.targets?.join(", ") ?? "react"})`);
469
- }
470
- }
471
- if (config.operationRefs?.length) {
472
- parts.push(`
473
- ### Operations`);
474
- for (const op of config.operationRefs) {
475
- parts.push(`- **${op.key}@${op.version}**`);
476
- }
477
- }
478
- parts.push(`
479
- Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`);
480
- return parts.join(`
481
- `);
482
- }
483
-
484
- // src/core/agent-tools-adapter.ts
485
- import { tool as tool2 } from "ai";
486
- import { z as z2 } from "zod";
487
- function getInputSchema(_schema) {
488
- return z2.object({}).passthrough();
489
- }
490
- function agentToolConfigsToToolSet(configs, handlers) {
491
- const result = {};
492
- for (const config of configs) {
493
- const handler = handlers?.[config.name];
494
- const inputSchema = getInputSchema(config.schema);
495
- result[config.name] = tool2({
496
- description: config.description ?? config.name,
497
- inputSchema,
498
- execute: async (input) => {
499
- if (!handler) {
500
- return {
501
- status: "unimplemented",
502
- message: "Wire handler in host",
503
- toolName: config.name
504
- };
505
- }
506
- try {
507
- const output = await Promise.resolve(handler(input));
508
- return typeof output === "string" ? output : output;
509
- } catch (err) {
510
- return {
511
- status: "error",
512
- error: err instanceof Error ? err.message : String(err),
513
- toolName: config.name
514
- };
515
- }
516
- }
517
- });
518
- }
519
- return result;
520
- }
521
-
522
- // src/core/surface-planner-tools.ts
523
- import { tool as tool3 } from "ai";
524
- import { z as z3 } from "zod";
525
- import {
526
- validatePatchProposal
527
- } from "@contractspec/lib.surface-runtime/spec/validate-surface-patch";
528
- import { buildSurfacePatchProposal } from "@contractspec/lib.surface-runtime/runtime/planner-tools";
529
- var VALID_OPS = [
530
- "insert-node",
531
- "replace-node",
532
- "remove-node",
533
- "move-node",
534
- "resize-panel",
535
- "set-layout",
536
- "reveal-field",
537
- "hide-field",
538
- "promote-action",
539
- "set-focus"
540
- ];
541
- var DEFAULT_NODE_KINDS = [
542
- "entity-section",
543
- "entity-card",
544
- "data-view",
545
- "assistant-panel",
546
- "chat-thread",
547
- "action-bar",
548
- "timeline",
549
- "table",
550
- "rich-doc",
551
- "form",
552
- "chart",
553
- "custom-widget"
554
- ];
555
- function collectSlotIdsFromRegion(node) {
556
- const ids = [];
557
- if (node.type === "slot") {
558
- ids.push(node.slotId);
559
- }
560
- if (node.type === "panel-group" || node.type === "stack") {
561
- for (const child of node.children) {
562
- ids.push(...collectSlotIdsFromRegion(child));
563
- }
564
- }
565
- if (node.type === "tabs") {
566
- for (const tab of node.tabs) {
567
- ids.push(...collectSlotIdsFromRegion(tab.child));
568
- }
569
- }
570
- if (node.type === "floating") {
571
- ids.push(node.anchorSlotId);
572
- ids.push(...collectSlotIdsFromRegion(node.child));
573
- }
574
- return ids;
575
- }
576
- function deriveConstraints(plan) {
577
- const slotIds = collectSlotIdsFromRegion(plan.layoutRoot);
578
- const uniqueSlots = [...new Set(slotIds)];
579
- return {
580
- allowedOps: VALID_OPS,
581
- allowedSlots: uniqueSlots.length > 0 ? uniqueSlots : ["assistant", "primary"],
582
- allowedNodeKinds: DEFAULT_NODE_KINDS
583
- };
584
- }
585
- var ProposePatchInputSchema = z3.object({
586
- proposalId: z3.string().describe("Unique proposal identifier"),
587
- ops: z3.array(z3.object({
588
- op: z3.enum([
589
- "insert-node",
590
- "replace-node",
591
- "remove-node",
592
- "move-node",
593
- "resize-panel",
594
- "set-layout",
595
- "reveal-field",
596
- "hide-field",
597
- "promote-action",
598
- "set-focus"
599
- ]),
600
- slotId: z3.string().optional(),
601
- nodeId: z3.string().optional(),
602
- toSlotId: z3.string().optional(),
603
- index: z3.number().optional(),
604
- node: z3.object({
605
- nodeId: z3.string(),
606
- kind: z3.string(),
607
- title: z3.string().optional(),
608
- props: z3.record(z3.string(), z3.unknown()).optional(),
609
- children: z3.array(z3.unknown()).optional()
610
- }).optional(),
611
- persistKey: z3.string().optional(),
612
- sizes: z3.array(z3.number()).optional(),
613
- layoutId: z3.string().optional(),
614
- fieldId: z3.string().optional(),
615
- actionId: z3.string().optional(),
616
- placement: z3.enum(["header", "inline", "context", "assistant"]).optional(),
617
- targetId: z3.string().optional()
618
- }))
619
- });
620
- function createSurfacePlannerTools(config) {
621
- const { plan, constraints, onPatchProposal } = config;
622
- const resolvedConstraints = constraints ?? deriveConstraints(plan);
623
- const proposePatchTool = tool3({
624
- description: "Propose surface patches (layout changes, node insertions, etc.) for user approval. " + "Only use allowed ops, slots, and node kinds from the planner context.",
625
- inputSchema: ProposePatchInputSchema,
528
+ inputSchema: WorkflowExtensionInputSchema,
626
529
  execute: async (input) => {
627
- const ops = input.ops;
530
+ const extension = {
531
+ workflow: input.workflow,
532
+ tenantId: input.tenantId,
533
+ role: input.role,
534
+ priority: input.priority,
535
+ customSteps: input.customSteps,
536
+ hiddenSteps: input.hiddenSteps
537
+ };
538
+ const base = baseByKey.get(input.workflow);
539
+ if (!base) {
540
+ return {
541
+ success: false,
542
+ error: `Base workflow "${input.workflow}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
543
+ extension
544
+ };
545
+ }
628
546
  try {
629
- validatePatchProposal(ops, resolvedConstraints);
630
- const proposal = buildSurfacePatchProposal(input.proposalId, ops);
631
- onPatchProposal?.(proposal);
547
+ validateExtension(extension, base);
632
548
  return {
633
549
  success: true,
634
- proposalId: proposal.proposalId,
635
- opsCount: proposal.ops.length,
636
- message: "Patch proposal validated; awaiting user approval"
550
+ message: "Extension validated successfully",
551
+ extension
637
552
  };
638
553
  } catch (err) {
639
554
  return {
640
555
  success: false,
641
556
  error: err instanceof Error ? err.message : String(err),
642
- proposalId: input.proposalId
557
+ extension
558
+ };
559
+ }
560
+ }
561
+ });
562
+ const composeWorkflowInputSchema = z3.object({
563
+ workflowKey: z3.string().describe("Base workflow meta.key"),
564
+ tenantId: z3.string().optional(),
565
+ role: z3.string().optional(),
566
+ extensions: z3.array(WorkflowExtensionInputSchema).optional().describe("Extensions to register before composing")
567
+ });
568
+ const composeWorkflowTool = tool3({
569
+ description: "Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",
570
+ inputSchema: composeWorkflowInputSchema,
571
+ execute: async (input) => {
572
+ const base = baseByKey.get(input.workflowKey);
573
+ if (!base) {
574
+ return {
575
+ success: false,
576
+ error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`
577
+ };
578
+ }
579
+ const comp = composer ?? new WorkflowComposer;
580
+ if (input.extensions?.length) {
581
+ for (const ext of input.extensions) {
582
+ comp.register({
583
+ workflow: ext.workflow,
584
+ tenantId: ext.tenantId,
585
+ role: ext.role,
586
+ priority: ext.priority,
587
+ customSteps: ext.customSteps,
588
+ hiddenSteps: ext.hiddenSteps
589
+ });
590
+ }
591
+ }
592
+ try {
593
+ const composed = comp.compose({
594
+ base,
595
+ tenantId: input.tenantId,
596
+ role: input.role
597
+ });
598
+ return {
599
+ success: true,
600
+ workflow: composed,
601
+ meta: composed.meta,
602
+ stepIds: composed.definition.steps.map((s) => s.id)
603
+ };
604
+ } catch (err) {
605
+ return {
606
+ success: false,
607
+ error: err instanceof Error ? err.message : String(err)
608
+ };
609
+ }
610
+ }
611
+ });
612
+ const generateWorkflowSpecCodeInputSchema = z3.object({
613
+ workflowKey: z3.string().describe("Workflow meta.key"),
614
+ composedSteps: z3.array(z3.object({
615
+ id: z3.string(),
616
+ type: z3.enum(["human", "automation", "decision"]),
617
+ label: z3.string(),
618
+ description: z3.string().optional()
619
+ })).optional().describe("Steps to include; if omitted, uses the base workflow")
620
+ });
621
+ const generateWorkflowSpecCodeTool = tool3({
622
+ description: "Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",
623
+ inputSchema: generateWorkflowSpecCodeInputSchema,
624
+ execute: async (input) => {
625
+ const base = baseByKey.get(input.workflowKey);
626
+ if (!base) {
627
+ return {
628
+ success: false,
629
+ error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
630
+ code: null
643
631
  };
644
632
  }
633
+ const steps = input.composedSteps ?? base.definition.steps;
634
+ const specVarName = toPascalCase((base.meta.key.split(".").pop() ?? "Workflow") + "") + "Workflow";
635
+ const stepsCode = steps.map((s) => ` {
636
+ id: '${s.id}',
637
+ type: '${s.type}',
638
+ label: '${escapeString(s.label)}',${s.description ? `
639
+ description: '${escapeString(s.description)}',` : ""}
640
+ }`).join(`,
641
+ `);
642
+ const meta = base.meta;
643
+ const transitionsJson = JSON.stringify(base.definition.transitions, null, 6);
644
+ const code = `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow';
645
+
646
+ /**
647
+ * Workflow: ${base.meta.key}
648
+ * Generated via AI chat workflow tools.
649
+ */
650
+ export const ${specVarName}: WorkflowSpec = {
651
+ meta: {
652
+ key: '${base.meta.key}',
653
+ version: '${String(base.meta.version)}',
654
+ title: '${escapeString(meta.title ?? base.meta.key)}',
655
+ description: '${escapeString(meta.description ?? "")}',
656
+ },
657
+ definition: {
658
+ entryStepId: '${base.definition.entryStepId ?? base.definition.steps[0]?.id ?? ""}',
659
+ steps: [
660
+ ${stepsCode}
661
+ ],
662
+ transitions: ${transitionsJson},
663
+ },
664
+ };
665
+ `;
666
+ return {
667
+ success: true,
668
+ code,
669
+ workflowKey: input.workflowKey
670
+ };
645
671
  }
646
672
  });
647
673
  return {
648
- "propose-patch": proposePatchTool
674
+ create_workflow_extension: createWorkflowExtensionTool,
675
+ compose_workflow: composeWorkflowTool,
676
+ generate_workflow_spec_code: generateWorkflowSpecCodeTool
649
677
  };
650
678
  }
651
- function buildPlannerPromptInput(plan) {
652
- const constraints = deriveConstraints(plan);
653
- return {
654
- bundleMeta: {
655
- key: plan.bundleKey,
656
- version: "0.0.0",
657
- title: plan.bundleKey
658
- },
659
- surfaceId: plan.surfaceId,
660
- allowedPatchOps: constraints.allowedOps,
661
- allowedSlots: [...constraints.allowedSlots],
662
- allowedNodeKinds: [...constraints.allowedNodeKinds],
663
- actions: plan.actions.map((a) => ({
664
- actionId: a.actionId,
665
- title: a.title
666
- })),
667
- preferences: {
668
- guidance: "hints",
669
- density: "standard",
670
- dataDepth: "detailed",
671
- control: "standard",
672
- media: "text",
673
- pace: "balanced",
674
- narrative: "top-down"
675
- }
676
- };
679
+ function toPascalCase(value) {
680
+ return value.split(/[-_.]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
681
+ }
682
+ function escapeString(value) {
683
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
677
684
  }
678
685
 
679
686
  // src/core/chat-service.ts
680
- import { compilePlannerPrompt } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
681
687
  var DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
682
688
 
683
689
  Your capabilities:
@@ -1093,9 +1099,6 @@ function createChatService(config) {
1093
1099
  }
1094
1100
 
1095
1101
  // src/presentation/hooks/useChat.tsx
1096
- import {
1097
- createProvider
1098
- } from "@contractspec/lib.ai-providers";
1099
1102
  "use client";
1100
1103
  function toolsToToolSet(defs) {
1101
1104
  const result = {};
@@ -1534,44 +1537,42 @@ function useChat(options = {}) {
1534
1537
  ...hasApprovalTools && { addToolApprovalResponse }
1535
1538
  };
1536
1539
  }
1537
- // src/presentation/hooks/useProviders.tsx
1540
+ // src/presentation/hooks/useConversations.ts
1538
1541
  import * as React2 from "react";
1539
- import {
1540
- getAvailableProviders,
1541
- getModelsForProvider
1542
- } from "@contractspec/lib.ai-providers";
1543
1542
  "use client";
1544
- function useProviders() {
1545
- const [providers, setProviders] = React2.useState([]);
1543
+ function useConversations(options) {
1544
+ const { store, projectId, tags, limit = 50 } = options;
1545
+ const [conversations, setConversations] = React2.useState([]);
1546
1546
  const [isLoading, setIsLoading] = React2.useState(true);
1547
- const loadProviders = React2.useCallback(async () => {
1547
+ const refresh = React2.useCallback(async () => {
1548
1548
  setIsLoading(true);
1549
1549
  try {
1550
- const available = getAvailableProviders();
1551
- const providersWithModels = available.map((p) => ({
1552
- ...p,
1553
- models: getModelsForProvider(p.provider)
1554
- }));
1555
- setProviders(providersWithModels);
1556
- } catch (error) {
1557
- console.error("Failed to load providers:", error);
1550
+ const list = await store.list({
1551
+ status: "active",
1552
+ projectId,
1553
+ tags,
1554
+ limit
1555
+ });
1556
+ setConversations(list);
1558
1557
  } finally {
1559
1558
  setIsLoading(false);
1560
1559
  }
1561
- }, []);
1560
+ }, [store, projectId, tags, limit]);
1562
1561
  React2.useEffect(() => {
1563
- loadProviders();
1564
- }, [loadProviders]);
1565
- const availableProviders = React2.useMemo(() => providers.filter((p) => p.available), [providers]);
1566
- const isAvailable = React2.useCallback((provider) => providers.some((p) => p.provider === provider && p.available), [providers]);
1567
- const getModelsCallback = React2.useCallback((provider) => providers.find((p) => p.provider === provider)?.models ?? [], [providers]);
1562
+ refresh();
1563
+ }, [refresh]);
1564
+ const deleteConversation = React2.useCallback(async (id) => {
1565
+ const ok = await store.delete(id);
1566
+ if (ok) {
1567
+ setConversations((prev) => prev.filter((c) => c.id !== id));
1568
+ }
1569
+ return ok;
1570
+ }, [store]);
1568
1571
  return {
1569
- providers,
1570
- availableProviders,
1571
- isAvailable,
1572
- getModels: getModelsCallback,
1572
+ conversations,
1573
1573
  isLoading,
1574
- refresh: loadProviders
1574
+ refresh,
1575
+ deleteConversation
1575
1576
  };
1576
1577
  }
1577
1578
  // src/presentation/hooks/useMessageSelection.ts
@@ -1617,47 +1618,46 @@ function useMessageSelection(messageIds) {
1617
1618
  selectedCount
1618
1619
  };
1619
1620
  }
1620
- // src/presentation/hooks/useConversations.ts
1621
+ // src/presentation/hooks/useProviders.tsx
1622
+ import {
1623
+ getAvailableProviders,
1624
+ getModelsForProvider
1625
+ } from "@contractspec/lib.ai-providers";
1621
1626
  import * as React4 from "react";
1622
1627
  "use client";
1623
- function useConversations(options) {
1624
- const { store, projectId, tags, limit = 50 } = options;
1625
- const [conversations, setConversations] = React4.useState([]);
1628
+ function useProviders() {
1629
+ const [providers, setProviders] = React4.useState([]);
1626
1630
  const [isLoading, setIsLoading] = React4.useState(true);
1627
- const refresh = React4.useCallback(async () => {
1631
+ const loadProviders = React4.useCallback(async () => {
1628
1632
  setIsLoading(true);
1629
1633
  try {
1630
- const list = await store.list({
1631
- status: "active",
1632
- projectId,
1633
- tags,
1634
- limit
1635
- });
1636
- setConversations(list);
1634
+ const available = getAvailableProviders();
1635
+ const providersWithModels = available.map((p) => ({
1636
+ ...p,
1637
+ models: getModelsForProvider(p.provider)
1638
+ }));
1639
+ setProviders(providersWithModels);
1640
+ } catch (error) {
1641
+ console.error("Failed to load providers:", error);
1637
1642
  } finally {
1638
1643
  setIsLoading(false);
1639
1644
  }
1640
- }, [store, projectId, tags, limit]);
1645
+ }, []);
1641
1646
  React4.useEffect(() => {
1642
- refresh();
1643
- }, [refresh]);
1644
- const deleteConversation = React4.useCallback(async (id) => {
1645
- const ok = await store.delete(id);
1646
- if (ok) {
1647
- setConversations((prev) => prev.filter((c) => c.id !== id));
1648
- }
1649
- return ok;
1650
- }, [store]);
1647
+ loadProviders();
1648
+ }, [loadProviders]);
1649
+ const availableProviders = React4.useMemo(() => providers.filter((p) => p.available), [providers]);
1650
+ const isAvailable = React4.useCallback((provider) => providers.some((p) => p.provider === provider && p.available), [providers]);
1651
+ const getModelsCallback = React4.useCallback((provider) => providers.find((p) => p.provider === provider)?.models ?? [], [providers]);
1651
1652
  return {
1652
- conversations,
1653
+ providers,
1654
+ availableProviders,
1655
+ isAvailable,
1656
+ getModels: getModelsCallback,
1653
1657
  isLoading,
1654
- refresh,
1655
- deleteConversation
1658
+ refresh: loadProviders
1656
1659
  };
1657
1660
  }
1658
-
1659
- // src/presentation/hooks/index.ts
1660
- import { useCompletion } from "@ai-sdk/react";
1661
1661
  export {
1662
1662
  useProviders,
1663
1663
  useMessageSelection,