@nanogpt/private-mode 0.1.4 → 0.2.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/README.md +2 -0
- package/lib/cacheScope.js +9 -0
- package/lib/originPolicy.js +19 -12
- package/lib/requestTransforms.js +27 -19
- package/lib/server.js +86 -30
- package/lib/serverErrorNormalization.js +19 -34
- package/models/private-tee.json +1 -22
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function buildPrivateModeCacheScopeProof(cacheScope) {
|
|
4
|
+
const normalized = String(cacheScope || '').trim();
|
|
5
|
+
if (!/^[a-f0-9]{64}$/i.test(normalized)) {
|
|
6
|
+
throw new Error('Cannot prove an invalid Private Mode cache scope.');
|
|
7
|
+
}
|
|
8
|
+
return createHash('sha256').update(normalized, 'utf8').digest('hex');
|
|
9
|
+
}
|
package/lib/originPolicy.js
CHANGED
|
@@ -28,20 +28,29 @@ function normalizeHostForOrigin(host) {
|
|
|
28
28
|
return trimmed;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function addAllowedOrigin(allowedOrigins, origin) {
|
|
32
|
+
const normalizedOrigin = normalizePrivateModeOrigin(origin);
|
|
33
|
+
if (normalizedOrigin) allowedOrigins.add(normalizedOrigin);
|
|
34
|
+
return normalizedOrigin;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function addDefaultLoopbackOrigins(allowedOrigins, host, port) {
|
|
38
|
+
for (const defaultHost of DEFAULT_LOCAL_HOSTS) {
|
|
39
|
+
allowedOrigins.add(`http://${defaultHost}:${port}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const normalizedHost = normalizeHostForOrigin(host);
|
|
43
|
+
if (normalizedHost) {
|
|
44
|
+
addAllowedOrigin(allowedOrigins, `http://${normalizedHost}:${port}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
export function buildPrivateModeOriginPolicy(options = {}) {
|
|
32
49
|
const port = Number.parseInt(String(options.port || '8787'), 10);
|
|
33
50
|
const allowedOrigins = new Set();
|
|
34
51
|
|
|
35
52
|
if (Number.isFinite(port) && port > 0) {
|
|
36
|
-
|
|
37
|
-
allowedOrigins.add(`http://${host}:${port}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const normalizedHost = normalizeHostForOrigin(options.host);
|
|
41
|
-
if (normalizedHost) {
|
|
42
|
-
const origin = normalizePrivateModeOrigin(`http://${normalizedHost}:${port}`);
|
|
43
|
-
if (origin) allowedOrigins.add(origin);
|
|
44
|
-
}
|
|
53
|
+
addDefaultLoopbackOrigins(allowedOrigins, options.host, port);
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
const configuredOrigins = [
|
|
@@ -50,11 +59,9 @@ export function buildPrivateModeOriginPolicy(options = {}) {
|
|
|
50
59
|
];
|
|
51
60
|
|
|
52
61
|
for (const configuredOrigin of configuredOrigins) {
|
|
53
|
-
|
|
54
|
-
if (!origin) {
|
|
62
|
+
if (!addAllowedOrigin(allowedOrigins, configuredOrigin)) {
|
|
55
63
|
throw new Error(`Invalid Private Mode browser origin "${configuredOrigin}". Use an http(s) origin, not a wildcard.`);
|
|
56
64
|
}
|
|
57
|
-
allowedOrigins.add(origin);
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
return {
|
package/lib/requestTransforms.js
CHANGED
|
@@ -6,6 +6,11 @@ const ASSISTANT_REASONING_MESSAGE_FIELDS = [
|
|
|
6
6
|
'reasoning_content',
|
|
7
7
|
'reasoning_details',
|
|
8
8
|
];
|
|
9
|
+
const MAX_TOKEN_ALIAS_FIELDS = [
|
|
10
|
+
'max_completion_tokens',
|
|
11
|
+
'maxCompletionTokens',
|
|
12
|
+
'maxTokens',
|
|
13
|
+
];
|
|
9
14
|
const PRIVATE_TINFOIL_CHAT_COMPLETION_BODY_FIELDS = new Set([
|
|
10
15
|
'chat_template_kwargs',
|
|
11
16
|
'frequency_penalty',
|
|
@@ -209,34 +214,37 @@ function applyTinfoilCompatibilityMutations(body, model) {
|
|
|
209
214
|
}
|
|
210
215
|
}
|
|
211
216
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (body.max_tokens === undefined && body.maxCompletionTokens !== undefined) {
|
|
219
|
-
body.max_tokens = body.maxCompletionTokens;
|
|
220
|
-
}
|
|
221
|
-
if (body.max_tokens === undefined && body.maxTokens !== undefined) {
|
|
222
|
-
body.max_tokens = body.maxTokens;
|
|
217
|
+
function normalizeMaxTokenAliases(body) {
|
|
218
|
+
for (const key of MAX_TOKEN_ALIAS_FIELDS) {
|
|
219
|
+
if (body.max_tokens === undefined && body[key] !== undefined) {
|
|
220
|
+
body.max_tokens = body[key];
|
|
221
|
+
}
|
|
222
|
+
delete body[key];
|
|
223
223
|
}
|
|
224
|
-
|
|
225
|
-
delete body.maxCompletionTokens;
|
|
226
|
-
delete body.maxTokens;
|
|
224
|
+
}
|
|
227
225
|
|
|
228
|
-
|
|
226
|
+
function normalizeStreamOptions(body) {
|
|
229
227
|
if (body.stream !== undefined) {
|
|
230
228
|
body.stream = body.stream === true;
|
|
231
229
|
}
|
|
230
|
+
|
|
232
231
|
if (body.stream === true) {
|
|
233
232
|
body.stream_options = {
|
|
234
233
|
...(body.stream_options && typeof body.stream_options === 'object' ? body.stream_options : {}),
|
|
235
234
|
include_usage: true,
|
|
236
235
|
};
|
|
237
|
-
|
|
238
|
-
delete body.stream_options;
|
|
236
|
+
return;
|
|
239
237
|
}
|
|
238
|
+
|
|
239
|
+
delete body.stream_options;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function applyPrivateModelRequestMutations(body, model) {
|
|
243
|
+
stripPrivateModeReasoningFromMessages(body);
|
|
244
|
+
normalizeMaxTokenAliases(body);
|
|
245
|
+
|
|
246
|
+
body.model = model.upstreamModel;
|
|
247
|
+
normalizeStreamOptions(body);
|
|
240
248
|
applyTinfoilCompatibilityMutations(body, model);
|
|
241
249
|
|
|
242
250
|
if (model.thinkingMode === 'gemma') {
|
|
@@ -246,14 +254,14 @@ export function applyPrivateModelRequestMutations(body, model) {
|
|
|
246
254
|
};
|
|
247
255
|
delete body.thinking;
|
|
248
256
|
delete body.reasoning_effort;
|
|
249
|
-
} else if (model.thinkingMode === '
|
|
257
|
+
} else if (model.thinkingMode === 'kimi-k2.6' || model.thinkingMode === 'glm-5.2') {
|
|
250
258
|
const thinkingEnabled = shouldEnableThinking(body, model);
|
|
251
259
|
body.chat_template_kwargs = {
|
|
252
260
|
...mergeChatTemplateKwargs(body),
|
|
253
261
|
thinking: thinkingEnabled,
|
|
254
262
|
};
|
|
255
263
|
|
|
256
|
-
if (thinkingEnabled) {
|
|
264
|
+
if (model.thinkingMode === 'glm-5.2' && thinkingEnabled) {
|
|
257
265
|
body.chat_template_kwargs.reasoning_effort =
|
|
258
266
|
normalizeDeepSeekV4ReasoningEffort(body.reasoning_effort);
|
|
259
267
|
} else {
|
package/lib/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
|
|
4
|
+
import { buildPrivateModeCacheScopeProof } from './cacheScope.js';
|
|
4
5
|
import {
|
|
5
6
|
buildPrivateModeOriginPolicy,
|
|
6
7
|
getCorsHeadersForRequest,
|
|
@@ -142,40 +143,49 @@ function readBody(req) {
|
|
|
142
143
|
|
|
143
144
|
function copyLocalHeaders(req) {
|
|
144
145
|
const headers = {};
|
|
145
|
-
const teamId = req
|
|
146
|
+
const teamId = req?.headers?.['x-team-id'];
|
|
146
147
|
if (typeof teamId === 'string' && teamId.trim()) {
|
|
147
148
|
headers['x-team-id'] = teamId.trim();
|
|
148
149
|
}
|
|
149
150
|
return headers;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
function createSecureState(apiBase) {
|
|
153
|
-
let
|
|
153
|
+
function createSecureState(apiBase, apiKey) {
|
|
154
|
+
let clientState = null;
|
|
154
155
|
let verificationDocument = null;
|
|
155
156
|
let verificationError = null;
|
|
156
157
|
|
|
157
|
-
async function getClient() {
|
|
158
|
-
if (!
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
158
|
+
async function getClient(userCacheSecret = clientState?.userCacheSecret) {
|
|
159
|
+
if (!userCacheSecret) {
|
|
160
|
+
throw new Error('NanoGPT preflight must establish cache isolation before attestation.');
|
|
161
|
+
}
|
|
162
|
+
if (clientState?.userCacheSecret === userCacheSecret) {
|
|
163
|
+
return clientState.promise;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const promise = import('tinfoil').then(async ({ SecureClient }) => {
|
|
167
|
+
const baseURL = `${apiBase}/api/v1/private/tinfoil/`;
|
|
168
|
+
const client = new SecureClient({
|
|
169
|
+
baseURL,
|
|
170
|
+
attestationBundleURL: `${apiBase}/api/v1/private/tinfoil`,
|
|
171
|
+
transport: 'ehbp',
|
|
172
|
+
userCacheSecret,
|
|
173
|
+
});
|
|
174
|
+
await client.ready();
|
|
175
|
+
verificationDocument = client.getVerificationDocument();
|
|
176
|
+
verificationError = null;
|
|
177
|
+
return client;
|
|
178
|
+
});
|
|
179
|
+
const nextState = { userCacheSecret, promise };
|
|
180
|
+
clientState = nextState;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
return await promise;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (clientState === nextState) clientState = null;
|
|
186
|
+
verificationError = error;
|
|
187
|
+
throw error;
|
|
177
188
|
}
|
|
178
|
-
return clientPromise;
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
return {
|
|
@@ -206,7 +216,27 @@ async function runPreflight({ apiBase, apiKey, model, req, requestBodyBytes }) {
|
|
|
206
216
|
body: JSON.stringify(preflightBody),
|
|
207
217
|
});
|
|
208
218
|
|
|
209
|
-
if (response.ok)
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
try {
|
|
221
|
+
const data = await response.json();
|
|
222
|
+
const cacheScope = typeof data?.cacheScope === 'string' ? data.cacheScope.trim() : '';
|
|
223
|
+
if (/^[a-f0-9]{64}$/i.test(cacheScope)) {
|
|
224
|
+
return { ok: true, cacheScope };
|
|
225
|
+
}
|
|
226
|
+
} catch {}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
status: 502,
|
|
231
|
+
body: {
|
|
232
|
+
error: {
|
|
233
|
+
message: 'NanoGPT preflight did not establish cache isolation.',
|
|
234
|
+
type: 'api_error',
|
|
235
|
+
code: 'cache_scope_unavailable',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
210
240
|
|
|
211
241
|
let errorBody = null;
|
|
212
242
|
try {
|
|
@@ -223,6 +253,22 @@ async function runPreflight({ apiBase, apiKey, model, req, requestBodyBytes }) {
|
|
|
223
253
|
return { ok: false, status: response.status, body: errorBody };
|
|
224
254
|
}
|
|
225
255
|
|
|
256
|
+
async function getPreflightedClient({ apiBase, apiKey, secureState, req, model = MODELS[0] }) {
|
|
257
|
+
const preflight = await runPreflight({
|
|
258
|
+
apiBase,
|
|
259
|
+
apiKey,
|
|
260
|
+
model,
|
|
261
|
+
req,
|
|
262
|
+
requestBodyBytes: 0,
|
|
263
|
+
});
|
|
264
|
+
if (!preflight.ok) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
preflight.body?.error?.message || `NanoGPT preflight failed with HTTP ${preflight.status}`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return secureState.getClient(preflight.cacheScope);
|
|
270
|
+
}
|
|
271
|
+
|
|
226
272
|
async function handleChatCompletion({ apiBase, apiKey, secureState, req, res, corsHeaders }) {
|
|
227
273
|
let rawBody;
|
|
228
274
|
try {
|
|
@@ -288,7 +334,7 @@ async function handleChatCompletion({ apiBase, apiKey, secureState, req, res, co
|
|
|
288
334
|
});
|
|
289
335
|
|
|
290
336
|
try {
|
|
291
|
-
const client = await secureState.getClient();
|
|
337
|
+
const client = await secureState.getClient(preflight.cacheScope);
|
|
292
338
|
response = await client.fetch(`${apiBase}/api/v1/private/tinfoil/v1/chat/completions`, {
|
|
293
339
|
method: 'POST',
|
|
294
340
|
headers: {
|
|
@@ -297,6 +343,7 @@ async function handleChatCompletion({ apiBase, apiKey, secureState, req, res, co
|
|
|
297
343
|
accept: privateStreamRequested ? 'text/event-stream' : 'application/json',
|
|
298
344
|
'x-nanogpt-private-model': model.id,
|
|
299
345
|
'x-nanogpt-private-stream': privateStreamRequested ? 'true' : 'false',
|
|
346
|
+
'x-nanogpt-private-cache-scope': buildPrivateModeCacheScopeProof(preflight.cacheScope),
|
|
300
347
|
'x-query-source': 'api',
|
|
301
348
|
...copyLocalHeaders(req),
|
|
302
349
|
},
|
|
@@ -309,7 +356,7 @@ async function handleChatCompletion({ apiBase, apiKey, secureState, req, res, co
|
|
|
309
356
|
const message = error instanceof Error ? error.message : String(error);
|
|
310
357
|
jsonResponse(res, 502, {
|
|
311
358
|
error: {
|
|
312
|
-
message:
|
|
359
|
+
message: normalizePrivateModeUpstreamErrorMessage(message, 'Private Mode request failed.'),
|
|
313
360
|
type: 'api_error',
|
|
314
361
|
code: 'private_mode_request_failed',
|
|
315
362
|
},
|
|
@@ -401,7 +448,7 @@ function handleOptions(req, res, corsHeaders) {
|
|
|
401
448
|
|
|
402
449
|
export async function startPrivateModeProxy(options) {
|
|
403
450
|
const apiBase = normalizeApiBase(options.apiBase);
|
|
404
|
-
const secureState = createSecureState(apiBase);
|
|
451
|
+
const secureState = createSecureState(apiBase, options.apiKey);
|
|
405
452
|
const localBase = `http://${options.host}:${options.port}/v1`;
|
|
406
453
|
const originPolicy = buildPrivateModeOriginPolicy(options);
|
|
407
454
|
|
|
@@ -442,7 +489,12 @@ export async function startPrivateModeProxy(options) {
|
|
|
442
489
|
|
|
443
490
|
if (req.method === 'GET' && url.pathname === '/v1/private-mode/attestation') {
|
|
444
491
|
try {
|
|
445
|
-
await
|
|
492
|
+
await getPreflightedClient({
|
|
493
|
+
apiBase,
|
|
494
|
+
apiKey: options.apiKey,
|
|
495
|
+
secureState,
|
|
496
|
+
req,
|
|
497
|
+
});
|
|
446
498
|
jsonResponse(res, 200, secureState.getVerificationState(), corsHeaders);
|
|
447
499
|
} catch (error) {
|
|
448
500
|
jsonResponse(res, 502, {
|
|
@@ -508,7 +560,11 @@ The verified TEE target and this local proxy can see plaintext.
|
|
|
508
560
|
}
|
|
509
561
|
|
|
510
562
|
if (options.warmAttestation !== false) {
|
|
511
|
-
|
|
563
|
+
getPreflightedClient({
|
|
564
|
+
apiBase,
|
|
565
|
+
apiKey: options.apiKey,
|
|
566
|
+
secureState,
|
|
567
|
+
})
|
|
512
568
|
.then((client) => {
|
|
513
569
|
const doc = client.getVerificationDocument();
|
|
514
570
|
process.stdout.write(`Attestation: verified=${doc.securityVerified === true}; enclave=${doc.enclaveHost || client.getEnclaveURL() || 'unknown'}\n`);
|
|
@@ -1,45 +1,30 @@
|
|
|
1
|
-
// Keep upstream error
|
|
2
|
-
|
|
1
|
+
// Keep user-facing upstream error classification in sync with
|
|
2
|
+
// lib/privateMode/tinfoilBrowserClient.ts. Never expose decrypted upstream
|
|
3
|
+
// response bodies or exception text to local proxy clients.
|
|
3
4
|
const MISSING_EHBP_RESPONSE_NONCE_PATTERN = /missing\s+ehbp-response-nonce\s+header/i;
|
|
5
|
+
const PRIVATE_MODE_REFUND_NOTICE = 'Any reserved balance will be released or refunded.';
|
|
4
6
|
|
|
5
|
-
export function
|
|
6
|
-
if (
|
|
7
|
-
return
|
|
7
|
+
export function privateModeProviderFailureMessage(status) {
|
|
8
|
+
if (status === 429) {
|
|
9
|
+
return `Private Mode is temporarily rate-limited. ${PRIVATE_MODE_REFUND_NOTICE}`;
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
return `The Private Mode provider temporarily failed. ${PRIVATE_MODE_REFUND_NOTICE}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizePrivateModeUpstreamErrorMessage(message, fallback) {
|
|
15
|
+
void fallback;
|
|
16
|
+
if (MISSING_EHBP_RESPONSE_NONCE_PATTERN.test(String(message || ''))) {
|
|
17
|
+
return `Private Mode could not verify the provider response. ${PRIVATE_MODE_REFUND_NOTICE}`;
|
|
11
18
|
}
|
|
12
|
-
return
|
|
19
|
+
return privateModeProviderFailureMessage();
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export async function readErrorMessage(response, fallback) {
|
|
16
|
-
|
|
23
|
+
void fallback;
|
|
17
24
|
try {
|
|
18
|
-
|
|
19
|
-
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
20
|
-
const error = data.error && typeof data.error === 'object' && !Array.isArray(data.error)
|
|
21
|
-
? data.error
|
|
22
|
-
: {};
|
|
23
|
-
message = (
|
|
24
|
-
typeof error.message === 'string' && error.message.trim()
|
|
25
|
-
? error.message
|
|
26
|
-
: typeof data.message === 'string' && data.message.trim()
|
|
27
|
-
? data.message
|
|
28
|
-
: fallback
|
|
29
|
-
);
|
|
30
|
-
} else if (typeof data === 'string' && data.trim()) {
|
|
31
|
-
message = data;
|
|
32
|
-
} else if (typeof data === 'number' || typeof data === 'boolean') {
|
|
33
|
-
message = String(data);
|
|
34
|
-
}
|
|
25
|
+
await response?.body?.cancel();
|
|
35
26
|
} catch {
|
|
36
|
-
|
|
37
|
-
const text = await response.text();
|
|
38
|
-
message = text || fallback;
|
|
39
|
-
} catch {
|
|
40
|
-
message = fallback;
|
|
41
|
-
}
|
|
27
|
+
// The body may already be locked or closed. Its contents remain discarded.
|
|
42
28
|
}
|
|
43
|
-
|
|
44
|
-
return normalizePrivateModeUpstreamErrorMessage(message, fallback);
|
|
29
|
+
return privateModeProviderFailureMessage(response?.status);
|
|
45
30
|
}
|
package/models/private-tee.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"upstreamModel": "kimi-k2-6",
|
|
6
6
|
"billingModel": "TEE/kimi-k2-6",
|
|
7
7
|
"teeTargetModel": "kimi-k2-6",
|
|
8
|
+
"thinkingMode": "kimi-k2.6",
|
|
8
9
|
"created": 1764547200,
|
|
9
10
|
"ownedBy": "nanogpt-private-mode",
|
|
10
11
|
"aliases": ["private/kimi-k2.6", "TEE/kimi-k2-6", "TEE/kimi-k2.6"]
|
|
@@ -72,27 +73,5 @@
|
|
|
72
73
|
"created": 1764547200,
|
|
73
74
|
"ownedBy": "nanogpt-private-mode",
|
|
74
75
|
"aliases": ["TEE/gemma4-31b:thinking", "gemma4-31b:thinking"]
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"id": "private/deepseek-v4-pro",
|
|
78
|
-
"name": "DeepSeek V4 Pro Private",
|
|
79
|
-
"upstreamModel": "deepseek-v4-pro",
|
|
80
|
-
"billingModel": "TEE/deepseek-v4-pro",
|
|
81
|
-
"teeTargetModel": "deepseek-v4-pro",
|
|
82
|
-
"thinkingMode": "deepseek-v4-pro",
|
|
83
|
-
"created": 1764547200,
|
|
84
|
-
"ownedBy": "nanogpt-private-mode",
|
|
85
|
-
"aliases": ["TEE/deepseek-v4-pro", "deepseek-v4-pro"]
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"id": "private/deepseek-v4-pro:thinking",
|
|
89
|
-
"name": "DeepSeek V4 Pro Thinking Private",
|
|
90
|
-
"upstreamModel": "deepseek-v4-pro",
|
|
91
|
-
"billingModel": "TEE/deepseek-v4-pro:thinking",
|
|
92
|
-
"teeTargetModel": "deepseek-v4-pro",
|
|
93
|
-
"thinkingMode": "deepseek-v4-pro",
|
|
94
|
-
"created": 1764547200,
|
|
95
|
-
"ownedBy": "nanogpt-private-mode",
|
|
96
|
-
"aliases": ["TEE/deepseek-v4-pro:thinking", "deepseek-v4-pro:thinking"]
|
|
97
76
|
}
|
|
98
77
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanogpt/private-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenAI-compatible localhost proxy for NanoGPT Private Mode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
15
|
+
"lib/cacheScope.js",
|
|
15
16
|
"lib/originPolicy.js",
|
|
16
17
|
"lib/requestTransforms.js",
|
|
17
18
|
"lib/server.js",
|
|
@@ -25,10 +26,11 @@
|
|
|
25
26
|
"start": "node ./bin/nanogpt-private-mode.js"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
+
"ai": "6.0.220",
|
|
30
|
+
"tinfoil": "1.1.12"
|
|
29
31
|
},
|
|
30
32
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
33
|
+
"node": ">=22"
|
|
32
34
|
},
|
|
33
35
|
"license": "MIT"
|
|
34
36
|
}
|