@prismnetwork/agent-sdk 0.6.0 → 0.7.0
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 +201 -0
- package/NOTICE +10 -0
- package/attest.d.mts +125 -0
- package/attest.mjs +826 -0
- package/e2ee.d.mts +89 -0
- package/e2ee.mjs +215 -0
- package/package.json +27 -6
- package/prism.d.mts +63 -0
- package/prism.mjs +461 -42
- package/vendor/aci-verifier/crypto.mjs +100 -0
- package/vendor/aci-verifier/digest.mjs +39 -0
- package/vendor/aci-verifier/errors.mjs +21 -0
- package/vendor/aci-verifier/index.mjs +32 -0
- package/vendor/aci-verifier/receipt.mjs +88 -0
- package/vendor/aci-verifier/report.mjs +206 -0
- package/vendor/aci-verifier/session.mjs +33 -0
package/e2ee.d.mts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { KeyObject } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export declare const E2EE_VERSION: string;
|
|
4
|
+
export declare const X25519_SUITE: string;
|
|
5
|
+
|
|
6
|
+
export declare class E2eeError extends Error {}
|
|
7
|
+
|
|
8
|
+
export interface KeysetKey {
|
|
9
|
+
key_id: string;
|
|
10
|
+
algo: string;
|
|
11
|
+
public_key: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AttestedKeyset {
|
|
15
|
+
e2ee_public_keys?: KeysetKey[];
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// The client half of one encrypted exchange: the static key the response is
|
|
20
|
+
/// encrypted to, and the request context every field's associated data binds.
|
|
21
|
+
export interface ClientKey {
|
|
22
|
+
privateKey: KeyObject;
|
|
23
|
+
publicKey: string;
|
|
24
|
+
algo: string;
|
|
25
|
+
keyId: string;
|
|
26
|
+
model: string;
|
|
27
|
+
nonce: string;
|
|
28
|
+
ts: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SealedRequest {
|
|
32
|
+
/// The body to send, with every message content replaced by its envelope.
|
|
33
|
+
bytes: Buffer;
|
|
34
|
+
/// The compact plaintext body the workload restores and hashes into the
|
|
35
|
+
/// receipt (§8 of the E2EE v2 protocol).
|
|
36
|
+
restored: Buffer;
|
|
37
|
+
/// All five headers a v2 request must carry; without any one of them the
|
|
38
|
+
/// service rejects the request.
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
clientKey: ClientKey;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export declare function selectE2eeKey(keyset: AttestedKeyset): KeysetKey;
|
|
44
|
+
|
|
45
|
+
/// §5 restoration: a decrypted whole-content plaintext that parses as a JSON
|
|
46
|
+
/// array comes back as structured content, and anything else stays a string.
|
|
47
|
+
export declare function restoreContent(plaintext: string): string | unknown[];
|
|
48
|
+
|
|
49
|
+
export declare function encryptChatRequest(
|
|
50
|
+
body: Record<string, unknown>,
|
|
51
|
+
keyset: AttestedKeyset,
|
|
52
|
+
options?: { now?: number; rand?: (length: number) => Uint8Array },
|
|
53
|
+
): SealedRequest;
|
|
54
|
+
|
|
55
|
+
export declare function decryptResponse(
|
|
56
|
+
bodyBytes: Uint8Array | string,
|
|
57
|
+
clientKey: ClientKey,
|
|
58
|
+
options?: { model?: string },
|
|
59
|
+
): Record<string, unknown>;
|
|
60
|
+
|
|
61
|
+
export declare function requestAad(input: {
|
|
62
|
+
algo: string;
|
|
63
|
+
model: string;
|
|
64
|
+
field: string;
|
|
65
|
+
nonce: string;
|
|
66
|
+
ts: number;
|
|
67
|
+
}): Uint8Array;
|
|
68
|
+
|
|
69
|
+
export declare function responseAad(input: {
|
|
70
|
+
algo: string;
|
|
71
|
+
model: string;
|
|
72
|
+
id: string;
|
|
73
|
+
field: string;
|
|
74
|
+
nonce: string;
|
|
75
|
+
ts: number;
|
|
76
|
+
}): Uint8Array;
|
|
77
|
+
|
|
78
|
+
export declare function sealField(
|
|
79
|
+
recipientRaw: Uint8Array,
|
|
80
|
+
plaintext: string,
|
|
81
|
+
aad: Uint8Array,
|
|
82
|
+
rand?: (length: number) => Uint8Array,
|
|
83
|
+
): string;
|
|
84
|
+
|
|
85
|
+
export declare function openField(privateKey: KeyObject, envelopeHex: string, aad: Uint8Array): string;
|
|
86
|
+
|
|
87
|
+
export declare function publicKeyFromRaw(raw: Uint8Array): KeyObject;
|
|
88
|
+
export declare function privateKeyFromSeed(seed: Uint8Array): KeyObject;
|
|
89
|
+
export declare function rawPublicKey(key: KeyObject): Uint8Array;
|
package/e2ee.mjs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// E2EE v2 client for attested confidential inference: the field-level
|
|
2
|
+
// encryption an agent puts around a chat request so the relay that carries it
|
|
3
|
+
// holds ciphertext only, and the enclave holding the attested key is the one
|
|
4
|
+
// thing that can read the prompt.
|
|
5
|
+
//
|
|
6
|
+
// The wire contract is the X25519 suite of the ACI E2EE v2 protocol. Each
|
|
7
|
+
// protected field value is hex(ephemeral_public_key(32) || gcm_nonce(12) ||
|
|
8
|
+
// ciphertext || tag(16)); the AES-256-GCM key is HKDF-SHA256 over the X25519
|
|
9
|
+
// shared secret with an empty salt and the suite's info string; the AEAD's
|
|
10
|
+
// associated data is the JCS form of a purpose-tagged object that pins the
|
|
11
|
+
// field path, the model, the request nonce and the timestamp, so a ciphertext
|
|
12
|
+
// cannot be moved to another field, request or model.
|
|
13
|
+
import {
|
|
14
|
+
createCipheriv,
|
|
15
|
+
createDecipheriv,
|
|
16
|
+
createPrivateKey,
|
|
17
|
+
createPublicKey,
|
|
18
|
+
diffieHellman,
|
|
19
|
+
hkdfSync,
|
|
20
|
+
randomBytes,
|
|
21
|
+
} from "node:crypto";
|
|
22
|
+
import { jcsBytes } from "./vendor/aci-verifier/index.mjs";
|
|
23
|
+
|
|
24
|
+
export const E2EE_VERSION = "2";
|
|
25
|
+
export const X25519_SUITE = "x25519-aes-256-gcm-hkdf-sha256";
|
|
26
|
+
const HKDF_INFO = "aci.e2ee.v2.x25519";
|
|
27
|
+
const REQUEST_PURPOSE = "aci.e2ee.request.v2";
|
|
28
|
+
const RESPONSE_PURPOSE = "aci.e2ee.response.v2";
|
|
29
|
+
|
|
30
|
+
// Node's X25519 keys travel as DER. Raw 32-byte keys are the wire form on both
|
|
31
|
+
// sides, so wrap them in the one fixed SPKI/PKCS8 prefix each rather than
|
|
32
|
+
// pulling in a curve library to do it.
|
|
33
|
+
const SPKI_PREFIX = Buffer.from("302a300506032b656e032100", "hex");
|
|
34
|
+
const PKCS8_PREFIX = Buffer.from("302e020100300506032b656e04220420", "hex");
|
|
35
|
+
|
|
36
|
+
const RESPONSE_FIELDS = ["content", "reasoning", "reasoning_content"];
|
|
37
|
+
|
|
38
|
+
export class E2eeError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "E2eeError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// The x25519 entry of a quote-bound key set. The service publishes a secp256k1
|
|
46
|
+
/// suite too; this client speaks the x25519 one, which is the suite the spec
|
|
47
|
+
/// recommends and the only one it implements.
|
|
48
|
+
export function selectE2eeKey(keyset) {
|
|
49
|
+
const keys = Array.isArray(keyset?.e2ee_public_keys) ? keyset.e2ee_public_keys : [];
|
|
50
|
+
const entry = keys.find((k) => k.algo === X25519_SUITE);
|
|
51
|
+
if (!entry) throw new E2eeError(`the attested key set publishes no ${X25519_SUITE} key`);
|
|
52
|
+
return entry;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function publicKeyFromRaw(raw) {
|
|
56
|
+
return createPublicKey({
|
|
57
|
+
key: Buffer.concat([SPKI_PREFIX, Buffer.from(raw)]),
|
|
58
|
+
format: "der",
|
|
59
|
+
type: "spki",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function privateKeyFromSeed(seed) {
|
|
64
|
+
if (seed.length !== 32) throw new E2eeError("an x25519 private key is 32 bytes");
|
|
65
|
+
return createPrivateKey({
|
|
66
|
+
key: Buffer.concat([PKCS8_PREFIX, Buffer.from(seed)]),
|
|
67
|
+
format: "der",
|
|
68
|
+
type: "pkcs8",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function rawPublicKey(key) {
|
|
73
|
+
return key.export({ type: "spki", format: "der" }).subarray(SPKI_PREFIX.length);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// The associated data for one request field (§6). Byte-exact: the test vectors
|
|
77
|
+
/// pin this string.
|
|
78
|
+
export function requestAad({ algo, model, field, nonce, ts }) {
|
|
79
|
+
return jcsBytes({ purpose: REQUEST_PURPOSE, algo, model, field, nonce, ts });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// The response variant, which additionally binds the response id.
|
|
83
|
+
export function responseAad({ algo, model, id, field, nonce, ts }) {
|
|
84
|
+
return jcsBytes({ purpose: RESPONSE_PURPOSE, algo, model, id, field, nonce, ts });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// One field envelope, encrypted to `recipientRaw` under a fresh ephemeral key.
|
|
88
|
+
export function sealField(recipientRaw, plaintext, aad, rand = randomBytes) {
|
|
89
|
+
const ephemeral = privateKeyFromSeed(Buffer.from(rand(32)));
|
|
90
|
+
const shared = diffieHellman({ privateKey: ephemeral, publicKey: publicKeyFromRaw(recipientRaw) });
|
|
91
|
+
const key = Buffer.from(hkdfSync("sha256", shared, Buffer.alloc(0), Buffer.from(HKDF_INFO), 32));
|
|
92
|
+
const nonce = Buffer.from(rand(12));
|
|
93
|
+
const cipher = createCipheriv("aes-256-gcm", key, nonce);
|
|
94
|
+
cipher.setAAD(aad);
|
|
95
|
+
const body = Buffer.concat([cipher.update(Buffer.from(plaintext, "utf8")), cipher.final()]);
|
|
96
|
+
return Buffer.concat([rawPublicKey(createPublicKey(ephemeral)), nonce, body, cipher.getAuthTag()]).toString("hex");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// The inverse. A tampered envelope, AAD or key fails the AEAD tag and throws.
|
|
100
|
+
export function openField(privateKey, envelopeHex, aad) {
|
|
101
|
+
const blob = Buffer.from(envelopeHex.startsWith("0x") ? envelopeHex.slice(2) : envelopeHex, "hex");
|
|
102
|
+
if (blob.length < 32 + 12 + 16) throw new E2eeError("e2ee envelope is too short to hold a field");
|
|
103
|
+
const shared = diffieHellman({ privateKey, publicKey: publicKeyFromRaw(blob.subarray(0, 32)) });
|
|
104
|
+
const key = Buffer.from(hkdfSync("sha256", shared, Buffer.alloc(0), Buffer.from(HKDF_INFO), 32));
|
|
105
|
+
const decipher = createDecipheriv("aes-256-gcm", key, blob.subarray(32, 44));
|
|
106
|
+
decipher.setAAD(aad);
|
|
107
|
+
decipher.setAuthTag(blob.subarray(blob.length - 16));
|
|
108
|
+
try {
|
|
109
|
+
return Buffer.concat([decipher.update(blob.subarray(44, blob.length - 16)), decipher.final()]).toString("utf8");
|
|
110
|
+
} catch {
|
|
111
|
+
throw new E2eeError("e2ee field did not authenticate: wrong key, wrong context, or altered ciphertext");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// §5 restoration: a decrypted whole-content plaintext that parses as a JSON
|
|
116
|
+
/// array is restored as structured content, and anything else stays the string
|
|
117
|
+
/// it was. The receipt covers what the workload restored, so a client that
|
|
118
|
+
/// reproduces the hash has to apply the same rule to its own copy.
|
|
119
|
+
export function restoreContent(plaintext) {
|
|
120
|
+
if (typeof plaintext !== "string" || plaintext.trimStart()[0] !== "[") return plaintext;
|
|
121
|
+
try {
|
|
122
|
+
const value = JSON.parse(plaintext);
|
|
123
|
+
return Array.isArray(value) ? value : plaintext;
|
|
124
|
+
} catch {
|
|
125
|
+
return plaintext;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Encrypt every message content of a chat-completions body to the attested
|
|
130
|
+
/// service key. Returns the bytes to send, the five headers that must travel
|
|
131
|
+
/// with them, the client key the response is encrypted to, and the compact
|
|
132
|
+
/// restored-plaintext bytes the receipt's `request.received` hash covers.
|
|
133
|
+
///
|
|
134
|
+
/// `now` and `rand` are injectable so a test can pin the whole envelope; both
|
|
135
|
+
/// default to the real clock and the system CSPRNG.
|
|
136
|
+
export function encryptChatRequest(body, keyset, { now = Math.floor(Date.now() / 1000), rand = randomBytes } = {}) {
|
|
137
|
+
if (typeof body?.model !== "string") throw new E2eeError("an e2ee request needs a top-level model string");
|
|
138
|
+
if (!Array.isArray(body.messages) || body.messages.length === 0) {
|
|
139
|
+
throw new E2eeError("an e2ee chat request needs a messages array");
|
|
140
|
+
}
|
|
141
|
+
const serviceKey = selectE2eeKey(keyset);
|
|
142
|
+
const serviceRaw = Buffer.from(serviceKey.public_key.replace(/^0x/, ""), "hex");
|
|
143
|
+
if (serviceRaw.length !== 32) throw new E2eeError("the attested x25519 key is not 32 bytes");
|
|
144
|
+
|
|
145
|
+
const clientPrivate = privateKeyFromSeed(Buffer.from(rand(32)));
|
|
146
|
+
const clientPublic = rawPublicKey(createPublicKey(clientPrivate));
|
|
147
|
+
const nonce = Buffer.from(rand(32)).toString("hex");
|
|
148
|
+
const ts = now;
|
|
149
|
+
|
|
150
|
+
// The plaintext copy is what the workload hashes into the receipt after it
|
|
151
|
+
// restores the fields (§8 of the v2 protocol), so it is built alongside the
|
|
152
|
+
// encrypted one from the same object and in the same member order.
|
|
153
|
+
const restored = { ...body, messages: [] };
|
|
154
|
+
const sealed = { ...body, messages: [] };
|
|
155
|
+
body.messages.forEach((message, index) => {
|
|
156
|
+
if (typeof message?.content !== "string") {
|
|
157
|
+
// Any plaintext string at a protected path fails the request upstream, so
|
|
158
|
+
// a body this client cannot fully protect is refused here instead.
|
|
159
|
+
throw new E2eeError(`messages.${index}.content must be a string to be encrypted`);
|
|
160
|
+
}
|
|
161
|
+
const field = `messages.${index}.content`;
|
|
162
|
+
const aad = requestAad({ algo: serviceKey.algo, model: body.model, field, nonce, ts });
|
|
163
|
+
restored.messages.push({ ...message, content: restoreContent(message.content) });
|
|
164
|
+
sealed.messages.push({ ...message, content: sealField(serviceRaw, message.content, aad, rand) });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
bytes: Buffer.from(JSON.stringify(sealed), "utf8"),
|
|
169
|
+
restored: Buffer.from(JSON.stringify(restored), "utf8"),
|
|
170
|
+
headers: {
|
|
171
|
+
"X-E2EE-Version": E2EE_VERSION,
|
|
172
|
+
"X-Client-Pub-Key": Buffer.from(clientPublic).toString("hex"),
|
|
173
|
+
"X-Model-Pub-Key": serviceKey.public_key,
|
|
174
|
+
"X-E2EE-Nonce": nonce,
|
|
175
|
+
"X-E2EE-Timestamp": String(ts),
|
|
176
|
+
},
|
|
177
|
+
clientKey: {
|
|
178
|
+
privateKey: clientPrivate,
|
|
179
|
+
publicKey: Buffer.from(clientPublic).toString("hex"),
|
|
180
|
+
algo: serviceKey.algo,
|
|
181
|
+
keyId: serviceKey.key_id,
|
|
182
|
+
model: body.model,
|
|
183
|
+
nonce,
|
|
184
|
+
ts,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Decrypt a buffered chat-completions response in place. Every generated
|
|
190
|
+
/// content field the service encrypted is authenticated against the AAD that
|
|
191
|
+
/// names its position, so a field lifted from another response does not open.
|
|
192
|
+
export function decryptResponse(bodyBytes, clientKey, { model = clientKey.model } = {}) {
|
|
193
|
+
const text = typeof bodyBytes === "string" ? bodyBytes : Buffer.from(bodyBytes).toString("utf8");
|
|
194
|
+
const body = JSON.parse(text);
|
|
195
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
196
|
+
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
197
|
+
choices.forEach((choice, position) => {
|
|
198
|
+
const index = Number.isInteger(choice?.index) ? choice.index : position;
|
|
199
|
+
const message = choice?.message;
|
|
200
|
+
if (message === null || typeof message !== "object") return;
|
|
201
|
+
for (const name of RESPONSE_FIELDS) {
|
|
202
|
+
if (typeof message[name] !== "string" || message[name] === "") continue;
|
|
203
|
+
const aad = responseAad({
|
|
204
|
+
algo: clientKey.algo,
|
|
205
|
+
model,
|
|
206
|
+
id,
|
|
207
|
+
field: `choices.${index}.message.${name}`,
|
|
208
|
+
nonce: clientKey.nonce,
|
|
209
|
+
ts: clientKey.ts,
|
|
210
|
+
});
|
|
211
|
+
message[name] = openField(clientKey.privateKey, message[name], aad);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return body;
|
|
215
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prismnetwork/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Headless GPU leasing and renter-encrypted storage on Prism Network for wallet-holding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "prism.mjs",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
"types": "./prism.d.mts",
|
|
10
10
|
"default": "./prism.mjs"
|
|
11
11
|
},
|
|
12
|
+
"./attest": {
|
|
13
|
+
"types": "./attest.d.mts",
|
|
14
|
+
"default": "./attest.mjs"
|
|
15
|
+
},
|
|
16
|
+
"./e2ee": {
|
|
17
|
+
"types": "./e2ee.d.mts",
|
|
18
|
+
"default": "./e2ee.mjs"
|
|
19
|
+
},
|
|
12
20
|
"./toolset": {
|
|
13
21
|
"types": "./toolset.d.mts",
|
|
14
22
|
"default": "./toolset.mjs"
|
|
@@ -24,15 +32,22 @@
|
|
|
24
32
|
},
|
|
25
33
|
"files": [
|
|
26
34
|
"prism.mjs",
|
|
27
|
-
"relay.mjs",
|
|
28
|
-
"toolset.d.mts",
|
|
29
35
|
"prism.d.mts",
|
|
36
|
+
"attest.mjs",
|
|
37
|
+
"attest.d.mts",
|
|
38
|
+
"e2ee.mjs",
|
|
39
|
+
"e2ee.d.mts",
|
|
40
|
+
"relay.mjs",
|
|
30
41
|
"toolset.mjs",
|
|
42
|
+
"toolset.d.mts",
|
|
31
43
|
"vault.mjs",
|
|
32
44
|
"vault.d.ts",
|
|
33
45
|
"workspace.mjs",
|
|
34
46
|
"workspace.d.ts",
|
|
35
|
-
"
|
|
47
|
+
"vendor/aci-verifier/*.mjs",
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"NOTICE"
|
|
36
51
|
],
|
|
37
52
|
"engines": {
|
|
38
53
|
"node": ">=20"
|
|
@@ -47,7 +62,9 @@
|
|
|
47
62
|
"web3",
|
|
48
63
|
"usdg",
|
|
49
64
|
"compute",
|
|
50
|
-
"llm"
|
|
65
|
+
"llm",
|
|
66
|
+
"tee",
|
|
67
|
+
"attestation"
|
|
51
68
|
],
|
|
52
69
|
"homepage": "https://prismnetwork.tech",
|
|
53
70
|
"repository": {
|
|
@@ -62,5 +79,9 @@
|
|
|
62
79
|
"publishConfig": {
|
|
63
80
|
"access": "public"
|
|
64
81
|
},
|
|
65
|
-
"types": "prism.d.mts"
|
|
82
|
+
"types": "prism.d.mts",
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"@phala/dcap-qvl": "^0.6.1",
|
|
85
|
+
"jose": "^6.0.0"
|
|
86
|
+
}
|
|
66
87
|
}
|
package/prism.d.mts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import type { AttestationResult, VerifyConfidentialOptions, WorkloadPin } from "./attest.d.mts";
|
|
2
|
+
|
|
1
3
|
export declare const robinhoodChain: unknown;
|
|
2
4
|
export declare const USDG: string;
|
|
3
5
|
export declare const DEFAULT_IMAGE: string;
|
|
4
6
|
export declare const TRUST_CLASSES: readonly ["open", "isolated", "attested", "confidential"];
|
|
5
7
|
|
|
8
|
+
export { DEFAULT_CONFIDENTIAL_BASE, EXPECTED_WORKLOAD, renderChecks, verifyConfidential } from "./attest.d.mts";
|
|
9
|
+
export type { AttestationCheck, AttestationResult, WorkloadPin } from "./attest.d.mts";
|
|
10
|
+
|
|
6
11
|
/// `mode` says which of the two shapes arrived. Brokered capacity fills in
|
|
7
12
|
/// `ssh_host` and `ssh_port`; a node that accepts nothing inbound fills in the
|
|
8
13
|
/// gateway fields instead and is reached through a relay.
|
|
@@ -54,6 +59,35 @@ export interface RunResult {
|
|
|
54
59
|
timedOut: boolean;
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
export interface PaidResponse {
|
|
63
|
+
status: number;
|
|
64
|
+
headers: Headers;
|
|
65
|
+
/// The response bytes exactly as they arrived, which is what a signed receipt
|
|
66
|
+
/// over the exchange commits to.
|
|
67
|
+
bytes: Buffer;
|
|
68
|
+
tx: string;
|
|
69
|
+
/// The request as the attempt that was served built it. Under E2EE each
|
|
70
|
+
/// attempt seals its own envelope, so this is the one the receipt covers.
|
|
71
|
+
sent: { bytes: Buffer; headers?: Record<string, string>; [key: string]: unknown };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// One confidential generation, with everything needed to check it afterwards.
|
|
75
|
+
export interface ConfidentialRun {
|
|
76
|
+
model: string;
|
|
77
|
+
content: string | null;
|
|
78
|
+
usage: Record<string, unknown> | null;
|
|
79
|
+
receiptId: string | null;
|
|
80
|
+
receipt: Record<string, unknown> | null;
|
|
81
|
+
/// The key set the prompt was encrypted to, when e2ee was on.
|
|
82
|
+
keysetDigest: string | null;
|
|
83
|
+
e2ee: boolean;
|
|
84
|
+
priceMicros: string;
|
|
85
|
+
priceUsdg: string;
|
|
86
|
+
tx: string;
|
|
87
|
+
bytes: { request: Buffer; response: Buffer; restoredRequest?: Buffer };
|
|
88
|
+
verify(options?: Partial<VerifyConfidentialOptions>): Promise<AttestationResult>;
|
|
89
|
+
}
|
|
90
|
+
|
|
57
91
|
export declare class PrismAgent {
|
|
58
92
|
constructor(options: { privateKey: string; escrow: string; apiBase?: string; rpcUrl?: string });
|
|
59
93
|
readonly address: string;
|
|
@@ -96,6 +130,35 @@ export declare class PrismAgent {
|
|
|
96
130
|
/// not a one-shot command: scp, a notebook client, an interactive shell.
|
|
97
131
|
forward(lease: LeaseHandle, options?: { service?: "ssh" | "jupyter" }): Promise<RelayForwarder>;
|
|
98
132
|
endLease(lease: LeaseHandle): void;
|
|
133
|
+
/// Pay for one call to a metered endpoint, keeping the payment until the
|
|
134
|
+
/// endpoint serves. Bytes are sent verbatim. Pass `seal` instead of `body` for
|
|
135
|
+
/// a request that has to be rebuilt per attempt, with `fingerprint` so the
|
|
136
|
+
/// kept payment still recognises the two attempts as the same request.
|
|
137
|
+
payAndPost(options: {
|
|
138
|
+
base: string;
|
|
139
|
+
path: string;
|
|
140
|
+
price: bigint | number | string;
|
|
141
|
+
payTo: string;
|
|
142
|
+
body?: Uint8Array | string | Record<string, unknown> | null;
|
|
143
|
+
headers?: Record<string, string>;
|
|
144
|
+
seal?: (() => { bytes: Uint8Array; headers?: Record<string, string> }) | null;
|
|
145
|
+
fingerprint?: Uint8Array | string | null;
|
|
146
|
+
retryDelayMs?: number;
|
|
147
|
+
caller?: string;
|
|
148
|
+
}): Promise<PaidResponse>;
|
|
149
|
+
/// One generation from the confidential tier, end-to-end encrypted by default
|
|
150
|
+
/// to a key the serving enclave's attestation quote commits to, from an
|
|
151
|
+
/// enclave running the code `expectedWorkload` pins.
|
|
152
|
+
confidentialInfer(options: {
|
|
153
|
+
prompt?: string;
|
|
154
|
+
messages?: Array<{ role: string; content: string }>;
|
|
155
|
+
model?: string | null;
|
|
156
|
+
maxUsdg?: number;
|
|
157
|
+
maxTokens?: number;
|
|
158
|
+
e2ee?: boolean;
|
|
159
|
+
expectedWorkload?: WorkloadPin | null;
|
|
160
|
+
endpoint?: string;
|
|
161
|
+
}): Promise<ConfidentialRun>;
|
|
99
162
|
}
|
|
100
163
|
|
|
101
164
|
export declare class PrismError extends Error {
|