@premai/api-sdk 1.0.41 → 1.0.43

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
@@ -34,6 +34,7 @@ Environment Variables:
34
34
  * ENCLAVE_URL
35
35
  * PROXY_URL
36
36
  * CLIENT_KEK
37
+ * JSON_BODY_LIMIT (optional, default: `32mb`)
37
38
  * HOST (optional)
38
39
  * PORT (optional)
39
40
 
@@ -42,8 +43,8 @@ Run as a standalone server with automatic DEK store management per API key:
42
43
  ```bash
43
44
  # ways to run the termination proxy
44
45
  ## run command from local/remote NPM package
45
- bunx -p @premai/api-sdk pcci-proxy
46
- npx -p @premai/api-sdk pcci-proxy
46
+ bunx -p @premai/api-sdk confidential-proxy
47
+ npx -p @premai/api-sdk confidential-proxy
47
48
 
48
49
  ## install globally
49
50
  ## make sure to have the "global bin dir" in your PATH
@@ -51,13 +52,54 @@ npx -p @premai/api-sdk pcci-proxy
51
52
  npm i -g @premai/api-sdk
52
53
  bun i -g @premai/api-sdk
53
54
 
54
- # Server runs on http://localhost:3000
55
+ # Server runs on http://127.0.0.1:8000
55
56
  ```
56
57
 
57
- Use with any OpenAI-compatible client:
58
+ ### CLI Options
58
59
 
59
60
  ```bash
60
- curl http://localhost:3000/v1/chat/completions \
61
+ # Basic usage
62
+ confidential-proxy --host 127.0.0.1 --port 8000
63
+
64
+ # Anthropic compatibility mode
65
+ confidential-proxy --compat anthropic
66
+
67
+ # Both OpenAI and Anthropic (served under /openai and /anthropic prefixes)
68
+ confidential-proxy --compat both
69
+
70
+ # Custom route prefixes
71
+ confidential-proxy --compat both --openai-prefix /openai --anthropic-prefix /anthropic
72
+
73
+ # Custom backend URLs
74
+ confidential-proxy --proxy-url https://proxy.example.com --enclave-url https://enclave.example.com
75
+
76
+ # Client KEK
77
+ confidential-proxy --kek your-kek
78
+
79
+ # JSON body size limit
80
+ confidential-proxy --json-body-limit 64mb
81
+ ```
82
+
83
+ ### Compatibility Modes
84
+
85
+ The server supports three compatibility modes via `--compat`:
86
+
87
+ | Mode | Description | Routes |
88
+ |------|-------------|--------|
89
+ | `openai` | OpenAI-compatible API only | `/v1/*` |
90
+ | `anthropic` | Anthropic-compatible API only | `/v1/*` |
91
+ | `both` | Both APIs side-by-side | `/openai/v1/*` and `/anthropic/v1/*` |
92
+
93
+ When using `--compat both`, the server exposes both OpenAI and Anthropic APIs under separate route prefixes to avoid conflicts. Custom prefixes can be set with `--openai-prefix` and `--anthropic-prefix`.
94
+
95
+ The Anthropic API translates incoming Anthropic Messages requests to the internal OpenAI-compatible enclave, then pipes the response back as Anthropic SSE events.
96
+
97
+ ### OpenAI API Usage
98
+
99
+ Use with any OpenAI-compatible client via curl:
100
+
101
+ ```bash
102
+ curl http://127.0.0.1:8000/v1/chat/completions \
61
103
  -H "Authorization: Bearer your-api-key" \
62
104
  -H "Content-Type: application/json" \
63
105
  -d '{
@@ -76,7 +118,7 @@ import OpenAI from "openai";
76
118
 
77
119
  const client = new OpenAI({
78
120
  apiKey: "your-api-key",
79
- baseURL: "http://localhost:3000/v1",
121
+ baseURL: "http://127.0.0.1:8000/v1",
80
122
  });
81
123
 
82
124
  const stream = await client.chat.completions.create({
@@ -90,19 +132,92 @@ for await (const chunk of stream) {
90
132
  }
91
133
  ```
92
134
 
