@nanogpt/private-mode 0.2.8 → 0.2.9

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.
@@ -14,8 +14,7 @@ const MAX_TOKEN_ALIAS_FIELDS = [
14
14
  ];
15
15
  const KIMI_K3_STANDARD_DEFAULT_MAX_COMPLETION_TOKENS = 16_384;
16
16
  const KIMI_K3_DEFAULT_MAX_COMPLETION_TOKENS = 65_536;
17
- const KIMI_K3_MAX_COMPLETION_TOKENS = 1_048_576;
18
- const KIMI_K3_CONTEXT_WINDOW_TOKENS = 1_048_576;
17
+ const KIMI_K3_PRIVATE_CONTEXT_WINDOW_TOKENS = 256_000;
19
18
  const KIMI_K3_CONTEXT_SAFETY_MARGIN_TOKENS = 10;
20
19
  const KIMI_K3_ESTIMATED_TEXT_BYTES_PER_TOKEN = 3;
21
20
  const KIMI_K3_HIGH_ENTROPY_TEXT_MIN_LENGTH = 2_048;
@@ -549,8 +548,18 @@ function resolvePrivateModeKimiK3ReasoningEffort(body) {
549
548
  return excludesReasoning ? normalizedEffort ?? 'low' : normalizedEffort ?? 'max';
550
549
  }
551
550
 
