@objectstack/service-ai 4.0.3 → 4.0.5
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 +293 -0
- package/dist/index.cjs +1176 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1225 -430
- package/dist/index.d.ts +1225 -430
- package/dist/index.js +1160 -128
- package/dist/index.js.map +1 -1
- package/package.json +35 -8
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -53
- package/src/__tests__/ai-service.test.ts +0 -964
- 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/dist/index.js
CHANGED
|
@@ -244,6 +244,10 @@ var init_create_object_tool = __esm({
|
|
|
244
244
|
type: "string",
|
|
245
245
|
description: "Human-readable display name (e.g. Project Task)"
|
|
246
246
|
},
|
|
247
|
+
packageId: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "Package ID that will own this object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
250
|
+
},
|
|
247
251
|
fields: {
|
|
248
252
|
type: "array",
|
|
249
253
|
description: "Initial fields to create with the object",
|
|
@@ -293,6 +297,10 @@ var init_add_field_tool = __esm({
|
|
|
293
297
|
parameters: {
|
|
294
298
|
type: "object",
|
|
295
299
|
properties: {
|
|
300
|
+
packageId: {
|
|
301
|
+
type: "string",
|
|
302
|
+
description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
303
|
+
},
|
|
296
304
|
objectName: {
|
|
297
305
|
type: "string",
|
|
298
306
|
description: "Target object machine name (snake_case)"
|
|
@@ -359,6 +367,10 @@ var init_modify_field_tool = __esm({
|
|
|
359
367
|
parameters: {
|
|
360
368
|
type: "object",
|
|
361
369
|
properties: {
|
|
370
|
+
packageId: {
|
|
371
|
+
type: "string",
|
|
372
|
+
description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
373
|
+
},
|
|
362
374
|
objectName: {
|
|
363
375
|
type: "string",
|
|
364
376
|
description: "Target object machine name (snake_case)"
|
|
@@ -404,6 +416,10 @@ var init_delete_field_tool = __esm({
|
|
|
404
416
|
parameters: {
|
|
405
417
|
type: "object",
|
|
406
418
|
properties: {
|
|
419
|
+
packageId: {
|
|
420
|
+
type: "string",
|
|
421
|
+
description: "Package ID that owns the target object (e.g., com.acme.crm). If not provided, uses the active package from conversation context."
|
|
422
|
+
},
|
|
407
423
|
objectName: {
|
|
408
424
|
type: "string",
|
|
409
425
|
description: "Target object machine name (snake_case)"
|
|
@@ -492,12 +508,55 @@ __export(metadata_tools_exports, {
|
|
|
492
508
|
function isSnakeCase(value) {
|
|
493
509
|
return SNAKE_CASE_RE.test(value);
|
|
494
510
|
}
|
|
511
|
+
async function getActivePackageId(ctx) {
|
|
512
|
+
if (!ctx.conversationService?.getMetadata || !ctx.conversationId) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
const metadata = await ctx.conversationService.getMetadata(ctx.conversationId);
|
|
516
|
+
return metadata?.activePackageId ?? null;
|
|
517
|
+
}
|
|
518
|
+
async function resolvePackageId(ctx, explicitPackageId) {
|
|
519
|
+
let packageId = null;
|
|
520
|
+
if (explicitPackageId) {
|
|
521
|
+
packageId = explicitPackageId;
|
|
522
|
+
} else {
|
|
523
|
+
packageId = await getActivePackageId(ctx);
|
|
524
|
+
}
|
|
525
|
+
if (!packageId) {
|
|
526
|
+
return {
|
|
527
|
+
packageId: null,
|
|
528
|
+
warning: "No package specified. Metadata will be created without package association. Consider using set_active_package or providing packageId parameter."
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (ctx.packageRegistry) {
|
|
532
|
+
const exists = await ctx.packageRegistry.exists(packageId);
|
|
533
|
+
if (!exists) {
|
|
534
|
+
return {
|
|
535
|
+
packageId: null,
|
|
536
|
+
error: `Package "${packageId}" not found. Use list_packages to see available packages or create_package to create a new one.`
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const pkg = await ctx.packageRegistry.get(packageId);
|
|
540
|
+
if (pkg?.manifest.source === "filesystem") {
|
|
541
|
+
return {
|
|
542
|
+
packageId: null,
|
|
543
|
+
error: `Package "${packageId}" is read-only (loaded from code). Only database packages can be modified. Use create_package to create a new database package.`
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return { packageId };
|
|
548
|
+
}
|
|
495
549
|
function createCreateObjectHandler(ctx) {
|
|
496
550
|
return async (args) => {
|
|
497
|
-
const { name, label, fields, enableFeatures } = args;
|
|
551
|
+
const { name, label, packageId: explicitPackageId, fields, enableFeatures } = args;
|
|
498
552
|
if (!name || !label) {
|
|
499
553
|
return JSON.stringify({ error: 'Both "name" and "label" are required' });
|
|
500
554
|
}
|
|
555
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
556
|
+
if (resolved.error) {
|
|
557
|
+
return JSON.stringify({ error: resolved.error });
|
|
558
|
+
}
|
|
559
|
+
const packageId = resolved.packageId;
|
|
501
560
|
if (!isSnakeCase(name)) {
|
|
502
561
|
return JSON.stringify({ error: `Invalid object name "${name}". Must be snake_case.` });
|
|
503
562
|
}
|
|
@@ -529,6 +588,7 @@ function createCreateObjectHandler(ctx) {
|
|
|
529
588
|
const objectDef = {
|
|
530
589
|
name,
|
|
531
590
|
label,
|
|
591
|
+
...packageId ? { packageId } : {},
|
|
532
592
|
...Object.keys(fieldMap).length > 0 ? { fields: fieldMap } : {},
|
|
533
593
|
...enableFeatures ? { enable: enableFeatures } : {}
|
|
534
594
|
};
|
|
@@ -536,16 +596,21 @@ function createCreateObjectHandler(ctx) {
|
|
|
536
596
|
return JSON.stringify({
|
|
537
597
|
name,
|
|
538
598
|
label,
|
|
599
|
+
...packageId ? { packageId } : {},
|
|
539
600
|
fieldCount: Object.keys(fieldMap).length
|
|
540
601
|
});
|
|
541
602
|
};
|
|
542
603
|
}
|
|
543
604
|
function createAddFieldHandler(ctx) {
|
|
544
605
|
return async (args) => {
|
|
545
|
-
const { objectName, name, label, type, required, defaultValue, options, reference } = args;
|
|
606
|
+
const { objectName, name, label, type, required, defaultValue, options, reference, packageId: explicitPackageId } = args;
|
|
546
607
|
if (!objectName || !name || !type) {
|
|
547
608
|
return JSON.stringify({ error: '"objectName", "name", and "type" are required' });
|
|
548
609
|
}
|
|
610
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
611
|
+
if (resolved.error) {
|
|
612
|
+
return JSON.stringify({ error: resolved.error });
|
|
613
|
+
}
|
|
549
614
|
if (!isSnakeCase(objectName)) {
|
|
550
615
|
return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
|
|
551
616
|
}
|
|
@@ -586,16 +651,21 @@ function createAddFieldHandler(ctx) {
|
|
|
586
651
|
return JSON.stringify({
|
|
587
652
|
objectName,
|
|
588
653
|
fieldName: name,
|
|
589
|
-
fieldType: type
|
|
654
|
+
fieldType: type,
|
|
655
|
+
packageId: resolved.packageId
|
|
590
656
|
});
|
|
591
657
|
};
|
|
592
658
|
}
|
|
593
659
|
function createModifyFieldHandler(ctx) {
|
|
594
660
|
return async (args) => {
|
|
595
|
-
const { objectName, fieldName, changes } = args;
|
|
661
|
+
const { objectName, fieldName, changes, packageId: explicitPackageId } = args;
|
|
596
662
|
if (!objectName || !fieldName || !changes) {
|
|
597
663
|
return JSON.stringify({ error: '"objectName", "fieldName", and "changes" are required' });
|
|
598
664
|
}
|
|
665
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
666
|
+
if (resolved.error) {
|
|
667
|
+
return JSON.stringify({ error: resolved.error });
|
|
668
|
+
}
|
|
599
669
|
if (!isSnakeCase(objectName)) {
|
|
600
670
|
return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
|
|
601
671
|
}
|
|
@@ -620,16 +690,21 @@ function createModifyFieldHandler(ctx) {
|
|
|
620
690
|
return JSON.stringify({
|
|
621
691
|
objectName,
|
|
622
692
|
fieldName,
|
|
623
|
-
updatedProperties: Object.keys(changes)
|
|
693
|
+
updatedProperties: Object.keys(changes),
|
|
694
|
+
packageId: resolved.packageId
|
|
624
695
|
});
|
|
625
696
|
};
|
|
626
697
|
}
|
|
627
698
|
function createDeleteFieldHandler(ctx) {
|
|
628
699
|
return async (args) => {
|
|
629
|
-
const { objectName, fieldName } = args;
|
|
700
|
+
const { objectName, fieldName, packageId: explicitPackageId } = args;
|
|
630
701
|
if (!objectName || !fieldName) {
|
|
631
702
|
return JSON.stringify({ error: '"objectName" and "fieldName" are required' });
|
|
632
703
|
}
|
|
704
|
+
const resolved = await resolvePackageId(ctx, explicitPackageId);
|
|
705
|
+
if (resolved.error) {
|
|
706
|
+
return JSON.stringify({ error: resolved.error });
|
|
707
|
+
}
|
|
633
708
|
if (!isSnakeCase(objectName)) {
|
|
634
709
|
return JSON.stringify({ error: `Invalid object name "${objectName}". Must be snake_case.` });
|
|
635
710
|
}
|
|
@@ -652,7 +727,8 @@ function createDeleteFieldHandler(ctx) {
|
|
|
652
727
|
return JSON.stringify({
|
|
653
728
|
objectName,
|
|
654
729
|
fieldName,
|
|
655
|
-
success: true
|
|
730
|
+
success: true,
|
|
731
|
+
packageId: resolved.packageId
|
|
656
732
|
});
|
|
657
733
|
};
|
|
658
734
|
}
|
|
@@ -1265,25 +1341,41 @@ async function* encodeVercelDataStream(events) {
|
|
|
1265
1341
|
yield sse({ type: "text-start", id: "0" });
|
|
1266
1342
|
let textOpen = true;
|
|
1267
1343
|
let finishReason = "stop";
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1344
|
+
let errorMessage;
|
|
1345
|
+
try {
|
|
1346
|
+
for await (const part of events) {
|
|
1347
|
+
if (part.type === "error") {
|
|
1348
|
+
const errPart = part;
|
|
1349
|
+
const raw = errPart.error;
|
|
1350
|
+
errorMessage = raw && typeof raw === "object" && "message" in raw ? String(raw.message) : typeof raw === "string" ? raw : "Unknown provider error";
|
|
1351
|
+
finishReason = "error";
|
|
1352
|
+
break;
|
|
1353
|
+
}
|
|
1354
|
+
if (part.type === "finish") {
|
|
1355
|
+
finishReason = part.finishReason ?? "stop";
|
|
1356
|
+
}
|
|
1357
|
+
if (part.type === "finish-step" || part.type === "finish") {
|
|
1358
|
+
if (textOpen) {
|
|
1359
|
+
yield sse({ type: "text-end", id: "0" });
|
|
1360
|
+
textOpen = false;
|
|
1361
|
+
}
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
const frame = encodeStreamPart(part);
|
|
1365
|
+
if (frame) {
|
|
1366
|
+
yield frame;
|
|
1276
1367
|
}
|
|
1277
|
-
continue;
|
|
1278
|
-
}
|
|
1279
|
-
const frame = encodeStreamPart(part);
|
|
1280
|
-
if (frame) {
|
|
1281
|
-
yield frame;
|
|
1282
1368
|
}
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
errorMessage = err instanceof Error ? err.message : String(err);
|
|
1371
|
+
finishReason = "error";
|
|
1283
1372
|
}
|
|
1284
1373
|
if (textOpen) {
|
|
1285
1374
|
yield sse({ type: "text-end", id: "0" });
|
|
1286
1375
|
}
|
|
1376
|
+
if (errorMessage) {
|
|
1377
|
+
yield sse({ type: "error", errorText: errorMessage });
|
|
1378
|
+
}
|
|
1287
1379
|
yield sse({ type: "finish-step" });
|
|
1288
1380
|
yield sse({ type: "finish", finishReason });
|
|
1289
1381
|
yield "data: [DONE]\n\n";
|
|
@@ -1687,10 +1779,12 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
|
|
|
1687
1779
|
return { status: 403, body: { error: `Agent "${agentName}" is not active` } };
|
|
1688
1780
|
}
|
|
1689
1781
|
try {
|
|
1690
|
-
const
|
|
1782
|
+
const activeSkills = await agentRuntime.resolveActiveSkills(agent, chatContext);
|
|
1783
|
+
const systemMessages = agentRuntime.buildSystemMessages(agent, chatContext, activeSkills);
|
|
1691
1784
|
const agentOptions = agentRuntime.buildRequestOptions(
|
|
1692
1785
|
agent,
|
|
1693
|
-
aiService.toolRegistry.getAll()
|
|
1786
|
+
aiService.toolRegistry.getAll(),
|
|
1787
|
+
activeSkills
|
|
1694
1788
|
);
|
|
1695
1789
|
const safeOverrides = {};
|
|
1696
1790
|
if (extraOptions) {
|
|
@@ -1744,6 +1838,217 @@ function buildAgentRoutes(aiService, agentRuntime, logger) {
|
|
|
1744
1838
|
];
|
|
1745
1839
|
}
|
|
1746
1840
|
|
|
1841
|
+
// src/routes/assistant-routes.ts
|
|
1842
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
1843
|
+
function validateAssistantMessage(raw) {
|
|
1844
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1845
|
+
return "each message must be an object";
|
|
1846
|
+
}
|
|
1847
|
+
const msg = raw;
|
|
1848
|
+
if (typeof msg.role !== "string" || !ALLOWED_ROLES.has(msg.role)) {
|
|
1849
|
+
return `message.role must be one of ${[...ALLOWED_ROLES].map((r) => `"${r}"`).join(", ")} for assistant chat`;
|
|
1850
|
+
}
|
|
1851
|
+
const allowEmpty = msg.role === "assistant";
|
|
1852
|
+
return validateMessageContent(msg, { allowEmptyContent: allowEmpty });
|
|
1853
|
+
}
|
|
1854
|
+
function parseContext(raw) {
|
|
1855
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
1856
|
+
const obj = raw;
|
|
1857
|
+
const ctx = {};
|
|
1858
|
+
for (const key of Object.keys(obj)) {
|
|
1859
|
+
ctx[key] = obj[key];
|
|
1860
|
+
}
|
|
1861
|
+
return ctx;
|
|
1862
|
+
}
|
|
1863
|
+
function buildAssistantRoutes(aiService, agentRuntime, skillRegistry, logger) {
|
|
1864
|
+
return [
|
|
1865
|
+
// ── Resolve current assistant + skill set ──────────────────
|
|
1866
|
+
{
|
|
1867
|
+
method: "GET",
|
|
1868
|
+
path: "/api/v1/ai/assistant",
|
|
1869
|
+
description: "Resolve the default AI assistant and active skills for a given context",
|
|
1870
|
+
auth: true,
|
|
1871
|
+
permissions: ["ai:chat"],
|
|
1872
|
+
handler: async (req) => {
|
|
1873
|
+
try {
|
|
1874
|
+
const context = parseContextFromQuery(req.query);
|
|
1875
|
+
const explicitAgentName = typeof req.query?.agent === "string" ? req.query.agent : void 0;
|
|
1876
|
+
const agent = explicitAgentName ? await agentRuntime.loadAgent(explicitAgentName) : await agentRuntime.resolveDefaultAgent(context);
|
|
1877
|
+
if (!agent) {
|
|
1878
|
+
return {
|
|
1879
|
+
status: 200,
|
|
1880
|
+
body: { agent: null, skills: [] }
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
const skills = await agentRuntime.resolveActiveSkills(agent, context);
|
|
1884
|
+
return {
|
|
1885
|
+
status: 200,
|
|
1886
|
+
body: {
|
|
1887
|
+
agent: {
|
|
1888
|
+
name: agent.name,
|
|
1889
|
+
label: agent.label,
|
|
1890
|
+
role: agent.role,
|
|
1891
|
+
avatar: agent.avatar,
|
|
1892
|
+
instructions: agent.instructions
|
|
1893
|
+
},
|
|
1894
|
+
skills: skills.map((s) => skillRegistry.toSummary(s)),
|
|
1895
|
+
context
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
} catch (err) {
|
|
1899
|
+
logger.error("[AI Route] /assistant error", err instanceof Error ? err : void 0);
|
|
1900
|
+
return { status: 500, body: { error: "Internal AI service error" } };
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
},
|
|
1904
|
+
// ── List active skills (slash-command palette) ─────────────
|
|
1905
|
+
{
|
|
1906
|
+
method: "GET",
|
|
1907
|
+
path: "/api/v1/ai/assistant/skills",
|
|
1908
|
+
description: "List active AI skills for a given context (used by slash-command palettes)",
|
|
1909
|
+
auth: true,
|
|
1910
|
+
permissions: ["ai:chat"],
|
|
1911
|
+
handler: async (req) => {
|
|
1912
|
+
try {
|
|
1913
|
+
const context = parseContextFromQuery(req.query);
|
|
1914
|
+
const agentName = typeof req.query?.agent === "string" ? req.query.agent : void 0;
|
|
1915
|
+
let restrictTo;
|
|
1916
|
+
if (agentName) {
|
|
1917
|
+
const agent = await agentRuntime.loadAgent(agentName);
|
|
1918
|
+
if (agent?.skills) restrictTo = agent.skills;
|
|
1919
|
+
}
|
|
1920
|
+
const skills = await skillRegistry.listActiveSkills(context, restrictTo);
|
|
1921
|
+
return {
|
|
1922
|
+
status: 200,
|
|
1923
|
+
body: { skills: skills.map((s) => skillRegistry.toSummary(s)) }
|
|
1924
|
+
};
|
|
1925
|
+
} catch (err) {
|
|
1926
|
+
logger.error("[AI Route] /assistant/skills error", err instanceof Error ? err : void 0);
|
|
1927
|
+
return { status: 500, body: { error: "Internal AI service error" } };
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
},
|
|
1931
|
+
// ── Ambient chat (the "single entry" Claude-Code pattern) ──
|
|
1932
|
+
{
|
|
1933
|
+
method: "POST",
|
|
1934
|
+
path: "/api/v1/ai/assistant/chat",
|
|
1935
|
+
description: "Ambient AI chat \u2014 auto-resolves agent and skills from context (supports Vercel Data Stream Protocol)",
|
|
1936
|
+
auth: true,
|
|
1937
|
+
permissions: ["ai:chat"],
|
|
1938
|
+
handler: async (req) => {
|
|
1939
|
+
const body = req.body ?? {};
|
|
1940
|
+
const {
|
|
1941
|
+
messages: rawMessages,
|
|
1942
|
+
context: rawContext,
|
|
1943
|
+
options: extraOptions,
|
|
1944
|
+
agent: explicitAgentName,
|
|
1945
|
+
skill: explicitSkillName
|
|
1946
|
+
} = body;
|
|
1947
|
+
if (!Array.isArray(rawMessages) || rawMessages.length === 0) {
|
|
1948
|
+
return { status: 400, body: { error: "messages array is required" } };
|
|
1949
|
+
}
|
|
1950
|
+
for (const msg of rawMessages) {
|
|
1951
|
+
const err = validateAssistantMessage(msg);
|
|
1952
|
+
if (err) return { status: 400, body: { error: err } };
|
|
1953
|
+
}
|
|
1954
|
+
const context = parseContext(rawContext);
|
|
1955
|
+
const agent = explicitAgentName ? await agentRuntime.loadAgent(explicitAgentName) : await agentRuntime.resolveDefaultAgent(context);
|
|
1956
|
+
if (!agent) {
|
|
1957
|
+
return {
|
|
1958
|
+
status: 404,
|
|
1959
|
+
body: { error: "No active assistant available \u2014 register at least one agent or set defaultAgent on the app metadata" }
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
if (agent.active === false) {
|
|
1963
|
+
return { status: 403, body: { error: `Agent "${agent.name}" is not active` } };
|
|
1964
|
+
}
|
|
1965
|
+
try {
|
|
1966
|
+
let activeSkills = await agentRuntime.resolveActiveSkills(agent, context);
|
|
1967
|
+
if (explicitSkillName) {
|
|
1968
|
+
activeSkills = activeSkills.filter((s) => s.name === explicitSkillName);
|
|
1969
|
+
if (activeSkills.length === 0) {
|
|
1970
|
+
const direct = await skillRegistry.loadSkill(explicitSkillName);
|
|
1971
|
+
if (direct && direct.active !== false) activeSkills = [direct];
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
const systemMessages = agentRuntime.buildSystemMessages(agent, context, activeSkills);
|
|
1975
|
+
const agentOptions = agentRuntime.buildRequestOptions(
|
|
1976
|
+
agent,
|
|
1977
|
+
aiService.toolRegistry.getAll(),
|
|
1978
|
+
activeSkills
|
|
1979
|
+
);
|
|
1980
|
+
const safeOverrides = {};
|
|
1981
|
+
if (extraOptions) {
|
|
1982
|
+
const ALLOWED_KEYS = /* @__PURE__ */ new Set(["temperature", "maxTokens", "stop"]);
|
|
1983
|
+
for (const key of Object.keys(extraOptions)) {
|
|
1984
|
+
if (ALLOWED_KEYS.has(key)) safeOverrides[key] = extraOptions[key];
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const mergedOptions = { ...agentOptions, ...safeOverrides };
|
|
1988
|
+
const fullMessages = [
|
|
1989
|
+
...systemMessages,
|
|
1990
|
+
...rawMessages.map((m) => normalizeMessage(m))
|
|
1991
|
+
];
|
|
1992
|
+
const chatWithToolsOptions = {
|
|
1993
|
+
...mergedOptions,
|
|
1994
|
+
maxIterations: agent.planning?.maxIterations
|
|
1995
|
+
};
|
|
1996
|
+
const wantStream = body.stream !== false;
|
|
1997
|
+
if (wantStream) {
|
|
1998
|
+
if (!aiService.streamChatWithTools) {
|
|
1999
|
+
return { status: 501, body: { error: "Streaming is not supported by the configured AI service" } };
|
|
2000
|
+
}
|
|
2001
|
+
const events = aiService.streamChatWithTools(fullMessages, chatWithToolsOptions);
|
|
2002
|
+
return {
|
|
2003
|
+
status: 200,
|
|
2004
|
+
stream: true,
|
|
2005
|
+
vercelDataStream: true,
|
|
2006
|
+
contentType: "text/event-stream",
|
|
2007
|
+
headers: {
|
|
2008
|
+
"Content-Type": "text/event-stream",
|
|
2009
|
+
"Cache-Control": "no-cache",
|
|
2010
|
+
"Connection": "keep-alive",
|
|
2011
|
+
"x-vercel-ai-ui-message-stream": "v1",
|
|
2012
|
+
"x-objectstack-agent": agent.name,
|
|
2013
|
+
"x-objectstack-skills": activeSkills.map((s) => s.name).join(",")
|
|
2014
|
+
},
|
|
2015
|
+
events: encodeVercelDataStream(events)
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
const result = await aiService.chatWithTools(fullMessages, chatWithToolsOptions);
|
|
2019
|
+
return {
|
|
2020
|
+
status: 200,
|
|
2021
|
+
body: {
|
|
2022
|
+
...result ?? {},
|
|
2023
|
+
_agent: agent.name,
|
|
2024
|
+
_skills: activeSkills.map((s) => s.name)
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
} catch (err) {
|
|
2028
|
+
logger.error(
|
|
2029
|
+
"[AI Route] /assistant/chat error",
|
|
2030
|
+
err instanceof Error ? err : void 0
|
|
2031
|
+
);
|
|
2032
|
+
const upstreamMsg = err instanceof Error ? err.message : String(err);
|
|
2033
|
+
return {
|
|
2034
|
+
status: 500,
|
|
2035
|
+
body: { error: "Internal AI service error", detail: upstreamMsg }
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
];
|
|
2041
|
+
}
|
|
2042
|
+
function parseContextFromQuery(query) {
|
|
2043
|
+
if (!query) return {};
|
|
2044
|
+
const ctx = {};
|
|
2045
|
+
for (const [key, value] of Object.entries(query)) {
|
|
2046
|
+
if (value == null) continue;
|
|
2047
|
+
ctx[key] = value;
|
|
2048
|
+
}
|
|
2049
|
+
return ctx;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
1747
2052
|
// src/routes/tool-routes.ts
|
|
1748
2053
|
function extractOutputValue(output) {
|
|
1749
2054
|
if (!output) return "";
|
|
@@ -2059,8 +2364,7 @@ var ObjectQLConversationService = class {
|
|
|
2059
2364
|
// src/objects/ai-conversation.object.ts
|
|
2060
2365
|
import { ObjectSchema, Field } from "@objectstack/spec/data";
|
|
2061
2366
|
var AiConversationObject = ObjectSchema.create({
|
|
2062
|
-
|
|
2063
|
-
name: "conversations",
|
|
2367
|
+
name: "ai_conversations",
|
|
2064
2368
|
label: "AI Conversation",
|
|
2065
2369
|
pluralLabel: "AI Conversations",
|
|
2066
2370
|
icon: "message-square",
|
|
@@ -2078,16 +2382,14 @@ var AiConversationObject = ObjectSchema.create({
|
|
|
2078
2382
|
maxLength: 500,
|
|
2079
2383
|
description: "Conversation title or summary"
|
|
2080
2384
|
}),
|
|
2081
|
-
agent_id: Field.
|
|
2082
|
-
label: "Agent
|
|
2385
|
+
agent_id: Field.lookup("sys_agent", {
|
|
2386
|
+
label: "Agent",
|
|
2083
2387
|
required: false,
|
|
2084
|
-
|
|
2085
|
-
description: "Associated AI agent identifier"
|
|
2388
|
+
description: "Associated AI agent"
|
|
2086
2389
|
}),
|
|
2087
|
-
user_id: Field.
|
|
2088
|
-
label: "User
|
|
2390
|
+
user_id: Field.lookup("sys_user", {
|
|
2391
|
+
label: "User",
|
|
2089
2392
|
required: false,
|
|
2090
|
-
maxLength: 255,
|
|
2091
2393
|
description: "User who owns the conversation"
|
|
2092
2394
|
}),
|
|
2093
2395
|
metadata: Field.textarea({
|
|
@@ -2126,8 +2428,7 @@ var AiConversationObject = ObjectSchema.create({
|
|
|
2126
2428
|
// src/objects/ai-message.object.ts
|
|
2127
2429
|
import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
|
|
2128
2430
|
var AiMessageObject = ObjectSchema2.create({
|
|
2129
|
-
|
|
2130
|
-
name: "messages",
|
|
2431
|
+
name: "ai_messages",
|
|
2131
2432
|
label: "AI Message",
|
|
2132
2433
|
pluralLabel: "AI Messages",
|
|
2133
2434
|
icon: "message-circle",
|
|
@@ -2139,8 +2440,8 @@ var AiMessageObject = ObjectSchema2.create({
|
|
|
2139
2440
|
required: true,
|
|
2140
2441
|
readonly: true
|
|
2141
2442
|
}),
|
|
2142
|
-
conversation_id: Field2.
|
|
2143
|
-
label: "Conversation
|
|
2443
|
+
conversation_id: Field2.lookup("ai_conversations", {
|
|
2444
|
+
label: "Conversation",
|
|
2144
2445
|
required: true,
|
|
2145
2446
|
description: "Foreign key to ai_conversations"
|
|
2146
2447
|
}),
|
|
@@ -2198,8 +2499,9 @@ init_metadata_tools();
|
|
|
2198
2499
|
// src/agent-runtime.ts
|
|
2199
2500
|
import { AgentSchema } from "@objectstack/spec/ai";
|
|
2200
2501
|
var AgentRuntime = class {
|
|
2201
|
-
constructor(metadataService) {
|
|
2502
|
+
constructor(metadataService, skillRegistry) {
|
|
2202
2503
|
this.metadataService = metadataService;
|
|
2504
|
+
this.skillRegistry = skillRegistry;
|
|
2203
2505
|
}
|
|
2204
2506
|
// ── Public API ────────────────────────────────────────────────
|
|
2205
2507
|
/**
|
|
@@ -2243,12 +2545,21 @@ var AgentRuntime = class {
|
|
|
2243
2545
|
/**
|
|
2244
2546
|
* Build the system message(s) that should be prepended to the
|
|
2245
2547
|
* conversation when chatting with the given agent.
|
|
2548
|
+
*
|
|
2549
|
+
* The composed prompt has up to three sections:
|
|
2550
|
+
* 1. The agent's base `instructions` (its persona / prime directives).
|
|
2551
|
+
* 2. UI context hints from {@link AgentChatContext} (current object,
|
|
2552
|
+
* record, view) so the agent can tailor responses without extra
|
|
2553
|
+
* tool calls.
|
|
2554
|
+
* 3. An "Active Skills" block describing the capabilities currently
|
|
2555
|
+
* available — only populated when `activeSkills` is provided.
|
|
2246
2556
|
*/
|
|
2247
|
-
buildSystemMessages(agent, context) {
|
|
2557
|
+
buildSystemMessages(agent, context, activeSkills) {
|
|
2248
2558
|
const parts = [];
|
|
2249
2559
|
parts.push(agent.instructions);
|
|
2250
2560
|
if (context) {
|
|
2251
2561
|
const ctx = [];
|
|
2562
|
+
if (context.appName) ctx.push(`Current app: ${context.appName}`);
|
|
2252
2563
|
if (context.objectName) ctx.push(`Current object: ${context.objectName}`);
|
|
2253
2564
|
if (context.recordId) ctx.push(`Selected record ID: ${context.recordId}`);
|
|
2254
2565
|
if (context.viewName) ctx.push(`Current view: ${context.viewName}`);
|
|
@@ -2256,6 +2567,10 @@ var AgentRuntime = class {
|
|
|
2256
2567
|
parts.push("\n--- Current Context ---\n" + ctx.join("\n"));
|
|
2257
2568
|
}
|
|
2258
2569
|
}
|
|
2570
|
+
if (activeSkills && activeSkills.length > 0 && this.skillRegistry) {
|
|
2571
|
+
const block = this.skillRegistry.composeInstructionsBlock(activeSkills);
|
|
2572
|
+
if (block) parts.push(block);
|
|
2573
|
+
}
|
|
2259
2574
|
return [{ role: "system", content: parts.join("\n") }];
|
|
2260
2575
|
}
|
|
2261
2576
|
/**
|
|
@@ -2263,37 +2578,272 @@ var AgentRuntime = class {
|
|
|
2263
2578
|
*
|
|
2264
2579
|
* Tool references declared in `agent.tools` are resolved by name against
|
|
2265
2580
|
* `availableTools` (i.e. the full set of ToolRegistry definitions).
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
2268
|
-
*
|
|
2581
|
+
* Tools belonging to `activeSkills` are also resolved and merged into
|
|
2582
|
+
* the final tool list (deduplicated by name).
|
|
2583
|
+
*
|
|
2584
|
+
* Any unresolved references (tools the agent or skill declares but
|
|
2585
|
+
* that are not registered) are silently skipped — this is intentional
|
|
2586
|
+
* so that agents/skills can be defined before all tools are available.
|
|
2269
2587
|
*
|
|
2270
2588
|
* @param agent - The agent definition to derive options from
|
|
2271
2589
|
* @param availableTools - All tool definitions currently registered in the ToolRegistry
|
|
2590
|
+
* @param activeSkills - Skills resolved from agent.skills[] + context filtering
|
|
2272
2591
|
* @returns Request options with model config and resolved tool definitions
|
|
2273
2592
|
*/
|
|
2274
|
-
buildRequestOptions(agent, availableTools) {
|
|
2593
|
+
buildRequestOptions(agent, availableTools, activeSkills) {
|
|
2275
2594
|
const options = {};
|
|
2276
2595
|
if (agent.model) {
|
|
2277
2596
|
options.model = agent.model.model;
|
|
2278
2597
|
options.temperature = agent.model.temperature;
|
|
2279
2598
|
options.maxTokens = agent.model.maxTokens;
|
|
2280
2599
|
}
|
|
2600
|
+
const toolMap = new Map(availableTools.map((t) => [t.name, t]));
|
|
2601
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2602
|
+
const resolved = [];
|
|
2281
2603
|
if (agent.tools && agent.tools.length > 0) {
|
|
2282
|
-
const toolMap = new Map(availableTools.map((t) => [t.name, t]));
|
|
2283
|
-
const resolved = [];
|
|
2284
2604
|
for (const ref of agent.tools) {
|
|
2605
|
+
if (seen.has(ref.name)) continue;
|
|
2285
2606
|
const def = toolMap.get(ref.name);
|
|
2286
2607
|
if (def) {
|
|
2287
2608
|
resolved.push(def);
|
|
2609
|
+
seen.add(ref.name);
|
|
2288
2610
|
}
|
|
2289
2611
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2612
|
+
}
|
|
2613
|
+
if (activeSkills && activeSkills.length > 0 && this.skillRegistry) {
|
|
2614
|
+
const skillTools = this.skillRegistry.flattenToTools(activeSkills, availableTools);
|
|
2615
|
+
for (const def of skillTools) {
|
|
2616
|
+
if (seen.has(def.name)) continue;
|
|
2617
|
+
resolved.push(def);
|
|
2618
|
+
seen.add(def.name);
|
|
2293
2619
|
}
|
|
2294
2620
|
}
|
|
2621
|
+
if (resolved.length > 0) {
|
|
2622
|
+
options.tools = resolved;
|
|
2623
|
+
options.toolChoice = "auto";
|
|
2624
|
+
}
|
|
2295
2625
|
return options;
|
|
2296
2626
|
}
|
|
2627
|
+
// ── Skill resolution helpers ─────────────────────────────────
|
|
2628
|
+
/**
|
|
2629
|
+
* Resolve the set of skills active for a given agent in a given
|
|
2630
|
+
* context. Combines:
|
|
2631
|
+
*
|
|
2632
|
+
* 1. The agent's declared `skills[]` whitelist (if any).
|
|
2633
|
+
* 2. Filtering by `triggerConditions` against the runtime context.
|
|
2634
|
+
*
|
|
2635
|
+
* When the agent declares no skills, returns the empty list (i.e.
|
|
2636
|
+
* the agent only uses its inline `tools[]`).
|
|
2637
|
+
*
|
|
2638
|
+
* Returns an empty array if no SkillRegistry was provided to the
|
|
2639
|
+
* runtime (legacy mode).
|
|
2640
|
+
*/
|
|
2641
|
+
async resolveActiveSkills(agent, context) {
|
|
2642
|
+
if (!this.skillRegistry) return [];
|
|
2643
|
+
if (!agent.skills || agent.skills.length === 0) return [];
|
|
2644
|
+
return this.skillRegistry.listActiveSkills(context ?? {}, agent.skills);
|
|
2645
|
+
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Pick a default agent for the given context, used by the ambient
|
|
2648
|
+
* chat endpoint when the client doesn't specify an `agentName`.
|
|
2649
|
+
*
|
|
2650
|
+
* Resolution order:
|
|
2651
|
+
* 1. The `defaultAgent` of the app named by `context.appName`.
|
|
2652
|
+
* 2. The first active agent in the registry (deterministic fallback).
|
|
2653
|
+
* 3. `undefined` if no agents are registered.
|
|
2654
|
+
*/
|
|
2655
|
+
async resolveDefaultAgent(context) {
|
|
2656
|
+
if (context?.appName) {
|
|
2657
|
+
const rawApp = await this.metadataService.get("app", context.appName).catch(() => void 0);
|
|
2658
|
+
const defaultAgentName = rawApp?.defaultAgent;
|
|
2659
|
+
if (defaultAgentName) {
|
|
2660
|
+
const agent = await this.loadAgent(defaultAgentName);
|
|
2661
|
+
if (agent && agent.active !== false) return agent;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
const summaries = await this.listAgents();
|
|
2665
|
+
if (summaries.length === 0) return void 0;
|
|
2666
|
+
return this.loadAgent(summaries[0].name);
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
|
|
2670
|
+
// src/skill-registry.ts
|
|
2671
|
+
import { SkillSchema } from "@objectstack/spec/ai";
|
|
2672
|
+
var SkillRegistry = class {
|
|
2673
|
+
constructor(metadataService) {
|
|
2674
|
+
this.metadataService = metadataService;
|
|
2675
|
+
}
|
|
2676
|
+
// ── Loading ────────────────────────────────────────────────────
|
|
2677
|
+
/**
|
|
2678
|
+
* Load and validate a single skill definition by name.
|
|
2679
|
+
*
|
|
2680
|
+
* Returns `undefined` when the skill is missing or fails Zod
|
|
2681
|
+
* validation (so callers don't accidentally feed malformed metadata
|
|
2682
|
+
* to the LLM).
|
|
2683
|
+
*/
|
|
2684
|
+
async loadSkill(skillName) {
|
|
2685
|
+
const raw = await this.metadataService.get("skill", skillName);
|
|
2686
|
+
if (!raw) return void 0;
|
|
2687
|
+
const result = SkillSchema.safeParse(raw);
|
|
2688
|
+
if (!result.success) return void 0;
|
|
2689
|
+
return result.data;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Load all skill definitions, dropping any that fail validation
|
|
2693
|
+
* or are explicitly inactive.
|
|
2694
|
+
*/
|
|
2695
|
+
async listSkills() {
|
|
2696
|
+
const raw = await this.metadataService.list("skill");
|
|
2697
|
+
const skills = [];
|
|
2698
|
+
for (const item of raw) {
|
|
2699
|
+
const result = SkillSchema.safeParse(item);
|
|
2700
|
+
if (result.success && result.data.active !== false) {
|
|
2701
|
+
skills.push(result.data);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
return skills;
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* Load only the skills referenced by `skillNames`, preserving
|
|
2708
|
+
* declaration order. Missing or invalid skill names are silently
|
|
2709
|
+
* dropped (logged at the route layer if needed) so an Agent can be
|
|
2710
|
+
* defined before all its skills are persisted.
|
|
2711
|
+
*/
|
|
2712
|
+
async loadSkills(skillNames) {
|
|
2713
|
+
const skills = [];
|
|
2714
|
+
for (const name of skillNames) {
|
|
2715
|
+
const skill = await this.loadSkill(name);
|
|
2716
|
+
if (skill && skill.active !== false) {
|
|
2717
|
+
skills.push(skill);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return skills;
|
|
2721
|
+
}
|
|
2722
|
+
// ── Context filtering ──────────────────────────────────────────
|
|
2723
|
+
/**
|
|
2724
|
+
* Return skills whose `triggerConditions` are satisfied by the
|
|
2725
|
+
* given context. Skills without any conditions are always considered
|
|
2726
|
+
* active and returned in their declaration order.
|
|
2727
|
+
*
|
|
2728
|
+
* If `restrictTo` is provided, the result is intersected with that
|
|
2729
|
+
* allow-list (typically the agent's `skills[]` field) so an agent
|
|
2730
|
+
* never sees skills outside its declared scope.
|
|
2731
|
+
*/
|
|
2732
|
+
async listActiveSkills(context = {}, restrictTo) {
|
|
2733
|
+
const allowList = restrictTo ? new Set(restrictTo) : void 0;
|
|
2734
|
+
const all = await this.listSkills();
|
|
2735
|
+
return all.filter((skill) => {
|
|
2736
|
+
if (allowList && !allowList.has(skill.name)) return false;
|
|
2737
|
+
return this.matchesContext(skill, context);
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
/**
|
|
2741
|
+
* Evaluate a skill's `triggerConditions` against the given context.
|
|
2742
|
+
*
|
|
2743
|
+
* Semantics:
|
|
2744
|
+
* - No conditions defined → always matches.
|
|
2745
|
+
* - All conditions must pass (logical AND).
|
|
2746
|
+
* - Operators: `eq`, `neq`, `in`, `not_in`, `contains`.
|
|
2747
|
+
* - `contains` does substring matching for strings and `Array.includes`
|
|
2748
|
+
* for arrays.
|
|
2749
|
+
* - Missing context fields fail unless the operator is `neq` /
|
|
2750
|
+
* `not_in` (treating "absent" as "not equal to anything").
|
|
2751
|
+
*/
|
|
2752
|
+
matchesContext(skill, context) {
|
|
2753
|
+
const conditions = skill.triggerConditions;
|
|
2754
|
+
if (!conditions || conditions.length === 0) return true;
|
|
2755
|
+
return conditions.every((cond) => this.evaluateCondition(cond, context));
|
|
2756
|
+
}
|
|
2757
|
+
evaluateCondition(cond, context) {
|
|
2758
|
+
const fieldValue = context[cond.field];
|
|
2759
|
+
const expected = cond.value;
|
|
2760
|
+
switch (cond.operator) {
|
|
2761
|
+
case "eq":
|
|
2762
|
+
return fieldValue === expected;
|
|
2763
|
+
case "neq":
|
|
2764
|
+
return fieldValue !== expected;
|
|
2765
|
+
case "in": {
|
|
2766
|
+
const list = Array.isArray(expected) ? expected : [expected];
|
|
2767
|
+
return list.includes(fieldValue);
|
|
2768
|
+
}
|
|
2769
|
+
case "not_in": {
|
|
2770
|
+
const list = Array.isArray(expected) ? expected : [expected];
|
|
2771
|
+
return !list.includes(fieldValue);
|
|
2772
|
+
}
|
|
2773
|
+
case "contains": {
|
|
2774
|
+
if (typeof fieldValue === "string" && typeof expected === "string") {
|
|
2775
|
+
return fieldValue.includes(expected);
|
|
2776
|
+
}
|
|
2777
|
+
if (Array.isArray(fieldValue)) {
|
|
2778
|
+
return Array.isArray(expected) ? expected.every((v) => fieldValue.includes(v)) : fieldValue.includes(expected);
|
|
2779
|
+
}
|
|
2780
|
+
return false;
|
|
2781
|
+
}
|
|
2782
|
+
default:
|
|
2783
|
+
return false;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
// ── Tool resolution ───────────────────────────────────────────
|
|
2787
|
+
/**
|
|
2788
|
+
* Flatten a list of skills to a deduplicated array of concrete tool
|
|
2789
|
+
* definitions, preserving the order skills declared their tools.
|
|
2790
|
+
*
|
|
2791
|
+
* Tools that are declared by a skill but missing from the available
|
|
2792
|
+
* tool registry are silently dropped — this is intentional so a skill
|
|
2793
|
+
* can be authored before all its underlying tools are registered.
|
|
2794
|
+
*/
|
|
2795
|
+
flattenToTools(skills, availableTools) {
|
|
2796
|
+
const toolMap = new Map(availableTools.map((t) => [t.name, t]));
|
|
2797
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2798
|
+
const resolved = [];
|
|
2799
|
+
for (const skill of skills) {
|
|
2800
|
+
for (const toolName of skill.tools) {
|
|
2801
|
+
if (seen.has(toolName)) continue;
|
|
2802
|
+
const def = toolMap.get(toolName);
|
|
2803
|
+
if (def) {
|
|
2804
|
+
resolved.push(def);
|
|
2805
|
+
seen.add(toolName);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
return resolved;
|
|
2810
|
+
}
|
|
2811
|
+
// ── System-prompt composition ─────────────────────────────────
|
|
2812
|
+
/**
|
|
2813
|
+
* Build the "Active Skills" block to append to an agent's system
|
|
2814
|
+
* prompt. The block lists each skill's label + instructions so the
|
|
2815
|
+
* LLM knows which capabilities are available and how to invoke them.
|
|
2816
|
+
*
|
|
2817
|
+
* Returns an empty string when there are no skills, so the caller
|
|
2818
|
+
* can safely concatenate without producing dangling whitespace.
|
|
2819
|
+
*/
|
|
2820
|
+
composeInstructionsBlock(skills) {
|
|
2821
|
+
if (skills.length === 0) return "";
|
|
2822
|
+
const lines = ["", "--- Active Skills ---"];
|
|
2823
|
+
for (const skill of skills) {
|
|
2824
|
+
lines.push(`
|
|
2825
|
+
### ${skill.label} (${skill.name})`);
|
|
2826
|
+
if (skill.description) lines.push(skill.description);
|
|
2827
|
+
if (skill.instructions) lines.push(skill.instructions);
|
|
2828
|
+
if (skill.tools.length > 0) {
|
|
2829
|
+
lines.push(`Tools: ${skill.tools.join(", ")}`);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return lines.join("\n");
|
|
2833
|
+
}
|
|
2834
|
+
/**
|
|
2835
|
+
* Project a skill to a wire-friendly summary suitable for the
|
|
2836
|
+
* `/api/v1/ai/skills` endpoint and slash-command palettes.
|
|
2837
|
+
*/
|
|
2838
|
+
toSummary(skill) {
|
|
2839
|
+
return {
|
|
2840
|
+
name: skill.name,
|
|
2841
|
+
label: skill.label,
|
|
2842
|
+
description: skill.description,
|
|
2843
|
+
triggerPhrases: skill.triggerPhrases,
|
|
2844
|
+
toolCount: skill.tools.length
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2297
2847
|
};
|
|
2298
2848
|
|
|
2299
2849
|
// src/agents/data-chat-agent.ts
|
|
@@ -2303,34 +2853,15 @@ var DATA_CHAT_AGENT = {
|
|
|
2303
2853
|
role: "Business Data Analyst",
|
|
2304
2854
|
instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
|
|
2305
2855
|
|
|
2306
|
-
|
|
2307
|
-
- List available data objects (tables) and their schemas
|
|
2308
|
-
- Query records with filters, sorting, and pagination
|
|
2309
|
-
- Look up individual records by ID
|
|
2310
|
-
- Perform aggregations and statistical analysis (count, sum, avg, min, max)
|
|
2311
|
-
|
|
2312
|
-
Guidelines:
|
|
2313
|
-
1. Always use the describe_object tool first to understand a table's structure before querying it.
|
|
2314
|
-
2. Respect the user's current context \u2014 if they are viewing a specific object or record, use that as the default scope.
|
|
2315
|
-
3. When presenting data, format it in a clear and readable way using markdown tables or bullet lists.
|
|
2316
|
-
4. For large result sets, summarize the data and mention the total count.
|
|
2317
|
-
5. When performing aggregations, explain the results in plain language.
|
|
2318
|
-
6. If a query returns no results, suggest possible reasons and alternative queries.
|
|
2319
|
-
7. Never expose internal IDs unless the user explicitly asks for them.
|
|
2320
|
-
8. Always answer in the same language the user is using.`,
|
|
2856
|
+
Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
|
|
2321
2857
|
model: {
|
|
2322
2858
|
provider: "openai",
|
|
2323
2859
|
model: "gpt-4",
|
|
2324
2860
|
temperature: 0.3,
|
|
2325
2861
|
maxTokens: 4096
|
|
2326
2862
|
},
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
{ type: "query", name: "describe_object", description: "Get schema/fields of a data object" },
|
|
2330
|
-
{ type: "query", name: "query_records", description: "Query records with filters and pagination" },
|
|
2331
|
-
{ type: "query", name: "get_record", description: "Get a single record by ID" },
|
|
2332
|
-
{ type: "query", name: "aggregate_data", description: "Aggregate/statistics on data" }
|
|
2333
|
-
],
|
|
2863
|
+
// Capability bundle lives on the skill; the agent only references it.
|
|
2864
|
+
skills: ["data_explorer"],
|
|
2334
2865
|
active: true,
|
|
2335
2866
|
visibility: "global",
|
|
2336
2867
|
guardrails: {
|
|
@@ -2358,39 +2889,15 @@ var METADATA_ASSISTANT_AGENT = {
|
|
|
2358
2889
|
role: "Schema Architect",
|
|
2359
2890
|
instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
|
|
2360
2891
|
|
|
2361
|
-
|
|
2362
|
-
- Create new data objects (tables) with fields
|
|
2363
|
-
- Add fields (columns) to existing objects
|
|
2364
|
-
- Modify field properties (label, type, required, default value)
|
|
2365
|
-
- Delete fields from objects
|
|
2366
|
-
- List all registered metadata objects and their schemas
|
|
2367
|
-
- Describe the full schema of a specific object
|
|
2368
|
-
|
|
2369
|
-
Guidelines:
|
|
2370
|
-
1. Before creating a new object, use list_objects to check if a similar one already exists.
|
|
2371
|
-
2. Before modifying or deleting fields, use describe_object to understand the current schema.
|
|
2372
|
-
3. Always use snake_case for object names and field names (e.g. project_task, due_date).
|
|
2373
|
-
4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
|
|
2374
|
-
5. When creating objects, propose a reasonable set of initial fields based on the entity type.
|
|
2375
|
-
6. Explain what changes you are about to make before executing them.
|
|
2376
|
-
7. After making changes, confirm the result by describing the updated schema.
|
|
2377
|
-
8. For destructive operations (deleting fields), always warn the user about potential data loss.
|
|
2378
|
-
9. Always answer in the same language the user is using.
|
|
2379
|
-
10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
|
|
2892
|
+
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.`,
|
|
2380
2893
|
model: {
|
|
2381
2894
|
provider: "openai",
|
|
2382
2895
|
model: "gpt-4",
|
|
2383
2896
|
temperature: 0.2,
|
|
2384
2897
|
maxTokens: 4096
|
|
2385
2898
|
},
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
{ type: "action", name: "add_field", description: "Add a field to an existing object" },
|
|
2389
|
-
{ type: "action", name: "modify_field", description: "Modify an existing field definition" },
|
|
2390
|
-
{ type: "action", name: "delete_field", description: "Delete a field from an object" },
|
|
2391
|
-
{ type: "query", name: "list_objects", description: "List all data objects" },
|
|
2392
|
-
{ type: "query", name: "describe_object", description: "Describe an object schema" }
|
|
2393
|
-
],
|
|
2899
|
+
// Capability bundle lives on the skill; the agent only references it.
|
|
2900
|
+
skills: ["metadata_authoring"],
|
|
2394
2901
|
active: true,
|
|
2395
2902
|
visibility: "global",
|
|
2396
2903
|
guardrails: {
|
|
@@ -2411,6 +2918,98 @@ Guidelines:
|
|
|
2411
2918
|
}
|
|
2412
2919
|
};
|
|
2413
2920
|
|
|
2921
|
+
// src/skills/data-explorer-skill.ts
|
|
2922
|
+
var DATA_EXPLORER_SKILL = {
|
|
2923
|
+
name: "data_explorer",
|
|
2924
|
+
label: "Data Explorer",
|
|
2925
|
+
description: "Read-only Q&A over the user's business data \u2014 schema discovery, filtered queries, lookups, and aggregations.",
|
|
2926
|
+
instructions: `You can explore the user's business data through these tools.
|
|
2927
|
+
|
|
2928
|
+
Capabilities:
|
|
2929
|
+
- List available data objects (tables) and their schemas
|
|
2930
|
+
- Query records with filters, sorting, and pagination
|
|
2931
|
+
- Look up individual records by ID
|
|
2932
|
+
- Perform aggregations and statistical analysis (count, sum, avg, min, max)
|
|
2933
|
+
|
|
2934
|
+
Guidelines:
|
|
2935
|
+
1. Always use the describe_object tool first to understand a table's structure before querying it.
|
|
2936
|
+
2. Respect the user's current context \u2014 if they are viewing a specific object or record, use that as the default scope.
|
|
2937
|
+
3. When presenting data, format it in a clear and readable way using markdown tables or bullet lists.
|
|
2938
|
+
4. For large result sets, summarize the data and mention the total count.
|
|
2939
|
+
5. When performing aggregations, explain the results in plain language.
|
|
2940
|
+
6. If a query returns no results, suggest possible reasons and alternative queries.
|
|
2941
|
+
7. Never expose internal IDs unless the user explicitly asks for them.
|
|
2942
|
+
8. Always answer in the same language the user is using.`,
|
|
2943
|
+
tools: [
|
|
2944
|
+
"list_objects",
|
|
2945
|
+
"describe_object",
|
|
2946
|
+
"query_records",
|
|
2947
|
+
"get_record",
|
|
2948
|
+
"aggregate_data"
|
|
2949
|
+
],
|
|
2950
|
+
triggerPhrases: [
|
|
2951
|
+
"show me",
|
|
2952
|
+
"list",
|
|
2953
|
+
"how many",
|
|
2954
|
+
"count",
|
|
2955
|
+
"find records",
|
|
2956
|
+
"query",
|
|
2957
|
+
"aggregate",
|
|
2958
|
+
"sum",
|
|
2959
|
+
"average"
|
|
2960
|
+
],
|
|
2961
|
+
active: true
|
|
2962
|
+
};
|
|
2963
|
+
|
|
2964
|
+
// src/skills/metadata-authoring-skill.ts
|
|
2965
|
+
var METADATA_AUTHORING_SKILL = {
|
|
2966
|
+
name: "metadata_authoring",
|
|
2967
|
+
label: "Metadata Authoring",
|
|
2968
|
+
description: "Create and modify ObjectStack metadata \u2014 objects, fields, schema changes through natural language.",
|
|
2969
|
+
instructions: `You are an expert metadata architect. When the user asks you to design or change a data model, use these tools.
|
|
2970
|
+
|
|
2971
|
+
Capabilities:
|
|
2972
|
+
- Create new data objects (tables) with fields
|
|
2973
|
+
- Add fields (columns) to existing objects
|
|
2974
|
+
- Modify field properties (label, type, required, default value)
|
|
2975
|
+
- Delete fields from objects
|
|
2976
|
+
- List all registered metadata objects and their schemas
|
|
2977
|
+
- Describe the full schema of a specific object
|
|
2978
|
+
|
|
2979
|
+
Guidelines:
|
|
2980
|
+
1. Before creating a new object, use list_objects to check if a similar one already exists.
|
|
2981
|
+
2. Before modifying or deleting fields, use describe_object to understand the current schema.
|
|
2982
|
+
3. Always use snake_case for object names and field names (e.g. project_task, due_date).
|
|
2983
|
+
4. Suggest meaningful field types based on the user's description (e.g. "deadline" \u2192 date, "active" \u2192 boolean).
|
|
2984
|
+
5. When creating objects, propose a reasonable set of initial fields based on the entity type.
|
|
2985
|
+
6. Explain what changes you are about to make before executing them.
|
|
2986
|
+
7. After making changes, confirm the result by describing the updated schema.
|
|
2987
|
+
8. For destructive operations (deleting fields), always warn the user about potential data loss.
|
|
2988
|
+
9. Always answer in the same language the user is using.
|
|
2989
|
+
10. If the user's request is ambiguous, ask clarifying questions before proceeding.`,
|
|
2990
|
+
tools: [
|
|
2991
|
+
"create_object",
|
|
2992
|
+
"add_field",
|
|
2993
|
+
"modify_field",
|
|
2994
|
+
"delete_field",
|
|
2995
|
+
"list_objects",
|
|
2996
|
+
"describe_object"
|
|
2997
|
+
],
|
|
2998
|
+
triggerPhrases: [
|
|
2999
|
+
"create object",
|
|
3000
|
+
"create table",
|
|
3001
|
+
"add field",
|
|
3002
|
+
"add column",
|
|
3003
|
+
"modify field",
|
|
3004
|
+
"change field",
|
|
3005
|
+
"delete field",
|
|
3006
|
+
"drop field",
|
|
3007
|
+
"design schema",
|
|
3008
|
+
"new entity"
|
|
3009
|
+
],
|
|
3010
|
+
active: true
|
|
3011
|
+
};
|
|
3012
|
+
|
|
2414
3013
|
// src/adapters/vercel-adapter.ts
|
|
2415
3014
|
import { generateText, streamText, tool as vercelTool, jsonSchema } from "ai";
|
|
2416
3015
|
function buildVercelOptions(options) {
|
|
@@ -2478,8 +3077,15 @@ var VercelLLMAdapter = class {
|
|
|
2478
3077
|
messages,
|
|
2479
3078
|
...buildVercelOptions(options)
|
|
2480
3079
|
});
|
|
2481
|
-
|
|
2482
|
-
|
|
3080
|
+
try {
|
|
3081
|
+
for await (const part of result.fullStream) {
|
|
3082
|
+
yield part;
|
|
3083
|
+
}
|
|
3084
|
+
} catch (err) {
|
|
3085
|
+
yield {
|
|
3086
|
+
type: "error",
|
|
3087
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
3088
|
+
};
|
|
2483
3089
|
}
|
|
2484
3090
|
}
|
|
2485
3091
|
async embed(_input) {
|
|
@@ -2561,11 +3167,14 @@ var AIServicePlugin = class {
|
|
|
2561
3167
|
/* webpackIgnore: true */
|
|
2562
3168
|
pkg
|
|
2563
3169
|
);
|
|
2564
|
-
const
|
|
2565
|
-
if (typeof
|
|
3170
|
+
const provider = mod[factory] ?? mod.default;
|
|
3171
|
+
if (typeof provider === "function") {
|
|
2566
3172
|
const modelId = process.env.AI_MODEL ?? defaultModel;
|
|
2567
|
-
const
|
|
2568
|
-
|
|
3173
|
+
const useChatApi = factory === "openai" && typeof provider.chat === "function";
|
|
3174
|
+
const model = useChatApi ? provider.chat(modelId) : provider(modelId);
|
|
3175
|
+
const adapter = new VercelLLMAdapter({ model });
|
|
3176
|
+
const apiSuffix = useChatApi ? " [chat-completions]" : "";
|
|
3177
|
+
return { adapter, description: `${displayName} (model: ${modelId})${apiSuffix}` };
|
|
2569
3178
|
}
|
|
2570
3179
|
} catch (err) {
|
|
2571
3180
|
ctx.logger.warn(
|
|
@@ -2626,6 +3235,7 @@ var AIServicePlugin = class {
|
|
|
2626
3235
|
name: "AI Service",
|
|
2627
3236
|
version: "1.0.0",
|
|
2628
3237
|
type: "plugin",
|
|
3238
|
+
scope: "project",
|
|
2629
3239
|
namespace: "ai",
|
|
2630
3240
|
objects: [AiConversationObject, AiMessageObject]
|
|
2631
3241
|
});
|
|
@@ -2634,25 +3244,12 @@ var AIServicePlugin = class {
|
|
|
2634
3244
|
ctx.logger.debug("[AI] Before chat", { messages });
|
|
2635
3245
|
});
|
|
2636
3246
|
}
|
|
2637
|
-
try {
|
|
2638
|
-
const setupNav = ctx.getService("setupNav");
|
|
2639
|
-
if (setupNav) {
|
|
2640
|
-
setupNav.contribute({
|
|
2641
|
-
areaId: "area_ai",
|
|
2642
|
-
items: [
|
|
2643
|
-
{ id: "nav_ai_conversations", type: "object", label: { key: "setup.nav.ai_conversations", defaultValue: "Conversations" }, objectName: "conversations", icon: "message-square", order: 10 },
|
|
2644
|
-
{ id: "nav_ai_messages", type: "object", label: { key: "setup.nav.ai_messages", defaultValue: "Messages" }, objectName: "messages", icon: "messages-square", order: 20 }
|
|
2645
|
-
]
|
|
2646
|
-
});
|
|
2647
|
-
ctx.logger.info("[AI] Navigation items contributed to Setup App");
|
|
2648
|
-
}
|
|
2649
|
-
} catch {
|
|
2650
|
-
}
|
|
2651
3247
|
ctx.logger.info("[AI] Service initialized");
|
|
2652
3248
|
}
|
|
2653
3249
|
async start(ctx) {
|
|
2654
3250
|
if (!this.service) return;
|
|
2655
3251
|
let metadataService;
|
|
3252
|
+
const withTimeout = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
|
|
2656
3253
|
try {
|
|
2657
3254
|
metadataService = ctx.getService("metadata");
|
|
2658
3255
|
console.log("[AI Plugin] Retrieved metadata service:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
|
|
@@ -2660,6 +3257,13 @@ var AIServicePlugin = class {
|
|
|
2660
3257
|
console.log("[AI] Metadata service not available:", e.message);
|
|
2661
3258
|
ctx.logger.debug("[AI] Metadata service not available");
|
|
2662
3259
|
}
|
|
3260
|
+
if (metadataService && typeof metadataService.exists === "function") {
|
|
3261
|
+
const probeResult = await withTimeout(metadataService.exists("tool", "__probe__"), 3e3);
|
|
3262
|
+
if (probeResult === null) {
|
|
3263
|
+
ctx.logger.warn("[AI] Metadata service unreachable (timed out) \u2014 AI tools/agents will work but Studio visibility unavailable");
|
|
3264
|
+
metadataService = void 0;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
2663
3267
|
try {
|
|
2664
3268
|
const dataEngine = ctx.getService("data");
|
|
2665
3269
|
if (dataEngine) {
|
|
@@ -2668,18 +3272,31 @@ var AIServicePlugin = class {
|
|
|
2668
3272
|
if (metadataService) {
|
|
2669
3273
|
const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
|
|
2670
3274
|
for (const toolDef of DATA_TOOL_DEFINITIONS2) {
|
|
2671
|
-
const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
|
|
3275
|
+
const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
|
|
3276
|
+
if (toolExists === null) {
|
|
3277
|
+
ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
|
|
3278
|
+
break;
|
|
3279
|
+
}
|
|
2672
3280
|
if (!toolExists) {
|
|
2673
|
-
|
|
3281
|
+
try {
|
|
3282
|
+
await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
|
|
3283
|
+
} catch (err) {
|
|
3284
|
+
ctx.logger.warn(
|
|
3285
|
+
"[AI] Failed to persist tool metadata (non-fatal)",
|
|
3286
|
+
err instanceof Error ? { tool: toolDef.name, error: err.message } : { tool: toolDef.name }
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
2674
3289
|
}
|
|
2675
3290
|
}
|
|
2676
3291
|
ctx.logger.info(`[AI] ${DATA_TOOL_DEFINITIONS2.length} data tools registered as metadata`);
|
|
2677
3292
|
}
|
|
2678
3293
|
if (metadataService) {
|
|
2679
3294
|
try {
|
|
2680
|
-
const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", DATA_CHAT_AGENT.name) : false;
|
|
2681
|
-
if (
|
|
2682
|
-
|
|
3295
|
+
const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
|
|
3296
|
+
if (agentExists === null) {
|
|
3297
|
+
ctx.logger.warn("[AI] Metadata service timed out checking data_chat agent, skipping");
|
|
3298
|
+
} else if (!agentExists) {
|
|
3299
|
+
await withTimeout(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
|
|
2683
3300
|
console.log("[AI] Registered data_chat agent to metadataService");
|
|
2684
3301
|
ctx.logger.info("[AI] data_chat agent registered");
|
|
2685
3302
|
} else {
|
|
@@ -2689,6 +3306,19 @@ var AIServicePlugin = class {
|
|
|
2689
3306
|
} catch (err) {
|
|
2690
3307
|
ctx.logger.warn("[AI] Failed to register data_chat agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
|
|
2691
3308
|
}
|
|
3309
|
+
try {
|
|
3310
|
+
const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
|
|
3311
|
+
if (skillExists === null) {
|
|
3312
|
+
ctx.logger.warn("[AI] Metadata service timed out checking data_explorer skill, skipping");
|
|
3313
|
+
} else if (!skillExists) {
|
|
3314
|
+
await withTimeout(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
|
|
3315
|
+
ctx.logger.info("[AI] data_explorer skill registered");
|
|
3316
|
+
} else {
|
|
3317
|
+
ctx.logger.debug("[AI] data_explorer skill already exists, skipping auto-registration");
|
|
3318
|
+
}
|
|
3319
|
+
} catch (err) {
|
|
3320
|
+
ctx.logger.warn("[AI] Failed to register data_explorer skill", err instanceof Error ? { error: err.message } : { error: String(err) });
|
|
3321
|
+
}
|
|
2692
3322
|
}
|
|
2693
3323
|
}
|
|
2694
3324
|
} catch {
|
|
@@ -2700,16 +3330,29 @@ var AIServicePlugin = class {
|
|
|
2700
3330
|
ctx.logger.info("[AI] Built-in metadata tools registered");
|
|
2701
3331
|
const { METADATA_TOOL_DEFINITIONS: METADATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_metadata_tools(), metadata_tools_exports));
|
|
2702
3332
|
for (const toolDef of METADATA_TOOL_DEFINITIONS2) {
|
|
2703
|
-
const toolExists = typeof metadataService.exists === "function" ? await metadataService.exists("tool", toolDef.name) : false;
|
|
3333
|
+
const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
|
|
3334
|
+
if (toolExists === null) {
|
|
3335
|
+
ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
|
|
3336
|
+
break;
|
|
3337
|
+
}
|
|
2704
3338
|
if (!toolExists) {
|
|
2705
|
-
|
|
3339
|
+
try {
|
|
3340
|
+
await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
|
|
3341
|
+
} catch (err) {
|
|
3342
|
+
ctx.logger.warn(
|
|
3343
|
+
"[AI] Failed to persist tool metadata (non-fatal)",
|
|
3344
|
+
err instanceof Error ? { tool: toolDef.name, error: err.message } : { tool: toolDef.name }
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
2706
3347
|
}
|
|
2707
3348
|
}
|
|
2708
3349
|
ctx.logger.info(`[AI] ${METADATA_TOOL_DEFINITIONS2.length} metadata tools registered as metadata`);
|
|
2709
3350
|
try {
|
|
2710
|
-
const agentExists = typeof metadataService.exists === "function" ? await metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name) : false;
|
|
2711
|
-
if (
|
|
2712
|
-
|
|
3351
|
+
const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
|
|
3352
|
+
if (agentExists === null) {
|
|
3353
|
+
ctx.logger.warn("[AI] Metadata service timed out checking metadata_assistant agent, skipping");
|
|
3354
|
+
} else if (!agentExists) {
|
|
3355
|
+
await withTimeout(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
|
|
2713
3356
|
console.log("[AI] Registered metadata_assistant agent to metadataService");
|
|
2714
3357
|
ctx.logger.info("[AI] metadata_assistant agent registered");
|
|
2715
3358
|
} else {
|
|
@@ -2719,22 +3362,71 @@ var AIServicePlugin = class {
|
|
|
2719
3362
|
} catch (err) {
|
|
2720
3363
|
ctx.logger.warn("[AI] Failed to register metadata_assistant agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
|
|
2721
3364
|
}
|
|
3365
|
+
try {
|
|
3366
|
+
const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
|
|
3367
|
+
if (skillExists === null) {
|
|
3368
|
+
ctx.logger.warn("[AI] Metadata service timed out checking metadata_authoring skill, skipping");
|
|
3369
|
+
} else if (!skillExists) {
|
|
3370
|
+
await withTimeout(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
|
|
3371
|
+
ctx.logger.info("[AI] metadata_authoring skill registered");
|
|
3372
|
+
} else {
|
|
3373
|
+
ctx.logger.debug("[AI] metadata_authoring skill already exists, skipping auto-registration");
|
|
3374
|
+
}
|
|
3375
|
+
} catch (err) {
|
|
3376
|
+
ctx.logger.warn("[AI] Failed to register metadata_authoring skill", err instanceof Error ? { error: err.message } : { error: String(err) });
|
|
3377
|
+
}
|
|
2722
3378
|
} catch (err) {
|
|
2723
3379
|
ctx.logger.debug("[AI] Failed to register metadata tools", err instanceof Error ? err : void 0);
|
|
2724
3380
|
}
|
|
2725
3381
|
}
|
|
2726
3382
|
await ctx.trigger("ai:ready", this.service);
|
|
3383
|
+
if (metadataService) {
|
|
3384
|
+
try {
|
|
3385
|
+
const objectql = ctx.getService("objectql");
|
|
3386
|
+
const registry = objectql?.registry;
|
|
3387
|
+
if (registry && typeof registry.listItems === "function") {
|
|
3388
|
+
const stackAgents = registry.listItems("agent");
|
|
3389
|
+
let bridged = 0;
|
|
3390
|
+
for (const entry of stackAgents) {
|
|
3391
|
+
const agent = entry?.content ?? entry;
|
|
3392
|
+
const agentName = agent?.name;
|
|
3393
|
+
if (!agentName || typeof agentName !== "string") continue;
|
|
3394
|
+
const exists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", agentName)) : false;
|
|
3395
|
+
if (exists === true) continue;
|
|
3396
|
+
try {
|
|
3397
|
+
await withTimeout(metadataService.register("agent", agentName, agent));
|
|
3398
|
+
bridged++;
|
|
3399
|
+
} catch (err) {
|
|
3400
|
+
ctx.logger.warn(
|
|
3401
|
+
"[AI] Failed to bridge stack agent into metadata service (non-fatal)",
|
|
3402
|
+
err instanceof Error ? { agent: agentName, error: err.message } : { agent: agentName }
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
if (bridged > 0) {
|
|
3407
|
+
ctx.logger.info(`[AI] Bridged ${bridged} stack-defined agent(s) from ObjectQL registry`);
|
|
3408
|
+
console.log(`[AI] Bridged ${bridged} stack-defined agent(s) from ObjectQL registry`);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
} catch (err) {
|
|
3412
|
+
ctx.logger.debug("[AI] ObjectQL registry not available, skipping agent bridge", err instanceof Error ? err : void 0);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
2727
3415
|
const routes = buildAIRoutes(this.service, this.service.conversationService, ctx.logger);
|
|
2728
3416
|
const toolRoutes = buildToolRoutes(this.service, ctx.logger);
|
|
2729
3417
|
routes.push(...toolRoutes);
|
|
2730
3418
|
ctx.logger.info(`[AI] Tool routes registered (${toolRoutes.length} routes)`);
|
|
2731
3419
|
if (metadataService) {
|
|
2732
|
-
const
|
|
3420
|
+
const skillRegistry = new SkillRegistry(metadataService);
|
|
3421
|
+
const agentRuntime = new AgentRuntime(metadataService, skillRegistry);
|
|
2733
3422
|
const agentRoutes = buildAgentRoutes(this.service, agentRuntime, ctx.logger);
|
|
2734
3423
|
routes.push(...agentRoutes);
|
|
2735
3424
|
ctx.logger.info(`[AI] Agent routes registered (${agentRoutes.length} routes)`);
|
|
3425
|
+
const assistantRoutes = buildAssistantRoutes(this.service, agentRuntime, skillRegistry, ctx.logger);
|
|
3426
|
+
routes.push(...assistantRoutes);
|
|
3427
|
+
ctx.logger.info(`[AI] Assistant (ambient) routes registered (${assistantRoutes.length} routes)`);
|
|
2736
3428
|
} else {
|
|
2737
|
-
ctx.logger.debug("[AI] Metadata service not available, skipping agent routes");
|
|
3429
|
+
ctx.logger.debug("[AI] Metadata service not available, skipping agent and assistant routes");
|
|
2738
3430
|
}
|
|
2739
3431
|
await ctx.trigger("ai:routes", routes);
|
|
2740
3432
|
const kernel = ctx.getKernel();
|
|
@@ -2754,6 +3446,337 @@ var AIServicePlugin = class {
|
|
|
2754
3446
|
init_data_tools();
|
|
2755
3447
|
init_metadata_tools();
|
|
2756
3448
|
init_metadata_tools();
|
|
3449
|
+
|
|
3450
|
+
// src/tools/list-packages.tool.ts
|
|
3451
|
+
import { defineTool as defineTool7 } from "@objectstack/spec/ai";
|
|
3452
|
+
var listPackagesTool = defineTool7({
|
|
3453
|
+
name: "list_packages",
|
|
3454
|
+
label: "List Packages",
|
|
3455
|
+
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.",
|
|
3456
|
+
category: "utility",
|
|
3457
|
+
builtIn: true,
|
|
3458
|
+
parameters: {
|
|
3459
|
+
type: "object",
|
|
3460
|
+
properties: {
|
|
3461
|
+
status: {
|
|
3462
|
+
type: "string",
|
|
3463
|
+
description: "Filter by package status",
|
|
3464
|
+
enum: ["installed", "disabled", "installing", "upgrading", "uninstalling", "error"]
|
|
3465
|
+
},
|
|
3466
|
+
enabled: {
|
|
3467
|
+
type: "boolean",
|
|
3468
|
+
description: "Filter by enabled state (true = only enabled, false = only disabled)"
|
|
3469
|
+
}
|
|
3470
|
+
},
|
|
3471
|
+
additionalProperties: false
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3474
|
+
|
|
3475
|
+
// src/tools/get-package.tool.ts
|
|
3476
|
+
import { defineTool as defineTool8 } from "@objectstack/spec/ai";
|
|
3477
|
+
var getPackageTool = defineTool8({
|
|
3478
|
+
name: "get_package",
|
|
3479
|
+
label: "Get Package",
|
|
3480
|
+
description: "Gets detailed information about a specific installed package, including its manifest, metadata, and installation status.",
|
|
3481
|
+
category: "utility",
|
|
3482
|
+
builtIn: true,
|
|
3483
|
+
parameters: {
|
|
3484
|
+
type: "object",
|
|
3485
|
+
properties: {
|
|
3486
|
+
packageId: {
|
|
3487
|
+
type: "string",
|
|
3488
|
+
description: "Package identifier (reverse domain notation, e.g., com.acme.crm)"
|
|
3489
|
+
}
|
|
3490
|
+
},
|
|
3491
|
+
required: ["packageId"],
|
|
3492
|
+
additionalProperties: false
|
|
3493
|
+
}
|
|
3494
|
+
});
|
|
3495
|
+
|
|
3496
|
+
// src/tools/create-package.tool.ts
|
|
3497
|
+
import { defineTool as defineTool9 } from "@objectstack/spec/ai";
|
|
3498
|
+
var createPackageTool = defineTool9({
|
|
3499
|
+
name: "create_package",
|
|
3500
|
+
label: "Create Package",
|
|
3501
|
+
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.",
|
|
3502
|
+
category: "utility",
|
|
3503
|
+
builtIn: true,
|
|
3504
|
+
parameters: {
|
|
3505
|
+
type: "object",
|
|
3506
|
+
properties: {
|
|
3507
|
+
id: {
|
|
3508
|
+
type: "string",
|
|
3509
|
+
description: "Package identifier in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)"
|
|
3510
|
+
},
|
|
3511
|
+
name: {
|
|
3512
|
+
type: "string",
|
|
3513
|
+
description: 'Human-readable package name (e.g., "CRM Application", "Sales Module")'
|
|
3514
|
+
},
|
|
3515
|
+
version: {
|
|
3516
|
+
type: "string",
|
|
3517
|
+
description: 'Semantic version (e.g., "1.0.0")',
|
|
3518
|
+
default: "1.0.0"
|
|
3519
|
+
},
|
|
3520
|
+
description: {
|
|
3521
|
+
type: "string",
|
|
3522
|
+
description: "Brief description of what this package provides"
|
|
3523
|
+
},
|
|
3524
|
+
namespace: {
|
|
3525
|
+
type: "string",
|
|
3526
|
+
description: "Namespace prefix for metadata (snake_case, e.g., crm, sales). If not provided, derived from package ID."
|
|
3527
|
+
},
|
|
3528
|
+
type: {
|
|
3529
|
+
type: "string",
|
|
3530
|
+
description: "Package type",
|
|
3531
|
+
enum: ["application", "plugin", "library", "template"],
|
|
3532
|
+
default: "application"
|
|
3533
|
+
}
|
|
3534
|
+
},
|
|
3535
|
+
required: ["id", "name"],
|
|
3536
|
+
additionalProperties: false
|
|
3537
|
+
}
|
|
3538
|
+
});
|
|
3539
|
+
|
|
3540
|
+
// src/tools/get-active-package.tool.ts
|
|
3541
|
+
import { defineTool as defineTool10 } from "@objectstack/spec/ai";
|
|
3542
|
+
var getActivePackageTool = defineTool10({
|
|
3543
|
+
name: "get_active_package",
|
|
3544
|
+
label: "Get Active Package",
|
|
3545
|
+
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.",
|
|
3546
|
+
category: "utility",
|
|
3547
|
+
builtIn: true,
|
|
3548
|
+
parameters: {
|
|
3549
|
+
type: "object",
|
|
3550
|
+
properties: {},
|
|
3551
|
+
additionalProperties: false
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
|
|
3555
|
+
// src/tools/set-active-package.tool.ts
|
|
3556
|
+
import { defineTool as defineTool11 } from "@objectstack/spec/ai";
|
|
3557
|
+
var setActivePackageTool = defineTool11({
|
|
3558
|
+
name: "set_active_package",
|
|
3559
|
+
label: "Set Active Package",
|
|
3560
|
+
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.",
|
|
3561
|
+
category: "utility",
|
|
3562
|
+
builtIn: true,
|
|
3563
|
+
parameters: {
|
|
3564
|
+
type: "object",
|
|
3565
|
+
properties: {
|
|
3566
|
+
packageId: {
|
|
3567
|
+
type: "string",
|
|
3568
|
+
description: "Package identifier to set as active (e.g., com.acme.crm)"
|
|
3569
|
+
}
|
|
3570
|
+
},
|
|
3571
|
+
required: ["packageId"],
|
|
3572
|
+
additionalProperties: false
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
|
|
3576
|
+
// src/tools/package-tools.ts
|
|
3577
|
+
var PACKAGE_TOOL_DEFINITIONS = [
|
|
3578
|
+
listPackagesTool,
|
|
3579
|
+
getPackageTool,
|
|
3580
|
+
createPackageTool,
|
|
3581
|
+
getActivePackageTool,
|
|
3582
|
+
setActivePackageTool
|
|
3583
|
+
];
|
|
3584
|
+
var REVERSE_DOMAIN_RE = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/;
|
|
3585
|
+
var SNAKE_CASE_RE2 = /^[a-z_][a-z0-9_]*$/;
|
|
3586
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(-[a-z0-9]+(\.[a-z0-9]+)*)?$/;
|
|
3587
|
+
function isReverseDomain(value) {
|
|
3588
|
+
return REVERSE_DOMAIN_RE.test(value);
|
|
3589
|
+
}
|
|
3590
|
+
function isSnakeCase2(value) {
|
|
3591
|
+
return SNAKE_CASE_RE2.test(value);
|
|
3592
|
+
}
|
|
3593
|
+
function isSemVer(value) {
|
|
3594
|
+
return SEMVER_RE.test(value);
|
|
3595
|
+
}
|
|
3596
|
+
function deriveNamespace(packageId) {
|
|
3597
|
+
const parts = packageId.split(".");
|
|
3598
|
+
return parts[parts.length - 1];
|
|
3599
|
+
}
|
|
3600
|
+
function createListPackagesHandler(ctx) {
|
|
3601
|
+
return async (args) => {
|
|
3602
|
+
const { status, enabled } = args ?? {};
|
|
3603
|
+
const filter = {};
|
|
3604
|
+
if (status) filter.status = status;
|
|
3605
|
+
if (enabled !== void 0) filter.enabled = enabled;
|
|
3606
|
+
const packages = await ctx.packageRegistry.list(filter);
|
|
3607
|
+
const result = packages.map((pkg) => ({
|
|
3608
|
+
id: pkg.manifest.id,
|
|
3609
|
+
name: pkg.manifest.name,
|
|
3610
|
+
version: pkg.manifest.version,
|
|
3611
|
+
type: pkg.manifest.type,
|
|
3612
|
+
status: pkg.status,
|
|
3613
|
+
enabled: pkg.enabled,
|
|
3614
|
+
installedAt: pkg.installedAt,
|
|
3615
|
+
description: pkg.manifest.description
|
|
3616
|
+
}));
|
|
3617
|
+
return JSON.stringify({
|
|
3618
|
+
packages: result,
|
|
3619
|
+
total: result.length
|
|
3620
|
+
});
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
function createGetPackageHandler(ctx) {
|
|
3624
|
+
return async (args) => {
|
|
3625
|
+
const { packageId } = args;
|
|
3626
|
+
if (!packageId) {
|
|
3627
|
+
return JSON.stringify({ error: "packageId is required" });
|
|
3628
|
+
}
|
|
3629
|
+
const pkg = await ctx.packageRegistry.get(packageId);
|
|
3630
|
+
if (!pkg) {
|
|
3631
|
+
return JSON.stringify({ error: `Package "${packageId}" not found` });
|
|
3632
|
+
}
|
|
3633
|
+
return JSON.stringify({
|
|
3634
|
+
id: pkg.manifest.id,
|
|
3635
|
+
name: pkg.manifest.name,
|
|
3636
|
+
version: pkg.manifest.version,
|
|
3637
|
+
type: pkg.manifest.type,
|
|
3638
|
+
status: pkg.status,
|
|
3639
|
+
enabled: pkg.enabled,
|
|
3640
|
+
installedAt: pkg.installedAt,
|
|
3641
|
+
updatedAt: pkg.updatedAt,
|
|
3642
|
+
description: pkg.manifest.description,
|
|
3643
|
+
namespace: pkg.manifest.namespace,
|
|
3644
|
+
dependencies: pkg.manifest.dependencies,
|
|
3645
|
+
registeredNamespaces: pkg.registeredNamespaces
|
|
3646
|
+
});
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
function createCreatePackageHandler(ctx) {
|
|
3650
|
+
return async (args) => {
|
|
3651
|
+
const { id, name, version = "1.0.0", description, namespace, type = "application" } = args;
|
|
3652
|
+
if (!id || !name) {
|
|
3653
|
+
return JSON.stringify({ error: 'Both "id" and "name" are required' });
|
|
3654
|
+
}
|
|
3655
|
+
if (!isReverseDomain(id)) {
|
|
3656
|
+
return JSON.stringify({
|
|
3657
|
+
error: `Invalid package ID "${id}". Must be in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)`
|
|
3658
|
+
});
|
|
3659
|
+
}
|
|
3660
|
+
if (!isSemVer(version)) {
|
|
3661
|
+
return JSON.stringify({
|
|
3662
|
+
error: `Invalid version "${version}". Must be semantic version (e.g., 1.0.0, 2.1.3-beta)`
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
const exists = await ctx.packageRegistry.exists(id);
|
|
3666
|
+
if (exists) {
|
|
3667
|
+
return JSON.stringify({ error: `Package "${id}" already exists` });
|
|
3668
|
+
}
|
|
3669
|
+
const derivedNamespace = namespace || deriveNamespace(id);
|
|
3670
|
+
if (!isSnakeCase2(derivedNamespace)) {
|
|
3671
|
+
return JSON.stringify({
|
|
3672
|
+
error: `Invalid namespace "${derivedNamespace}". Must be snake_case (e.g., crm, sales_module)`
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
const manifest = {
|
|
3676
|
+
id,
|
|
3677
|
+
name,
|
|
3678
|
+
version,
|
|
3679
|
+
type,
|
|
3680
|
+
namespace: derivedNamespace,
|
|
3681
|
+
...description ? { description } : {}
|
|
3682
|
+
};
|
|
3683
|
+
const installedPackage = await ctx.packageRegistry.install(manifest);
|
|
3684
|
+
if (ctx.conversationService && ctx.conversationId) {
|
|
3685
|
+
try {
|
|
3686
|
+
await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
|
|
3687
|
+
activePackageId: id
|
|
3688
|
+
});
|
|
3689
|
+
} catch (err) {
|
|
3690
|
+
console.warn("Failed to set active package in conversation:", err);
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
return JSON.stringify({
|
|
3694
|
+
packageId: installedPackage.manifest.id,
|
|
3695
|
+
name: installedPackage.manifest.name,
|
|
3696
|
+
version: installedPackage.manifest.version,
|
|
3697
|
+
namespace: installedPackage.manifest.namespace,
|
|
3698
|
+
status: installedPackage.status,
|
|
3699
|
+
message: `Package "${name}" created successfully and set as active package`
|
|
3700
|
+
});
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
function createGetActivePackageHandler(ctx) {
|
|
3704
|
+
return async () => {
|
|
3705
|
+
if (!ctx.conversationService || !ctx.conversationId) {
|
|
3706
|
+
return JSON.stringify({
|
|
3707
|
+
activePackageId: null,
|
|
3708
|
+
message: "No conversation context available to track active package"
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
try {
|
|
3712
|
+
const metadata = await ctx.conversationService.getMetadata?.(ctx.conversationId);
|
|
3713
|
+
const activePackageId = metadata?.activePackageId;
|
|
3714
|
+
if (!activePackageId) {
|
|
3715
|
+
return JSON.stringify({
|
|
3716
|
+
activePackageId: null,
|
|
3717
|
+
message: "No active package set. Use set_active_package or create a new package."
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
const pkg = await ctx.packageRegistry.get(activePackageId);
|
|
3721
|
+
if (!pkg) {
|
|
3722
|
+
return JSON.stringify({
|
|
3723
|
+
activePackageId,
|
|
3724
|
+
error: `Active package "${activePackageId}" not found. It may have been uninstalled.`
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
return JSON.stringify({
|
|
3728
|
+
activePackageId: pkg.manifest.id,
|
|
3729
|
+
name: pkg.manifest.name,
|
|
3730
|
+
version: pkg.manifest.version,
|
|
3731
|
+
namespace: pkg.manifest.namespace,
|
|
3732
|
+
type: pkg.manifest.type
|
|
3733
|
+
});
|
|
3734
|
+
} catch (err) {
|
|
3735
|
+
return JSON.stringify({
|
|
3736
|
+
error: `Failed to get active package: ${err.message}`
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
function createSetActivePackageHandler(ctx) {
|
|
3742
|
+
return async (args) => {
|
|
3743
|
+
const { packageId } = args;
|
|
3744
|
+
if (!packageId) {
|
|
3745
|
+
return JSON.stringify({ error: "packageId is required" });
|
|
3746
|
+
}
|
|
3747
|
+
const pkg = await ctx.packageRegistry.get(packageId);
|
|
3748
|
+
if (!pkg) {
|
|
3749
|
+
return JSON.stringify({ error: `Package "${packageId}" not found` });
|
|
3750
|
+
}
|
|
3751
|
+
if (!ctx.conversationService || !ctx.conversationId) {
|
|
3752
|
+
return JSON.stringify({
|
|
3753
|
+
error: "No conversation context available. Cannot set active package."
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
try {
|
|
3757
|
+
await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
|
|
3758
|
+
activePackageId: packageId
|
|
3759
|
+
});
|
|
3760
|
+
return JSON.stringify({
|
|
3761
|
+
activePackageId: packageId,
|
|
3762
|
+
name: pkg.manifest.name,
|
|
3763
|
+
namespace: pkg.manifest.namespace,
|
|
3764
|
+
message: `Active package set to "${pkg.manifest.name}"`
|
|
3765
|
+
});
|
|
3766
|
+
} catch (err) {
|
|
3767
|
+
return JSON.stringify({
|
|
3768
|
+
error: `Failed to set active package: ${err.message}`
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
function registerPackageTools(registry, context) {
|
|
3774
|
+
registry.register(listPackagesTool, createListPackagesHandler(context));
|
|
3775
|
+
registry.register(getPackageTool, createGetPackageHandler(context));
|
|
3776
|
+
registry.register(createPackageTool, createCreatePackageHandler(context));
|
|
3777
|
+
registry.register(getActivePackageTool, createGetActivePackageHandler(context));
|
|
3778
|
+
registry.register(setActivePackageTool, createSetActivePackageHandler(context));
|
|
3779
|
+
}
|
|
2757
3780
|
export {
|
|
2758
3781
|
AIService,
|
|
2759
3782
|
AIServicePlugin,
|
|
@@ -2767,20 +3790,29 @@ export {
|
|
|
2767
3790
|
METADATA_TOOL_DEFINITIONS,
|
|
2768
3791
|
MemoryLLMAdapter,
|
|
2769
3792
|
ObjectQLConversationService,
|
|
3793
|
+
PACKAGE_TOOL_DEFINITIONS,
|
|
3794
|
+
SkillRegistry,
|
|
2770
3795
|
ToolRegistry,
|
|
2771
3796
|
VercelLLMAdapter,
|
|
2772
3797
|
addFieldTool,
|
|
2773
3798
|
buildAIRoutes,
|
|
2774
3799
|
buildAgentRoutes,
|
|
3800
|
+
buildAssistantRoutes,
|
|
2775
3801
|
buildToolRoutes,
|
|
2776
3802
|
createObjectTool,
|
|
3803
|
+
createPackageTool,
|
|
2777
3804
|
deleteFieldTool,
|
|
2778
3805
|
describeObjectTool,
|
|
2779
3806
|
encodeStreamPart,
|
|
2780
3807
|
encodeVercelDataStream,
|
|
3808
|
+
getActivePackageTool,
|
|
3809
|
+
getPackageTool,
|
|
2781
3810
|
listObjectsTool,
|
|
3811
|
+
listPackagesTool,
|
|
2782
3812
|
modifyFieldTool,
|
|
2783
3813
|
registerDataTools,
|
|
2784
|
-
registerMetadataTools
|
|
3814
|
+
registerMetadataTools,
|
|
3815
|
+
registerPackageTools,
|
|
3816
|
+
setActivePackageTool
|
|
2785
3817
|
};
|
|
2786
3818
|
//# sourceMappingURL=index.js.map
|