@zeke-02/tinfoil 0.0.2
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/LICENSE +661 -0
- package/README.md +169 -0
- package/dist/__tests__/test-utils.d.ts +1 -0
- package/dist/__tests__/test-utils.js +44 -0
- package/dist/ai-sdk-provider.d.ts +7 -0
- package/dist/ai-sdk-provider.js +23 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +20 -0
- package/dist/encrypted-body-fetch.d.ts +8 -0
- package/dist/encrypted-body-fetch.js +93 -0
- package/dist/env.d.ts +5 -0
- package/dist/env.js +20 -0
- package/dist/esm/__tests__/test-utils.d.ts +1 -0
- package/dist/esm/__tests__/test-utils.js +38 -0
- package/dist/esm/ai-sdk-provider.d.ts +7 -0
- package/dist/esm/ai-sdk-provider.js +20 -0
- package/dist/esm/config.d.ts +17 -0
- package/dist/esm/config.js +17 -0
- package/dist/esm/encrypted-body-fetch.d.ts +8 -0
- package/dist/esm/encrypted-body-fetch.js +86 -0
- package/dist/esm/env.d.ts +5 -0
- package/dist/esm/env.js +17 -0
- package/dist/esm/fetch-adapter.d.ts +21 -0
- package/dist/esm/fetch-adapter.js +23 -0
- package/dist/esm/index.browser.d.ts +7 -0
- package/dist/esm/index.browser.js +8 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/pinned-tls-fetch.d.ts +1 -0
- package/dist/esm/pinned-tls-fetch.js +110 -0
- package/dist/esm/secure-client.d.ts +20 -0
- package/dist/esm/secure-client.js +123 -0
- package/dist/esm/secure-fetch.browser.d.ts +1 -0
- package/dist/esm/secure-fetch.browser.js +10 -0
- package/dist/esm/secure-fetch.d.ts +1 -0
- package/dist/esm/secure-fetch.js +22 -0
- package/dist/esm/tinfoilai.d.ts +54 -0
- package/dist/esm/tinfoilai.js +134 -0
- package/dist/esm/unverified-client.d.ts +18 -0
- package/dist/esm/unverified-client.js +33 -0
- package/dist/esm/verifier.d.ts +141 -0
- package/dist/esm/verifier.js +741 -0
- package/dist/esm/wasm-exec.js +668 -0
- package/dist/fetch-adapter.d.ts +21 -0
- package/dist/fetch-adapter.js +27 -0
- package/dist/index.browser.d.ts +7 -0
- package/dist/index.browser.js +29 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +49 -0
- package/dist/pinned-tls-fetch.d.ts +1 -0
- package/dist/pinned-tls-fetch.js +116 -0
- package/dist/secure-client.d.ts +20 -0
- package/dist/secure-client.js +127 -0
- package/dist/secure-fetch.browser.d.ts +1 -0
- package/dist/secure-fetch.browser.js +13 -0
- package/dist/secure-fetch.d.ts +1 -0
- package/dist/secure-fetch.js +25 -0
- package/dist/tinfoilai.d.ts +54 -0
- package/dist/tinfoilai.js +141 -0
- package/dist/unverified-client.d.ts +18 -0
- package/dist/unverified-client.js +37 -0
- package/dist/verifier.d.ts +141 -0
- package/dist/verifier.js +781 -0
- package/dist/wasm-exec.js +668 -0
- package/package.json +97 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch adapter for Tauri v2
|
|
3
|
+
*
|
|
4
|
+
* This module provides a centralized fetch implementation for Tauri v2.
|
|
5
|
+
* For testing purposes, the fetch function can be overridden by setting
|
|
6
|
+
* the global __TINFOIL_TEST_FETCH__ property.
|
|
7
|
+
*/
|
|
8
|
+
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
|
|
9
|
+
/**
|
|
10
|
+
* Get the fetch implementation to use.
|
|
11
|
+
* In tests, this can be overridden by setting globalThis.__TINFOIL_TEST_FETCH__
|
|
12
|
+
*/
|
|
13
|
+
export function getFetch() {
|
|
14
|
+
if (typeof globalThis.__TINFOIL_TEST_FETCH__ === "function") {
|
|
15
|
+
return globalThis.__TINFOIL_TEST_FETCH__;
|
|
16
|
+
}
|
|
17
|
+
return tauriFetch;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The fetch function to use throughout the application.
|
|
21
|
+
* Uses Tauri's fetch by default, but can be mocked for testing.
|
|
22
|
+
*/
|
|
23
|
+
export const fetch = getFetch();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { TinfoilAI } from "./tinfoilai";
|
|
2
|
+
export { TinfoilAI as default } from "./tinfoilai";
|
|
3
|
+
export * from "./verifier";
|
|
4
|
+
export * from "./ai-sdk-provider";
|
|
5
|
+
export * from "./config";
|
|
6
|
+
export { SecureClient } from "./secure-client";
|
|
7
|
+
export { UnverifiedClient } from "./unverified-client";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Browser-safe entry point: avoids Node built-ins
|
|
2
|
+
export { TinfoilAI } from "./tinfoilai";
|
|
3
|
+
export { TinfoilAI as default } from "./tinfoilai";
|
|
4
|
+
export * from "./verifier";
|
|
5
|
+
export * from "./ai-sdk-provider";
|
|
6
|
+
export * from "./config";
|
|
7
|
+
export { SecureClient } from "./secure-client";
|
|
8
|
+
export { UnverifiedClient } from "./unverified-client";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { TinfoilAI } from "./tinfoilai";
|
|
2
|
+
export { TinfoilAI as default } from "./tinfoilai";
|
|
3
|
+
export * from "./verifier";
|
|
4
|
+
export * from "./ai-sdk-provider";
|
|
5
|
+
export * from "./config";
|
|
6
|
+
export { SecureClient } from "./secure-client";
|
|
7
|
+
export { UnverifiedClient } from "./unverified-client";
|
|
8
|
+
export { type Uploadable, toFile, APIPromise, PagePromise, OpenAIError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, NotFoundError, ConflictError, RateLimitError, BadRequestError, AuthenticationError, InternalServerError, PermissionDeniedError, UnprocessableEntityError, } from "openai";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Re-export the TinfoilAI class
|
|
2
|
+
export { TinfoilAI } from "./tinfoilai";
|
|
3
|
+
export { TinfoilAI as default } from "./tinfoilai";
|
|
4
|
+
// Export verifier
|
|
5
|
+
export * from "./verifier";
|
|
6
|
+
export * from "./ai-sdk-provider";
|
|
7
|
+
export * from "./config";
|
|
8
|
+
export { SecureClient } from "./secure-client";
|
|
9
|
+
export { UnverifiedClient } from "./unverified-client";
|
|
10
|
+
// Re-export OpenAI utility types and classes that users might need
|
|
11
|
+
// Using public exports from the main OpenAI package instead of deep imports
|
|
12
|
+
export { toFile, APIPromise, PagePromise, OpenAIError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, NotFoundError, ConflictError, RateLimitError, BadRequestError, AuthenticationError, InternalServerError, PermissionDeniedError, UnprocessableEntityError, } from "openai";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createPinnedTlsFetch(baseURL: string, expectedFingerprintHex: string): typeof fetch;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import https from "https";
|
|
2
|
+
import { checkServerIdentity as tlsCheckServerIdentity } from "tls";
|
|
3
|
+
import { X509Certificate, createHash } from "crypto";
|
|
4
|
+
import { Readable } from "stream";
|
|
5
|
+
export function createPinnedTlsFetch(baseURL, expectedFingerprintHex) {
|
|
6
|
+
return (async (input, init) => {
|
|
7
|
+
// Normalize URL with base URL support
|
|
8
|
+
const makeURL = (value) => {
|
|
9
|
+
if (typeof value === "string")
|
|
10
|
+
return new URL(value, baseURL);
|
|
11
|
+
if (value instanceof URL)
|
|
12
|
+
return value;
|
|
13
|
+
return new URL(value.url, baseURL);
|
|
14
|
+
};
|
|
15
|
+
const url = makeURL(input);
|
|
16
|
+
if (url.protocol !== "https:") {
|
|
17
|
+
throw new Error(`HTTP connections are not allowed. Use HTTPS. URL: ${url.toString()}`);
|
|
18
|
+
}
|
|
19
|
+
// Gather method and headers
|
|
20
|
+
const method = (init?.method || input.method || "GET").toUpperCase();
|
|
21
|
+
const headers = new Headers(init?.headers || input?.headers || {});
|
|
22
|
+
const headerObj = {};
|
|
23
|
+
headers.forEach((v, k) => {
|
|
24
|
+
headerObj[k] = v;
|
|
25
|
+
});
|
|
26
|
+
// Resolve body
|
|
27
|
+
let body = init?.body;
|
|
28
|
+
if (!body && input instanceof Request) {
|
|
29
|
+
// If the original was a Request with a body, read it
|
|
30
|
+
try {
|
|
31
|
+
const buf = await input.arrayBuffer();
|
|
32
|
+
if (buf && buf.byteLength)
|
|
33
|
+
body = Buffer.from(buf);
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
}
|
|
37
|
+
// Convert web streams to Node streams if needed
|
|
38
|
+
if (body && typeof body.getReader === "function") {
|
|
39
|
+
body = Readable.fromWeb(body);
|
|
40
|
+
}
|
|
41
|
+
if (body instanceof ArrayBuffer) {
|
|
42
|
+
body = Buffer.from(body);
|
|
43
|
+
}
|
|
44
|
+
if (ArrayBuffer.isView(body)) {
|
|
45
|
+
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
46
|
+
}
|
|
47
|
+
const requestOptions = {
|
|
48
|
+
protocol: url.protocol,
|
|
49
|
+
hostname: url.hostname,
|
|
50
|
+
port: url.port ? Number(url.port) : 443,
|
|
51
|
+
path: `${url.pathname}${url.search}`,
|
|
52
|
+
method,
|
|
53
|
+
headers: headerObj,
|
|
54
|
+
checkServerIdentity: (host, cert) => {
|
|
55
|
+
const raw = cert.raw;
|
|
56
|
+
if (!raw) {
|
|
57
|
+
return new Error("Certificate raw bytes are unavailable for pinning");
|
|
58
|
+
}
|
|
59
|
+
const x509 = new X509Certificate(raw);
|
|
60
|
+
const publicKeyDer = x509.publicKey.export({ type: "spki", format: "der" });
|
|
61
|
+
const fp = createHash("sha256").update(publicKeyDer).digest("hex");
|
|
62
|
+
if (fp !== expectedFingerprintHex) {
|
|
63
|
+
return new Error(`Certificate public key fingerprint mismatch. Expected: ${expectedFingerprintHex}, Got: ${fp}`);
|
|
64
|
+
}
|
|
65
|
+
return tlsCheckServerIdentity(host, cert);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const { signal } = init || {};
|
|
69
|
+
const res = await new Promise((resolve, reject) => {
|
|
70
|
+
const req = https.request(requestOptions, resolve);
|
|
71
|
+
req.on("error", reject);
|
|
72
|
+
if (signal) {
|
|
73
|
+
if (signal.aborted) {
|
|
74
|
+
req.destroy(new Error("Request aborted"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
signal.addEventListener("abort", () => req.destroy(new Error("Request aborted")));
|
|
78
|
+
}
|
|
79
|
+
if (body === undefined || body === null) {
|
|
80
|
+
req.end();
|
|
81
|
+
}
|
|
82
|
+
else if (typeof body === "string" || Buffer.isBuffer(body) || ArrayBuffer.isView(body)) {
|
|
83
|
+
req.end(body);
|
|
84
|
+
}
|
|
85
|
+
else if (typeof body.pipe === "function") {
|
|
86
|
+
body.pipe(req);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Fallback: try to serialize objects
|
|
90
|
+
req.end(String(body));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
const responseHeaders = new Headers();
|
|
94
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
95
|
+
if (Array.isArray(v)) {
|
|
96
|
+
v.forEach(item => responseHeaders.append(k, item));
|
|
97
|
+
}
|
|
98
|
+
else if (v != null) {
|
|
99
|
+
responseHeaders.set(k, String(v));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Convert Node stream to Web ReadableStream
|
|
103
|
+
const webStream = Readable.toWeb(res);
|
|
104
|
+
return new Response(webStream, {
|
|
105
|
+
status: res.statusCode || 0,
|
|
106
|
+
statusText: res.statusMessage || "",
|
|
107
|
+
headers: responseHeaders,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { VerificationDocument } from "./verifier";
|
|
2
|
+
interface SecureClientOptions {
|
|
3
|
+
baseURL?: string;
|
|
4
|
+
enclaveURL?: string;
|
|
5
|
+
configRepo?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class SecureClient {
|
|
8
|
+
private initPromise;
|
|
9
|
+
private verificationDocument;
|
|
10
|
+
private _fetch;
|
|
11
|
+
private readonly baseURL?;
|
|
12
|
+
private readonly enclaveURL?;
|
|
13
|
+
private readonly configRepo?;
|
|
14
|
+
constructor(options?: SecureClientOptions);
|
|
15
|
+
ready(): Promise<void>;
|
|
16
|
+
private initSecureClient;
|
|
17
|
+
getVerificationDocument(): Promise<VerificationDocument>;
|
|
18
|
+
get fetch(): typeof fetch;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Verifier } from "./verifier";
|
|
2
|
+
import { TINFOIL_CONFIG } from "./config";
|
|
3
|
+
import { createSecureFetch } from "./secure-fetch";
|
|
4
|
+
export class SecureClient {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.initPromise = null;
|
|
7
|
+
this.verificationDocument = null;
|
|
8
|
+
this._fetch = null;
|
|
9
|
+
this.baseURL = options.baseURL || TINFOIL_CONFIG.INFERENCE_BASE_URL;
|
|
10
|
+
this.enclaveURL = options.enclaveURL || TINFOIL_CONFIG.ENCLAVE_URL;
|
|
11
|
+
this.configRepo = options.configRepo || TINFOIL_CONFIG.INFERENCE_PROXY_REPO;
|
|
12
|
+
}
|
|
13
|
+
async ready() {
|
|
14
|
+
if (!this.initPromise) {
|
|
15
|
+
this.initPromise = this.initSecureClient();
|
|
16
|
+
}
|
|
17
|
+
return this.initPromise;
|
|
18
|
+
}
|
|
19
|
+
async initSecureClient() {
|
|
20
|
+
const verifier = new Verifier({
|
|
21
|
+
serverURL: this.enclaveURL,
|
|
22
|
+
configRepo: this.configRepo,
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
await verifier.verify();
|
|
26
|
+
const doc = verifier.getVerificationDocument();
|
|
27
|
+
if (!doc) {
|
|
28
|
+
throw new Error("Verification document not available after successful verification");
|
|
29
|
+
}
|
|
30
|
+
this.verificationDocument = doc;
|
|
31
|
+
// Extract keys from the verification document
|
|
32
|
+
const { hpkePublicKey, tlsPublicKeyFingerprint } = this.verificationDocument.enclaveMeasurement;
|
|
33
|
+
try {
|
|
34
|
+
this._fetch = createSecureFetch(this.baseURL, this.enclaveURL, hpkePublicKey, tlsPublicKeyFingerprint);
|
|
35
|
+
}
|
|
36
|
+
catch (transportError) {
|
|
37
|
+
this.verificationDocument.steps.createTransport = {
|
|
38
|
+
status: "failed",
|
|
39
|
+
error: transportError.message,
|
|
40
|
+
};
|
|
41
|
+
this.verificationDocument.securityVerified = false;
|
|
42
|
+
throw transportError;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const doc = verifier.getVerificationDocument();
|
|
47
|
+
if (doc) {
|
|
48
|
+
this.verificationDocument = doc;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.verificationDocument = {
|
|
52
|
+
configRepo: this.configRepo,
|
|
53
|
+
enclaveHost: new URL(this.enclaveURL).hostname,
|
|
54
|
+
releaseDigest: "",
|
|
55
|
+
codeMeasurement: { type: "", registers: [] },
|
|
56
|
+
enclaveMeasurement: { measurement: { type: "", registers: [] } },
|
|
57
|
+
securityVerified: false,
|
|
58
|
+
steps: {
|
|
59
|
+
fetchDigest: { status: "pending" },
|
|
60
|
+
verifyCode: { status: "pending" },
|
|
61
|
+
verifyEnclave: { status: "pending" },
|
|
62
|
+
compareMeasurements: { status: "pending" },
|
|
63
|
+
otherError: { status: "failed", error: error.message },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async getVerificationDocument() {
|
|
71
|
+
if (!this.initPromise) {
|
|
72
|
+
await this.ready();
|
|
73
|
+
}
|
|
74
|
+
await this.initPromise.catch(() => { });
|
|
75
|
+
if (!this.verificationDocument) {
|
|
76
|
+
throw new Error("Verification document unavailable: client not verified yet");
|
|
77
|
+
}
|
|
78
|
+
return this.verificationDocument;
|
|
79
|
+
}
|
|
80
|
+
get fetch() {
|
|
81
|
+
return async (input, init) => {
|
|
82
|
+
await this.ready();
|
|
83
|
+
try {
|
|
84
|
+
return await this._fetch(input, init);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (this.verificationDocument) {
|
|
88
|
+
const errorMessage = error.message;
|
|
89
|
+
if (errorMessage.includes("HPKE public key mismatch")) {
|
|
90
|
+
this.verificationDocument.steps.verifyHPKEKey = {
|
|
91
|
+
status: "failed",
|
|
92
|
+
error: errorMessage,
|
|
93
|
+
};
|
|
94
|
+
this.verificationDocument.securityVerified = false;
|
|
95
|
+
}
|
|
96
|
+
else if (errorMessage.includes("Transport initialization failed") ||
|
|
97
|
+
errorMessage.includes("Request initialization failed")) {
|
|
98
|
+
this.verificationDocument.steps.createTransport = {
|
|
99
|
+
status: "failed",
|
|
100
|
+
error: errorMessage,
|
|
101
|
+
};
|
|
102
|
+
this.verificationDocument.securityVerified = false;
|
|
103
|
+
}
|
|
104
|
+
else if (errorMessage.includes("Failed to get HPKE key")) {
|
|
105
|
+
this.verificationDocument.steps.verifyHPKEKey = {
|
|
106
|
+
status: "failed",
|
|
107
|
+
error: errorMessage,
|
|
108
|
+
};
|
|
109
|
+
this.verificationDocument.securityVerified = false;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.verificationDocument.steps.otherError = {
|
|
113
|
+
status: "failed",
|
|
114
|
+
error: errorMessage,
|
|
115
|
+
};
|
|
116
|
+
this.verificationDocument.securityVerified = false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createSecureFetch(baseURL: string, enclaveURL?: string, hpkePublicKey?: string, tlsPublicKeyFingerprint?: string): typeof fetch;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createEncryptedBodyFetch } from "./encrypted-body-fetch";
|
|
2
|
+
export function createSecureFetch(baseURL, enclaveURL, hpkePublicKey, tlsPublicKeyFingerprint) {
|
|
3
|
+
if (hpkePublicKey) {
|
|
4
|
+
return createEncryptedBodyFetch(baseURL, hpkePublicKey, enclaveURL);
|
|
5
|
+
}
|
|
6
|
+
else {
|
|
7
|
+
throw new Error("HPKE public key not available and TLS-only verification is not supported in browsers. " +
|
|
8
|
+
"Only HPKE-enabled enclaves can be used in browser environments.");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createSecureFetch(baseURL: string, enclaveURL?: string, hpkePublicKey?: string, tlsPublicKeyFingerprint?: string): typeof fetch;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createEncryptedBodyFetch } from "./encrypted-body-fetch";
|
|
2
|
+
import { createPinnedTlsFetch } from "./pinned-tls-fetch";
|
|
3
|
+
import { isRealBrowser } from "./env";
|
|
4
|
+
export function createSecureFetch(baseURL, enclaveURL, hpkePublicKey, tlsPublicKeyFingerprint) {
|
|
5
|
+
let fetchFunction;
|
|
6
|
+
if (hpkePublicKey) {
|
|
7
|
+
fetchFunction = createEncryptedBodyFetch(baseURL, hpkePublicKey, enclaveURL);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
// HPKE not available: check if we're in a browser
|
|
11
|
+
if (isRealBrowser()) {
|
|
12
|
+
throw new Error("HPKE public key not available and TLS-only verification is not supported in browsers. " +
|
|
13
|
+
"Only HPKE-enabled enclaves can be used in browser environments.");
|
|
14
|
+
}
|
|
15
|
+
// Node.js environment: fall back to TLS-only verification using pinned TLS fetch
|
|
16
|
+
if (!tlsPublicKeyFingerprint) {
|
|
17
|
+
throw new Error("Neither HPKE public key nor TLS public key fingerprint available for verification");
|
|
18
|
+
}
|
|
19
|
+
fetchFunction = createPinnedTlsFetch(baseURL, tlsPublicKeyFingerprint);
|
|
20
|
+
}
|
|
21
|
+
return fetchFunction;
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import type { Audio, Beta, Chat, Embeddings, Files, FineTuning, Images, Models, Moderations, Responses } from "openai/resources";
|
|
3
|
+
import type { VerificationDocument } from "./verifier";
|
|
4
|
+
interface TinfoilAIOptions {
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
baseURL?: string;
|
|
7
|
+
enclaveURL?: string;
|
|
8
|
+
configRepo?: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
export declare class TinfoilAI {
|
|
12
|
+
private client?;
|
|
13
|
+
private clientPromise;
|
|
14
|
+
private readyPromise?;
|
|
15
|
+
private configRepo?;
|
|
16
|
+
private secureClient;
|
|
17
|
+
private verificationDocument?;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
baseURL?: string;
|
|
20
|
+
enclaveURL?: string;
|
|
21
|
+
constructor(options?: TinfoilAIOptions);
|
|
22
|
+
ready(): Promise<void>;
|
|
23
|
+
private initClient;
|
|
24
|
+
private createOpenAIClient;
|
|
25
|
+
private ensureReady;
|
|
26
|
+
getVerificationDocument(): Promise<VerificationDocument>;
|
|
27
|
+
get chat(): Chat;
|
|
28
|
+
get files(): Files;
|
|
29
|
+
get fineTuning(): FineTuning;
|
|
30
|
+
get images(): Images;
|
|
31
|
+
get audio(): Audio;
|
|
32
|
+
get responses(): Responses;
|
|
33
|
+
get embeddings(): Embeddings;
|
|
34
|
+
get models(): Models;
|
|
35
|
+
get moderations(): Moderations;
|
|
36
|
+
get beta(): Beta;
|
|
37
|
+
}
|
|
38
|
+
export declare namespace TinfoilAI {
|
|
39
|
+
export import Chat = OpenAI.Chat;
|
|
40
|
+
export import Audio = OpenAI.Audio;
|
|
41
|
+
export import Beta = OpenAI.Beta;
|
|
42
|
+
export import Batches = OpenAI.Batches;
|
|
43
|
+
export import Completions = OpenAI.Completions;
|
|
44
|
+
export import Embeddings = OpenAI.Embeddings;
|
|
45
|
+
export import Files = OpenAI.Files;
|
|
46
|
+
export import FineTuning = OpenAI.FineTuning;
|
|
47
|
+
export import Images = OpenAI.Images;
|
|
48
|
+
export import Models = OpenAI.Models;
|
|
49
|
+
export import Moderations = OpenAI.Moderations;
|
|
50
|
+
export import Responses = OpenAI.Responses;
|
|
51
|
+
export import Uploads = OpenAI.Uploads;
|
|
52
|
+
export import VectorStores = OpenAI.VectorStores;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { SecureClient } from "./secure-client";
|
|
3
|
+
import { TINFOIL_CONFIG } from "./config";
|
|
4
|
+
import { isRealBrowser } from "./env";
|
|
5
|
+
function createAsyncProxy(promise) {
|
|
6
|
+
return new Proxy({}, {
|
|
7
|
+
get(target, prop) {
|
|
8
|
+
return new Proxy(() => { }, {
|
|
9
|
+
get(_, nestedProp) {
|
|
10
|
+
return (...args) => promise.then((obj) => {
|
|
11
|
+
const value = obj[prop][nestedProp];
|
|
12
|
+
return typeof value === "function"
|
|
13
|
+
? value.apply(obj[prop], args)
|
|
14
|
+
: value;
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
apply(_, __, args) {
|
|
18
|
+
return promise.then((obj) => {
|
|
19
|
+
const value = obj[prop];
|
|
20
|
+
return typeof value === "function" ? value.apply(obj, args) : value;
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export class TinfoilAI {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
const openAIOptions = { ...options };
|
|
30
|
+
// In browser builds, never read secrets from process.env to avoid
|
|
31
|
+
// leaking credentials into client bundles. Require explicit apiKey.
|
|
32
|
+
if (options.apiKey) {
|
|
33
|
+
openAIOptions.apiKey = options.apiKey;
|
|
34
|
+
}
|
|
35
|
+
else if (!isRealBrowser() && process.env.TINFOIL_API_KEY) {
|
|
36
|
+
openAIOptions.apiKey = process.env.TINFOIL_API_KEY;
|
|
37
|
+
}
|
|
38
|
+
this.apiKey = openAIOptions.apiKey;
|
|
39
|
+
this.baseURL = options.baseURL || TINFOIL_CONFIG.INFERENCE_BASE_URL;
|
|
40
|
+
this.enclaveURL = options.enclaveURL || TINFOIL_CONFIG.ENCLAVE_URL;
|
|
41
|
+
this.configRepo = options.configRepo || TINFOIL_CONFIG.INFERENCE_PROXY_REPO;
|
|
42
|
+
this.secureClient = new SecureClient({
|
|
43
|
+
baseURL: this.baseURL,
|
|
44
|
+
enclaveURL: this.enclaveURL,
|
|
45
|
+
configRepo: this.configRepo,
|
|
46
|
+
});
|
|
47
|
+
this.clientPromise = this.initClient(openAIOptions);
|
|
48
|
+
}
|
|
49
|
+
async ready() {
|
|
50
|
+
if (!this.readyPromise) {
|
|
51
|
+
this.readyPromise = (async () => {
|
|
52
|
+
this.client = await this.clientPromise;
|
|
53
|
+
})();
|
|
54
|
+
}
|
|
55
|
+
return this.readyPromise;
|
|
56
|
+
}
|
|
57
|
+
async initClient(options) {
|
|
58
|
+
return this.createOpenAIClient(options);
|
|
59
|
+
}
|
|
60
|
+
async createOpenAIClient(options = {}) {
|
|
61
|
+
await this.secureClient.ready();
|
|
62
|
+
this.verificationDocument = await this.secureClient.getVerificationDocument();
|
|
63
|
+
if (!this.verificationDocument) {
|
|
64
|
+
throw new Error("Verification document not available after successful verification");
|
|
65
|
+
}
|
|
66
|
+
const clientOptions = {
|
|
67
|
+
...options,
|
|
68
|
+
baseURL: this.baseURL,
|
|
69
|
+
fetch: this.secureClient.fetch,
|
|
70
|
+
};
|
|
71
|
+
if (isRealBrowser() || options.dangerouslyAllowBrowser === true) {
|
|
72
|
+
clientOptions.dangerouslyAllowBrowser = true;
|
|
73
|
+
}
|
|
74
|
+
return new OpenAI(clientOptions);
|
|
75
|
+
}
|
|
76
|
+
async ensureReady() {
|
|
77
|
+
await this.ready();
|
|
78
|
+
return this.client;
|
|
79
|
+
}
|
|
80
|
+
async getVerificationDocument() {
|
|
81
|
+
await this.ready();
|
|
82
|
+
if (!this.verificationDocument) {
|
|
83
|
+
throw new Error("Verification document unavailable: client not verified yet");
|
|
84
|
+
}
|
|
85
|
+
return this.verificationDocument;
|
|
86
|
+
}
|
|
87
|
+
get chat() {
|
|
88
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.chat));
|
|
89
|
+
}
|
|
90
|
+
get files() {
|
|
91
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.files));
|
|
92
|
+
}
|
|
93
|
+
get fineTuning() {
|
|
94
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.fineTuning));
|
|
95
|
+
}
|
|
96
|
+
get images() {
|
|
97
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.images));
|
|
98
|
+
}
|
|
99
|
+
get audio() {
|
|
100
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.audio));
|
|
101
|
+
}
|
|
102
|
+
get responses() {
|
|
103
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.responses));
|
|
104
|
+
}
|
|
105
|
+
get embeddings() {
|
|
106
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.embeddings));
|
|
107
|
+
}
|
|
108
|
+
get models() {
|
|
109
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.models));
|
|
110
|
+
}
|
|
111
|
+
get moderations() {
|
|
112
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.moderations));
|
|
113
|
+
}
|
|
114
|
+
get beta() {
|
|
115
|
+
return createAsyncProxy(this.ensureReady().then((client) => client.beta));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Namespace declaration merge to add OpenAI types to TinfoilAI
|
|
119
|
+
(function (TinfoilAI) {
|
|
120
|
+
TinfoilAI.Chat = OpenAI.Chat;
|
|
121
|
+
TinfoilAI.Audio = OpenAI.Audio;
|
|
122
|
+
TinfoilAI.Beta = OpenAI.Beta;
|
|
123
|
+
TinfoilAI.Batches = OpenAI.Batches;
|
|
124
|
+
TinfoilAI.Completions = OpenAI.Completions;
|
|
125
|
+
TinfoilAI.Embeddings = OpenAI.Embeddings;
|
|
126
|
+
TinfoilAI.Files = OpenAI.Files;
|
|
127
|
+
TinfoilAI.FineTuning = OpenAI.FineTuning;
|
|
128
|
+
TinfoilAI.Images = OpenAI.Images;
|
|
129
|
+
TinfoilAI.Models = OpenAI.Models;
|
|
130
|
+
TinfoilAI.Moderations = OpenAI.Moderations;
|
|
131
|
+
TinfoilAI.Responses = OpenAI.Responses;
|
|
132
|
+
TinfoilAI.Uploads = OpenAI.Uploads;
|
|
133
|
+
TinfoilAI.VectorStores = OpenAI.VectorStores;
|
|
134
|
+
})(TinfoilAI || (TinfoilAI = {}));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface UnverifiedClientOptions {
|
|
2
|
+
baseURL?: string;
|
|
3
|
+
enclaveURL?: string;
|
|
4
|
+
configRepo?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class UnverifiedClient {
|
|
7
|
+
private initPromise;
|
|
8
|
+
private _fetch;
|
|
9
|
+
private readonly baseURL;
|
|
10
|
+
private readonly enclaveURL;
|
|
11
|
+
private readonly configRepo;
|
|
12
|
+
constructor(options?: UnverifiedClientOptions);
|
|
13
|
+
ready(): Promise<void>;
|
|
14
|
+
private initUnverifiedClient;
|
|
15
|
+
getVerificationDocument(): Promise<void>;
|
|
16
|
+
get fetch(): typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TINFOIL_CONFIG } from "./config";
|
|
2
|
+
import { createEncryptedBodyFetch } from "./encrypted-body-fetch";
|
|
3
|
+
export class UnverifiedClient {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.initPromise = null;
|
|
6
|
+
this._fetch = null;
|
|
7
|
+
this.baseURL = options.baseURL || TINFOIL_CONFIG.INFERENCE_BASE_URL;
|
|
8
|
+
this.enclaveURL = options.enclaveURL || TINFOIL_CONFIG.ENCLAVE_URL;
|
|
9
|
+
this.configRepo = options.configRepo || TINFOIL_CONFIG.INFERENCE_PROXY_REPO;
|
|
10
|
+
}
|
|
11
|
+
async ready() {
|
|
12
|
+
if (!this.initPromise) {
|
|
13
|
+
this.initPromise = this.initUnverifiedClient();
|
|
14
|
+
}
|
|
15
|
+
return this.initPromise;
|
|
16
|
+
}
|
|
17
|
+
async initUnverifiedClient() {
|
|
18
|
+
this._fetch = createEncryptedBodyFetch(this.baseURL, undefined, this.enclaveURL);
|
|
19
|
+
}
|
|
20
|
+
async getVerificationDocument() {
|
|
21
|
+
if (!this.initPromise) {
|
|
22
|
+
await this.ready();
|
|
23
|
+
}
|
|
24
|
+
await this.initPromise;
|
|
25
|
+
throw new Error("Verification document unavailable: this version of the client is unverified");
|
|
26
|
+
}
|
|
27
|
+
get fetch() {
|
|
28
|
+
return async (input, init) => {
|
|
29
|
+
await this.ready();
|
|
30
|
+
return this._fetch(input, init);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|