@nanogpt/private-mode 0.2.13 → 0.2.16
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 +20 -4
- package/bin/nanogpt-private-mode.js +2 -0
- package/lib/requestTransforms.js +40 -14
- package/lib/statusContract.js +2 -1
- package/lib/transportTimeouts.js +28 -0
- package/models/private-tee.json +17 -28
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ const client = new OpenAI({
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
const response = await client.chat.completions.create({
|
|
28
|
-
model: "private/glm-5-
|
|
28
|
+
model: "private/glm-5-3",
|
|
29
29
|
messages: [{ role: "user", content: "Hello" }],
|
|
30
30
|
});
|
|
31
31
|
```
|
|
@@ -68,18 +68,34 @@ GET http://127.0.0.1:8787/v1/private-mode/attestation
|
|
|
68
68
|
|
|
69
69
|
Supported private model IDs include:
|
|
70
70
|
|
|
71
|
+
- `private/deepseek-v4-1-flash` - DeepSeek V4.1 Flash
|
|
71
72
|
- `private/deepseek-v4-flash` - DeepSeek V4 Flash 0731
|
|
72
73
|
- `private/kimi-k3`
|
|
73
74
|
- `private/gpt-oss-120b`
|
|
74
75
|
- `private/llama3-3-70b`
|
|
75
|
-
- `private/glm-5-2`
|
|
76
|
-
- `private/glm-5-2:thinking`
|
|
77
76
|
- `private/glm-5-3`
|
|
78
77
|
- `private/glm-5-3-flash`
|
|
79
78
|
- `private/gemma4-31b`
|
|
80
79
|
- `private/gemma4-31b:thinking`
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
GLM 5.3 Private and GLM 5.3 Flash Private each have a 1,048,576-token total context limit and a 131,072-token output ceiling. Kimi K3 Private has a 256,000-token context limit. DeepSeek V4 Flash 0731 Private has a 1,048,576-token total context limit. The model-list extension `context_length` is the combined prompt-and-output window, while `max_output_tokens` is the output ceiling within that same window. These values are not additive.
|
|
82
|
+
|
|
83
|
+
### Thinking without leaving Private Mode
|
|
84
|
+
|
|
85
|
+
Keep the same private model ID when enabling thinking. For example:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"model": "private/deepseek-v4-flash",
|
|
90
|
+
"messages": [{ "role": "user", "content": "What is 17 times 19?" }],
|
|
91
|
+
"reasoning_effort": "high",
|
|
92
|
+
"stream": true
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
DeepSeek V4 Flash uses the 0731 release and supports optional thinking. Gemma 4 also supports optional thinking. Use `thinking: true` or `enable_thinking: true` to enable it; `thinking: false` disables it for these optional-thinking models. Kimi K3, GLM 5.3, and GLM 5.3 Flash always generate reasoning. GLM 5.3 models accept `low`, `high`, or `max` effort. `reasoning: { "exclude": true }` controls response visibility and does not disable thinking.
|
|
97
|
+
|
|
98
|
+
Non-streaming reasoning requests may take more than five minutes. The CLI allows the encrypted request to wait for the hosted route's response, with a 30-minute transport limit. The hosted route stops non-streaming generation after at most 29 minutes to leave time before its platform deadline. Use `stream: true` for earlier output, and configure your API client's timeout to allow long generations. Upgrade the local proxy as well as the hosted service to receive both timeout fixes.
|
|
83
99
|
|
|
84
100
|
Browser requests are locked down by default. The proxy only accepts same-machine clients and rejects browser `Origin` headers that are not explicitly allowed, so a random website or LAN client cannot spend the local `NANOGPT_API_KEY` while the proxy is running. If a local browser app needs to call the proxy directly, allow that exact origin:
|
|
85
101
|
|
|
@@ -4,6 +4,7 @@ import { parseArgs } from 'node:util';
|
|
|
4
4
|
import { PRIVATE_MODE_PROXY_VERSION } from '../lib/packageInfo.js';
|
|
5
5
|
import { startPrivateModeProxy } from '../lib/server.js';
|
|
6
6
|
import { verifyPrivateModeReceiptCli } from '../lib/verifyReceipt.js';
|
|
7
|
+
import { installPrivateModeTransportTimeouts } from '../lib/transportTimeouts.js';
|
|
7
8
|
|
|
8
9
|
const { values, positionals } = parseArgs({
|
|
9
10
|
options: {
|
|
@@ -69,6 +70,7 @@ if (!apiKey) {
|
|
|
69
70
|
process.exit(1);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
installPrivateModeTransportTimeouts(String(values['api-base']));
|
|
72
74
|
startPrivateModeProxy({
|
|
73
75
|
apiBase: String(values['api-base']),
|
|
74
76
|
apiKey,
|
package/lib/requestTransforms.js
CHANGED
|
@@ -236,8 +236,16 @@ function isReasoningEnabled(reasoning) {
|
|
|
236
236
|
return undefined;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
function normalizeDeepSeekV4ReasoningEffort(value) {
|
|
239
|
+
function normalizeDeepSeekV4ReasoningEffort(value, model, nativeTemplateValue = false) {
|
|
240
240
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
241
|
+
if (model?.thinkingMode === 'deepseek-v4.1') {
|
|
242
|
+
if (nativeTemplateValue && ['low', 'high', 'xhigh'].includes(normalized)) {
|
|
243
|
+
return normalized;
|
|
244
|
+
}
|
|
245
|
+
if (normalized === 'low' || normalized === 'minimal') return 'low';
|
|
246
|
+
if (normalized === 'medium') return 'high';
|
|
247
|
+
return 'xhigh';
|
|
248
|
+
}
|
|
241
249
|
if (normalized === 'max' || normalized === 'xhigh') return 'max';
|
|
242
250
|
if (normalized === 'medium') return 'medium';
|
|
243
251
|
if (normalized === 'low' || normalized === 'minimal') return 'low';
|
|
@@ -271,8 +279,11 @@ function shouldEnableThinking(body, model) {
|
|
|
271
279
|
));
|
|
272
280
|
|
|
273
281
|
return explicitThinking
|
|
282
|
+
?? isThinkingEnabled(body.enable_thinking)
|
|
274
283
|
?? explicitReasoning
|
|
275
284
|
?? (body.reasoning_effort !== undefined ? reasoningEffort !== 'none' : undefined)
|
|
285
|
+
?? isThinkingEnabled(body.chat_template_kwargs?.thinking)
|
|
286
|
+
?? isThinkingEnabled(body.chat_template_kwargs?.enable_thinking)
|
|
276
287
|
?? hasThinkingSuffix;
|
|
277
288
|
}
|
|
278
289
|
|
|
@@ -526,7 +537,6 @@ export function normalizePrivateModelReasoningControls(body, model) {
|
|
|
526
537
|
|
|
527
538
|
export function shouldSuppressPrivateModelReasoning(body, model) {
|
|
528
539
|
const isKimiK3 = isPrivateModeKimiK3Model(model);
|
|
529
|
-
if (!isKimiK3 && model.thinkingMode !== 'deepseek-v4' && model.thinkingMode !== 'glm-5.3') return false;
|
|
530
540
|
const reasoningVisibilityOptOut = body.reasoningOptOut === true ||
|
|
531
541
|
body.exposeReasoning === false ||
|
|
532
542
|
body.materializeReasoning === false ||
|
|
@@ -604,14 +614,17 @@ function applyPrivateModeKimiK3RequestParams(body, model) {
|
|
|
604
614
|
);
|
|
605
615
|
}
|
|
606
616
|
|
|
607
|
-
function clampPrivateModeDeepSeekV4Output(body) {
|
|
617
|
+
function clampPrivateModeDeepSeekV4Output(body, model) {
|
|
618
|
+
const maxCompletionTokens = Number.isFinite(model?.maxOutputTokens)
|
|
619
|
+
? Math.max(1, Math.floor(model.maxOutputTokens))
|
|
620
|
+
: DEEPSEEK_V4_MAX_COMPLETION_TOKENS;
|
|
608
621
|
const requestedMaxTokens = typeof body.max_tokens === 'number' && Number.isFinite(body.max_tokens)
|
|
609
622
|
? body.max_tokens
|
|
610
623
|
: undefined;
|
|
611
624
|
if (requestedMaxTokens === undefined) return;
|
|
612
625
|
|
|
613
626
|
const normalizedMaxTokens = requestedMaxTokens < 0
|
|
614
|
-
?
|
|
627
|
+
? maxCompletionTokens
|
|
615
628
|
: requestedMaxTokens;
|
|
616
629
|
const promptTokenEstimate = estimatePrivateModeKimiK3PromptTokens(body);
|
|
617
630
|
const remainingContext = Math.max(
|
|
@@ -622,7 +635,7 @@ function clampPrivateModeDeepSeekV4Output(body) {
|
|
|
622
635
|
);
|
|
623
636
|
body.max_tokens = Math.min(
|
|
624
637
|
Math.max(1, Math.floor(normalizedMaxTokens)),
|
|
625
|
-
|
|
638
|
+
maxCompletionTokens,
|
|
626
639
|
remainingContext,
|
|
627
640
|
);
|
|
628
641
|
}
|
|
@@ -1049,8 +1062,11 @@ export function applyPrivateModelRequestMutations(body, model) {
|
|
|
1049
1062
|
applyTinfoilCompatibilityMutations(body, model);
|
|
1050
1063
|
if (isPrivateModeKimiK3Model(model)) {
|
|
1051
1064
|
applyPrivateModeKimiK3RequestParams(body, model);
|
|
1052
|
-
} else if (
|
|
1053
|
-
|
|
1065
|
+
} else if (
|
|
1066
|
+
model.thinkingMode === 'deepseek-v4' ||
|
|
1067
|
+
model.thinkingMode === 'deepseek-v4.1'
|
|
1068
|
+
) {
|
|
1069
|
+
clampPrivateModeDeepSeekV4Output(body, model);
|
|
1054
1070
|
} else if (model.thinkingMode === 'glm-5.3') {
|
|
1055
1071
|
clampPrivateModeGlm53Output(body);
|
|
1056
1072
|
}
|
|
@@ -1065,28 +1081,38 @@ export function applyPrivateModelRequestMutations(body, model) {
|
|
|
1065
1081
|
} else if (
|
|
1066
1082
|
model.thinkingMode === 'glm-5.2' ||
|
|
1067
1083
|
model.thinkingMode === 'glm-5.3' ||
|
|
1068
|
-
model.thinkingMode === 'deepseek-v4'
|
|
1084
|
+
model.thinkingMode === 'deepseek-v4' ||
|
|
1085
|
+
model.thinkingMode === 'deepseek-v4.1'
|
|
1069
1086
|
) {
|
|
1070
1087
|
const thinkingEnabled = model.thinkingMode === 'glm-5.3'
|
|
1071
1088
|
? true
|
|
1072
1089
|
: shouldEnableThinking(body, model);
|
|
1073
|
-
const
|
|
1090
|
+
const publicReasoningEffort = body.reasoning_effort
|
|
1074
1091
|
?? (isPlainObject(body.reasoning) ? body.reasoning.effort : undefined);
|
|
1092
|
+
const nativeTemplateReasoningEffort = body.chat_template_kwargs?.reasoning_effort;
|
|
1093
|
+
const requestedReasoningEffort = publicReasoningEffort ?? nativeTemplateReasoningEffort;
|
|
1075
1094
|
body.chat_template_kwargs = {
|
|
1076
1095
|
...mergeChatTemplateKwargs(body),
|
|
1077
1096
|
thinking: thinkingEnabled,
|
|
1078
1097
|
};
|
|
1079
1098
|
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1099
|
+
if (model.thinkingMode === 'glm-5.3') {
|
|
1100
|
+
// Match the native GLM API and the browser client: low/high/max belongs
|
|
1101
|
+
// at the top level, including on tool turns.
|
|
1102
|
+
body.reasoning_effort = normalizeGlm53ReasoningEffort(requestedReasoningEffort);
|
|
1103
|
+
delete body.chat_template_kwargs.reasoning_effort;
|
|
1104
|
+
} else if (thinkingEnabled) {
|
|
1105
|
+
body.chat_template_kwargs.reasoning_effort = normalizeDeepSeekV4ReasoningEffort(
|
|
1106
|
+
requestedReasoningEffort,
|
|
1107
|
+
model,
|
|
1108
|
+
publicReasoningEffort === undefined && nativeTemplateReasoningEffort !== undefined,
|
|
1109
|
+
);
|
|
1084
1110
|
} else {
|
|
1085
1111
|
delete body.chat_template_kwargs.reasoning_effort;
|
|
1086
1112
|
}
|
|
1087
1113
|
|
|
1088
1114
|
delete body.thinking;
|
|
1089
|
-
delete body.reasoning_effort;
|
|
1115
|
+
if (model.thinkingMode !== 'glm-5.3') delete body.reasoning_effort;
|
|
1090
1116
|
}
|
|
1091
1117
|
|
|
1092
1118
|
stripUnsupportedPrivateTinfoilFields(body);
|
package/lib/statusContract.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
export const PRIVATE_MODE_FRONTEND_SUPPORTED_FEATURES = Object.freeze([
|
|
4
4
|
'text_chat',
|
|
5
5
|
'vision_image_inputs',
|
|
6
|
+
'encrypted_pdf_conversion',
|
|
6
7
|
'streaming',
|
|
7
8
|
'conversation_history',
|
|
8
9
|
'model_settings',
|
|
@@ -10,7 +11,7 @@ export const PRIVATE_MODE_FRONTEND_SUPPORTED_FEATURES = Object.freeze([
|
|
|
10
11
|
]);
|
|
11
12
|
|
|
12
13
|
export const PRIVATE_MODE_FRONTEND_DISABLED_FEATURES = Object.freeze([
|
|
13
|
-
'
|
|
14
|
+
'non_image_non_pdf_attachments',
|
|
14
15
|
'web_search',
|
|
15
16
|
'url_scraped_content',
|
|
16
17
|
'project_chats',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Agent, setGlobalDispatcher } from 'undici';
|
|
2
|
+
import { PRIVATE_MODE_STREAM_ROUTE_MAX_DURATION_MS } from './constants.js';
|
|
3
|
+
|
|
4
|
+
export class PrivateModeDispatcher extends Agent {
|
|
5
|
+
constructor(apiBase) {
|
|
6
|
+
super();
|
|
7
|
+
this.chatUrl = new URL(`${apiBase.replace(/\/+$/, '')}/api/v1/private/tinfoil/v1/chat/completions`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
dispatch(options, handler) {
|
|
11
|
+
// EHBP calls native fetch after encryption, without retaining a custom
|
|
12
|
+
// dispatcher. Scope the CLI's override to its configured encrypted endpoint.
|
|
13
|
+
// Override dispatch options, not Agent defaults: fetch supplies its own
|
|
14
|
+
// five-minute timeout values on every dispatch.
|
|
15
|
+
if (String(options.origin) === this.chatUrl.origin && options.path === this.chatUrl.pathname) {
|
|
16
|
+
options = {
|
|
17
|
+
...options,
|
|
18
|
+
headersTimeout: PRIVATE_MODE_STREAM_ROUTE_MAX_DURATION_MS,
|
|
19
|
+
bodyTimeout: PRIVATE_MODE_STREAM_ROUTE_MAX_DURATION_MS,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return super.dispatch(options, handler);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function installPrivateModeTransportTimeouts(apiBase) {
|
|
27
|
+
setGlobalDispatcher(new PrivateModeDispatcher(apiBase));
|
|
28
|
+
}
|
package/models/private-tee.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"id": "private/deepseek-v4-1-flash",
|
|
4
|
+
"name": "DeepSeek V4.1 Flash Private",
|
|
5
|
+
"upstreamModel": "deepseek-v4-1-flash",
|
|
6
|
+
"billingModel": "TEE/deepseek-v4.1-flash",
|
|
7
|
+
"providerPricingModel": "TEE/deepseek-v4.1-flash",
|
|
8
|
+
"teeTargetModel": "deepseek-v4-1-flash",
|
|
9
|
+
"thinkingMode": "deepseek-v4.1",
|
|
10
|
+
"maxInputTokens": 1048576,
|
|
11
|
+
"maxOutputTokens": 384000,
|
|
12
|
+
"created": 1789084800,
|
|
13
|
+
"ownedBy": "nanogpt-private-mode",
|
|
14
|
+
"aliases": ["private/deepseek-v4.1-flash", "TEE/deepseek-v4.1-flash"]
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"id": "private/deepseek-v4-flash",
|
|
4
18
|
"name": "DeepSeek V4 Flash 0731 Private",
|
|
@@ -48,34 +62,6 @@
|
|
|
48
62
|
"ownedBy": "nanogpt-private-mode",
|
|
49
63
|
"aliases": ["private/llama-3.3-70b", "TEE/llama3-3-70b"]
|
|
50
64
|
},
|
|
51
|
-
{
|
|
52
|
-
"id": "private/glm-5-2",
|
|
53
|
-
"name": "GLM 5.2 Private",
|
|
54
|
-
"upstreamModel": "glm-5-2",
|
|
55
|
-
"billingModel": "TEE/glm-5-2",
|
|
56
|
-
"providerPricingModel": "TEE/glm-5.2",
|
|
57
|
-
"teeTargetModel": "glm-5-2",
|
|
58
|
-
"thinkingMode": "glm-5.2",
|
|
59
|
-
"maxInputTokens": 393216,
|
|
60
|
-
"maxOutputTokens": 131072,
|
|
61
|
-
"created": 1781827200,
|
|
62
|
-
"ownedBy": "nanogpt-private-mode",
|
|
63
|
-
"aliases": ["private/glm-5.2", "TEE/glm-5-2", "TEE/glm-5.2"]
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "private/glm-5-2:thinking",
|
|
67
|
-
"name": "GLM 5.2 Thinking Private",
|
|
68
|
-
"upstreamModel": "glm-5-2",
|
|
69
|
-
"billingModel": "TEE/glm-5-2:thinking",
|
|
70
|
-
"providerPricingModel": "TEE/glm-5.2:thinking",
|
|
71
|
-
"teeTargetModel": "glm-5-2",
|
|
72
|
-
"thinkingMode": "glm-5.2",
|
|
73
|
-
"maxInputTokens": 393216,
|
|
74
|
-
"maxOutputTokens": 131072,
|
|
75
|
-
"created": 1781827200,
|
|
76
|
-
"ownedBy": "nanogpt-private-mode",
|
|
77
|
-
"aliases": ["private/glm-5.2:thinking", "TEE/glm-5-2:thinking", "TEE/glm-5.2:thinking"]
|
|
78
|
-
},
|
|
79
65
|
{
|
|
80
66
|
"id": "private/glm-5-3",
|
|
81
67
|
"name": "GLM 5.3 Private",
|
|
@@ -97,6 +83,9 @@
|
|
|
97
83
|
"billingModel": "TEE/glm-5.3-flash",
|
|
98
84
|
"providerPricingModel": "TEE/glm-5.3-flash",
|
|
99
85
|
"teeTargetModel": "glm-5-3-flash",
|
|
86
|
+
"teeTargetAliases": [
|
|
87
|
+
"glm5-3-flash"
|
|
88
|
+
],
|
|
100
89
|
"maxInputTokens": 1048576,
|
|
101
90
|
"maxOutputTokens": 131072,
|
|
102
91
|
"thinkingMode": "glm-5.3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanogpt/private-mode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "OpenAI-compatible localhost proxy for NanoGPT Private Mode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"lib/serverErrorNormalization.js",
|
|
24
24
|
"lib/sseResponsePump.js",
|
|
25
25
|
"lib/statusContract.js",
|
|
26
|
+
"lib/transportTimeouts.js",
|
|
26
27
|
"lib/verifyReceipt.js",
|
|
27
28
|
"models",
|
|
28
29
|
"README.md"
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"ai": "6.0.220",
|
|
35
|
-
"tinfoil": "1.1.12"
|
|
36
|
+
"tinfoil": "1.1.12",
|
|
37
|
+
"undici": "6.28.0"
|
|
36
38
|
},
|
|
37
39
|
"engines": {
|
|
38
40
|
"node": ">=22"
|