@dyrected/core 2.12.0 → 2.13.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/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-D_ODsBWA.d.cts} +470 -2
- package/dist/{app-config-C6hYM8WO.d.ts → app-D_ODsBWA.d.ts} +470 -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-VOE2LOLF.js → chunk-DZMBVIMN.js} +265 -1760
- package/dist/chunk-HRRJ3RYT.js +70 -0
- package/dist/chunk-W7GKJM2X.js +1136 -0
- package/dist/chunk-ZYYDTHF3.js +635 -0
- package/dist/index.cjs +501 -525
- package/dist/index.d.cts +80 -4
- package/dist/index.d.ts +80 -4
- package/dist/index.js +41 -2
- package/dist/rag.service-YDGEYTXS.js +11 -0
- package/dist/server.cjs +4971 -1388
- package/dist/server.d.cts +358 -328
- package/dist/server.d.ts +358 -328
- package/dist/server.js +2035 -262
- package/package.json +12 -9
- package/LICENSE.md +0 -50
- 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,24 @@ __export(index_exports, {
|
|
|
2391
1885
|
generateOpenApi: () => generateOpenApi,
|
|
2392
1886
|
get: () => get,
|
|
2393
1887
|
getAdminAuthCollection: () => getAdminAuthCollection,
|
|
1888
|
+
getAllowedSitesForUser: () => getAllowedSitesForUser,
|
|
2394
1889
|
getPublicAdminAuthConfig: () => getPublicAdminAuthConfig,
|
|
2395
1890
|
includes: () => includes,
|
|
2396
1891
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
1892
|
+
isAICollection: () => isAICollection,
|
|
2397
1893
|
isConfigValidationError: () => isConfigValidationError,
|
|
2398
1894
|
isDetailItemVisible: () => isDetailItemVisible,
|
|
2399
1895
|
isEmpty: () => isEmpty,
|
|
2400
1896
|
isFuture: () => isFuture,
|
|
2401
1897
|
isPast: () => isPast,
|
|
1898
|
+
isSiteAuthorized: () => isSiteAuthorized,
|
|
2402
1899
|
join: () => join,
|
|
2403
1900
|
last: () => last,
|
|
2404
1901
|
length: () => length,
|
|
2405
1902
|
lower: () => lower,
|
|
1903
|
+
maskEmail: () => maskEmail,
|
|
1904
|
+
maskPhone: () => maskPhone,
|
|
1905
|
+
maskTextPII: () => maskTextPII,
|
|
2406
1906
|
materializeWorkflowDocument: () => materializeWorkflowDocument,
|
|
2407
1907
|
max: () => max,
|
|
2408
1908
|
min: () => min,
|
|
@@ -2420,8 +1920,10 @@ __export(index_exports, {
|
|
|
2420
1920
|
registerJexlHelpers: () => registerJexlHelpers,
|
|
2421
1921
|
replace: () => replace,
|
|
2422
1922
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
1923
|
+
resolveAuthorizedSiteId: () => resolveAuthorizedSiteId,
|
|
2423
1924
|
round: () => round,
|
|
2424
1925
|
runCollectionHooks: () => runCollectionHooks,
|
|
1926
|
+
sanitizeDocForAI: () => sanitizeDocForAI,
|
|
2425
1927
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
2426
1928
|
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
2427
1929
|
slugify: () => slugify,
|
|
@@ -2440,7 +1942,8 @@ __export(index_exports, {
|
|
|
2440
1942
|
validatePreviewUrlExpression: () => validatePreviewUrlExpression,
|
|
2441
1943
|
when: () => when,
|
|
2442
1944
|
wordCount: () => wordCount,
|
|
2443
|
-
workflowCapabilities: () => workflowCapabilities
|
|
1945
|
+
workflowCapabilities: () => workflowCapabilities,
|
|
1946
|
+
z: () => import_zod.z
|
|
2444
1947
|
});
|
|
2445
1948
|
module.exports = __toCommonJS(index_exports);
|
|
2446
1949
|
|
|
@@ -2460,6 +1963,40 @@ function defineAction(config) {
|
|
|
2460
1963
|
return config;
|
|
2461
1964
|
}
|
|
2462
1965
|
|
|
1966
|
+
// src/types/ai.ts
|
|
1967
|
+
var AI_THREADS_COLLECTION = "_dyrected_ai_threads";
|
|
1968
|
+
var AI_MESSAGES_COLLECTION = "_dyrected_ai_messages";
|
|
1969
|
+
var AI_CHUNKS_COLLECTION = "_dyrected_ai_chunks";
|
|
1970
|
+
var AI_ACTIONS_COLLECTION = "_dyrected_ai_actions";
|
|
1971
|
+
var AI_AUDIT_COLLECTION = "_dyrected_ai_audit";
|
|
1972
|
+
function isAICollection(slug) {
|
|
1973
|
+
return slug === AI_THREADS_COLLECTION || slug === AI_MESSAGES_COLLECTION || slug === AI_CHUNKS_COLLECTION || slug === AI_ACTIONS_COLLECTION || slug === AI_AUDIT_COLLECTION;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// src/types/ai-errors.ts
|
|
1977
|
+
var DyrectedAIError = class _DyrectedAIError extends Error {
|
|
1978
|
+
code;
|
|
1979
|
+
status;
|
|
1980
|
+
details;
|
|
1981
|
+
constructor(code, message, status = 500, details) {
|
|
1982
|
+
super(message);
|
|
1983
|
+
this.name = "DyrectedAIError";
|
|
1984
|
+
this.code = code;
|
|
1985
|
+
this.status = status;
|
|
1986
|
+
this.details = details;
|
|
1987
|
+
Object.setPrototypeOf(this, _DyrectedAIError.prototype);
|
|
1988
|
+
}
|
|
1989
|
+
toJSON() {
|
|
1990
|
+
return {
|
|
1991
|
+
error: true,
|
|
1992
|
+
code: this.code,
|
|
1993
|
+
message: this.message,
|
|
1994
|
+
status: this.status,
|
|
1995
|
+
details: this.details
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2463
2000
|
// src/auth/token.ts
|
|
2464
2001
|
var import_jose = require("jose");
|
|
2465
2002
|
|
|
@@ -2862,18 +2399,31 @@ async function transitionWorkflow(args) {
|
|
|
2862
2399
|
await hook({ ...hookContext, doc: updated, event: events[0] });
|
|
2863
2400
|
} catch (error) {
|
|
2864
2401
|
try {
|
|
2865
|
-
const
|
|
2866
|
-
|
|
2402
|
+
const obs = config?.__observability;
|
|
2403
|
+
obs?.recordWorkflowHookFailure?.({
|
|
2867
2404
|
collection: collection.slug,
|
|
2868
2405
|
transition: transition.name
|
|
2869
2406
|
});
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2407
|
+
const logger = config?.logger;
|
|
2408
|
+
if (logger && typeof logger.child === "function") {
|
|
2409
|
+
logger.child({ component: "workflow" }).error({
|
|
2410
|
+
err: error,
|
|
2411
|
+
msg: "afterTransition hook failed",
|
|
2412
|
+
collection: collection.slug,
|
|
2413
|
+
transition: transition.name,
|
|
2414
|
+
documentId: id
|
|
2415
|
+
});
|
|
2416
|
+
} else if (logger && typeof logger.error === "function") {
|
|
2417
|
+
logger.error({
|
|
2418
|
+
err: error,
|
|
2419
|
+
msg: "afterTransition hook failed",
|
|
2420
|
+
collection: collection.slug,
|
|
2421
|
+
transition: transition.name,
|
|
2422
|
+
documentId: id
|
|
2423
|
+
});
|
|
2424
|
+
} else {
|
|
2425
|
+
console.error("afterTransition hook failed:", error);
|
|
2426
|
+
}
|
|
2877
2427
|
} catch {
|
|
2878
2428
|
console.error("afterTransition hook failed:", error);
|
|
2879
2429
|
}
|
|
@@ -5023,6 +4573,182 @@ function generateOpenApi(config) {
|
|
|
5023
4573
|
}
|
|
5024
4574
|
}
|
|
5025
4575
|
};
|
|
4576
|
+
spec.paths["/api/ai/chat"] = {
|
|
4577
|
+
post: {
|
|
4578
|
+
tags: ["AI"],
|
|
4579
|
+
summary: "Send prompt and stream AI assistant response",
|
|
4580
|
+
requestBody: {
|
|
4581
|
+
required: true,
|
|
4582
|
+
content: {
|
|
4583
|
+
"application/json": {
|
|
4584
|
+
schema: {
|
|
4585
|
+
type: "object",
|
|
4586
|
+
properties: {
|
|
4587
|
+
content: { type: "string", description: "User prompt message" },
|
|
4588
|
+
prompt: { type: "string", description: "Alias for content" },
|
|
4589
|
+
threadId: { type: "string", description: "Existing conversation thread ID" },
|
|
4590
|
+
messages: { type: "array", items: { type: "object" }, description: "Conversation history" }
|
|
4591
|
+
},
|
|
4592
|
+
required: []
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
},
|
|
4597
|
+
responses: {
|
|
4598
|
+
200: {
|
|
4599
|
+
description: "Server-Sent Events (SSE) AI message stream",
|
|
4600
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
};
|
|
4605
|
+
spec.paths["/api/ai/threads"] = {
|
|
4606
|
+
get: {
|
|
4607
|
+
tags: ["AI"],
|
|
4608
|
+
summary: "List conversation threads",
|
|
4609
|
+
parameters: [
|
|
4610
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } }
|
|
4611
|
+
],
|
|
4612
|
+
responses: { 200: { description: "Array of conversation threads" } }
|
|
4613
|
+
},
|
|
4614
|
+
post: {
|
|
4615
|
+
tags: ["AI"],
|
|
4616
|
+
summary: "Create a new conversation thread",
|
|
4617
|
+
requestBody: {
|
|
4618
|
+
content: {
|
|
4619
|
+
"application/json": {
|
|
4620
|
+
schema: {
|
|
4621
|
+
type: "object",
|
|
4622
|
+
properties: { title: { type: "string" } }
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
},
|
|
4627
|
+
responses: { 201: { description: "Created conversation thread" } }
|
|
4628
|
+
},
|
|
4629
|
+
delete: {
|
|
4630
|
+
tags: ["AI"],
|
|
4631
|
+
summary: "Clear all conversation threads",
|
|
4632
|
+
responses: { 200: { description: "Count of deleted threads" } }
|
|
4633
|
+
}
|
|
4634
|
+
};
|
|
4635
|
+
spec.paths["/api/ai/threads/{threadId}"] = {
|
|
4636
|
+
get: {
|
|
4637
|
+
tags: ["AI"],
|
|
4638
|
+
summary: "Get conversation thread and historical messages",
|
|
4639
|
+
parameters: [
|
|
4640
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4641
|
+
],
|
|
4642
|
+
responses: { 200: { description: "Thread details and messages" } }
|
|
4643
|
+
},
|
|
4644
|
+
delete: {
|
|
4645
|
+
tags: ["AI"],
|
|
4646
|
+
summary: "Delete a specific conversation thread",
|
|
4647
|
+
parameters: [
|
|
4648
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4649
|
+
],
|
|
4650
|
+
responses: { 200: { description: "Deletion success status" } }
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
spec.paths["/api/ai/threads/{threadId}/messages"] = {
|
|
4654
|
+
post: {
|
|
4655
|
+
tags: ["AI"],
|
|
4656
|
+
summary: "Post a message to an existing thread and stream reply",
|
|
4657
|
+
parameters: [
|
|
4658
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4659
|
+
],
|
|
4660
|
+
requestBody: {
|
|
4661
|
+
required: true,
|
|
4662
|
+
content: {
|
|
4663
|
+
"application/json": {
|
|
4664
|
+
schema: {
|
|
4665
|
+
type: "object",
|
|
4666
|
+
properties: { content: { type: "string" } },
|
|
4667
|
+
required: ["content"]
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
},
|
|
4672
|
+
responses: {
|
|
4673
|
+
200: {
|
|
4674
|
+
description: "Server-Sent Events (SSE) AI message stream",
|
|
4675
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
};
|
|
4680
|
+
spec.paths["/api/ai/actions/{actionId}"] = {
|
|
4681
|
+
get: {
|
|
4682
|
+
tags: ["AI"],
|
|
4683
|
+
summary: "Get an AI mutation action proposal",
|
|
4684
|
+
parameters: [
|
|
4685
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4686
|
+
],
|
|
4687
|
+
responses: { 200: { description: "AI Action details and diff payload" } }
|
|
4688
|
+
}
|
|
4689
|
+
};
|
|
4690
|
+
spec.paths["/api/ai/actions/{actionId}/execute"] = {
|
|
4691
|
+
post: {
|
|
4692
|
+
tags: ["AI"],
|
|
4693
|
+
summary: "Approve and execute an AI mutation proposal",
|
|
4694
|
+
parameters: [
|
|
4695
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4696
|
+
],
|
|
4697
|
+
responses: { 200: { description: "Execution result" } }
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
spec.paths["/api/ai/actions/{actionId}/reject"] = {
|
|
4701
|
+
post: {
|
|
4702
|
+
tags: ["AI"],
|
|
4703
|
+
summary: "Reject an AI mutation proposal",
|
|
4704
|
+
parameters: [
|
|
4705
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4706
|
+
],
|
|
4707
|
+
responses: { 200: { description: "Rejection result" } }
|
|
4708
|
+
}
|
|
4709
|
+
};
|
|
4710
|
+
spec.paths["/api/ai/rag/reindex"] = {
|
|
4711
|
+
post: {
|
|
4712
|
+
tags: ["AI"],
|
|
4713
|
+
summary: "Trigger vector reindexing for semantic search",
|
|
4714
|
+
requestBody: {
|
|
4715
|
+
content: {
|
|
4716
|
+
"application/json": {
|
|
4717
|
+
schema: {
|
|
4718
|
+
type: "object",
|
|
4719
|
+
properties: {
|
|
4720
|
+
collection: { type: "string" },
|
|
4721
|
+
force: { type: "boolean" }
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
},
|
|
4727
|
+
responses: { 200: { description: "Reindexing results and chunk counts" } }
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
spec.paths["/api/ai/rag/search"] = {
|
|
4731
|
+
post: {
|
|
4732
|
+
tags: ["AI"],
|
|
4733
|
+
summary: "Perform semantic vector search against indexed content",
|
|
4734
|
+
requestBody: {
|
|
4735
|
+
required: true,
|
|
4736
|
+
content: {
|
|
4737
|
+
"application/json": {
|
|
4738
|
+
schema: {
|
|
4739
|
+
type: "object",
|
|
4740
|
+
properties: {
|
|
4741
|
+
query: { type: "string" },
|
|
4742
|
+
limit: { type: "integer", default: 5 }
|
|
4743
|
+
},
|
|
4744
|
+
required: ["query"]
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
},
|
|
4749
|
+
responses: { 200: { description: "Semantic search results" } }
|
|
4750
|
+
}
|
|
4751
|
+
};
|
|
5026
4752
|
for (const collection of config.collections) {
|
|
5027
4753
|
spec.components.schemas[collection.slug] = collectionToSchema(collection);
|
|
5028
4754
|
}
|
|
@@ -6496,7 +6222,240 @@ function createExpr() {
|
|
|
6496
6222
|
var expr = createExpr();
|
|
6497
6223
|
var when = expr;
|
|
6498
6224
|
|
|
6225
|
+
// src/utils/tenant.ts
|
|
6226
|
+
var import_http_exception = require("hono/http-exception");
|
|
6227
|
+
function getAllowedSitesForUser(user, payload) {
|
|
6228
|
+
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);
|
|
6229
|
+
if (Array.isArray(raw)) {
|
|
6230
|
+
return raw.filter((s) => typeof s === "string" && s.trim().length > 0);
|
|
6231
|
+
}
|
|
6232
|
+
return void 0;
|
|
6233
|
+
}
|
|
6234
|
+
function isSiteAuthorized(user, payload, requestedSiteId) {
|
|
6235
|
+
const allowedSites = getAllowedSitesForUser(user, payload);
|
|
6236
|
+
if (!allowedSites || allowedSites.length === 0) {
|
|
6237
|
+
return true;
|
|
6238
|
+
}
|
|
6239
|
+
if (!requestedSiteId) {
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
return allowedSites.includes(requestedSiteId);
|
|
6243
|
+
}
|
|
6244
|
+
function resolveAuthorizedSiteId(c) {
|
|
6245
|
+
const user = typeof c.get === "function" ? c.get("user") : void 0;
|
|
6246
|
+
const tokenPayload = typeof c.get === "function" ? c.get("authTokenPayload") : void 0;
|
|
6247
|
+
const requestedSiteId = (typeof c.req?.header === "function" ? c.req.header("X-Site-Id") : void 0) || (typeof c.get === "function" ? c.get("siteId") : void 0);
|
|
6248
|
+
const allowedSites = getAllowedSitesForUser(user, tokenPayload);
|
|
6249
|
+
if (Array.isArray(allowedSites) && allowedSites.length > 0) {
|
|
6250
|
+
if (!requestedSiteId) {
|
|
6251
|
+
return allowedSites[0];
|
|
6252
|
+
}
|
|
6253
|
+
if (!allowedSites.includes(requestedSiteId)) {
|
|
6254
|
+
throw new import_http_exception.HTTPException(403, {
|
|
6255
|
+
message: `Forbidden: User is not authorized to access site "${requestedSiteId}".`
|
|
6256
|
+
});
|
|
6257
|
+
}
|
|
6258
|
+
return requestedSiteId;
|
|
6259
|
+
}
|
|
6260
|
+
return requestedSiteId || "default";
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
// src/utils/ai-pii.ts
|
|
6264
|
+
var NON_NEGOTIABLE_CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
|
|
6265
|
+
"password",
|
|
6266
|
+
"salt",
|
|
6267
|
+
"hash",
|
|
6268
|
+
"resetPasswordToken",
|
|
6269
|
+
"apiKey",
|
|
6270
|
+
"token",
|
|
6271
|
+
"secret",
|
|
6272
|
+
"accessToken",
|
|
6273
|
+
"refreshToken",
|
|
6274
|
+
"privateKey",
|
|
6275
|
+
"private_key",
|
|
6276
|
+
"webhookSecret",
|
|
6277
|
+
"webhook_secret",
|
|
6278
|
+
"authSecret",
|
|
6279
|
+
"auth_secret",
|
|
6280
|
+
"cvv",
|
|
6281
|
+
"cvc",
|
|
6282
|
+
"pin"
|
|
6283
|
+
]);
|
|
6284
|
+
function maskEmail(email, strategy = "mask") {
|
|
6285
|
+
if (!email || typeof email !== "string") return email;
|
|
6286
|
+
if (strategy === "token") return "[REDACTED_EMAIL]";
|
|
6287
|
+
const atIdx = email.indexOf("@");
|
|
6288
|
+
if (atIdx <= 1) {
|
|
6289
|
+
return "***@" + (email.slice(atIdx + 1) || "example.com");
|
|
6290
|
+
}
|
|
6291
|
+
const prefix = email[0];
|
|
6292
|
+
const domain = email.slice(atIdx);
|
|
6293
|
+
return `${prefix}***${domain}`;
|
|
6294
|
+
}
|
|
6295
|
+
function maskPhone(phone, strategy = "mask") {
|
|
6296
|
+
if (!phone || typeof phone !== "string") return phone;
|
|
6297
|
+
if (strategy === "token") return "[REDACTED_PHONE]";
|
|
6298
|
+
const digits = phone.replace(/\D/g, "");
|
|
6299
|
+
if (digits.length < 4) return "***-***-****";
|
|
6300
|
+
const lastFour = digits.slice(-4);
|
|
6301
|
+
return `***-***-${lastFour}`;
|
|
6302
|
+
}
|
|
6303
|
+
var PII_REGEX_PATTERNS = {
|
|
6304
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
6305
|
+
phone_number: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6306
|
+
phoneNumber: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6307
|
+
phone: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6308
|
+
credit_card: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
6309
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
6310
|
+
ipv4: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
6311
|
+
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,
|
|
6312
|
+
iban: /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g,
|
|
6313
|
+
api_key: /\b(?:sk_(?:live|test)_[0-9a-zA-Z_]{16,}|ghp_[0-9a-zA-Z_]{20,}|AKIA[0-9A-Z]{16})\b/g,
|
|
6314
|
+
jwt: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g,
|
|
6315
|
+
passport: /\b[A-Z]{1,2}[0-9]{6,9}\b/g
|
|
6316
|
+
};
|
|
6317
|
+
var PII_PATTERN_PRIORITY = {
|
|
6318
|
+
api_key: 1,
|
|
6319
|
+
jwt: 2,
|
|
6320
|
+
iban: 3,
|
|
6321
|
+
credit_card: 4,
|
|
6322
|
+
ipv6: 5,
|
|
6323
|
+
ipv4: 6,
|
|
6324
|
+
email: 7,
|
|
6325
|
+
passport: 8,
|
|
6326
|
+
ssn: 9,
|
|
6327
|
+
phone: 10,
|
|
6328
|
+
phone_number: 10,
|
|
6329
|
+
phoneNumber: 10
|
|
6330
|
+
};
|
|
6331
|
+
function maskTextPII(text, piiConfig) {
|
|
6332
|
+
if (!text || typeof text !== "string") return text;
|
|
6333
|
+
if (!piiConfig || piiConfig.enabled === false) return text;
|
|
6334
|
+
let result = text;
|
|
6335
|
+
const strategy = piiConfig.strategy || "mask";
|
|
6336
|
+
const rawPatterns = piiConfig.patterns || [
|
|
6337
|
+
"email",
|
|
6338
|
+
"phone",
|
|
6339
|
+
"phone_number",
|
|
6340
|
+
"phoneNumber",
|
|
6341
|
+
"ssn",
|
|
6342
|
+
"credit_card",
|
|
6343
|
+
"ipv4"
|
|
6344
|
+
];
|
|
6345
|
+
const patterns = [...rawPatterns].sort(
|
|
6346
|
+
(a, b) => (PII_PATTERN_PRIORITY[a] ?? 99) - (PII_PATTERN_PRIORITY[b] ?? 99)
|
|
6347
|
+
);
|
|
6348
|
+
for (const patternName of patterns) {
|
|
6349
|
+
const regex = PII_REGEX_PATTERNS[patternName];
|
|
6350
|
+
if (!regex) continue;
|
|
6351
|
+
result = result.replace(regex, (match) => {
|
|
6352
|
+
switch (patternName) {
|
|
6353
|
+
case "email":
|
|
6354
|
+
return maskEmail(match, strategy);
|
|
6355
|
+
case "phone":
|
|
6356
|
+
case "phone_number":
|
|
6357
|
+
case "phoneNumber":
|
|
6358
|
+
return maskPhone(match, strategy);
|
|
6359
|
+
case "credit_card":
|
|
6360
|
+
return strategy === "token" ? "[REDACTED_CREDIT_CARD]" : "****-****-****-" + match.replace(/\D/g, "").slice(-4);
|
|
6361
|
+
case "ssn":
|
|
6362
|
+
return strategy === "token" ? "[REDACTED_SSN]" : "***-**-" + match.replace(/\D/g, "").slice(-4);
|
|
6363
|
+
case "ipv4":
|
|
6364
|
+
return "[REDACTED_IP]";
|
|
6365
|
+
case "ipv6":
|
|
6366
|
+
return "[REDACTED_IPV6]";
|
|
6367
|
+
case "iban":
|
|
6368
|
+
return strategy === "token" ? "[REDACTED_IBAN]" : "****-****-" + match.replace(/\s/g, "").slice(-4);
|
|
6369
|
+
case "api_key":
|
|
6370
|
+
return "[REDACTED_API_KEY]";
|
|
6371
|
+
case "jwt":
|
|
6372
|
+
return "[REDACTED_JWT]";
|
|
6373
|
+
case "passport":
|
|
6374
|
+
return strategy === "token" ? "[REDACTED_PASSPORT]" : "***" + match.slice(-3);
|
|
6375
|
+
default:
|
|
6376
|
+
return "[REDACTED]";
|
|
6377
|
+
}
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6380
|
+
if (typeof piiConfig.customScrubber === "function") {
|
|
6381
|
+
result = piiConfig.customScrubber(result);
|
|
6382
|
+
}
|
|
6383
|
+
return result;
|
|
6384
|
+
}
|
|
6385
|
+
function sanitizeDocForAI(options) {
|
|
6386
|
+
const { doc, collectionConfig, globalAIConfig } = options;
|
|
6387
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
6388
|
+
const copy = { ...doc };
|
|
6389
|
+
for (const cred of NON_NEGOTIABLE_CREDENTIAL_FIELDS) {
|
|
6390
|
+
delete copy[cred];
|
|
6391
|
+
}
|
|
6392
|
+
if (collectionConfig?.ai?.excludeFields) {
|
|
6393
|
+
for (const field of collectionConfig.ai.excludeFields) {
|
|
6394
|
+
delete copy[field];
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
const fields = collectionConfig?.fields || [];
|
|
6398
|
+
for (const field of fields) {
|
|
6399
|
+
const fieldName = field.name;
|
|
6400
|
+
if (!fieldName || copy[fieldName] === void 0) continue;
|
|
6401
|
+
if (field.ai?.exclude === true) {
|
|
6402
|
+
delete copy[fieldName];
|
|
6403
|
+
continue;
|
|
6404
|
+
}
|
|
6405
|
+
const val = copy[fieldName];
|
|
6406
|
+
const normalizedName = fieldName.toLowerCase().replace(/[_\s-]/g, "");
|
|
6407
|
+
const isEmail = field.type === "email" || normalizedName.includes("email");
|
|
6408
|
+
const isPhone = field.type === "phone" || normalizedName.includes("phone") || normalizedName === "tel" || normalizedName === "telephone" || normalizedName === "mobile" || normalizedName === "cell";
|
|
6409
|
+
if (isEmail) {
|
|
6410
|
+
if (globalAIConfig?.pii?.enabled !== false && field.ai?.allowRaw !== true && typeof val === "string") {
|
|
6411
|
+
copy[fieldName] = maskEmail(val, globalAIConfig?.pii?.strategy);
|
|
6412
|
+
}
|
|
6413
|
+
continue;
|
|
6414
|
+
}
|
|
6415
|
+
if (isPhone) {
|
|
6416
|
+
if (globalAIConfig?.pii?.enabled !== false && field.ai?.allowRaw !== true && typeof val === "string") {
|
|
6417
|
+
copy[fieldName] = maskPhone(val, globalAIConfig?.pii?.strategy);
|
|
6418
|
+
}
|
|
6419
|
+
continue;
|
|
6420
|
+
}
|
|
6421
|
+
if (field.ai?.redact === true || field.ai?.redact === "mask") {
|
|
6422
|
+
if (typeof val === "string") {
|
|
6423
|
+
copy[fieldName] = "[REDACTED]";
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
if (collectionConfig?.ai?.redactFields) {
|
|
6428
|
+
for (const field of collectionConfig.ai.redactFields) {
|
|
6429
|
+
if (copy[field] !== void 0) {
|
|
6430
|
+
const val = copy[field];
|
|
6431
|
+
if (typeof val === "string") {
|
|
6432
|
+
if (field.toLowerCase().includes("email")) {
|
|
6433
|
+
copy[field] = maskEmail(val, globalAIConfig?.pii?.strategy);
|
|
6434
|
+
} else if (field.toLowerCase().includes("phone")) {
|
|
6435
|
+
copy[field] = maskPhone(val, globalAIConfig?.pii?.strategy);
|
|
6436
|
+
} else {
|
|
6437
|
+
copy[field] = "[REDACTED]";
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
let result = copy;
|
|
6444
|
+
if (typeof collectionConfig?.ai?.sanitizeDoc === "function") {
|
|
6445
|
+
result = collectionConfig.ai.sanitizeDoc(copy);
|
|
6446
|
+
}
|
|
6447
|
+
if (globalAIConfig?.pii?.enabled === true) {
|
|
6448
|
+
for (const [k, v] of Object.entries(result)) {
|
|
6449
|
+
if (typeof v === "string") {
|
|
6450
|
+
result[k] = maskTextPII(v, globalAIConfig.pii);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
return result;
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6499
6457
|
// src/index.ts
|
|
6458
|
+
var import_zod = require("zod");
|
|
6500
6459
|
function defineCollection(config) {
|
|
6501
6460
|
return config;
|
|
6502
6461
|
}
|
|
@@ -6545,12 +6504,20 @@ function defineTab(args) {
|
|
|
6545
6504
|
}
|
|
6546
6505
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6547
6506
|
0 && (module.exports = {
|
|
6507
|
+
AI_ACTIONS_COLLECTION,
|
|
6508
|
+
AI_AUDIT_COLLECTION,
|
|
6509
|
+
AI_CHUNKS_COLLECTION,
|
|
6510
|
+
AI_MESSAGES_COLLECTION,
|
|
6511
|
+
AI_THREADS_COLLECTION,
|
|
6548
6512
|
BUILTIN_JEXL_HELPERS,
|
|
6549
6513
|
ConfigValidationError,
|
|
6514
|
+
DyrectedAIError,
|
|
6550
6515
|
FieldConditionBuilder,
|
|
6551
6516
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
6552
6517
|
LIFECYCLE_EVENT_NAMES,
|
|
6553
6518
|
MatchBuilder,
|
|
6519
|
+
NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
6520
|
+
PII_REGEX_PATTERNS,
|
|
6554
6521
|
WORKFLOW_HISTORY_COLLECTION,
|
|
6555
6522
|
abs,
|
|
6556
6523
|
addDays,
|
|
@@ -6638,18 +6605,24 @@ function defineTab(args) {
|
|
|
6638
6605
|
generateOpenApi,
|
|
6639
6606
|
get,
|
|
6640
6607
|
getAdminAuthCollection,
|
|
6608
|
+
getAllowedSitesForUser,
|
|
6641
6609
|
getPublicAdminAuthConfig,
|
|
6642
6610
|
includes,
|
|
6643
6611
|
initializeWorkflowDocument,
|
|
6612
|
+
isAICollection,
|
|
6644
6613
|
isConfigValidationError,
|
|
6645
6614
|
isDetailItemVisible,
|
|
6646
6615
|
isEmpty,
|
|
6647
6616
|
isFuture,
|
|
6648
6617
|
isPast,
|
|
6618
|
+
isSiteAuthorized,
|
|
6649
6619
|
join,
|
|
6650
6620
|
last,
|
|
6651
6621
|
length,
|
|
6652
6622
|
lower,
|
|
6623
|
+
maskEmail,
|
|
6624
|
+
maskPhone,
|
|
6625
|
+
maskTextPII,
|
|
6653
6626
|
materializeWorkflowDocument,
|
|
6654
6627
|
max,
|
|
6655
6628
|
min,
|
|
@@ -6667,8 +6640,10 @@ function defineTab(args) {
|
|
|
6667
6640
|
registerJexlHelpers,
|
|
6668
6641
|
replace,
|
|
6669
6642
|
resolveAdminAuthCollection,
|
|
6643
|
+
resolveAuthorizedSiteId,
|
|
6670
6644
|
round,
|
|
6671
6645
|
runCollectionHooks,
|
|
6646
|
+
sanitizeDocForAI,
|
|
6672
6647
|
saveWorkflowDraft,
|
|
6673
6648
|
simplePublishingWorkflow,
|
|
6674
6649
|
slugify,
|
|
@@ -6687,5 +6662,6 @@ function defineTab(args) {
|
|
|
6687
6662
|
validatePreviewUrlExpression,
|
|
6688
6663
|
when,
|
|
6689
6664
|
wordCount,
|
|
6690
|
-
workflowCapabilities
|
|
6665
|
+
workflowCapabilities,
|
|
6666
|
+
z
|
|
6691
6667
|
});
|