135
+ ### Anthropic API Usage
136
+
137
+ When `--compat anthropic` or `--compat both`, the server exposes an Anthropic-compatible Messages API:
138
+
139
+ ```bash
140
+ curl http://127.0.0.1:8000/v1/messages \
141
+ -H "x-api-key: your-api-key" \
142
+ -H "anthropic-version: 2023-06-01" \
143
+ -H "Content-Type: application/json" \
144
+ -d '{
145
+ "model": "claude-sonnet-4-6",
146
+ "max_tokens": 1024,
147
+ "messages": [
148
+ {"role": "user", "content": "Hello!"}
149
+ ]
150
+ }'
151
+ ```
152
+
153
+ With streaming:
154
+
155
+ ```bash
156
+ curl -N http://127.0.0.1:8000/v1/messages \
157
+ -H "x-api-key: your-api-key" \
158
+ -H "anthropic-version: 2023-06-01" \
159
+ -H "Content-Type: application/json" \
160
+ -d '{
161
+ "model": "claude-sonnet-4-6",
162
+ "max_tokens": 1024,
163
+ "messages": [
164
+ {"role": "user", "content": "Count to 10"}
165
+ ],
166
+ "stream": true
167
+ }'
168
+ ```
169
+
170
+ The server supports system prompts, tool use, image inputs, stop sequences, temperature, and top_p. Response events follow the Anthropic SSE format (`message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta`, `message_stop`).
171
+
93
172
  The server caches clients in memory per API key for better performance.
94
173
 
174
+ ## confidential-claude — Claude Code Launcher
175
+
176
+ Run [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) through the encrypted proxy with zero configuration:
177
+
178
+ ```bash
179
+ # run without installing
180
+ bunx -p @premai/api-sdk confidential-claude
181
+ npx -p @premai/api-sdk confidential-claude
182
+
183
+ # or install globally first
184
+ npm i -g @premai/api-sdk
185
+ bun i -g @premai/api-sdk
186
+
187
+ confidential-claude
188
+ ```
189
+
190
+ The launcher:
191
+
192
+ 1. Reads `.env` from application directory or environment variables (prompts interactively if missing)
193
+ 2. Spawns an encrypted proxy subprocess in Anthropic-only mode
194
+ 3. Fetches the available model list and lets you pick one
195
+ 4. Launches `claude` wired to the confidential gateway
196
+
197
+ All traffic remains end-to-end encrypted. The proxy subprocess is cleaned up when Claude Code exits.
198
+
199
+ Any extra arguments are forwarded to `claude` directly:
200
+
201
+ ```bash
202
+ # Run in a specific directory
203
+ confidential-claude -p /path/to/project
204
+
205
+ # Run a one-shot command
206
+ confidential-claude --print "summarize the changed files"
207
+ ```
208
+
95
209
  ## Features
96
210
 
97
- - ✅ **Chat Completions** - OpenAI-compatible API with streaming support
98
- - ✅ **Models** - List available models
99
- - ✅ **File Management** - Upload, list, get, and delete encrypted files
100
- - ✅ **File Upload** - Client-side encrypted file uploads with per-file DEKs
101
- - ✅ **Tools** - Image generation, transcription, RAG search, and more
102
- - ✅ **End-to-end Encryption** - Post-quantum cryptography (XWing)
103
- - ✅ **KEK Architecture** - Key Encryption Key wraps all file DEKs
104
- - ✅ **RAG Support** - Encrypted document retrieval with persistent RAG DEK
105
- - **TypeScript** - Full type safety
211
+ - Chat Completions - OpenAI and Anthropic compatible APIs with streaming support
212
+ - Models - List available models
213
+ - File Management - Upload, list, get, and delete encrypted files
214
+ - File Upload - Client-side encrypted file uploads with per-file DEKs
215
+ - Tools - Image generation, transcription, RAG search, and more
216
+ - End-to-end Encryption - Post-quantum cryptography (XWing)
217
+ - KEK Architecture - Key Encryption Key wraps all file DEKs
218
+ - RAG Support - Encrypted document retrieval with persistent RAG DEK
219
+ - Attestation - Enclave attestation is always enabled for security
220
+ - TypeScript - Full type safety
106
221
 
107
222
  ## Chat Completions
108
223
 
