@lowdefy/api 5.5.1 → 6.0.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.
Files changed (56) hide show
  1. package/dist/context/createAuthorize.js +4 -1
  2. package/dist/context/resolveStrategyCaller.js +56 -0
  3. package/dist/index.js +16 -3
  4. package/dist/response/buildEndpointResult.js +33 -0
  5. package/dist/response/normalizeErrorSources.js +64 -0
  6. package/dist/response/omitErrorProps.js +51 -0
  7. package/dist/response/redactErrorResponse.js +30 -0
  8. package/dist/response/redactResponse.js +31 -0
  9. package/dist/routes/agent/authorizeAgent.js +39 -0
  10. package/dist/routes/agent/callAgent.js +7 -180
  11. package/dist/routes/agent/prepareAgent.js +173 -0
  12. package/dist/routes/auth/createLogger.js +2 -5
  13. package/dist/routes/auth/createPrefixedCookies.js +52 -0
  14. package/dist/routes/auth/{getNextAuthConfig.js → getAuthConfig.js} +27 -16
  15. package/dist/routes/auth/resolveCookies.js +37 -0
  16. package/dist/routes/auth/strategies/createAuthStrategies.js +77 -0
  17. package/dist/routes/auth/strategies/getAuthStrategies.js +31 -0
  18. package/dist/routes/endpoints/addStepResult.js +12 -2
  19. package/dist/routes/endpoints/authorizeApiEndpoint.js +8 -2
  20. package/dist/routes/endpoints/callEndpoint.js +32 -13
  21. package/dist/routes/endpoints/control/controlReject.js +3 -1
  22. package/dist/routes/endpoints/control/controlSetState.js +13 -2
  23. package/dist/routes/endpoints/control/controlThrow.js +4 -1
  24. package/dist/routes/endpoints/findSchedule.js +35 -0
  25. package/dist/routes/endpoints/forwardScheduledEndpoint.js +106 -0
  26. package/dist/routes/endpoints/getEndpointConfig.js +9 -3
  27. package/dist/routes/endpoints/getEnvironmentSchedules.js +29 -0
  28. package/dist/routes/endpoints/handleAgentCall.js +83 -0
  29. package/dist/routes/endpoints/handleEndpointCall.js +49 -1
  30. package/dist/routes/endpoints/handleRenderNotification.js +189 -0
  31. package/dist/routes/endpoints/handleValidateSchema.js +3 -1
  32. package/dist/routes/endpoints/isUnauthenticatedHuman.js +31 -0
  33. package/dist/routes/endpoints/resolveCronEnvironment.js +37 -0
  34. package/dist/routes/endpoints/runDetachedEndpoint.js +66 -0
  35. package/dist/routes/endpoints/runRoutine.js +15 -1
  36. package/dist/routes/endpoints/runScheduledEndpoint.js +104 -0
  37. package/dist/routes/endpoints/runWebhookEndpoint.js +83 -0
  38. package/dist/routes/endpoints/scheduleBackground.js +48 -0
  39. package/dist/routes/mcp/createMcpServer.js +160 -0
  40. package/dist/routes/notifications/derivePreview.js +30 -0
  41. package/dist/routes/notifications/getNotificationConfig.js +32 -0
  42. package/dist/routes/notifications/resolveNotificationLinks.js +72 -0
  43. package/dist/routes/notifications/resolveThemeLogo.js +35 -0
  44. package/dist/routes/page/dynamic/resolveDynamicContent.js +172 -0
  45. package/dist/routes/page/dynamic/unescapeOperators.js +36 -0
  46. package/dist/routes/page/dynamic/validateFragment.js +104 -0
  47. package/dist/routes/page/getPageConfig.js +16 -6
  48. package/dist/routes/request/callRequest.js +2 -1
  49. package/dist/routes/websocket/authorizeWebsocket.js +27 -0
  50. package/dist/routes/websocket/createChannelRegistry.js +269 -0
  51. package/dist/routes/websocket/createWebSocketConnection.js +131 -0
  52. package/dist/routes/websocket/getWebsocketConfig.js +30 -0
  53. package/dist/routes/websocket/getWebsocketResolver.js +33 -0
  54. package/dist/routes/websocket/prepareChannel.js +72 -0
  55. package/dist/test/testContext.js +10 -3
  56. package/package.json +12 -10
