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