@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.
- package/dist/context/createAuthorize.js +4 -1
- package/dist/context/resolveStrategyCaller.js +56 -0
- package/dist/index.js +16 -3
- package/dist/response/buildEndpointResult.js +33 -0
- package/dist/response/normalizeErrorSources.js +64 -0
- package/dist/response/omitErrorProps.js +51 -0
- package/dist/response/redactErrorResponse.js +30 -0
- package/dist/response/redactResponse.js +31 -0
- package/dist/routes/agent/authorizeAgent.js +39 -0
- package/dist/routes/agent/callAgent.js +7 -180
- package/dist/routes/agent/prepareAgent.js +173 -0
- package/dist/routes/auth/createLogger.js +2 -5
- package/dist/routes/auth/createPrefixedCookies.js +52 -0
- package/dist/routes/auth/{getNextAuthConfig.js → getAuthConfig.js} +27 -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/addStepResult.js +12 -2
- package/dist/routes/endpoints/authorizeApiEndpoint.js +8 -2
- package/dist/routes/endpoints/callEndpoint.js +32 -13
- package/dist/routes/endpoints/control/controlReject.js +3 -1
- package/dist/routes/endpoints/control/controlSetState.js +13 -2
- package/dist/routes/endpoints/control/controlThrow.js +4 -1
- package/dist/routes/endpoints/findSchedule.js +35 -0
- package/dist/routes/endpoints/forwardScheduledEndpoint.js +106 -0
- package/dist/routes/endpoints/getEndpointConfig.js +9 -3
- package/dist/routes/endpoints/getEnvironmentSchedules.js +29 -0
- package/dist/routes/endpoints/handleAgentCall.js +83 -0
- package/dist/routes/endpoints/handleEndpointCall.js +49 -1
- package/dist/routes/endpoints/handleRenderNotification.js +189 -0
- package/dist/routes/endpoints/handleValidateSchema.js +3 -1
- package/dist/routes/endpoints/isUnauthenticatedHuman.js +31 -0
- package/dist/routes/endpoints/resolveCronEnvironment.js +37 -0
- package/dist/routes/endpoints/runDetachedEndpoint.js +66 -0
- package/dist/routes/endpoints/runRoutine.js +15 -1
- package/dist/routes/endpoints/runScheduledEndpoint.js +104 -0
- package/dist/routes/endpoints/runWebhookEndpoint.js +83 -0
- package/dist/routes/endpoints/scheduleBackground.js +48 -0
- package/dist/routes/mcp/createMcpServer.js +160 -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 +2 -1
- package/dist/routes/websocket/authorizeWebsocket.js +27 -0
- package/dist/routes/websocket/createChannelRegistry.js +269 -0
- package/dist/routes/websocket/createWebSocketConnection.js +131 -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 +10 -3
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
16
|
-
import
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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,
|