@@ -224,7 +339,7 @@ const client = await createRvencClient({
224
339
  const serializedStore = serializeDEKStore(client.dekStore);
225
340
  fs.writeFileSync("./dek-store.json", serializedStore);
226
341
 
227
- console.log("DEK store saved. Keep this file secure!");
342
+ console.log("DEK store saved. Keep this file secure!");
228
343
  ```
229
344
 
230
345
  ### Subsequent Usage
@@ -263,7 +378,7 @@ console.log("Uploaded:", result.id);
263
378
  // ⚠️ IMPORTANT: Save dekStore after upload to persist file DEKs
264
379
  const serialized = serializeDEKStore(client.dekStore);
265
380
  fs.writeFileSync("./dek-store.json", serialized);
266
- console.log("DEK store updated with file encryption keys");
381
+ console.log("DEK store updated with file encryption keys");
267
382
  ```
268
383
 
269
384
  ### Upload Files with RAG Indexing
@@ -357,7 +472,7 @@ console.log(file.mime_type);
357
472
  console.log(file.created_at);
358
473
 
359
474
  // Get file with signed download URL
360
- const fileWithUrl = await client.files.get({
475
+ const fileWithUrl = await client.files.get({
361
476
  id: 'file-id-123',
362
477
  url: true // Include signed download URL
363
478
  });
@@ -409,9 +524,9 @@ const indexResult = await client.files.index({
409
524
  // Check results
410
525
  indexResult.data.results.forEach(result => {
411
526
  if (result.success) {
412
- console.log(`✓ File ${result.file_id}: ${result.rag_status}`);
527
+ console.log(`File ${result.file_id}: ${result.rag_status}`);
413
528
  } else {
414
- console.error(`✗ File ${result.file_id} failed: ${result.error}`);
529
+ console.error(`File ${result.file_id} failed: ${result.error}`);
415
530
  }
416
531
  });
417
532
  ```
@@ -440,14 +555,14 @@ await client.files.index({
440
555
  // Option 3: Provide custom fileDEK for specific files
441
556
  await client.files.index({
442
557
  files: [
443
- {
444
- fileId: 'file-id-1',
558
+ {
559
+ fileId: 'file-id-1',
445
560
  filePath: 's3/path/file1.enc',
446
561
  fileDEK: customFileDEK // Custom DEK for this file
447
562
  },
448
- {
449
- fileId: 'file-id-2',
450
- filePath: 's3/path/file2.enc'
563
+ {
564
+ fileId: 'file-id-2',
565
+ filePath: 's3/path/file2.enc'
451
566
  // Uses dekStore for this file
452
567
  },
453
568
  ],
@@ -504,9 +619,9 @@ const deleteResult = await client.files.deleteIndex({
504
619
  // Check results
505
620
  deleteResult.data.results.forEach(result => {
506
621
  if (result.success) {
507
- console.log(`✓ File ${result.file_id} removed from index`);
622
+ console.log(`File ${result.file_id} removed from index`);
508
623
  } else {
509
- console.error(`✗ File ${result.file_id} failed: ${result.error}`);
624
+ console.error(`File ${result.file_id} failed: ${result.error}`);
510
625
  }
511
626
  });
512
627
  ```
@@ -701,8 +816,9 @@ console.log(ragResults);
701
816
  |--------|------|---------|-------------|
702
817
  | `apiKey` | `string` | **required** | Authorization token |
703
818
  | `encryptionKeys` | `EncryptionKeys` | auto-generated | Pre-generated ML-KEM keys |
819
+ | `clientKEK` | `string` | `CLIENT_KEK` env | Key Encryption Key for wrapping file DEKs |
704
820
  | `dekStore` | `DEKStore` | auto-generated | DEKs for files and RAG |
705
- | `requestTimeoutMs` | `number` | `30000` | Request timeout in milliseconds |
821
+ | `requestTimeoutMs` | `number` | `600000` | Request timeout in milliseconds (10 min) |
706
822
  | `maxBufferSize` | `number` | `10485760` | Max SSE buffer size (10MB) |
707
823
 
708
824
  ## DEK Store Management
@@ -775,6 +891,15 @@ fs.writeFileSync("dek-store.json", serialized);
775
891
  3. **Encrypted Transport**: Sends `{ cipherText, encryptedInference, nonce }` to proxy
776
892
  4. **Response Decryption**: Decrypts response (streaming SSE or JSON) using shared secret
777
893
 
894
+ ### Anthropic API Compatibility
895
+
896
+ The server translates Anthropic Messages requests to OpenAI-compatible format internally:
897
+
898
+ 1. **Request conversion**: Anthropic system blocks, tool_use, image inputs, and tool_result blocks are mapped to OpenAI equivalents
899
+ 2. **Enclave call**: The translated request is dispatched through the encrypted OpenAI pipeline
900
+ 3. **Response conversion**: OpenAI completions (or chunk streams) are translated back to Anthropic SSE events with proper `content_block_start`, `content_block_delta`, and `content_block_stop` framing
901
+ 4. **Tool streaming**: OpenAI tool_call chunks are replayed as Anthropic `input_json_delta` events with incremental argument flushing
902
+
778
903
  ### File Upload
779
904
 
780
905
  1. **DEK Generation**: Generates random 32-byte DEK for this file
@@ -801,7 +926,7 @@ fs.writeFileSync("dek-store.json", serialized);
801
926
  4. **Enclave Decrypts**: Enclave decrypts fileDEK, then decrypts file from S3
802
927
  5. **Response**: Returns encrypted result, SDK decrypts with shared secret
803
928
 
804
- #### File-Producing Tools
929
+ #### File-Producing Tools
805
930
  1. **Generate DEK**: Creates random DEK for output file
806
931
  2. **Encrypt Request**: Encrypts tool parameters with shared secret
807
932
  3. **Enclave Executes**: Generates file, encrypts with provided DEK
@@ -824,10 +949,10 @@ try {
824
949
  apiKey: "your-api-key",
825
950
  clientKEK: "your-kek" // You can pass clientKEK here or as an env variable CLIENT_KEK
826
951
  });
827
-
952
+
828
953
  const image = await client.tools.generateImage("mountain landscape");
829
954
  fs.writeFileSync(image.fileName, image.content);
830
-
955
+
831
956
  } catch (error) {
832
957
  if (error.message.includes("timeout")) {
833
958
  console.error("Request timed out");
@@ -839,14 +964,15 @@ try {
839
964
 
840
965
  ## Security Notes
841
966
 
842
- - ⚠️ **Use environment variables** for API keys & client KEK
843
- - ⚠️ **Always persist dekStore after file uploads** - file DEKs must be saved
844
- - ⚠️ **Backup your dekStore** - losing it means losing access to uploaded files
845
- - All encryption happens client-side (zero-knowledge)
846
- - Files are encrypted with unique DEKs before upload
847
- - KEK-based architecture - KEK wraps all file DEKs
848
- - Tool responses are automatically decrypted
849
- - Post-quantum secure (XWing: ML-KEM768 + X25519)
967
+ - **Use environment variables** for API keys & client KEK
968
+ - **Always persist dekStore after file uploads** - file DEKs must be saved
969
+ - **Backup your dekStore** - losing it means losing access to uploaded files
970
+ - All encryption happens client-side (zero-knowledge)
971
+ - Files are encrypted with unique DEKs before upload
972
+ - KEK-based architecture - KEK wraps all file DEKs
973
+ - Tool responses are automatically decrypted
974
+ - Post-quantum secure (XWing: ML-KEM768 + X25519)
975
+ - Enclave attestation is always enabled and cannot be disabled
850
976
 
851
977
  ### Encryption Architecture
852
978
 
@@ -897,3 +1023,22 @@ import type {
897
1023
  PaginationInfo,
898
1024
  } from "@premai/api-sdk";
899
1025
  ```
1026
+
1027
+ ## BareKit (mobile)
1028
+
1029
+ When running this SDK inside a [react-native-bare-kit](https://github.com/holepunchto/react-native-bare-kit) worklet, pass an `assets` directory to `new Worklet(...)`. bare-kit extracts bundled assets to that directory at startup and rewrites resolutions to real `file://` URLs — this lets WASM to load directly from disk on the fast path:
1030
+
1031
+ ```js
1032
+ import { Worklet } from 'react-native-bare-kit';
1033
+ import * as FileSystem from 'expo-file-system'
1034
+
1035
+ const cacheDir = new FileSystem.Directory(FileSystem.Paths.cache, 'reticle-assets')
1036
+ if (!cacheDir.exists) cacheDir.create()
1037
+
1038
+ const worklet = new Worklet({
1039
+ assets: cacheDir.uri.replace(/^file:\/\//, ''),
1040
+ });
1041
+ await worklet.start('/worklet.bundle', source);
1042
+ ```
1043
+
1044
+ If `assets` is omitted, the SDK falls back to an inline base64 copy of the WASM bytes shipped inside the module.
@@ -0,0 +1,3 @@
1
+ import type { Router } from "express";
2
+ import type { RvencServerDeps } from "../server/create-app";
3
+ export declare function registerAnthropicCountTokensRoute(router: Router, _deps: RvencServerDeps): void;
@@ -0,0 +1,33 @@
1
+ import type { Response } from "express";
2
+ import type OpenAI from "openai";
3
+ export type AnthropicUsage = {
4
+ input_tokens: number;
5
+ output_tokens: number;
6
+ };
7
+ export type AnthropicTextContentBlock = {
8
+ type: "text";
9
+ text: string;
10
+ };
11
+ export type AnthropicToolUseContentBlock = {
12
+ type: "tool_use";
13
+ id: string;
14
+ name: string;
15
+ input: unknown;
16
+ };
17
+ export type AnthropicAssistantContentBlock = AnthropicTextContentBlock | AnthropicToolUseContentBlock;
18
+ export type AnthropicMessagePayload = {
19
+ id: string;
20
+ type: "message";
21
+ role: "assistant";
22
+ content: AnthropicAssistantContentBlock[];
23
+ model: string;
24
+ stop_reason: AnthropicStopReason | null;
25
+ stop_sequence: string | null;
26
+ usage: AnthropicUsage;
27
+ };
28
+ export type AnthropicStopReason = "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal";
29
+ export declare function openAIChatCompletionToAnthropicMessage(completion: OpenAI.Chat.ChatCompletion, requestModel: string): AnthropicMessagePayload;
30
+ export declare function pipeOpenAIChunkStreamToAnthropicSse(res: Response, stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>, options: {
31
+ anthropicModel: string;
32
+ messageId: string;
33
+ }): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import type { Request, Response } from "express";
2
+ export declare const ANTHROPIC_VERSION_DEFAULT = "2023-06-01";
3
+ export declare function isAnthropicApiVersionSupported(version: string): boolean;
4
+ export declare function newAnthropicRequestId(): string;
5
+ export declare function newAnthropicMessageId(): string;
6
+ export declare function extractAnthropicApiKey(req: Request): string | null;
7
+ export declare function getAnthropicVersionHeader(req: Request): string | null;
8
+ export type AnthropicVersionResult = {
9
+ ok: true;
10
+ version: string;
11
+ } | {
12
+ ok: false;
13
+ message: string;
14
+ };
15
+ export declare function resolveAnthropicVersion(req: Request): AnthropicVersionResult;
16
+ export declare function sendAnthropicHttpError(res: Response, status: number, errorType: string, message: string, requestId: string): void;
17
+ export declare function httpStatusToAnthropicErrorType(status: number): string;
18
+ export declare function mapUnknownErrorToAnthropicResponse(err: unknown, res: Response, requestId: string): void;
19
+ export declare function writeAnthropicSseEvent(res: Response, event: string, data: Record<string, unknown>): void;
@@ -0,0 +1,3 @@
1
+ import type { Router } from "express";
2
+ import type { RvencServerDeps } from "../server/create-app";
3
+ export declare function registerAnthropicMessagesRoute(router: Router, deps: RvencServerDeps): void;
@@ -0,0 +1,3 @@
1
+ import type { Router } from "express";
2
+ import type { RvencServerDeps } from "../server/create-app";
3
+ export declare function registerAnthropicModelsRoute(router: Router, deps: RvencServerDeps): void;
@@ -0,0 +1,35 @@
1
+ import type OpenAI from "openai";
2
+ export type AnthropicTextBlock = {
3
+ type: "text";
4
+ text: string;
5
+ };
6
+ type AnthropicContentPart = AnthropicTextBlock | {
7
+ type: string;
8
+ [key: string]: unknown;
9
+ };
10
+ export type AnthropicMessage = {
11
+ role: "user" | "assistant";
12
+ content: string | AnthropicContentPart[];
13
+ };
14
+ export type AnthropicMessagesCreateBody = {
15
+ model: string;
16
+ max_tokens: number;
17
+ messages: AnthropicMessage[];
18
+ system?: string | AnthropicTextBlock | AnthropicTextBlock[];
19
+ stream?: boolean;
20
+ metadata?: unknown;
21
+ stop_sequences?: string[];
22
+ temperature?: number;
23
+ top_p?: number;
24
+ top_k?: number;
25
+ tools?: unknown;
26
+ tool_choice?: unknown;
27
+ [key: string]: unknown;
28
+ };
29
+ export declare class AnthropicRequestValidationError extends Error {
30
+ readonly status = 400;
31
+ readonly anthropicType = "invalid_request_error";
32
+ constructor(message: string);
33
+ }
34
+ export declare function anthropicMessagesCreateToOpenAI(body: AnthropicMessagesCreateBody): OpenAI.Chat.ChatCompletionCreateParams;
35
+ export {};