@naisys/common 3.0.0-beta.5 → 3.0.0-beta.7
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/agentConfigFile.js +106 -134
- package/dist/agentStatus.js +13 -9
- package/dist/authCache.js +37 -36
- package/dist/builtInModels.js +251 -251
- package/dist/configUtils.js +7 -6
- package/dist/costUtils.js +15 -19
- package/dist/errorHandler.js +29 -36
- package/dist/formatFileSize.js +5 -3
- package/dist/globalConfigLoader.js +45 -45
- package/dist/hateoas-types.js +29 -29
- package/dist/hateoas.js +47 -43
- package/dist/lenientJsonParser.js +9 -12
- package/dist/mimeTypes.js +21 -21
- package/dist/modelTypes.js +92 -95
- package/dist/securityHeaders.js +9 -15
- package/dist/sleep.js +1 -1
- package/dist/urlSafeKey.js +9 -12
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { sanitizeSpendLimit } from "./configUtils.js";
|
|
2
2
|
/** Keys that should never be distributed to clients */
|
|
3
3
|
const EXCLUDED_KEYS = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
"HUB_ACCESS_KEY",
|
|
5
|
+
"HUB_PORT",
|
|
6
|
+
"NAISYS_FOLDER",
|
|
7
|
+
"NAISYS_HOSTNAME",
|
|
8
|
+
"NODE_ENV",
|
|
9
|
+
"SUPERVISOR_PORT",
|
|
10
10
|
];
|
|
11
11
|
/**
|
|
12
12
|
* Builds hub-distributable config from the provided env vars.
|
|
@@ -15,44 +15,44 @@ const EXCLUDED_KEYS = [
|
|
|
15
15
|
* When undefined (e.g. .env fallback), all variables are exported for backwards compat.
|
|
16
16
|
*/
|
|
17
17
|
export function buildClientConfig(variables, shellExportKeys) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
const shellCommand = {
|
|
19
|
+
outputTokenMax: 7500,
|
|
20
|
+
timeoutSeconds: 10,
|
|
21
|
+
maxTimeoutSeconds: 60 * 5,
|
|
22
|
+
};
|
|
23
|
+
const retrySecondsMax = 30 * 60;
|
|
24
|
+
const webTokenMax = 5000;
|
|
25
|
+
const compactSessionEnabled = true;
|
|
26
|
+
const preemptiveCompactEnabled = true;
|
|
27
|
+
// Build variableMap, filtering out excluded keys and undefined values
|
|
28
|
+
const variableMap = {};
|
|
29
|
+
const shellVariableMap = {};
|
|
30
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
31
|
+
if (value !== undefined && !EXCLUDED_KEYS.includes(key)) {
|
|
32
|
+
variableMap[key] = value;
|
|
33
|
+
// When shellExportKeys is undefined (standalone .env mode), export all
|
|
34
|
+
if (!shellExportKeys || shellExportKeys.has(key)) {
|
|
35
|
+
shellVariableMap[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
};
|
|
39
|
+
const googleSearchEngineId = variableMap.GOOGLE_SEARCH_ENGINE_ID;
|
|
40
|
+
const spendLimitDollars = sanitizeSpendLimit(variableMap.SPEND_LIMIT_DOLLARS);
|
|
41
|
+
const spendLimitHours = sanitizeSpendLimit(variableMap.SPEND_LIMIT_HOURS);
|
|
42
|
+
const useToolsForLlmConsoleResponses = true;
|
|
43
|
+
const autoStartAgentsOnMessage = true;
|
|
44
|
+
return {
|
|
45
|
+
shellCommand,
|
|
46
|
+
retrySecondsMax,
|
|
47
|
+
webTokenMax,
|
|
48
|
+
compactSessionEnabled,
|
|
49
|
+
preemptiveCompactEnabled,
|
|
50
|
+
variableMap,
|
|
51
|
+
shellVariableMap,
|
|
52
|
+
googleSearchEngineId,
|
|
53
|
+
spendLimitDollars,
|
|
54
|
+
spendLimitHours,
|
|
55
|
+
useToolsForLlmConsoleResponses,
|
|
56
|
+
autoStartAgentsOnMessage,
|
|
57
|
+
};
|
|
58
58
|
}
|
package/dist/hateoas-types.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
export const HateoasLinkSchema = z.object({
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
rel: z.string(),
|
|
4
|
+
href: z.string(),
|
|
5
|
+
method: z.string().optional(),
|
|
6
|
+
title: z.string().optional(),
|
|
7
|
+
schema: z.string().optional(),
|
|
8
8
|
});
|
|
9
9
|
export const AlternateEncodingSchema = z.object({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
contentType: z.string(),
|
|
11
|
+
description: z.string().optional(),
|
|
12
|
+
fileFields: z.array(z.string()),
|
|
13
13
|
});
|
|
14
14
|
export const HateoasActionSchema = z.object({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
rel: z.string(),
|
|
16
|
+
href: z.string(),
|
|
17
|
+
method: z.string(),
|
|
18
|
+
title: z.string().optional(),
|
|
19
|
+
schema: z.string().optional(),
|
|
20
|
+
body: z.record(z.string(), z.unknown()).optional(),
|
|
21
|
+
alternateEncoding: AlternateEncodingSchema.optional(),
|
|
22
|
+
disabled: z.boolean().optional(),
|
|
23
|
+
disabledReason: z.union([z.string(), z.array(z.string())]).optional(),
|
|
24
24
|
});
|
|
25
25
|
export const HateoasActionTemplateSchema = z.object({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
rel: z.string(),
|
|
27
|
+
hrefTemplate: z.string(),
|
|
28
|
+
method: z.string(),
|
|
29
|
+
title: z.string().optional(),
|
|
30
|
+
schema: z.string().optional(),
|
|
31
|
+
body: z.record(z.string(), z.unknown()).optional(),
|
|
32
|
+
alternateEncoding: AlternateEncodingSchema.optional(),
|
|
33
33
|
});
|
|
34
34
|
export const HateoasLinkTemplateSchema = z.object({
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
rel: z.string(),
|
|
36
|
+
hrefTemplate: z.string(),
|
|
37
|
+
title: z.string().optional(),
|
|
38
38
|
});
|
|
39
39
|
export const HateoasLinksSchema = z.object({
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
_links: z.array(HateoasLinkSchema),
|
|
41
|
+
_actions: z.array(HateoasActionSchema).optional(),
|
|
42
42
|
});
|
package/dist/hateoas.js
CHANGED
|
@@ -4,59 +4,63 @@
|
|
|
4
4
|
* (e.g. when rendering a disabled button with a tooltip).
|
|
5
5
|
*/
|
|
6
6
|
export function hasAction(actions, rel, opts) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const a = actions?.find((a) => a.rel === rel);
|
|
8
|
+
if (!a)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (a.disabled && !opts?.includeDisabled)
|
|
11
|
+
return undefined;
|
|
12
|
+
return a;
|
|
11
13
|
}
|
|
12
14
|
export function hasActionTemplate(templates, rel) {
|
|
13
|
-
|
|
15
|
+
return templates?.find((t) => t.rel === rel);
|
|
14
16
|
}
|
|
15
17
|
export function hasLinkTemplate(templates, rel) {
|
|
16
|
-
|
|
18
|
+
return templates?.find((t) => t.rel === rel);
|
|
17
19
|
}
|
|
18
20
|
export function resolveActions(defs, baseHref, ctx, checkPermission) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const actions = [];
|
|
22
|
+
for (const def of defs) {
|
|
23
|
+
if (def.statuses) {
|
|
24
|
+
const status = ctx.status;
|
|
25
|
+
if (!status || !def.statuses.includes(status))
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (def.visibleWhen && !def.visibleWhen(ctx))
|
|
29
|
+
continue;
|
|
30
|
+
const hasPerm = !def.permission || checkPermission(def.permission);
|
|
31
|
+
if (def.hideWithoutPermission && !hasPerm)
|
|
32
|
+
continue;
|
|
33
|
+
const gate = !hasPerm && def.permission
|
|
34
|
+
? {
|
|
35
|
+
disabled: true,
|
|
36
|
+
disabledReason: `Requires ${def.permission} permission`,
|
|
37
|
+
}
|
|
38
|
+
: {};
|
|
39
|
+
const disabledReason = hasPerm && def.disabledWhen ? def.disabledWhen(ctx) : null;
|
|
40
|
+
actions.push({
|
|
41
|
+
rel: def.rel,
|
|
42
|
+
href: def.href ?? baseHref + (def.path ?? ""),
|
|
43
|
+
method: def.method,
|
|
44
|
+
title: def.title,
|
|
45
|
+
...(def.schema ? { schema: def.schema } : {}),
|
|
46
|
+
...(def.body ? { body: def.body } : {}),
|
|
47
|
+
...gate,
|
|
48
|
+
...(disabledReason ? { disabled: true, disabledReason } : {}),
|
|
49
|
+
});
|
|
24
50
|
}
|
|
25
|
-
|
|
26
|
-
const hasPerm = !def.permission || checkPermission(def.permission);
|
|
27
|
-
if (def.hideWithoutPermission && !hasPerm) continue;
|
|
28
|
-
const gate =
|
|
29
|
-
!hasPerm && def.permission
|
|
30
|
-
? {
|
|
31
|
-
disabled: true,
|
|
32
|
-
disabledReason: `Requires ${def.permission} permission`,
|
|
33
|
-
}
|
|
34
|
-
: {};
|
|
35
|
-
const disabledReason =
|
|
36
|
-
hasPerm && def.disabledWhen ? def.disabledWhen(ctx) : null;
|
|
37
|
-
actions.push({
|
|
38
|
-
rel: def.rel,
|
|
39
|
-
href: def.href ?? baseHref + (def.path ?? ""),
|
|
40
|
-
method: def.method,
|
|
41
|
-
title: def.title,
|
|
42
|
-
...(def.schema ? { schema: def.schema } : {}),
|
|
43
|
-
...(def.body ? { body: def.body } : {}),
|
|
44
|
-
...gate,
|
|
45
|
-
...(disabledReason ? { disabled: true, disabledReason } : {}),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return actions;
|
|
51
|
+
return actions;
|
|
49
52
|
}
|
|
50
53
|
export function permGate(hasPerm, permission) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
return hasPerm
|
|
55
|
+
? {}
|
|
56
|
+
: {
|
|
57
|
+
disabled: true,
|
|
58
|
+
disabledReason: `Requires ${permission} permission`,
|
|
59
|
+
};
|
|
57
60
|
}
|
|
58
61
|
/** Normalize a `disabledReason` (string | string[] | undefined) to a single display string. */
|
|
59
62
|
export function formatDisabledReason(reason) {
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
if (!reason)
|
|
64
|
+
return undefined;
|
|
65
|
+
return Array.isArray(reason) ? reason.join("\n") : reason;
|
|
62
66
|
}
|
|
@@ -7,16 +7,13 @@
|
|
|
7
7
|
* Interface is duck-typed so @naisys/common doesn't need a Fastify dependency.
|
|
8
8
|
*/
|
|
9
9
|
export function registerLenientJsonParser(fastify) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
);
|
|
10
|
+
fastify.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
11
|
+
try {
|
|
12
|
+
done(null, body.length > 0 ? JSON.parse(body) : {});
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
err.statusCode = 400;
|
|
16
|
+
done(err, undefined);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
22
19
|
}
|
package/dist/mimeTypes.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
const MIME_TYPES = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
2
|
+
// Images
|
|
3
|
+
".jpg": "image/jpeg",
|
|
4
|
+
".jpeg": "image/jpeg",
|
|
5
|
+
".png": "image/png",
|
|
6
|
+
".gif": "image/gif",
|
|
7
|
+
".webp": "image/webp",
|
|
8
|
+
".svg": "image/svg+xml",
|
|
9
|
+
".bmp": "image/bmp",
|
|
10
|
+
// Audio
|
|
11
|
+
".wav": "audio/wav",
|
|
12
|
+
".mp3": "audio/mpeg",
|
|
13
|
+
".m4a": "audio/mp4",
|
|
14
|
+
".flac": "audio/flac",
|
|
15
|
+
".ogg": "audio/ogg",
|
|
16
|
+
".webm": "audio/webm",
|
|
17
|
+
// Documents
|
|
18
|
+
".pdf": "application/pdf",
|
|
19
|
+
".txt": "text/plain",
|
|
20
|
+
".json": "application/json",
|
|
21
21
|
};
|
|
22
22
|
export function mimeFromFilename(filename) {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
24
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
25
25
|
}
|
package/dist/modelTypes.js
CHANGED
|
@@ -3,15 +3,15 @@ import { builtInImageModels, builtInLlmModels } from "./builtInModels.js";
|
|
|
3
3
|
// --- Enums ---
|
|
4
4
|
export var LlmApiType;
|
|
5
5
|
(function (LlmApiType) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
LlmApiType["OpenAI"] = "openai";
|
|
7
|
+
LlmApiType["Google"] = "google";
|
|
8
|
+
LlmApiType["Anthropic"] = "anthropic";
|
|
9
|
+
LlmApiType["Mock"] = "mock";
|
|
10
|
+
LlmApiType["None"] = "none";
|
|
11
11
|
})(LlmApiType || (LlmApiType = {}));
|
|
12
12
|
// --- Model schemas ---
|
|
13
13
|
export const LlmModelSchema = z
|
|
14
|
-
|
|
14
|
+
.object({
|
|
15
15
|
key: z.string().min(1),
|
|
16
16
|
label: z.string().min(1),
|
|
17
17
|
versionName: z.string().min(1),
|
|
@@ -27,119 +27,116 @@ export const LlmModelSchema = z
|
|
|
27
27
|
supportsVision: z.boolean().optional(),
|
|
28
28
|
supportsHearing: z.boolean().optional(),
|
|
29
29
|
supportsComputerUse: z.boolean().optional(),
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
code: z.ZodIssueCode.custom,
|
|
40
|
-
message: `baseUrl is only supported for OpenAI, Anthropic, and Google API types (got "${data.apiType}")`,
|
|
41
|
-
path: ["baseUrl"],
|
|
42
|
-
});
|
|
30
|
+
})
|
|
31
|
+
.superRefine((data, ctx) => {
|
|
32
|
+
if (data.baseUrl &&
|
|
33
|
+
![LlmApiType.OpenAI, LlmApiType.Anthropic, LlmApiType.Google].includes(data.apiType)) {
|
|
34
|
+
ctx.addIssue({
|
|
35
|
+
code: z.ZodIssueCode.custom,
|
|
36
|
+
message: `baseUrl is only supported for OpenAI, Anthropic, and Google API types (got "${data.apiType}")`,
|
|
37
|
+
path: ["baseUrl"],
|
|
38
|
+
});
|
|
43
39
|
}
|
|
44
|
-
|
|
40
|
+
});
|
|
45
41
|
export const ImageModelSchema = z.object({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
key: z.string().min(1),
|
|
43
|
+
label: z.string().min(1),
|
|
44
|
+
versionName: z.string().min(1),
|
|
45
|
+
size: z.string().min(1),
|
|
46
|
+
baseUrl: z.string().optional(),
|
|
47
|
+
apiKeyVar: z.string(),
|
|
48
|
+
cost: z.number(),
|
|
49
|
+
quality: z.enum(["standard", "hd", "high", "medium", "low"]).optional(),
|
|
54
50
|
});
|
|
55
51
|
// --- Custom models file schema ---
|
|
56
52
|
export const CustomModelsFileSchema = z.object({
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
llmModels: z.array(LlmModelSchema).optional(),
|
|
54
|
+
imageModels: z.array(ImageModelSchema).optional(),
|
|
59
55
|
});
|
|
60
56
|
// --- DB meta schemas (for JSON stored in models.meta column) ---
|
|
61
57
|
const LlmMetaSchema = z.object({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
apiType: z.enum(LlmApiType),
|
|
59
|
+
maxTokens: z.number().int().positive(),
|
|
60
|
+
baseUrl: z.string().optional(),
|
|
61
|
+
apiKeyVar: z.string(),
|
|
62
|
+
inputCost: z.number().default(0),
|
|
63
|
+
outputCost: z.number().default(0),
|
|
64
|
+
cacheWriteCost: z.number().optional(),
|
|
65
|
+
cacheReadCost: z.number().optional(),
|
|
66
|
+
cacheTtlSeconds: z.number().int().positive().optional(),
|
|
67
|
+
supportsVision: z.boolean().optional(),
|
|
68
|
+
supportsHearing: z.boolean().optional(),
|
|
69
|
+
supportsComputerUse: z.boolean().optional(),
|
|
74
70
|
});
|
|
75
71
|
const ImageMetaSchema = z.object({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
size: z.string().min(1),
|
|
73
|
+
baseUrl: z.string().optional(),
|
|
74
|
+
apiKeyVar: z.string(),
|
|
75
|
+
cost: z.number(),
|
|
76
|
+
quality: z.enum(["standard", "hd", "high", "medium", "low"]).optional(),
|
|
81
77
|
});
|
|
82
78
|
export function llmModelToDbFields(model, isBuiltin, isCustom) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
const { key, label, versionName, ...metaFields } = model;
|
|
80
|
+
return {
|
|
81
|
+
key,
|
|
82
|
+
type: "llm",
|
|
83
|
+
label,
|
|
84
|
+
version_name: versionName,
|
|
85
|
+
is_builtin: isBuiltin,
|
|
86
|
+
is_custom: isCustom,
|
|
87
|
+
meta: JSON.stringify(metaFields),
|
|
88
|
+
};
|
|
93
89
|
}
|
|
94
90
|
export function imageModelToDbFields(model, isBuiltin, isCustom) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
const { key, label, versionName, ...metaFields } = model;
|
|
92
|
+
return {
|
|
93
|
+
key,
|
|
94
|
+
type: "image",
|
|
95
|
+
label,
|
|
96
|
+
version_name: versionName,
|
|
97
|
+
is_builtin: isBuiltin,
|
|
98
|
+
is_custom: isCustom,
|
|
99
|
+
meta: JSON.stringify(metaFields),
|
|
100
|
+
};
|
|
105
101
|
}
|
|
106
102
|
export function dbFieldsToLlmModel(row) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
const meta = LlmMetaSchema.parse(JSON.parse(row.meta));
|
|
104
|
+
return {
|
|
105
|
+
key: row.key,
|
|
106
|
+
label: row.label,
|
|
107
|
+
versionName: row.version_name,
|
|
108
|
+
...meta,
|
|
109
|
+
};
|
|
114
110
|
}
|
|
115
111
|
export function dbFieldsToImageModel(row) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
const meta = ImageMetaSchema.parse(JSON.parse(row.meta));
|
|
113
|
+
return {
|
|
114
|
+
key: row.key,
|
|
115
|
+
label: row.label,
|
|
116
|
+
versionName: row.version_name,
|
|
117
|
+
...meta,
|
|
118
|
+
};
|
|
123
119
|
}
|
|
124
120
|
// --- Merge helpers ---
|
|
125
121
|
function mergeModels(builtIn, custom) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
122
|
+
if (!custom || custom.length === 0) {
|
|
123
|
+
return [...builtIn];
|
|
124
|
+
}
|
|
125
|
+
const result = [...builtIn];
|
|
126
|
+
for (const c of custom) {
|
|
127
|
+
const idx = result.findIndex((m) => m.key === c.key);
|
|
128
|
+
if (idx >= 0) {
|
|
129
|
+
result[idx] = c;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
result.push(c);
|
|
133
|
+
}
|
|
136
134
|
}
|
|
137
|
-
|
|
138
|
-
return result;
|
|
135
|
+
return result;
|
|
139
136
|
}
|
|
140
137
|
export function getAllLlmModels(customLlmModels) {
|
|
141
|
-
|
|
138
|
+
return mergeModels(builtInLlmModels, customLlmModels);
|
|
142
139
|
}
|
|
143
140
|
export function getAllImageModels(customImageModels) {
|
|
144
|
-
|
|
141
|
+
return mergeModels(builtInImageModels, customImageModels);
|
|
145
142
|
}
|
package/dist/securityHeaders.js
CHANGED
|
@@ -4,19 +4,13 @@
|
|
|
4
4
|
* Interfaces are duck-typed so @naisys/common doesn't need a Fastify dependency.
|
|
5
5
|
*/
|
|
6
6
|
export function registerSecurityHeaders(fastify, options) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"Strict-Transport-Security",
|
|
17
|
-
"max-age=31536000; includeSubDomains",
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
done();
|
|
21
|
-
});
|
|
7
|
+
fastify.addHook("onSend", (_request, reply, _payload, done) => {
|
|
8
|
+
reply.header("X-Content-Type-Options", "nosniff");
|
|
9
|
+
reply.header("X-Frame-Options", "DENY");
|
|
10
|
+
reply.header("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; font-src 'self' data:; frame-ancestors 'none'");
|
|
11
|
+
if (options.enforceHsts) {
|
|
12
|
+
reply.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
13
|
+
}
|
|
14
|
+
done();
|
|
15
|
+
});
|
|
22
16
|
}
|
package/dist/sleep.js
CHANGED
package/dist/urlSafeKey.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/** Characters allowed in URL path segments used as database keys (usernames, hostnames). */
|
|
2
2
|
export const URL_SAFE_KEY_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
3
|
-
export const URL_SAFE_KEY_MESSAGE =
|
|
4
|
-
"Must contain only letters, numbers, hyphens, and underscores";
|
|
3
|
+
export const URL_SAFE_KEY_MESSAGE = "Must contain only letters, numbers, hyphens, and underscores";
|
|
5
4
|
/** Sanitize a string into a URL-safe key (replace spaces/special chars with hyphens). */
|
|
6
5
|
export function toUrlSafeKey(input) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
return input
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/[^a-zA-Z0-9_-]/g, "-")
|
|
9
|
+
.replace(/-{2,}/g, "-")
|
|
10
|
+
.replace(/^-+|-+$/g, "");
|
|
12
11
|
}
|
|
13
12
|
/** Throws if the value is not a valid URL-safe key. */
|
|
14
13
|
export function assertUrlSafeKey(value, label) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
19
|
-
}
|
|
14
|
+
if (!URL_SAFE_KEY_REGEX.test(value)) {
|
|
15
|
+
throw new Error(`${label} "${value}" is not URL-safe. ${URL_SAFE_KEY_MESSAGE}`);
|
|
16
|
+
}
|
|
20
17
|
}
|