@lowdefy/api 5.6.0 → 6.1.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/context/createAuthorize.js +4 -1
- package/dist/context/createEvaluateOperators.js +3 -1
- package/dist/context/getCurrentEnvironment.js +27 -0
- package/dist/context/resolveStrategyCaller.js +56 -0
- package/dist/index.js +13 -2
- package/dist/response/buildEndpointResult.js +3 -1
- package/dist/response/createWireProjection.js +88 -0
- package/dist/response/normalizeErrorSources.js +10 -13
- package/dist/response/redactErrorResponse.js +42 -8
- package/dist/response/redactResponse.js +10 -10
- package/dist/routes/agent/callAgent.js +7 -184
- package/dist/routes/agent/prepareAgent.js +179 -0
- package/dist/routes/auth/createLogger.js +2 -5
- package/dist/routes/auth/createPrefixedCookies.js +52 -0
- package/dist/routes/auth/defaultAuthUrl.js +31 -0
- package/dist/routes/auth/{getNextAuthConfig.js → getAuthConfig.js} +31 -16
- package/dist/routes/auth/resolveCookies.js +37 -0
- package/dist/routes/auth/strategies/createAuthStrategies.js +77 -0
- package/dist/routes/auth/strategies/getAuthStrategies.js +31 -0
- package/dist/routes/endpoints/authorizeApiEndpoint.js +8 -2
- package/dist/routes/endpoints/callEndpoint.js +27 -3
- package/dist/routes/endpoints/control/controlFor.js +4 -7
- package/dist/routes/endpoints/control/controlIf.js +4 -8
- package/dist/routes/endpoints/control/controlLog.js +6 -14
- package/dist/routes/endpoints/control/controlParallelFor.js +4 -7
- package/dist/routes/endpoints/control/controlReject.js +16 -16
- package/dist/routes/endpoints/control/controlReturn.js +4 -9
- package/dist/routes/endpoints/control/controlSetState.js +4 -8
- package/dist/routes/endpoints/control/controlSwitch.js +4 -8
- package/dist/routes/endpoints/control/controlThrow.js +21 -20
- package/dist/routes/endpoints/control/controlTry.js +20 -10
- package/dist/routes/endpoints/evaluateRoutineOperators.js +26 -0
- package/dist/routes/endpoints/findSchedule.js +35 -0
- package/dist/routes/endpoints/forwardScheduledEndpoint.js +111 -0
- package/dist/routes/endpoints/getEndpointConfig.js +9 -3
- package/dist/routes/endpoints/getEnvironmentSchedules.js +27 -0
- package/dist/routes/endpoints/handleAgentCall.js +80 -0
- package/dist/routes/endpoints/handleEndpointCall.js +53 -8
- package/dist/routes/endpoints/handleRenderNotification.js +196 -0
- package/dist/routes/endpoints/handleRequest.js +1 -5
- package/dist/routes/endpoints/handleValidateSchema.js +14 -10
- package/dist/routes/endpoints/invokeEndpoint.js +1 -0
- package/dist/routes/endpoints/isUnauthenticatedHuman.js +31 -0
- package/dist/routes/endpoints/operatorScope.js +27 -0
- package/dist/routes/endpoints/resolveCronEnvironment.js +38 -0
- package/dist/routes/endpoints/runDetachedEndpoint.js +67 -0
- package/dist/routes/endpoints/runRoutine.js +12 -0
- package/dist/routes/endpoints/runScheduledEndpoint.js +105 -0
- package/dist/routes/endpoints/runWebhookEndpoint.js +84 -0
- package/dist/routes/endpoints/scheduleBackground.js +48 -0
- package/dist/routes/endpoints/test/runTest.js +17 -2
- package/dist/routes/mcp/createMcpServer.js +178 -0
- package/dist/routes/notifications/derivePreview.js +30 -0
- package/dist/routes/notifications/getNotificationConfig.js +32 -0
- package/dist/routes/notifications/resolveNotificationLinks.js +72 -0
- package/dist/routes/notifications/resolveThemeLogo.js +35 -0
- package/dist/routes/page/dynamic/resolveDynamicContent.js +172 -0
- package/dist/routes/page/dynamic/unescapeOperators.js +36 -0
- package/dist/routes/page/dynamic/validateFragment.js +104 -0
- package/dist/routes/page/getPageConfig.js +16 -6
- package/dist/routes/request/callRequest.js +9 -3
- package/dist/routes/request/callRequestResolver.js +6 -0
- package/dist/routes/request/evaluateOperators.js +8 -12
- package/dist/routes/websocket/authorizeWebsocket.js +27 -0
- package/dist/routes/websocket/createChannelRegistry.js +283 -0
- package/dist/routes/websocket/createWebSocketConnection.js +143 -0
- package/dist/routes/websocket/getWebsocketConfig.js +30 -0
- package/dist/routes/websocket/getWebsocketResolver.js +33 -0
- package/dist/routes/websocket/prepareChannel.js +72 -0
- package/dist/test/testContext.js +7 -3
- package/package.json +12 -10
- package/dist/response/omitErrorProps.js +0 -51
|
@@ -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) {
|
|
@@ -23,8 +23,10 @@ function createEvaluateOperators(context) {
|
|
|
23
23
|
secrets,
|
|
24
24
|
user
|
|
25
25
|
});
|
|
26
|
-
function evaluateOperators({ input, items, location, payload, state, steps }) {
|
|
26
|
+
function evaluateOperators({ arrayIndices, error, input, items, location, payload, state, steps }) {
|
|
27
27
|
const { output, errors } = operatorsParser.parse({
|
|
28
|
+
arrayIndices,
|
|
29
|
+
error,
|
|
28
30
|
input,
|
|
29
31
|
items,
|
|
30
32
|
location,
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
*/ // The environment this deployment is, resolved by the build from LOWDEFY_ENVIRONMENT into
|
|
16
|
+
// config.environment and config.environments. Null when no current environment is set or when it
|
|
17
|
+
// is not declared, so callers fall back to their own defaults.
|
|
18
|
+
function getCurrentEnvironment({ config }) {
|
|
19
|
+
const name = config?.environment;
|
|
20
|
+
const settings = config?.environments?.[name];
|
|
21
|
+
if (settings === undefined) return null;
|
|
22
|
+
return {
|
|
23
|
+
...settings,
|
|
24
|
+
name
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export default getCurrentEnvironment;
|
|
@@ -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
|
@@ -15,14 +15,25 @@
|
|
|
15
15
|
*/ import buildEndpointResult from './response/buildEndpointResult.js';
|
|
16
16
|
import callAgent from './routes/agent/callAgent.js';
|
|
17
17
|
import callEndpoint from './routes/endpoints/callEndpoint.js';
|
|
18
|
+
import getEndpointConfig from './routes/endpoints/getEndpointConfig.js';
|
|
18
19
|
import callRequest from './routes/request/callRequest.js';
|
|
19
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';
|
|
24
|
+
import createWireProjection from './response/createWireProjection.js';
|
|
20
25
|
import createSessionCallback from './routes/auth/callbacks/createSessionCallback.js';
|
|
26
|
+
import getAuthConfig from './routes/auth/getAuthConfig.js';
|
|
27
|
+
import getAuthStrategies from './routes/auth/strategies/getAuthStrategies.js';
|
|
21
28
|
import getHomeAndMenus from './routes/rootConfig/getHomeAndMenus.js';
|
|
22
|
-
import
|
|
29
|
+
import resolveStrategyCaller from './context/resolveStrategyCaller.js';
|
|
23
30
|
import getPageConfig from './routes/page/getPageConfig.js';
|
|
24
31
|
import getRootConfig from './routes/rootConfig/getRootConfig.js';
|
|
25
32
|
import logClientError from './routes/log/logClientError.js';
|
|
26
33
|
import redactErrorResponse from './response/redactErrorResponse.js';
|
|
27
34
|
import redactResponse from './response/redactResponse.js';
|
|
28
|
-
|
|
35
|
+
import forwardScheduledEndpoint from './routes/endpoints/forwardScheduledEndpoint.js';
|
|
36
|
+
import runDetachedEndpoint from './routes/endpoints/runDetachedEndpoint.js';
|
|
37
|
+
import runWebhookEndpoint from './routes/endpoints/runWebhookEndpoint.js';
|
|
38
|
+
import runScheduledEndpoint from './routes/endpoints/runScheduledEndpoint.js';
|
|
39
|
+
export { buildEndpointResult, callAgent, callEndpoint, getEndpointConfig, callRequest, createApiContext, createChannelRegistry, createMcpServer, createSessionCallback, createWebSocketConnection, createWireProjection, getAuthConfig, getAuthStrategies, getHomeAndMenus, getPageConfig, getRootConfig, logClientError, redactErrorResponse, redactResponse, resolveStrategyCaller, forwardScheduledEndpoint, runDetachedEndpoint, runWebhookEndpoint, runScheduledEndpoint };
|
|
@@ -17,7 +17,9 @@ import redactResponse from './redactResponse.js';
|
|
|
17
17
|
// The wire object every endpoint route returns after running its routine. One
|
|
18
18
|
// function rather than a copy of the same return statement per route, so the
|
|
19
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.
|
|
20
|
+
// it - see redactResponse for why the response needs the policy at all. `error` is
|
|
21
|
+
// redactErrorResponse's error payload, so in dev it carries `devError`; `response`
|
|
22
|
+
// never does.
|
|
21
23
|
function buildEndpointResult(context, { error, response, status }) {
|
|
22
24
|
const success = ![
|
|
23
25
|
'error',
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { lowdefyErrorNames, readErrorCodes } from '@lowdefy/errors';
|
|
16
|
+
import { translate } from '@lowdefy/helpers';
|
|
17
|
+
// The framework writes these messages to reveal nothing, and clients branch on
|
|
18
|
+
// them, so they keep their text where every other non-author error does not.
|
|
19
|
+
const AUTH_REFUSAL_NAMES = new Set([
|
|
20
|
+
'AuthenticationError',
|
|
21
|
+
'AuthorizationError',
|
|
22
|
+
'TwoFactorEnrolmentRequiredError'
|
|
23
|
+
]);
|
|
24
|
+
// The client-bound error policy, as an allowlist. Passed to serializer.serialize
|
|
25
|
+
// as `projectError`, so it runs at every error node the walk emits - the cause
|
|
26
|
+
// chain, an Error-valued own property, an Error nested in a response value - and
|
|
27
|
+
// cannot become depth-limited.
|
|
28
|
+
//
|
|
29
|
+
// Only the author's message crosses. A library's message embeds whatever the
|
|
30
|
+
// library saw - URLs with credentials, SQL, hostnames - so every other error
|
|
31
|
+
// becomes one translated generic message plus the fields client config branches
|
|
32
|
+
// on. The full error stays in the server log, and in dev reaches the developer
|
|
33
|
+
// through `devError`, which config never reads.
|
|
34
|
+
//
|
|
35
|
+
// The context is optional: a websocket broadcast has no single request, so it
|
|
36
|
+
// passes no `rid`, and the server error handler can run before any request
|
|
37
|
+
// context exists.
|
|
38
|
+
function createWireProjection(context) {
|
|
39
|
+
const requestId = context?.rid;
|
|
40
|
+
const genericMessage = translate({
|
|
41
|
+
key: 'server.genericError',
|
|
42
|
+
i18n: context?.i18n
|
|
43
|
+
});
|
|
44
|
+
return function project(err) {
|
|
45
|
+
// A PluginError defaults configKey to null; the walk only drops undefined.
|
|
46
|
+
const configKey = err.configKey ?? undefined;
|
|
47
|
+
// Keyed on the name rather than instanceof: the error may come from another
|
|
48
|
+
// copy of @lowdefy/errors or from a serializer round trip, where the class
|
|
49
|
+
// identity is gone but the name survives. The cause is returned raw so the
|
|
50
|
+
// walk projects it too - an Error cause takes the generic shape, and a
|
|
51
|
+
// non-Error cause is author data cleaned as a value.
|
|
52
|
+
if (err.name === 'UserError') {
|
|
53
|
+
return {
|
|
54
|
+
name: err.name,
|
|
55
|
+
message: err.message,
|
|
56
|
+
cause: err.cause,
|
|
57
|
+
metaData: err.metaData,
|
|
58
|
+
blockId: err.blockId,
|
|
59
|
+
pageId: err.pageId,
|
|
60
|
+
isReject: err.isReject,
|
|
61
|
+
configKey,
|
|
62
|
+
// Not read from the node: a caught error rebuilt for `_error` keeps only
|
|
63
|
+
// its class, and without the flag the client wraps a rethrown UserError
|
|
64
|
+
// in an ActionError and reports it back to the server a second time.
|
|
65
|
+
isLowdefyError: true,
|
|
66
|
+
handled: err.handled,
|
|
67
|
+
requestId
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const { code, statusCode } = readErrorCodes(err);
|
|
71
|
+
return {
|
|
72
|
+
// A foreign class name can itself say which library or driver failed.
|
|
73
|
+
name: lowdefyErrorNames.has(err.name) ? err.name : 'Error',
|
|
74
|
+
message: AUTH_REFUSAL_NAMES.has(err.name) ? err.message : genericMessage,
|
|
75
|
+
code,
|
|
76
|
+
statusCode,
|
|
77
|
+
configKey,
|
|
78
|
+
requestId,
|
|
79
|
+
// The client revives errors without running a constructor, so this flag
|
|
80
|
+
// exists there only if the wire carries it. Without it the action runner
|
|
81
|
+
// wraps the error in a new ActionError without `handled`, POSTs it back to
|
|
82
|
+
// /api/client-error, and the server logs and reports it a second time.
|
|
83
|
+
isLowdefyError: true,
|
|
84
|
+
handled: err.handled
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export default createWireProjection;
|
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
import { type } from '@lowdefy/helpers';
|
|
17
17
|
// extractErrorProps emits a nested error as a plain props object, not as a second
|
|
18
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
|
|
20
|
-
//
|
|
21
|
-
// still visits every position.
|
|
19
|
+
// rather than on a map of where errors can appear means every position is visited
|
|
20
|
+
// without re-deriving where the walk puts errors.
|
|
22
21
|
//
|
|
23
|
-
// The check matters because the payload also carries author-written data
|
|
24
|
-
//
|
|
25
|
-
//
|
|
22
|
+
// The check matters because the payload also carries author-written data - a
|
|
23
|
+
// UserError's non-Error cause, its metaData - and a `source` key inside those
|
|
24
|
+
// belongs to the app, not to us.
|
|
26
25
|
function isErrorNode(value) {
|
|
27
26
|
return type.isString(value.name) && type.isString(value.message);
|
|
28
27
|
}
|
|
@@ -43,17 +42,15 @@ function stripConfigDirectory(value, prefix) {
|
|
|
43
42
|
}
|
|
44
43
|
Object.values(value).forEach((child)=>stripConfigDirectory(child, prefix));
|
|
45
44
|
}
|
|
46
|
-
//
|
|
47
|
-
// path
|
|
48
|
-
//
|
|
49
|
-
//
|
|
45
|
+
// Makes `source` in the dev-only `devError` config-relative, never an absolute
|
|
46
|
+
// server path, so the dev tools show the path the developer knows. The wire error
|
|
47
|
+
// carries no `source` at all. resolveConfigLocation makes it absolute whenever the
|
|
48
|
+
// context carries a configDirectory, which server-dev does.
|
|
50
49
|
//
|
|
51
50
|
// A rewrite rather than an omission, and it needs the context, so it runs as a pass
|
|
52
51
|
// over the serialized payload instead of inside the walk.
|
|
53
52
|
function normalizeErrorSources(context, payload) {
|
|
54
|
-
|
|
55
|
-
// missing context is a supported call - it only means no source to normalise.
|
|
56
|
-
const configDirectory = context?.configDirectory;
|
|
53
|
+
const configDirectory = context.configDirectory;
|
|
57
54
|
if (type.isNone(configDirectory)) return payload;
|
|
58
55
|
// configDirectory is `LOWDEFY_DIRECTORY_CONFIG || process.cwd()` at every site
|
|
59
56
|
// that sets it, so it may be relative or carry a trailing separator, while
|
|
@@ -13,18 +13,52 @@
|
|
|
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 createWireProjection from './createWireProjection.js';
|
|
16
17
|
import normalizeErrorSources from './normalizeErrorSources.js';
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
18
|
+
// The one error payload every route and transport sends for a thrown error, at
|
|
19
|
+
// any status. Owning the serialization as well as the policy leaves no bare
|
|
20
|
+
// serializer.serialize(error) in response position for a caller to forget.
|
|
21
|
+
//
|
|
22
|
+
// `~e` is the wire error - identical in dev and prod, because it is what app
|
|
23
|
+
// config reads. In dev the payload also carries `devError`, the error as the dev
|
|
24
|
+
// terminal logs it, for the dev tools. It sits beside `~e` rather than inside it:
|
|
25
|
+
// the serializer's reviver replaces any object holding `~e` with its error, so a
|
|
26
|
+
// plain deserialize of the payload drops `devError` and config never sees it.
|
|
27
|
+
//
|
|
28
|
+
// `received` is left out of devError at every node: on the request path it is
|
|
29
|
+
// the evaluated request properties, resolved _secret values included, and the
|
|
30
|
+
// dev server can be reached over a LAN or tunnel. The terminal still logs it.
|
|
31
|
+
function projectDevError(err) {
|
|
32
|
+
const props = {
|
|
33
|
+
message: err.message,
|
|
34
|
+
name: err.name,
|
|
35
|
+
stack: err.stack,
|
|
36
|
+
cause: err.cause,
|
|
37
|
+
...err
|
|
38
|
+
};
|
|
39
|
+
delete props.received;
|
|
40
|
+
return props;
|
|
41
|
+
}
|
|
22
42
|
function redactErrorResponse(context, error) {
|
|
23
43
|
// Endpoint routes serialize the error field on success too, where it is null.
|
|
24
44
|
// Passing that through unchanged keeps them from emitting an empty {'~e'}.
|
|
25
45
|
if (type.isNone(error)) return error;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
46
|
+
const payload = serializer.serialize(error, {
|
|
47
|
+
projectError: createWireProjection(context)
|
|
48
|
+
});
|
|
49
|
+
// The server error handler has no request context when it fails before the
|
|
50
|
+
// context middleware runs, so there is no mode to read and no devError.
|
|
51
|
+
if (context?.mode === 'dev') {
|
|
52
|
+
const full = serializer.serialize(error, {
|
|
53
|
+
projectError: projectDevError
|
|
54
|
+
});
|
|
55
|
+
payload.devError = normalizeErrorSources(context, {
|
|
56
|
+
'~e': {
|
|
57
|
+
...full['~e'],
|
|
58
|
+
requestId: context.rid
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return payload;
|
|
29
63
|
}
|
|
30
64
|
export default redactErrorResponse;
|
|
@@ -13,19 +13,19 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { serializer } from '@lowdefy/helpers';
|
|
16
|
-
import
|
|
17
|
-
import omitErrorProps from './omitErrorProps.js';
|
|
16
|
+
import createWireProjection from './createWireProjection.js';
|
|
18
17
|
// The response-value call shape, beside redactErrorResponse's error-only one.
|
|
19
18
|
// 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 -
|
|
21
|
-
// grep
|
|
22
|
-
//
|
|
19
|
+
// in a value, so a response holding one is an error-serialization site too - a
|
|
20
|
+
// grep for serialize(error) cannot see those sites, which is why this exists as
|
|
21
|
+
// a function rather than as a rule to remember.
|
|
23
22
|
//
|
|
24
|
-
// Same
|
|
25
|
-
//
|
|
23
|
+
// Same wire projection as the error field, because it reaches the same audience.
|
|
24
|
+
// No devError, in any mode: an Error in a response value is data the routine
|
|
25
|
+
// returned, not a failure of this request.
|
|
26
26
|
function redactResponse(context, response) {
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
})
|
|
27
|
+
return serializer.serialize(response, {
|
|
28
|
+
projectError: createWireProjection(context)
|
|
29
|
+
});
|
|
30
30
|
}
|
|
31
31
|
export default redactResponse;
|
|
@@ -12,17 +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
|
|
16
|
-
import
|
|
17
|
-
import createEvaluateOperators from '../../context/createEvaluateOperators.js';
|
|
18
|
-
import authorizeAgent from './authorizeAgent.js';
|
|
19
|
-
import authorizeApiEndpoint from '../endpoints/authorizeApiEndpoint.js';
|
|
20
|
-
import getEndpointConfig from '../endpoints/getEndpointConfig.js';
|
|
21
|
-
import runRoutine from '../endpoints/runRoutine.js';
|
|
22
|
-
import getAgentConfig from './getAgentConfig.js';
|
|
23
|
-
import getAgentResolver from './getAgentResolver.js';
|
|
24
|
-
import getConnectionConfig from '../connections/getConnectionConfig.js';
|
|
25
|
-
import getConnection from '../connections/getConnection.js';
|
|
15
|
+
*/ import createEvaluateOperators from '../../context/createEvaluateOperators.js';
|
|
16
|
+
import prepareAgent from './prepareAgent.js';
|
|
26
17
|
async function callAgent(context, { agentId, pageId, messages, conversationId, urlQuery, sharedState }) {
|
|
27
18
|
const { logger } = context;
|
|
28
19
|
context.pageId = pageId;
|
|
@@ -32,12 +23,6 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
|
|
|
32
23
|
agentId,
|
|
33
24
|
pageId
|
|
34
25
|
});
|
|
35
|
-
const agentConfig = await getAgentConfig(context, {
|
|
36
|
-
agentId
|
|
37
|
-
});
|
|
38
|
-
authorizeAgent(context, {
|
|
39
|
-
agentConfig
|
|
40
|
-
});
|
|
41
26
|
const agentContext = {
|
|
42
27
|
conversationId: conversationId ?? undefined,
|
|
43
28
|
pageId,
|
|
@@ -45,174 +30,12 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
|
|
|
45
30
|
urlQuery: urlQuery ?? {},
|
|
46
31
|
userId: context.user?.sub ?? context.user?.id ?? null
|
|
47
32
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
input: agentConfig.properties ?? {},
|
|
51
|
-
location: agentConfig.agentId,
|
|
52
|
-
payload: agentContext,
|
|
53
|
-
state: {},
|
|
54
|
-
steps: {}
|
|
55
|
-
});
|
|
56
|
-
// Load connection config from build artifacts using agent's connectionId
|
|
57
|
-
const connectionConfig = await getConnectionConfig(context, {
|
|
58
|
-
connectionId: agentConfig.connectionId,
|
|
59
|
-
configKey: agentConfig['~k']
|
|
60
|
-
});
|
|
61
|
-
// Get connection plugin from registry
|
|
62
|
-
const connection = getConnection(context, {
|
|
63
|
-
connectionConfig
|
|
64
|
-
});
|
|
65
|
-
// Evaluate operators in connection properties
|
|
66
|
-
const connectionProperties = context.evaluateOperators({
|
|
67
|
-
input: connectionConfig.properties || {},
|
|
68
|
-
location: connectionConfig.connectionId,
|
|
69
|
-
payload: {},
|
|
70
|
-
state: {},
|
|
71
|
-
steps: {}
|
|
72
|
-
});
|
|
73
|
-
// Create connection instance (e.g., Anthropic provider)
|
|
74
|
-
const connectionInstance = connection.create({
|
|
75
|
-
connection: connectionProperties
|
|
76
|
-
});
|
|
77
|
-
// Get agent type from plugin registry
|
|
78
|
-
const agentType = getAgentResolver(context, {
|
|
79
|
-
agentConfig
|
|
80
|
-
});
|
|
81
|
-
// Build resolver context with callEndpoint that allows InternalApi endpoints
|
|
82
|
-
const resolverContext = {
|
|
33
|
+
const { agentConfig, connectionInstance, agentType, resolverContext } = await prepareAgent(context, {
|
|
34
|
+
agentId,
|
|
83
35
|
agentContext,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
location: agentConfig.agentId,
|
|
88
|
-
payload: agentContext,
|
|
89
|
-
state: {},
|
|
90
|
-
steps: {}
|
|
91
|
-
}),
|
|
92
|
-
callEndpoint: async (endpointId, { payload, abortSignal })=>{
|
|
93
|
-
const endpointConfig = await getEndpointConfig(context, {
|
|
94
|
-
endpointId
|
|
95
|
-
});
|
|
96
|
-
authorizeApiEndpoint(context, {
|
|
97
|
-
endpointConfig
|
|
98
|
-
});
|
|
99
|
-
const routineContext = {
|
|
100
|
-
steps: {},
|
|
101
|
-
payload: payload ?? {},
|
|
102
|
-
arrayIndices: [],
|
|
103
|
-
items: {},
|
|
104
|
-
state: {},
|
|
105
|
-
endpointDepth: 0
|
|
106
|
-
};
|
|
107
|
-
const { error, response, status } = await runRoutine(context, routineContext, {
|
|
108
|
-
routine: endpointConfig.routine
|
|
109
|
-
});
|
|
110
|
-
return buildEndpointResult(context, {
|
|
111
|
-
error,
|
|
112
|
-
response,
|
|
113
|
-
status
|
|
114
|
-
});
|
|
115
|
-
},
|
|
116
|
-
getEndpointConfig: async ({ endpointId })=>{
|
|
117
|
-
return getEndpointConfig(context, {
|
|
118
|
-
endpointId
|
|
119
|
-
});
|
|
120
|
-
},
|
|
121
|
-
getAgentConfig: async ({ agentId })=>{
|
|
122
|
-
const subAgentConfig = await getAgentConfig(context, {
|
|
123
|
-
agentId
|
|
124
|
-
});
|
|
125
|
-
authorizeAgent(context, {
|
|
126
|
-
agentConfig: subAgentConfig
|
|
127
|
-
});
|
|
128
|
-
return subAgentConfig;
|
|
129
|
-
},
|
|
130
|
-
getConnectionForAgent: async ({ agentConfig: subAgentConfig })=>{
|
|
131
|
-
const subConnectionConfig = await getConnectionConfig(context, {
|
|
132
|
-
connectionId: subAgentConfig.connectionId,
|
|
133
|
-
configKey: subAgentConfig['~k']
|
|
134
|
-
});
|
|
135
|
-
const subConnection = getConnection(context, {
|
|
136
|
-
connectionConfig: subConnectionConfig
|
|
137
|
-
});
|
|
138
|
-
const subConnectionProperties = context.evaluateOperators({
|
|
139
|
-
input: subConnectionConfig.properties || {},
|
|
140
|
-
location: subConnectionConfig.connectionId,
|
|
141
|
-
payload: {},
|
|
142
|
-
state: {},
|
|
143
|
-
steps: {}
|
|
144
|
-
});
|
|
145
|
-
return subConnection.create({
|
|
146
|
-
connection: subConnectionProperties
|
|
147
|
-
});
|
|
148
|
-
},
|
|
149
|
-
resolveMcpSources: async ({ agentConfig: subAgentConfig })=>{
|
|
150
|
-
const resolvedMcp = [];
|
|
151
|
-
for (const mcpSource of subAgentConfig.mcp ?? []){
|
|
152
|
-
if (!type.isNone(mcpSource.connectionId)) {
|
|
153
|
-
const mcpConnConfig = await getConnectionConfig(context, {
|
|
154
|
-
connectionId: mcpSource.connectionId,
|
|
155
|
-
configKey: subAgentConfig['~k']
|
|
156
|
-
});
|
|
157
|
-
const mcpConnection = getConnection(context, {
|
|
158
|
-
connectionConfig: mcpConnConfig
|
|
159
|
-
});
|
|
160
|
-
const mcpConnProps = context.evaluateOperators({
|
|
161
|
-
input: mcpConnConfig.properties || {},
|
|
162
|
-
location: mcpConnConfig.connectionId,
|
|
163
|
-
payload: {},
|
|
164
|
-
state: {},
|
|
165
|
-
steps: {}
|
|
166
|
-
});
|
|
167
|
-
const mcpConfig = mcpConnection.create({
|
|
168
|
-
connection: mcpConnProps
|
|
169
|
-
});
|
|
170
|
-
const { connectionId: _, ...overrides } = mcpSource;
|
|
171
|
-
resolvedMcp.push({
|
|
172
|
-
...mcpConfig,
|
|
173
|
-
...overrides
|
|
174
|
-
});
|
|
175
|
-
} else {
|
|
176
|
-
resolvedMcp.push(mcpSource);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return resolvedMcp;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
// Resolve MCP connection references to inline config.
|
|
183
|
-
// Agent-level overrides (like confirm) may still contain operators —
|
|
184
|
-
// handleAgentChat evaluates those via its existing evaluateOperators call.
|
|
185
|
-
const resolvedMcp = [];
|
|
186
|
-
for (const mcpSource of agentConfig.mcp ?? []){
|
|
187
|
-
if (!type.isNone(mcpSource.connectionId)) {
|
|
188
|
-
const mcpConnConfig = await getConnectionConfig(context, {
|
|
189
|
-
connectionId: mcpSource.connectionId,
|
|
190
|
-
configKey: agentConfig['~k']
|
|
191
|
-
});
|
|
192
|
-
const mcpConnection = getConnection(context, {
|
|
193
|
-
connectionConfig: mcpConnConfig
|
|
194
|
-
});
|
|
195
|
-
const mcpConnProps = context.evaluateOperators({
|
|
196
|
-
input: mcpConnConfig.properties || {},
|
|
197
|
-
location: mcpConnConfig.connectionId,
|
|
198
|
-
payload: {},
|
|
199
|
-
state: {},
|
|
200
|
-
steps: {}
|
|
201
|
-
});
|
|
202
|
-
const mcpConfig = mcpConnection.create({
|
|
203
|
-
connection: mcpConnProps
|
|
204
|
-
});
|
|
205
|
-
// Merge: connection properties as base, agent-level overrides on top
|
|
206
|
-
const { connectionId: _, ...overrides } = mcpSource;
|
|
207
|
-
resolvedMcp.push({
|
|
208
|
-
...mcpConfig,
|
|
209
|
-
...overrides
|
|
210
|
-
});
|
|
211
|
-
} else {
|
|
212
|
-
resolvedMcp.push(mcpSource);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
agentConfig.mcp = resolvedMcp;
|
|
36
|
+
endpointDepth: 0,
|
|
37
|
+
mode: 'chat'
|
|
38
|
+
});
|
|
216
39
|
// Call the agent resolver
|
|
217
40
|
const { response } = await agentType.resolver({
|
|
218
41
|
connection: connectionInstance,
|