@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 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-2",
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
- Both GLM 5.2 variants are deployed with a 393,216-token total context limit, with prompt and output sharing that window. 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.
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,
@@ -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
- ? DEEPSEEK_V4_MAX_COMPLETION_TOKENS
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
- DEEPSEEK_V4_MAX_COMPLETION_TOKENS,
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 (model.thinkingMode === 'deepseek-v4') {
1053
- clampPrivateModeDeepSeekV4Output(body);
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 requestedReasoningEffort = body.reasoning_effort
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 (thinkingEnabled) {
1081
- body.chat_template_kwargs.reasoning_effort = model.thinkingMode === 'glm-5.3'
1082
- ? normalizeGlm53ReasoningEffort(requestedReasoningEffort)
1083
- : normalizeDeepSeekV4ReasoningEffort(requestedReasoningEffort);
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);
@@ -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
- 'non_image_attachments',
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
+ }
@@ -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.13",
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"