@objectstack/service-ai 7.2.1 → 7.4.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 CHANGED
@@ -5145,7 +5145,8 @@ var SchemaRetriever = class {
5145
5145
  if (obj.label) parts.push(obj.label);
5146
5146
  if (obj.pluralLabel && obj.pluralLabel !== obj.label) parts.push(`(${obj.pluralLabel})`);
5147
5147
  const header = parts.length > 0 ? ` \u2014 ${parts.join(" ")}` : "";
5148
- lines.push(`### ${obj.name}${header}`);
5148
+ const badge = obj.external !== void 0 ? ` [external, ${obj.external?.writable ? "writable" : "read-only"}, datasource=${obj.datasource ?? "default"}]` : "";
5149
+ lines.push(`### ${obj.name}${header}${badge}`);
5149
5150
  const fields = Object.entries(obj.fields ?? {}).slice(0, maxFieldsPerObject);
5150
5151
  for (const [name, field] of fields) {
5151
5152
  lines.push(` - ${name}: ${describeField(field)}`);
@@ -5289,6 +5290,15 @@ var QUERY_DATA_TOOL = {
5289
5290
  function createQueryDataHandler(ctx) {
5290
5291
  const retriever = new SchemaRetriever(ctx.metadata, {}, ctx.protocol);
5291
5292
  const maxLimit = ctx.maxLimit ?? 100;
5293
+ const externalTimeoutFallback = ctx.externalQueryTimeoutMs ?? 3e4;
5294
+ const resolveExternalTimeout = async (datasource) => {
5295
+ try {
5296
+ const ds = await ctx.metadata.get?.("datasource", datasource);
5297
+ return ds?.external?.queryTimeoutMs ?? externalTimeoutFallback;
5298
+ } catch {
5299
+ return externalTimeoutFallback;
5300
+ }
5301
+ };
5292
5302
  return async (args, execCtx) => {
5293
5303
  const { request } = args;
5294
5304
  if (!request || typeof request !== "string") {
@@ -5351,14 +5361,20 @@ function createQueryDataHandler(ctx) {
5351
5361
  });
5352
5362
  }
5353
5363
  }
5364
+ const isExternal = matchedObject.external !== void 0;
5354
5365
  try {
5355
- const records = await ctx.dataEngine.find(plan.objectName, {
5366
+ const findPromise = ctx.dataEngine.find(plan.objectName, {
5356
5367
  where,
5357
5368
  fields: plan.fields ?? void 0,
5358
5369
  orderBy: plan.orderBy ?? void 0,
5359
5370
  limit,
5360
5371
  context: buildAiEngineContext(execCtx)
5361
5372
  });
5373
+ const records = isExternal ? await withTimeout(
5374
+ findPromise,
5375
+ await resolveExternalTimeout(matchedObject.datasource ?? "default"),
5376
+ plan.objectName
5377
+ ) : await findPromise;
5362
5378
  return JSON.stringify({
5363
5379
  plan: { ...plan, where },
5364
5380
  count: records.length,
@@ -5372,6 +5388,28 @@ function createQueryDataHandler(ctx) {
5372
5388
  }
5373
5389
  };
5374
5390
  }
5391
+ function withTimeout(p, ms, object) {
5392
+ return new Promise((resolve, reject) => {
5393
+ const timer = setTimeout(() => {
5394
+ reject(
5395
+ new Error(
5396
+ `query on external object '${object}' exceeded the ${ms}ms timeout. Narrow the filter or lower the limit.`
5397
+ )
5398
+ );
5399
+ }, ms);
5400
+ timer.unref?.();
5401
+ p.then(
5402
+ (v) => {
5403
+ clearTimeout(timer);
5404
+ resolve(v);
5405
+ },
5406
+ (e) => {
5407
+ clearTimeout(timer);
5408
+ reject(e);
5409
+ }
5410
+ );
5411
+ });
5412
+ }
5375
5413
  function registerQueryDataTool(registry, context) {
5376
5414
  registry.register(QUERY_DATA_TOOL, createQueryDataHandler(context));
5377
5415
  }
@@ -5792,6 +5830,82 @@ async function registerActionsAsTools(registry, context) {
5792
5830
 
5793
5831
  // src/agent-runtime.ts
5794
5832
  var import_ai7 = require("@objectstack/spec/ai");
5833
+
5834
+ // src/agents/data-chat-agent.ts
5835
+ var DEFAULT_DATA_AGENT_NAME = "data_chat";
5836
+ var DATA_CHAT_AGENT = {
5837
+ name: DEFAULT_DATA_AGENT_NAME,
5838
+ label: "Data Assistant",
5839
+ role: "Business Data Analyst",
5840
+ instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
5841
+
5842
+ Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
5843
+ model: {
5844
+ provider: "openai",
5845
+ model: "gpt-4",
5846
+ temperature: 0.3,
5847
+ maxTokens: 4096
5848
+ },
5849
+ // Capability bundle lives on the skill; the agent only references it.
5850
+ // `data_explorer` = read side, `actions_executor` = write side.
5851
+ skills: ["data_explorer", "actions_executor"],
5852
+ active: true,
5853
+ visibility: "global",
5854
+ guardrails: {
5855
+ maxTokensPerInvocation: 8192,
5856
+ maxExecutionTimeSec: 30,
5857
+ blockedTopics: ["delete_records", "drop_table", "alter_schema"]
5858
+ },
5859
+ planning: {
5860
+ strategy: "react",
5861
+ maxIterations: 5,
5862
+ allowReplan: false
5863
+ },
5864
+ memory: {
5865
+ shortTerm: {
5866
+ maxMessages: 20,
5867
+ maxTokens: 4096
5868
+ }
5869
+ }
5870
+ };
5871
+
5872
+ // src/agents/metadata-assistant-agent.ts
5873
+ var METADATA_ASSISTANT_AGENT = {
5874
+ name: "metadata_assistant",
5875
+ label: "Metadata Assistant",
5876
+ role: "Schema Architect",
5877
+ instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
5878
+
5879
+ 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.`,
5880
+ model: {
5881
+ provider: "openai",
5882
+ model: "gpt-4",
5883
+ temperature: 0.2,
5884
+ maxTokens: 4096
5885
+ },
5886
+ // Capability bundle lives on the skill; the agent only references it.
5887
+ skills: ["metadata_authoring"],
5888
+ active: true,
5889
+ visibility: "global",
5890
+ guardrails: {
5891
+ maxTokensPerInvocation: 8192,
5892
+ maxExecutionTimeSec: 60,
5893
+ blockedTopics: ["drop_database", "raw_sql", "system_tables"]
5894
+ },
5895
+ planning: {
5896
+ strategy: "react",
5897
+ maxIterations: 10,
5898
+ allowReplan: true
5899
+ },
5900
+ memory: {
5901
+ shortTerm: {
5902
+ maxMessages: 30,
5903
+ maxTokens: 8192
5904
+ }
5905
+ }
5906
+ };
5907
+
5908
+ // src/agent-runtime.ts
5795
5909
  var AgentRuntime = class {
5796
5910
  constructor(metadataService, skillRegistry) {
5797
5911
  this.metadataService = metadataService;
@@ -5942,9 +6056,14 @@ var AgentRuntime = class {
5942
6056
  * chat endpoint when the client doesn't specify an `agentName`.
5943
6057
  *
5944
6058
  * Resolution order:
5945
- * 1. The `defaultAgent` of the app named by `context.appName`.
5946
- * 2. The first active agent in the registry (deterministic fallback).
5947
- * 3. `undefined` if no agents are registered.
6059
+ * 1. The `defaultAgent` of the app named by `context.appName`
6060
+ * (e.g. Studio `metadata_assistant`).
6061
+ * 2. The platform data-query agent (`data_chat`) the implicit
6062
+ * copilot bound to every app that doesn't pin its own. This is
6063
+ * what end users get by default, so they never have to choose.
6064
+ * 3. The first active agent in the registry (last-resort fallback,
6065
+ * e.g. in stripped-down deployments without the data agent).
6066
+ * 4. `undefined` if no agents are registered.
5948
6067
  */
5949
6068
  async resolveDefaultAgent(context) {
5950
6069
  if (context?.appName) {
@@ -5955,6 +6074,8 @@ var AgentRuntime = class {
5955
6074
  if (agent && agent.active !== false) return agent;
5956
6075
  }
5957
6076
  }
6077
+ const dataAgent = await this.loadAgent(DEFAULT_DATA_AGENT_NAME);
6078
+ if (dataAgent && dataAgent.active !== false) return dataAgent;
5958
6079
  const summaries = await this.listAgents();
5959
6080
  if (summaries.length === 0) return void 0;
5960
6081
  return this.loadAgent(summaries[0].name);
@@ -6150,79 +6271,6 @@ var SkillRegistry = class {
6150
6271
  }
6151
6272
  };
6152
6273
 
6153
- // src/agents/data-chat-agent.ts
6154
- var DATA_CHAT_AGENT = {
6155
- name: "data_chat",
6156
- label: "Data Assistant",
6157
- role: "Business Data Analyst",
6158
- instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
6159
-
6160
- Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
6161
- model: {
6162
- provider: "openai",
6163
- model: "gpt-4",
6164
- temperature: 0.3,
6165
- maxTokens: 4096
6166
- },
6167
- // Capability bundle lives on the skill; the agent only references it.
6168
- // `data_explorer` = read side, `actions_executor` = write side.
6169
- skills: ["data_explorer", "actions_executor"],
6170
- active: true,
6171
- visibility: "global",
6172
- guardrails: {
6173
- maxTokensPerInvocation: 8192,
6174
- maxExecutionTimeSec: 30,
6175
- blockedTopics: ["delete_records", "drop_table", "alter_schema"]
6176
- },
6177
- planning: {
6178
- strategy: "react",
6179
- maxIterations: 5,
6180
- allowReplan: false
6181
- },
6182
- memory: {
6183
- shortTerm: {
6184
- maxMessages: 20,
6185
- maxTokens: 4096
6186
- }
6187
- }
6188
- };
6189
-
6190
- // src/agents/metadata-assistant-agent.ts
6191
- var METADATA_ASSISTANT_AGENT = {
6192
- name: "metadata_assistant",
6193
- label: "Metadata Assistant",
6194
- role: "Schema Architect",
6195
- instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
6196
-
6197
- 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.`,
6198
- model: {
6199
- provider: "openai",
6200
- model: "gpt-4",
6201
- temperature: 0.2,
6202
- maxTokens: 4096
6203
- },
6204
- // Capability bundle lives on the skill; the agent only references it.
6205
- skills: ["metadata_authoring"],
6206
- active: true,
6207
- visibility: "global",
6208
- guardrails: {
6209
- maxTokensPerInvocation: 8192,
6210
- maxExecutionTimeSec: 60,
6211
- blockedTopics: ["drop_database", "raw_sql", "system_tables"]
6212
- },
6213
- planning: {
6214
- strategy: "react",
6215
- maxIterations: 10,
6216
- allowReplan: true
6217
- },
6218
- memory: {
6219
- shortTerm: {
6220
- maxMessages: 30,
6221
- maxTokens: 8192
6222
- }
6223
- }
6224
- };
6225
-
6226
6274
  // src/skills/data-explorer-skill.ts
6227
6275
  var DATA_EXPLORER_SKILL = {
6228
6276
  name: "data_explorer",
@@ -6897,7 +6945,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6897
6945
  async start(ctx) {
6898
6946
  if (!this.service) return;
6899
6947
  let metadataService;
6900
- const withTimeout = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
6948
+ const withTimeout2 = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
6901
6949
  try {
6902
6950
  metadataService = ctx.getService("metadata");
6903
6951
  console.log("[AI Plugin] Retrieved metadata service:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
@@ -6906,7 +6954,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6906
6954
  ctx.logger.debug("[AI] Metadata service not available");
6907
6955
  }
6908
6956
  if (metadataService && typeof metadataService.exists === "function") {
6909
- const probeResult = await withTimeout(metadataService.exists("tool", "__probe__"), 3e3);
6957
+ const probeResult = await withTimeout2(metadataService.exists("tool", "__probe__"), 3e3);
6910
6958
  if (probeResult === null) {
6911
6959
  ctx.logger.warn("[AI] Metadata service unreachable (timed out) \u2014 AI tools/agents will work but Studio visibility unavailable");
6912
6960
  metadataService = void 0;
@@ -6978,7 +7026,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6978
7026
  if (metadataService) {
6979
7027
  const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
6980
7028
  for (const toolDef of DATA_TOOL_DEFINITIONS2) {
6981
- const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
7029
+ const toolExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("tool", toolDef.name)) : false;
6982
7030
  if (toolExists === null) {
6983
7031
  ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
6984
7032
  break;
@@ -6986,7 +7034,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6986
7034
  if (!toolExists) {
6987
7035
  try {
6988
7036
  const label = toolDef.label ?? toToolLabel(toolDef.name);
6989
- await withTimeout(metadataService.register("tool", toolDef.name, { ...toolDef, label }));
7037
+ await withTimeout2(metadataService.register("tool", toolDef.name, { ...toolDef, label }));
6990
7038
  } catch (err) {
6991
7039
  ctx.logger.warn(
6992
7040
  "[AI] Failed to persist tool metadata (non-fatal)",
@@ -6999,11 +7047,11 @@ var _AIServicePlugin = class _AIServicePlugin {
6999
7047
  }
7000
7048
  if (metadataService) {
7001
7049
  try {
7002
- const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
7050
+ const agentExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
7003
7051
  if (agentExists === null) {
7004
7052
  ctx.logger.warn("[AI] Metadata service timed out checking data_chat agent, skipping");
7005
7053
  } else if (!agentExists) {
7006
- await withTimeout(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
7054
+ await withTimeout2(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
7007
7055
  console.log("[AI] Registered data_chat agent to metadataService");
7008
7056
  ctx.logger.info("[AI] data_chat agent registered");
7009
7057
  } else {
@@ -7014,11 +7062,11 @@ var _AIServicePlugin = class _AIServicePlugin {
7014
7062
  ctx.logger.warn("[AI] Failed to register data_chat agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
7015
7063
  }
7016
7064
  try {
7017
- const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
7065
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
7018
7066
  if (skillExists === null) {
7019
7067
  ctx.logger.warn("[AI] Metadata service timed out checking data_explorer skill, skipping");
7020
7068
  } else if (!skillExists) {
7021
- await withTimeout(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
7069
+ await withTimeout2(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
7022
7070
  ctx.logger.info("[AI] data_explorer skill registered");
7023
7071
  } else {
7024
7072
  ctx.logger.debug("[AI] data_explorer skill already exists, skipping auto-registration");
@@ -7027,11 +7075,11 @@ var _AIServicePlugin = class _AIServicePlugin {
7027
7075
  ctx.logger.warn("[AI] Failed to register data_explorer skill", err instanceof Error ? { error: err.message } : { error: String(err) });
7028
7076
  }
7029
7077
  try {
7030
- const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", ACTIONS_EXECUTOR_SKILL.name)) : false;
7078
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("skill", ACTIONS_EXECUTOR_SKILL.name)) : false;
7031
7079
  if (skillExists === null) {
7032
7080
  ctx.logger.warn("[AI] Metadata service timed out checking actions_executor skill, skipping");
7033
7081
  } else if (!skillExists) {
7034
- await withTimeout(metadataService.register("skill", ACTIONS_EXECUTOR_SKILL.name, ACTIONS_EXECUTOR_SKILL));
7082
+ await withTimeout2(metadataService.register("skill", ACTIONS_EXECUTOR_SKILL.name, ACTIONS_EXECUTOR_SKILL));
7035
7083
  ctx.logger.info("[AI] actions_executor skill registered");
7036
7084
  } else {
7037
7085
  ctx.logger.debug("[AI] actions_executor skill already exists, skipping auto-registration");
@@ -7050,14 +7098,14 @@ var _AIServicePlugin = class _AIServicePlugin {
7050
7098
  ctx.logger.info("[AI] Built-in metadata tools registered");
7051
7099
  const { METADATA_TOOL_DEFINITIONS: METADATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_metadata_tools(), metadata_tools_exports));
7052
7100
  for (const toolDef of METADATA_TOOL_DEFINITIONS2) {
7053
- const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
7101
+ const toolExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("tool", toolDef.name)) : false;
7054
7102
  if (toolExists === null) {
7055
7103
  ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
7056
7104
  break;
7057
7105
  }
7058
7106
  if (!toolExists) {
7059
7107
  try {
7060
- await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
7108
+ await withTimeout2(metadataService.register("tool", toolDef.name, toolDef));
7061
7109
  } catch (err) {
7062
7110
  ctx.logger.warn(
7063
7111
  "[AI] Failed to persist tool metadata (non-fatal)",
@@ -7068,11 +7116,11 @@ var _AIServicePlugin = class _AIServicePlugin {
7068
7116
  }
7069
7117
  ctx.logger.info(`[AI] ${METADATA_TOOL_DEFINITIONS2.length} metadata tools registered as metadata`);
7070
7118
  try {
7071
- const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
7119
+ const agentExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
7072
7120
  if (agentExists === null) {
7073
7121
  ctx.logger.warn("[AI] Metadata service timed out checking metadata_assistant agent, skipping");
7074
7122
  } else if (!agentExists) {
7075
- await withTimeout(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
7123
+ await withTimeout2(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
7076
7124
  console.log("[AI] Registered metadata_assistant agent to metadataService");
7077
7125
  ctx.logger.info("[AI] metadata_assistant agent registered");
7078
7126
  } else {
@@ -7083,11 +7131,11 @@ var _AIServicePlugin = class _AIServicePlugin {
7083
7131
  ctx.logger.warn("[AI] Failed to register metadata_assistant agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
7084
7132
  }
7085
7133
  try {
7086
- const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
7134
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
7087
7135
  if (skillExists === null) {
7088
7136
  ctx.logger.warn("[AI] Metadata service timed out checking metadata_authoring skill, skipping");
7089
7137
  } else if (!skillExists) {
7090
- await withTimeout(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
7138
+ await withTimeout2(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
7091
7139
  ctx.logger.info("[AI] metadata_authoring skill registered");
7092
7140
  } else {
7093
7141
  ctx.logger.debug("[AI] metadata_authoring skill already exists, skipping auto-registration");
@@ -7111,10 +7159,10 @@ var _AIServicePlugin = class _AIServicePlugin {
7111
7159
  const agent = entry?.content ?? entry;
7112
7160
  const agentName = agent?.name;
7113
7161
  if (!agentName || typeof agentName !== "string") continue;
7114
- const exists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", agentName)) : false;
7162
+ const exists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("agent", agentName)) : false;
7115
7163
  if (exists === true) continue;
7116
7164
  try {
7117
- await withTimeout(metadataService.register("agent", agentName, agent));
7165
+ await withTimeout2(metadataService.register("agent", agentName, agent));
7118
7166
  bridged++;
7119
7167
  } catch (err) {
7120
7168
  ctx.logger.warn(