@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.js CHANGED
@@ -5071,7 +5071,8 @@ var SchemaRetriever = class {
5071
5071
  if (obj.label) parts.push(obj.label);
5072
5072
  if (obj.pluralLabel && obj.pluralLabel !== obj.label) parts.push(`(${obj.pluralLabel})`);
5073
5073
  const header = parts.length > 0 ? ` \u2014 ${parts.join(" ")}` : "";
5074
- lines.push(`### ${obj.name}${header}`);
5074
+ const badge = obj.external !== void 0 ? ` [external, ${obj.external?.writable ? "writable" : "read-only"}, datasource=${obj.datasource ?? "default"}]` : "";
5075
+ lines.push(`### ${obj.name}${header}${badge}`);
5075
5076
  const fields = Object.entries(obj.fields ?? {}).slice(0, maxFieldsPerObject);
5076
5077
  for (const [name, field] of fields) {
5077
5078
  lines.push(` - ${name}: ${describeField(field)}`);
@@ -5215,6 +5216,15 @@ var QUERY_DATA_TOOL = {
5215
5216
  function createQueryDataHandler(ctx) {
5216
5217
  const retriever = new SchemaRetriever(ctx.metadata, {}, ctx.protocol);
5217
5218
  const maxLimit = ctx.maxLimit ?? 100;
5219
+ const externalTimeoutFallback = ctx.externalQueryTimeoutMs ?? 3e4;
5220
+ const resolveExternalTimeout = async (datasource) => {
5221
+ try {
5222
+ const ds = await ctx.metadata.get?.("datasource", datasource);
5223
+ return ds?.external?.queryTimeoutMs ?? externalTimeoutFallback;
5224
+ } catch {
5225
+ return externalTimeoutFallback;
5226
+ }
5227
+ };
5218
5228
  return async (args, execCtx) => {
5219
5229
  const { request } = args;
5220
5230
  if (!request || typeof request !== "string") {
@@ -5277,14 +5287,20 @@ function createQueryDataHandler(ctx) {
5277
5287
  });
5278
5288
  }
5279
5289
  }
5290
+ const isExternal = matchedObject.external !== void 0;
5280
5291
  try {
5281
- const records = await ctx.dataEngine.find(plan.objectName, {
5292
+ const findPromise = ctx.dataEngine.find(plan.objectName, {
5282
5293
  where,
5283
5294
  fields: plan.fields ?? void 0,
5284
5295
  orderBy: plan.orderBy ?? void 0,
5285
5296
  limit,
5286
5297
  context: buildAiEngineContext(execCtx)
5287
5298
  });
5299
+ const records = isExternal ? await withTimeout(
5300
+ findPromise,
5301
+ await resolveExternalTimeout(matchedObject.datasource ?? "default"),
5302
+ plan.objectName
5303
+ ) : await findPromise;
5288
5304
  return JSON.stringify({
5289
5305
  plan: { ...plan, where },
5290
5306
  count: records.length,
@@ -5298,6 +5314,28 @@ function createQueryDataHandler(ctx) {
5298
5314
  }
5299
5315
  };
5300
5316
  }
5317
+ function withTimeout(p, ms, object) {
5318
+ return new Promise((resolve, reject) => {
5319
+ const timer = setTimeout(() => {
5320
+ reject(
5321
+ new Error(
5322
+ `query on external object '${object}' exceeded the ${ms}ms timeout. Narrow the filter or lower the limit.`
5323
+ )
5324
+ );
5325
+ }, ms);
5326
+ timer.unref?.();
5327
+ p.then(
5328
+ (v) => {
5329
+ clearTimeout(timer);
5330
+ resolve(v);
5331
+ },
5332
+ (e) => {
5333
+ clearTimeout(timer);
5334
+ reject(e);
5335
+ }
5336
+ );
5337
+ });
5338
+ }
5301
5339
  function registerQueryDataTool(registry, context) {
5302
5340
  registry.register(QUERY_DATA_TOOL, createQueryDataHandler(context));
5303
5341
  }
@@ -5718,6 +5756,82 @@ async function registerActionsAsTools(registry, context) {
5718
5756
 
5719
5757
  // src/agent-runtime.ts
5720
5758
  import { AgentSchema } from "@objectstack/spec/ai";
5759
+
5760
+ // src/agents/data-chat-agent.ts
5761
+ var DEFAULT_DATA_AGENT_NAME = "data_chat";
5762
+ var DATA_CHAT_AGENT = {
5763
+ name: DEFAULT_DATA_AGENT_NAME,
5764
+ label: "Data Assistant",
5765
+ role: "Business Data Analyst",
5766
+ instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
5767
+
5768
+ Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
5769
+ model: {
5770
+ provider: "openai",
5771
+ model: "gpt-4",
5772
+ temperature: 0.3,
5773
+ maxTokens: 4096
5774
+ },
5775
+ // Capability bundle lives on the skill; the agent only references it.
5776
+ // `data_explorer` = read side, `actions_executor` = write side.
5777
+ skills: ["data_explorer", "actions_executor"],
5778
+ active: true,
5779
+ visibility: "global",
5780
+ guardrails: {
5781
+ maxTokensPerInvocation: 8192,
5782
+ maxExecutionTimeSec: 30,
5783
+ blockedTopics: ["delete_records", "drop_table", "alter_schema"]
5784
+ },
5785
+ planning: {
5786
+ strategy: "react",
5787
+ maxIterations: 5,
5788
+ allowReplan: false
5789
+ },
5790
+ memory: {
5791
+ shortTerm: {
5792
+ maxMessages: 20,
5793
+ maxTokens: 4096
5794
+ }
5795
+ }
5796
+ };
5797
+
5798
+ // src/agents/metadata-assistant-agent.ts
5799
+ var METADATA_ASSISTANT_AGENT = {
5800
+ name: "metadata_assistant",
5801
+ label: "Metadata Assistant",
5802
+ role: "Schema Architect",
5803
+ instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
5804
+
5805
+ 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.`,
5806
+ model: {
5807
+ provider: "openai",
5808
+ model: "gpt-4",
5809
+ temperature: 0.2,
5810
+ maxTokens: 4096
5811
+ },
5812
+ // Capability bundle lives on the skill; the agent only references it.
5813
+ skills: ["metadata_authoring"],
5814
+ active: true,
5815
+ visibility: "global",
5816
+ guardrails: {
5817
+ maxTokensPerInvocation: 8192,
5818
+ maxExecutionTimeSec: 60,
5819
+ blockedTopics: ["drop_database", "raw_sql", "system_tables"]
5820
+ },
5821
+ planning: {
5822
+ strategy: "react",
5823
+ maxIterations: 10,
5824
+ allowReplan: true
5825
+ },
5826
+ memory: {
5827
+ shortTerm: {
5828
+ maxMessages: 30,
5829
+ maxTokens: 8192
5830
+ }
5831
+ }
5832
+ };
5833
+
5834
+ // src/agent-runtime.ts
5721
5835
  var AgentRuntime = class {
5722
5836
  constructor(metadataService, skillRegistry) {
5723
5837
  this.metadataService = metadataService;
@@ -5868,9 +5982,14 @@ var AgentRuntime = class {
5868
5982
  * chat endpoint when the client doesn't specify an `agentName`.
5869
5983
  *
5870
5984
  * Resolution order:
5871
- * 1. The `defaultAgent` of the app named by `context.appName`.
5872
- * 2. The first active agent in the registry (deterministic fallback).
5873
- * 3. `undefined` if no agents are registered.
5985
+ * 1. The `defaultAgent` of the app named by `context.appName`
5986
+ * (e.g. Studio `metadata_assistant`).
5987
+ * 2. The platform data-query agent (`data_chat`) the implicit
5988
+ * copilot bound to every app that doesn't pin its own. This is
5989
+ * what end users get by default, so they never have to choose.
5990
+ * 3. The first active agent in the registry (last-resort fallback,
5991
+ * e.g. in stripped-down deployments without the data agent).
5992
+ * 4. `undefined` if no agents are registered.
5874
5993
  */
5875
5994
  async resolveDefaultAgent(context) {
5876
5995
  if (context?.appName) {
@@ -5881,6 +6000,8 @@ var AgentRuntime = class {
5881
6000
  if (agent && agent.active !== false) return agent;
5882
6001
  }
5883
6002
  }
6003
+ const dataAgent = await this.loadAgent(DEFAULT_DATA_AGENT_NAME);
6004
+ if (dataAgent && dataAgent.active !== false) return dataAgent;
5884
6005
  const summaries = await this.listAgents();
5885
6006
  if (summaries.length === 0) return void 0;
5886
6007
  return this.loadAgent(summaries[0].name);
@@ -6076,79 +6197,6 @@ var SkillRegistry = class {
6076
6197
  }
6077
6198
  };
6078
6199
 
6079
- // src/agents/data-chat-agent.ts
6080
- var DATA_CHAT_AGENT = {
6081
- name: "data_chat",
6082
- label: "Data Assistant",
6083
- role: "Business Data Analyst",
6084
- instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
6085
-
6086
- Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
6087
- model: {
6088
- provider: "openai",
6089
- model: "gpt-4",
6090
- temperature: 0.3,
6091
- maxTokens: 4096
6092
- },
6093
- // Capability bundle lives on the skill; the agent only references it.
6094
- // `data_explorer` = read side, `actions_executor` = write side.
6095
- skills: ["data_explorer", "actions_executor"],
6096
- active: true,
6097
- visibility: "global",
6098
- guardrails: {
6099
- maxTokensPerInvocation: 8192,
6100
- maxExecutionTimeSec: 30,
6101
- blockedTopics: ["delete_records", "drop_table", "alter_schema"]
6102
- },
6103
- planning: {
6104
- strategy: "react",
6105
- maxIterations: 5,
6106
- allowReplan: false
6107
- },
6108
- memory: {
6109
- shortTerm: {
6110
- maxMessages: 20,
6111
- maxTokens: 4096
6112
- }
6113
- }
6114
- };
6115
-
6116
- // src/agents/metadata-assistant-agent.ts
6117
- var METADATA_ASSISTANT_AGENT = {
6118
- name: "metadata_assistant",
6119
- label: "Metadata Assistant",
6120
- role: "Schema Architect",
6121
- instructions: `You are an expert metadata architect that helps users design and manage their data models through natural language.
6122
-
6123
- 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.`,
6124
- model: {
6125
- provider: "openai",
6126
- model: "gpt-4",
6127
- temperature: 0.2,
6128
- maxTokens: 4096
6129
- },
6130
- // Capability bundle lives on the skill; the agent only references it.
6131
- skills: ["metadata_authoring"],
6132
- active: true,
6133
- visibility: "global",
6134
- guardrails: {
6135
- maxTokensPerInvocation: 8192,
6136
- maxExecutionTimeSec: 60,
6137
- blockedTopics: ["drop_database", "raw_sql", "system_tables"]
6138
- },
6139
- planning: {
6140
- strategy: "react",
6141
- maxIterations: 10,
6142
- allowReplan: true
6143
- },
6144
- memory: {
6145
- shortTerm: {
6146
- maxMessages: 30,
6147
- maxTokens: 8192
6148
- }
6149
- }
6150
- };
6151
-
6152
6200
  // src/skills/data-explorer-skill.ts
6153
6201
  var DATA_EXPLORER_SKILL = {
6154
6202
  name: "data_explorer",
@@ -6823,7 +6871,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6823
6871
  async start(ctx) {
6824
6872
  if (!this.service) return;
6825
6873
  let metadataService;
6826
- const withTimeout = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
6874
+ const withTimeout2 = (promise, ms = 2e3) => Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
6827
6875
  try {
6828
6876
  metadataService = ctx.getService("metadata");
6829
6877
  console.log("[AI Plugin] Retrieved metadata service:", !!metadataService, "has getRegisteredTypes:", typeof metadataService?.getRegisteredTypes);
@@ -6832,7 +6880,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6832
6880
  ctx.logger.debug("[AI] Metadata service not available");
6833
6881
  }
6834
6882
  if (metadataService && typeof metadataService.exists === "function") {
6835
- const probeResult = await withTimeout(metadataService.exists("tool", "__probe__"), 3e3);
6883
+ const probeResult = await withTimeout2(metadataService.exists("tool", "__probe__"), 3e3);
6836
6884
  if (probeResult === null) {
6837
6885
  ctx.logger.warn("[AI] Metadata service unreachable (timed out) \u2014 AI tools/agents will work but Studio visibility unavailable");
6838
6886
  metadataService = void 0;
@@ -6904,7 +6952,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6904
6952
  if (metadataService) {
6905
6953
  const { DATA_TOOL_DEFINITIONS: DATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_data_tools(), data_tools_exports));
6906
6954
  for (const toolDef of DATA_TOOL_DEFINITIONS2) {
6907
- const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
6955
+ const toolExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("tool", toolDef.name)) : false;
6908
6956
  if (toolExists === null) {
6909
6957
  ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
6910
6958
  break;
@@ -6912,7 +6960,7 @@ var _AIServicePlugin = class _AIServicePlugin {
6912
6960
  if (!toolExists) {
6913
6961
  try {
6914
6962
  const label = toolDef.label ?? toToolLabel(toolDef.name);
6915
- await withTimeout(metadataService.register("tool", toolDef.name, { ...toolDef, label }));
6963
+ await withTimeout2(metadataService.register("tool", toolDef.name, { ...toolDef, label }));
6916
6964
  } catch (err) {
6917
6965
  ctx.logger.warn(
6918
6966
  "[AI] Failed to persist tool metadata (non-fatal)",
@@ -6925,11 +6973,11 @@ var _AIServicePlugin = class _AIServicePlugin {
6925
6973
  }
6926
6974
  if (metadataService) {
6927
6975
  try {
6928
- const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
6976
+ const agentExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("agent", DATA_CHAT_AGENT.name)) : false;
6929
6977
  if (agentExists === null) {
6930
6978
  ctx.logger.warn("[AI] Metadata service timed out checking data_chat agent, skipping");
6931
6979
  } else if (!agentExists) {
6932
- await withTimeout(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
6980
+ await withTimeout2(metadataService.register("agent", DATA_CHAT_AGENT.name, DATA_CHAT_AGENT));
6933
6981
  console.log("[AI] Registered data_chat agent to metadataService");
6934
6982
  ctx.logger.info("[AI] data_chat agent registered");
6935
6983
  } else {
@@ -6940,11 +6988,11 @@ var _AIServicePlugin = class _AIServicePlugin {
6940
6988
  ctx.logger.warn("[AI] Failed to register data_chat agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
6941
6989
  }
6942
6990
  try {
6943
- const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
6991
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("skill", DATA_EXPLORER_SKILL.name)) : false;
6944
6992
  if (skillExists === null) {
6945
6993
  ctx.logger.warn("[AI] Metadata service timed out checking data_explorer skill, skipping");
6946
6994
  } else if (!skillExists) {
6947
- await withTimeout(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
6995
+ await withTimeout2(metadataService.register("skill", DATA_EXPLORER_SKILL.name, DATA_EXPLORER_SKILL));
6948
6996
  ctx.logger.info("[AI] data_explorer skill registered");
6949
6997
  } else {
6950
6998
  ctx.logger.debug("[AI] data_explorer skill already exists, skipping auto-registration");
@@ -6953,11 +7001,11 @@ var _AIServicePlugin = class _AIServicePlugin {
6953
7001
  ctx.logger.warn("[AI] Failed to register data_explorer skill", err instanceof Error ? { error: err.message } : { error: String(err) });
6954
7002
  }
6955
7003
  try {
6956
- const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", ACTIONS_EXECUTOR_SKILL.name)) : false;
7004
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("skill", ACTIONS_EXECUTOR_SKILL.name)) : false;
6957
7005
  if (skillExists === null) {
6958
7006
  ctx.logger.warn("[AI] Metadata service timed out checking actions_executor skill, skipping");
6959
7007
  } else if (!skillExists) {
6960
- await withTimeout(metadataService.register("skill", ACTIONS_EXECUTOR_SKILL.name, ACTIONS_EXECUTOR_SKILL));
7008
+ await withTimeout2(metadataService.register("skill", ACTIONS_EXECUTOR_SKILL.name, ACTIONS_EXECUTOR_SKILL));
6961
7009
  ctx.logger.info("[AI] actions_executor skill registered");
6962
7010
  } else {
6963
7011
  ctx.logger.debug("[AI] actions_executor skill already exists, skipping auto-registration");
@@ -6976,14 +7024,14 @@ var _AIServicePlugin = class _AIServicePlugin {
6976
7024
  ctx.logger.info("[AI] Built-in metadata tools registered");
6977
7025
  const { METADATA_TOOL_DEFINITIONS: METADATA_TOOL_DEFINITIONS2 } = await Promise.resolve().then(() => (init_metadata_tools(), metadata_tools_exports));
6978
7026
  for (const toolDef of METADATA_TOOL_DEFINITIONS2) {
6979
- const toolExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("tool", toolDef.name)) : false;
7027
+ const toolExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("tool", toolDef.name)) : false;
6980
7028
  if (toolExists === null) {
6981
7029
  ctx.logger.warn("[AI] Metadata service timed out checking tool existence (non-fatal), skipping persistence");
6982
7030
  break;
6983
7031
  }
6984
7032
  if (!toolExists) {
6985
7033
  try {
6986
- await withTimeout(metadataService.register("tool", toolDef.name, toolDef));
7034
+ await withTimeout2(metadataService.register("tool", toolDef.name, toolDef));
6987
7035
  } catch (err) {
6988
7036
  ctx.logger.warn(
6989
7037
  "[AI] Failed to persist tool metadata (non-fatal)",
@@ -6994,11 +7042,11 @@ var _AIServicePlugin = class _AIServicePlugin {
6994
7042
  }
6995
7043
  ctx.logger.info(`[AI] ${METADATA_TOOL_DEFINITIONS2.length} metadata tools registered as metadata`);
6996
7044
  try {
6997
- const agentExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
7045
+ const agentExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("agent", METADATA_ASSISTANT_AGENT.name)) : false;
6998
7046
  if (agentExists === null) {
6999
7047
  ctx.logger.warn("[AI] Metadata service timed out checking metadata_assistant agent, skipping");
7000
7048
  } else if (!agentExists) {
7001
- await withTimeout(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
7049
+ await withTimeout2(metadataService.register("agent", METADATA_ASSISTANT_AGENT.name, METADATA_ASSISTANT_AGENT));
7002
7050
  console.log("[AI] Registered metadata_assistant agent to metadataService");
7003
7051
  ctx.logger.info("[AI] metadata_assistant agent registered");
7004
7052
  } else {
@@ -7009,11 +7057,11 @@ var _AIServicePlugin = class _AIServicePlugin {
7009
7057
  ctx.logger.warn("[AI] Failed to register metadata_assistant agent", err instanceof Error ? { error: err.message, stack: err.stack } : { error: String(err) });
7010
7058
  }
7011
7059
  try {
7012
- const skillExists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
7060
+ const skillExists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("skill", METADATA_AUTHORING_SKILL.name)) : false;
7013
7061
  if (skillExists === null) {
7014
7062
  ctx.logger.warn("[AI] Metadata service timed out checking metadata_authoring skill, skipping");
7015
7063
  } else if (!skillExists) {
7016
- await withTimeout(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
7064
+ await withTimeout2(metadataService.register("skill", METADATA_AUTHORING_SKILL.name, METADATA_AUTHORING_SKILL));
7017
7065
  ctx.logger.info("[AI] metadata_authoring skill registered");
7018
7066
  } else {
7019
7067
  ctx.logger.debug("[AI] metadata_authoring skill already exists, skipping auto-registration");
@@ -7037,10 +7085,10 @@ var _AIServicePlugin = class _AIServicePlugin {
7037
7085
  const agent = entry?.content ?? entry;
7038
7086
  const agentName = agent?.name;
7039
7087
  if (!agentName || typeof agentName !== "string") continue;
7040
- const exists = typeof metadataService.exists === "function" ? await withTimeout(metadataService.exists("agent", agentName)) : false;
7088
+ const exists = typeof metadataService.exists === "function" ? await withTimeout2(metadataService.exists("agent", agentName)) : false;
7041
7089
  if (exists === true) continue;
7042
7090
  try {
7043
- await withTimeout(metadataService.register("agent", agentName, agent));
7091
+ await withTimeout2(metadataService.register("agent", agentName, agent));
7044
7092
  bridged++;
7045
7093
  } catch (err) {
7046
7094
  ctx.logger.warn(