@dyrected/core 2.12.0 → 2.13.1
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/access-P6Z2XAFS.js +10 -0
- package/dist/ai-X3M2SE5A.js +17 -0
- package/dist/ai-tools-UPKJY3TX.js +14 -0
- package/dist/{app-config-C6hYM8WO.d.cts → app-De-3eNn1.d.cts} +476 -2
- package/dist/{app-config-C6hYM8WO.d.ts → app-De-3eNn1.d.ts} +476 -2
- package/dist/chunk-254THFDS.js +202 -0
- package/dist/chunk-27YRIGK5.js +1758 -0
- package/dist/chunk-4RAQJVJM.js +18 -0
- package/dist/chunk-HRRJ3RYT.js +70 -0
- package/dist/{chunk-VOE2LOLF.js → chunk-LYENW2EN.js} +280 -1760
- package/dist/chunk-W7GKJM2X.js +1136 -0
- package/dist/chunk-ZYYDTHF3.js +635 -0
- package/dist/index.cjs +518 -525
- package/dist/index.d.cts +91 -4
- package/dist/index.d.ts +91 -4
- package/dist/index.js +45 -2
- package/dist/rag.service-YDGEYTXS.js +11 -0
- package/dist/server.cjs +5004 -1391
- package/dist/server.d.cts +358 -328
- package/dist/server.d.ts +358 -328
- package/dist/server.js +2059 -267
- package/package.json +4 -1
- package/dist/chunk-FRSJJO27.js +0 -519
- package/dist/observability-HQF4EUSV.js +0 -35
package/dist/index.cjs
CHANGED
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
8
|
var __commonJS = (cb, mod) => function __require() {
|
|
12
9
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
10
|
};
|
|
@@ -33,517 +30,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
30
|
));
|
|
34
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
32
|
|
|
36
|
-
// src/observability.ts
|
|
37
|
-
var observability_exports = {};
|
|
38
|
-
__export(observability_exports, {
|
|
39
|
-
attachRequestMetrics: () => attachRequestMetrics,
|
|
40
|
-
bindObservabilityRuntime: () => bindObservabilityRuntime,
|
|
41
|
-
buildRequestLogger: () => buildRequestLogger,
|
|
42
|
-
captureRequestBody: () => captureRequestBody,
|
|
43
|
-
createObservabilityRuntime: () => createObservabilityRuntime,
|
|
44
|
-
createRequestSpan: () => createRequestSpan,
|
|
45
|
-
endRequestSpan: () => endRequestSpan,
|
|
46
|
-
getConfigLogger: () => getConfigLogger,
|
|
47
|
-
getObservabilityRuntime: () => getObservabilityRuntime,
|
|
48
|
-
getRequestLogger: () => getRequestLogger,
|
|
49
|
-
redactBody: () => redactBody,
|
|
50
|
-
redactHeaders: () => redactHeaders,
|
|
51
|
-
renderPrometheusMetrics: () => renderPrometheusMetrics,
|
|
52
|
-
resolveObservabilityConfig: () => resolveObservabilityConfig,
|
|
53
|
-
shouldSampleRequest: () => shouldSampleRequest
|
|
54
|
-
});
|
|
55
|
-
function resolveObservabilityConfig(config) {
|
|
56
|
-
const requestLogging = config?.requestLogging;
|
|
57
|
-
const sampling = config?.sampling;
|
|
58
|
-
const tracing = config?.tracing;
|
|
59
|
-
const metrics = config?.metrics;
|
|
60
|
-
return {
|
|
61
|
-
requestLogging: {
|
|
62
|
-
enabled: requestLogging?.enabled ?? true,
|
|
63
|
-
logBodies: requestLogging?.logBodies ?? false,
|
|
64
|
-
maxBodyBytes: requestLogging?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,
|
|
65
|
-
redactPaths: uniqueValues([
|
|
66
|
-
...DEFAULT_REDACT_PATHS,
|
|
67
|
-
...requestLogging?.redactPaths ?? []
|
|
68
|
-
]),
|
|
69
|
-
includeHeaders: normalizeHeaderNames(requestLogging?.includeHeaders ?? []),
|
|
70
|
-
redactHeaders: uniqueValues(
|
|
71
|
-
normalizeHeaderNames([
|
|
72
|
-
...DEFAULT_REDACT_HEADERS,
|
|
73
|
-
...requestLogging?.redactHeaders ?? []
|
|
74
|
-
])
|
|
75
|
-
)
|
|
76
|
-
},
|
|
77
|
-
sampling: {
|
|
78
|
-
successRate: clampRate(sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE),
|
|
79
|
-
traceSuccessRate: clampRate(
|
|
80
|
-
sampling?.traceSuccessRate ?? sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE
|
|
81
|
-
),
|
|
82
|
-
bodySuccessRate: clampRate(
|
|
83
|
-
sampling?.bodySuccessRate ?? Math.min(DEFAULT_BODY_SAMPLE_RATE, sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE)
|
|
84
|
-
),
|
|
85
|
-
alwaysKeep4xx: sampling?.alwaysKeep4xx ?? true,
|
|
86
|
-
alwaysKeep5xx: sampling?.alwaysKeep5xx ?? true
|
|
87
|
-
},
|
|
88
|
-
tracing: {
|
|
89
|
-
enabled: tracing?.enabled ?? false,
|
|
90
|
-
serviceName: tracing?.serviceName ?? "dyrected",
|
|
91
|
-
exporter: tracing?.exporter ?? "none",
|
|
92
|
-
headers: tracing?.headers ?? {},
|
|
93
|
-
endpoint: tracing?.endpoint
|
|
94
|
-
},
|
|
95
|
-
metrics: {
|
|
96
|
-
enabled: metrics?.enabled ?? false,
|
|
97
|
-
exporter: metrics?.exporter ?? "none",
|
|
98
|
-
endpoint: metrics?.endpoint,
|
|
99
|
-
path: metrics?.path ?? "/metrics"
|
|
100
|
-
},
|
|
101
|
-
transports: {
|
|
102
|
-
targets: config?.transports?.targets && config.transports.targets.length > 0 ? config.transports.targets : [{ type: "stdout" }]
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function createObservabilityRuntime(config) {
|
|
107
|
-
const resolved = resolveObservabilityConfig(config.observability);
|
|
108
|
-
const logger = createRootLogger(config.logger, resolved);
|
|
109
|
-
const shutdownTasks = [];
|
|
110
|
-
const meterRuntime = resolved.metrics.enabled || resolved.metrics.exporter === "prometheus" ? createMeterProvider(resolved) : void 0;
|
|
111
|
-
const tracerProvider = resolved.tracing.enabled ? createTracerProvider(resolved) : void 0;
|
|
112
|
-
const tracer = tracerProvider?.getTracer("dyrected");
|
|
113
|
-
const meter = meterRuntime?.provider.getMeter("dyrected");
|
|
114
|
-
const metrics = meter ? {
|
|
115
|
-
requestCount: meter.createCounter("dyrected_http_requests_total", {
|
|
116
|
-
description: "Total HTTP requests served by Dyrected"
|
|
117
|
-
}),
|
|
118
|
-
authFailureCount: meter.createCounter("dyrected_auth_failures_total", {
|
|
119
|
-
description: "Authentication failures"
|
|
120
|
-
}),
|
|
121
|
-
uncaughtErrorCount: meter.createCounter("dyrected_uncaught_errors_total", {
|
|
122
|
-
description: "Uncaught application errors"
|
|
123
|
-
}),
|
|
124
|
-
auditWriteFailureCount: meter.createCounter("dyrected_audit_write_failures_total", {
|
|
125
|
-
description: "Audit log persistence failures"
|
|
126
|
-
}),
|
|
127
|
-
emailSendFailureCount: meter.createCounter("dyrected_email_send_failures_total", {
|
|
128
|
-
description: "Transactional email delivery failures"
|
|
129
|
-
}),
|
|
130
|
-
workflowHookFailureCount: meter.createCounter("dyrected_workflow_hook_failures_total", {
|
|
131
|
-
description: "Workflow hook failures isolated after successful writes"
|
|
132
|
-
}),
|
|
133
|
-
requestDuration: meter.createHistogram("dyrected_http_request_duration_ms", {
|
|
134
|
-
description: "HTTP request duration in milliseconds",
|
|
135
|
-
unit: "ms"
|
|
136
|
-
})
|
|
137
|
-
} : void 0;
|
|
138
|
-
if (meterRuntime) {
|
|
139
|
-
shutdownTasks.push(() => meterRuntime.provider.shutdown());
|
|
140
|
-
}
|
|
141
|
-
if (tracerProvider) {
|
|
142
|
-
shutdownTasks.push(() => tracerProvider.shutdown());
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
logger,
|
|
146
|
-
config: resolved,
|
|
147
|
-
tracer,
|
|
148
|
-
metrics,
|
|
149
|
-
prometheusExporter: meterRuntime?.prometheusExporter,
|
|
150
|
-
shutdown: async () => {
|
|
151
|
-
for (const task of shutdownTasks) {
|
|
152
|
-
await task();
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
recordAuthFailure: (attributes) => metrics?.authFailureCount.add(1, attributes),
|
|
156
|
-
recordUncaughtError: (attributes) => metrics?.uncaughtErrorCount.add(1, attributes),
|
|
157
|
-
recordAuditWriteFailure: (attributes) => metrics?.auditWriteFailureCount.add(1, attributes),
|
|
158
|
-
recordEmailSendFailure: (attributes) => metrics?.emailSendFailureCount.add(1, attributes),
|
|
159
|
-
recordWorkflowHookFailure: (attributes) => metrics?.workflowHookFailureCount.add(1, attributes)
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function bindObservabilityRuntime(config, runtime) {
|
|
163
|
-
runtimeByConfig.set(config, runtime);
|
|
164
|
-
}
|
|
165
|
-
function getObservabilityRuntime(config) {
|
|
166
|
-
if (!config || typeof config !== "object") return void 0;
|
|
167
|
-
return runtimeByConfig.get(config);
|
|
168
|
-
}
|
|
169
|
-
function createRootLogger(loggerConfig, observability) {
|
|
170
|
-
if (loggerConfig && !("options" in loggerConfig)) {
|
|
171
|
-
return loggerConfig;
|
|
172
|
-
}
|
|
173
|
-
const options = loggerConfig?.options ?? {};
|
|
174
|
-
const enabled = options.enabled ?? process.env.DISABLE_LOGGING !== "true";
|
|
175
|
-
const name = options.name ?? "dyrected";
|
|
176
|
-
if (loggerConfig?.destination) {
|
|
177
|
-
return (0, import_pino.default)(
|
|
178
|
-
{
|
|
179
|
-
...options,
|
|
180
|
-
enabled,
|
|
181
|
-
name,
|
|
182
|
-
timestamp: options.timestamp ?? import_pino.stdTimeFunctions.isoTime
|
|
183
|
-
},
|
|
184
|
-
loggerConfig.destination
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
const streams = buildTransportStreams(observability);
|
|
188
|
-
const destination = streams.length === 1 ? streams[0] : (0, import_pino.multistream)(streams.map((stream) => ({ stream })));
|
|
189
|
-
return (0, import_pino.default)(
|
|
190
|
-
{
|
|
191
|
-
...options,
|
|
192
|
-
enabled,
|
|
193
|
-
name,
|
|
194
|
-
timestamp: options.timestamp ?? import_pino.stdTimeFunctions.isoTime
|
|
195
|
-
},
|
|
196
|
-
destination
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
function buildTransportStreams(observability) {
|
|
200
|
-
const targets = observability.transports.targets;
|
|
201
|
-
if (targets.length === 0) {
|
|
202
|
-
return [defaultLoggerDestination()];
|
|
203
|
-
}
|
|
204
|
-
return targets.map((target) => {
|
|
205
|
-
switch (target.type) {
|
|
206
|
-
case "stdout":
|
|
207
|
-
return defaultLoggerDestination();
|
|
208
|
-
case "stderr":
|
|
209
|
-
return (0, import_pino.destination)(2);
|
|
210
|
-
case "file":
|
|
211
|
-
return (0, import_pino.destination)(target.path);
|
|
212
|
-
case "otlp":
|
|
213
|
-
return new OtlpLogWritable(target.endpoint, target.headers);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
function defaultLoggerDestination() {
|
|
218
|
-
if (process.env.NODE_ENV !== "production" && typeof window === "undefined") {
|
|
219
|
-
try {
|
|
220
|
-
const req = globalThis.require;
|
|
221
|
-
if (req) {
|
|
222
|
-
const pinoPretty = req("pino-pretty");
|
|
223
|
-
return pinoPretty({
|
|
224
|
-
colorize: true,
|
|
225
|
-
ignore: "pid,hostname",
|
|
226
|
-
translateTime: "SYS:HH:MM:ss",
|
|
227
|
-
destination: 1,
|
|
228
|
-
sync: true
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
} catch {
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return (0, import_pino.destination)(1);
|
|
235
|
-
}
|
|
236
|
-
function createTracerProvider(observability) {
|
|
237
|
-
const exporter = createTraceExporter(observability);
|
|
238
|
-
const provider = new import_sdk_trace_base.BasicTracerProvider({
|
|
239
|
-
resource: (0, import_resources.resourceFromAttributes)({
|
|
240
|
-
"service.name": observability.tracing.serviceName
|
|
241
|
-
}),
|
|
242
|
-
spanProcessors: exporter ? [new import_sdk_trace_base.BatchSpanProcessor(exporter)] : []
|
|
243
|
-
});
|
|
244
|
-
import_api.trace.setGlobalTracerProvider(provider);
|
|
245
|
-
return provider;
|
|
246
|
-
}
|
|
247
|
-
function createTraceExporter(observability) {
|
|
248
|
-
switch (observability.tracing.exporter) {
|
|
249
|
-
case "console":
|
|
250
|
-
return new import_sdk_trace_base.ConsoleSpanExporter();
|
|
251
|
-
case "otlp":
|
|
252
|
-
return new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
253
|
-
url: observability.tracing.endpoint,
|
|
254
|
-
headers: observability.tracing.headers
|
|
255
|
-
});
|
|
256
|
-
default:
|
|
257
|
-
return void 0;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
function createMeterProvider(observability) {
|
|
261
|
-
const readers = [];
|
|
262
|
-
let prometheusExporter;
|
|
263
|
-
switch (observability.metrics.exporter) {
|
|
264
|
-
case "otlp":
|
|
265
|
-
readers.push(
|
|
266
|
-
new import_sdk_metrics.PeriodicExportingMetricReader({
|
|
267
|
-
exporter: new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
268
|
-
url: observability.metrics.endpoint
|
|
269
|
-
})
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
break;
|
|
273
|
-
case "prometheus":
|
|
274
|
-
prometheusExporter = new import_exporter_prometheus.PrometheusExporter({
|
|
275
|
-
endpoint: observability.metrics.path,
|
|
276
|
-
preventServerStart: true
|
|
277
|
-
});
|
|
278
|
-
readers.push(prometheusExporter);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
const provider = new import_sdk_metrics.MeterProvider({
|
|
282
|
-
resource: (0, import_resources.resourceFromAttributes)({
|
|
283
|
-
"service.name": observability.tracing.serviceName
|
|
284
|
-
}),
|
|
285
|
-
readers
|
|
286
|
-
});
|
|
287
|
-
return { provider, prometheusExporter };
|
|
288
|
-
}
|
|
289
|
-
function getConfigLogger(config, component) {
|
|
290
|
-
const base = config?.logger && !("options" in config.logger) ? config.logger : fallbackLogger;
|
|
291
|
-
return base.child({ component });
|
|
292
|
-
}
|
|
293
|
-
function getRequestLogger(c, component) {
|
|
294
|
-
const base = c.get("logger") ?? fallbackLogger;
|
|
295
|
-
return component ? base.child({ component }) : base;
|
|
296
|
-
}
|
|
297
|
-
function buildRequestLogger(runtime, vars, requestId) {
|
|
298
|
-
const trace2 = vars.requestTrace;
|
|
299
|
-
return runtime.logger.child({
|
|
300
|
-
requestId,
|
|
301
|
-
siteId: vars.siteId,
|
|
302
|
-
workspaceId: vars.workspaceId,
|
|
303
|
-
traceId: trace2?.traceId,
|
|
304
|
-
spanId: trace2?.spanId
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
function shouldSampleRequest(requestId, statusCode, sampling, kind) {
|
|
308
|
-
if (statusCode >= 500 && sampling.alwaysKeep5xx) return true;
|
|
309
|
-
if (statusCode >= 400 && sampling.alwaysKeep4xx) return true;
|
|
310
|
-
const rate = kind === "trace" ? sampling.traceSuccessRate : kind === "body" ? sampling.bodySuccessRate : sampling.successRate;
|
|
311
|
-
return deterministicSample(requestId ?? "no-request-id", rate);
|
|
312
|
-
}
|
|
313
|
-
function redactHeaders(headers, observability) {
|
|
314
|
-
const includeHeaders = new Set(observability.requestLogging.includeHeaders);
|
|
315
|
-
const redactHeaders2 = new Set(observability.requestLogging.redactHeaders);
|
|
316
|
-
const output = {};
|
|
317
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
318
|
-
const normalizedKey = key.toLowerCase();
|
|
319
|
-
if (includeHeaders.size > 0 && !includeHeaders.has(normalizedKey)) continue;
|
|
320
|
-
output[normalizedKey] = redactHeaders2.has(normalizedKey) ? REDACTED_VALUE : value;
|
|
321
|
-
}
|
|
322
|
-
return output;
|
|
323
|
-
}
|
|
324
|
-
async function captureRequestBody(request, observability) {
|
|
325
|
-
const contentType = request.headers.get("content-type") ?? void 0;
|
|
326
|
-
const contentLength = toNumber(request.headers.get("content-length"));
|
|
327
|
-
const jsonBody = contentType?.includes("application/json");
|
|
328
|
-
if (!jsonBody) {
|
|
329
|
-
return {
|
|
330
|
-
attempted: false,
|
|
331
|
-
contentType,
|
|
332
|
-
contentLength
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
const text = await request.clone().text();
|
|
337
|
-
if (!text) {
|
|
338
|
-
return { attempted: true, contentType, contentLength };
|
|
339
|
-
}
|
|
340
|
-
const truncated = text.length > observability.requestLogging.maxBodyBytes;
|
|
341
|
-
const raw = truncated ? text.slice(0, observability.requestLogging.maxBodyBytes) : text;
|
|
342
|
-
try {
|
|
343
|
-
const parsed = JSON.parse(raw);
|
|
344
|
-
return {
|
|
345
|
-
attempted: true,
|
|
346
|
-
contentType,
|
|
347
|
-
contentLength: contentLength ?? text.length,
|
|
348
|
-
truncated,
|
|
349
|
-
body: redactBody(parsed, observability.requestLogging.redactPaths)
|
|
350
|
-
};
|
|
351
|
-
} catch {
|
|
352
|
-
return {
|
|
353
|
-
attempted: true,
|
|
354
|
-
contentType,
|
|
355
|
-
contentLength: contentLength ?? text.length,
|
|
356
|
-
truncated,
|
|
357
|
-
parseFailed: true
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
return {
|
|
362
|
-
attempted: true,
|
|
363
|
-
contentType,
|
|
364
|
-
contentLength,
|
|
365
|
-
parseFailed: true
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function redactBody(value, redactPaths) {
|
|
370
|
-
const cloned = deepClone(value);
|
|
371
|
-
for (const path of redactPaths) {
|
|
372
|
-
applyRedaction(cloned, path.split("."));
|
|
373
|
-
}
|
|
374
|
-
return cloned;
|
|
375
|
-
}
|
|
376
|
-
function applyRedaction(target, segments) {
|
|
377
|
-
if (!target || segments.length === 0) return;
|
|
378
|
-
const [segment, ...rest] = segments;
|
|
379
|
-
if (Array.isArray(target)) {
|
|
380
|
-
for (const item of target) {
|
|
381
|
-
if (segment === "*") {
|
|
382
|
-
applyRedaction(item, rest);
|
|
383
|
-
} else {
|
|
384
|
-
applyRedaction(item, segments);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (typeof target !== "object") return;
|
|
390
|
-
const record = target;
|
|
391
|
-
if (segment === "*") {
|
|
392
|
-
for (const value of Object.values(record)) {
|
|
393
|
-
applyRedaction(value, rest);
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (!(segment in record)) return;
|
|
398
|
-
if (rest.length === 0) {
|
|
399
|
-
record[segment] = REDACTED_VALUE;
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
applyRedaction(record[segment], rest);
|
|
403
|
-
}
|
|
404
|
-
function deepClone(value) {
|
|
405
|
-
if (typeof structuredClone === "function") {
|
|
406
|
-
return structuredClone(value);
|
|
407
|
-
}
|
|
408
|
-
return JSON.parse(JSON.stringify(value));
|
|
409
|
-
}
|
|
410
|
-
function createRequestSpan(runtime, name, attributes) {
|
|
411
|
-
if (!runtime.tracer) return void 0;
|
|
412
|
-
const span = runtime.tracer.startSpan(name, { attributes }, import_api.context.active());
|
|
413
|
-
const spanContext = span.spanContext();
|
|
414
|
-
return {
|
|
415
|
-
span,
|
|
416
|
-
traceId: spanContext.traceId,
|
|
417
|
-
spanId: spanContext.spanId,
|
|
418
|
-
sampled: spanContext.traceFlags === import_api.TraceFlags.SAMPLED
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
function endRequestSpan(requestTrace, statusCode, error) {
|
|
422
|
-
if (!requestTrace) return;
|
|
423
|
-
requestTrace.span.setAttribute("http.status_code", statusCode);
|
|
424
|
-
if (error) {
|
|
425
|
-
requestTrace.span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
426
|
-
}
|
|
427
|
-
requestTrace.span.end();
|
|
428
|
-
}
|
|
429
|
-
function attachRequestMetrics(runtime, args) {
|
|
430
|
-
runtime.metrics?.requestCount.add(1, {
|
|
431
|
-
method: args.method,
|
|
432
|
-
route: args.route,
|
|
433
|
-
status_class: `${Math.floor(args.statusCode / 100)}xx`
|
|
434
|
-
});
|
|
435
|
-
runtime.metrics?.requestDuration.record(args.durationMs, {
|
|
436
|
-
method: args.method,
|
|
437
|
-
route: args.route,
|
|
438
|
-
status_class: `${Math.floor(args.statusCode / 100)}xx`
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
async function renderPrometheusMetrics(exporter) {
|
|
442
|
-
let statusCode = 200;
|
|
443
|
-
const headers = {};
|
|
444
|
-
let body = "";
|
|
445
|
-
await new Promise((resolve) => {
|
|
446
|
-
const response = {
|
|
447
|
-
statusCode,
|
|
448
|
-
setHeader(name, value) {
|
|
449
|
-
headers[name.toLowerCase()] = value;
|
|
450
|
-
},
|
|
451
|
-
end(chunk) {
|
|
452
|
-
if (chunk) body += chunk;
|
|
453
|
-
statusCode = this.statusCode;
|
|
454
|
-
resolve();
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
exporter.getMetricsRequestHandler({}, response);
|
|
458
|
-
});
|
|
459
|
-
return { body, headers, statusCode };
|
|
460
|
-
}
|
|
461
|
-
function normalizeHeaderNames(headers) {
|
|
462
|
-
return headers.map((value) => value.toLowerCase());
|
|
463
|
-
}
|
|
464
|
-
function uniqueValues(values) {
|
|
465
|
-
return [...new Set(values)];
|
|
466
|
-
}
|
|
467
|
-
function clampRate(value) {
|
|
468
|
-
if (Number.isNaN(value)) return 0;
|
|
469
|
-
return Math.max(0, Math.min(1, value));
|
|
470
|
-
}
|
|
471
|
-
function deterministicSample(seed, rate) {
|
|
472
|
-
if (rate >= 1) return true;
|
|
473
|
-
if (rate <= 0) return false;
|
|
474
|
-
let hash = 0;
|
|
475
|
-
for (let index = 0; index < seed.length; index += 1) {
|
|
476
|
-
hash = hash * 31 + seed.charCodeAt(index) >>> 0;
|
|
477
|
-
}
|
|
478
|
-
return hash / 4294967295 <= rate;
|
|
479
|
-
}
|
|
480
|
-
function toNumber(value) {
|
|
481
|
-
if (!value) return void 0;
|
|
482
|
-
const parsed = Number(value);
|
|
483
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
484
|
-
}
|
|
485
|
-
var import_api, import_exporter_metrics_otlp_http, import_exporter_prometheus, import_exporter_trace_otlp_http, import_resources, import_sdk_metrics, import_sdk_trace_base, import_pino, import_node_stream, DEFAULT_REDACT_HEADERS, DEFAULT_REDACT_PATHS, DEFAULT_MAX_BODY_BYTES, DEFAULT_SUCCESS_SAMPLE_RATE, DEFAULT_BODY_SAMPLE_RATE, REDACTED_VALUE, runtimeByConfig, fallbackLogger, OtlpLogWritable;
|
|
486
|
-
var init_observability = __esm({
|
|
487
|
-
"src/observability.ts"() {
|
|
488
|
-
"use strict";
|
|
489
|
-
import_api = require("@opentelemetry/api");
|
|
490
|
-
import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
491
|
-
import_exporter_prometheus = require("@opentelemetry/exporter-prometheus");
|
|
492
|
-
import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
493
|
-
import_resources = require("@opentelemetry/resources");
|
|
494
|
-
import_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
495
|
-
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
496
|
-
import_pino = __toESM(require("pino"), 1);
|
|
497
|
-
import_node_stream = require("stream");
|
|
498
|
-
DEFAULT_REDACT_HEADERS = ["authorization", "cookie", "set-cookie", "x-api-key"];
|
|
499
|
-
DEFAULT_REDACT_PATHS = [
|
|
500
|
-
"password",
|
|
501
|
-
"currentPassword",
|
|
502
|
-
"newPassword",
|
|
503
|
-
"confirmPassword",
|
|
504
|
-
"token",
|
|
505
|
-
"refreshToken",
|
|
506
|
-
"accessToken",
|
|
507
|
-
"secret",
|
|
508
|
-
"apiKey",
|
|
509
|
-
"inviteToken",
|
|
510
|
-
"resetToken"
|
|
511
|
-
];
|
|
512
|
-
DEFAULT_MAX_BODY_BYTES = 8192;
|
|
513
|
-
DEFAULT_SUCCESS_SAMPLE_RATE = 0.1;
|
|
514
|
-
DEFAULT_BODY_SAMPLE_RATE = 0.02;
|
|
515
|
-
REDACTED_VALUE = "[REDACTED]";
|
|
516
|
-
runtimeByConfig = /* @__PURE__ */ new WeakMap();
|
|
517
|
-
fallbackLogger = (0, import_pino.default)({
|
|
518
|
-
name: "dyrected",
|
|
519
|
-
enabled: process.env.DISABLE_LOGGING !== "true",
|
|
520
|
-
timestamp: import_pino.stdTimeFunctions.isoTime
|
|
521
|
-
});
|
|
522
|
-
OtlpLogWritable = class extends import_node_stream.Writable {
|
|
523
|
-
constructor(endpoint, headers = {}) {
|
|
524
|
-
super();
|
|
525
|
-
this.endpoint = endpoint;
|
|
526
|
-
this.headers = headers;
|
|
527
|
-
}
|
|
528
|
-
endpoint;
|
|
529
|
-
headers;
|
|
530
|
-
_write(chunk, _encoding, callback) {
|
|
531
|
-
const payload = chunk.toString();
|
|
532
|
-
void fetch(this.endpoint, {
|
|
533
|
-
method: "POST",
|
|
534
|
-
headers: {
|
|
535
|
-
"content-type": "application/json",
|
|
536
|
-
...this.headers
|
|
537
|
-
},
|
|
538
|
-
body: JSON.stringify({ logs: payload.trim().split("\n").filter(Boolean) })
|
|
539
|
-
}).then(() => callback()).catch(
|
|
540
|
-
(error) => callback(error instanceof Error ? error : new Error(String(error)))
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
|
|
547
33
|
// ../../node_modules/.pnpm/@babel+runtime@7.29.7/node_modules/@babel/runtime/helpers/interopRequireDefault.js
|
|
548
34
|
var require_interopRequireDefault = __commonJS({
|
|
549
35
|
"../../node_modules/.pnpm/@babel+runtime@7.29.7/node_modules/@babel/runtime/helpers/interopRequireDefault.js"(exports2, module2) {
|
|
@@ -2298,12 +1784,20 @@ var require_Jexl = __commonJS({
|
|
|
2298
1784
|
// src/index.ts
|
|
2299
1785
|
var index_exports = {};
|
|
2300
1786
|
__export(index_exports, {
|
|
1787
|
+
AI_ACTIONS_COLLECTION: () => AI_ACTIONS_COLLECTION,
|
|
1788
|
+
AI_AUDIT_COLLECTION: () => AI_AUDIT_COLLECTION,
|
|
1789
|
+
AI_CHUNKS_COLLECTION: () => AI_CHUNKS_COLLECTION,
|
|
1790
|
+
AI_MESSAGES_COLLECTION: () => AI_MESSAGES_COLLECTION,
|
|
1791
|
+
AI_THREADS_COLLECTION: () => AI_THREADS_COLLECTION,
|
|
2301
1792
|
BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
|
|
2302
1793
|
ConfigValidationError: () => ConfigValidationError,
|
|
1794
|
+
DyrectedAIError: () => DyrectedAIError,
|
|
2303
1795
|
FieldConditionBuilder: () => FieldConditionBuilder,
|
|
2304
1796
|
LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
|
|
2305
1797
|
LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
|
|
2306
1798
|
MatchBuilder: () => MatchBuilder,
|
|
1799
|
+
NON_NEGOTIABLE_CREDENTIAL_FIELDS: () => NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
1800
|
+
PII_REGEX_PATTERNS: () => PII_REGEX_PATTERNS,
|
|
2307
1801
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
2308
1802
|
abs: () => abs,
|
|
2309
1803
|
addDays: () => addDays,
|
|
@@ -2391,18 +1885,26 @@ __export(index_exports, {
|
|
|
2391
1885
|
generateOpenApi: () => generateOpenApi,
|
|
2392
1886
|
get: () => get,
|
|
2393
1887
|
getAdminAuthCollection: () => getAdminAuthCollection,
|
|
1888
|
+
getAdminRoleForCollection: () => getAdminRoleForCollection,
|
|
1889
|
+
getAllowedSitesForUser: () => getAllowedSitesForUser,
|
|
2394
1890
|
getPublicAdminAuthConfig: () => getPublicAdminAuthConfig,
|
|
2395
1891
|
includes: () => includes,
|
|
2396
1892
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
1893
|
+
isAICollection: () => isAICollection,
|
|
2397
1894
|
isConfigValidationError: () => isConfigValidationError,
|
|
2398
1895
|
isDetailItemVisible: () => isDetailItemVisible,
|
|
2399
1896
|
isEmpty: () => isEmpty,
|
|
2400
1897
|
isFuture: () => isFuture,
|
|
2401
1898
|
isPast: () => isPast,
|
|
1899
|
+
isSiteAuthorized: () => isSiteAuthorized,
|
|
1900
|
+
isUserAdmin: () => isUserAdmin,
|
|
2402
1901
|
join: () => join,
|
|
2403
1902
|
last: () => last,
|
|
2404
1903
|
length: () => length,
|
|
2405
1904
|
lower: () => lower,
|
|
1905
|
+
maskEmail: () => maskEmail,
|
|
1906
|
+
maskPhone: () => maskPhone,
|
|
1907
|
+
maskTextPII: () => maskTextPII,
|
|
2406
1908
|
materializeWorkflowDocument: () => materializeWorkflowDocument,
|
|
2407
1909
|
max: () => max,
|
|
2408
1910
|
min: () => min,
|
|
@@ -2420,8 +1922,10 @@ __export(index_exports, {
|
|
|
2420
1922
|
registerJexlHelpers: () => registerJexlHelpers,
|
|
2421
1923
|
replace: () => replace,
|
|
2422
1924
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
1925
|
+
resolveAuthorizedSiteId: () => resolveAuthorizedSiteId,
|
|
2423
1926
|
round: () => round,
|
|
2424
1927
|
runCollectionHooks: () => runCollectionHooks,
|
|
1928
|
+
sanitizeDocForAI: () => sanitizeDocForAI,
|
|
2425
1929
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
2426
1930
|
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
2427
1931
|
slugify: () => slugify,
|
|
@@ -2440,7 +1944,8 @@ __export(index_exports, {
|
|
|
2440
1944
|
validatePreviewUrlExpression: () => validatePreviewUrlExpression,
|
|
2441
1945
|
when: () => when,
|
|
2442
1946
|
wordCount: () => wordCount,
|
|
2443
|
-
workflowCapabilities: () => workflowCapabilities
|
|
1947
|
+
workflowCapabilities: () => workflowCapabilities,
|
|
1948
|
+
z: () => import_zod.z
|
|
2444
1949
|
});
|
|
2445
1950
|
module.exports = __toCommonJS(index_exports);
|
|
2446
1951
|
|
|
@@ -2460,6 +1965,40 @@ function defineAction(config) {
|
|
|
2460
1965
|
return config;
|
|
2461
1966
|
}
|
|
2462
1967
|
|
|
1968
|
+
// src/types/ai.ts
|
|
1969
|
+
var AI_THREADS_COLLECTION = "_dyrected_ai_threads";
|
|
1970
|
+
var AI_MESSAGES_COLLECTION = "_dyrected_ai_messages";
|
|
1971
|
+
var AI_CHUNKS_COLLECTION = "_dyrected_ai_chunks";
|
|
1972
|
+
var AI_ACTIONS_COLLECTION = "_dyrected_ai_actions";
|
|
1973
|
+
var AI_AUDIT_COLLECTION = "_dyrected_ai_audit";
|
|
1974
|
+
function isAICollection(slug) {
|
|
1975
|
+
return slug === AI_THREADS_COLLECTION || slug === AI_MESSAGES_COLLECTION || slug === AI_CHUNKS_COLLECTION || slug === AI_ACTIONS_COLLECTION || slug === AI_AUDIT_COLLECTION;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// src/types/ai-errors.ts
|
|
1979
|
+
var DyrectedAIError = class _DyrectedAIError extends Error {
|
|
1980
|
+
code;
|
|
1981
|
+
status;
|
|
1982
|
+
details;
|
|
1983
|
+
constructor(code, message, status = 500, details) {
|
|
1984
|
+
super(message);
|
|
1985
|
+
this.name = "DyrectedAIError";
|
|
1986
|
+
this.code = code;
|
|
1987
|
+
this.status = status;
|
|
1988
|
+
this.details = details;
|
|
1989
|
+
Object.setPrototypeOf(this, _DyrectedAIError.prototype);
|
|
1990
|
+
}
|
|
1991
|
+
toJSON() {
|
|
1992
|
+
return {
|
|
1993
|
+
error: true,
|
|
1994
|
+
code: this.code,
|
|
1995
|
+
message: this.message,
|
|
1996
|
+
status: this.status,
|
|
1997
|
+
details: this.details
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2463
2002
|
// src/auth/token.ts
|
|
2464
2003
|
var import_jose = require("jose");
|
|
2465
2004
|
|
|
@@ -2862,18 +2401,31 @@ async function transitionWorkflow(args) {
|
|
|
2862
2401
|
await hook({ ...hookContext, doc: updated, event: events[0] });
|
|
2863
2402
|
} catch (error) {
|
|
2864
2403
|
try {
|
|
2865
|
-
const
|
|
2866
|
-
|
|
2404
|
+
const obs = config?.__observability;
|
|
2405
|
+
obs?.recordWorkflowHookFailure?.({
|
|
2867
2406
|
collection: collection.slug,
|
|
2868
2407
|
transition: transition.name
|
|
2869
2408
|
});
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2409
|
+
const logger = config?.logger;
|
|
2410
|
+
if (logger && typeof logger.child === "function") {
|
|
2411
|
+
logger.child({ component: "workflow" }).error({
|
|
2412
|
+
err: error,
|
|
2413
|
+
msg: "afterTransition hook failed",
|
|
2414
|
+
collection: collection.slug,
|
|
2415
|
+
transition: transition.name,
|
|
2416
|
+
documentId: id
|
|
2417
|
+
});
|
|
2418
|
+
} else if (logger && typeof logger.error === "function") {
|
|
2419
|
+
logger.error({
|
|
2420
|
+
err: error,
|
|
2421
|
+
msg: "afterTransition hook failed",
|
|
2422
|
+
collection: collection.slug,
|
|
2423
|
+
transition: transition.name,
|
|
2424
|
+
documentId: id
|
|
2425
|
+
});
|
|
2426
|
+
} else {
|
|
2427
|
+
console.error("afterTransition hook failed:", error);
|
|
2428
|
+
}
|
|
2877
2429
|
} catch {
|
|
2878
2430
|
console.error("afterTransition hook failed:", error);
|
|
2879
2431
|
}
|
|
@@ -2916,6 +2468,19 @@ function humanizeProviderName(id, type) {
|
|
|
2916
2468
|
if (!cleaned) return type.toUpperCase();
|
|
2917
2469
|
return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
2918
2470
|
}
|
|
2471
|
+
function getAdminRoleForCollection(collection) {
|
|
2472
|
+
if (!collection?.auth) return "admin";
|
|
2473
|
+
if (typeof collection.auth === "object" && collection.auth.adminRole) {
|
|
2474
|
+
return collection.auth.adminRole;
|
|
2475
|
+
}
|
|
2476
|
+
return "admin";
|
|
2477
|
+
}
|
|
2478
|
+
function isUserAdmin(user, collection) {
|
|
2479
|
+
if (!user || !Array.isArray(user.roles)) return false;
|
|
2480
|
+
const configuredRole = getAdminRoleForCollection(collection);
|
|
2481
|
+
if (user.roles.includes(configuredRole)) return true;
|
|
2482
|
+
return user.roles.includes("admin") || user.roles.includes("super_admin");
|
|
2483
|
+
}
|
|
2919
2484
|
|
|
2920
2485
|
// src/utils/block-references.ts
|
|
2921
2486
|
function mergeUniqueBlocks(blocks) {
|
|
@@ -5023,6 +4588,182 @@ function generateOpenApi(config) {
|
|
|
5023
4588
|
}
|
|
5024
4589
|
}
|
|
5025
4590
|
};
|
|
4591
|
+
spec.paths["/api/ai/chat"] = {
|
|
4592
|
+
post: {
|
|
4593
|
+
tags: ["AI"],
|
|
4594
|
+
summary: "Send prompt and stream AI assistant response",
|
|
4595
|
+
requestBody: {
|
|
4596
|
+
required: true,
|
|
4597
|
+
content: {
|
|
4598
|
+
"application/json": {
|
|
4599
|
+
schema: {
|
|
4600
|
+
type: "object",
|
|
4601
|
+
properties: {
|
|
4602
|
+
content: { type: "string", description: "User prompt message" },
|
|
4603
|
+
prompt: { type: "string", description: "Alias for content" },
|
|
4604
|
+
threadId: { type: "string", description: "Existing conversation thread ID" },
|
|
4605
|
+
messages: { type: "array", items: { type: "object" }, description: "Conversation history" }
|
|
4606
|
+
},
|
|
4607
|
+
required: []
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
},
|
|
4612
|
+
responses: {
|
|
4613
|
+
200: {
|
|
4614
|
+
description: "Server-Sent Events (SSE) AI message stream",
|
|
4615
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4620
|
+
spec.paths["/api/ai/threads"] = {
|
|
4621
|
+
get: {
|
|
4622
|
+
tags: ["AI"],
|
|
4623
|
+
summary: "List conversation threads",
|
|
4624
|
+
parameters: [
|
|
4625
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } }
|
|
4626
|
+
],
|
|
4627
|
+
responses: { 200: { description: "Array of conversation threads" } }
|
|
4628
|
+
},
|
|
4629
|
+
post: {
|
|
4630
|
+
tags: ["AI"],
|
|
4631
|
+
summary: "Create a new conversation thread",
|
|
4632
|
+
requestBody: {
|
|
4633
|
+
content: {
|
|
4634
|
+
"application/json": {
|
|
4635
|
+
schema: {
|
|
4636
|
+
type: "object",
|
|
4637
|
+
properties: { title: { type: "string" } }
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
},
|
|
4642
|
+
responses: { 201: { description: "Created conversation thread" } }
|
|
4643
|
+
},
|
|
4644
|
+
delete: {
|
|
4645
|
+
tags: ["AI"],
|
|
4646
|
+
summary: "Clear all conversation threads",
|
|
4647
|
+
responses: { 200: { description: "Count of deleted threads" } }
|
|
4648
|
+
}
|
|
4649
|
+
};
|
|
4650
|
+
spec.paths["/api/ai/threads/{threadId}"] = {
|
|
4651
|
+
get: {
|
|
4652
|
+
tags: ["AI"],
|
|
4653
|
+
summary: "Get conversation thread and historical messages",
|
|
4654
|
+
parameters: [
|
|
4655
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4656
|
+
],
|
|
4657
|
+
responses: { 200: { description: "Thread details and messages" } }
|
|
4658
|
+
},
|
|
4659
|
+
delete: {
|
|
4660
|
+
tags: ["AI"],
|
|
4661
|
+
summary: "Delete a specific conversation thread",
|
|
4662
|
+
parameters: [
|
|
4663
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4664
|
+
],
|
|
4665
|
+
responses: { 200: { description: "Deletion success status" } }
|
|
4666
|
+
}
|
|
4667
|
+
};
|
|
4668
|
+
spec.paths["/api/ai/threads/{threadId}/messages"] = {
|
|
4669
|
+
post: {
|
|
4670
|
+
tags: ["AI"],
|
|
4671
|
+
summary: "Post a message to an existing thread and stream reply",
|
|
4672
|
+
parameters: [
|
|
4673
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4674
|
+
],
|
|
4675
|
+
requestBody: {
|
|
4676
|
+
required: true,
|
|
4677
|
+
content: {
|
|
4678
|
+
"application/json": {
|
|
4679
|
+
schema: {
|
|
4680
|
+
type: "object",
|
|
4681
|
+
properties: { content: { type: "string" } },
|
|
4682
|
+
required: ["content"]
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
},
|
|
4687
|
+
responses: {
|
|
4688
|
+
200: {
|
|
4689
|
+
description: "Server-Sent Events (SSE) AI message stream",
|
|
4690
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
};
|
|
4695
|
+
spec.paths["/api/ai/actions/{actionId}"] = {
|
|
4696
|
+
get: {
|
|
4697
|
+
tags: ["AI"],
|
|
4698
|
+
summary: "Get an AI mutation action proposal",
|
|
4699
|
+
parameters: [
|
|
4700
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4701
|
+
],
|
|
4702
|
+
responses: { 200: { description: "AI Action details and diff payload" } }
|
|
4703
|
+
}
|
|
4704
|
+
};
|
|
4705
|
+
spec.paths["/api/ai/actions/{actionId}/execute"] = {
|
|
4706
|
+
post: {
|
|
4707
|
+
tags: ["AI"],
|
|
4708
|
+
summary: "Approve and execute an AI mutation proposal",
|
|
4709
|
+
parameters: [
|
|
4710
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4711
|
+
],
|
|
4712
|
+
responses: { 200: { description: "Execution result" } }
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
spec.paths["/api/ai/actions/{actionId}/reject"] = {
|
|
4716
|
+
post: {
|
|
4717
|
+
tags: ["AI"],
|
|
4718
|
+
summary: "Reject an AI mutation proposal",
|
|
4719
|
+
parameters: [
|
|
4720
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4721
|
+
],
|
|
4722
|
+
responses: { 200: { description: "Rejection result" } }
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
spec.paths["/api/ai/rag/reindex"] = {
|
|
4726
|
+
post: {
|
|
4727
|
+
tags: ["AI"],
|
|
4728
|
+
summary: "Trigger vector reindexing for semantic search",
|
|
4729
|
+
requestBody: {
|
|
4730
|
+
content: {
|
|
4731
|
+
"application/json": {
|
|
4732
|
+
schema: {
|
|
4733
|
+
type: "object",
|
|
4734
|
+
properties: {
|
|
4735
|
+
collection: { type: "string" },
|
|
4736
|
+
force: { type: "boolean" }
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
},
|
|
4742
|
+
responses: { 200: { description: "Reindexing results and chunk counts" } }
|
|
4743
|
+
}
|
|
4744
|
+
};
|
|
4745
|
+
spec.paths["/api/ai/rag/search"] = {
|
|
4746
|
+
post: {
|
|
4747
|
+
tags: ["AI"],
|
|
4748
|
+
summary: "Perform semantic vector search against indexed content",
|
|
4749
|
+
requestBody: {
|
|
4750
|
+
required: true,
|
|
4751
|
+
content: {
|
|
4752
|
+
"application/json": {
|
|
4753
|
+
schema: {
|
|
4754
|
+
type: "object",
|
|
4755
|
+
properties: {
|
|
4756
|
+
query: { type: "string" },
|
|
4757
|
+
limit: { type: "integer", default: 5 }
|
|
4758
|
+
},
|
|
4759
|
+
required: ["query"]
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
},
|
|
4764
|
+
responses: { 200: { description: "Semantic search results" } }
|
|
4765
|
+
}
|
|
4766
|
+
};
|
|
5026
4767
|
for (const collection of config.collections) {
|
|
5027
4768
|
spec.components.schemas[collection.slug] = collectionToSchema(collection);
|
|
5028
4769
|
}
|
|
@@ -6496,7 +6237,240 @@ function createExpr() {
|
|
|
6496
6237
|
var expr = createExpr();
|
|
6497
6238
|
var when = expr;
|
|
6498
6239
|
|
|
6240
|
+
// src/utils/tenant.ts
|
|
6241
|
+
var import_http_exception = require("hono/http-exception");
|
|
6242
|
+
function getAllowedSitesForUser(user, payload) {
|
|
6243
|
+
const raw = user?.allowedSites ?? user?.siteIds ?? user?.siteAccess ?? (user?.siteId ? [user.siteId] : void 0) ?? payload?.allowedSites ?? payload?.siteIds ?? payload?.siteAccess ?? (payload?.siteId ? [payload.siteId] : void 0);
|
|
6244
|
+
if (Array.isArray(raw)) {
|
|
6245
|
+
return raw.filter((s) => typeof s === "string" && s.trim().length > 0);
|
|
6246
|
+
}
|
|
6247
|
+
return void 0;
|
|
6248
|
+
}
|
|
6249
|
+
function isSiteAuthorized(user, payload, requestedSiteId) {
|
|
6250
|
+
const allowedSites = getAllowedSitesForUser(user, payload);
|
|
6251
|
+
if (!allowedSites || allowedSites.length === 0) {
|
|
6252
|
+
return true;
|
|
6253
|
+
}
|
|
6254
|
+
if (!requestedSiteId) {
|
|
6255
|
+
return false;
|
|
6256
|
+
}
|
|
6257
|
+
return allowedSites.includes(requestedSiteId);
|
|
6258
|
+
}
|
|
6259
|
+
function resolveAuthorizedSiteId(c) {
|
|
6260
|
+
const user = typeof c.get === "function" ? c.get("user") : void 0;
|
|
6261
|
+
const tokenPayload = typeof c.get === "function" ? c.get("authTokenPayload") : void 0;
|
|
6262
|
+
const requestedSiteId = (typeof c.req?.header === "function" ? c.req.header("X-Site-Id") : void 0) || (typeof c.get === "function" ? c.get("siteId") : void 0);
|
|
6263
|
+
const allowedSites = getAllowedSitesForUser(user, tokenPayload);
|
|
6264
|
+
if (Array.isArray(allowedSites) && allowedSites.length > 0) {
|
|
6265
|
+
if (!requestedSiteId) {
|
|
6266
|
+
return allowedSites[0];
|
|
6267
|
+
}
|
|
6268
|
+
if (!allowedSites.includes(requestedSiteId)) {
|
|
6269
|
+
throw new import_http_exception.HTTPException(403, {
|
|
6270
|
+
message: `Forbidden: User is not authorized to access site "${requestedSiteId}".`
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6273
|
+
return requestedSiteId;
|
|
6274
|
+
}
|
|
6275
|
+
return requestedSiteId || "default";
|
|
6276
|
+
}
|
|
6277
|
+
|
|
6278
|
+
// src/utils/ai-pii.ts
|
|
6279
|
+
var NON_NEGOTIABLE_CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
|
|
6280
|
+
"password",
|
|
6281
|
+
"salt",
|
|
6282
|
+
"hash",
|
|
6283
|
+
"resetPasswordToken",
|
|
6284
|
+
"apiKey",
|
|
6285
|
+
"token",
|
|
6286
|
+
"secret",
|
|
6287
|
+
"accessToken",
|
|
6288
|
+
"refreshToken",
|
|
6289
|
+
"privateKey",
|
|
6290
|
+
"private_key",
|
|
6291
|
+
"webhookSecret",
|
|
6292
|
+
"webhook_secret",
|
|
6293
|
+
"authSecret",
|
|
6294
|
+
"auth_secret",
|
|
6295
|
+
"cvv",
|
|
6296
|
+
"cvc",
|
|
6297
|
+
"pin"
|
|
6298
|
+
]);
|
|
6299
|
+
function maskEmail(email, strategy = "mask") {
|
|
6300
|
+
if (!email || typeof email !== "string") return email;
|
|
6301
|
+
if (strategy === "token") return "[REDACTED_EMAIL]";
|
|
6302
|
+
const atIdx = email.indexOf("@");
|
|
6303
|
+
if (atIdx <= 1) {
|
|
6304
|
+
return "***@" + (email.slice(atIdx + 1) || "example.com");
|
|
6305
|
+
}
|
|
6306
|
+
const prefix = email[0];
|
|
6307
|
+
const domain = email.slice(atIdx);
|
|
6308
|
+
return `${prefix}***${domain}`;
|
|
6309
|
+
}
|
|
6310
|
+
function maskPhone(phone, strategy = "mask") {
|
|
6311
|
+
if (!phone || typeof phone !== "string") return phone;
|
|
6312
|
+
if (strategy === "token") return "[REDACTED_PHONE]";
|
|
6313
|
+
const digits = phone.replace(/\D/g, "");
|
|
6314
|
+
if (digits.length < 4) return "***-***-****";
|
|
6315
|
+
const lastFour = digits.slice(-4);
|
|
6316
|
+
return `***-***-${lastFour}`;
|
|
6317
|
+
}
|
|
6318
|
+
var PII_REGEX_PATTERNS = {
|
|
6319
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
6320
|
+
phone_number: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6321
|
+
phoneNumber: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6322
|
+
phone: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6323
|
+
credit_card: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
6324
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
6325
|
+
ipv4: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
6326
|
+
ipv6: /(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}/g,
|
|
6327
|
+
iban: /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g,
|
|
6328
|
+
api_key: /\b(?:sk_(?:live|test)_[0-9a-zA-Z_]{16,}|ghp_[0-9a-zA-Z_]{20,}|AKIA[0-9A-Z]{16})\b/g,
|
|
6329
|
+
jwt: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g,
|
|
6330
|
+
passport: /\b[A-Z]{1,2}[0-9]{6,9}\b/g
|
|
6331
|
+
};
|
|
6332
|
+
var PII_PATTERN_PRIORITY = {
|
|
6333
|
+
api_key: 1,
|
|
6334
|
+
jwt: 2,
|
|
6335
|
+
iban: 3,
|
|
6336
|
+
credit_card: 4,
|
|
6337
|
+
ipv6: 5,
|
|
6338
|
+
ipv4: 6,
|
|
6339
|
+
email: 7,
|
|
6340
|
+
passport: 8,
|
|
6341
|
+
ssn: 9,
|
|
6342
|
+
phone: 10,
|
|
6343
|
+
phone_number: 10,
|
|
6344
|
+
phoneNumber: 10
|
|
6345
|
+
};
|
|
6346
|
+
function maskTextPII(text, piiConfig) {
|
|
6347
|
+
if (!text || typeof text !== "string") return text;
|
|
6348
|
+
if (!piiConfig || piiConfig.enabled === false) return text;
|
|
6349
|
+
let result = text;
|
|
6350
|
+
const strategy = piiConfig.strategy || "mask";
|
|
6351
|
+
const rawPatterns = piiConfig.patterns || [
|
|
6352
|
+
"email",
|
|
6353
|
+
"phone",
|
|
6354
|
+
"phone_number",
|
|
6355
|
+
"phoneNumber",
|
|
6356
|
+
"ssn",
|
|
6357
|
+
"credit_card",
|
|
6358
|
+
"ipv4"
|
|
6359
|
+
];
|
|
6360
|
+
const patterns = [...rawPatterns].sort(
|
|
6361
|
+
(a, b) => (PII_PATTERN_PRIORITY[a] ?? 99) - (PII_PATTERN_PRIORITY[b] ?? 99)
|
|
6362
|
+
);
|
|
6363
|
+
for (const patternName of patterns) {
|
|
6364
|
+
const regex = PII_REGEX_PATTERNS[patternName];
|
|
6365
|
+
if (!regex) continue;
|
|
6366
|
+
result = result.replace(regex, (match) => {
|
|
6367
|
+
switch (patternName) {
|
|
6368
|
+
case "email":
|
|
6369
|
+
return maskEmail(match, strategy);
|
|
6370
|
+
case "phone":
|
|
6371
|
+
case "phone_number":
|
|
6372
|
+
case "phoneNumber":
|
|
6373
|
+
return maskPhone(match, strategy);
|
|
6374
|
+
case "credit_card":
|
|
6375
|
+
return strategy === "token" ? "[REDACTED_CREDIT_CARD]" : "****-****-****-" + match.replace(/\D/g, "").slice(-4);
|
|
6376
|
+
case "ssn":
|
|
6377
|
+
return strategy === "token" ? "[REDACTED_SSN]" : "***-**-" + match.replace(/\D/g, "").slice(-4);
|
|
6378
|
+
case "ipv4":
|
|
6379
|
+
return "[REDACTED_IP]";
|
|
6380
|
+
case "ipv6":
|
|
6381
|
+
return "[REDACTED_IPV6]";
|
|
6382
|
+
case "iban":
|
|
6383
|
+
return strategy === "token" ? "[REDACTED_IBAN]" : "****-****-" + match.replace(/\s/g, "").slice(-4);
|
|
6384
|
+
case "api_key":
|
|
6385
|
+
return "[REDACTED_API_KEY]";
|
|
6386
|
+
case "jwt":
|
|
6387
|
+
return "[REDACTED_JWT]";
|
|
6388
|
+
case "passport":
|
|
6389
|
+
return strategy === "token" ? "[REDACTED_PASSPORT]" : "***" + match.slice(-3);
|
|
6390
|
+
default:
|
|
6391
|
+
return "[REDACTED]";
|
|
6392
|
+
}
|
|
6393
|
+
});
|
|
6394
|
+
}
|
|
6395
|
+
if (typeof piiConfig.customScrubber === "function") {
|
|
6396
|
+
result = piiConfig.customScrubber(result);
|
|
6397
|
+
}
|
|
6398
|
+
return result;
|
|
6399
|
+
}
|
|
6400
|
+
function sanitizeDocForAI(options) {
|
|
6401
|
+
const { doc, collectionConfig, globalAIConfig } = options;
|
|
6402
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
6403
|
+
const copy = { ...doc };
|
|
6404
|
+
for (const cred of NON_NEGOTIABLE_CREDENTIAL_FIELDS) {
|
|
6405
|
+
delete copy[cred];
|
|
6406
|
+
}
|
|
6407
|
+
if (collectionConfig?.ai?.excludeFields) {
|
|
6408
|
+
for (const field of collectionConfig.ai.excludeFields) {
|
|
6409
|
+
delete copy[field];
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
const fields = collectionConfig?.fields || [];
|
|
6413
|
+
for (const field of fields) {
|
|
6414
|
+
const fieldName = field.name;
|
|
6415
|
+
if (!fieldName || copy[fieldName] === void 0) continue;
|
|
6416
|
+
if (field.ai?.exclude === true) {
|
|
6417
|
+
delete copy[fieldName];
|
|
6418
|
+
continue;
|
|
6419
|
+
}
|
|
6420
|
+
const val = copy[fieldName];
|
|
6421
|
+
const normalizedName = fieldName.toLowerCase().replace(/[_\s-]/g, "");
|
|
6422
|
+
const isEmail = field.type === "email" || normalizedName.includes("email");
|
|
6423
|
+
const isPhone = field.type === "phone" || normalizedName.includes("phone") || normalizedName === "tel" || normalizedName === "telephone" || normalizedName === "mobile" || normalizedName === "cell";
|
|
6424
|
+
if (isEmail) {
|
|
6425
|
+
if (globalAIConfig?.pii?.enabled !== false && field.ai?.allowRaw !== true && typeof val === "string") {
|
|
6426
|
+
copy[fieldName] = maskEmail(val, globalAIConfig?.pii?.strategy);
|
|
6427
|
+
}
|
|
6428
|
+
continue;
|
|
6429
|
+
}
|
|
6430
|
+
if (isPhone) {
|
|
6431
|
+
if (globalAIConfig?.pii?.enabled !== false && field.ai?.allowRaw !== true && typeof val === "string") {
|
|
6432
|
+
copy[fieldName] = maskPhone(val, globalAIConfig?.pii?.strategy);
|
|
6433
|
+
}
|
|
6434
|
+
continue;
|
|
6435
|
+
}
|
|
6436
|
+
if (field.ai?.redact === true || field.ai?.redact === "mask") {
|
|
6437
|
+
if (typeof val === "string") {
|
|
6438
|
+
copy[fieldName] = "[REDACTED]";
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
if (collectionConfig?.ai?.redactFields) {
|
|
6443
|
+
for (const field of collectionConfig.ai.redactFields) {
|
|
6444
|
+
if (copy[field] !== void 0) {
|
|
6445
|
+
const val = copy[field];
|
|
6446
|
+
if (typeof val === "string") {
|
|
6447
|
+
if (field.toLowerCase().includes("email")) {
|
|
6448
|
+
copy[field] = maskEmail(val, globalAIConfig?.pii?.strategy);
|
|
6449
|
+
} else if (field.toLowerCase().includes("phone")) {
|
|
6450
|
+
copy[field] = maskPhone(val, globalAIConfig?.pii?.strategy);
|
|
6451
|
+
} else {
|
|
6452
|
+
copy[field] = "[REDACTED]";
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
let result = copy;
|
|
6459
|
+
if (typeof collectionConfig?.ai?.sanitizeDoc === "function") {
|
|
6460
|
+
result = collectionConfig.ai.sanitizeDoc(copy);
|
|
6461
|
+
}
|
|
6462
|
+
if (globalAIConfig?.pii?.enabled === true) {
|
|
6463
|
+
for (const [k, v] of Object.entries(result)) {
|
|
6464
|
+
if (typeof v === "string") {
|
|
6465
|
+
result[k] = maskTextPII(v, globalAIConfig.pii);
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
return result;
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6499
6472
|
// src/index.ts
|
|
6473
|
+
var import_zod = require("zod");
|
|
6500
6474
|
function defineCollection(config) {
|
|
6501
6475
|
return config;
|
|
6502
6476
|
}
|
|
@@ -6545,12 +6519,20 @@ function defineTab(args) {
|
|
|
6545
6519
|
}
|
|
6546
6520
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6547
6521
|
0 && (module.exports = {
|
|
6522
|
+
AI_ACTIONS_COLLECTION,
|
|
6523
|
+
AI_AUDIT_COLLECTION,
|
|
6524
|
+
AI_CHUNKS_COLLECTION,
|
|
6525
|
+
AI_MESSAGES_COLLECTION,
|
|
6526
|
+
AI_THREADS_COLLECTION,
|
|
6548
6527
|
BUILTIN_JEXL_HELPERS,
|
|
6549
6528
|
ConfigValidationError,
|
|
6529
|
+
DyrectedAIError,
|
|
6550
6530
|
FieldConditionBuilder,
|
|
6551
6531
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
6552
6532
|
LIFECYCLE_EVENT_NAMES,
|
|
6553
6533
|
MatchBuilder,
|
|
6534
|
+
NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
6535
|
+
PII_REGEX_PATTERNS,
|
|
6554
6536
|
WORKFLOW_HISTORY_COLLECTION,
|
|
6555
6537
|
abs,
|
|
6556
6538
|
addDays,
|
|
@@ -6638,18 +6620,26 @@ function defineTab(args) {
|
|
|
6638
6620
|
generateOpenApi,
|
|
6639
6621
|
get,
|
|
6640
6622
|
getAdminAuthCollection,
|
|
6623
|
+
getAdminRoleForCollection,
|
|
6624
|
+
getAllowedSitesForUser,
|
|
6641
6625
|
getPublicAdminAuthConfig,
|
|
6642
6626
|
includes,
|
|
6643
6627
|
initializeWorkflowDocument,
|
|
6628
|
+
isAICollection,
|
|
6644
6629
|
isConfigValidationError,
|
|
6645
6630
|
isDetailItemVisible,
|
|
6646
6631
|
isEmpty,
|
|
6647
6632
|
isFuture,
|
|
6648
6633
|
isPast,
|
|
6634
|
+
isSiteAuthorized,
|
|
6635
|
+
isUserAdmin,
|
|
6649
6636
|
join,
|
|
6650
6637
|
last,
|
|
6651
6638
|
length,
|
|
6652
6639
|
lower,
|
|
6640
|
+
maskEmail,
|
|
6641
|
+
maskPhone,
|
|
6642
|
+
maskTextPII,
|
|
6653
6643
|
materializeWorkflowDocument,
|
|
6654
6644
|
max,
|
|
6655
6645
|
min,
|
|
@@ -6667,8 +6657,10 @@ function defineTab(args) {
|
|
|
6667
6657
|
registerJexlHelpers,
|
|
6668
6658
|
replace,
|
|
6669
6659
|
resolveAdminAuthCollection,
|
|
6660
|
+
resolveAuthorizedSiteId,
|
|
6670
6661
|
round,
|
|
6671
6662
|
runCollectionHooks,
|
|
6663
|
+
sanitizeDocForAI,
|
|
6672
6664
|
saveWorkflowDraft,
|
|
6673
6665
|
simplePublishingWorkflow,
|
|
6674
6666
|
slugify,
|
|
@@ -6687,5 +6679,6 @@ function defineTab(args) {
|
|
|
6687
6679
|
validatePreviewUrlExpression,
|
|
6688
6680
|
when,
|
|
6689
6681
|
wordCount,
|
|
6690
|
-
workflowCapabilities
|
|
6682
|
+
workflowCapabilities,
|
|
6683
|
+
z
|
|
6691
6684
|
});
|