@objectstack/service-ai 4.0.4 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1176 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1752 -431
- package/dist/index.d.ts +1752 -431
- package/dist/index.js +1160 -127
- package/dist/index.js.map +1 -1
- package/package.json +35 -8
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -61
- package/src/__tests__/ai-service.test.ts +0 -981
- package/src/__tests__/auth-and-toolcalling.test.ts +0 -677
- package/src/__tests__/chatbot-features.test.ts +0 -1116
- package/src/__tests__/metadata-tools.test.ts +0 -970
- package/src/__tests__/objectql-conversation-service.test.ts +0 -382
- package/src/__tests__/tool-routes.test.ts +0 -191
- package/src/__tests__/vercel-stream-encoder.test.ts +0 -310
- package/src/adapters/index.ts +0 -6
- package/src/adapters/memory-adapter.ts +0 -72
- package/src/adapters/types.ts +0 -3
- package/src/adapters/vercel-adapter.ts +0 -148
- package/src/agent-runtime.ts +0 -154
- package/src/agents/data-chat-agent.ts +0 -79
- package/src/agents/index.ts +0 -4
- package/src/agents/metadata-assistant-agent.ts +0 -87
- package/src/ai-service.ts +0 -364
- package/src/conversation/in-memory-conversation-service.ts +0 -103
- package/src/conversation/index.ts +0 -4
- package/src/conversation/objectql-conversation-service.ts +0 -301
- package/src/index.ts +0 -60
- package/src/objects/ai-conversation.object.ts +0 -86
- package/src/objects/ai-message.object.ts +0 -86
- package/src/objects/index.ts +0 -10
- package/src/plugin.ts +0 -391
- package/src/routes/agent-routes.ts +0 -190
- package/src/routes/ai-routes.ts +0 -439
- package/src/routes/index.ts +0 -5
- package/src/routes/message-utils.ts +0 -90
- package/src/routes/tool-routes.ts +0 -142
- package/src/stream/index.ts +0 -3
- package/src/stream/vercel-stream-encoder.ts +0 -153
- package/src/tools/add-field.tool.ts +0 -70
- package/src/tools/create-object.tool.ts +0 -66
- package/src/tools/data-tools.ts +0 -293
- package/src/tools/delete-field.tool.ts +0 -38
- package/src/tools/describe-object.tool.ts +0 -31
- package/src/tools/index.ts +0 -18
- package/src/tools/list-objects.tool.ts +0 -34
- package/src/tools/metadata-tools.ts +0 -430
- package/src/tools/modify-field.tool.ts +0 -44
- package/src/tools/tool-registry.ts +0 -132
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -23
package/dist/index.cjs
CHANGED
|
@@ -256,6 +256,10 @@ var init_create_object_tool = __esm({
|
|
|
256
256
|
type: "string",
|
|
257
257
|
description: "Human-readable display name (e.g. Project Task)"
|
|
258
258
|
},
|
|
259
|
+
packageId: {
|
|
260
|
+
type: "string",
|
|
261
|
+
description: "Package ID that will own this object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
262
|
+
},
|
|
259
263
|
fields: {
|
|
260
264
|
type: "array",
|
|
261
265
|
description: "Initial fields to create with the object",
|
|
@@ -305,6 +309,10 @@ var init_add_field_tool = __esm({
|
|
|
305
309
|
parameters: {
|
|
306
310
|
type: "object",
|
|
307
311
|
properties: {
|
|
312
|
+
packageId: {
|
|
313
|
+
type: "string",
|
|
314
|
+
description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
315
|
+
},
|
|
308
316
|
objectName: {
|
|
309
317
|
type: "string",
|
|
310
318
|
description: "Target object machine name (snake_case)"
|
|
@@ -371,6 +379,10 @@ var init_modify_field_tool = __esm({
|
|
|
371
379
|
parameters: {
|
|
372
380
|
type: "object",
|
|
373
381
|
properties: {
|
|
382
|
+
packageId: {
|
|
383
|
+
type: "string",
|
|
384
|
+
description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
385
|
+
},
|
|
374
386
|
objectName: {
|
|
375
387
|
type: "string",
|
|
376
388
|
description: "Target object machine name (snake_case)"
|
|
@@ -416,6 +428,10 @@ var init_delete_field_tool = __esm({
|
|
|
416
428
|
parameters: {
|
|
417
429
|
type: "object",
|
|
418
430
|
properties: {
|
|
431
|
+
packageId: {
|
|
432
|
+
type: "string",
|
|
433
|
+
description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
434
|
+
},
|
|
419
435
|
objectName: {
|
|
420
436
|
type: "string",
|
|
421
437
|
description: "Target object machine name (snake_case)"
|
|
@@ -504,12 +520,55 @@ __export(metadata_tools_exports, {
|
|
|
504
520
|
function isSnakeCase(value) {
|
|
505
521
|
return SNAKE_CASE_RE.test(value);
|
|
506
522
|
}
|
|
523
|
+
async function getActivePackageId(ctx) {
|
|
524
|
+
if (!ctx.conversationService?.getMetadata || !ctx.conversationId) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
const metadata = await ctx.conversationService.getMetadata(ctx.conversationId);
|
|
528
|
+
return metadata?.activePackageId ?? null;
|
|
529
|
+
}
|
|
530
|
+
async function resolvePackageId(ctx, explicitPackageId) {
|
|
531
|
+
let packageId = null;
|
|
532
|
+
if (explicitPackageId) {
|
|
533
|
+
packageId = explicitPackageId;
|
|
534
|
+
} else {
|
|
535
|
+
packageId = await getActivePackageId(ctx);
|
|
536
|
+
}
|
|
537
|
+
if (!packageId) {
|
|
538
|
+
return {
|
|
539
|
+
packageId: null,
|
|
540
|
+
warning: "No package specified. Metadata will be created without package association. Consider using set_active_package or providing packageId parameter."
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
if (ctx.packageRegistry) {
|
|
544
|
+
const exists = await ctx.packageRegistry.exists(packageId);
|
|
545
|
+
if (!exists) {
|
|
546
|
+
return {
|
|
547
|
+
packageId: null,
|
|
548
|
+
error: `Package "${packageId}" not found. Use list_packages to see available packages or create_package to create a new one.`
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
const pkg = await ctx.packageRegistry.get(packageId);
|
|
552
|
+
if (pkg?.manifest.source === "filesystem") {
|
|
553
|
+
return {
|
|
554
|
+
packageId: null,
|
|
555
|
+
error: `Package "${packageId}" is read-only (loaded from code). Only database packages can be modified. Use create_package to create a new database package.`
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return { packageId };
|
|
560
|
+
}
|
|
507
561
|
function createCreateObjectHandler(ctx) {
|
|
508
562
|
return async (args) => {
|
|
509
|
-
const { name, label, fields, enableFeatures } = args;
|
|
563
|
+
const { name, label, packageId: explicitPackageId, fields, enableFeatures } = args;
|
|
510
564
|
if (!name || !label) {
|
|
511
565
|
return JSON.stringify({ error: 'Both "name" and "label" are required' });
|
|
512
566
|
}
|
|
567
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
568
|
+
if (resolved.error) {
|
|
569
|
+
return JSON.stringify({ error: resolved.error });
|
|
570
|
+
}
|
|
571
|
+
const packageId = resolved.packageId;
|
|
513
572
|
if (!isSnakeCase(name)) {
|
|
514
573
|
return JSON.stringify({ error: `Invalid object name "${name}". Must be snake_case.` });
|
|
515
574
|
}
|
|
@@ -541,6 +600,7 @@ function createCreateObjectHandler(ctx) {
|
|
|
541
600
|
const objectDef = {
|
|
542
601
|
name,
|
|
543
602
|
label,
|
|
603
|
+
...packageId ? { packageId } : {},
|
|
544
604
|
...Object.keys(fieldMap).length > 0 ? { fields: fieldMap } : {},
|
|
545
605
|
...enableFeatures ? { enable: enableFeatures } : {}
|
|
546
606
|
};
|
|
@@ -548,16 +608,21 @@ function createCreateObjectHandler(ctx) {
|
|
|
548
608
|
return JSON.stringify({
|
|
549
609
|
name,
|
|
550
610
|
label,
|
|
611
|
+
...packageId ? { packageId } : {},
|
|
551
612
|
fieldCount: Object.keys(fieldMap).length
|
|
552
613
|
});
|
|
553
614
|
};
|
|
554
615
|
}
|
|
555
616
|
function createAddFieldHandler(ctx) {
|
|
556
617
|
return async (args) => {
|
|
557
|
-
const { objectName, name, label, type, required, defaultValue, options, reference } = args;
|
|
618
|
+
const { objectName, name, label, type, required, defaultValue, options, reference, packageId: explicitPackageId } = args;
|
|
558
619
|
if (!objectName || !name || !type) {
|
|
559
620
|
return JSON.stringify({ error: '"objectName", "name", and "type" are required' });
|
|
560
621
|
}
|
|
622
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
623
|
+
if (resolved.error) {
|
|
624
|
+
return JSON.stringify({ error: resolved.error });
|
|
625
|
+
}
|
|
561
626
|
if (!isSnakeCase(objectName)) {
|
|
562
627
|
return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
|
|
563
628
|
}
|
|
@@ -598,16 +663,21 @@ function createAddFieldHandler(ctx) {
|
|
|
598
663
|
return JSON.stringify({
|
|
599
664
|
objectName,
|
|
600
665
|
fieldName: name,
|
|
601
|
-
fieldType: type
|
|
666
|
+
fieldType: type,
|
|
667
|
+
packageId: resolved.packageId
|
|
602
668
|
});
|
|
603
669
|
};
|
|
604
670
|
}
|
|
605
671
|
function createModifyFieldHandler(ctx) {
|
|
606
672
|
return async (args) => {
|
|
607
|
-
const { objectName, fieldName, changes } = args;
|
|
673
|
+
const { objectName, fieldName, changes, packageId: explicitPackageId } = args;
|
|
608
674
|
if (!objectName || !fieldName || !changes) {
|
|
609
675
|
return JSON.stringify({ error: '"objectName", "fieldName", and "changes" are required' });
|
|
610
676
|
}
|
|
677
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
678
|
+
if (resolved.error) {
|
|
679
|
+
return JSON.stringify({ error: resolved.error });
|
|
680
|
+
}
|
|
611
681
|
if (!isSnakeCase(objectName)) {
|
|
612
682
|
return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
|
|
613
683
|
}
|
|
@@ -632,16 +702,21 @@ function createModifyFieldHandler(ctx) {
|
|
|
632
702
|
return JSON.stringify({
|
|
633
703
|
objectName,
|
|
634
704
|
fieldName,
|
|
635
|
-
updatedProperties: Object.keys(changes)
|
|
705
|
+
updatedProperties: Object.keys(changes),
|
|
706
|
+
packageId: resolved.packageId
|
|
636
707
|
});
|
|
637
708
|
};
|
|
638
709
|
}
|
|
639
710
|
function createDeleteFieldHandler(ctx) {
|
|
640
711
|
return async (args) => {
|
|
641
|
-
const { objectName, fieldName } = args;
|
|
712
|
+
const { objectName, fieldName, packageId: explicitPackageId } = args;
|
|
642
713
|
if (!objectName || !fieldName) {
|
|
643
714
|
return JSON.stringify({ error: '"objectName" and "fieldName" are required' });
|
|
644
715
|
}
|
|
716
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
717
|
+
if (resolved.error) {
|
|
718
|
+
return JSON.stringify({ error: resolved.error });
|
|
719
|
+
}
|
|
645
720
|
if (!isSnakeCase(objectName)) {
|
|
646
721
|
return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
|
|
647
722
|
}
|
|
@@ -664,7 +739,8 @@ function createDeleteFieldHandler(ctx) {
|
|
|
664
739
|
return JSON.stringify({
|
|
665
740
|
objectName,
|
|
666
741
|
fieldName,
|
|
667
|
-
success: true
|
|
742
|
+
success: true,
|
|
743
|
+
packageId: resolved.packageId
|
|
668
744
|
});
|
|
669
745
|
};
|
|
670
746
|
}
|
|
@@ -781,21 +857,30 @@ __export(index_exports, {
|
|
|
781
857
|
METADATA_TOOL_DEFINITIONS: () => METADATA_TOOL_DEFINITIONS,
|
|
782
858
|
MemoryLLMAdapter: () => MemoryLLMAdapter,
|
|
783
859
|
ObjectQLConversationService: () => ObjectQLConversationService,
|
|
860
|
+
PACKAGE_TOOL_DEFINITIONS: () => PACKAGE_TOOL_DEFINITIONS,
|
|
861
|
+
SkillRegistry: () => SkillRegistry,
|
|
784
862
|
ToolRegistry: () => ToolRegistry,
|
|
785
863
|
VercelLLMAdapter: () => VercelLLMAdapter,
|
|
786
864
|
addFieldTool: () => addFieldTool,
|
|
787
865
|
buildAIRoutes: () => buildAIRoutes,
|
|
788
866
|
buildAgentRoutes: () => buildAgentRoutes,
|
|
867
|
+
buildAssistantRoutes: () => buildAssistantRoutes,
|
|
789
868
|
buildToolRoutes: () => buildToolRoutes,
|
|
790
869
|
createObjectTool: () => createObjectTool,
|
|
870
|
+
createPackageTool: () => createPackageTool,
|
|
791
871
|
deleteFieldTool: () => deleteFieldTool,
|
|
792
872
|
describeObjectTool: () => describeObjectTool,
|
|
793
873
|
encodeStreamPart: () => encodeStreamPart,
|
|
794
874
|
encodeVercelDataStream: () => encodeVercelDataStream,
|
|
875
|
+
getActivePackageTool: () => getActivePackageTool,
|
|
876
|
+
getPackageTool: () => getPackageTool,
|
|
795
877
|
listObjectsTool: () => listObjectsTool,
|
|
878
|
+
listPackagesTool: () => listPackagesTool,
|
|
796
879
|
modifyFieldTool: () => modifyFieldTool,
|
|
797
880
|
registerDataTools: () => registerDataTools,
|
|
798
|
-
registerMetadataTools: () => registerMetadataTools
|
|
881
|
+
registerMetadataTools: () => registerMetadataTools,
|
|
882
|
+
registerPackageTools: () => registerPackageTools,
|
|
883
|
+
setActivePackageTool: () => setActivePackageTool
|
|
799
884
|
});
|
|
800
885
|
module.exports = __toCommonJS(index_exports);
|
|
801
886
|
|
|
@@ -1310,25 +1395,41 @@ async function* encodeVercelDataStream(events) {
|
|
|
1310
1395
|
yield sse({ type: "text-start", id: "0" });
|
|
1311
1396
|
let textOpen = true;
|
|
1312
1397
|
let finishReason = "stop";
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1398
|
+
let errorMessage;
|
|
1399
|
+
try {
|
|
1400
|
+
for await (const part of events) {
|
|
1401
|
+
if (part.type === "error") {
|
|
1402
|
+
const errPart = part;
|
|
1403
|
+
const raw = errPart.error;
|
|
1404
|
+
errorMessage = raw && typeof raw === "object" && "message" in raw ? String(raw.message) : typeof raw === "string" ? raw : "Unknown provider error";
|
|
1405
|
+
finishReason = "error";
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
if (part.type === "finish") {
|
|
1409
|
+
finishReason = part.finishReason ?? "stop";
|
|
1410
|
+
}
|
|
1411
|
+
if (part.type === "finish-step" || part.type === "finish") {
|
|
1412
|
+
if (textOpen) {
|
|
1413
|
+
yield sse({ type: "text-end", id: "0" });
|
|
1414
|
+
textOpen = false;
|
|
1415
|
+
}
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
const frame = encodeStreamPart(part);
|
|
1419
|
+
if (frame) {
|
|
1420
|
+
yield frame;
|
|
1321
1421
|
}
|
|
1322
|
-
continue;
|
|
1323
|
-
}
|
|
1324
|
-
const frame = encodeStreamPart(part);
|
|
1325
|
-
if (frame) {
|
|
1326
|
-
yield frame;
|
|
1327
1422
|
}
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
errorMessage = err instanceof Error ? err.message : String(err);
|
|
1425
|
+
finishReason = "error";
|
|
1328
1426
|
}
|
|
1329
1427
|
if (textOpen) {
|
|
1330
1428
|
yield sse({ type: "text-end", id: "0" });
|
|
1331
1429
|
}
|
|
1430
|
+
if (errorMessage) {
|
|
1431
|
+
yield sse({ type: "error", errorText: errorMessage });
|
|
1432
|
+
}
|
|
1332
1433
|
yield sse({ type: "finish-step" });
|
|
1333
1434
|
yield sse({ type: "finish", finishReason });
|
|
1334
1435
|
yield "data: [DONE]\n\n";
|
|
@@ -1732,10 +1833,12 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
|
|
|
1732
1833
|
return { status: 403, body: { error: `Agent "${agentName}" is not active` } };
|
|
1733
1834
|
}
|
|
1734
1835
|
try {
|
|
1735
|
-
const
|
|
1836
|
+
const activeSkills = await agentRuntime.resolveActiveSkills(agent, chatContext);
|
|
1837
|
+
const systemMessages = agentRuntime.buildSystemMessages(agent, chatContext, activeSkills);
|
|
1736
1838
|
const agentOptions = agentRuntime.buildRequestOptions(
|
|
1737
1839
|
agent,
|
|
1738
|
-
aiService.toolRegistry.getAll()
|
|
1840
|
+
aiService.toolRegistry.getAll(),
|
|
1841
|
+
activeSkills
|
|
1739
1842
|
);
|
|
1740
1843
|
const safeOverrides = {};
|
|
1741
1844
|
if (extraOptions) {
|
|
@@ -1789,6 +1892,217 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
|
|
|
1789
1892
|
];
|
|
1790
1893
|
}
|
|
1791
1894
|
|
|
1895
|
+
// src/routes/assistant-routes.ts
|
|
1896
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
1897
|
+
function validateAssistantMessage(raw) {
|
|
1898
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1899
|
+
return "each message must be an object";
|
|
1900
|
+
}
|
|
1901
|
+
const msg = raw;
|
|
1902
|
+
if (typeof msg.role !== "string" || !ALLOWED_ROLES.has(msg.role)) {
|
|
1903
|
+
return `message.role must be one of ${[...ALLOWED_ROLES].map((r) => `"${r}"`).join(", ")} for assistant chat`;
|
|
1904
|
+
}
|
|
1905
|
+
const allowEmpty = msg.role === "assistant";
|
|
1906
|
+
return validateMessageContent(msg, { allowEmptyContent: allowEmpty });
|
|
1907
|
+
}
|
|
1908
|
+
function parseContext(raw) {
|
|
1909
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
1910
|
+
const obj = raw;
|
|
1911
|
+
const ctx = {};
|
|
1912
|
+
for (const key of Object.keys(obj)) {
|
|
1913
|
+
ctx[key] = obj[key];
|
|
1914
|
+
}
|
|
1915
|
+
return ctx;
|
|
1916
|
+
}
|
|
1917
|
+
function buildAssistantRoutes(aiService, agentRuntime, skillRegistry, logger) {
|
|
1918
|
+
return [
|
|
1919
|
+
// ── Resolve current assistant + skill set ──────────────────
|
|
1920
|
+
{
|
|
1921
|
+
method: "GET",
|
|
1922
|
+
path: "/api/v1/ai/assistant",
|
|
1923
|
+
description: "Resolve the default AI assistant and active skills for a given context",
|
|
1924
|
+
auth: true,
|
|
1925
|
+
permissions: ["ai:chat"],
|
|
1926
|
+
handler: async (req) => {
|
|
1927
|
+
try {
|
|
1928
|
+
const context = parseContextFromQuery(req.query);
|
|
1929
|
+
const explicitAgentName = typeof req.query?.agent === "string" ? req.query.agent : void 0;
|
|
1930
|
+
const agent = explicitAgentName ? await agentRuntime.loadAgent(explicitAgentName) : await agentRuntime.resolveDefaultAgent(context);
|
|
1931
|
+
if (!agent) {
|
|
1932
|
+
return {
|
|
1933
|
+
status: 200,
|
|
1934
|
+
body: { agent: null, skills: [] }
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
const skills = await agentRuntime.resolveActiveSkills(agent, context);
|
|
1938
|
+
return {
|
|
1939
|
+
status: 200,
|
|
1940
|
+
body: {
|
|
1941
|
+
agent: {
|
|
1942
|
+
name: agent.name,
|
|
1943
|
+
label: agent.label,
|
|
1944
|
+
role: agent.role,
|
|
1945
|
+
avatar: agent.avatar,
|
|
1946
|
+
instructions: agent.instructions
|
|
1947
|
+
},
|
|
1948
|
+
skills: skills.map((s) => skillRegistry.toSummary(s)),
|
|
1949
|
+
context
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
} catch (err) {
|
|
1953
|
+
logger.error("[AI Route] /assistant error", err instanceof Error ? err : void 0);
|
|
1954
|
+
return { status: 500, body: { error: "Internal AI service error" } };
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
// ── List active skills (slash-command palette) ─────────────
|
|
1959
|
+
{
|
|
1960
|
+
method: "GET",
|
|
1961
|
+
path: "/api/v1/ai/assistant/skills",
|
|
1962
|
+
description: "List active AI skills for a given context (used by slash-command palettes)",
|
|
1963
|
+
auth: true,
|
|
1964
|
+
permissions: ["ai:chat"],
|
|
1965
|
+
handler: async (req) => {
|
|
1966
|
+
try {
|
|
1967
|
+
const context = parseContextFromQuery(req.query);
|
|
1968
|
+
const agentName = typeof req.query?.agent === "string" ? req.query.agent : void 0;
|
|
1969
|
+
let restrictTo;
|
|
1970
|
+
if (agentName) {
|
|
1971
|
+
const agent = await agentRuntime.loadAgent(agentName);
|
|
1972
|
+
if (agent?.skills) restrictTo = agent.skills;
|
|
1973
|
+
}
|
|
1974
|
+
const skills = await skillRegistry.listActiveSkills(context, restrictTo);
|
|
1975
|
+
return {
|
|
1976
|
+
status: 200,
|
|
1977
|
+
body: { skills: skills.map((s) => skillRegistry.toSummary(s)) }
|
|
1978
|
+
};
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
logger.error("[AI Route] /assistant/skills error", err instanceof Error ? err : void 0);
|
|
1981
|
+
return { status: 500, body: { error: "Internal AI service error" } };
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
},
|
|
1985
|
+
// ── Ambient chat (the "single entry" Claude-Code pattern) ──
|
|
1986
|
+
{
|
|
1987
|
+
method: "POST",
|
|
1988
|
+
path: "/api/v1/ai/assistant/chat",
|
|
1989
|
+
description: "Ambient AI chat \u2014 auto-resolves agent and skills from context (supports Vercel Data Stream Protocol)",
|
|
1990
|
+
auth: true,
|
|
1991
|
+
permissions: ["ai:chat"],
|
|
1992
|
+
handler: async (req) => {
|
|
1993
|
+
const body = req.body ?? {};
|
|
1994
|
+
const {
|
|
1995
|
+
messages: rawMessages,
|
|
1996
|
+
context: rawContext,
|
|
1997
|
+
options: extraOptions,
|
|
1998
|
+
agent: explicitAgentName,
|
|
1999
|
+
skill: explicitSkillName
|
|
2000
|
+
} = body;
|
|
2001
|
+
if (!Array.isArray(rawMessages) || rawMessages.length === 0) {
|
|
2002
|
+
return { status: 400, body: { error: "messages array is required" } };
|
|
2003
|
+
}
|
|
2004
|
+
for (const msg of rawMessages) {
|
|
2005
|
+
const err = validateAssistantMessage(msg);
|
|
2006
|
+
if (err) return { status: 400, body: { error: err } };
|
|
2007
|
+
}
|
|
2008
|
+
const context = parseContext(rawContext);
|
|
2009
|
+
const agent = explicitAgentName ? await agentRuntime.loadAgent(explicitAgentName) : await agentRuntime.resolveDefaultAgent(context);
|
|
2010
|
+
if (!agent) {
|
|
2011
|
+
return {
|
|
2012
|
+
status: 404,
|
|
2013
|
+
body: { error: "No active assistant available \u2014 register at least one agent or set defaultAgent on the app metadata" }
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
if (agent.active === false) {
|
|
2017
|
+
return { status: 403, body: { error: `Agent "${agent.name}" is not active` } };
|
|
2018
|
+
}
|
|
2019
|
+
try {
|
|
2020
|
+
let activeSkills = await agentRuntime.resolveActiveSkills(agent, context);
|
|
2021
|
+
if (explicitSkillName) {
|
|
2022
|
+
activeSkills = activeSkills.filter((s) => s.name === explicitSkillName);
|
|
2023
|
+
if (activeSkills.length === 0) {
|
|
2024
|
+
const direct = await skillRegistry.loadSkill(explicitSkillName);
|
|
2025
|
+
if (direct && direct.active !== false) activeSkills = [direct];
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
const systemMessages = agentRuntime.buildSystemMessages(agent, context, activeSkills);
|
|
2029
|
+
const agentOptions = agentRuntime.buildRequestOptions(
|
|
2030
|
+
agent,
|
|
2031
|
+
aiService.toolRegistry.getAll(),
|
|
2032
|
+
activeSkills
|
|
2033
|
+
);
|
|
2034
|
+
const safeOverrides = {};
|
|
2035
|
+
if (extraOptions) {
|
|
2036
|
+
const ALLOWED_KEYS = /* @__PURE__ */ new Set(["temperature", "maxTokens", "stop"]);
|
|
2037
|
+
for (const key of Object.keys(extraOptions)) {
|
|
2038
|
+
if (ALLOWED_KEYS.has(key)) safeOverrides[key] = extraOptions[key];
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
const mergedOptions = { ...agentOptions, ...safeOverrides };
|
|
2042
|
+
const fullMessages = [
|
|
2043
|
+
...systemMessages,
|
|
2044
|
+
...rawMessages.map((m) => normalizeMessage(m))
|
|
2045
|
+
];
|
|
2046
|
+
const chatWithToolsOptions = {
|
|
2047
|
+
...mergedOptions,
|
|
2048
|
+
maxIterations: agent.planning?.maxIterations
|
|
2049
|
+
};
|
|
2050
|
+
const wantStream = body.stream !== false;
|
|
2051
|
+
if (wantStream) {
|
|
2052
|
+
if (!aiService.streamChatWithTools) {
|
|
2053
|
+
return { status: 501, body: { error: "Streaming is not supported by the configured AI service" } };
|
|
2054
|
+
}
|
|
2055
|
+
const events = aiService.streamChatWithTools(fullMessages, chatWithToolsOptions);
|
|
2056
|
+
return {
|
|
2057
|
+
status: 200,
|
|
2058
|
+
stream: true,
|
|
2059
|
+
vercelDataStream: true,
|
|
2060
|
+
contentType: "text/event-stream",
|
|
2061
|
+
headers: {
|
|
2062
|
+
"Content-Type": "text/event-stream",
|
|
2063
|
+
"Cache-Control": "no-cache",
|
|
2064
|
+
"Connection": "keep-alive",
|
|
2065
|
+
"x-vercel-ai-ui-message-stream": "v1",
|
|
2066
|
+
"x-objectstack-agent": agent.name,
|
|
2067
|
+
"x-objectstack-skills": activeSkills.map((s) => s.name).join(",")
|
|
2068
|
+
},
|
|
2069
|
+
events: encodeVercelDataStream(events)
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
const result = await aiService.chatWithTools(fullMessages, chatWithToolsOptions);
|
|
2073
|
+
return {
|
|
2074
|
+
status: 200,
|
|
2075
|
+
body: {
|
|
2076
|
+
...result ?? {},
|
|
2077
|
+
_agent: agent.name,
|
|
2078
|
+
_skills: activeSkills.map((s) => s.name)
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
logger.error(
|
|
2083
|
+
"[AI Route] /assistant/chat error",
|
|
2084
|
+
err instanceof Error ? err : void 0
|
|
2085
|
+
);
|
|
2086
|
+
const upstreamMsg = err instanceof Error ? err.message : String(err);
|
|
2087
|
+
return {
|
|
2088
|
+
status: 500,
|
|
2089
|
+
body: { error: "Internal AI service error", detail: upstreamMsg }
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
];
|
|
2095
|
+
}
|
|
2096
|
+
function parseContextFromQuery(query) {
|
|
2097
|
+
if (!query) return {};
|
|
2098
|
+
const ctx = {};
|
|
2099
|
+
for (const [key, value] of Object.entries(query)) {
|
|
2100
|
+
if (value == null) continue;
|
|
2101
|
+
ctx[key] = value;
|
|
2102
|
+
}
|
|
2103
|
+
return ctx;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1792
2106
|
// src/routes/tool-routes.ts
|
|
1793
2107
|
function extractOutputValue(output) {
|
|
1794
2108
|
if (!output) return "";
|
|
@@ -2104,8 +2418,7 @@ var ObjectQLConversationService = class {
|
|
|
2104
2418
|
// src/objects/ai-conversation.object.ts
|
|
2105
2419
|
var import_data = require("@objectstack/spec/data");
|
|
2106
2420
|
var AiConversationObject = import_data.ObjectSchema.create({
|
|
2107
|
-
|
|
2108
|
-
name: "conversations",
|
|
2421
|
+
name: "ai_conversations",
|
|
2109
2422
|
label: "AI Conversation",
|
|
2110
2423
|
pluralLabel: "AI Conversations",
|
|
2111
2424
|
icon: "message-square",
|
|
@@ -2124,15 +2437,14 @@ var AiConversationObject = import_data.ObjectSchema.create({
|
|
|
2124
2437
|
description: "Conversation title or summary"
|
|
2125
2438
|
}),
|
|
2126
2439
|
agent_id: import_data.Field.text({
|
|
2127
|
-
label: "Agent
|
|
2440
|
+
label: "Agent",
|
|
2128
2441
|
required: false,
|
|
2129
|
-
maxLength:
|
|
2130
|
-
description: "Associated AI agent
|
|
2442
|
+
maxLength: 128,
|
|
2443
|
+
description: "Associated AI agent (metadata name \u2014 agents live as JSON in sys_metadata, no lookup table)"
|
|
2131
2444
|
}),
|
|
2132
|
-
user_id: import_data.Field.
|
|
2133
|
-
label: "User
|
|
2445
|
+
user_id: import_data.Field.lookup("sys_user", {
|
|
2446
|
+
label: "User",
|
|
2134
2447
|
required: false,
|
|
2135
|
-
maxLength: 255,
|
|
2136
2448
|
description: "User who owns the conversation"
|
|
2137
2449
|
}),
|
|
2138
2450
|
metadata: import_data.Field.textarea({
|
|
@@ -2171,8 +2483,7 @@ var AiConversationObject = import_data.ObjectSchema.create({
|
|
|
2171
2483
|
// src/objects/ai-message.object.ts
|
|
2172
2484
|
var import_data2 = require("@objectstack/spec/data");
|
|
2173
2485
|
var AiMessageObject = import_data2.ObjectSchema.create({
|
|
2174
|
-
|
|
2175
|
-
name: "messages",
|
|
2486
|
+
name: "ai_messages",
|
|
2176
2487
|
label: "AI Message",
|
|
2177
2488
|
pluralLabel: "AI Messages",
|
|
2178
2489
|
icon: "message-circle",
|
|
@@ -2184,8 +2495,8 @@ var AiMessageObject = import_data2.ObjectSchema.create({
|
|
|
2184
2495
|
required: true,
|
|
2185
2496
|
readonly: true
|
|
2186
2497
|
}),
|
|
2187
|
-
conversation_id: import_data2.Field.
|
|
2188
|
-
label: "Conversation
|
|
2498
|
+
conversation_id: import_data2.Field.lookup("ai_conversations", {
|
|
2499
|
+
label: "Conversation",
|
|
2189
2500
|
required: true,
|
|
2190
2501
|
description: "Foreign key to ai_conversations"
|
|
2191
2502
|
}),
|
|
@@ -2243,8 +2554,9 @@ init_metadata_tools();
|
|
|
2243
2554
|
// src/agent-runtime.ts
|
|
2244
2555
|
var import_ai7 = require("@objectstack/spec/ai");
|
|
2245
2556
|
var AgentRuntime = class {
|
|
2246
|
-
constructor(metadataService) {
|
|
2557
|
+
constructor(metadataService, skillRegistry) {
|
|
2247
2558
|
this.metadataService = metadataService;
|
|
2559
|
+
this.skillRegistry = skillRegistry;
|
|
2248
2560
|
}
|
|
2249
2561
|
// ── Public API ────────────────────────────────────────────────
|
|
2250
2562
|
/**
|
|
@@ -2288,12 +2600,21 @@ var AgentRuntime = class {
|
|
|
2288
2600
|
/**
|
|
2289
2601
|
* Build the system message(s) that should be prepended to the
|
|
2290
2602
|
* conversation when chatting with the given agent.
|
|
2603
|
+
*
|
|
2604
|
+
* The composed prompt has up to three sections:
|
|
2605
|
+
* 1. The agent's base `instructions` (its persona / prime directives).
|
|
2606
|
+
* 2. UI context hints from {@link AgentChatContext} (current object,
|
|
2607
|
+
* record, view) so the agent can tailor responses without extra
|
|
2608
|
+
* tool calls.
|
|
2609
|
+
* 3. An "Active Skills" block describing the capabilities currently
|
|
2610
|
+
* available — only populated when `activeSkills` is provided.
|
|
2291
2611
|
*/
|
|
2292
|
-
buildSystemMessages(agent, context) {
|
|
2612
|
+
buildSystemMessages(agent, context, activeSkills) {
|
|
2293
2613
|
const parts = [];
|
|
2294
2614
|
parts.push(agent.instructions);
|
|
2295
2615
|
if (context) {
|
|
2296
2616
|
const ctx = [];
|
|
2617
|
+
if (context.appName) ctx.push(`Current app: ${context.appName}`);
|
|
2297
2618
|
if (context.objectName) ctx.push(`Current object: ${context.objectName}`);
|
|
2298
2619
|
if (context.recordId) ctx.push(`Selected record ID: ${context.recordId}`);
|
|
2299
2620
|
if (context.viewName) ctx.push(`Current view: ${context.viewName}`);
|
|
@@ -2301,6 +2622,10 @@ var AgentRuntime = class {
|
|
|
2301
2622
|
parts.push("\n--- Current Context ---\n" + ctx.join("\n"));
|
|
2302
2623
|
}
|
|
2303
2624
|
}
|
|
2625
|
+
if (activeSkills && activeSkills.length > 0 && this.skillRegistry) {
|
|
2626
|
+
const block = this.skillRegistry.composeInstructionsBlock(activeSkills);
|
|
2627
|
+
if (block) parts.push(block);
|
|
2628
|
+
}
|
|
2304
2629
|
return [{ role: "system", content: parts.join("\n") }];
|
|
2305
2630
|
}
|
|
2306
2631
|
/**
|
|
@@ -2308,37 +2633,272 @@ var AgentRuntime = class {
|
|
|
2308
2633
|
*
|
|
2309
2634
|
* Tool references declared in `agent.tools` are resolved by name against
|
|
2310
2635
|
* `availableTools` (i.e. the full set of ToolRegistry definitions).
|
|
2311
|
-
*
|
|
2312
|
-
*
|
|
2313
|
-
*
|
|
2636
|
+
* Tools belonging to `activeSkills` are also resolved and merged into
|
|
2637
|
+
* the final tool list (deduplicated by name).
|
|
2638
|
+
*
|
|
2639
|
+
* Any unresolved references (tools the agent or skill declares but
|
|
2640
|
+
* that are not registered) are silently skipped — this is intentional
|
|
2641
|
+
* so that agents/skills can be defined before all tools are available.
|
|
2314
2642
|
*
|
|
2315
2643
|
* @param agent - The agent definition to derive options from
|
|
2316
2644
|
* @param availableTools - All tool definitions currently registered in the ToolRegistry
|
|
2645
|
+
* @param activeSkills - Skills resolved from agent.skills[] + context filtering
|
|
2317
2646
|
* @returns Request options with model config and resolved tool definitions
|
|
2318
2647
|
*/
|
|
2319
|
-
buildRequestOptions(agent, availableTools) {
|
|
2648
|
+
buildRequestOptions(agent, availableTools, activeSkills) {
|
|
2320
2649
|
const options = {};
|
|
2321
2650
|
if (agent.model) {
|
|
2322
2651
|
options.model = agent.model.model;
|
|
2323
2652
|
options.temperature = agent.model.temperature;
|
|
2324
2653
|
options.maxTokens = agent.model.maxTokens;
|
|
2325
2654
|
}
|
|
2655
|
+
const toolMap = new Map(availableTools.map((t) => [t.name, t]));
|
|
2656
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2657
|
+
const resolved = [];
|
|
2326
2658
|
if (agent.tools && agent.tools.length > 0) {
|
|
2327
|
-
const toolMap = new Map(availableTools.map((t) => [t.name, t]));
|
|
2328
|
-
const resolved = [];
|
|
2329
2659
|
for (const ref of agent.tools) {
|
|
2660
|
+
if (seen.has(ref.name)) continue;
|
|
2330
2661
|
const def = toolMap.get(ref.name);
|
|
2331
2662
|
if (def) {
|
|
2332
2663
|
resolved.push(def);
|
|
2664
|
+
seen.add(ref.name);
|
|
2333
2665
|
}
|
|
2334
2666
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2667
|
+
}
|
|
2668
|
+
if (activeSkills && activeSkills.length > 0 && this.skillRegistry) {
|
|
2669
|
+
const skillTools = this.skillRegistry.flattenToTools(activeSkills, availableTools);
|
|
2670
|
+
for (const def of skillTools) {
|
|
2671
|
+
if (seen.has(def.name)) continue;
|
|
2672
|
+
resolved.push(def);
|
|
2673
|
+
seen.add(def.name);
|
|
2338
2674
|
}
|
|
2339
2675
|
}
|
|
2676
|
+
if (resolved.length > 0) {
|
|
2677
|
+
options.tools = resolved;
|
|
2678
|
+
options.toolChoice = "auto";
|
|
2679
|
+
}
|
|
2340
2680
|
return options;
|
|
2341
2681
|
}
|
|
2682
|
+
// ── Skill resolution helpers ─────────────────────────────────
|
|
2683
|
+
/**
|
|
2684
|
+
* Resolve the set of skills active for a given agent in a given
|
|
2685
|
+
* context. Combines:
|
|
2686
|
+
*
|
|
2687
|
+
* 1. The agent's declared `skills[]` whitelist (if any).
|
|
2688
|
+
* 2. Filtering by `triggerConditions` against the runtime context.
|
|
2689
|
+
*
|
|
2690
|
+
* When the agent declares no skills, returns the empty list (i.e.
|
|
2691
|
+
* the agent only uses its inline `tools[]`).
|
|
2692
|
+
*
|
|
2693
|
+
* Returns an empty array if no SkillRegistry was provided to the
|
|
2694
|
+
* runtime (legacy mode).
|
|
2695
|
+
*/
|
|
2696
|
+
async resolveActiveSkills(agent, context) {
|
|
2697
|
+
if (!this.skillRegistry) return [];
|
|
2698
|
+
if (!agent.skills || agent.skills.length === 0) return [];
|
|
2699
|
+
return this.skillRegistry.listActiveSkills(context ?? {}, agent.skills);
|
|
2700
|
+
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Pick a default agent for the given context, used by the ambient
|
|
2703
|
+
* chat endpoint when the client doesn't specify an `agentName`.
|
|
2704
|
+
*
|
|
2705
|
+
* Resolution order:
|
|
2706
|
+
* 1. The `defaultAgent` of the app named by `context.appName`.
|
|
2707
|
+
* 2. The first active agent in the registry (deterministic fallback).
|
|
2708
|
+
* 3. `undefined` if no agents are registered.
|
|
2709
|
+
*/
|
|
2710
|
+
async resolveDefaultAgent(context) {
|
|
2711
|
+
if (context?.appName) {
|
|
2712
|
+
const rawApp = await this.metadataService.get("app", context.appName).catch(() => void 0);
|
|
2713
|
+
const defaultAgentName = rawApp?.defaultAgent;
|
|
2714
|
+
if (defaultAgentName) {
|
|
2715
|
+
const agent = await this.loadAgent(defaultAgentName);
|
|
2716
|
+
if (agent && agent.active !== false) return agent;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
const summaries = await this.listAgents();
|
|
2720
|
+
if (summaries.length === 0) return void 0;
|
|
2721
|
+
return this.loadAgent(summaries[0].name);
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
|
|
2725
|
+
// src/skill-registry.ts
|
|
2726
|
+
var import_ai8 = require("@objectstack/spec/ai");
|
|
2727
|
+
var SkillRegistry = class {
|
|
2728
|
+
constructor(metadataService) {
|
|
2729
|
+
this.metadataService = metadataService;
|
|
2730
|
+
}
|
|
2731
|
+
// ── Loading ────────────────────────────────────────────────────
|
|
2732
|
+
/**
|
|
2733
|
+
* Load and validate a single skill definition by name.
|
|
2734
|
+
*
|
|
2735
|
+
* Returns `undefined` when the skill is missing or fails Zod
|
|
2736
|
+
* validation (so callers don't accidentally feed malformed metadata
|
|
2737
|
+
* to the LLM).
|
|
2738
|
+
*/
|
|
2739
|
+
async loadSkill(skillName) {
|
|
2740
|
+
const raw = await this.metadataService.get("skill", skillName);
|
|
2741
|
+
if (!raw) return void 0;
|
|
2742
|
+
const result = import_ai8.SkillSchema.safeParse(raw);
|
|
2743
|
+
if (!result.success) return void 0;
|
|
2744
|
+
return result.data;
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Load all skill definitions, dropping any that fail validation
|
|
2748
|
+
* or are explicitly inactive.
|
|
2749
|
+
*/
|
|
2750
|
+
async listSkills() {
|
|
2751
|
+
const raw = await this.metadataService.list("skill");
|
|
2752
|
+
const skills = [];
|
|
2753
|
+
for (const item of raw) {
|
|
2754
|
+
const result = import_ai8.SkillSchema.safeParse(item);
|
|
2755
|
+
if (result.success && result.data.active !== false) {
|
|
2756
|
+
skills.push(result.data);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
return skills;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Load only the skills referenced by `skillNames`, preserving
|
|
2763
|
+
* declaration order. Missing or invalid skill names are silently
|
|
2764
|
+
* dropped (logged at the route layer if needed) so an Agent can be
|
|
2765
|
+
* defined before all its skills are persisted.
|
|
2766
|
+
*/
|
|
2767
|
+
async loadSkills(skillNames) {
|
|
2768
|
+
const skills = [];
|
|
2769
|
+
for (const name of skillNames) {
|
|
2770
|
+
const skill = await this.loadSkill(name);
|
|
2771
|
+
if (skill && skill.active !== false) {
|
|
2772
|
+
skills.push(skill);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
return skills;
|
|
2776
|
+
}
|
|
2777
|
+
// ── Context filtering ──────────────────────────────────────────
|
|
2778
|
+
/**
|
|
2779
|
+
* Return skills whose `triggerConditions` are satisfied by the
|
|
2780
|
+
* given context. Skills without any conditions are always considered
|
|
2781
|
+
* active and returned in their declaration order.
|
|
2782
|
+
*
|
|
2783
|
+
* If `restrictTo` is provided, the result is intersected with that
|
|
2784
|
+
* allow-list (typically the agent's `skills[]` field) so an agent
|
|
2785
|
+
* never sees skills outside its declared scope.
|
|
2786
|
+
*/
|
|
2787
|
+
async listActiveSkills(context = {}, restrictTo) {
|
|
2788
|
+
const allowList = restrictTo ? new Set(restrictTo) : void 0;
|
|
2789
|
+
const all = await this.listSkills();
|
|
2790
|
+
return all.filter((skill) => {
|
|
2791
|
+
if (allowList && !allowList.has(skill.name)) return false;
|
|
2792
|
+
return this.matchesContext(skill, context);
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Evaluate a skill's `triggerConditions` against the given context.
|
|
2797
|
+
*
|
|
2798
|
+
* Semantics:
|
|
2799
|
+
* - No conditions defined → always matches.
|
|
2800
|
+
* - All conditions must pass (logical AND).
|
|
2801
|
+
* - Operators: `eq`, `neq`, `in`, `not_in`, `contains`.
|
|
2802
|
+
* - `contains` does substring matching for strings and `Array.includes`
|
|
2803
|
+
* for arrays.
|
|
2804
|
+
* - Missing context fields fail unless the operator is `neq` /
|
|
2805
|
+
* `not_in` (treating "absent" as "not equal to anything").
|
|
2806
|
+
*/
|
|
2807
|
+
matchesContext(skill, context) {
|
|
2808
|
+
const conditions = skill.triggerConditions;
|
|
2809
|
+
if (!conditions || conditions.length === 0) return true;
|
|
2810
|
+
return conditions.every((cond) => this.evaluateCondition(cond, context));
|
|
2811
|
+
}
|
|
2812
|
+
evaluateCondition(cond, context) {
|
|
2813
|
+
const fieldValue = context[cond.field];
|
|
2814
|
+
const expected = cond.value;
|
|
2815
|
+
switch (cond.operator) {
|
|
2816
|
+
case "eq":
|
|
2817
|
+
return fieldValue === expected;
|
|
2818
|
+
case "neq":
|
|
2819
|
+
return fieldValue !== expected;
|
|
2820
|
+
case "in": {
|
|
2821
|
+
const list = Array.isArray(expected) ? expected : [expected];
|
|
2822
|
+
return list.includes(fieldValue);
|
|
2823
|
+
}
|
|
2824
|
+
case "not_in": {
|
|
2825
|
+
const list = Array.isArray(expected) ? expected : [expected];
|
|
2826
|
+
return !list.includes(fieldValue);
|
|
2827
|
+
}
|
|
2828
|
+
case "contains": {
|
|
2829
|
+
if (typeof fieldValue === "string" && typeof expected === "string") {
|
|
2830
|
+
return fieldValue.includes(expected);
|
|
2831
|
+
}
|
|
2832
|
+
if (Array.isArray(fieldValue)) {
|
|
2833
|
+
return Array.isArray(expected) ? expected.every((v) => fieldValue.includes(v)) : fieldValue.includes(expected);
|
|
2834
|
+
}
|
|
2835
|
+
return false;
|
|
2836
|
+
}
|
|
2837
|
+
default:
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
// ── Tool resolution ───────────────────────────────────────────
|
|
2842
|
+
/**
|
|
2843
|
+
* Flatten a list of skills to a deduplicated array of concrete tool
|
|
2844
|
+
* definitions, preserving the order skills declared their tools.
|
|
2845
|
+
*
|
|
2846
|
+
* Tools that are declared by a skill but missing from the available
|
|
2847
|
+
* tool registry are silently dropped — this is intentional so a skill
|
|
2848
|
+
* can be authored before all its underlying tools are registered.
|
|
2849
|
+
*/
|
|
2850
|
+
flattenToTools(skills, availableTools) {
|
|
2851
|
+
const toolMap = new Map(availableTools.map((t) => [t.name, t]));
|
|
2852
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2853
|
+
const resolved = [];
|
|
2854
|
+
for (const skill of skills) {
|
|
2855
|
+
for (const toolName of skill.tools) {
|
|
2856
|
+
if (seen.has(toolName)) continue;
|
|
2857
|
+
const def = toolMap.get(toolName);
|
|
2858
|
+
if (def) {
|
|
2859
|
+
resolved.push(def);
|
|
2860
|
+
seen.add(toolName);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return resolved;
|
|
2865
|
+
}
|
|
2866
|
+
// ── System-prompt composition ─────────────────────────────────
|
|
2867
|
+
/**
|
|
2868
|
+
* Build the "Active Skills" block to append to an agent's system
|
|
2869
|
+
* prompt. The block lists each skill's label + instructions so the
|
|
2870
|
+
* LLM knows which capabilities are available and how to invoke them.
|
|
2871
|
+
*
|
|
2872
|
+
* Returns an empty string when there are no skills, so the caller
|
|
2873
|
+
* can safely concatenate without producing dangling whitespace.
|
|
2874
|
+
*/
|
|
2875
|
+
composeInstructionsBlock(skills) {
|
|
2876
|
+
if (skills.length === 0) return "";
|
|
2877
|
+
const lines = ["", "--- Active Skills ---"];
|
|
2878
|
+
for (const skill of skills) {
|
|
2879
|
+
lines.push(`
|
|
2880
|
+
### ${skill.label} (${skill.name})`);
|
|
2881
|
+
if (skill.description) lines.push(skill.description);
|
|
2882
|
+
if (skill.instructions) lines.push(skill.instructions);
|
|
2883
|
+
if (skill.tools.length > 0) {
|
|
2884
|
+
lines.push(`Tools: ${skill.tools.join(", ")}`);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
return lines.join("\n");
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Project a skill to a wire-friendly summary suitable for the
|
|
2891
|
+
* `/api/v1/ai/skills` endpoint and slash-command palettes.
|
|
2892
|
+
*/
|
|
2893
|
+
toSummary(skill) {
|
|
2894
|
+
return {
|
|
2895
|
+
name: skill.name,
|
|
2896
|
+
label: skill.label,
|
|
2897
|
+
description: skill.description,
|
|
2898
|
+
triggerPhrases: skill.triggerPhrases,
|
|
2899
|
+
toolCount: skill.tools.length
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2342
2902
|
};
|
|
2343
2903
|
|
|
2344
2904
|
// src/agents/data-chat-agent.ts
|
|
@@ -2348,34 +2908,15 @@ var DATA_CHAT_AGENT = {
|
|
|
2348
2908
|
role: "Business Data Analyst",
|
|
2349
2909
|
instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
|
|
2350
2910
|
|
|
2351
|
-
|
|
2352
|
-
- List available data objects (tables) and their schemas
|
|
2353
|
-
- Query records with filters, sorting, and pagination
|
|
2354
|
-
- Look up individual records by ID
|
|
2355
|
-
- Perform aggregations and statistical analysis (count, sum, avg, min, max)
|
|
2356
|
-
|
|
2357
|
-
Guidelines:
|
|
2358
|
-
1. Always use the describe_object tool first to understand a table's structure before querying it.
|
|
2359
|
-
2. Respect the user's current context \u2014 if they are viewing a specific object or record, use that as the default scope.
|
|
2360
|
-
3. When presenting data, format it in a clear and readable way using markdown tables or bullet lists.
|
|
2361
|
-
4. For large result sets, summarize the data and mention the total count.
|
|
2362
|
-
5. When performing aggregations, explain the results in plain language.
|
|
2363
|
-
6. If a query returns no results, suggest possible reasons and alternative queries.
|
|
2364
|
-
7. Never expose internal IDs unless the user explicitly asks for them.
|
|
2365
|
-
8. Always answer in the same language the user is using.`,
|
|
2911
|
+
Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
|
|
2366
2912
|
model: {
|
|
2367
2913
|
provider: "openai",
|
|
2368
2914
|
model: "gpt-4",
|
|
2369
2915
|
temperature: 0.3,
|
|
2370
2916
|
maxTokens: 4096
|
|
2371
2917
|
},
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
{ type: "query", name: "describe_object", description: "Get schema/fields of a data object" },
|
|
2375
|
-
{ type: "query", name: "query_records", description: "Query records with filters and pagination" },
|
|
2376
|
-
{ type: "query", name: "get_record", description: "Get a single record by ID" },
|
|
2377
|
-
{ type: "query", name: "aggregate_data", description: "Aggregate/statistics on data" }
|
|
2378
|
-
],
|
|
2918
|
+
// Capability bundle lives on the skill; the agent only references it.
|
|
2919
|
+
skills: ["data_explorer"],
|
|
2379
2920
|
active: true,
|
|
2380
2921
|
visibility: "global",
|
|
2381
2922
|
guardrails: {
|
|
@@ -2403,39 +2944,15 @@ var METADATA_ASSISTANT_AGENT = {
|
|
|
2403
2944
|
role: "Schema Architect",
|
|
2404
2945
|
instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
|
|
2405
2946
|
|
|
2406
|
-
|
|
2407
|
-
- Create new data objects (tables) with fields
|
|
2408
|
-
- Add fields (columns) to existing objects
|
|
2409
|
-
- Modify field properties (label, type, required, default value)
|
|
2410
|
-
- Delete fields from objects
|
|
2411
|
-
- List all registered metadata objects and their schemas
|
|
2412
|
-
- Describe the full schema of a specific object
|
|
2413
|
-
|
|
2414
|
-
Guidelines:
|
|
2415
|
-
1. Before creating a new object, use list_objects to check if a similar one already exists.
|
|
2416
|
-
2. Before modifying or deleting fields, use describe_object to understand the current schema.
|
|
2417
|
-
3. Always use snake_case for object names and field names (e.g. project_task, due_date).
|
|
2418
|
-
4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
|
|
2419
|
-
5. When creating objects, propose a reasonable set of initial fields based on the entity type.
|
|
2420
|
-
6. Explain what changes you are about to make before executing them.
|
|
2421
|
-
7. After making changes, confirm the result by describing the updated schema.
|
|
2422
|
-
8. For destructive operations (deleting fields), always warn the user about potential data loss.
|
|
2423
|
-
9. Always answer in the same language the user is using.
|
|
2424
|
-
10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
|
|
2947
|
+
Always answer in the same language the user is using. If the user's request is ambiguous, ask clarifying questions before proceeding. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
|
|
2425
2948
|
model: {
|
|
2426
2949
|
provider: "openai",
|
|
2427
2950
|
model: "gpt-4",
|
|
2428
2951
|
temperature: 0.2,
|
|
2429
2952
|
maxTokens: 4096
|
|
2430
2953
|
},
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
{ type: "action", name: "add_field", description: "Add a field to an existing object" },
|
|
2434
|
-
{ type: "action", name: "modify_field", description: "Modify an existing field definition" },
|
|
2435
|
-
{ type: "action", name: "delete_field", description: "Delete a field from an object" },
|
|
2436
|
-
{ type: "query", name: "list_objects", description: "List all data objects" },
|
|
2437
|
-
{ type: "query", name: "describe_object", description: "Describe an object schema" }
|
|
2438
|
-
],
|
|
2954
|
+
// Capability bundle lives on the skill; the agent only references it.
|
|
2955
|
+
skills: ["metadata_authoring"],
|
|
2439
2956
|
active: true,
|
|
2440
2957
|
visibility: "global",
|
|
2441
2958
|
guardrails: {
|
|
@@ -2456,8 +2973,100 @@ Guidelines:
|
|
|
2456
2973
|
}
|
|
2457
2974
|
};
|
|
2458
2975
|
|
|
2976
|
+
// src/skills/data-explorer-skill.ts
|
|
2977
|
+
var DATA_EXPLORER_SKILL = {
|
|
2978
|
+
name: "data_explorer",
|
|
2979
|
+
label: "Data Explorer",
|
|
2980
|
+
description: "Read-only Q&A over the user's business data \u2014 schema discovery, filtered queries, lookups, and aggregations.",
|
|
2981
|
+
instructions: `You can explore the user's business data through these tools.
|
|
2982
|
+
|
|
2983
|
+
Capabilities:
|
|
2984
|
+
- List available data objects (tables) and their schemas
|
|
2985
|
+
- Query records with filters, sorting, and pagination
|
|
2986
|
+
- Look up individual records by ID
|
|
2987
|
+
- Perform aggregations and statistical analysis (count, sum, avg, min, max)
|
|
2988
|
+
|
|
2989
|
+
Guidelines:
|
|
2990
|
+
1. Always use the describe_object tool first to understand a table's structure before querying it.
|
|
2991
|
+
2. Respect the user's current context \u2014 if they are viewing a specific object or record, use that as the default scope.
|
|
2992
|
+
3. When presenting data, format it in a clear and readable way using markdown tables or bullet lists.
|
|
2993
|
+
4. For large result sets, summarize the data and mention the total count.
|
|
2994
|
+
5. When performing aggregations, explain the results in plain language.
|
|
2995
|
+
6. If a query returns no results, suggest possible reasons and alternative queries.
|
|
2996
|
+
7. Never expose internal IDs unless the user explicitly asks for them.
|
|
2997
|
+
8. Always answer in the same language the user is using.`,
|
|
2998
|
+
tools: [
|
|
2999
|
+
"list_objects",
|
|
3000
|
+
"describe_object",
|
|
3001
|
+
"query_records",
|
|
3002
|
+
"get_record",
|
|
3003
|
+
"aggregate_data"
|
|
3004
|
+
],
|
|
3005
|
+
triggerPhrases: [
|
|
3006
|
+
"show me",
|
|
3007
|
+
"list",
|
|
3008
|
+
"how many",
|
|
3009
|
+
"count",
|
|
3010
|
+
"find records",
|
|
3011
|
+
"query",
|
|
3012
|
+
"aggregate",
|
|
3013
|
+
"sum",
|
|
3014
|
+
"average"
|
|
3015
|
+
],
|
|
3016
|
+
active: true
|
|
3017
|
+
};
|
|
3018
|
+
|
|
3019
|
+
// src/skills/metadata-authoring-skill.ts
|
|
3020
|
+
var METADATA_AUTHORING_SKILL = {
|
|
3021
|
+
name: "metadata_authoring",
|
|
3022
|
+
label: "Metadata Authoring",
|
|
3023
|
+
description: "Create and modify ObjectStack metadata \u2014 objects, fields, schema changes through natural language.",
|
|
3024
|
+
instructions: `You are an expert metadata architect. When the user asks you to design or change a data model, use these tools.
|
|
3025
|
+
|
|
3026
|
+
Capabilities:
|
|
3027
|
+
- Create new data objects (tables) with fields
|
|
3028
|
+
- Add fields (columns) to existing objects
|
|
3029
|
+
- Modify field properties (label, type, required, default value)
|
|
3030
|
+
- Delete fields from objects
|
|
3031
|
+
- List all registered metadata objects and their schemas
|
|
3032
|
+
- Describe the full schema of a specific object
|
|
3033
|
+
|
|
3034
|
+
Guidelines:
|
|
3035
|
+
1. Before creating a new object, use list_objects to check if a similar one already exists.
|
|
3036
|
+
2. Before modifying or deleting fields, use describe_object to understand the current schema.
|
|
3037
|
+
3. Always use snake_case for object names and field names (e.g. project_task, due_date).
|
|
3038
|
+
4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
|
|
3039
|
+
5. When creating objects, propose a reasonable set of initial fields based on the entity type.
|
|
3040
|
+
6. Explain what changes you are about to make before executing them.
|
|
3041
|
+
7. After making changes, confirm the result by describing the updated schema.
|
|
3042
|
+
8. For destructive operations (deleting fields), always warn the user about potential data loss.
|
|
3043
|
+
9. Always answer in the same language the user is using.
|
|
3044
|
+
10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
|
|
3045
|
+
tools: [
|
|
3046
|
+
"create_object",
|
|
3047
|
+
"add_field",
|
|
3048
|
+
"modify_field",
|
|
3049
|
+
"delete_field",
|
|
3050
|
+
"list_objects",
|
|
3051
|
+
"describe_object"
|
|
3052
|
+
],
|
|
3053
|
+
triggerPhrases: [
|
|
3054
|
+
"create object",
|
|
3055
|
+
"create table",
|
|
3056
|
+
"add field",
|
|
3057
|
+
"add column",
|
|
3058
|
+
"modify field",
|
|
3059
|
+
"change field",
|
|
3060
|
+
"delete field",
|
|
3061
|
+
"drop field",
|
|
3062
|
+
"design schema",
|
|
3063
|
+
"new entity"
|
|
3064
|
+
],
|
|
3065
|
+
active: true
|
|
3066
|
+
};
|
|
3067
|
+
|
|
2459
3068
|
// src/adapters/vercel-adapter.ts
|
|
2460
|
-
var
|
|
3069
|
+
var import_ai9 = require("ai");
|
|
2461
3070
|
function buildVercelOptions(options) {
|
|
2462
3071
|
if (!options) return {};
|
|
2463
3072
|
const opts = {};
|
|
@@ -2467,9 +3076,9 @@ function buildVercelOptions(options) {
|
|
|
2467
3076
|
if (options.tools?.length) {
|
|
2468
3077
|
const tools = {};
|
|
2469
3078
|
for (const t of options.tools) {
|
|
2470
|
-
tools[t.name] = (0,
|
|
3079
|
+
tools[t.name] = (0, import_ai9.tool)({
|
|
2471
3080
|
description: t.description,
|
|
2472
|
-
inputSchema: (0,
|
|
3081
|
+
inputSchema: (0, import_ai9.jsonSchema)(t.parameters)
|
|
2473
3082
|
});
|
|
2474
3083
|
}
|
|
2475
3084
|
opts.tools = tools;
|
|
@@ -2485,7 +3094,7 @@ var VercelLLMAdapter = class {
|
|
|
2485
3094
|
this.model = config.model;
|
|
2486
3095
|
}
|
|
2487
3096
|
async chat(messages, options) {
|
|
2488
|
-
const result = await (0,
|
|
3097
|
+
const result = await (0, import_ai9.generateText)({
|
|
2489
3098
|
model: this.model,
|
|
2490
3099
|
messages,
|
|
2491
3100
|
...buildVercelOptions(options)
|
|
@@ -2502,7 +3111,7 @@ var VercelLLMAdapter = class {
|
|
|
2502
3111
|
};
|
|
2503
3112
|
}
|
|
2504
3113
|
async complete(prompt, options) {
|
|
2505
|
-
const result = await (0,
|
|
3114
|
+
const result = await (0, import_ai9.generateText)({
|
|
2506
3115
|
model: this.model,
|
|
2507
3116
|
prompt,
|
|
2508
3117
|
...buildVercelOptions(options)
|
|
@@ -2518,13 +3127,20 @@ var VercelLLMAdapter = class {
|
|
|
2518
3127
|
};
|
|
2519
3128
|
}
|
|
2520
3129
|
async *streamChat(messages, options) {
|
|
2521
|
-
const result = (0,
|
|
3130
|
+
const result = (0, import_ai9.streamText)({
|
|
2522
3131
|
model: this.model,
|
|
2523
3132
|
messages,
|
|
2524
3133
|
...buildVercelOptions(options)
|
|
2525
3134
|
});
|
|
2526
|
-
|
|
2527
|
-
|
|
3135
|
+
try {
|
|
3136
|
+
for await (const part of result.fullStream) {
|
|
3137
|
+
yield part;
|
|
3138
|
+
}
|
|
3139
|
+
} catch (err) {
|
|
3140
|
+
yield {
|
|
3141
|
+
type: "error",
|
|
3142
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
3143
|
+
};
|
|
2528
3144
|
}
|
|
2529
3145
|
}
|
|
2530
3146
|
async embed(_input) {
|
|
@@ -2606,11 +3222,14 @@ var AIServicePlugin = class {
|
|
|
2606
3222
|
/* webpackIgnore: true */
|
|
2607
3223
|
pkg
|
|
2608
3224
|
);
|
|
2609
|
-
const
|
|
2610
|
-
if (typeof
|
|
3225
|
+
const provider = mod[factory] ?? mod.default;
|
|
3226
|
+
if (typeof provider === "function") {
|
|
2611
3227
|
const modelId = process.env.AI_MODEL ?? defaultModel;
|
|
2612
|
-
const
|
|
2613
|
-
|
|
3228
|
+
const useChatApi = factory === "openai" && typeof provider.chat === "function";
|
|
3229
|
+
const model = useChatApi ? provider.chat(modelId) : provider(modelId);
|
|
3230
|
+
const adapter = new VercelLLMAdapter({ model });
|
|
3231
|
+
const apiSuffix = useChatApi ? " [chat-completions]" : "";
|
|
3232
|
+
return { adapter, description: `${displayName} (model: ${modelId})${apiSuffix}` };
|
|
2614
3233
|
}
|
|
2615
3234
|
} catch (err) {
|
|
2616
3235
|
ctx.logger.warn(
|
|
@@ -2671,6 +3290,7 @@ var AIServicePlugin = class {
|
|
|
2671
3290
|
name: "AI Service",
|
|
2672
3291
|
version: "1.0.0",
|
|
2673
3292
|
type: "plugin",
|
|
3293
|
+
scope: "project",
|
|
2674
3294
|
namespace: "ai",
|
|
2675
3295
|
objects: [AiConversationObject, AiMessageObject]
|
|
2676
3296
|
});
|
|
@@ -2679,25 +3299,12 @@ var AIServicePlugin = class {
|
|
|
2679
3299
|
ctx.logger.debug("[AI] Before chat", { messages });
|
|
2680
3300
|
});
|
|
2681
3301
|
}
|
|
2682
|
-
try {
|
|
2683
|
-
const setupNav = ctx.getService("setupNav");
|
|
2684
|
-
if (setupNav) {
|
|
2685
|
-
setupNav.contribute({
|
|
2686
|
-
areaId: "area_ai",
|
|
2687
|
-
items: [
|
|
2688
|
-
{ id: "nav_ai_conversations", type: "object", label: "Conversations", objectName: "conversations", icon: "message-square", order: 10 },
|
|
2689
|
-
{ id: "nav_ai_messages", type: "object", label: "Messages", objectName: "messages", icon: "messages-square", order: 20 }
|
|
2690
|
-
]
|
|
2691
|
-
});
|
|
2692
|
-
ctx.logger.info("[AI] Navigation items contributed to Setup App");
|
|
2693
|
-
}
|
|
2694
|
-
} catch {
|
|
2695
|
-
}
|
|
2696
3302
|
ctx.logger.info("[AI] Service initialized");
|
|
2697
3303
|
}
|
|
2698
3304
|
async start(ctx) {
|
|
2699
3305
|
if (!this.service) return;
|
|
2700
3306
|
let metadataService;
|
|
3307
|
+
const withTimeout = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
|
|
2701
3308
|
try {
|
|
2702
3309
|
metadataService = ctx.getService("metadata");
|
|
2703
3310
|
console.log("[AI Plugin] Retrieved metadata service:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
|
|
@@ -2705,6 +3312,13 @@ var AIServicePlugin = class {
|
|
|
2705
3312
|
console.log("[AI] Metadata service not available:", e.message);
|
|
2706
3313
|
ctx.logger.debug("[AI] Metadata service not available");
|
|
2707
3314
|
}
|
|
3315
|
+
if (metadataService && typeof metadataService.exists === "function") {
|
|
3316
|
+
const probeResult = await withTimeout(metadataService.exists("tool", "__probe__"), 3e3);
|
|
3317
|
+
if (probeResult === null) {
|
|
3318
|
+
ctx.logger.warn("[AI] Metadata service unreachable (timed out) \u2014 AI tools/agents will work but Studio visibility unavailable");
|
|
3319
|
+
metadataService = void 0;
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
2708
3322
|
try {
|
|
2709
3323
|
const dataEngine = ctx.getService("data");
|
|
2710
3324
|
if (dataEngine) {
|
|
@@ -2713,18 +3327,31 @@ var AIServicePlugin = class {
|
|
|
2713
3327
|
if (metadataService) {
|
|
2714
3328
|
const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
|
|
2715
3329
|
for (const toolDef of DATA_TOOL_DEFINITIONS2) {
|
|
2716
|
-
const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
|
|
3330
|
+
const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
|
|
3331
|
+
if (toolExists === null) {
|
|
3332
|
+
ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
|
|
3333
|
+
break;
|
|
3334
|
+
}
|
|
2717
3335
|
if (!toolExists) {
|
|
2718
|
-
|
|
3336
|
+
try {
|
|
3337
|
+
await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
ctx.logger.warn(
|
|
3340
|
+
"[AI] Failed to persist tool metadata (non-fatal)",
|
|
3341
|
+
err instanceof Error ? { tool: toolDef.name, error: err.message } : { tool: toolDef.name }
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
2719
3344
|
}
|
|
2720
3345
|
}
|
|
2721
3346
|
ctx.logger.info(`[AI] ${DATA_TOOL_DEFINITIONS2.length} data tools registered as metadata`);
|
|
2722
3347
|
}
|
|
2723
3348
|
if (metadataService) {
|
|
2724
3349
|
try {
|
|
2725
|
-
const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", DATA_CHAT_AGENT.name) : false;
|
|
2726
|
-
if (
|
|
2727
|
-
|
|
3350
|
+
const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
|
|
3351
|
+
if (agentExists === null) {
|
|
3352
|
+
ctx.logger.warn("[AI] Metadata service timed out checking data_chat agent, skipping");
|
|
3353
|
+
} else if (!agentExists) {
|
|
3354
|
+
await withTimeout(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
|
|
2728
3355
|
console.log("[AI] Registered data_chat agent to metadataService");
|
|
2729
3356
|
ctx.logger.info("[AI] data_chat agent registered");
|
|
2730
3357
|
} else {
|
|
@@ -2734,6 +3361,19 @@ var AIServicePlugin = class {
|
|
|
2734
3361
|
} catch (err) {
|
|
2735
3362
|
ctx.logger.warn("[AI] Failed to register data_chat agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
|
|
2736
3363
|
}
|
|
3364
|
+
try {
|
|
3365
|
+
const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
|
|
3366
|
+
if (skillExists === null) {
|
|
3367
|
+
ctx.logger.warn("[AI] Metadata service timed out checking data_explorer skill, skipping");
|
|
3368
|
+
} else if (!skillExists) {
|
|
3369
|
+
await withTimeout(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
|
|
3370
|
+
ctx.logger.info("[AI] data_explorer skill registered");
|
|
3371
|
+
} else {
|
|
3372
|
+
ctx.logger.debug("[AI] data_explorer skill already exists, skipping auto-registration");
|
|
3373
|
+
}
|
|
3374
|
+
} catch (err) {
|
|
3375
|
+
ctx.logger.warn("[AI] Failed to register data_explorer skill", err instanceof Error ? { error: err.message } : { error: String(err) });
|
|
3376
|
+
}
|
|
2737
3377
|
}
|
|
2738
3378
|
}
|
|
2739
3379
|
} catch {
|
|
@@ -2745,16 +3385,29 @@ var AIServicePlugin = class {
|
|
|
2745
3385
|
ctx.logger.info("[AI] Built-in metadata tools registered");
|
|
2746
3386
|
const { METADATA_TOOL_DEFINITIONS: METADATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_metadata_tools(), metadata_tools_exports));
|
|
2747
3387
|
for (const toolDef of METADATA_TOOL_DEFINITIONS2) {
|
|
2748
|
-
const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
|
|
3388
|
+
const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
|
|
3389
|
+
if (toolExists === null) {
|
|
3390
|
+
ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
|
|
3391
|
+
break;
|
|
3392
|
+
}
|
|
2749
3393
|
if (!toolExists) {
|
|
2750
|
-
|
|
3394
|
+
try {
|
|
3395
|
+
await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
|
|
3396
|
+
} catch (err) {
|
|
3397
|
+
ctx.logger.warn(
|
|
3398
|
+
"[AI] Failed to persist tool metadata (non-fatal)",
|
|
3399
|
+
err instanceof Error ? { tool: toolDef.name, error: err.message } : { tool: toolDef.name }
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
2751
3402
|
}
|
|
2752
3403
|
}
|
|
2753
3404
|
ctx.logger.info(`[AI] ${METADATA_TOOL_DEFINITIONS2.length} metadata tools registered as metadata`);
|
|
2754
3405
|
try {
|
|
2755
|
-
const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name) : false;
|
|
2756
|
-
if (
|
|
2757
|
-
|
|
3406
|
+
const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
|
|
3407
|
+
if (agentExists === null) {
|
|
3408
|
+
ctx.logger.warn("[AI] Metadata service timed out checking metadata_assistant agent, skipping");
|
|
3409
|
+
} else if (!agentExists) {
|
|
3410
|
+
await withTimeout(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
|
|
2758
3411
|
console.log("[AI] Registered metadata_assistant agent to metadataService");
|
|
2759
3412
|
ctx.logger.info("[AI] metadata_assistant agent registered");
|
|
2760
3413
|
} else {
|
|
@@ -2764,22 +3417,71 @@ var AIServicePlugin = class {
|
|
|
2764
3417
|
} catch (err) {
|
|
2765
3418
|
ctx.logger.warn("[AI] Failed to register metadata_assistant agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
|
|
2766
3419
|
}
|
|
3420
|
+
try {
|
|
3421
|
+
const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
|
|
3422
|
+
if (skillExists === null) {
|
|
3423
|
+
ctx.logger.warn("[AI] Metadata service timed out checking metadata_authoring skill, skipping");
|
|
3424
|
+
} else if (!skillExists) {
|
|
3425
|
+
await withTimeout(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
|
|
3426
|
+
ctx.logger.info("[AI] metadata_authoring skill registered");
|
|
3427
|
+
} else {
|
|
3428
|
+
ctx.logger.debug("[AI] metadata_authoring skill already exists, skipping auto-registration");
|
|
3429
|
+
}
|
|
3430
|
+
} catch (err) {
|
|
3431
|
+
ctx.logger.warn("[AI] Failed to register metadata_authoring skill", err instanceof Error ? { error: err.message } : { error: String(err) });
|
|
3432
|
+
}
|
|
2767
3433
|
} catch (err) {
|
|
2768
3434
|
ctx.logger.debug("[AI] Failed to register metadata tools", err instanceof Error ? err : void 0);
|
|
2769
3435
|
}
|
|
2770
3436
|
}
|
|
2771
3437
|
await ctx.trigger("ai:ready", this.service);
|
|
3438
|
+
if (metadataService) {
|
|
3439
|
+
try {
|
|
3440
|
+
const objectql = ctx.getService("objectql");
|
|
3441
|
+
const registry = objectql?.registry;
|
|
3442
|
+
if (registry && typeof registry.listItems === "function") {
|
|
3443
|
+
const stackAgents = registry.listItems("agent");
|
|
3444
|
+
let bridged = 0;
|
|
3445
|
+
for (const entry of stackAgents) {
|
|
3446
|
+
const agent = entry?.content ?? entry;
|
|
3447
|
+
const agentName = agent?.name;
|
|
3448
|
+
if (!agentName || typeof agentName !== "string") continue;
|
|
3449
|
+
const exists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", agentName)) : false;
|
|
3450
|
+
if (exists === true) continue;
|
|
3451
|
+
try {
|
|
3452
|
+
await withTimeout(metadataService.register("agent", agentName, agent));
|
|
3453
|
+
bridged++;
|
|
3454
|
+
} catch (err) {
|
|
3455
|
+
ctx.logger.warn(
|
|
3456
|
+
"[AI] Failed to bridge stack agent into metadata service (non-fatal)",
|
|
3457
|
+
err instanceof Error ? { agent: agentName, error: err.message } : { agent: agentName }
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
if (bridged > 0) {
|
|
3462
|
+
ctx.logger.info(`[AI] Bridged ${bridged} stack-defined agent(s) from ObjectQL registry`);
|
|
3463
|
+
console.log(`[AI] Bridged ${bridged} stack-defined agent(s) from ObjectQL registry`);
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
} catch (err) {
|
|
3467
|
+
ctx.logger.debug("[AI] ObjectQL registry not available, skipping agent bridge", err instanceof Error ? err : void 0);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
2772
3470
|
const routes = buildAIRoutes(this.service, this.service.conversationService, ctx.logger);
|
|
2773
3471
|
const toolRoutes = buildToolRoutes(this.service, ctx.logger);
|
|
2774
3472
|
routes.push(...toolRoutes);
|
|
2775
3473
|
ctx.logger.info(`[AI] Tool routes registered (${toolRoutes.length} routes)`);
|
|
2776
3474
|
if (metadataService) {
|
|
2777
|
-
const
|
|
3475
|
+
const skillRegistry = new SkillRegistry(metadataService);
|
|
3476
|
+
const agentRuntime = new AgentRuntime(metadataService, skillRegistry);
|
|
2778
3477
|
const agentRoutes = buildAgentRoutes(this.service, agentRuntime, ctx.logger);
|
|
2779
3478
|
routes.push(...agentRoutes);
|
|
2780
3479
|
ctx.logger.info(`[AI] Agent routes registered (${agentRoutes.length} routes)`);
|
|
3480
|
+
const assistantRoutes = buildAssistantRoutes(this.service, agentRuntime, skillRegistry, ctx.logger);
|
|
3481
|
+
routes.push(...assistantRoutes);
|
|
3482
|
+
ctx.logger.info(`[AI] Assistant (ambient) routes registered (${assistantRoutes.length} routes)`);
|
|
2781
3483
|
} else {
|
|
2782
|
-
ctx.logger.debug("[AI] Metadata service not available, skipping agent routes");
|
|
3484
|
+
ctx.logger.debug("[AI] Metadata service not available, skipping agent and assistant routes");
|
|
2783
3485
|
}
|
|
2784
3486
|
await ctx.trigger("ai:routes", routes);
|
|
2785
3487
|
const kernel = ctx.getKernel();
|
|
@@ -2799,6 +3501,337 @@ var AIServicePlugin = class {
|
|
|
2799
3501
|
init_data_tools();
|
|
2800
3502
|
init_metadata_tools();
|
|
2801
3503
|
init_metadata_tools();
|
|
3504
|
+
|
|
3505
|
+
// src/tools/list-packages.tool.ts
|
|
3506
|
+
var import_ai10 = require("@objectstack/spec/ai");
|
|
3507
|
+
var listPackagesTool = (0, import_ai10.defineTool)({
|
|
3508
|
+
name: "list_packages",
|
|
3509
|
+
label: "List Packages",
|
|
3510
|
+
description: "Lists all installed packages in the system. Use this to see what packages are available before creating or modifying metadata. Packages are the containers that hold metadata.",
|
|
3511
|
+
category: "utility",
|
|
3512
|
+
builtIn: true,
|
|
3513
|
+
parameters: {
|
|
3514
|
+
type: "object",
|
|
3515
|
+
properties: {
|
|
3516
|
+
status: {
|
|
3517
|
+
type: "string",
|
|
3518
|
+
description: "Filter by package status",
|
|
3519
|
+
enum: ["installed", "disabled", "installing", "upgrading", "uninstalling", "error"]
|
|
3520
|
+
},
|
|
3521
|
+
enabled: {
|
|
3522
|
+
type: "boolean",
|
|
3523
|
+
description: "Filter by enabled state (true = only enabled, false = only disabled)"
|
|
3524
|
+
}
|
|
3525
|
+
},
|
|
3526
|
+
additionalProperties: false
|
|
3527
|
+
}
|
|
3528
|
+
});
|
|
3529
|
+
|
|
3530
|
+
// src/tools/get-package.tool.ts
|
|
3531
|
+
var import_ai11 = require("@objectstack/spec/ai");
|
|
3532
|
+
var getPackageTool = (0, import_ai11.defineTool)({
|
|
3533
|
+
name: "get_package",
|
|
3534
|
+
label: "Get Package",
|
|
3535
|
+
description: "Gets detailed information about a specific installed package, including its manifest, metadata, and installation status.",
|
|
3536
|
+
category: "utility",
|
|
3537
|
+
builtIn: true,
|
|
3538
|
+
parameters: {
|
|
3539
|
+
type: "object",
|
|
3540
|
+
properties: {
|
|
3541
|
+
packageId: {
|
|
3542
|
+
type: "string",
|
|
3543
|
+
description: "Package identifier (reverse domain notation, e.g., com.acme.crm)"
|
|
3544
|
+
}
|
|
3545
|
+
},
|
|
3546
|
+
required: ["packageId"],
|
|
3547
|
+
additionalProperties: false
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
|
|
3551
|
+
// src/tools/create-package.tool.ts
|
|
3552
|
+
var import_ai12 = require("@objectstack/spec/ai");
|
|
3553
|
+
var createPackageTool = (0, import_ai12.defineTool)({
|
|
3554
|
+
name: "create_package",
|
|
3555
|
+
label: "Create Package",
|
|
3556
|
+
description: "Creates a new package (metadata container) with the specified manifest. All metadata in ObjectStack should belong to a package. Use this when starting new development or when the user wants to organize their metadata into a new module.",
|
|
3557
|
+
category: "utility",
|
|
3558
|
+
builtIn: true,
|
|
3559
|
+
parameters: {
|
|
3560
|
+
type: "object",
|
|
3561
|
+
properties: {
|
|
3562
|
+
id: {
|
|
3563
|
+
type: "string",
|
|
3564
|
+
description: "Package identifier in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)"
|
|
3565
|
+
},
|
|
3566
|
+
name: {
|
|
3567
|
+
type: "string",
|
|
3568
|
+
description: 'Human-readable package name (e.g., "CRM Application", "Sales Module")'
|
|
3569
|
+
},
|
|
3570
|
+
version: {
|
|
3571
|
+
type: "string",
|
|
3572
|
+
description: 'Semantic version (e.g., "1.0.0")',
|
|
3573
|
+
default: "1.0.0"
|
|
3574
|
+
},
|
|
3575
|
+
description: {
|
|
3576
|
+
type: "string",
|
|
3577
|
+
description: "Brief description of what this package provides"
|
|
3578
|
+
},
|
|
3579
|
+
namespace: {
|
|
3580
|
+
type: "string",
|
|
3581
|
+
description: "Namespace prefix for metadata (snake_case, e.g., crm, sales). If not provided, derived from package ID."
|
|
3582
|
+
},
|
|
3583
|
+
type: {
|
|
3584
|
+
type: "string",
|
|
3585
|
+
description: "Package type",
|
|
3586
|
+
enum: ["application", "plugin", "library", "template"],
|
|
3587
|
+
default: "application"
|
|
3588
|
+
}
|
|
3589
|
+
},
|
|
3590
|
+
required: ["id", "name"],
|
|
3591
|
+
additionalProperties: false
|
|
3592
|
+
}
|
|
3593
|
+
});
|
|
3594
|
+
|
|
3595
|
+
// src/tools/get-active-package.tool.ts
|
|
3596
|
+
var import_ai13 = require("@objectstack/spec/ai");
|
|
3597
|
+
var getActivePackageTool = (0, import_ai13.defineTool)({
|
|
3598
|
+
name: "get_active_package",
|
|
3599
|
+
label: "Get Active Package",
|
|
3600
|
+
description: "Gets the currently active package in this conversation. The active package determines where new metadata will be created. Returns null if no package is set.",
|
|
3601
|
+
category: "utility",
|
|
3602
|
+
builtIn: true,
|
|
3603
|
+
parameters: {
|
|
3604
|
+
type: "object",
|
|
3605
|
+
properties: {},
|
|
3606
|
+
additionalProperties: false
|
|
3607
|
+
}
|
|
3608
|
+
});
|
|
3609
|
+
|
|
3610
|
+
// src/tools/set-active-package.tool.ts
|
|
3611
|
+
var import_ai14 = require("@objectstack/spec/ai");
|
|
3612
|
+
var setActivePackageTool = (0, import_ai14.defineTool)({
|
|
3613
|
+
name: "set_active_package",
|
|
3614
|
+
label: "Set Active Package",
|
|
3615
|
+
description: "Sets the active package for this conversation. All subsequent metadata creation operations (objects, views, flows, etc.) will be associated with this package unless explicitly overridden.",
|
|
3616
|
+
category: "utility",
|
|
3617
|
+
builtIn: true,
|
|
3618
|
+
parameters: {
|
|
3619
|
+
type: "object",
|
|
3620
|
+
properties: {
|
|
3621
|
+
packageId: {
|
|
3622
|
+
type: "string",
|
|
3623
|
+
description: "Package identifier to set as active (e.g., com.acme.crm)"
|
|
3624
|
+
}
|
|
3625
|
+
},
|
|
3626
|
+
required: ["packageId"],
|
|
3627
|
+
additionalProperties: false
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
|
|
3631
|
+
// src/tools/package-tools.ts
|
|
3632
|
+
var PACKAGE_TOOL_DEFINITIONS = [
|
|
3633
|
+
listPackagesTool,
|
|
3634
|
+
getPackageTool,
|
|
3635
|
+
createPackageTool,
|
|
3636
|
+
getActivePackageTool,
|
|
3637
|
+
setActivePackageTool
|
|
3638
|
+
];
|
|
3639
|
+
var REVERSE_DOMAIN_RE = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/;
|
|
3640
|
+
var SNAKE_CASE_RE2 = /^[a-z_][a-z0-9_]*$/;
|
|
3641
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(-[a-z0-9]+(\.[a-z0-9]+)*)?$/;
|
|
3642
|
+
function isReverseDomain(value) {
|
|
3643
|
+
return REVERSE_DOMAIN_RE.test(value);
|
|
3644
|
+
}
|
|
3645
|
+
function isSnakeCase2(value) {
|
|
3646
|
+
return SNAKE_CASE_RE2.test(value);
|
|
3647
|
+
}
|
|
3648
|
+
function isSemVer(value) {
|
|
3649
|
+
return SEMVER_RE.test(value);
|
|
3650
|
+
}
|
|
3651
|
+
function deriveNamespace(packageId) {
|
|
3652
|
+
const parts = packageId.split(".");
|
|
3653
|
+
return parts[parts.length - 1];
|
|
3654
|
+
}
|
|
3655
|
+
function createListPackagesHandler(ctx) {
|
|
3656
|
+
return async (args) => {
|
|
3657
|
+
const { status, enabled } = args ?? {};
|
|
3658
|
+
const filter = {};
|
|
3659
|
+
if (status) filter.status = status;
|
|
3660
|
+
if (enabled !== void 0) filter.enabled = enabled;
|
|
3661
|
+
const packages = await ctx.packageRegistry.list(filter);
|
|
3662
|
+
const result = packages.map((pkg) => ({
|
|
3663
|
+
id: pkg.manifest.id,
|
|
3664
|
+
name: pkg.manifest.name,
|
|
3665
|
+
version: pkg.manifest.version,
|
|
3666
|
+
type: pkg.manifest.type,
|
|
3667
|
+
status: pkg.status,
|
|
3668
|
+
enabled: pkg.enabled,
|
|
3669
|
+
installedAt: pkg.installedAt,
|
|
3670
|
+
description: pkg.manifest.description
|
|
3671
|
+
}));
|
|
3672
|
+
return JSON.stringify({
|
|
3673
|
+
packages: result,
|
|
3674
|
+
total: result.length
|
|
3675
|
+
});
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
function createGetPackageHandler(ctx) {
|
|
3679
|
+
return async (args) => {
|
|
3680
|
+
const { packageId } = args;
|
|
3681
|
+
if (!packageId) {
|
|
3682
|
+
return JSON.stringify({ error: "packageId is required" });
|
|
3683
|
+
}
|
|
3684
|
+
const pkg = await ctx.packageRegistry.get(packageId);
|
|
3685
|
+
if (!pkg) {
|
|
3686
|
+
return JSON.stringify({ error: `Package "${packageId}" not found` });
|
|
3687
|
+
}
|
|
3688
|
+
return JSON.stringify({
|
|
3689
|
+
id: pkg.manifest.id,
|
|
3690
|
+
name: pkg.manifest.name,
|
|
3691
|
+
version: pkg.manifest.version,
|
|
3692
|
+
type: pkg.manifest.type,
|
|
3693
|
+
status: pkg.status,
|
|
3694
|
+
enabled: pkg.enabled,
|
|
3695
|
+
installedAt: pkg.installedAt,
|
|
3696
|
+
updatedAt: pkg.updatedAt,
|
|
3697
|
+
description: pkg.manifest.description,
|
|
3698
|
+
namespace: pkg.manifest.namespace,
|
|
3699
|
+
dependencies: pkg.manifest.dependencies,
|
|
3700
|
+
registeredNamespaces: pkg.registeredNamespaces
|
|
3701
|
+
});
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
function createCreatePackageHandler(ctx) {
|
|
3705
|
+
return async (args) => {
|
|
3706
|
+
const { id, name, version = "1.0.0", description, namespace, type = "application" } = args;
|
|
3707
|
+
if (!id || !name) {
|
|
3708
|
+
return JSON.stringify({ error: 'Both "id" and "name" are required' });
|
|
3709
|
+
}
|
|
3710
|
+
if (!isReverseDomain(id)) {
|
|
3711
|
+
return JSON.stringify({
|
|
3712
|
+
error: `Invalid package ID "${id}". Must be in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)`
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
if (!isSemVer(version)) {
|
|
3716
|
+
return JSON.stringify({
|
|
3717
|
+
error: `Invalid version "${version}". Must be semantic version (e.g., 1.0.0, 2.1.3-beta)`
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
const exists = await ctx.packageRegistry.exists(id);
|
|
3721
|
+
if (exists) {
|
|
3722
|
+
return JSON.stringify({ error: `Package "${id}" already exists` });
|
|
3723
|
+
}
|
|
3724
|
+
const derivedNamespace = namespace || deriveNamespace(id);
|
|
3725
|
+
if (!isSnakeCase2(derivedNamespace)) {
|
|
3726
|
+
return JSON.stringify({
|
|
3727
|
+
error: `Invalid namespace "${derivedNamespace}". Must be snake_case (e.g., crm, sales_module)`
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
const manifest = {
|
|
3731
|
+
id,
|
|
3732
|
+
name,
|
|
3733
|
+
version,
|
|
3734
|
+
type,
|
|
3735
|
+
namespace: derivedNamespace,
|
|
3736
|
+
...description ? { description } : {}
|
|
3737
|
+
};
|
|
3738
|
+
const installedPackage = await ctx.packageRegistry.install(manifest);
|
|
3739
|
+
if (ctx.conversationService && ctx.conversationId) {
|
|
3740
|
+
try {
|
|
3741
|
+
await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
|
|
3742
|
+
activePackageId: id
|
|
3743
|
+
});
|
|
3744
|
+
} catch (err) {
|
|
3745
|
+
console.warn("Failed to set active package in conversation:", err);
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
return JSON.stringify({
|
|
3749
|
+
packageId: installedPackage.manifest.id,
|
|
3750
|
+
name: installedPackage.manifest.name,
|
|
3751
|
+
version: installedPackage.manifest.version,
|
|
3752
|
+
namespace: installedPackage.manifest.namespace,
|
|
3753
|
+
status: installedPackage.status,
|
|
3754
|
+
message: `Package "${name}" created successfully and set as active package`
|
|
3755
|
+
});
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
function createGetActivePackageHandler(ctx) {
|
|
3759
|
+
return async () => {
|
|
3760
|
+
if (!ctx.conversationService || !ctx.conversationId) {
|
|
3761
|
+
return JSON.stringify({
|
|
3762
|
+
activePackageId: null,
|
|
3763
|
+
message: "No conversation context available to track active package"
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
try {
|
|
3767
|
+
const metadata = await ctx.conversationService.getMetadata?.(ctx.conversationId);
|
|
3768
|
+
const activePackageId = metadata?.activePackageId;
|
|
3769
|
+
if (!activePackageId) {
|
|
3770
|
+
return JSON.stringify({
|
|
3771
|
+
activePackageId: null,
|
|
3772
|
+
message: "No active package set. Use set_active_package or create a new package."
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
const pkg = await ctx.packageRegistry.get(activePackageId);
|
|
3776
|
+
if (!pkg) {
|
|
3777
|
+
return JSON.stringify({
|
|
3778
|
+
activePackageId,
|
|
3779
|
+
error: `Active package "${activePackageId}" not found. It may have been uninstalled.`
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
return JSON.stringify({
|
|
3783
|
+
activePackageId: pkg.manifest.id,
|
|
3784
|
+
name: pkg.manifest.name,
|
|
3785
|
+
version: pkg.manifest.version,
|
|
3786
|
+
namespace: pkg.manifest.namespace,
|
|
3787
|
+
type: pkg.manifest.type
|
|
3788
|
+
});
|
|
3789
|
+
} catch (err) {
|
|
3790
|
+
return JSON.stringify({
|
|
3791
|
+
error: `Failed to get active package: ${err.message}`
|
|
3792
|
+
});
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
}
|
|
3796
|
+
function createSetActivePackageHandler(ctx) {
|
|
3797
|
+
return async (args) => {
|
|
3798
|
+
const { packageId } = args;
|
|
3799
|
+
if (!packageId) {
|
|
3800
|
+
return JSON.stringify({ error: "packageId is required" });
|
|
3801
|
+
}
|
|
3802
|
+
const pkg = await ctx.packageRegistry.get(packageId);
|
|
3803
|
+
if (!pkg) {
|
|
3804
|
+
return JSON.stringify({ error: `Package "${packageId}" not found` });
|
|
3805
|
+
}
|
|
3806
|
+
if (!ctx.conversationService || !ctx.conversationId) {
|
|
3807
|
+
return JSON.stringify({
|
|
3808
|
+
error: "No conversation context available. Cannot set active package."
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
try {
|
|
3812
|
+
await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
|
|
3813
|
+
activePackageId: packageId
|
|
3814
|
+
});
|
|
3815
|
+
return JSON.stringify({
|
|
3816
|
+
activePackageId: packageId,
|
|
3817
|
+
name: pkg.manifest.name,
|
|
3818
|
+
namespace: pkg.manifest.namespace,
|
|
3819
|
+
message: `Active package set to "${pkg.manifest.name}"`
|
|
3820
|
+
});
|
|
3821
|
+
} catch (err) {
|
|
3822
|
+
return JSON.stringify({
|
|
3823
|
+
error: `Failed to set active package: ${err.message}`
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
function registerPackageTools(registry, context) {
|
|
3829
|
+
registry.register(listPackagesTool, createListPackagesHandler(context));
|
|
3830
|
+
registry.register(getPackageTool, createGetPackageHandler(context));
|
|
3831
|
+
registry.register(createPackageTool, createCreatePackageHandler(context));
|
|
3832
|
+
registry.register(getActivePackageTool, createGetActivePackageHandler(context));
|
|
3833
|
+
registry.register(setActivePackageTool, createSetActivePackageHandler(context));
|
|
3834
|
+
}
|
|
2802
3835
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2803
3836
|
0 && (module.exports = {
|
|
2804
3837
|
AIService,
|
|
@@ -2813,20 +3846,29 @@ init_metadata_tools();
|
|
|
2813
3846
|
METADATA_TOOL_DEFINITIONS,
|
|
2814
3847
|
MemoryLLMAdapter,
|
|
2815
3848
|
ObjectQLConversationService,
|
|
3849
|
+
PACKAGE_TOOL_DEFINITIONS,
|
|
3850
|
+
SkillRegistry,
|
|
2816
3851
|
ToolRegistry,
|
|
2817
3852
|
VercelLLMAdapter,
|
|
2818
3853
|
addFieldTool,
|
|
2819
3854
|
buildAIRoutes,
|
|
2820
3855
|
buildAgentRoutes,
|
|
3856
|
+
buildAssistantRoutes,
|
|
2821
3857
|
buildToolRoutes,
|
|
2822
3858
|
createObjectTool,
|
|
3859
|
+
createPackageTool,
|
|
2823
3860
|
deleteFieldTool,
|
|
2824
3861
|
describeObjectTool,
|
|
2825
3862
|
encodeStreamPart,
|
|
2826
3863
|
encodeVercelDataStream,
|
|
3864
|
+
getActivePackageTool,
|
|
3865
|
+
getPackageTool,
|
|
2827
3866
|
listObjectsTool,
|
|
3867
|
+
listPackagesTool,
|
|
2828
3868
|
modifyFieldTool,
|
|
2829
3869
|
registerDataTools,
|
|
2830
|
-
registerMetadataTools
|
|
3870
|
+
registerMetadataTools,
|
|
3871
|
+
registerPackageTools,
|
|
3872
|
+
setActivePackageTool
|
|
2831
3873
|
});
|
|
2832
3874
|
//# sourceMappingURL=index.cjs.map
|