@lowdefy/api 0.0.0-experimental-20260420104522 → 0.0.0-experimental-20260420135540

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.
@@ -13,6 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { serializer, type } from '@lowdefy/helpers';
16
+ import { createPhaseLogger } from '@lowdefy/ai-utils';
16
17
  import createEvaluateOperators from '../../context/createEvaluateOperators.js';
17
18
  import authorizeApiEndpoint from '../endpoints/authorizeApiEndpoint.js';
18
19
  import getEndpointConfig from '../endpoints/getEndpointConfig.js';
@@ -21,18 +22,30 @@ import getAgentConfig from './getAgentConfig.js';
21
22
  import getAgentResolver from './getAgentResolver.js';
22
23
  import getConnectionConfig from '../request/getConnectionConfig.js';
23
24
  import getConnection from '../request/getConnection.js';
24
- async function callAgent(context, { agentId, pageId, messages, conversationId, urlQuery, sharedState }) {
25
+ async function callAgent(context, { agentId, pageId, messages, conversationId, urlQuery, sharedState, phaseLogger: ingressPhaseLogger, turnId }) {
25
26
  const { logger } = context;
26
27
  context.pageId = pageId;
27
28
  context.evaluateOperators = createEvaluateOperators(context);
29
+ // Use the ingress-provided phase logger when present so turnId/turnStart are
30
+ // continuous with the HTTP-ingress logs. Fall back to a fresh logger so
31
+ // internal callers of callAgent still get structured output.
32
+ const phaseLogger = ingressPhaseLogger ?? createPhaseLogger({
33
+ logger,
34
+ agentId,
35
+ pageId,
36
+ conversationId: conversationId ?? undefined,
37
+ turnId
38
+ });
39
+ context.phaseLogger = phaseLogger;
28
40
  logger.debug({
29
41
  event: 'debug_agent',
30
42
  agentId,
31
43
  pageId
32
44
  });
33
- const agentConfig = await getAgentConfig(context, {
34
- agentId
35
- });
45
+ phaseLogger.phase('handler.entry');
46
+ const agentConfig = await phaseLogger.time('config.agent.load', ()=>getAgentConfig(context, {
47
+ agentId
48
+ }));
36
49
  const agentContext = {
37
50
  conversationId: conversationId ?? undefined,
38
51
  pageId,
@@ -41,75 +54,88 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
41
54
  userId: context.user?.sub ?? context.user?.id ?? null
42
55
  };
43
56
  // Evaluate operators in agent properties (e.g. _user, _secret, _payload)
44
- agentConfig.properties = context.evaluateOperators({
45
- input: agentConfig.properties ?? {},
46
- location: agentConfig.agentId,
47
- payload: agentContext,
48
- steps: {}
49
- });
57
+ agentConfig.properties = await phaseLogger.time('operators.agent.properties', ()=>context.evaluateOperators({
58
+ input: agentConfig.properties ?? {},
59
+ location: agentConfig.agentId,
60
+ payload: agentContext,
61
+ steps: {}
62
+ }));
50
63
  // Load connection config from build artifacts using agent's connectionId
51
- const connectionConfig = await getConnectionConfig(context, {
52
- requestConfig: {
53
- connectionId: agentConfig.connectionId,
54
- requestId: agentConfig.agentId,
55
- '~k': agentConfig['~k']
56
- }
57
- });
64
+ const connectionConfig = await phaseLogger.time('config.connection.load', ()=>getConnectionConfig(context, {
65
+ requestConfig: {
66
+ connectionId: agentConfig.connectionId,
67
+ requestId: agentConfig.agentId,
68
+ '~k': agentConfig['~k']
69
+ }
70
+ }));
58
71
  // Get connection plugin from registry
59
72
  const connection = getConnection(context, {
60
73
  connectionConfig
61
74
  });
62
75
  // Evaluate operators in connection properties
63
- const connectionProperties = context.evaluateOperators({
64
- input: connectionConfig.properties || {},
65
- location: connectionConfig.connectionId,
66
- payload: {},
67
- steps: {}
68
- });
76
+ const connectionProperties = await phaseLogger.time('operators.connection.properties', ()=>context.evaluateOperators({
77
+ input: connectionConfig.properties || {},
78
+ location: connectionConfig.connectionId,
79
+ payload: {},
80
+ steps: {}
81
+ }));
69
82
  // Create connection instance (e.g., Anthropic provider)
70
- const connectionInstance = connection.create({
71
- connection: connectionProperties
72
- });
83
+ const connectionInstance = await phaseLogger.time('connection.instantiate', ()=>Promise.resolve(connection.create({
84
+ connection: connectionProperties
85
+ })));
73
86
  // Get agent type from plugin registry
74
87
  const agentType = getAgentResolver(context, {
75
88
  agentConfig
76
89
  });
90
+ phaseLogger.phase('resolver.lookup.done', {
91
+ type: agentConfig.type
92
+ });
77
93
  // Build resolver context with callEndpoint that allows InternalApi endpoints
78
94
  const resolverContext = {
79
95
  agentContext,
96
+ phaseLogger,
80
97
  evaluateOperators: (input)=>context.evaluateOperators({
81
98
  input,
82
99
  location: agentConfig.agentId,
83
100
  payload: agentContext,
84
101
  steps: {}
85
102
  }),
86
- callEndpoint: async (endpointId, { payload, abortSignal })=>{
87
- const endpointConfig = await getEndpointConfig(context, {
88
- endpointId
89
- });
90
- authorizeApiEndpoint(context, {
91
- endpointConfig
103
+ callEndpoint: async (endpointId, { payload, abortSignal, kind = 'tool' } = {})=>{
104
+ const prefix = kind === 'hook' ? 'hook.endpoint' : 'tool.endpoint';
105
+ const callLog = phaseLogger.child({
106
+ endpointId,
107
+ kind
92
108
  });
93
- const routineContext = {
94
- steps: {},
95
- payload: payload ?? {},
96
- arrayIndices: [],
97
- items: {},
98
- endpointDepth: 0
99
- };
100
- const { error, response, status } = await runRoutine(context, routineContext, {
101
- routine: endpointConfig.routine
109
+ return callLog.time(`${prefix}.exec`, async ()=>{
110
+ const endpointConfig = await callLog.time(`${prefix}.config.load`, ()=>getEndpointConfig(context, {
111
+ endpointId
112
+ }));
113
+ authorizeApiEndpoint(context, {
114
+ endpointConfig
115
+ });
116
+ const routineContext = {
117
+ steps: {},
118
+ payload: payload ?? {},
119
+ arrayIndices: [],
120
+ items: {},
121
+ endpointDepth: 0
122
+ };
123
+ const { error, response, status } = await runRoutine(context, routineContext, {
124
+ routine: endpointConfig.routine
125
+ });
126
+ const success = ![
127
+ 'error',
128
+ 'reject'
129
+ ].includes(status);
130
+ return {
131
+ error: serializer.serialize(error),
132
+ response: serializer.serialize(response),
133
+ status: success ? 'success' : status,
134
+ success
135
+ };
136
+ }, {
137
+ hasPayload: payload != null
102
138
  });
103
- const success = ![
104
- 'error',
105
- 'reject'
106
- ].includes(status);
107
- return {
108
- error: serializer.serialize(error),
109
- response: serializer.serialize(response),
110
- status: success ? 'success' : status,
111
- success
112
- };
113
139
  },
114
140
  getEndpointConfig: async ({ endpointId })=>{
115
141
  return getEndpointConfig(context, {
@@ -181,28 +207,39 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
181
207
  // Agent-level overrides (like confirm) may still contain operators —
182
208
  // handleAgentChat evaluates those via its existing evaluateOperators call.
183
209
  const resolvedMcp = [];
184
- for (const mcpSource of agentConfig.mcp ?? []){
210
+ const mcpSources = agentConfig.mcp ?? [];
211
+ phaseLogger.phase('mcp.sources.resolve.start', {
212
+ sourceCount: mcpSources.length
213
+ });
214
+ for(let i = 0; i < mcpSources.length; i += 1){
215
+ const mcpSource = mcpSources[i];
185
216
  if (!type.isNone(mcpSource.connectionId)) {
186
- const mcpConnConfig = await getConnectionConfig(context, {
187
- requestConfig: {
188
- connectionId: mcpSource.connectionId,
189
- requestId: agentConfig.agentId,
190
- '~k': agentConfig['~k']
191
- }
217
+ const mcpLog = phaseLogger.child({
218
+ mcpIndex: i,
219
+ connectionId: mcpSource.connectionId
192
220
  });
193
- const mcpConnection = getConnection(context, {
194
- connectionConfig: mcpConnConfig
221
+ // eslint-disable-next-line no-await-in-loop
222
+ const mcpConfig = await mcpLog.time('mcp.source.resolve', async ()=>{
223
+ const mcpConnConfig = await getConnectionConfig(context, {
224
+ requestConfig: {
225
+ connectionId: mcpSource.connectionId,
226
+ requestId: agentConfig.agentId,
227
+ '~k': agentConfig['~k']
228
+ }
229
+ });
230
+ const mcpConnection = getConnection(context, {
231
+ connectionConfig: mcpConnConfig
232
+ });
233
+ const mcpConnProps = context.evaluateOperators({
234
+ input: mcpConnConfig.properties || {},
235
+ location: mcpConnConfig.connectionId,
236
+ payload: {},
237
+ steps: {}
238
+ });
239
+ return mcpConnection.create({
240
+ connection: mcpConnProps
241
+ });
195
242
  });
196
- const mcpConnProps = context.evaluateOperators({
197
- input: mcpConnConfig.properties || {},
198
- location: mcpConnConfig.connectionId,
199
- payload: {},
200
- steps: {}
201
- });
202
- const mcpConfig = mcpConnection.create({
203
- connection: mcpConnProps
204
- });
205
- // Merge: connection properties as base, agent-level overrides on top
206
243
  const { connectionId: _, ...overrides } = mcpSource;
207
244
  resolvedMcp.push({
208
245
  ...mcpConfig,
@@ -213,6 +250,12 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
213
250
  }
214
251
  }
215
252
  agentConfig.mcp = resolvedMcp;
253
+ phaseLogger.phase('mcp.sources.resolve.done', {
254
+ resolvedCount: resolvedMcp.length
255
+ });
256
+ phaseLogger.phase('resolver.dispatch.start', {
257
+ type: agentConfig.type
258
+ });
216
259
  // Call the agent resolver
217
260
  const { response } = await agentType.resolver({
218
261
  connection: connectionInstance,
@@ -222,6 +265,7 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
222
265
  },
223
266
  context: resolverContext
224
267
  });
268
+ phaseLogger.phase('resolver.dispatch.returned');
225
269
  return {
226
270
  response
227
271
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/api",
3
- "version": "0.0.0-experimental-20260420104522",
3
+ "version": "0.0.0-experimental-20260420135540",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -34,13 +34,14 @@
34
34
  "dist/*"
35
35
  ],
36
36
  "dependencies": {
37
- "@lowdefy/ajv": "0.0.0-experimental-20260420104522",
38
- "@lowdefy/errors": "0.0.0-experimental-20260420104522",
39
- "@lowdefy/helpers": "0.0.0-experimental-20260420104522",
40
- "@lowdefy/node-utils": "0.0.0-experimental-20260420104522",
41
- "@lowdefy/nunjucks": "0.0.0-experimental-20260420104522",
42
- "@lowdefy/operators": "0.0.0-experimental-20260420104522",
43
- "@lowdefy/operators-js": "0.0.0-experimental-20260420104522"
37
+ "@lowdefy/ai-utils": "0.0.0-experimental-20260420135540",
38
+ "@lowdefy/ajv": "0.0.0-experimental-20260420135540",
39
+ "@lowdefy/errors": "0.0.0-experimental-20260420135540",
40
+ "@lowdefy/helpers": "0.0.0-experimental-20260420135540",
41
+ "@lowdefy/node-utils": "0.0.0-experimental-20260420135540",
42
+ "@lowdefy/nunjucks": "0.0.0-experimental-20260420135540",
43
+ "@lowdefy/operators": "0.0.0-experimental-20260420135540",
44
+ "@lowdefy/operators-js": "0.0.0-experimental-20260420135540"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@jest/globals": "28.1.3",