@@ -13,7 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { ConfigError } from '@lowdefy/errors';
16
- function createAuthorize({ session }) {
16
+ function createAuthorize({ session, system = false }) {
17
17
  // Next-auth getSession provides a session object if the user is authenticated
18
18
  // else session will be null
19
19
  const authenticated = !!session;
@@ -24,6 +24,9 @@ function createAuthorize({ session }) {
24
24
  });
25
25
  }
26
26
  function authorize(config) {
27
+ // A system context (scheduled, webhook, detached runs) was authorized at the
28
+ // transport layer, so nested endpoint calls are never gated on a user session.
29
+ if (system === true) return true;
27
30
  const { auth } = config;
28
31
  if (auth.public === true) return true;
29
32
  if (auth.public === false) {
@@ -0,0 +1,56 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ // Tries the configured API auth strategies in config order; the first
17
+ // verifier match wins. A strategy caller is a config-derived, org-less
18
+ // principal: roles are the strategy's static grant unioned with any
19
+ // claim-derived roles, and attributes are the static bag shallow-merged
20
+ // with claim-mapped values (claim wins), so _user.attributes reads the same
21
+ // for session and strategy callers.
22
+ async function resolveStrategyCaller({ headers, logger, strategies }) {
23
+ for (const strategy of strategies ?? []){
24
+ const match = await strategy.verify({
25
+ headers,
26
+ logger
27
+ });
28
+ if (type.isNone(match)) {
29
+ continue;
30
+ }
31
+ const roles = [
32
+ ...new Set([
33
+ ...strategy.roles,
34
+ ...match.roles ?? []
35
+ ])
36
+ ];
37
+ const attributes = {
38
+ ...strategy.attributes,
39
+ ...match.attributes ?? {}
40
+ };
41
+ logger.debug({
42
+ event: 'auth_strategy_authenticated',
43
+ authMethod: strategy.type,
44
+ strategyId: strategy.id
45
+ }, `Request authenticated by auth strategy "${strategy.id}" (${strategy.type}).`);
46
+ return {
47
+ ...match.user,
48
+ authMethod: strategy.type,
49
+ strategyId: strategy.id,
50
+ roles,
51
+ attributes
52
+ };
53
+ }
54
+ return null;
55
+ }
56
+ export default resolveStrategyCaller;
package/dist/index.js CHANGED
@@ -12,14 +12,27 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import callAgent from './routes/agent/callAgent.js';
15
+ */ import buildEndpointResult from './response/buildEndpointResult.js';
16
+ import callAgent from './routes/agent/callAgent.js';
16
17
  import callEndpoint from './routes/endpoints/callEndpoint.js';
18
+ import getEndpointConfig from './routes/endpoints/getEndpointConfig.js';
17
19
  import callRequest from './routes/request/callRequest.js';
18
20
  import createApiContext from './context/createApiContext.js';
21
+ import createChannelRegistry from './routes/websocket/createChannelRegistry.js';
22
+ import createMcpServer from './routes/mcp/createMcpServer.js';
23
+ import createWebSocketConnection from './routes/websocket/createWebSocketConnection.js';
19
24
  import createSessionCallback from './routes/auth/callbacks/createSessionCallback.js';
25
+ import getAuthConfig from './routes/auth/getAuthConfig.js';
26
+ import getAuthStrategies from './routes/auth/strategies/getAuthStrategies.js';
20
27
  import getHomeAndMenus from './routes/rootConfig/getHomeAndMenus.js';
21
- import getNextAuthConfig from './routes/auth/getNextAuthConfig.js';
28
+ import resolveStrategyCaller from './context/resolveStrategyCaller.js';
22
29
  import getPageConfig from './routes/page/getPageConfig.js';
23
30
  import getRootConfig from './routes/rootConfig/getRootConfig.js';
24
31
  import logClientError from './routes/log/logClientError.js';
25
- export { callAgent, callEndpoint, callRequest, createApiContext, createSessionCallback, getHomeAndMenus, getNextAuthConfig, getPageConfig, getRootConfig, logClientError };
32
+ import redactErrorResponse from './response/redactErrorResponse.js';
33
+ import redactResponse from './response/redactResponse.js';
34
+ import forwardScheduledEndpoint from './routes/endpoints/forwardScheduledEndpoint.js';
35
+ import runDetachedEndpoint from './routes/endpoints/runDetachedEndpoint.js';
36
+ import runWebhookEndpoint from './routes/endpoints/runWebhookEndpoint.js';
37
+ import runScheduledEndpoint from './routes/endpoints/runScheduledEndpoint.js';
38
+ export { buildEndpointResult, callAgent, callEndpoint, getEndpointConfig, callRequest, createApiContext, createChannelRegistry, createMcpServer, createSessionCallback, createWebSocketConnection, getAuthConfig, getAuthStrategies, getHomeAndMenus, getPageConfig, getRootConfig, logClientError, redactErrorResponse, redactResponse, resolveStrategyCaller, forwardScheduledEndpoint, runDetachedEndpoint, runWebhookEndpoint, runScheduledEndpoint };
@@ -0,0 +1,33 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import redactErrorResponse from './redactErrorResponse.js';
16
+ import redactResponse from './redactResponse.js';
17
+ // The wire object every endpoint route returns after running its routine. One
18
+ // function rather than a copy of the same return statement per route, so the
19
+ // `response` field cannot end up policed differently from the `error` field beside
20
+ // it - see redactResponse for why the response needs the policy at all.
21
+ function buildEndpointResult(context, { error, response, status }) {
22
+ const success = ![
23
+ 'error',
24
+ 'reject'
25
+ ].includes(status);
26
+ return {
27
+ error: redactErrorResponse(context, error),
28
+ response: redactResponse(context, response),
29
+ status: success ? 'success' : status,
30
+ success
31
+ };
32
+ }
33
+ export default buildEndpointResult;
@@ -0,0 +1,64 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import path from 'path';
16
+ import { type } from '@lowdefy/helpers';
17
+ // extractErrorProps emits a nested error as a plain props object, not as a second
18
+ // '~e' wrapper, so an error node is recognised by its shape. Keying on the shape
19
+ // rather than on a map of where errors can appear is deliberate: re-deriving those
20
+ // positions is the mistake the walk-level omit exists to avoid, and this predicate
21
+ // still visits every position.
22
+ //
23
+ // The check matters because the payload also carries author-written data the policy
24
+ // deliberately preserves - a UserError's non-Error cause, its metaData - and a
25
+ // `source` key inside those belongs to the app, not to us.
26
+ function isErrorNode(value) {
27
+ return type.isString(value.name) && type.isString(value.message);
28
+ }
29
+ // A prefix slice rather than path.relative or a parse of a `path:line` shape:
30
+ // source is `${resolvedPath}:${lineNumber}` only when a line number resolved and
31
+ // the bare path otherwise, and removing a prefix never touches the suffix, so both
32
+ // forms work without knowing which this is. Applied at every error node carrying a
33
+ // source, not only the outermost, because a strip is a no-op when the prefix is
34
+ // absent. Mutates the freshly serialized payload in place - nothing else holds it.
35
+ function stripConfigDirectory(value, prefix) {
36
+ if (type.isArray(value)) {
37
+ value.forEach((item)=>stripConfigDirectory(item, prefix));
38
+ return;
39
+ }
40
+ if (!type.isObject(value)) return;
41
+ if (isErrorNode(value) && type.isString(value.source) && value.source.startsWith(prefix)) {
42
+ value.source = value.source.slice(prefix.length);
43
+ }
44
+ Object.values(value).forEach((child)=>stripConfigDirectory(child, prefix));
45
+ }
46
+ // Guarantees `source` reaches a client config-relative, never as an absolute server
47
+ // path. resolveConfigLocation makes it absolute whenever the context carries a
48
+ // configDirectory - server-dev and server-e2e do, and production happens not to
49
+ // today by omission rather than by invariant, which is the drift this closes.
50
+ //
51
+ // A rewrite rather than an omission, and it needs the context, so it runs as a pass
52
+ // over the serialized payload instead of inside the walk.
53
+ function normalizeErrorSources(context, payload) {
54
+ // errorHandler has an else branch for requests with no lowdefyContext, so a
55
+ // missing context is a supported call - it only means no source to normalise.
56
+ const configDirectory = context?.configDirectory;
57
+ if (type.isNone(configDirectory)) return payload;
58
+ // configDirectory is `LOWDEFY_DIRECTORY_CONFIG || process.cwd()` at every site
59
+ // that sets it, so it may be relative or carry a trailing separator, while
60
+ // resolveConfigLocation built source with path.resolve. Compare normalised.
61
+ stripConfigDirectory(payload, `${path.resolve(configDirectory)}${path.sep}`);
62
+ return payload;
63
+ }
64
+ export default normalizeErrorSources;
@@ -0,0 +1,51 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { UserError } from '@lowdefy/errors';
16
+ import { type } from '@lowdefy/helpers';
17
+ // The client-bound error policy: which fields of an error may cross the wire.
18
+ // Passed to serializer.serialize as `omitErrorProps`, so extractErrorProps applies
19
+ // it at EVERY error node the walk emits - the cause chain, an Error-valued own
20
+ // property, and an Error nested inside a plain object or array. The policy is
21
+ // stated against the emitter rather than against a response shape, so it cannot
22
+ // become depth-limited.
23
+ //
24
+ // `received` is not merely "may be sensitive": on the request path
25
+ // callRequestResolver sets it to the EVALUATED request properties, so a _secret
26
+ // resolved into a request header is in it. `stack` exposes server internals,
27
+ // including absolute node_modules paths. Both are unbounded runtime data nobody
28
+ // chose, in every environment - the full value stays in the server log, which is
29
+ // where a developer on their own machine reads it.
30
+ const ALWAYS_OMITTED = [
31
+ 'received',
32
+ 'stack'
33
+ ];
34
+ const OMITTED_WITH_CAUSE = [
35
+ ...ALWAYS_OMITTED,
36
+ 'cause'
37
+ ];
38
+ function omitErrorProps(error) {
39
+ // An Error cause is the trace the browser renders (name + message per level),
40
+ // so it is always kept - fields are taken from causes, causes are never pruned.
41
+ // A non-Error cause is internal server config: the whole endpoint routine, a
42
+ // control node, ajv errors. UserError is the exception, the one class whose
43
+ // payload the author wrote for the client.
44
+ //
45
+ // type.isError is `instanceof Error`, the same test extractErrorProps uses to
46
+ // decide which branch emits the cause - the two must agree.
47
+ if (type.isError(error.cause)) return ALWAYS_OMITTED;
48
+ if (error instanceof UserError) return ALWAYS_OMITTED;
49
+ return OMITTED_WITH_CAUSE;
50
+ }
51
+ export default omitErrorProps;
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { serializer, type } from '@lowdefy/helpers';
16
+ import normalizeErrorSources from './normalizeErrorSources.js';
17
+ import omitErrorProps from './omitErrorProps.js';
18
+ // Owns the serialization as well as the policy, so the policy cannot be forgotten:
19
+ // no bare serializer.serialize(error) is left in response position to wrap. Every
20
+ // route returning an error to a caller - any status, any transport - goes through
21
+ // here or through buildEndpointResult.
22
+ function redactErrorResponse(context, error) {
23
+ // Endpoint routes serialize the error field on success too, where it is null.
24
+ // Passing that through unchanged keeps them from emitting an empty {'~e'}.
25
+ if (type.isNone(error)) return error;
26
+ return normalizeErrorSources(context, serializer.serialize(error, {
27
+ omitErrorProps
28
+ }));
29
+ }
30
+ export default redactErrorResponse;
@@ -0,0 +1,31 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { serializer } from '@lowdefy/helpers';
16
+ import normalizeErrorSources from './normalizeErrorSources.js';
17
+ import omitErrorProps from './omitErrorProps.js';
18
+ // The response-value call shape, beside redactErrorResponse's error-only one.
19
+ // A response is not an error, but makeReplacer wraps any Error it meets anywhere
20
+ // in a value, so a response holding one is an error-serialization site too - the
21
+ // grep that enumerated those sites could not see them, which is why this exists
22
+ // as a function rather than as a rule to remember.
23
+ //
24
+ // Same policy as the error field, because it reaches the same audience: a browser
25
+ // for a request or endpoint body, a third party for cron and detached.
26
+ function redactResponse(context, response) {
27
+ return normalizeErrorSources(context, serializer.serialize(response, {
28
+ omitErrorProps
29
+ }));
30
+ }
31
+ export default redactResponse;
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigError } from '@lowdefy/errors';
16
+ import { translate } from '@lowdefy/helpers';
17
+ function authorizeAgent({ authorize, i18n, logger }, { agentConfig }) {
18
+ if (!authorize(agentConfig)) {
19
+ logger.debug({
20
+ event: 'debug_agent_authorize',
21
+ authorized: false,
22
+ auth_config: agentConfig.auth
23
+ });
24
+ // Same message as an unknown agentId so responses do not reveal which agents exist.
25
+ throw new ConfigError(translate({
26
+ key: 'agent.runtime.agentNotFound',
27
+ values: {
28
+ agentId: agentConfig.agentId
29
+ },
30
+ i18n
31
+ }));
32
+ }
33
+ logger.debug({
34
+ event: 'debug_agent_authorize',
35
+ authorized: true,
36
+ auth_config: agentConfig.auth
37
+ });
38
+ }
39
+ export default authorizeAgent;
@@ -12,15 +12,8 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import { serializer, type } from '@lowdefy/helpers';
16
- import createEvaluateOperators from '../../context/createEvaluateOperators.js';
17
- import authorizeApiEndpoint from '../endpoints/authorizeApiEndpoint.js';
18
- import getEndpointConfig from '../endpoints/getEndpointConfig.js';
19
- import runRoutine from '../endpoints/runRoutine.js';
20
- import getAgentConfig from './getAgentConfig.js';
21
- import getAgentResolver from './getAgentResolver.js';
22
- import getConnectionConfig from '../connections/getConnectionConfig.js';
23
- import getConnection from '../connections/getConnection.js';
15
+ */ import createEvaluateOperators from '../../context/createEvaluateOperators.js';
16
+ import prepareAgent from './prepareAgent.js';
24
17
  async function callAgent(context, { agentId, pageId, messages, conversationId, urlQuery, sharedState }) {
25
18
  const { logger } = context;
26
19
  context.pageId = pageId;
@@ -30,9 +23,6 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
30
23
  agentId,
31
24
  pageId
32
25
  });
