@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
package/dist/builtInModels.js
CHANGED
|
@@ -2,259 +2,259 @@ import { LlmApiType } from "./modelTypes.js";
|
|
|
2
2
|
// --- Built-in LLM models ---
|
|
3
3
|
// Prices are per 1M tokens in USD. Last updated: February 2026.
|
|
4
4
|
export const builtInLlmModels = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
5
|
+
{
|
|
6
|
+
key: LlmApiType.None,
|
|
7
|
+
label: "None",
|
|
8
|
+
versionName: LlmApiType.None,
|
|
9
|
+
apiType: LlmApiType.None,
|
|
10
|
+
apiKeyVar: "",
|
|
11
|
+
maxTokens: 10_000,
|
|
12
|
+
inputCost: 0,
|
|
13
|
+
outputCost: 0,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: LlmApiType.Mock,
|
|
17
|
+
label: "Mock",
|
|
18
|
+
versionName: LlmApiType.Mock,
|
|
19
|
+
apiType: LlmApiType.Mock,
|
|
20
|
+
apiKeyVar: "",
|
|
21
|
+
maxTokens: 10_000,
|
|
22
|
+
inputCost: 0,
|
|
23
|
+
outputCost: 0,
|
|
24
|
+
},
|
|
25
|
+
// ── Open Router ──────────────────────────────────────────────────────
|
|
26
|
+
{
|
|
27
|
+
key: "llama4",
|
|
28
|
+
label: "Llama 4 Maverick",
|
|
29
|
+
versionName: "meta-llama/llama-4-maverick",
|
|
30
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
31
|
+
apiType: LlmApiType.OpenAI,
|
|
32
|
+
apiKeyVar: "OPENROUTER_API_KEY",
|
|
33
|
+
maxTokens: 1_000_000,
|
|
34
|
+
inputCost: 0.15,
|
|
35
|
+
outputCost: 0.6,
|
|
36
|
+
supportsVision: true,
|
|
37
|
+
},
|
|
38
|
+
// ── xAI / Grok ──────────────────────────────────────────────────────
|
|
39
|
+
// https://docs.x.ai/developers/models
|
|
40
|
+
{
|
|
41
|
+
key: "grok4",
|
|
42
|
+
label: "Grok 4",
|
|
43
|
+
versionName: "grok-4",
|
|
44
|
+
baseUrl: "https://api.x.ai/v1",
|
|
45
|
+
apiType: LlmApiType.OpenAI,
|
|
46
|
+
apiKeyVar: "XAI_API_KEY",
|
|
47
|
+
maxTokens: 256_000,
|
|
48
|
+
inputCost: 3,
|
|
49
|
+
outputCost: 15,
|
|
50
|
+
cacheWriteCost: 0.75,
|
|
51
|
+
cacheReadCost: 0.75,
|
|
52
|
+
cacheTtlSeconds: 300,
|
|
53
|
+
supportsVision: true,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "grok4fast",
|
|
57
|
+
label: "Grok 4.1 Fast",
|
|
58
|
+
versionName: "grok-4.1-fast",
|
|
59
|
+
baseUrl: "https://api.x.ai/v1",
|
|
60
|
+
apiType: LlmApiType.OpenAI,
|
|
61
|
+
apiKeyVar: "XAI_API_KEY",
|
|
62
|
+
maxTokens: 2_000_000,
|
|
63
|
+
inputCost: 0.2,
|
|
64
|
+
outputCost: 0.5,
|
|
65
|
+
cacheWriteCost: 0.05,
|
|
66
|
+
cacheReadCost: 0.05,
|
|
67
|
+
cacheTtlSeconds: 300,
|
|
68
|
+
supportsVision: true,
|
|
69
|
+
},
|
|
70
|
+
// ── OpenAI Models ────────────────────────────────────────────────────
|
|
71
|
+
// https://openai.com/api/pricing/
|
|
72
|
+
{
|
|
73
|
+
key: "gpt5",
|
|
74
|
+
label: "GPT 5.4",
|
|
75
|
+
versionName: "gpt-5.4",
|
|
76
|
+
apiType: LlmApiType.OpenAI,
|
|
77
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
78
|
+
maxTokens: 400_000,
|
|
79
|
+
inputCost: 2.5,
|
|
80
|
+
outputCost: 15.0,
|
|
81
|
+
cacheWriteCost: 0.25,
|
|
82
|
+
cacheReadCost: 0.25,
|
|
83
|
+
cacheTtlSeconds: 300,
|
|
84
|
+
supportsVision: true,
|
|
85
|
+
supportsComputerUse: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "gpt5mini",
|
|
89
|
+
label: "GPT 5 Mini",
|
|
90
|
+
versionName: "gpt-5-mini",
|
|
91
|
+
apiType: LlmApiType.OpenAI,
|
|
92
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
93
|
+
maxTokens: 400_000,
|
|
94
|
+
inputCost: 0.25,
|
|
95
|
+
outputCost: 2.0,
|
|
96
|
+
cacheWriteCost: 0.025,
|
|
97
|
+
cacheReadCost: 0.025,
|
|
98
|
+
cacheTtlSeconds: 300,
|
|
99
|
+
supportsVision: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: "gpt5nano",
|
|
103
|
+
label: "GPT 5 Nano",
|
|
104
|
+
versionName: "gpt-5-nano",
|
|
105
|
+
apiType: LlmApiType.OpenAI,
|
|
106
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
107
|
+
maxTokens: 400_000,
|
|
108
|
+
inputCost: 0.05,
|
|
109
|
+
outputCost: 0.4,
|
|
110
|
+
cacheWriteCost: 0.005,
|
|
111
|
+
cacheReadCost: 0.005,
|
|
112
|
+
cacheTtlSeconds: 300,
|
|
113
|
+
supportsVision: true,
|
|
114
|
+
},
|
|
115
|
+
// ── Google Models ────────────────────────────────────────────────────
|
|
116
|
+
// https://ai.google.dev/gemini-api/docs/pricing
|
|
117
|
+
{
|
|
118
|
+
key: "gemini3pro",
|
|
119
|
+
label: "Gemini 3.1 Pro",
|
|
120
|
+
versionName: "gemini-3.1-pro-preview",
|
|
121
|
+
apiType: LlmApiType.Google,
|
|
122
|
+
apiKeyVar: "GOOGLE_API_KEY",
|
|
123
|
+
maxTokens: 2_000_000,
|
|
124
|
+
inputCost: 2.0,
|
|
125
|
+
outputCost: 12.0,
|
|
126
|
+
cacheWriteCost: 0.2,
|
|
127
|
+
cacheReadCost: 0.2,
|
|
128
|
+
cacheTtlSeconds: 300,
|
|
129
|
+
supportsVision: true,
|
|
130
|
+
supportsHearing: true,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "gemini3flash",
|
|
134
|
+
label: "Gemini 3 Flash",
|
|
135
|
+
versionName: "gemini-3-flash-preview",
|
|
136
|
+
apiType: LlmApiType.Google,
|
|
137
|
+
apiKeyVar: "GOOGLE_API_KEY",
|
|
138
|
+
maxTokens: 1_000_000,
|
|
139
|
+
inputCost: 0.5,
|
|
140
|
+
outputCost: 3.0,
|
|
141
|
+
cacheWriteCost: 0.05,
|
|
142
|
+
cacheReadCost: 0.05,
|
|
143
|
+
cacheTtlSeconds: 300,
|
|
144
|
+
supportsVision: true,
|
|
145
|
+
supportsHearing: true,
|
|
146
|
+
supportsComputerUse: true,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: "gemini2pro",
|
|
150
|
+
label: "Gemini 2 Pro",
|
|
151
|
+
versionName: "gemini-2.5-computer-use-preview-10-2025",
|
|
152
|
+
apiType: LlmApiType.Google,
|
|
153
|
+
apiKeyVar: "GOOGLE_API_KEY",
|
|
154
|
+
maxTokens: 2_000_000,
|
|
155
|
+
inputCost: 2.0,
|
|
156
|
+
outputCost: 12.0,
|
|
157
|
+
cacheWriteCost: 0.2,
|
|
158
|
+
cacheReadCost: 0.2,
|
|
159
|
+
cacheTtlSeconds: 300,
|
|
160
|
+
supportsVision: true,
|
|
161
|
+
supportsHearing: true,
|
|
162
|
+
supportsComputerUse: true,
|
|
163
|
+
},
|
|
164
|
+
// ── Anthropic Models ─────────────────────────────────────────────────
|
|
165
|
+
// https://platform.claude.com/docs/en/about-claude/pricing
|
|
166
|
+
// Cache: 5m write = 1.25× input, read = 0.1× input
|
|
167
|
+
{
|
|
168
|
+
key: "claude4opus",
|
|
169
|
+
label: "Claude Opus 4.6",
|
|
170
|
+
versionName: "claude-opus-4-6",
|
|
171
|
+
apiType: LlmApiType.Anthropic,
|
|
172
|
+
apiKeyVar: "ANTHROPIC_API_KEY",
|
|
173
|
+
maxTokens: 200_000,
|
|
174
|
+
inputCost: 5,
|
|
175
|
+
outputCost: 25,
|
|
176
|
+
cacheWriteCost: 6.25,
|
|
177
|
+
cacheReadCost: 0.5,
|
|
178
|
+
cacheTtlSeconds: 300,
|
|
179
|
+
supportsVision: true,
|
|
180
|
+
supportsComputerUse: true,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: "claude4sonnet",
|
|
184
|
+
label: "Claude Sonnet 4.6",
|
|
185
|
+
versionName: "claude-sonnet-4-6",
|
|
186
|
+
apiType: LlmApiType.Anthropic,
|
|
187
|
+
apiKeyVar: "ANTHROPIC_API_KEY",
|
|
188
|
+
maxTokens: 200_000,
|
|
189
|
+
inputCost: 3,
|
|
190
|
+
outputCost: 15,
|
|
191
|
+
cacheWriteCost: 3.75,
|
|
192
|
+
cacheReadCost: 0.3,
|
|
193
|
+
cacheTtlSeconds: 300,
|
|
194
|
+
supportsVision: true,
|
|
195
|
+
supportsComputerUse: true,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
key: "claude4haiku",
|
|
199
|
+
label: "Claude Haiku 4.5",
|
|
200
|
+
versionName: "claude-haiku-4-5",
|
|
201
|
+
apiType: LlmApiType.Anthropic,
|
|
202
|
+
apiKeyVar: "ANTHROPIC_API_KEY",
|
|
203
|
+
maxTokens: 200_000,
|
|
204
|
+
inputCost: 1,
|
|
205
|
+
outputCost: 5,
|
|
206
|
+
cacheWriteCost: 1.25,
|
|
207
|
+
cacheReadCost: 0.1,
|
|
208
|
+
cacheTtlSeconds: 300,
|
|
209
|
+
supportsVision: true,
|
|
210
|
+
supportsComputerUse: true,
|
|
211
|
+
},
|
|
212
212
|
];
|
|
213
213
|
// --- Built-in image models ---
|
|
214
214
|
// Costs are approximate per-image for 1024x1024.
|
|
215
215
|
export const builtInImageModels = [
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
216
|
+
{
|
|
217
|
+
key: "gptimage1high",
|
|
218
|
+
label: "GPT Image 1.5 High",
|
|
219
|
+
versionName: "gpt-image-1.5",
|
|
220
|
+
size: "1024x1024",
|
|
221
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
222
|
+
quality: "high",
|
|
223
|
+
cost: 0.17,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
key: "gptimage1medium",
|
|
227
|
+
label: "GPT Image 1.5 Medium",
|
|
228
|
+
versionName: "gpt-image-1.5",
|
|
229
|
+
size: "1024x1024",
|
|
230
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
231
|
+
quality: "medium",
|
|
232
|
+
cost: 0.04,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
key: "gptimage1low",
|
|
236
|
+
label: "GPT Image 1.5 Low",
|
|
237
|
+
versionName: "gpt-image-1.5",
|
|
238
|
+
size: "1024x1024",
|
|
239
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
240
|
+
quality: "low",
|
|
241
|
+
cost: 0.01,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
key: "dalle3-1024-HD",
|
|
245
|
+
label: "DALL-E 3 1024 HD",
|
|
246
|
+
versionName: "dall-e-3",
|
|
247
|
+
size: "1024x1024",
|
|
248
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
249
|
+
quality: "hd",
|
|
250
|
+
cost: 0.08,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
key: "dalle3-1024",
|
|
254
|
+
label: "DALL-E 3 1024",
|
|
255
|
+
versionName: "dall-e-3",
|
|
256
|
+
size: "1024x1024",
|
|
257
|
+
apiKeyVar: "OPENAI_API_KEY",
|
|
258
|
+
cost: 0.04,
|
|
259
|
+
},
|
|
260
260
|
];
|
package/dist/configUtils.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export function sanitizeSpendLimit(num) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
if (num === undefined)
|
|
3
|
+
return undefined;
|
|
4
|
+
const n = Number(num);
|
|
5
|
+
if (isNaN(n) || n <= 0) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
return n;
|
|
8
9
|
}
|
package/dist/costUtils.js
CHANGED
|
@@ -5,23 +5,19 @@
|
|
|
5
5
|
* only for the window to close again, and the llm cache to *expire* creating a cycle of constant cache misses
|
|
6
6
|
*/
|
|
7
7
|
export function calculatePeriodBoundaries(hours) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
midnight.getTime() +
|
|
22
|
-
|
|
23
|
-
const periodEnd = new Date(
|
|
24
|
-
midnight.getTime() + periodEndHours * 60 * 60 * 1000,
|
|
25
|
-
);
|
|
26
|
-
return { periodStart, periodEnd };
|
|
8
|
+
const now = new Date();
|
|
9
|
+
// Get midnight of current day in local time
|
|
10
|
+
const midnight = new Date(now);
|
|
11
|
+
midnight.setHours(0, 0, 0, 0);
|
|
12
|
+
// Calculate milliseconds since midnight
|
|
13
|
+
const msSinceMidnight = now.getTime() - midnight.getTime();
|
|
14
|
+
const hoursSinceMidnight = msSinceMidnight / (1000 * 60 * 60);
|
|
15
|
+
// Calculate which period we're in (0, 1, 2, ...)
|
|
16
|
+
const periodIndex = Math.floor(hoursSinceMidnight / hours);
|
|
17
|
+
// Calculate period start and end
|
|
18
|
+
const periodStartHours = periodIndex * hours;
|
|
19
|
+
const periodEndHours = (periodIndex + 1) * hours;
|
|
20
|
+
const periodStart = new Date(midnight.getTime() + periodStartHours * 60 * 60 * 1000);
|
|
21
|
+
const periodEnd = new Date(midnight.getTime() + periodEndHours * 60 * 60 * 1000);
|
|
22
|
+
return { periodStart, periodEnd };
|
|
27
23
|
}
|
package/dist/errorHandler.js
CHANGED
|
@@ -7,45 +7,38 @@
|
|
|
7
7
|
* Interfaces are duck-typed so @naisys/common doesn't need a Fastify dependency.
|
|
8
8
|
*/
|
|
9
9
|
function errorLabel(statusCode) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
switch (statusCode) {
|
|
11
|
+
case 400:
|
|
12
|
+
return "Bad Request";
|
|
13
|
+
case 401:
|
|
14
|
+
return "Unauthorized";
|
|
15
|
+
case 403:
|
|
16
|
+
return "Forbidden";
|
|
17
|
+
case 404:
|
|
18
|
+
return "Not Found";
|
|
19
|
+
case 409:
|
|
20
|
+
return "Conflict";
|
|
21
|
+
default:
|
|
22
|
+
return statusCode >= 500 ? "Internal Server Error" : "Error";
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
25
|
export function commonErrorHandler(error, request, reply) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// Pre-stringify to bypass the route's Zod response serializer
|
|
41
|
-
reply
|
|
42
|
-
.status(statusCode)
|
|
43
|
-
.header("content-type", "application/json; charset=utf-8")
|
|
44
|
-
.send(
|
|
45
|
-
JSON.stringify({
|
|
26
|
+
let statusCode = error.statusCode ?? 500;
|
|
27
|
+
let message = error.message;
|
|
28
|
+
// Prisma unique-constraint violation → 409 Conflict
|
|
29
|
+
if (error.name === "PrismaClientKnownRequestError" &&
|
|
30
|
+
error.code === "P2002") {
|
|
31
|
+
statusCode = 409;
|
|
32
|
+
message = "A record with that unique value already exists";
|
|
33
|
+
}
|
|
34
|
+
request.log.error({ err: error, url: request.url, method: request.method }, "Request error");
|
|
35
|
+
// Pre-stringify to bypass the route's Zod response serializer
|
|
36
|
+
reply
|
|
37
|
+
.status(statusCode)
|
|
38
|
+
.header("content-type", "application/json; charset=utf-8")
|
|
39
|
+
.send(JSON.stringify({
|
|
46
40
|
statusCode,
|
|
47
41
|
error: errorLabel(statusCode),
|
|
48
42
|
message,
|
|
49
|
-
|
|
50
|
-
);
|
|
43
|
+
}));
|
|
51
44
|
}
|
package/dist/formatFileSize.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
export const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
3
3
|
/** Format a byte count into a human-readable size string (e.g. "1.2 KB") */
|
|
4
4
|
export function formatFileSize(bytes) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
if (bytes < 1024)
|
|
6
|
+
return `${bytes} B`;
|
|
7
|
+
if (bytes < 1024 * 1024)
|
|
8
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
9
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8
10
|
}
|