@fungi.computer/veil 0.2.1 → 0.4.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/README.md +69 -0
- package/dist/connections/contract.d.ts +141 -0
- package/dist/connections/contract.js +55 -0
- package/dist/connections/core.d.ts +48 -0
- package/dist/connections/core.js +372 -0
- package/dist/connections/egress.d.ts +8 -0
- package/dist/connections/egress.js +77 -0
- package/dist/connections/index.d.ts +3 -0
- package/dist/connections/index.js +3 -0
- package/dist/connections/sealed.d.ts +21 -0
- package/dist/connections/sealed.js +54 -0
- package/dist/credential-store.d.ts +64 -0
- package/dist/credential-store.js +151 -0
- package/dist/index.d.ts +1 -1
- package/dist/model-credentials.d.ts +8 -7
- package/dist/model-credentials.js +15 -12
- package/dist/model-keys.d.ts +24 -0
- package/dist/openapi-secrets.d.ts +1 -1
- package/dist/openapi-secrets.js +19 -58
- package/dist/provider-egress.d.ts +16 -5
- package/dist/provider-egress.js +194 -4
- package/package.json +12 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
4
|
+
/**
|
|
5
|
+
* Veil's one encrypted credential store. A host holds a dedicated 32-byte
|
|
6
|
+
* master key as a host secret; credentials (model API keys, connector tokens)
|
|
7
|
+
* are sealed under it with WebCrypto AES-GCM, bound to an owner address, and
|
|
8
|
+
* leave the store only into a callback. Listings carry no secret.
|
|
9
|
+
*/
|
|
10
|
+
/** Stable, secret-free reason a credential could not be sealed, opened or stored. */
|
|
11
|
+
export class CredentialError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code) {
|
|
14
|
+
super(code);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = "CredentialError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const keySchema = Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/u)));
|
|
20
|
+
const keyIdSchema = Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,64}$/u)));
|
|
21
|
+
const envelopeSchema = Schema.Struct({
|
|
22
|
+
version: Schema.Literal(1),
|
|
23
|
+
keyId: keyIdSchema,
|
|
24
|
+
iv: Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_-]{16}$/u))),
|
|
25
|
+
ciphertext: Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_-]{22,22000}$/u))),
|
|
26
|
+
});
|
|
27
|
+
const encoder = new TextEncoder();
|
|
28
|
+
/**
|
|
29
|
+
* WebCrypto owns authenticated encryption. `domain` and `context` are the
|
|
30
|
+
* additional authenticated data: a sealed value opens only for its own owner.
|
|
31
|
+
*/
|
|
32
|
+
export function createCredentialSealer(configuration, domain) {
|
|
33
|
+
const keys = new Map();
|
|
34
|
+
let activeKeyId;
|
|
35
|
+
try {
|
|
36
|
+
activeKeyId = Schema.decodeUnknownSync(keyIdSchema)(configuration.activeKeyId);
|
|
37
|
+
for (const [id, encoded] of Object.entries(configuration.keys)) {
|
|
38
|
+
Schema.decodeUnknownSync(keyIdSchema)(id);
|
|
39
|
+
const parsed = Schema.decodeUnknownSync(keySchema)(encoded);
|
|
40
|
+
const bytes = Uint8Array.from(Buffer.from(parsed, "base64url"));
|
|
41
|
+
if (bytes.length !== 32)
|
|
42
|
+
throw new CredentialError("unavailable");
|
|
43
|
+
keys.set(id, crypto.subtle.importKey("raw", bytes, "AES-GCM", false, [
|
|
44
|
+
"encrypt",
|
|
45
|
+
"decrypt",
|
|
46
|
+
]));
|
|
47
|
+
}
|
|
48
|
+
if (!keys.has(activeKeyId))
|
|
49
|
+
throw new CredentialError("unavailable");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new CredentialError("unavailable");
|
|
53
|
+
}
|
|
54
|
+
const key = (id) => {
|
|
55
|
+
const found = keys.get(id);
|
|
56
|
+
if (!found)
|
|
57
|
+
throw new CredentialError("denied");
|
|
58
|
+
return found;
|
|
59
|
+
};
|
|
60
|
+
const binding = (keyId, context) => encoder.encode(JSON.stringify([domain, keyId, ...context]));
|
|
61
|
+
return {
|
|
62
|
+
async seal(context, credential) {
|
|
63
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
64
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
65
|
+
name: "AES-GCM",
|
|
66
|
+
iv,
|
|
67
|
+
additionalData: binding(activeKeyId, context),
|
|
68
|
+
}, await key(activeKeyId), encoder.encode(credential));
|
|
69
|
+
return JSON.stringify({
|
|
70
|
+
version: 1,
|
|
71
|
+
keyId: activeKeyId,
|
|
72
|
+
iv: Buffer.from(iv).toString("base64url"),
|
|
73
|
+
ciphertext: Buffer.from(ciphertext).toString("base64url"),
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
async open(context, sealed) {
|
|
77
|
+
try {
|
|
78
|
+
const envelope = Schema.decodeUnknownSync(Schema.fromJsonString(envelopeSchema))(sealed);
|
|
79
|
+
const plaintext = await crypto.subtle.decrypt({
|
|
80
|
+
name: "AES-GCM",
|
|
81
|
+
iv: Uint8Array.from(Buffer.from(envelope.iv, "base64url")),
|
|
82
|
+
additionalData: binding(envelope.keyId, context),
|
|
83
|
+
}, await key(envelope.keyId), Uint8Array.from(Buffer.from(envelope.ciphertext, "base64url")));
|
|
84
|
+
return new TextDecoder().decode(plaintext);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw new CredentialError("denied");
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const ids = defineIdFamily({
|
|
93
|
+
family: "veil-credential",
|
|
94
|
+
version: 1,
|
|
95
|
+
kinds: { credential: { prefix: "cred" } },
|
|
96
|
+
});
|
|
97
|
+
const prefix = "credential:";
|
|
98
|
+
export function createCredentialStore(options) {
|
|
99
|
+
const now = options.now ?? Date.now;
|
|
100
|
+
const limit = options.limit ?? 64;
|
|
101
|
+
const publicRecord = (row) => ({
|
|
102
|
+
credentialId: row.credentialId,
|
|
103
|
+
kind: row.kind,
|
|
104
|
+
label: row.label,
|
|
105
|
+
createdAt: row.createdAt,
|
|
106
|
+
validatedAt: row.validatedAt,
|
|
107
|
+
});
|
|
108
|
+
const row = async (credentialId) => options.storage.get(`${prefix}${credentialId}`);
|
|
109
|
+
return {
|
|
110
|
+
/** Seal one credential bound to its id and kind. */
|
|
111
|
+
async put(input) {
|
|
112
|
+
if ((await options.storage.list(prefix)).size >= limit)
|
|
113
|
+
throw new CredentialError("limit");
|
|
114
|
+
const credentialId = ids.credential.generate();
|
|
115
|
+
const at = now();
|
|
116
|
+
const stored = {
|
|
117
|
+
credentialId,
|
|
118
|
+
kind: input.kind,
|
|
119
|
+
label: input.label,
|
|
120
|
+
createdAt: at,
|
|
121
|
+
validatedAt: at,
|
|
122
|
+
sealed: await options.sealer.seal([credentialId, input.kind], input.secret),
|
|
123
|
+
};
|
|
124
|
+
await options.storage.put(`${prefix}${credentialId}`, stored);
|
|
125
|
+
return publicRecord(stored);
|
|
126
|
+
},
|
|
127
|
+
async list(kind) {
|
|
128
|
+
return [...(await options.storage.list(prefix)).values()]
|
|
129
|
+
.filter((item) => kind === undefined || item.kind === kind)
|
|
130
|
+
.map(publicRecord)
|
|
131
|
+
.sort((a, b) => a.createdAt - b.createdAt);
|
|
132
|
+
},
|
|
133
|
+
async get(credentialId) {
|
|
134
|
+
const found = await row(credentialId);
|
|
135
|
+
return found === undefined ? null : publicRecord(found);
|
|
136
|
+
},
|
|
137
|
+
async remove(credentialId) {
|
|
138
|
+
return (await options.storage.delete(`${prefix}${credentialId}`))
|
|
139
|
+
? "deleted"
|
|
140
|
+
: "absent";
|
|
141
|
+
},
|
|
142
|
+
/** The only way out: the secret exists for the callback and nowhere else. */
|
|
143
|
+
async withSecret(credentialId, action) {
|
|
144
|
+
const found = await row(credentialId);
|
|
145
|
+
if (found === undefined)
|
|
146
|
+
throw new CredentialError("absent");
|
|
147
|
+
const secret = await options.sealer.open([found.credentialId, found.kind], found.sealed);
|
|
148
|
+
return action(secret, publicRecord(found));
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -227,7 +227,7 @@ export declare function createGitHubConnections(options: Readonly<{
|
|
|
227
227
|
status: "completed";
|
|
228
228
|
connectionId: string;
|
|
229
229
|
authorityVersion: number;
|
|
230
|
-
custody: "
|
|
230
|
+
custody: "absent" | "deleted";
|
|
231
231
|
}>;
|
|
232
232
|
readRepository: (binding: ReadBinding, signal?: AbortSignal) => Promise<{
|
|
233
233
|
id: number;
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
* provider login policy: the local runtime, the TUI login registry and the Hub
|
|
4
4
|
* read it; none of them decides visibility on its own.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* hosted
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* is forbidden on both hosts.
|
|
6
|
+
* Offer a person's own Claude or ChatGPT subscription locally, never on
|
|
7
|
+
* hosted Fungi. A subscription is always one person's: never Team-scoped,
|
|
8
|
+
* shared or pooled. `subscriptionKillSwitch` can turn off a provider if it
|
|
9
|
+
* objects or blocks tokens. Hosted BYOK uses API keys. The retired Codex
|
|
10
|
+
* provider is forbidden on both hosts.
|
|
12
11
|
* See wiki/3-resources/decisions/adr-byok-model-keys-v1.md.
|
|
13
12
|
*/
|
|
14
13
|
/** The owner's own machine, or Fungi's servers acting for a Team. */
|
|
@@ -69,7 +68,9 @@ export declare const modelCredentialPolicy: Readonly<{
|
|
|
69
68
|
status: "allowed";
|
|
70
69
|
}>;
|
|
71
70
|
hosted: Readonly<{
|
|
72
|
-
status: "
|
|
71
|
+
status: "forbidden";
|
|
72
|
+
reason: string;
|
|
73
|
+
source: string;
|
|
73
74
|
}>;
|
|
74
75
|
};
|
|
75
76
|
};
|
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
* provider login policy: the local runtime, the TUI login registry and the Hub
|
|
4
4
|
* read it; none of them decides visibility on its own.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* hosted
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* is forbidden on both hosts.
|
|
6
|
+
* Offer a person's own Claude or ChatGPT subscription locally, never on
|
|
7
|
+
* hosted Fungi. A subscription is always one person's: never Team-scoped,
|
|
8
|
+
* shared or pooled. `subscriptionKillSwitch` can turn off a provider if it
|
|
9
|
+
* objects or blocks tokens. Hosted BYOK uses API keys. The retired Codex
|
|
10
|
+
* provider is forbidden on both hosts.
|
|
12
11
|
* See wiki/3-resources/decisions/adr-byok-model-keys-v1.md.
|
|
13
12
|
*/
|
|
14
13
|
const allowed = { status: "allowed" };
|
|
15
14
|
const unreviewed = { status: "unreviewed" };
|
|
16
15
|
const notOffered = {
|
|
16
|
+
status: "forbidden",
|
|
17
|
+
reason: "Fungi does not offer this credential method on this host",
|
|
18
|
+
source: "wiki/3-resources/decisions/adr-byok-model-keys-v1.md",
|
|
19
|
+
};
|
|
20
|
+
const providerNotOffered = {
|
|
17
21
|
status: "forbidden",
|
|
18
22
|
reason: "The provider does not offer this credential method",
|
|
19
23
|
source: "provider documentation",
|
|
@@ -25,7 +29,7 @@ const retiredCodex = {
|
|
|
25
29
|
};
|
|
26
30
|
const apiKeyOnly = {
|
|
27
31
|
api_key: { local: allowed, hosted: allowed },
|
|
28
|
-
subscription: { local:
|
|
32
|
+
subscription: { local: providerNotOffered, hosted: providerNotOffered },
|
|
29
33
|
};
|
|
30
34
|
/**
|
|
31
35
|
* Reviewed providers, keyed by Pi provider id. `openai-compatible` is a
|
|
@@ -38,9 +42,8 @@ export const modelCredentialPolicy = Object.freeze({
|
|
|
38
42
|
},
|
|
39
43
|
anthropic: {
|
|
40
44
|
api_key: { local: allowed, hosted: allowed },
|
|
41
|
-
// Local: Pi's Claude Pro/Max OAuth
|
|
42
|
-
|
|
43
|
-
subscription: { local: allowed, hosted: allowed },
|
|
45
|
+
// Local: Pi's Claude Pro/Max OAuth. Hosted: no Claude subscription login.
|
|
46
|
+
subscription: { local: allowed, hosted: notOffered },
|
|
44
47
|
},
|
|
45
48
|
"openai-codex": {
|
|
46
49
|
api_key: { local: retiredCodex, hosted: retiredCodex },
|
|
@@ -52,8 +55,8 @@ export const modelCredentialPolicy = Object.freeze({
|
|
|
52
55
|
local: allowed,
|
|
53
56
|
hosted: {
|
|
54
57
|
status: "forbidden",
|
|
55
|
-
reason: "ChatGPT
|
|
56
|
-
source: "
|
|
58
|
+
reason: "Fungi does not offer ChatGPT subscription sign-in on hosted Fungi; approval alone would not enable it",
|
|
59
|
+
source: "wiki/3-resources/decisions/adr-byok-model-keys-v1.md",
|
|
57
60
|
},
|
|
58
61
|
},
|
|
59
62
|
},
|
package/dist/model-keys.d.ts
CHANGED
|
@@ -11,6 +11,30 @@ export declare function isModelKeyProvider(value: string): value is ModelKeyProv
|
|
|
11
11
|
export declare function modelKeyBaseUrl(provider: ModelKeyProvider, configured?: string | null, options?: Readonly<{
|
|
12
12
|
allowLocalDev?: boolean;
|
|
13
13
|
}>): string;
|
|
14
|
+
/** A stored key as an owner sees it: no secret, no custody locator. */
|
|
15
|
+
export type ModelKeyRecord = Readonly<{
|
|
16
|
+
keyId: string;
|
|
17
|
+
provider: ModelKeyProvider;
|
|
18
|
+
label: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
validatedAt: number;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* The neutral model-key seam every host composes: standalone Oyster over
|
|
24
|
+
* Veil's credential store, the Hub over its Nango custodian. `seal` validates
|
|
25
|
+
* with the provider before storing; `send` is the only place a key meets a
|
|
26
|
+
* request.
|
|
27
|
+
*/
|
|
28
|
+
export type ModelKeyVault = Readonly<{
|
|
29
|
+
seal: (input: Readonly<{
|
|
30
|
+
provider: string;
|
|
31
|
+
label: string;
|
|
32
|
+
apiKey: string;
|
|
33
|
+
}>) => Promise<ModelKeyRecord>;
|
|
34
|
+
list: () => Promise<readonly ModelKeyRecord[]>;
|
|
35
|
+
remove: (keyId: string) => Promise<"deleted" | "absent">;
|
|
36
|
+
send: (keyId: string, request: Request) => Promise<Response>;
|
|
37
|
+
}>;
|
|
14
38
|
/** Private to the Team owner and the custodian adapter; never a public field. */
|
|
15
39
|
export type ModelKeyAccess = Readonly<{
|
|
16
40
|
locator: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type OpenApiCredentialKeyring, type OpenApiAccess } from "./openapi-contract.js";
|
|
2
|
-
/**
|
|
2
|
+
/** Connector credentials sealed by Veil's one credential sealer. */
|
|
3
3
|
export declare function createOpenApiSecrets(configuration: OpenApiCredentialKeyring): {
|
|
4
4
|
seal(access: OpenApiAccess, credential: string): Promise<string>;
|
|
5
5
|
open(access: OpenApiAccess, sealed: string): Promise<string>;
|
package/dist/openapi-secrets.js
CHANGED
|
@@ -1,76 +1,37 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import * as Schema from "effect/Schema";
|
|
3
1
|
import { OpenApiError, } from "./openapi-contract.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
version: Schema.Literal(1),
|
|
8
|
-
keyId: keyIdSchema,
|
|
9
|
-
iv: Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_-]{16}$/u))),
|
|
10
|
-
ciphertext: Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_-]{22,22000}$/u))),
|
|
11
|
-
});
|
|
12
|
-
const encoder = new TextEncoder();
|
|
13
|
-
const binding = (access, keyId) => encoder.encode(JSON.stringify([
|
|
14
|
-
"fungi.veil.openapi.v1",
|
|
15
|
-
keyId,
|
|
2
|
+
import { createCredentialSealer } from "./credential-store.js";
|
|
3
|
+
/** The connector's owner address and policy are the sealed value's context. */
|
|
4
|
+
const context = (access) => [
|
|
16
5
|
access.teamId,
|
|
17
6
|
access.connectorId,
|
|
18
7
|
access.baseUrl,
|
|
19
8
|
access.auth.kind,
|
|
20
|
-
access.auth.kind === "api_key_header" ||
|
|
21
|
-
access.auth.kind === "api_key_query"
|
|
9
|
+
access.auth.kind === "api_key_header" || access.auth.kind === "api_key_query"
|
|
22
10
|
? access.auth.param
|
|
23
11
|
: null,
|
|
24
|
-
]
|
|
25
|
-
/**
|
|
12
|
+
];
|
|
13
|
+
/** Connector credentials sealed by Veil's one credential sealer. */
|
|
26
14
|
export function createOpenApiSecrets(configuration) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
activeKeyId = Schema.decodeUnknownSync(keyIdSchema)(configuration.activeKeyId);
|
|
31
|
-
for (const [id, encoded] of Object.entries(configuration.keys)) {
|
|
32
|
-
Schema.decodeUnknownSync(keyIdSchema)(id);
|
|
33
|
-
const parsed = Schema.decodeUnknownSync(keySchema)(encoded);
|
|
34
|
-
const bytes = Uint8Array.from(Buffer.from(parsed, "base64url"));
|
|
35
|
-
if (bytes.length !== 32)
|
|
36
|
-
throw new OpenApiError("unavailable");
|
|
37
|
-
keys.set(id, crypto.subtle.importKey("raw", bytes, "AES-GCM", false, [
|
|
38
|
-
"encrypt",
|
|
39
|
-
"decrypt",
|
|
40
|
-
]));
|
|
15
|
+
const sealer = (() => {
|
|
16
|
+
try {
|
|
17
|
+
return createCredentialSealer(configuration, "fungi.veil.openapi.v1");
|
|
41
18
|
}
|
|
42
|
-
|
|
19
|
+
catch {
|
|
43
20
|
throw new OpenApiError("unavailable");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
throw new OpenApiError("unavailable");
|
|
47
|
-
}
|
|
48
|
-
const key = (id) => {
|
|
49
|
-
const found = keys.get(id);
|
|
50
|
-
if (!found)
|
|
51
|
-
throw new OpenApiError("denied");
|
|
52
|
-
return found;
|
|
53
|
-
};
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
54
23
|
return {
|
|
55
24
|
async seal(access, credential) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
ciphertext: Buffer.from(ciphertext).toString("base64url"),
|
|
63
|
-
});
|
|
25
|
+
try {
|
|
26
|
+
return await sealer.seal(context(access), credential);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new OpenApiError("denied");
|
|
30
|
+
}
|
|
64
31
|
},
|
|
65
32
|
async open(access, sealed) {
|
|
66
33
|
try {
|
|
67
|
-
|
|
68
|
-
const plaintext = await crypto.subtle.decrypt({
|
|
69
|
-
name: "AES-GCM",
|
|
70
|
-
iv: Uint8Array.from(Buffer.from(envelope.iv, "base64url")),
|
|
71
|
-
additionalData: binding(access, envelope.keyId),
|
|
72
|
-
}, await key(envelope.keyId), Uint8Array.from(Buffer.from(envelope.ciphertext, "base64url")));
|
|
73
|
-
return new TextDecoder().decode(plaintext);
|
|
34
|
+
return await sealer.open(context(access), sealed);
|
|
74
35
|
}
|
|
75
36
|
catch {
|
|
76
37
|
throw new OpenApiError("denied");
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* `/parallel` route, with `x-api-key`.
|
|
15
15
|
* - `ai-gateway-logs`: Cloudflare's API log read used for usage
|
|
16
16
|
* reconciliation, `Authorization: Bearer` with the gateway token.
|
|
17
|
+
* - `solana-rpc` / `solana-rpc-fallback`: finalized snapshot reads through
|
|
18
|
+
* Helius / QuickNode, with private query / path credentials.
|
|
19
|
+
* - `sanctions-screen`: the official OFAC SDN XML export, without a key.
|
|
17
20
|
*/
|
|
18
21
|
/** Private Worker bindings read only here; never Team storage or responses. */
|
|
19
22
|
export type ProviderKeyBindings = Readonly<{
|
|
@@ -24,8 +27,16 @@ export type ProviderKeyBindings = Readonly<{
|
|
|
24
27
|
/** Cloudflare account that owns the gateway; routes are scoped to it. */
|
|
25
28
|
AI_GATEWAY_ACCOUNT_ID?: string;
|
|
26
29
|
AI_GATEWAY_ID?: string;
|
|
30
|
+
/** Credential-free Helius mainnet root; the key is a separate Worker secret. */
|
|
31
|
+
SOLANA_RPC_URL?: string;
|
|
32
|
+
SOLANA_RPC_API_KEY?: string;
|
|
33
|
+
/** Credential-free QuickNode Solana mainnet root; token is a Worker secret. */
|
|
34
|
+
SOLANA_RPC_FALLBACK_URL?: string;
|
|
35
|
+
SOLANA_RPC_FALLBACK_API_KEY?: string;
|
|
36
|
+
/** Opt-in to the pinned public OFAC SDN XML export. No API key is needed. */
|
|
37
|
+
SANCTIONS_SDN_URL?: string;
|
|
27
38
|
}>;
|
|
28
|
-
export type ProviderRoute = "camel" | "ai-gateway" | "parallel" | "ai-gateway-logs";
|
|
39
|
+
export type ProviderRoute = "camel" | "ai-gateway" | "parallel" | "ai-gateway-logs" | "solana-rpc" | "solana-rpc-fallback" | "sanctions-screen";
|
|
29
40
|
/**
|
|
30
41
|
* The provider-key door. `fetch` is the transport the authorized request is
|
|
31
42
|
* finally sent on (a test double, or the Hub's telemetry-wrapped fetch).
|
|
@@ -33,16 +44,16 @@ export type ProviderRoute = "camel" | "ai-gateway" | "parallel" | "ai-gateway-lo
|
|
|
33
44
|
export declare function createProviderEgress(env: ProviderKeyBindings, options?: Readonly<{
|
|
34
45
|
fetch?: typeof globalThis.fetch;
|
|
35
46
|
}>): {
|
|
36
|
-
/** Whether
|
|
47
|
+
/** Whether required custody/configuration is present; reveals no value. */
|
|
37
48
|
credentialed(route: ProviderRoute): boolean;
|
|
38
49
|
/** The route's public base URL, or `null` while it is not configured. */
|
|
39
50
|
baseUrl(route: ProviderRoute): string | null;
|
|
40
51
|
/**
|
|
41
52
|
* Send a credential-free request to a route. Refuses (`denied`) any
|
|
42
53
|
* destination outside the route and (`unavailable`) a route with no
|
|
43
|
-
* credential.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
54
|
+
* credential. Credentialed routes never follow redirects (workerd has no
|
|
55
|
+
* `error` mode; `manual` hands the 3xx back instead). Only the anonymous
|
|
56
|
+
* sanctions GET may follow one pinned OFAC government-S3 302.
|
|
46
57
|
*/
|
|
47
58
|
fetch(route: ProviderRoute, input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
48
59
|
};
|