@dudko.dev/agent-web 0.0.7 → 0.0.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.
- package/README.md +49 -0
- package/dist/agent-web.js +95 -88
- package/dist/agent-web.js.map +1 -1
- package/dist/chunk-H7W6MBK2.js +125 -0
- package/dist/chunk-H7W6MBK2.js.map +1 -0
- package/dist/index.d.cts +3 -35
- package/dist/index.d.ts +3 -35
- package/dist/index.js +10 -114
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +520 -54
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +197 -6
- package/dist/mcp.d.ts +197 -6
- package/dist/mcp.js +407 -54
- package/dist/mcp.js.map +1 -1
- package/dist/vault-BHlMmKXM.d.cts +35 -0
- package/dist/vault-BHlMmKXM.d.ts +35 -0
- package/package.json +13 -13
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/storage/db.ts
|
|
2
|
+
import { openDB } from "idb";
|
|
3
|
+
var KEYS_STORE = "keys";
|
|
4
|
+
var SECRETS_STORE = "secrets";
|
|
5
|
+
var SESSIONS_STORE = "sessions";
|
|
6
|
+
var cache = /* @__PURE__ */ new Map();
|
|
7
|
+
var openAgentWebDB = (opts = {}) => {
|
|
8
|
+
const name = opts.dbName ?? "agent-web";
|
|
9
|
+
let p = cache.get(name);
|
|
10
|
+
if (!p) {
|
|
11
|
+
p = openDB(name, 1, {
|
|
12
|
+
upgrade(db) {
|
|
13
|
+
if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE);
|
|
14
|
+
if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE);
|
|
15
|
+
if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
cache.set(name, p);
|
|
19
|
+
p.catch(() => {
|
|
20
|
+
if (cache.get(name) === p) cache.delete(name);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return p;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/secrets/crypto.ts
|
|
27
|
+
var KEY_ID = "vault-key-v1";
|
|
28
|
+
var webcrypto = () => {
|
|
29
|
+
const c = globalThis.crypto;
|
|
30
|
+
if (!c?.subtle || !c.getRandomValues) {
|
|
31
|
+
throw new Error("WebCrypto (crypto.subtle) is unavailable in this environment");
|
|
32
|
+
}
|
|
33
|
+
return c;
|
|
34
|
+
};
|
|
35
|
+
var loadOrCreateKey = async (dbName) => {
|
|
36
|
+
const db = await openAgentWebDB({ dbName });
|
|
37
|
+
const existing = await db.get(KEYS_STORE, KEY_ID);
|
|
38
|
+
if (existing) return existing;
|
|
39
|
+
const key = await webcrypto().subtle.generateKey(
|
|
40
|
+
{ name: "AES-GCM", length: 256 },
|
|
41
|
+
/* extractable */
|
|
42
|
+
false,
|
|
43
|
+
["encrypt", "decrypt"]
|
|
44
|
+
);
|
|
45
|
+
const tx = db.transaction(KEYS_STORE, "readwrite");
|
|
46
|
+
const winner = await tx.store.get(KEY_ID);
|
|
47
|
+
if (winner) {
|
|
48
|
+
await tx.done;
|
|
49
|
+
return winner;
|
|
50
|
+
}
|
|
51
|
+
await tx.store.put(key, KEY_ID);
|
|
52
|
+
await tx.done;
|
|
53
|
+
return key;
|
|
54
|
+
};
|
|
55
|
+
var keyCache = /* @__PURE__ */ new Map();
|
|
56
|
+
var getOrCreateVaultKey = (dbName) => {
|
|
57
|
+
const cacheKey = dbName ?? "agent-web";
|
|
58
|
+
let p = keyCache.get(cacheKey);
|
|
59
|
+
if (!p) {
|
|
60
|
+
p = loadOrCreateKey(dbName);
|
|
61
|
+
keyCache.set(cacheKey, p);
|
|
62
|
+
p.catch(() => {
|
|
63
|
+
if (keyCache.get(cacheKey) === p) keyCache.delete(cacheKey);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return p;
|
|
67
|
+
};
|
|
68
|
+
var textEncoder = new TextEncoder();
|
|
69
|
+
var textDecoder = new TextDecoder();
|
|
70
|
+
var encryptJSON = async (key, value) => {
|
|
71
|
+
const c = webcrypto();
|
|
72
|
+
const iv = c.getRandomValues(new Uint8Array(12));
|
|
73
|
+
const data = textEncoder.encode(JSON.stringify(value));
|
|
74
|
+
const ciphertext = await c.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
|
|
75
|
+
return { iv, ciphertext };
|
|
76
|
+
};
|
|
77
|
+
var decryptJSON = async (key, blob) => {
|
|
78
|
+
const iv = new Uint8Array(blob.iv);
|
|
79
|
+
const plain = await webcrypto().subtle.decrypt({ name: "AES-GCM", iv }, key, blob.ciphertext);
|
|
80
|
+
return JSON.parse(textDecoder.decode(plain));
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/secrets/vault.ts
|
|
84
|
+
var IndexedDBVault = class {
|
|
85
|
+
dbName;
|
|
86
|
+
constructor(opts = {}) {
|
|
87
|
+
this.dbName = opts.dbName;
|
|
88
|
+
}
|
|
89
|
+
key() {
|
|
90
|
+
return getOrCreateVaultKey(this.dbName);
|
|
91
|
+
}
|
|
92
|
+
/** Encrypt and store a secret string under `id`. */
|
|
93
|
+
async setSecret(id, value) {
|
|
94
|
+
const blob = await encryptJSON(await this.key(), value);
|
|
95
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
96
|
+
await db.put(SECRETS_STORE, blob, id);
|
|
97
|
+
}
|
|
98
|
+
/** Fetch and decrypt a secret, or undefined if absent. */
|
|
99
|
+
async getSecret(id) {
|
|
100
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
101
|
+
const blob = await db.get(SECRETS_STORE, id);
|
|
102
|
+
if (!blob) return void 0;
|
|
103
|
+
return decryptJSON(await this.key(), blob);
|
|
104
|
+
}
|
|
105
|
+
async deleteSecret(id) {
|
|
106
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
107
|
+
await db.delete(SECRETS_STORE, id);
|
|
108
|
+
}
|
|
109
|
+
async listSecretIds() {
|
|
110
|
+
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
111
|
+
return (await db.getAllKeys(SECRETS_STORE)).map(String);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
KEYS_STORE,
|
|
117
|
+
SECRETS_STORE,
|
|
118
|
+
SESSIONS_STORE,
|
|
119
|
+
openAgentWebDB,
|
|
120
|
+
getOrCreateVaultKey,
|
|
121
|
+
encryptJSON,
|
|
122
|
+
decryptJSON,
|
|
123
|
+
IndexedDBVault
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=chunk-H7W6MBK2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/db.ts","../src/secrets/crypto.ts","../src/secrets/vault.ts"],"sourcesContent":["import { openDB, type IDBPDatabase } from 'idb'\n\nexport interface AgentWebDBOptions {\n /** Database name (default 'agent-web'). Use a distinct name to isolate apps. */\n dbName?: string\n}\n\nexport const KEYS_STORE = 'keys'\nexport const SECRETS_STORE = 'secrets'\nexport const SESSIONS_STORE = 'sessions'\n\nlet cache = new Map<string, Promise<IDBPDatabase>>()\n\n/**\n * The single owner of the IndexedDB database. Every persistent store — the\n * non-extractable crypto key, encrypted secrets, and session transcripts —\n * lives under ONE database name + version. Centralising `openDB` here prevents\n * the classic bug where two modules open the same DB name with different store\n * sets and the second call throws a VersionError.\n */\nexport const openAgentWebDB = (opts: AgentWebDBOptions = {}): Promise<IDBPDatabase> => {\n const name = opts.dbName ?? 'agent-web'\n let p = cache.get(name)\n if (!p) {\n p = openDB(name, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE)\n if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE)\n if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE)\n },\n })\n cache.set(name, p)\n // A failed open (private mode, storage denied) must not stay cached, or\n // every later call would replay the same rejection forever.\n p.catch(() => {\n if (cache.get(name) === p) cache.delete(name)\n })\n }\n return p\n}\n\n/** Test hook: drop cached connections so a fresh (e.g. fake-indexeddb) DB is opened. */\nexport const _resetDBCache = (): void => {\n cache = new Map()\n}\n","import { KEYS_STORE, openAgentWebDB } from '../storage/db.js'\n\nconst KEY_ID = 'vault-key-v1'\n\nconst webcrypto = (): Crypto => {\n const c = (globalThis as { crypto?: Crypto }).crypto\n if (!c?.subtle || !c.getRandomValues) {\n throw new Error('WebCrypto (crypto.subtle) is unavailable in this environment')\n }\n return c\n}\n\nconst loadOrCreateKey = async (dbName?: string): Promise<CryptoKey> => {\n const db = await openAgentWebDB({ dbName })\n const existing = (await db.get(KEYS_STORE, KEY_ID)) as CryptoKey | undefined\n if (existing) return existing\n const key = await webcrypto().subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n /* extractable */ false,\n ['encrypt', 'decrypt'],\n )\n // Double-check inside one readwrite transaction: another tab may have created\n // a key while ours was generating. Losing that race and overwriting would\n // leave the other tab's secrets permanently undecryptable.\n const tx = db.transaction(KEYS_STORE, 'readwrite')\n const winner = (await tx.store.get(KEY_ID)) as CryptoKey | undefined\n if (winner) {\n await tx.done\n return winner\n }\n await tx.store.put(key, KEY_ID)\n await tx.done\n return key\n}\n\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\n/**\n * Fetch (or lazily create) the vault's AES-GCM key. The key is generated\n * NON-EXTRACTABLE and stored as a live `CryptoKey` in IndexedDB — a CryptoKey\n * is structured-cloneable, so it persists across reloads WITHOUT its raw bytes\n * ever being exposed to JavaScript. Not even in-page script can read it out; it\n * can only be used to encrypt / decrypt. That is what makes at-rest storage of\n * the secrets meaningful (see IndexedDBVault's threat model).\n *\n * Concurrent callers share one in-flight promise per DB, and creation is\n * double-checked in a single IDB transaction, so two racing writers can never\n * end up encrypting under different keys.\n */\nexport const getOrCreateVaultKey = (dbName?: string): Promise<CryptoKey> => {\n const cacheKey = dbName ?? 'agent-web'\n let p = keyCache.get(cacheKey)\n if (!p) {\n p = loadOrCreateKey(dbName)\n keyCache.set(cacheKey, p)\n p.catch(() => {\n if (keyCache.get(cacheKey) === p) keyCache.delete(cacheKey)\n })\n }\n return p\n}\n\nexport interface EncryptedBlob {\n /** 96-bit random IV, fresh per write. */\n iv: Uint8Array\n ciphertext: ArrayBuffer\n}\n\nconst textEncoder = new TextEncoder()\nconst textDecoder = new TextDecoder()\n\n/** Encrypt a JSON-serialisable value with a fresh 96-bit IV (AES-GCM). */\nexport const encryptJSON = async (key: CryptoKey, value: unknown): Promise<EncryptedBlob> => {\n const c = webcrypto()\n const iv = c.getRandomValues(new Uint8Array(12))\n const data = textEncoder.encode(JSON.stringify(value))\n const ciphertext = await c.subtle.encrypt({ name: 'AES-GCM', iv }, key, data)\n return { iv, ciphertext }\n}\n\n/** Decrypt a blob produced by encryptJSON. Throws if the key or blob don't match. */\nexport const decryptJSON = async <T = unknown>(key: CryptoKey, blob: EncryptedBlob): Promise<T> => {\n // Copy the IV into a fresh ArrayBuffer-backed view: after a round-trip through\n // IndexedDB its element buffer is typed as ArrayBufferLike, which no longer\n // satisfies BufferSource under TS's stricter typed-array generics.\n const iv = new Uint8Array(blob.iv)\n const plain = await webcrypto().subtle.decrypt({ name: 'AES-GCM', iv }, key, blob.ciphertext)\n return JSON.parse(textDecoder.decode(plain)) as T\n}\n","import { openAgentWebDB, SECRETS_STORE } from '../storage/db.js'\nimport { decryptJSON, encryptJSON, getOrCreateVaultKey, type EncryptedBlob } from './crypto.js'\n\nexport interface VaultOptions {\n /** Database name, forwarded to the shared IDB owner (default 'agent-web'). */\n dbName?: string\n}\n\n/**\n * Encrypted key/value secret store backed by IndexedDB. Values are encrypted at\n * rest with the non-extractable AES-GCM vault key (see crypto.ts).\n *\n * ── THREAT MODEL (read before shipping any app-owned key to a browser) ──\n * This vault PROTECTS secrets **at rest** — an IndexedDB / disk dump yields\n * only ciphertext — and against **casual DevTools inspection** — no plaintext\n * key is stored, and the AES key cannot be exported even by in-page script.\n *\n * It does **NOT** defend against **active XSS on your origin**: any script that\n * already runs on the page can call `getSecret()`, or use the live `CryptoKey`\n * to decrypt. Browser storage is not a secrets manager.\n *\n * Therefore this vault is for the **end user's OWN key on their OWN device**\n * (bring-your-own-key). A shared, app-owned key must NEVER reach the client —\n * put it behind a server proxy (`ProviderModelSpec.baseURL`) or the Vercel AI\n * Gateway instead.\n */\nexport class IndexedDBVault {\n private readonly dbName?: string\n\n constructor(opts: VaultOptions = {}) {\n this.dbName = opts.dbName\n }\n\n private key(): Promise<CryptoKey> {\n return getOrCreateVaultKey(this.dbName)\n }\n\n /** Encrypt and store a secret string under `id`. */\n async setSecret(id: string, value: string): Promise<void> {\n const blob = await encryptJSON(await this.key(), value)\n const db = await openAgentWebDB({ dbName: this.dbName })\n await db.put(SECRETS_STORE, blob, id)\n }\n\n /** Fetch and decrypt a secret, or undefined if absent. */\n async getSecret(id: string): Promise<string | undefined> {\n const db = await openAgentWebDB({ dbName: this.dbName })\n const blob = (await db.get(SECRETS_STORE, id)) as EncryptedBlob | undefined\n if (!blob) return undefined\n return decryptJSON<string>(await this.key(), blob)\n }\n\n async deleteSecret(id: string): Promise<void> {\n const db = await openAgentWebDB({ dbName: this.dbName })\n await db.delete(SECRETS_STORE, id)\n }\n\n async listSecretIds(): Promise<string[]> {\n const db = await openAgentWebDB({ dbName: this.dbName })\n return (await db.getAllKeys(SECRETS_STORE)).map(String)\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as ai from 'ai';
|
|
2
2
|
import { LanguageModel, ToolSet, Tool, ModelMessage, generateText, streamText } from 'ai';
|
|
3
|
+
import { V as VaultOptions } from './vault-BHlMmKXM.cjs';
|
|
4
|
+
export { I as IndexedDBVault } from './vault-BHlMmKXM.cjs';
|
|
3
5
|
import { z, ZodType } from 'zod';
|
|
4
6
|
import { IDBPDatabase } from 'idb';
|
|
5
7
|
|
|
@@ -199,40 +201,6 @@ declare const defaultPrompts: Prompts;
|
|
|
199
201
|
/** Prepend a host-supplied systemPrompt to a phase system prompt, if present. */
|
|
200
202
|
declare const withSystem: (base: string, extra?: string) => string;
|
|
201
203
|
|
|
202
|
-
interface VaultOptions {
|
|
203
|
-
/** Database name, forwarded to the shared IDB owner (default 'agent-web'). */
|
|
204
|
-
dbName?: string;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Encrypted key/value secret store backed by IndexedDB. Values are encrypted at
|
|
208
|
-
* rest with the non-extractable AES-GCM vault key (see crypto.ts).
|
|
209
|
-
*
|
|
210
|
-
* ── THREAT MODEL (read before shipping any app-owned key to a browser) ──
|
|
211
|
-
* This vault PROTECTS secrets **at rest** — an IndexedDB / disk dump yields
|
|
212
|
-
* only ciphertext — and against **casual DevTools inspection** — no plaintext
|
|
213
|
-
* key is stored, and the AES key cannot be exported even by in-page script.
|
|
214
|
-
*
|
|
215
|
-
* It does **NOT** defend against **active XSS on your origin**: any script that
|
|
216
|
-
* already runs on the page can call `getSecret()`, or use the live `CryptoKey`
|
|
217
|
-
* to decrypt. Browser storage is not a secrets manager.
|
|
218
|
-
*
|
|
219
|
-
* Therefore this vault is for the **end user's OWN key on their OWN device**
|
|
220
|
-
* (bring-your-own-key). A shared, app-owned key must NEVER reach the client —
|
|
221
|
-
* put it behind a server proxy (`ProviderModelSpec.baseURL`) or the Vercel AI
|
|
222
|
-
* Gateway instead.
|
|
223
|
-
*/
|
|
224
|
-
declare class IndexedDBVault {
|
|
225
|
-
private readonly dbName?;
|
|
226
|
-
constructor(opts?: VaultOptions);
|
|
227
|
-
private key;
|
|
228
|
-
/** Encrypt and store a secret string under `id`. */
|
|
229
|
-
setSecret(id: string, value: string): Promise<void>;
|
|
230
|
-
/** Fetch and decrypt a secret, or undefined if absent. */
|
|
231
|
-
getSecret(id: string): Promise<string | undefined>;
|
|
232
|
-
deleteSecret(id: string): Promise<void>;
|
|
233
|
-
listSecretIds(): Promise<string[]>;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
204
|
/**
|
|
237
205
|
* The seam the provider registry uses to fetch API keys at model-build time.
|
|
238
206
|
* Swap in any backend: the encrypted vault (default), an in-memory map (tests /
|
|
@@ -959,4 +927,4 @@ interface CompressOptions {
|
|
|
959
927
|
*/
|
|
960
928
|
declare const compressHistory: (messages: StoredMessage[], model: LanguageModel, opts?: CompressOptions) => Promise<StoredMessage[]>;
|
|
961
929
|
|
|
962
|
-
export { type Agent, type AgentContext, type AgentEvent, type AgentEventHandler, type AgentLogger, type AgentLoggerSink, type AgentTool, type AgentToolSet, type AgentWebDBOptions, type BrowserAgentConfig, type CompressOptions, type ContextStore, type CredentialStore, type EncryptedBlob, type ExecutorPromptContext, type ExecutorResult, type GenerateOptions, type IPlan, type IPlanStep, type IStepResult, type IToolCall, type IUsage, IndexedDBStore, type IndexedDBStoreOptions,
|
|
930
|
+
export { type Agent, type AgentContext, type AgentEvent, type AgentEventHandler, type AgentLogger, type AgentLoggerSink, type AgentTool, type AgentToolSet, type AgentWebDBOptions, type BrowserAgentConfig, type CompressOptions, type ContextStore, type CredentialStore, type EncryptedBlob, type ExecutorPromptContext, type ExecutorResult, type GenerateOptions, type IPlan, type IPlanStep, type IStepResult, type IToolCall, type IUsage, IndexedDBStore, type IndexedDBStoreOptions, KEYS_STORE, type LogLevel, MemoryCredentialStore, MemoryStore, type ModelInput, type Phase, type PhaseBudgets, PlanSchema, type PlanShape, PlanStepSchema, type PlannerPromptContext, type PlannerResult, type PromptParts, type Prompts, type ProviderModelSpec, type ProviderType, type RawAction, type ReplanDecision, type ReplanMode, ReplanSchema, type ReplanShape, type ReplanTrigger, type ReplannerPromptContext, type ReplannerResult, type ResolvedConfig, type ResolvedStage, type RunOptions, type RunResult, SECRETS_STORE, SESSIONS_STORE, type StageInput, type StageOverride, type StoredMessage, type SynthesizerPromptContext, type ToolCallMode, type ToolLoopCallbacks, type ToolLoopOptions, type ToolLoopResult, type ToolMode, type ToolSelectionStrategy, VaultCredentialStore, VaultOptions, type WebLLMModelOptions, buildModelFromStage, compressHistory, createAgent, createLogger, createPlan, createWebLLMModel, decideReplan, decryptJSON, defaultPrompts, defineTool, directBrowserOk, dispatch, encryptJSON, executeStep, generate, generateStructured, getOrCreateVaultKey, isDirectModel, isProviderSpec, isWebGPUAvailable, looksLikeJson, normalizeSteps, normalizeUsage, openAgentWebDB, parseExecutorResponse, parsePlainText, parsePlannerResponse, parseReplannerResponse, preloadWebLLMModel, promptHintOf, renderCatalog, replanWanted, resolveConfig, resolveModel, resolveStage, runToolLoop, selectToolMode, shouldReplan, splitBlocker, stream, supportsNativeTools, supportsStructuredOutput, synthesizeAnswer, unloadWebLLMModel, withSystem };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as ai from 'ai';
|
|
2
2
|
import { LanguageModel, ToolSet, Tool, ModelMessage, generateText, streamText } from 'ai';
|
|
3
|
+
import { V as VaultOptions } from './vault-BHlMmKXM.js';
|
|
4
|
+
export { I as IndexedDBVault } from './vault-BHlMmKXM.js';
|
|
3
5
|
import { z, ZodType } from 'zod';
|
|
4
6
|
import { IDBPDatabase } from 'idb';
|
|
5
7
|
|
|
@@ -199,40 +201,6 @@ declare const defaultPrompts: Prompts;
|
|
|
199
201
|
/** Prepend a host-supplied systemPrompt to a phase system prompt, if present. */
|
|
200
202
|
declare const withSystem: (base: string, extra?: string) => string;
|
|
201
203
|
|
|
202
|
-
interface VaultOptions {
|
|
203
|
-
/** Database name, forwarded to the shared IDB owner (default 'agent-web'). */
|
|
204
|
-
dbName?: string;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Encrypted key/value secret store backed by IndexedDB. Values are encrypted at
|
|
208
|
-
* rest with the non-extractable AES-GCM vault key (see crypto.ts).
|
|
209
|
-
*
|
|
210
|
-
* ── THREAT MODEL (read before shipping any app-owned key to a browser) ──
|
|
211
|
-
* This vault PROTECTS secrets **at rest** — an IndexedDB / disk dump yields
|
|
212
|
-
* only ciphertext — and against **casual DevTools inspection** — no plaintext
|
|
213
|
-
* key is stored, and the AES key cannot be exported even by in-page script.
|
|
214
|
-
*
|
|
215
|
-
* It does **NOT** defend against **active XSS on your origin**: any script that
|
|
216
|
-
* already runs on the page can call `getSecret()`, or use the live `CryptoKey`
|
|
217
|
-
* to decrypt. Browser storage is not a secrets manager.
|
|
218
|
-
*
|
|
219
|
-
* Therefore this vault is for the **end user's OWN key on their OWN device**
|
|
220
|
-
* (bring-your-own-key). A shared, app-owned key must NEVER reach the client —
|
|
221
|
-
* put it behind a server proxy (`ProviderModelSpec.baseURL`) or the Vercel AI
|
|
222
|
-
* Gateway instead.
|
|
223
|
-
*/
|
|
224
|
-
declare class IndexedDBVault {
|
|
225
|
-
private readonly dbName?;
|
|
226
|
-
constructor(opts?: VaultOptions);
|
|
227
|
-
private key;
|
|
228
|
-
/** Encrypt and store a secret string under `id`. */
|
|
229
|
-
setSecret(id: string, value: string): Promise<void>;
|
|
230
|
-
/** Fetch and decrypt a secret, or undefined if absent. */
|
|
231
|
-
getSecret(id: string): Promise<string | undefined>;
|
|
232
|
-
deleteSecret(id: string): Promise<void>;
|
|
233
|
-
listSecretIds(): Promise<string[]>;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
204
|
/**
|
|
237
205
|
* The seam the provider registry uses to fetch API keys at model-build time.
|
|
238
206
|
* Swap in any backend: the encrypted vault (default), an in-memory map (tests /
|
|
@@ -959,4 +927,4 @@ interface CompressOptions {
|
|
|
959
927
|
*/
|
|
960
928
|
declare const compressHistory: (messages: StoredMessage[], model: LanguageModel, opts?: CompressOptions) => Promise<StoredMessage[]>;
|
|
961
929
|
|
|
962
|
-
export { type Agent, type AgentContext, type AgentEvent, type AgentEventHandler, type AgentLogger, type AgentLoggerSink, type AgentTool, type AgentToolSet, type AgentWebDBOptions, type BrowserAgentConfig, type CompressOptions, type ContextStore, type CredentialStore, type EncryptedBlob, type ExecutorPromptContext, type ExecutorResult, type GenerateOptions, type IPlan, type IPlanStep, type IStepResult, type IToolCall, type IUsage, IndexedDBStore, type IndexedDBStoreOptions,
|
|
930
|
+
export { type Agent, type AgentContext, type AgentEvent, type AgentEventHandler, type AgentLogger, type AgentLoggerSink, type AgentTool, type AgentToolSet, type AgentWebDBOptions, type BrowserAgentConfig, type CompressOptions, type ContextStore, type CredentialStore, type EncryptedBlob, type ExecutorPromptContext, type ExecutorResult, type GenerateOptions, type IPlan, type IPlanStep, type IStepResult, type IToolCall, type IUsage, IndexedDBStore, type IndexedDBStoreOptions, KEYS_STORE, type LogLevel, MemoryCredentialStore, MemoryStore, type ModelInput, type Phase, type PhaseBudgets, PlanSchema, type PlanShape, PlanStepSchema, type PlannerPromptContext, type PlannerResult, type PromptParts, type Prompts, type ProviderModelSpec, type ProviderType, type RawAction, type ReplanDecision, type ReplanMode, ReplanSchema, type ReplanShape, type ReplanTrigger, type ReplannerPromptContext, type ReplannerResult, type ResolvedConfig, type ResolvedStage, type RunOptions, type RunResult, SECRETS_STORE, SESSIONS_STORE, type StageInput, type StageOverride, type StoredMessage, type SynthesizerPromptContext, type ToolCallMode, type ToolLoopCallbacks, type ToolLoopOptions, type ToolLoopResult, type ToolMode, type ToolSelectionStrategy, VaultCredentialStore, VaultOptions, type WebLLMModelOptions, buildModelFromStage, compressHistory, createAgent, createLogger, createPlan, createWebLLMModel, decideReplan, decryptJSON, defaultPrompts, defineTool, directBrowserOk, dispatch, encryptJSON, executeStep, generate, generateStructured, getOrCreateVaultKey, isDirectModel, isProviderSpec, isWebGPUAvailable, looksLikeJson, normalizeSteps, normalizeUsage, openAgentWebDB, parseExecutorResponse, parsePlainText, parsePlannerResponse, parseReplannerResponse, preloadWebLLMModel, promptHintOf, renderCatalog, replanWanted, resolveConfig, resolveModel, resolveStage, runToolLoop, selectToolMode, shouldReplan, splitBlocker, stream, supportsNativeTools, supportsStructuredOutput, synthesizeAnswer, unloadWebLLMModel, withSystem };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IndexedDBVault,
|
|
3
|
+
KEYS_STORE,
|
|
4
|
+
SECRETS_STORE,
|
|
5
|
+
SESSIONS_STORE,
|
|
6
|
+
decryptJSON,
|
|
7
|
+
encryptJSON,
|
|
8
|
+
getOrCreateVaultKey,
|
|
9
|
+
openAgentWebDB
|
|
10
|
+
} from "./chunk-H7W6MBK2.js";
|
|
1
11
|
import {
|
|
2
12
|
createWebLLMModel,
|
|
3
13
|
isWebGPUAvailable,
|
|
@@ -1281,120 +1291,6 @@ var supportsNativeTools = (p) => CLOUD.has(p);
|
|
|
1281
1291
|
var supportsStructuredOutput = (p) => CLOUD.has(p);
|
|
1282
1292
|
var directBrowserOk = (p) => p === "google" || p === "gateway" || p === "openai-compatible" || p === "anthropic";
|
|
1283
1293
|
|
|
1284
|
-
// src/storage/db.ts
|
|
1285
|
-
import { openDB } from "idb";
|
|
1286
|
-
var KEYS_STORE = "keys";
|
|
1287
|
-
var SECRETS_STORE = "secrets";
|
|
1288
|
-
var SESSIONS_STORE = "sessions";
|
|
1289
|
-
var cache = /* @__PURE__ */ new Map();
|
|
1290
|
-
var openAgentWebDB = (opts = {}) => {
|
|
1291
|
-
const name = opts.dbName ?? "agent-web";
|
|
1292
|
-
let p = cache.get(name);
|
|
1293
|
-
if (!p) {
|
|
1294
|
-
p = openDB(name, 1, {
|
|
1295
|
-
upgrade(db) {
|
|
1296
|
-
if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE);
|
|
1297
|
-
if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE);
|
|
1298
|
-
if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE);
|
|
1299
|
-
}
|
|
1300
|
-
});
|
|
1301
|
-
cache.set(name, p);
|
|
1302
|
-
p.catch(() => {
|
|
1303
|
-
if (cache.get(name) === p) cache.delete(name);
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
return p;
|
|
1307
|
-
};
|
|
1308
|
-
|
|
1309
|
-
// src/secrets/crypto.ts
|
|
1310
|
-
var KEY_ID = "vault-key-v1";
|
|
1311
|
-
var webcrypto = () => {
|
|
1312
|
-
const c = globalThis.crypto;
|
|
1313
|
-
if (!c?.subtle || !c.getRandomValues) {
|
|
1314
|
-
throw new Error("WebCrypto (crypto.subtle) is unavailable in this environment");
|
|
1315
|
-
}
|
|
1316
|
-
return c;
|
|
1317
|
-
};
|
|
1318
|
-
var loadOrCreateKey = async (dbName) => {
|
|
1319
|
-
const db = await openAgentWebDB({ dbName });
|
|
1320
|
-
const existing = await db.get(KEYS_STORE, KEY_ID);
|
|
1321
|
-
if (existing) return existing;
|
|
1322
|
-
const key = await webcrypto().subtle.generateKey(
|
|
1323
|
-
{ name: "AES-GCM", length: 256 },
|
|
1324
|
-
/* extractable */
|
|
1325
|
-
false,
|
|
1326
|
-
["encrypt", "decrypt"]
|
|
1327
|
-
);
|
|
1328
|
-
const tx = db.transaction(KEYS_STORE, "readwrite");
|
|
1329
|
-
const winner = await tx.store.get(KEY_ID);
|
|
1330
|
-
if (winner) {
|
|
1331
|
-
await tx.done;
|
|
1332
|
-
return winner;
|
|
1333
|
-
}
|
|
1334
|
-
await tx.store.put(key, KEY_ID);
|
|
1335
|
-
await tx.done;
|
|
1336
|
-
return key;
|
|
1337
|
-
};
|
|
1338
|
-
var keyCache = /* @__PURE__ */ new Map();
|
|
1339
|
-
var getOrCreateVaultKey = (dbName) => {
|
|
1340
|
-
const cacheKey = dbName ?? "agent-web";
|
|
1341
|
-
let p = keyCache.get(cacheKey);
|
|
1342
|
-
if (!p) {
|
|
1343
|
-
p = loadOrCreateKey(dbName);
|
|
1344
|
-
keyCache.set(cacheKey, p);
|
|
1345
|
-
p.catch(() => {
|
|
1346
|
-
if (keyCache.get(cacheKey) === p) keyCache.delete(cacheKey);
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
return p;
|
|
1350
|
-
};
|
|
1351
|
-
var textEncoder = new TextEncoder();
|
|
1352
|
-
var textDecoder = new TextDecoder();
|
|
1353
|
-
var encryptJSON = async (key, value) => {
|
|
1354
|
-
const c = webcrypto();
|
|
1355
|
-
const iv = c.getRandomValues(new Uint8Array(12));
|
|
1356
|
-
const data = textEncoder.encode(JSON.stringify(value));
|
|
1357
|
-
const ciphertext = await c.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
|
|
1358
|
-
return { iv, ciphertext };
|
|
1359
|
-
};
|
|
1360
|
-
var decryptJSON = async (key, blob) => {
|
|
1361
|
-
const iv = new Uint8Array(blob.iv);
|
|
1362
|
-
const plain = await webcrypto().subtle.decrypt({ name: "AES-GCM", iv }, key, blob.ciphertext);
|
|
1363
|
-
return JSON.parse(textDecoder.decode(plain));
|
|
1364
|
-
};
|
|
1365
|
-
|
|
1366
|
-
// src/secrets/vault.ts
|
|
1367
|
-
var IndexedDBVault = class {
|
|
1368
|
-
dbName;
|
|
1369
|
-
constructor(opts = {}) {
|
|
1370
|
-
this.dbName = opts.dbName;
|
|
1371
|
-
}
|
|
1372
|
-
key() {
|
|
1373
|
-
return getOrCreateVaultKey(this.dbName);
|
|
1374
|
-
}
|
|
1375
|
-
/** Encrypt and store a secret string under `id`. */
|
|
1376
|
-
async setSecret(id, value) {
|
|
1377
|
-
const blob = await encryptJSON(await this.key(), value);
|
|
1378
|
-
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
1379
|
-
await db.put(SECRETS_STORE, blob, id);
|
|
1380
|
-
}
|
|
1381
|
-
/** Fetch and decrypt a secret, or undefined if absent. */
|
|
1382
|
-
async getSecret(id) {
|
|
1383
|
-
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
1384
|
-
const blob = await db.get(SECRETS_STORE, id);
|
|
1385
|
-
if (!blob) return void 0;
|
|
1386
|
-
return decryptJSON(await this.key(), blob);
|
|
1387
|
-
}
|
|
1388
|
-
async deleteSecret(id) {
|
|
1389
|
-
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
1390
|
-
await db.delete(SECRETS_STORE, id);
|
|
1391
|
-
}
|
|
1392
|
-
async listSecretIds() {
|
|
1393
|
-
const db = await openAgentWebDB({ dbName: this.dbName });
|
|
1394
|
-
return (await db.getAllKeys(SECRETS_STORE)).map(String);
|
|
1395
|
-
}
|
|
1396
|
-
};
|
|
1397
|
-
|
|
1398
1294
|
// src/secrets/store.ts
|
|
1399
1295
|
var VaultCredentialStore = class {
|
|
1400
1296
|
vault;
|