552
- function applyPrivateModeKimiK3RequestParams(body) {
551
+ function applyPrivateModeKimiK3RequestParams(body, model) {
553
552
  const reasoningEffort = resolvePrivateModeKimiK3ReasoningEffort(body);
553
+ const contextWindowTokens = typeof model.maxInputTokens === 'number'
554
+ && Number.isFinite(model.maxInputTokens)
555
+ && model.maxInputTokens > 0
556
+ ? Math.floor(model.maxInputTokens)
557
+ : KIMI_K3_PRIVATE_CONTEXT_WINDOW_TOKENS;
558
+ const maxCompletionTokens = typeof model.maxOutputTokens === 'number'
559
+ && Number.isFinite(model.maxOutputTokens)
560
+ && model.maxOutputTokens > 0
561
+ ? Math.floor(model.maxOutputTokens)
562
+ : contextWindowTokens;
554
563
  const requestedMaxTokens = typeof body.max_tokens === 'number' && Number.isFinite(body.max_tokens)
555
564
  ? body.max_tokens
556
565
  : undefined;
@@ -559,13 +568,13 @@ function applyPrivateModeKimiK3RequestParams(body) {
559
568
  ? KIMI_K3_DEFAULT_MAX_COMPLETION_TOKENS
560
569
  : KIMI_K3_STANDARD_DEFAULT_MAX_COMPLETION_TOKENS
561
570
  : requestedMaxTokens < 0
562
- ? KIMI_K3_MAX_COMPLETION_TOKENS
571
+ ? maxCompletionTokens
563
572
  : requestedMaxTokens;
564
573
  normalizePrivateModeKimiK3NamedToolChoice(body);
565
574
  const promptTokenEstimate = estimatePrivateModeKimiK3PromptTokens(body);
566
575
  const remainingContext = Math.max(
567
576
  1,
568
- KIMI_K3_CONTEXT_WINDOW_TOKENS -
577
+ contextWindowTokens -
569
578
  promptTokenEstimate -
570
579
  KIMI_K3_CONTEXT_SAFETY_MARGIN_TOKENS,
571
580
  );
@@ -899,7 +908,7 @@ export function applyPrivateModelRequestMutations(body, model) {
899
908
  normalizeStreamOptions(body);
900
909
  applyTinfoilCompatibilityMutations(body, model);
901
910
  if (isPrivateModeKimiK3Model(model)) {
902
- applyPrivateModeKimiK3RequestParams(body);
911
+ applyPrivateModeKimiK3RequestParams(body, model);
903
912
  } else if (model.thinkingMode === 'deepseek-v4') {
904
913
  clampPrivateModeDeepSeekV4Output(body);
905
914
  }
@@ -1,5 +1,40 @@
1
1
  const DEFAULT_SECURE_CLIENT_IDLE_RESET_MS = 5 * 60 * 1000;
2
+ const DEFAULT_VERIFICATION_MAX_AGE_MS = 5 * 60 * 1000;
2
3
  const EHBP_RESPONSE_NONCE_HEADER = 'ehbp-response-nonce';
4
+ const REQUIRED_VERIFICATION_STEPS = [
5
+ 'fetchDigest',
6
+ 'verifyCode',
7
+ 'verifyEnclave',
8
+ 'compareMeasurements',
9
+ ];
10
+
11
+ function isNonEmptyString(value) {
12
+ return typeof value === 'string' && value.trim().length > 0;
13
+ }
14
+
15
+ function hasCompleteVerificationEvidence(document) {
16
+ return document?.securityVerified === true
17
+ && isNonEmptyString(document.codeFingerprint)
18
+ && isNonEmptyString(document.enclaveFingerprint)
19
+ && isNonEmptyString(document.releaseDigest)
20
+ && isNonEmptyString(document.hpkePublicKey)
21
+ && REQUIRED_VERIFICATION_STEPS.every(
22
+ (step) => document.steps?.[step]?.status === 'success',
23
+ )
24
+ && !Object.values(document.steps || {}).some((step) => step?.status === 'failed');
25
+ }
26
+
27
+ function isMeasurementMismatchError(error) {
28
+ let current = error;
29
+ for (let depth = 0; depth < 5 && current; depth += 1) {
30
+ const message = String(current.message || current);
31
+ if (/\b(?:code\s+)?measurement mismatch\b|\bcannot compare measurements\b/i.test(message)) {
32
+ return true;
33
+ }
34
+ current = current.cause;
35
+ }
36
+ return false;
37
+ }
3
38
 
4
39
  function readSecureClientIdleResetMs() {
5
40
  const configured = process.env.NANOGPT_PRIVATE_CLIENT_IDLE_RESET_MS?.trim();
@@ -14,9 +49,14 @@ export function createSecureState(apiBase, options = {}) {
14
49
  : readSecureClientIdleResetMs();
15
50
  const loadTinfoil = options.loadTinfoil || (() => import('tinfoil'));
16
51
  const now = options.now || Date.now;
52
+ const verificationMaxAgeMs = Number.isFinite(options.verificationMaxAgeMs)
53
+ && options.verificationMaxAgeMs >= 0
54
+ ? Math.floor(options.verificationMaxAgeMs)
55
+ : DEFAULT_VERIFICATION_MAX_AGE_MS;
17
56
  let clientState = null;
18
57
  let verificationDocument = null;
19
58
  let verificationError = null;
59
+ let verifiedAtMs = null;
20
60
 
21
61
  async function getClient(userCacheSecret = clientState?.userCacheSecret) {
22
62
  if (!userCacheSecret) {
@@ -24,12 +64,18 @@ export function createSecureState(apiBase, options = {}) {
24
64
  }
25
65
  const nowMs = now();
26
66
  const idleMs = clientState ? nowMs - clientState.lastUsedAtMs : 0;
67
+ const verificationAgeMs = verifiedAtMs === null ? 0 : nowMs - verifiedAtMs;
27
68
  if (
28
69
  clientState?.userCacheSecret === userCacheSecret
29
70
  && (
30
71
  idleResetMs === 0
31
72
  || (idleMs >= 0 && idleMs < idleResetMs)
32
73
  )
74
+ && (
75
+ verifiedAtMs === null
76
+ || verificationMaxAgeMs === 0
77
+ || (verificationAgeMs >= 0 && verificationAgeMs < verificationMaxAgeMs)
78
+ )
33
79
  ) {
34
80
  clientState.lastUsedAtMs = nowMs;
35
81
  return clientState.promise;
@@ -40,6 +86,7 @@ export function createSecureState(apiBase, options = {}) {
40
86
  // still be finishing an older in-flight request.
41
87
  verificationDocument = null;
42
88
  verificationError = null;
89
+ verifiedAtMs = null;
43
90
  const nextState = {
44
91
  client: null,
45
92
  lastUsedAtMs: nowMs,
@@ -54,10 +101,19 @@ export function createSecureState(apiBase, options = {}) {
54
101
  transport: 'ehbp',
55
102
  userCacheSecret,
56
103
  });
57
- await client.ready();
58
104
  nextState.client = client;
105
+ try {
106
+ await client.ready();
107
+ } finally {
108
+ if (clientState === nextState) {
109
+ verificationDocument = client.getVerificationDocument();
110
+ }
111
+ }
112
+ if (!hasCompleteVerificationEvidence(verificationDocument)) {
113
+ throw new Error('Private Mode attestation evidence was incomplete.');
114
+ }
59
115
  if (clientState === nextState) {
60
- verificationDocument = client.getVerificationDocument();
116
+ verifiedAtMs = now();
61
117
  verificationError = null;
62
118
  }
63
119
  return client;
@@ -80,7 +136,23 @@ export function createSecureState(apiBase, options = {}) {
80
136
  getClient,
81
137
  markClientUsed(client) {
82
138
  if (!clientState || clientState.client !== client) return false;
83
- clientState.lastUsedAtMs = now();
139
+ const nowMs = now();
140
+ const currentDocument = client.getVerificationDocument();
141
+ if (currentDocument !== verificationDocument) {
142
+ verificationDocument = currentDocument;
143
+ if (hasCompleteVerificationEvidence(currentDocument)) {
144
+ verifiedAtMs = nowMs;
145
+ verificationError = null;
146
+ } else {
147
+ verifiedAtMs = null;
148
+ // Preserve the incomplete document for status visibility, but never
149
+ // reuse a transport after the SDK has invalidated its evidence.
150
+ clientState.lastUsedAtMs = nowMs;
151
+ clientState = null;
152
+ return true;
153
+ }
154
+ }
155
+ clientState.lastUsedAtMs = nowMs;
84
156
  return true;
85
157
  },
86
158
  invalidateClient(client) {
@@ -89,11 +161,49 @@ export function createSecureState(apiBase, options = {}) {
89
161
  clientState = null;
90
162
  verificationDocument = null;
91
163
  verificationError = null;
164
+ verifiedAtMs = null;
92
165
  return true;
93
166
  },
94
167
  getVerificationState() {
168
+ const nowMs = now();
169
+ // Tinfoil 1.1.12 resets its document to "pending" after an exhausted
170
+ // AttestationError retry, so retain the SDK's explicit mismatch signal.
171
+ const mismatch = verificationDocument?.steps?.compareMeasurements?.status === 'failed'
172
+ || isMeasurementMismatchError(verificationError);
173
+ const verified = hasCompleteVerificationEvidence(verificationDocument)
174
+ && !mismatch
175
+ && verifiedAtMs !== null;
176
+ const verificationAgeMs = verifiedAtMs === null ? null : nowMs - verifiedAtMs;
177
+ const clockRolledBack = verificationAgeMs !== null && verificationAgeMs < 0;
178
+ const expiresAtMs = verifiedAtMs === null || verificationMaxAgeMs === 0
179
+ ? null
180
+ : verifiedAtMs + verificationMaxAgeMs;
181
+ const isStale = verified && (
182
+ clockRolledBack
183
+ || (expiresAtMs !== null && nowMs >= expiresAtMs)
184
+ );
185
+ const freshnessSeconds = expiresAtMs === null
186
+ ? null
187
+ : clockRolledBack
188
+ ? 0
189
+ : Math.max(0, Math.floor((expiresAtMs - nowMs) / 1000));
190
+ const verificationStatus = mismatch
191
+ ? 'measurement_mismatch'
192
+ : verified && verifiedAtMs !== null
193
+ ? isStale
194
+ ? 'stale'
195
+ : 'verified'
196
+ : verificationError || verificationDocument
197
+ ? 'verification_failed'
198
+ : 'not_checked';
95
199
  return {
96
- verified: verificationDocument?.securityVerified === true,
200
+ verified,
201
+ verificationStatus,
202
+ verifiedAt: verifiedAtMs === null ? null : new Date(verifiedAtMs).toISOString(),
203
+ expiresAt: expiresAtMs === null
204
+ ? null
205
+ : new Date(expiresAtMs).toISOString(),
206
+ freshnessSeconds,
97
207
  verificationDocument,
98
208
  error: verificationError ? String(verificationError.message || verificationError) : null,
99
209
  };
package/lib/server.js CHANGED
@@ -23,7 +23,10 @@ import {
23
23
  createSecureState,
24
24
  fetchWithSecureClientRecovery,
25
25
  } from './secureClientLifecycle.js';
26
- import { buildPrivateModeStatusContract } from './statusContract.js';
26
+ import {
27
+ buildPrivateModeStatusContract,
28
+ buildPublicAttestationSummary,
29
+ } from './statusContract.js';
27
30
 
28
31
  const MODELS = JSON.parse(
29
32
  readFileSync(new URL('../models/private-tee.json', import.meta.url), 'utf8'),
@@ -82,6 +85,7 @@ function openAIModelList() {
82
85
  }
83
86
 
84
87
  export function privateModeStatus(apiBase, secureState, localBase, originPolicy) {
88
+ const verificationState = secureState.getVerificationState();
85
89
  return {
86
90
  ok: true,
87
91
  mode: 'private_tee',
@@ -99,10 +103,10 @@ export function privateModeStatus(apiBase, secureState, localBase, originPolicy)
99
103
  streaming_billing: 'precharged_reserve_with_verified_usage_refund',
100
104
  api_local_proxy_required: true,
101
105
  browser_frontend_local_proxy_required: false,
102
- ...buildPrivateModeStatusContract(),
106
+ ...buildPrivateModeStatusContract(verificationState),
103
107
  browser_origins_allowed: originPolicy.allowedOrigins,
104
108
  models: openAIModelList().data,
105
- attestation: secureState.getVerificationState(),
109
+ attestation: buildPublicAttestationSummary(verificationState),
106
110
  };
107
111
  }
108
112
 
@@ -1,3 +1,5 @@
1
+ import { createHash } from 'node:crypto';
2
+
1
3
  export const PRIVATE_MODE_FRONTEND_SUPPORTED_FEATURES = Object.freeze([
2
4
  'text_chat',
3
5
  'streaming',
@@ -43,12 +45,88 @@ export const PRIVATE_MODE_PLAINTEXT_VISIBLE_TO = Object.freeze([
43
45
  'verified_tee_target',
44
46
  ]);
45
47
 
46
- export function buildPrivateModeStatusContract() {
48
+ export const PRIVATE_MODE_KNOWN_LIMITATIONS = Object.freeze([
49
+ 'NanoGPT still observes account, routing, timing, size, status, and usage metadata.',
50
+ 'Provider-reported usage metrics are trusted for billing and are not end-to-end signed.',
51
+ 'A verification receipt does not prove usage correctness, prompt non-retention, or response content authenticity on its own.',
52
+ 'Verification receipts are browser-generated and unsigned; the hashed request identifier is for opaque correlation, not proof of server issuance.',
53
+ 'NanoGPT does not implement attestation-gated KMS release of upstream credentials; the inference enclave is provider-operated.',
54
+ ]);
55
+
56
+ const PRIVATE_MODE_VERIFICATION_STATUSES = new Set([
57
+ 'verified',
58
+ 'unavailable',
59
+ 'stale',
60
+ 'measurement_mismatch',
61
+ 'verification_failed',
62
+ 'unsupported',
63
+ 'not_checked',
64
+ ]);
65
+
66
+ function stringIdentifier(value) {
67
+ return typeof value === 'string' && value.trim() ? value.trim().slice(0, 512) : undefined;
68
+ }
69
+
70
+ function publicKeyFingerprint(value) {
71
+ return typeof value === 'string' && value
72
+ ? createHash('sha256').update(value).digest('hex')
73
+ : undefined;
74
+ }
75
+
76
+ export function buildPublicAttestationSummary(verificationState = {}) {
77
+ return {
78
+ verified: verificationState.verified === true,
79
+ verificationStatus: verificationState.verificationStatus,
80
+ verifiedAt: verificationState.verifiedAt,
81
+ expiresAt: verificationState.expiresAt,
82
+ freshnessSeconds: verificationState.freshnessSeconds,
83
+ error: verificationState.error ? 'Private Mode attestation failed.' : null,
84
+ };
85
+ }
86
+
87
+ export function buildPrivateModeStatusContract(verificationState = {}) {
88
+ const verificationStatus = PRIVATE_MODE_VERIFICATION_STATUSES.has(verificationState.verificationStatus)
89
+ ? verificationState.verificationStatus
90
+ : 'not_checked';
91
+ const document = verificationState.verificationDocument || {};
92
+ const keyFingerprint = publicKeyFingerprint(document.hpkePublicKey);
47
93
  return {
48
94
  frontend_supported_features: [...PRIVATE_MODE_FRONTEND_SUPPORTED_FEATURES],
49
95
  frontend_disabled_features: [...PRIVATE_MODE_FRONTEND_DISABLED_FEATURES],
50
96
  nanogpt_visible: [...PRIVATE_MODE_NANOGPT_VISIBLE],
51
97
  encrypted_from_nanogpt: [...PRIVATE_MODE_ENCRYPTED_FROM_NANOGPT],
52
98
  plaintext_visible_to: [...PRIVATE_MODE_PLAINTEXT_VISIBLE_TO],
99
+ assurance: {
100
+ contract_version: 1,
101
+ assurance_level: 'private_mode',
102
+ transport: 'ehbp',
103
+ verification_status: verificationStatus,
104
+ eligible: verificationStatus === 'verified',
105
+ verified_by: verificationState.verifiedAt || verificationStatus !== 'not_checked'
106
+ ? ['local_proxy:tinfoil-js']
107
+ : [],
108
+ ...(verificationState.verifiedAt ? { verified_at: verificationState.verifiedAt } : {}),
109
+ ...(verificationState.expiresAt ? { expires_at: verificationState.expiresAt } : {}),
110
+ ...(Number.isFinite(verificationState.freshnessSeconds)
111
+ ? { freshness_seconds: Math.max(0, Math.floor(verificationState.freshnessSeconds)) }
112
+ : {}),
113
+ ...(stringIdentifier(document.codeFingerprint)
114
+ ? { expected_measurement: stringIdentifier(document.codeFingerprint) }
115
+ : {}),
116
+ ...(stringIdentifier(document.enclaveFingerprint)
117
+ ? { observed_measurement: stringIdentifier(document.enclaveFingerprint) }
118
+ : {}),
119
+ ...(stringIdentifier(document.releaseDigest)
120
+ ? { release_digest: stringIdentifier(document.releaseDigest) }
121
+ : {}),
122
+ ...(keyFingerprint ? { public_key_fingerprint: keyFingerprint } : {}),
123
+ receipt_available: true,
124
+ verifier: { name: 'tinfoil-js', version: '1.1.12' },
125
+ documentation_url: 'https://nano-gpt.com/private-mode-verification',
126
+ known_limitations: [...PRIVATE_MODE_KNOWN_LIMITATIONS],
127
+ ...(verificationStatus !== 'verified' && verificationStatus !== 'not_checked'
128
+ ? { error_code: verificationStatus }
129
+ : {}),
130
+ },
53
131
  };
54
132
  }
@@ -2,6 +2,9 @@ import { createHash } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { fetchAttestationBundle, Verifier } from 'tinfoil';
4
4
 
5
+ const PRIVATE_MODE_ATTESTATION_MAX_AGE_MS = 5 * 60 * 1000;
6
+ const PRIVATE_MODE_RECEIPT_CLOCK_SKEW_MS = 60 * 1000;
7
+
5
8
  function isRecord(value) {
6
9
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
7
10
  }
@@ -84,7 +87,7 @@ function assertReceiptFlag(failures, label, value) {
84
87
  }
85
88
 
86
89
  export async function verifyPrivateModeReceipt(receipt) {
87
- if (!isRecord(receipt) || receipt.schemaVersion !== 1) {
90
+ if (!isRecord(receipt) || (receipt.schemaVersion !== 1 && receipt.schemaVersion !== 2)) {
88
91
  throw new Error('Unsupported or invalid Private Mode receipt.');
89
92
  }
90
93
 
@@ -113,6 +116,29 @@ export async function verifyPrivateModeReceipt(receipt) {
113
116
  if (receipt.status !== 'verified') {
114
117
  failures.push(`Receipt status is ${receipt.status || 'missing'}; expected verified.`);
115
118
  }
119
+ if (receipt.schemaVersion === 2) {
120
+ if (receipt.assuranceLevel !== 'private_mode') {
121
+ failures.push('Receipt assurance level is not private_mode.');
122
+ }
123
+ const verifiedAt = Date.parse(receipt.verifiedAt);
124
+ const expiresAt = Date.parse(receipt.expiresAt);
125
+ const now = Date.now();
126
+ if (
127
+ !Number.isFinite(verifiedAt)
128
+ || !Number.isFinite(expiresAt)
129
+ || expiresAt <= verifiedAt
130
+ || expiresAt - verifiedAt > PRIVATE_MODE_ATTESTATION_MAX_AGE_MS
131
+ || verifiedAt > now + PRIVATE_MODE_RECEIPT_CLOCK_SKEW_MS
132
+ ) {
133
+ failures.push('Receipt freshness timestamps are invalid.');
134
+ } else if (expiresAt <= now) {
135
+ warnings.push('Receipt attestation freshness window has expired; fresh evidence was verified again now.');
136
+ }
137
+ const requestIdSha256 = receipt.response?.requestIdSha256;
138
+ if (requestIdSha256 !== undefined && !/^[a-f0-9]{64}$/i.test(requestIdSha256)) {
139
+ failures.push('Request identifier binding is not a SHA-256 digest.');
140
+ }
141
+ }
116
142
 
117
143
  const encryption = isRecord(receipt.encryption) ? receipt.encryption : {};
118
144
  if (encryption.transport !== 'EHBP') {
@@ -126,6 +152,16 @@ export async function verifyPrivateModeReceipt(receipt) {
126
152
  assertRequiredEqual(failures, 'Runtime measurement', doc?.enclaveFingerprint, receipt.verifier?.enclaveFingerprint);
127
153
  assertRequiredEqual(failures, 'Release digest', doc?.releaseDigest, receipt.verifier?.releaseDigest);
128
154
  assertRequiredEqual(failures, 'HPKE public key', doc?.hpkePublicKey, receipt.verifier?.hpkePublicKey);
155
+ if (receipt.schemaVersion === 2) {
156
+ assertRequiredEqual(
157
+ failures,
158
+ 'HPKE public-key fingerprint',
159
+ isNonEmptyString(doc?.hpkePublicKey)
160
+ ? createHash('sha256').update(doc.hpkePublicKey).digest('hex')
161
+ : undefined,
162
+ receipt.verifier?.hpkePublicKeySha256,
163
+ );
164
+ }
129
165
  assertOptionalEqual(warnings, 'Attestation bundle SHA-256', attestationBundleSha256, receipt.verifier?.attestationBundleSha256);
130
166
 
131
167
  return {
@@ -137,6 +173,7 @@ export async function verifyPrivateModeReceipt(receipt) {
137
173
  enclaveFingerprint: doc?.enclaveFingerprint,
138
174
  attestationBundleSha256,
139
175
  verificationDocumentSha256,
176
+ schemaVersion: receipt.schemaVersion,
140
177
  };
141
178
  }
142
179
 
@@ -155,6 +192,7 @@ export async function verifyPrivateModeReceiptCli(positionals, io = process) {
155
192
  }
156
193
 
157
194
  io.stdout.write('Private Mode receipt verified.\n');
195
+ io.stdout.write(`Receipt schema: v${result.schemaVersion}\n`);
158
196
  io.stdout.write(`Enclave: ${result.enclaveURL}\n`);
159
197
  io.stdout.write(`Expected measurement: ${result.codeFingerprint}\n`);
160
198
  io.stdout.write(`Runtime measurement: ${result.enclaveFingerprint}\n`);
@@ -4,6 +4,7 @@
4
4
  "name": "DeepSeek V4 Flash Private",
5
5
  "upstreamModel": "deepseek-v4-flash",
6
6
  "billingModel": "private/deepseek-v4-flash",
7
+ "providerPricingModel": "TEE/deepseek-v4-flash",
7
8
  "teeTargetModel": "deepseek-v4-flash",
8
9
  "thinkingMode": "deepseek-v4",
9
10
  "maxOutputTokens": 1048576,
@@ -16,8 +17,10 @@
16
17
  "name": "Kimi K3 Private",
17
18
  "upstreamModel": "kimi-k3",
18
19
  "billingModel": "TEE/kimi-k3",
20
+ "providerPricingModel": "TEE/kimi-k3",
19
21
  "teeTargetModel": "kimi-k3",
20
- "maxOutputTokens": 1048576,
22
+ "maxInputTokens": 256000,
23
+ "maxOutputTokens": 256000,
21
24
  "created": 1786147200,
22
25
  "ownedBy": "nanogpt-private-mode",
23
26
  "aliases": ["TEE/kimi-k3"]
@@ -27,6 +30,7 @@
27
30
  "name": "GPT OSS 120B Private",
28
31
  "upstreamModel": "gpt-oss-120b",
29
32
  "billingModel": "TEE/gpt-oss-120b",
33
+ "providerPricingModel": "openai/gpt-oss-120b",
30
34
  "teeTargetModel": "gpt-oss-120b",
31
35
  "created": 1764547200,
32
36
  "ownedBy": "nanogpt-private-mode",
@@ -37,6 +41,7 @@
37
41
  "name": "Llama 3.3 70B Private",
38
42
  "upstreamModel": "llama3-3-70b",
39
43
  "billingModel": "TEE/llama3-3-70b",
44
+ "providerPricingModel": "meta-llama/llama-3.3-70b-instruct",
40
45
  "teeTargetModel": "llama3-3-70b",
41
46
  "created": 1764547200,
42
47
  "ownedBy": "nanogpt-private-mode",
@@ -47,6 +52,7 @@
47
52
  "name": "GLM 5.2 Private",
48
53
  "upstreamModel": "glm-5-2",
49
54
  "billingModel": "TEE/glm-5-2",
55
+ "providerPricingModel": "TEE/glm-5.2",
50
56
  "teeTargetModel": "glm-5-2",
51
57
  "thinkingMode": "glm-5.2",
52
58
  "created": 1781827200,
@@ -58,6 +64,7 @@
58
64
  "name": "GLM 5.2 Thinking Private",
59
65
  "upstreamModel": "glm-5-2",
60
66
  "billingModel": "TEE/glm-5-2:thinking",
67
+ "providerPricingModel": "TEE/glm-5.2:thinking",
61
68
  "teeTargetModel": "glm-5-2",
62
69
  "thinkingMode": "glm-5.2",
63
70
  "created": 1781827200,
@@ -69,6 +76,7 @@
69
76
  "name": "Gemma 4 31B Private",
70
77
  "upstreamModel": "gemma4-31b",
71
78
  "billingModel": "TEE/gemma4-31b",
79
+ "providerPricingModel": "TEE/gemma4-31b",
72
80
  "teeTargetModel": "gemma4-31b",
73
81
  "thinkingMode": "gemma",
74
82
  "created": 1764547200,
@@ -80,6 +88,7 @@
80
88
  "name": "Gemma 4 31B Thinking Private",
81
89
  "upstreamModel": "gemma4-31b",
82
90
  "billingModel": "TEE/gemma4-31b:thinking",
91
+ "providerPricingModel": "TEE/gemma4-31b:thinking",
83
92
  "teeTargetModel": "gemma4-31b",
84
93
  "thinkingMode": "gemma",
85
94
  "created": 1764547200,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanogpt/private-mode",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "OpenAI-compatible localhost proxy for NanoGPT Private Mode.",
5
5
  "type": "module",
6
6
  "publishConfig": {