@naisys/common 3.0.0-beta.4 → 3.0.0-beta.5
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 +134 -106
- package/dist/agentStatus.js +9 -13
- package/dist/authCache.js +36 -37
- package/dist/builtInModels.js +251 -251
- package/dist/configUtils.js +6 -7
- package/dist/costUtils.js +19 -15
- package/dist/errorHandler.js +36 -29
- package/dist/formatFileSize.js +3 -5
- package/dist/globalConfigLoader.js +45 -45
- package/dist/hateoas-types.js +29 -29
- package/dist/hateoas.js +43 -47
- package/dist/lenientJsonParser.js +12 -9
- package/dist/mimeTypes.js +21 -21
- package/dist/modelTypes.js +95 -92
- package/dist/securityHeaders.js +15 -9
- package/dist/sleep.js +1 -1
- package/dist/urlSafeKey.js +12 -9
- package/package.json +4 -2
- package/dist/agentConfigFile.d.ts +0 -62
- package/dist/agentStatus.d.ts +0 -9
- package/dist/authCache.d.ts +0 -23
- package/dist/builtInModels.d.ts +0 -3
- package/dist/configUtils.d.ts +0 -1
- package/dist/constants.d.ts +0 -4
- package/dist/costUtils.d.ts +0 -10
- package/dist/errorHandler.d.ts +0 -28
- package/dist/formatFileSize.d.ts +0 -4
- package/dist/globalConfigLoader.d.ts +0 -25
- package/dist/hateoas-types.d.ts +0 -76
- package/dist/hateoas.d.ts +0 -35
- package/dist/hostedServices.d.ts +0 -14
- package/dist/index.d.ts +0 -19
- package/dist/lenientJsonParser.d.ts +0 -15
- package/dist/mimeTypes.d.ts +0 -1
- package/dist/modelTypes.d.ts +0 -107
- package/dist/securityHeaders.d.ts +0 -14
- package/dist/sleep.d.ts +0 -2
- package/dist/urlSafeKey.d.ts +0 -7
|
@@ -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
|
-
|
|
37
|
-
}
|
|
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
|
+
}
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
38
|
+
}
|
|
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,63 +4,59 @@
|
|
|
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
|
-
|
|
11
|
-
return undefined;
|
|
12
|
-
return a;
|
|
7
|
+
const a = actions?.find((a) => a.rel === rel);
|
|
8
|
+
if (!a) return undefined;
|
|
9
|
+
if (a.disabled && !opts?.includeDisabled) return undefined;
|
|
10
|
+
return a;
|
|
13
11
|
}
|
|
14
12
|
export function hasActionTemplate(templates, rel) {
|
|
15
|
-
|
|
13
|
+
return templates?.find((t) => t.rel === rel);
|
|
16
14
|
}
|
|
17
15
|
export function hasLinkTemplate(templates, rel) {
|
|
18
|
-
|
|
16
|
+
return templates?.find((t) => t.rel === rel);
|
|
19
17
|
}
|
|
20
18
|
export function resolveActions(defs, baseHref, ctx, checkPermission) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
});
|
|
19
|
+
const actions = [];
|
|
20
|
+
for (const def of defs) {
|
|
21
|
+
if (def.statuses) {
|
|
22
|
+
const status = ctx.status;
|
|
23
|
+
if (!status || !def.statuses.includes(status)) continue;
|
|
50
24
|
}
|
|
51
|
-
|
|
25
|
+
if (def.visibleWhen && !def.visibleWhen(ctx)) continue;
|
|
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;
|
|
52
49
|
}
|
|
53
50
|
export function permGate(hasPerm, permission) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
return hasPerm
|
|
52
|
+
? {}
|
|
53
|
+
: {
|
|
54
|
+
disabled: true,
|
|
55
|
+
disabledReason: `Requires ${permission} permission`,
|
|
56
|
+
};
|
|
60
57
|
}
|
|
61
58
|
/** Normalize a `disabledReason` (string | string[] | undefined) to a single display string. */
|
|
62
59
|
export function formatDisabledReason(reason) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return Array.isArray(reason) ? reason.join("\n") : reason;
|
|
60
|
+
if (!reason) return undefined;
|
|
61
|
+
return Array.isArray(reason) ? reason.join("\n") : reason;
|
|
66
62
|
}
|
|
@@ -7,13 +7,16 @@
|
|
|
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
|
-
|
|
10
|
+
fastify.addContentTypeParser(
|
|
11
|
+
"application/json",
|
|
12
|
+
{ parseAs: "string" },
|
|
13
|
+
(_req, body, done) => {
|
|
14
|
+
try {
|
|
15
|
+
done(null, body.length > 0 ? JSON.parse(body) : {});
|
|
16
|
+
} catch (err) {
|
|
17
|
+
err.statusCode = 400;
|
|
18
|
+
done(err, undefined);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
);
|
|
19
22
|
}
|
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,116 +27,119 @@ 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
|
-
|
|
30
|
+
})
|
|
31
|
+
.superRefine((data, ctx) => {
|
|
32
|
+
if (
|
|
33
|
+
data.baseUrl &&
|
|
34
|
+
![LlmApiType.OpenAI, LlmApiType.Anthropic, LlmApiType.Google].includes(
|
|
35
|
+
data.apiType,
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
ctx.addIssue({
|
|
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
|
+
});
|
|
39
43
|
}
|
|
40
|
-
});
|
|
44
|
+
});
|
|
41
45
|
export const ImageModelSchema = z.object({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
key: z.string().min(1),
|
|
47
|
+
label: z.string().min(1),
|
|
48
|
+
versionName: z.string().min(1),
|
|
49
|
+
size: z.string().min(1),
|
|
50
|
+
baseUrl: z.string().optional(),
|
|
51
|
+
apiKeyVar: z.string(),
|
|
52
|
+
cost: z.number(),
|
|
53
|
+
quality: z.enum(["standard", "hd", "high", "medium", "low"]).optional(),
|
|
50
54
|
});
|
|
51
55
|
// --- Custom models file schema ---
|
|
52
56
|
export const CustomModelsFileSchema = z.object({
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
llmModels: z.array(LlmModelSchema).optional(),
|
|
58
|
+
imageModels: z.array(ImageModelSchema).optional(),
|
|
55
59
|
});
|
|
56
60
|
// --- DB meta schemas (for JSON stored in models.meta column) ---
|
|
57
61
|
const LlmMetaSchema = z.object({
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
apiType: z.enum(LlmApiType),
|
|
63
|
+
maxTokens: z.number().int().positive(),
|
|
64
|
+
baseUrl: z.string().optional(),
|
|
65
|
+
apiKeyVar: z.string(),
|
|
66
|
+
inputCost: z.number().default(0),
|
|
67
|
+
outputCost: z.number().default(0),
|
|
68
|
+
cacheWriteCost: z.number().optional(),
|
|
69
|
+
cacheReadCost: z.number().optional(),
|
|
70
|
+
cacheTtlSeconds: z.number().int().positive().optional(),
|
|
71
|
+
supportsVision: z.boolean().optional(),
|
|
72
|
+
supportsHearing: z.boolean().optional(),
|
|
73
|
+
supportsComputerUse: z.boolean().optional(),
|
|
70
74
|
});
|
|
71
75
|
const ImageMetaSchema = z.object({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
size: z.string().min(1),
|
|
77
|
+
baseUrl: z.string().optional(),
|
|
78
|
+
apiKeyVar: z.string(),
|
|
79
|
+
cost: z.number(),
|
|
80
|
+
quality: z.enum(["standard", "hd", "high", "medium", "low"]).optional(),
|
|
77
81
|
});
|
|
78
82
|
export function llmModelToDbFields(model, isBuiltin, isCustom) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const { key, label, versionName, ...metaFields } = model;
|
|
84
|
+
return {
|
|
85
|
+
key,
|
|
86
|
+
type: "llm",
|
|
87
|
+
label,
|
|
88
|
+
version_name: versionName,
|
|
89
|
+
is_builtin: isBuiltin,
|
|
90
|
+
is_custom: isCustom,
|
|
91
|
+
meta: JSON.stringify(metaFields),
|
|
92
|
+
};
|
|
89
93
|
}
|
|
90
94
|
export function imageModelToDbFields(model, isBuiltin, isCustom) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
const { key, label, versionName, ...metaFields } = model;
|
|
96
|
+
return {
|
|
97
|
+
key,
|
|
98
|
+
type: "image",
|
|
99
|
+
label,
|
|
100
|
+
version_name: versionName,
|
|
101
|
+
is_builtin: isBuiltin,
|
|
102
|
+
is_custom: isCustom,
|
|
103
|
+
meta: JSON.stringify(metaFields),
|
|
104
|
+
};
|
|
101
105
|
}
|
|
102
106
|
export function dbFieldsToLlmModel(row) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
const meta = LlmMetaSchema.parse(JSON.parse(row.meta));
|
|
108
|
+
return {
|
|
109
|
+
key: row.key,
|
|
110
|
+
label: row.label,
|
|
111
|
+
versionName: row.version_name,
|
|
112
|
+
...meta,
|
|
113
|
+
};
|
|
110
114
|
}
|
|
111
115
|
export function dbFieldsToImageModel(row) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
const meta = ImageMetaSchema.parse(JSON.parse(row.meta));
|
|
117
|
+
return {
|
|
118
|
+
key: row.key,
|
|
119
|
+
label: row.label,
|
|
120
|
+
versionName: row.version_name,
|
|
121
|
+
...meta,
|
|
122
|
+
};
|
|
119
123
|
}
|
|
120
124
|
// --- Merge helpers ---
|
|
121
125
|
function mergeModels(builtIn, custom) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
result.push(c);
|
|
133
|
-
}
|
|
126
|
+
if (!custom || custom.length === 0) {
|
|
127
|
+
return [...builtIn];
|
|
128
|
+
}
|
|
129
|
+
const result = [...builtIn];
|
|
130
|
+
for (const c of custom) {
|
|
131
|
+
const idx = result.findIndex((m) => m.key === c.key);
|
|
132
|
+
if (idx >= 0) {
|
|
133
|
+
result[idx] = c;
|
|
134
|
+
} else {
|
|
135
|
+
result.push(c);
|
|
134
136
|
}
|
|
135
|
-
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
136
139
|
}
|
|
137
140
|
export function getAllLlmModels(customLlmModels) {
|
|
138
|
-
|
|
141
|
+
return mergeModels(builtInLlmModels, customLlmModels);
|
|
139
142
|
}
|
|
140
143
|
export function getAllImageModels(customImageModels) {
|
|
141
|
-
|
|
144
|
+
return mergeModels(builtInImageModels, customImageModels);
|
|
142
145
|
}
|
package/dist/securityHeaders.js
CHANGED
|
@@ -4,13 +4,19 @@
|
|
|
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
|
-
|
|
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(
|
|
11
|
+
"Content-Security-Policy",
|
|
12
|
+
"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'",
|
|
13
|
+
);
|
|
14
|
+
if (options.enforceHsts) {
|
|
15
|
+
reply.header(
|
|
16
|
+
"Strict-Transport-Security",
|
|
17
|
+
"max-age=31536000; includeSubDomains",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
done();
|
|
21
|
+
});
|
|
16
22
|
}
|
package/dist/sleep.js
CHANGED
package/dist/urlSafeKey.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
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 =
|
|
3
|
+
export const URL_SAFE_KEY_MESSAGE =
|
|
4
|
+
"Must contain only letters, numbers, hyphens, and underscores";
|
|
4
5
|
/** Sanitize a string into a URL-safe key (replace spaces/special chars with hyphens). */
|
|
5
6
|
export function toUrlSafeKey(input) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
return input
|
|
8
|
+
.trim()
|
|
9
|
+
.replace(/[^a-zA-Z0-9_-]/g, "-")
|
|
10
|
+
.replace(/-{2,}/g, "-")
|
|
11
|
+
.replace(/^-+|-+$/g, "");
|
|
11
12
|
}
|
|
12
13
|
/** Throws if the value is not a valid URL-safe key. */
|
|
13
14
|
export function assertUrlSafeKey(value, label) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
if (!URL_SAFE_KEY_REGEX.test(value)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`${label} "${value}" is not URL-safe. ${URL_SAFE_KEY_MESSAGE}`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
17
20
|
}
|