33
- const agentConfig = await getAgentConfig(context, {
34
- agentId
35
- });
36
26
  const agentContext = {
37
27
  conversationId: conversationId ?? undefined,
38
28
  pageId,
@@ -40,175 +30,12 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
40
30
  urlQuery: urlQuery ?? {},
41
31
  userId: context.user?.sub ?? context.user?.id ?? null
42
32
  };
43
- // 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
- state: {},
49
- steps: {}
50
- });
51
- // Load connection config from build artifacts using agent's connectionId
52
- const connectionConfig = await getConnectionConfig(context, {
53
- connectionId: agentConfig.connectionId,
54
- configKey: agentConfig['~k']
55
- });
56
- // Get connection plugin from registry
57
- const connection = getConnection(context, {
58
- connectionConfig
59
- });
60
- // Evaluate operators in connection properties
61
- const connectionProperties = context.evaluateOperators({
62
- input: connectionConfig.properties || {},
63
- location: connectionConfig.connectionId,
64
- payload: {},
65
- state: {},
66
- steps: {}
67
- });
68
- // Create connection instance (e.g., Anthropic provider)
69
- const connectionInstance = connection.create({
70
- connection: connectionProperties
71
- });
72
- // Get agent type from plugin registry
73
- const agentType = getAgentResolver(context, {
74
- agentConfig
75
- });
76
- // Build resolver context with callEndpoint that allows InternalApi endpoints
77
- const resolverContext = {
33
+ const { agentConfig, connectionInstance, agentType, resolverContext } = await prepareAgent(context, {
34
+ agentId,
78
35
  agentContext,
79
- i18n: context.i18n,
80
- evaluateOperators: (input)=>context.evaluateOperators({
81
- input,
82
- location: agentConfig.agentId,
83
- payload: agentContext,
84
- state: {},
85
- steps: {}
86
- }),
87
- callEndpoint: async (endpointId, { payload, abortSignal })=>{
88
- const endpointConfig = await getEndpointConfig(context, {
89
- endpointId
90
- });
91
- authorizeApiEndpoint(context, {
92
- endpointConfig
93
- });
94
- const routineContext = {
95
- steps: {},
96
- payload: payload ?? {},
97
- arrayIndices: [],
98
- items: {},
99
- state: {},
100
- endpointDepth: 0
101
- };
102
- const { error, response, status } = await runRoutine(context, routineContext, {
103
- routine: endpointConfig.routine
104
- });
105
- const success = ![
106
- 'error',
107
- 'reject'
108
- ].includes(status);
109
- return {
110
- error: serializer.serialize(error),
111
- response: serializer.serialize(response),
112
- status: success ? 'success' : status,
113
- success
114
- };
115
- },
116
- getEndpointConfig: async ({ endpointId })=>{
117
- return getEndpointConfig(context, {
118
- endpointId
119
- });
120
- },
121
- getAgentConfig: async ({ agentId })=>{
122
- return getAgentConfig(context, {
123
- agentId
124
- });
125
- },
126
- getConnectionForAgent: async ({ agentConfig: subAgentConfig })=>{
127
- const subConnectionConfig = await getConnectionConfig(context, {
128
- connectionId: subAgentConfig.connectionId,
129
- configKey: subAgentConfig['~k']
130
- });
131
- const subConnection = getConnection(context, {
132
- connectionConfig: subConnectionConfig
133
- });
134
- const subConnectionProperties = context.evaluateOperators({
135
- input: subConnectionConfig.properties || {},
136
- location: subConnectionConfig.connectionId,
137
- payload: {},
138
- state: {},
139
- steps: {}
140
- });
141
- return subConnection.create({
142
- connection: subConnectionProperties
143
- });
144
- },
145
- resolveMcpSources: async ({ agentConfig: subAgentConfig })=>{
146
- const resolvedMcp = [];
147
- for (const mcpSource of subAgentConfig.mcp ?? []){
148
- if (!type.isNone(mcpSource.connectionId)) {
149
- const mcpConnConfig = await getConnectionConfig(context, {
150
- connectionId: mcpSource.connectionId,
151
- configKey: subAgentConfig['~k']
152
- });
153
- const mcpConnection = getConnection(context, {
154
- connectionConfig: mcpConnConfig
155
- });
156
- const mcpConnProps = context.evaluateOperators({
157
- input: mcpConnConfig.properties || {},
158
- location: mcpConnConfig.connectionId,
159
- payload: {},
160
- state: {},
161
- steps: {}
162
- });
163
- const mcpConfig = mcpConnection.create({
164
- connection: mcpConnProps
165
- });
166
- const { connectionId: _, ...overrides } = mcpSource;
167
- resolvedMcp.push({
168
- ...mcpConfig,
169
- ...overrides
170
- });
171
- } else {
172
- resolvedMcp.push(mcpSource);
173
- }
174
- }
175
- return resolvedMcp;
176
- }
177
- };
178
- // Resolve MCP connection references to inline config.
179
- // Agent-level overrides (like confirm) may still contain operators —
180
- // handleAgentChat evaluates those via its existing evaluateOperators call.
181
- const resolvedMcp = [];
182
- for (const mcpSource of agentConfig.mcp ?? []){
183
- if (!type.isNone(mcpSource.connectionId)) {
184
- const mcpConnConfig = await getConnectionConfig(context, {
185
- connectionId: mcpSource.connectionId,
186
- configKey: agentConfig['~k']
187
- });
188
- const mcpConnection = getConnection(context, {
189
- connectionConfig: mcpConnConfig
190
- });
191
- const mcpConnProps = context.evaluateOperators({
192
- input: mcpConnConfig.properties || {},
193
- location: mcpConnConfig.connectionId,
194
- payload: {},
195
- state: {},
196
- steps: {}
197
- });
198
- const mcpConfig = mcpConnection.create({
199
- connection: mcpConnProps
200
- });
201
- // Merge: connection properties as base, agent-level overrides on top
202
- const { connectionId: _, ...overrides } = mcpSource;
203
- resolvedMcp.push({
204
- ...mcpConfig,
205
- ...overrides
206
- });
207
- } else {
208
- resolvedMcp.push(mcpSource);
209
- }
210
- }
211
- agentConfig.mcp = resolvedMcp;
36
+ endpointDepth: 0,
37
+ mode: 'chat'
38
+ });
212
39
  // Call the agent resolver
213
40
  const { response } = await agentType.resolver({
214
41
  connection: connectionInstance,