@dudko.dev/agent-web 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,1298 @@
1
+ import {
2
+ createWebLLMModel,
3
+ isWebGPUAvailable
4
+ } from "./chunk-R7H4HSYT.js";
5
+
6
+ // src/config.ts
7
+ var resolveConfig = (c) => ({
8
+ clientName: c.clientName ?? "agent-web",
9
+ sessionId: c.sessionId ?? "default",
10
+ toolMode: c.toolMode ?? "auto",
11
+ toolSelectionStrategy: c.toolSelectionStrategy ?? "all",
12
+ maxIterations: c.maxIterations ?? 8,
13
+ maxStepsPerTask: c.maxStepsPerTask ?? 4,
14
+ maxRevisions: c.maxRevisions ?? 2,
15
+ chatTimeoutMs: c.chatTimeoutMs ?? 12e4,
16
+ budgets: {
17
+ planner: c.budgets?.planner ?? 800,
18
+ executor: c.budgets?.executor ?? 1200,
19
+ replanner: c.budgets?.replanner ?? 400,
20
+ synthesizer: c.budgets?.synthesizer ?? 400
21
+ },
22
+ temperature: c.temperature,
23
+ replan: c.replan ?? true,
24
+ synthesize: c.synthesize ?? true,
25
+ compressAfterChars: c.compressAfterChars ?? 12e3
26
+ });
27
+
28
+ // src/llm/generate.ts
29
+ import {
30
+ generateObject,
31
+ generateText,
32
+ streamText
33
+ } from "ai";
34
+
35
+ // src/llm/util.ts
36
+ var promptOf = (opts) => opts.messages ? { messages: opts.messages } : { prompt: opts.prompt ?? "" };
37
+ var normalizeUsage = (u) => {
38
+ const inputTokens = u?.inputTokens ?? 0;
39
+ const outputTokens = u?.outputTokens ?? 0;
40
+ return {
41
+ inputTokens,
42
+ outputTokens,
43
+ totalTokens: u?.totalTokens ?? inputTokens + outputTokens
44
+ };
45
+ };
46
+ var timeoutSignal = (signal, ms) => {
47
+ if (!ms || ms <= 0) return signal;
48
+ const timeout = AbortSignal.timeout(ms);
49
+ if (!signal) return timeout;
50
+ return typeof AbortSignal.any === "function" ? AbortSignal.any([signal, timeout]) : signal;
51
+ };
52
+
53
+ // src/llm/generate.ts
54
+ var generate = (model, opts = {}) => generateText({
55
+ model,
56
+ system: opts.system,
57
+ ...promptOf(opts),
58
+ tools: opts.tools,
59
+ maxOutputTokens: opts.maxOutputTokens,
60
+ temperature: opts.temperature,
61
+ abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
62
+ });
63
+ var stream = (model, opts = {}) => streamText({
64
+ model,
65
+ system: opts.system,
66
+ ...promptOf(opts),
67
+ tools: opts.tools,
68
+ maxOutputTokens: opts.maxOutputTokens,
69
+ temperature: opts.temperature,
70
+ abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
71
+ });
72
+ var generateStructured = (model, schema, opts = {}) => generateObject({
73
+ model,
74
+ schema,
75
+ system: opts.system,
76
+ ...promptOf(opts),
77
+ maxOutputTokens: opts.maxOutputTokens,
78
+ temperature: opts.temperature,
79
+ abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
80
+ });
81
+
82
+ // src/memory/compress.ts
83
+ var totalChars = (msgs) => msgs.reduce((n, m) => n + m.content.length, 0);
84
+ var compressHistory = async (messages, model, opts = {}) => {
85
+ const maxChars = opts.maxChars ?? 12e3;
86
+ const keepRecent = opts.keepRecent ?? 6;
87
+ if (maxChars <= 0 || messages.length <= keepRecent + 1 || totalChars(messages) <= maxChars) {
88
+ return messages;
89
+ }
90
+ const head = messages.slice(0, messages.length - keepRecent);
91
+ const tail = messages.slice(messages.length - keepRecent);
92
+ const transcript = head.map((m) => `${m.role}: ${m.content}`).join("\n").slice(0, 8e3);
93
+ try {
94
+ const result = await generate(model, {
95
+ system: "Summarize the following conversation into a compact set of durable facts and decisions. Output plain text only.",
96
+ prompt: transcript,
97
+ maxOutputTokens: 400
98
+ });
99
+ const clean = (result.text || "").trim();
100
+ if (!clean) return messages;
101
+ const summaryMsg = {
102
+ role: "system",
103
+ content: `Summary of earlier conversation:
104
+ ${clean}`,
105
+ ts: head[0]?.ts
106
+ };
107
+ return [summaryMsg, ...tail];
108
+ } catch {
109
+ return messages;
110
+ }
111
+ };
112
+
113
+ // src/providers/types.ts
114
+ var isDirectModel = (m) => typeof m === "string" || typeof m === "object" && m !== null && "specificationVersion" in m;
115
+ var isProviderSpec = (m) => typeof m === "object" && m !== null && !isDirectModel(m) && typeof m.providerType === "string";
116
+
117
+ // src/providers/registry.ts
118
+ var REQUIRES_BASE_URL = /* @__PURE__ */ new Set(["openai-compatible"]);
119
+ var NO_KEY = /* @__PURE__ */ new Set(["web-llm"]);
120
+ var PROVIDER_PACKAGE = {
121
+ openai: "@ai-sdk/openai",
122
+ anthropic: "@ai-sdk/anthropic",
123
+ google: "@ai-sdk/google",
124
+ "openai-compatible": "@ai-sdk/openai-compatible",
125
+ xai: "@ai-sdk/xai",
126
+ deepseek: "@ai-sdk/deepseek"
127
+ };
128
+ var loadProvider = async (pkg) => {
129
+ try {
130
+ return await import(
131
+ /* @vite-ignore */
132
+ pkg
133
+ );
134
+ } catch {
135
+ throw new Error(
136
+ `Provider package "${pkg}" is not installed. Add it to your app: npm install ${pkg}`
137
+ );
138
+ }
139
+ };
140
+ var normalizeSpec = (spec) => {
141
+ if (REQUIRES_BASE_URL.has(spec.providerType) && !spec.baseURL) {
142
+ throw new Error(`provider "${spec.providerType}" requires a baseURL (point it at your server)`);
143
+ }
144
+ return spec;
145
+ };
146
+ var resolveStage = (base, override, stageName) => {
147
+ if (override !== void 0 && isDirectModel(override)) {
148
+ return { type: "direct", model: override };
149
+ }
150
+ if (override !== void 0 && isProviderSpec(override)) {
151
+ if (isProviderSpec(base) && override.providerType !== base.providerType && !override.credentialRef && !override.apiKey && !NO_KEY.has(override.providerType)) {
152
+ throw new Error(
153
+ `${stageName}: provider "${override.providerType}" differs from the base "${base.providerType}"; set ${stageName}.credentialRef or apiKey (cross-provider key inheritance is unsafe)`
154
+ );
155
+ }
156
+ return { type: "spec", spec: normalizeSpec(override) };
157
+ }
158
+ if (isDirectModel(base)) {
159
+ if (override && Object.keys(override).length > 0) {
160
+ throw new Error(
161
+ `${stageName}: cannot layer a partial override onto a direct model \u2014 pass a full model or ProviderModelSpec for ${stageName}`
162
+ );
163
+ }
164
+ return { type: "direct", model: base };
165
+ }
166
+ if (!isProviderSpec(base)) {
167
+ throw new Error(`${stageName}: base model must be a LanguageModel or a ProviderModelSpec`);
168
+ }
169
+ const ov = override ?? {};
170
+ const merged = {
171
+ providerType: base.providerType,
172
+ model: ov.model ?? base.model,
173
+ baseURL: ov.baseURL ?? base.baseURL,
174
+ credentialRef: ov.credentialRef ?? base.credentialRef,
175
+ apiKey: ov.apiKey ?? base.apiKey,
176
+ headers: ov.headers ?? base.headers,
177
+ providerOptions: ov.providerOptions ?? base.providerOptions
178
+ };
179
+ return { type: "spec", spec: normalizeSpec(merged) };
180
+ };
181
+ var resolveApiKey = async (spec, credentials) => {
182
+ if (spec.apiKey) {
183
+ console.warn(
184
+ `[agent-web] using an inline apiKey for provider "${spec.providerType}"; prefer credentialRef so the key stays in the encrypted vault`
185
+ );
186
+ return spec.apiKey;
187
+ }
188
+ if (spec.credentialRef) {
189
+ if (!credentials) {
190
+ throw new Error(
191
+ `credentialRef "${spec.credentialRef}" is set but no CredentialStore was provided to the agent`
192
+ );
193
+ }
194
+ const key = await credentials.getApiKey(spec.credentialRef);
195
+ if (!key) {
196
+ throw new Error(
197
+ `no API key found in the CredentialStore for credentialRef "${spec.credentialRef}"`
198
+ );
199
+ }
200
+ return key;
201
+ }
202
+ return void 0;
203
+ };
204
+ var buildModelFromStage = async (resolved, credentials, clientName = "agent-web") => {
205
+ if (resolved.type === "direct") return resolved.model;
206
+ const spec = resolved.spec;
207
+ const providerOptions = spec.providerOptions ?? {};
208
+ const apiKey = await resolveApiKey(spec, credentials);
209
+ switch (spec.providerType) {
210
+ case "web-llm": {
211
+ const { createWebLLMModel: createWebLLMModel2 } = await import("./webllm-IM3UD6YI.js");
212
+ return createWebLLMModel2(spec.model, providerOptions);
213
+ }
214
+ case "gateway": {
215
+ const { createGateway } = await import("ai");
216
+ const provider = createGateway({
217
+ baseURL: spec.baseURL || void 0,
218
+ apiKey,
219
+ headers: spec.headers,
220
+ ...providerOptions
221
+ });
222
+ return provider(spec.model);
223
+ }
224
+ case "openai": {
225
+ const { createOpenAI } = await loadProvider(
226
+ PROVIDER_PACKAGE.openai
227
+ );
228
+ const provider = createOpenAI({
229
+ baseURL: spec.baseURL || void 0,
230
+ apiKey,
231
+ headers: spec.headers,
232
+ ...providerOptions
233
+ });
234
+ return provider(spec.model);
235
+ }
236
+ case "anthropic": {
237
+ const { createAnthropic } = await loadProvider(
238
+ PROVIDER_PACKAGE.anthropic
239
+ );
240
+ const provider = createAnthropic({
241
+ baseURL: spec.baseURL || void 0,
242
+ apiKey,
243
+ // Anthropic's SDK refuses browser calls unless this opt-in header is
244
+ // set; host headers may override it.
245
+ headers: { "anthropic-dangerous-direct-browser-access": "true", ...spec.headers },
246
+ ...providerOptions
247
+ });
248
+ return provider(spec.model);
249
+ }
250
+ case "google": {
251
+ const { createGoogleGenerativeAI } = await loadProvider(
252
+ PROVIDER_PACKAGE.google
253
+ );
254
+ const provider = createGoogleGenerativeAI({
255
+ baseURL: spec.baseURL || void 0,
256
+ apiKey,
257
+ headers: spec.headers,
258
+ ...providerOptions
259
+ });
260
+ return provider(spec.model);
261
+ }
262
+ case "openai-compatible": {
263
+ const { createOpenAICompatible } = await loadProvider(PROVIDER_PACKAGE["openai-compatible"]);
264
+ const provider = createOpenAICompatible({
265
+ name: clientName,
266
+ baseURL: spec.baseURL,
267
+ apiKey,
268
+ headers: spec.headers,
269
+ ...providerOptions
270
+ });
271
+ return provider(spec.model);
272
+ }
273
+ case "xai": {
274
+ const { createXai } = await loadProvider(PROVIDER_PACKAGE.xai);
275
+ const provider = createXai({
276
+ baseURL: spec.baseURL || void 0,
277
+ apiKey,
278
+ headers: spec.headers,
279
+ ...providerOptions
280
+ });
281
+ return provider(spec.model);
282
+ }
283
+ case "deepseek": {
284
+ const { createDeepSeek } = await loadProvider(
285
+ PROVIDER_PACKAGE.deepseek
286
+ );
287
+ const provider = createDeepSeek({
288
+ baseURL: spec.baseURL || void 0,
289
+ apiKey,
290
+ headers: spec.headers,
291
+ ...providerOptions
292
+ });
293
+ return provider(spec.model);
294
+ }
295
+ }
296
+ };
297
+ var resolveModel = (input, credentials, clientName) => buildModelFromStage(resolveStage(input, void 0, "model"), credentials, clientName);
298
+
299
+ // src/prompts.ts
300
+ var numbered = (items, empty) => items.length ? items.map((s, i) => `${i + 1}. ${s}`).join("\n") : empty;
301
+ var stateBlock = (state) => state && state.trim() ? `
302
+
303
+ STATE:
304
+ ${state}` : "";
305
+ var PLANNER_NATIVE = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
306
+ Produce a brief "thought" and an ordered "steps" list (1\u20136 DISTINCT, self-contained steps) grounded in the current STATE and the available TOOLS.
307
+ If the user only greets, thanks, makes small talk, asks a question, or is unclear: return an EMPTY steps list and put a short, friendly answer in "thought".
308
+ Never repeat or pad steps.`;
309
+ var PLANNER_PROMPTED = `You are the PLANNER of a tool-using agent that changes a workspace step by step.
310
+
311
+ Reply with a single JSON object, nothing else:
312
+ { "reply": string, "plan": string[] }
313
+ - Only produce a "plan" when the user CLEARLY asks to build, do, or change something. For a greeting, small talk, thanks, a question, or an unclear/empty request: set "plan": [] and put a short, friendly "reply".
314
+ - For a real goal: "reply" is one short sentence; "plan" is 1\u20136 DISTINCT, self-contained steps. Never repeat or pad steps.
315
+ - Plan realistic steps the available TOOLS can perform, grounded in the current STATE.`;
316
+ var EXECUTOR_NATIVE = `You are the EXECUTOR of a tool-using agent. Carry out ONLY the current step by calling the provided tools.
317
+ Build on the current STATE \u2014 do not repeat work that is already there.
318
+ When the step is done, reply with one short human sentence describing what you did (no JSON).
319
+ If you CANNOT complete the step (a needed tool is missing or an input is unavailable), explain why in one sentence and include the token [BLOCKER].`;
320
+ var EXECUTOR_PROMPTED = `You are the EXECUTOR of a tool-using agent. Carry out ONLY the current step by emitting tool calls.
321
+
322
+ Reply with a single JSON object, nothing else:
323
+ { "reply": string, "actions": [ { "tool": string, "args": object } ] }
324
+ - "actions" are the tool calls for THIS step ([] if none are needed). Use ONLY tools from the TOOLS list; "args" must match the tool's parameters.
325
+ - Build on the current STATE \u2014 do not repeat work that is already there.
326
+ - "reply" is one short human sentence (no JSON) describing what you did.
327
+ - If you CANNOT complete the step, set "actions": [] and put the token [BLOCKER] in "reply" with a short reason.`;
328
+ var REPLANNER_NATIVE = `You are the REPLANNER. After a step that was blocked or had a failed tool call, decide whether to keep going, revise the remaining steps, or finish.
329
+ "continue": the remaining steps still fit. "revise": provide a better "plan" for the REMAINING work (never repeat done work). "finish": the goal is already met.
330
+ Judge from the current STATE vs the goal. Prefer "continue".`;
331
+ var REPLANNER_PROMPTED = `You are the REPLANNER. After each executed step you decide whether to keep going, revise the remaining steps, or finish.
332
+
333
+ Reply with a single JSON object, nothing else:
334
+ { "decision": "continue" | "revise" | "finish", "reason": string, "plan": string[] }
335
+ - "continue": remaining steps still fit \u2014 proceed (omit "plan").
336
+ - "revise": replace the REMAINING steps with a better list in "plan"; never repeat done work.
337
+ - "finish": the goal is already met \u2014 stop (omit "plan").
338
+ Judge from the current STATE vs the goal. Prefer "continue".`;
339
+ var SYNTHESIZER_SYSTEM = `You are the SYNTHESIZER. In 1\u20133 short, friendly sentences, tell the user what was done to meet their goal. Be concrete. Output plain text only \u2014 no JSON, no code.`;
340
+ var defaultPrompts = {
341
+ planner: (ctx) => ({
342
+ system: ctx.mode === "prompted" ? PLANNER_PROMPTED : PLANNER_NATIVE,
343
+ prompt: `GOAL: ${ctx.goal}${stateBlock(ctx.state)}
344
+
345
+ TOOLS:
346
+ ${ctx.toolCatalog}`
347
+ }),
348
+ executor: (ctx) => ({
349
+ system: ctx.mode === "prompted" ? EXECUTOR_PROMPTED : EXECUTOR_NATIVE,
350
+ prompt: [
351
+ `GOAL: ${ctx.goal}`,
352
+ stateBlock(ctx.state).trimStart(),
353
+ ctx.mode === "prompted" ? `TOOLS:
354
+ ${ctx.toolCatalog}` : "",
355
+ ctx.done.length ? `Already done:
356
+ ${numbered(ctx.done, "")}` : "",
357
+ `STEP ${ctx.index}/${ctx.total}: ${ctx.step}`
358
+ ].filter(Boolean).join("\n\n")
359
+ }),
360
+ replanner: (ctx) => ({
361
+ system: ctx.mode === "prompted" ? REPLANNER_PROMPTED : REPLANNER_NATIVE,
362
+ prompt: [
363
+ `GOAL: ${ctx.goal}`,
364
+ stateBlock(ctx.state).trimStart(),
365
+ `Already done:
366
+ ${numbered(ctx.done, "(nothing yet)")}`,
367
+ `Remaining plan:
368
+ ${numbered(ctx.remaining, "(none)")}`,
369
+ "Decide: continue, revise, or finish."
370
+ ].filter(Boolean).join("\n\n")
371
+ }),
372
+ synthesizer: (ctx) => ({
373
+ system: SYNTHESIZER_SYSTEM,
374
+ prompt: [
375
+ `GOAL: ${ctx.goal}`,
376
+ stateBlock(ctx.state).trimStart(),
377
+ `What was done:
378
+ ${numbered(ctx.done, "(no changes)")}`,
379
+ "Write the final summary for the user."
380
+ ].filter(Boolean).join("\n\n")
381
+ })
382
+ };
383
+ var withSystem = (base, extra) => extra && extra.trim() ? `${extra.trim()}
384
+
385
+ ${base}` : base;
386
+
387
+ // src/tools/mode.ts
388
+ var modelProviderId = (model) => typeof model === "string" ? model : model.provider ?? "";
389
+ var LOCAL_RE = /web-?llm|mlc|browser-ai|transformers|nano|built-?in/i;
390
+ var selectToolMode = (model, mode = "auto") => {
391
+ if (mode === "native" || mode === "prompted") return mode;
392
+ return LOCAL_RE.test(modelProviderId(model)) ? "prompted" : "native";
393
+ };
394
+
395
+ // src/tools/types.ts
396
+ var promptHintOf = (tool2) => typeof tool2.promptHint === "string" ? tool2.promptHint : void 0;
397
+
398
+ // src/tools/prompted.ts
399
+ var renderCatalog = (tools) => {
400
+ const names = Object.keys(tools);
401
+ if (names.length === 0) return "(no tools)";
402
+ return names.map((name) => {
403
+ const t = tools[name];
404
+ const hint = promptHintOf(t) ?? "";
405
+ const desc = typeof t.description === "string" ? t.description : "";
406
+ return `- ${name}(${hint}): ${desc}`;
407
+ }).join("\n");
408
+ };
409
+ var dispatch = async (action, tools, ctx = {}) => {
410
+ const t = tools[action.tool];
411
+ if (!t) {
412
+ return {
413
+ name: action.tool,
414
+ input: action.args,
415
+ output: `unknown tool "${action.tool}"`,
416
+ ok: false
417
+ };
418
+ }
419
+ let input = action.args;
420
+ const schema = t.inputSchema;
421
+ if (schema && typeof schema.safeParse === "function") {
422
+ const parsed = schema.safeParse(input);
423
+ if (!parsed.success) {
424
+ return {
425
+ name: action.tool,
426
+ input,
427
+ output: `invalid args for "${action.tool}": ${parsed.error?.message ?? "schema validation failed"}`,
428
+ ok: false
429
+ };
430
+ }
431
+ input = parsed.data;
432
+ }
433
+ const execute = t.execute;
434
+ if (typeof execute !== "function") {
435
+ return { name: action.tool, input, output: `tool "${action.tool}" has no execute`, ok: false };
436
+ }
437
+ try {
438
+ const output = await execute(input, {
439
+ toolCallId: ctx.toolCallId ?? `prompted-${action.tool}`,
440
+ messages: ctx.messages ?? [],
441
+ abortSignal: ctx.abortSignal
442
+ });
443
+ return { name: action.tool, input, output, ok: true };
444
+ } catch (err) {
445
+ return {
446
+ name: action.tool,
447
+ input,
448
+ output: err instanceof Error ? err.message : "tool error",
449
+ ok: false
450
+ };
451
+ }
452
+ };
453
+
454
+ // src/llm/tool-loop.ts
455
+ import {
456
+ generateText as generateText2,
457
+ stepCountIs,
458
+ streamText as streamText2
459
+ } from "ai";
460
+
461
+ // src/parse.ts
462
+ var stripFences = (text) => (text || "").replace(/```(?:json)?/gi, "").trim();
463
+ var extractBalanced = (text, open) => {
464
+ const close = open === "{" ? "}" : "]";
465
+ const start = text.indexOf(open);
466
+ if (start < 0) return void 0;
467
+ let depth = 0;
468
+ let inStr = false;
469
+ let esc = false;
470
+ for (let i = start; i < text.length; i += 1) {
471
+ const c = text[i];
472
+ if (inStr) {
473
+ if (esc) esc = false;
474
+ else if (c === "\\") esc = true;
475
+ else if (c === '"') inStr = false;
476
+ continue;
477
+ }
478
+ if (c === '"') inStr = true;
479
+ else if (c === open) depth += 1;
480
+ else if (c === close) {
481
+ depth -= 1;
482
+ if (depth === 0) return text.slice(start, i + 1);
483
+ }
484
+ }
485
+ return void 0;
486
+ };
487
+ var tryParse = (s) => {
488
+ if (!s) return void 0;
489
+ try {
490
+ return JSON.parse(s);
491
+ } catch {
492
+ return void 0;
493
+ }
494
+ };
495
+ var looksLikeJson = (s) => {
496
+ const t = (s || "").trim();
497
+ return t.startsWith("{") || t.startsWith("[") || /"(tool|args|plan|reply|actions|decision)"\s*:/.test(t);
498
+ };
499
+ var asObject = (text) => {
500
+ const v = tryParse(text) ?? tryParse(extractBalanced(text, "{"));
501
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
502
+ };
503
+ var normalizeSteps = (arr, cap = 12) => {
504
+ if (!Array.isArray(arr)) return [];
505
+ const seen = {};
506
+ const out = [];
507
+ for (const s of arr) {
508
+ if (out.length >= cap || typeof s !== "string") continue;
509
+ const step = s.trim();
510
+ const key = step.toLowerCase();
511
+ if (!step || seen[key]) continue;
512
+ seen[key] = true;
513
+ out.push(step);
514
+ }
515
+ return out;
516
+ };
517
+ var salvageStringArray = (text, field) => {
518
+ const at = text.search(new RegExp(`"${field}"\\s*:`));
519
+ if (at < 0) return [];
520
+ const region = text.slice(at);
521
+ const out = [];
522
+ const re = /"((?:[^"\\]|\\.)*)"/g;
523
+ let skippedKey = false;
524
+ let m = re.exec(region);
525
+ while (m) {
526
+ if (skippedKey) out.push(m[1]);
527
+ skippedKey = true;
528
+ m = re.exec(region);
529
+ }
530
+ return out;
531
+ };
532
+ var salvageString = (text, field) => {
533
+ const m = text.match(new RegExp(`"${field}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
534
+ return m ? m[1].trim() : "";
535
+ };
536
+ var parsePlannerResponse = (raw) => {
537
+ const text = stripFences(raw);
538
+ const obj = asObject(text);
539
+ if (obj) {
540
+ const reply = typeof obj.reply === "string" ? obj.reply.trim() : "";
541
+ return { reply, plan: normalizeSteps(obj.plan) };
542
+ }
543
+ if (/"plan"\s*:/.test(text)) {
544
+ return {
545
+ reply: salvageString(text, "reply"),
546
+ plan: normalizeSteps(salvageStringArray(text, "plan"))
547
+ };
548
+ }
549
+ return { reply: looksLikeJson(text) ? "" : text.trim(), plan: [] };
550
+ };
551
+ var toActions = (value) => {
552
+ if (!Array.isArray(value)) return [];
553
+ const out = [];
554
+ for (const a of value) {
555
+ if (a && typeof a === "object" && typeof a.tool === "string") {
556
+ const rec = a;
557
+ out.push({
558
+ tool: rec.tool,
559
+ args: rec.args && typeof rec.args === "object" && !Array.isArray(rec.args) ? rec.args : {}
560
+ });
561
+ }
562
+ }
563
+ return out;
564
+ };
565
+ var parseExecutorResponse = (raw) => {
566
+ const text = stripFences(raw);
567
+ const obj = asObject(text);
568
+ if (obj) {
569
+ const reply = typeof obj.reply === "string" ? obj.reply.trim() : "";
570
+ return { reply, actions: toActions(obj.actions) };
571
+ }
572
+ const arr = tryParse(extractBalanced(text, "["));
573
+ if (Array.isArray(arr)) {
574
+ const actions = toActions(arr);
575
+ return { reply: "", actions };
576
+ }
577
+ return { reply: looksLikeJson(text) ? "" : text.trim(), actions: [] };
578
+ };
579
+ var parseReplannerResponse = (raw) => {
580
+ const obj = asObject(stripFences(raw));
581
+ const decision = obj?.decision === "revise" || obj?.decision === "finish" ? obj.decision : "continue";
582
+ const reason = typeof obj?.reason === "string" ? obj.reason.trim() : "";
583
+ return { decision, reason, plan: normalizeSteps(obj?.plan) };
584
+ };
585
+ var parsePlainText = (raw) => {
586
+ const text = stripFences(raw);
587
+ const obj = asObject(text);
588
+ if (obj && typeof obj.reply === "string") return obj.reply.trim();
589
+ return looksLikeJson(text) ? "" : text.trim();
590
+ };
591
+
592
+ // src/llm/tool-loop.ts
593
+ var runToolLoop = (model, opts) => opts.mode === "prompted" ? runPrompted(model, opts) : runNative(model, opts);
594
+ var runNative = async (model, opts) => {
595
+ const toolCalls = [];
596
+ const pending = /* @__PURE__ */ new Map();
597
+ const result = streamText2({
598
+ model,
599
+ tools: opts.tools,
600
+ ...opts.activeTools ? { activeTools: opts.activeTools } : {},
601
+ stopWhen: stepCountIs(opts.maxSteps ?? 4),
602
+ system: opts.system,
603
+ ...promptOf(opts),
604
+ maxOutputTokens: opts.maxOutputTokens,
605
+ temperature: opts.temperature,
606
+ abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
607
+ });
608
+ for await (const part of result.fullStream) {
609
+ switch (part.type) {
610
+ case "text-delta":
611
+ if (part.text) opts.callbacks?.onTextDelta?.(part.text);
612
+ break;
613
+ case "tool-call":
614
+ pending.set(part.toolCallId, { name: part.toolName, input: part.input });
615
+ opts.callbacks?.onToolCall?.(part.toolName, part.input);
616
+ break;
617
+ case "tool-result": {
618
+ const known = pending.get(part.toolCallId);
619
+ toolCalls.push({
620
+ name: part.toolName,
621
+ input: known?.input ?? part.input,
622
+ output: part.output,
623
+ ok: true
624
+ });
625
+ opts.callbacks?.onToolResult?.(part.toolName, part.output, true);
626
+ pending.delete(part.toolCallId);
627
+ break;
628
+ }
629
+ case "tool-error": {
630
+ const known = pending.get(part.toolCallId);
631
+ toolCalls.push({
632
+ name: part.toolName,
633
+ input: known?.input ?? part.input,
634
+ output: part.error,
635
+ ok: false
636
+ });
637
+ opts.callbacks?.onToolResult?.(part.toolName, part.error, false);
638
+ pending.delete(part.toolCallId);
639
+ break;
640
+ }
641
+ case "error":
642
+ throw part.error instanceof Error ? part.error : new Error(String(part.error));
643
+ }
644
+ }
645
+ const [text, usage] = await Promise.all([result.text, result.usage]);
646
+ return { text: text.trim(), toolCalls, usage: normalizeUsage(usage) };
647
+ };
648
+ var runPrompted = async (model, opts) => {
649
+ const result = await generateText2({
650
+ model,
651
+ system: opts.system,
652
+ ...promptOf(opts),
653
+ maxOutputTokens: opts.maxOutputTokens,
654
+ temperature: opts.temperature,
655
+ abortSignal: timeoutSignal(opts.abortSignal, opts.timeoutMs)
656
+ });
657
+ if (result.text) opts.callbacks?.onTextDelta?.(result.text);
658
+ const parsed = parseExecutorResponse(result.text);
659
+ const toolCalls = [];
660
+ for (const action of parsed.actions) {
661
+ opts.callbacks?.onToolCall?.(action.tool, action.args);
662
+ const rec = await dispatch(action, opts.tools, { abortSignal: opts.abortSignal });
663
+ toolCalls.push(rec);
664
+ opts.callbacks?.onToolResult?.(rec.name, rec.output, rec.ok);
665
+ }
666
+ return { text: parsed.reply, toolCalls, usage: normalizeUsage(result.usage) };
667
+ };
668
+
669
+ // src/agent/internal.ts
670
+ var systemFor = (ctx, base) => withSystem(base, ctx.raw.systemPrompt);
671
+
672
+ // src/agent/executor.ts
673
+ var BLOCKER = "[BLOCKER]";
674
+ var splitBlocker = (raw) => {
675
+ if (!raw.includes(BLOCKER)) return { summary: raw.trim(), blocked: false };
676
+ return { summary: raw.split(BLOCKER).join("").trim(), blocked: true };
677
+ };
678
+ var activeToolNames = (ctx, step) => {
679
+ if (ctx.config.toolSelectionStrategy !== "plan-narrowed") return void 0;
680
+ const suggested = step.suggestedTools ?? [];
681
+ const known = suggested.filter((n) => ctx.tools[n]);
682
+ if (known.length === 0 && suggested.length > 0) return Object.keys(ctx.tools);
683
+ return known;
684
+ };
685
+ var summaryOf = (text, toolCallCount) => {
686
+ if (text.length > 0) return text;
687
+ return toolCallCount > 0 ? `Executed ${toolCallCount} tool call(s).` : "Step produced no output.";
688
+ };
689
+ var executeStep = async (ctx, goal, step, index, total, done) => {
690
+ const state = await ctx.state();
691
+ const parts = ctx.prompts.executor({
692
+ goal,
693
+ state,
694
+ step: step.description,
695
+ index,
696
+ total,
697
+ toolCatalog: ctx.toolCatalog,
698
+ done,
699
+ mode: ctx.executorMode
700
+ });
701
+ const loop = await runToolLoop(ctx.executorModel, {
702
+ mode: ctx.executorMode,
703
+ system: systemFor(ctx, parts.system),
704
+ prompt: parts.prompt,
705
+ tools: ctx.tools,
706
+ activeTools: activeToolNames(ctx, step),
707
+ maxSteps: ctx.config.maxStepsPerTask,
708
+ maxOutputTokens: ctx.config.budgets.executor,
709
+ temperature: ctx.config.temperature,
710
+ abortSignal: ctx.signal,
711
+ timeoutMs: ctx.config.chatTimeoutMs,
712
+ callbacks: {
713
+ onTextDelta: (delta) => ctx.emit({ type: "step.text-delta", step, delta }),
714
+ onToolCall: (name, input) => ctx.emit({ type: "step.tool-call", step, name, input }),
715
+ onToolResult: (name, output, ok) => ctx.emit({ type: "step.tool-result", step, name, output, ok })
716
+ }
717
+ });
718
+ const { summary, blocked } = splitBlocker(loop.text);
719
+ return {
720
+ result: {
721
+ step,
722
+ summary: summaryOf(summary, loop.toolCalls.length),
723
+ toolCalls: loop.toolCalls,
724
+ blocked
725
+ },
726
+ usage: loop.usage
727
+ };
728
+ };
729
+ var shouldReplan = (result) => result.blocked || result.toolCalls.some((c) => !c.ok);
730
+
731
+ // src/agent/loop-types.ts
732
+ var emptyUsage = () => ({ inputTokens: 0, outputTokens: 0, totalTokens: 0 });
733
+ var addUsage = (a, b) => ({
734
+ inputTokens: a.inputTokens + b.inputTokens,
735
+ outputTokens: a.outputTokens + b.outputTokens,
736
+ totalTokens: a.totalTokens + b.totalTokens
737
+ });
738
+
739
+ // src/agent/schemas.ts
740
+ import { z } from "zod";
741
+ var PlanStepSchema = z.object({
742
+ description: z.string(),
743
+ expectedOutcome: z.string().optional(),
744
+ suggestedTools: z.array(z.string()).optional()
745
+ });
746
+ var PlanSchema = z.object({
747
+ /** One short sentence. For a greeting/question/unclear request, holds the answer and steps is empty. */
748
+ thought: z.string(),
749
+ steps: z.array(PlanStepSchema)
750
+ });
751
+ var ReplanSchema = z.object({
752
+ decision: z.enum(["continue", "revise", "finish"]),
753
+ reason: z.string(),
754
+ /** Replacement for the REMAINING steps when decision is 'revise'. */
755
+ plan: z.array(z.string()).optional()
756
+ });
757
+
758
+ // src/agent/planner.ts
759
+ var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).map((s, i) => ({
760
+ id: `s${i + 1}`,
761
+ description: s.description.trim(),
762
+ expectedOutcome: s.expectedOutcome,
763
+ suggestedTools: s.suggestedTools
764
+ }));
765
+ var createPlan = async (ctx, goal) => {
766
+ const state = await ctx.state();
767
+ const parts = ctx.prompts.planner({
768
+ goal,
769
+ state,
770
+ toolCatalog: ctx.toolCatalog,
771
+ mode: ctx.plannerMode
772
+ });
773
+ const system = systemFor(ctx, parts.system);
774
+ const common = {
775
+ system,
776
+ prompt: parts.prompt,
777
+ maxOutputTokens: ctx.config.budgets.planner,
778
+ temperature: ctx.config.temperature,
779
+ abortSignal: ctx.signal,
780
+ timeoutMs: ctx.config.chatTimeoutMs
781
+ };
782
+ if (ctx.plannerMode === "native") {
783
+ try {
784
+ const result2 = await generateStructured(ctx.plannerModel, PlanSchema, common);
785
+ return {
786
+ plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
787
+ usage: normalizeUsage(result2.usage)
788
+ };
789
+ } catch (err) {
790
+ ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
791
+ }
792
+ }
793
+ const result = await generate(ctx.plannerModel, common);
794
+ const parsed = parsePlannerResponse(result.text);
795
+ return {
796
+ plan: {
797
+ thought: parsed.reply,
798
+ steps: toSteps(parsed.plan.map((description) => ({ description })))
799
+ },
800
+ usage: ctx.plannerMode === "native" ? emptyUsage() : normalizeUsage(result.usage)
801
+ };
802
+ };
803
+ var asMessage = (err) => err instanceof Error ? err.message : String(err);
804
+
805
+ // src/agent/replanner.ts
806
+ var decideReplan = async (ctx, goal, done, remaining) => {
807
+ const state = await ctx.state();
808
+ const parts = ctx.prompts.replanner({ goal, state, done, remaining, mode: ctx.plannerMode });
809
+ const common = {
810
+ system: systemFor(ctx, parts.system),
811
+ prompt: parts.prompt,
812
+ maxOutputTokens: ctx.config.budgets.replanner,
813
+ temperature: ctx.config.temperature,
814
+ abortSignal: ctx.signal,
815
+ timeoutMs: ctx.config.chatTimeoutMs
816
+ };
817
+ try {
818
+ if (ctx.plannerMode === "native") {
819
+ const result2 = await generateStructured(ctx.plannerModel, ReplanSchema, common);
820
+ return {
821
+ decision: result2.object.decision,
822
+ reason: result2.object.reason,
823
+ plan: result2.object.plan ?? [],
824
+ usage: normalizeUsage(result2.usage)
825
+ };
826
+ }
827
+ const result = await generate(ctx.plannerModel, common);
828
+ const parsed = parseReplannerResponse(result.text);
829
+ return {
830
+ decision: parsed.decision,
831
+ reason: parsed.reason,
832
+ plan: parsed.plan,
833
+ usage: normalizeUsage(result.usage)
834
+ };
835
+ } catch {
836
+ return { decision: "continue", reason: "replanner unavailable", plan: [], usage: emptyUsage() };
837
+ }
838
+ };
839
+
840
+ // src/agent/synthesizer.ts
841
+ var synthesizeAnswer = async (ctx, goal, done) => {
842
+ const state = await ctx.state();
843
+ const parts = ctx.prompts.synthesizer({ goal, state, done });
844
+ const result = await generate(ctx.synthesizerModel, {
845
+ system: systemFor(ctx, parts.system),
846
+ prompt: parts.prompt,
847
+ maxOutputTokens: ctx.config.budgets.synthesizer,
848
+ temperature: ctx.config.temperature,
849
+ abortSignal: ctx.signal,
850
+ timeoutMs: ctx.config.chatTimeoutMs
851
+ });
852
+ const clean = parsePlainText(result.text);
853
+ return {
854
+ text: clean || "Done \u2014 the changes have been applied.",
855
+ usage: normalizeUsage(result.usage)
856
+ };
857
+ };
858
+
859
+ // src/agent/runner.ts
860
+ var errMessage = (err) => err instanceof Error ? err.message : String(err);
861
+ var createAgent = async (config) => {
862
+ const cfg = resolveConfig(config);
863
+ const [plannerModel, executorModel, synthesizerModel] = await Promise.all([
864
+ buildModelFromStage(
865
+ resolveStage(config.model, config.planner, "planner"),
866
+ config.credentials,
867
+ cfg.clientName
868
+ ),
869
+ buildModelFromStage(
870
+ resolveStage(config.model, void 0, "executor"),
871
+ config.credentials,
872
+ cfg.clientName
873
+ ),
874
+ buildModelFromStage(
875
+ resolveStage(config.model, config.synthesizer, "synthesizer"),
876
+ config.credentials,
877
+ cfg.clientName
878
+ )
879
+ ]);
880
+ const plannerMode = selectToolMode(plannerModel, cfg.toolMode);
881
+ const executorMode = selectToolMode(executorModel, cfg.toolMode);
882
+ const tools = config.tools ?? {};
883
+ const toolCatalog = renderCatalog(tools);
884
+ const prompts = { ...defaultPrompts, ...config.prompts };
885
+ const run = async (goal, opts = {}) => {
886
+ const emit = (event) => {
887
+ try {
888
+ opts.onEvent?.(event);
889
+ } catch {
890
+ }
891
+ };
892
+ const text = goal.trim();
893
+ const sessionId = opts.sessionId ?? cfg.sessionId;
894
+ let usage = emptyUsage();
895
+ const result = {
896
+ goal: text,
897
+ final: "",
898
+ plan: { thought: "", steps: [] },
899
+ trace: [],
900
+ steps: 0,
901
+ applied: 0,
902
+ stopped: false,
903
+ usage
904
+ };
905
+ if (!text) return result;
906
+ const state = async () => config.describeState ? config.describeState() : void 0;
907
+ const ctx = {
908
+ config: cfg,
909
+ raw: config,
910
+ plannerModel,
911
+ executorModel,
912
+ synthesizerModel,
913
+ plannerMode,
914
+ executorMode,
915
+ tools,
916
+ toolCatalog,
917
+ prompts,
918
+ emit,
919
+ signal: opts.signal,
920
+ state
921
+ };
922
+ const isAborted = () => opts.signal?.aborted === true;
923
+ const bumpUsage = (u, phase) => {
924
+ usage = addUsage(usage, u);
925
+ result.usage = usage;
926
+ emit({ type: "usage", phase, usage: u });
927
+ };
928
+ const remember = async (msg) => {
929
+ if (!config.memory) return;
930
+ try {
931
+ await config.memory.append(sessionId, msg);
932
+ } catch {
933
+ }
934
+ };
935
+ emit({ type: "run.start", goal: text });
936
+ await remember({ role: "user", content: text });
937
+ try {
938
+ const planned = await createPlan(ctx, text);
939
+ bumpUsage(planned.usage, "plan");
940
+ result.plan = planned.plan;
941
+ if (isAborted()) {
942
+ emit({ type: "stopped" });
943
+ result.stopped = true;
944
+ return result;
945
+ }
946
+ if (planned.plan.steps.length === 0) {
947
+ const answer = planned.plan.thought.trim() || "Tell me what you'd like to do.";
948
+ emit({ type: "final", text: answer });
949
+ await remember({ role: "assistant", content: answer });
950
+ result.final = answer;
951
+ return result;
952
+ }
953
+ emit({ type: "plan.created", plan: planned.plan });
954
+ planned.plan.steps.forEach((step, index) => emit({ type: "plan.step-added", step, index }));
955
+ const done = [];
956
+ let remaining = [...planned.plan.steps];
957
+ let iter = 0;
958
+ let revisions = 0;
959
+ while (remaining.length > 0 && iter < cfg.maxIterations) {
960
+ if (isAborted()) {
961
+ emit({ type: "stopped" });
962
+ result.stopped = true;
963
+ return result;
964
+ }
965
+ iter += 1;
966
+ const step = remaining.shift();
967
+ const stepNo = done.length + 1;
968
+ const total = stepNo + remaining.length;
969
+ emit({ type: "step.start", step, index: stepNo, total });
970
+ let stepResult;
971
+ try {
972
+ const out = await executeStep(ctx, text, step, stepNo, total, done);
973
+ bumpUsage(out.usage, "execute");
974
+ stepResult = out.result;
975
+ } catch (err) {
976
+ emit({ type: "error", phase: "execute", error: errMessage(err) });
977
+ stepResult = { step, summary: errMessage(err), toolCalls: [], blocked: true };
978
+ }
979
+ result.trace.push(stepResult);
980
+ const applied = stepResult.toolCalls.filter((c) => c.ok).length;
981
+ const failed = stepResult.toolCalls.length - applied;
982
+ result.applied += applied;
983
+ result.steps = stepNo;
984
+ emit({ type: "step.complete", step, result: stepResult });
985
+ done.push(
986
+ `${step.description} \u2014 ${applied} applied${failed ? `, ${failed} failed` : ""}${stepResult.blocked ? ", blocked" : ""}`
987
+ );
988
+ if (cfg.replan && remaining.length > 0 && iter < cfg.maxIterations && revisions < cfg.maxRevisions && !isAborted() && shouldReplan(stepResult)) {
989
+ const decision = await decideReplan(
990
+ ctx,
991
+ text,
992
+ done,
993
+ remaining.map((s) => s.description)
994
+ );
995
+ bumpUsage(decision.usage, "replan");
996
+ emit({ type: "replan.decision", mode: decision.decision, reason: decision.reason });
997
+ if (decision.decision === "finish") break;
998
+ if (decision.decision === "revise" && decision.plan.length > 0) {
999
+ revisions += 1;
1000
+ remaining = decision.plan.slice(0, cfg.maxIterations - iter).map((description, i) => ({ id: `r${iter}-${i + 1}`, description }));
1001
+ emit({
1002
+ type: "plan.revised",
1003
+ plan: { thought: planned.plan.thought, steps: remaining },
1004
+ reason: decision.reason
1005
+ });
1006
+ }
1007
+ }
1008
+ }
1009
+ if (isAborted()) {
1010
+ emit({ type: "stopped" });
1011
+ result.stopped = true;
1012
+ return result;
1013
+ }
1014
+ let summary = "Done \u2014 the changes have been applied.";
1015
+ if (cfg.synthesize) {
1016
+ try {
1017
+ const synth = await synthesizeAnswer(ctx, text, done);
1018
+ bumpUsage(synth.usage, "synthesize");
1019
+ if (synth.text) summary = synth.text;
1020
+ } catch {
1021
+ }
1022
+ }
1023
+ emit({ type: "final", text: summary });
1024
+ await remember({ role: "assistant", content: summary });
1025
+ result.final = summary;
1026
+ if (config.memory && cfg.compressAfterChars > 0) {
1027
+ try {
1028
+ const history = await config.memory.load(sessionId);
1029
+ const compacted = await compressHistory(history, synthesizerModel, {
1030
+ maxChars: cfg.compressAfterChars
1031
+ });
1032
+ if (compacted !== history) await config.memory.replace(sessionId, compacted);
1033
+ } catch {
1034
+ }
1035
+ }
1036
+ return result;
1037
+ } catch (err) {
1038
+ emit({ type: "error", phase: "run", error: errMessage(err) });
1039
+ result.final = errMessage(err);
1040
+ return result;
1041
+ }
1042
+ };
1043
+ return {
1044
+ run,
1045
+ models: { planner: plannerModel, executor: executorModel, synthesizer: synthesizerModel }
1046
+ };
1047
+ };
1048
+
1049
+ // src/providers/capabilities.ts
1050
+ var CLOUD = /* @__PURE__ */ new Set([
1051
+ "openai",
1052
+ "anthropic",
1053
+ "google",
1054
+ "openai-compatible",
1055
+ "xai",
1056
+ "deepseek",
1057
+ "gateway"
1058
+ ]);
1059
+ var supportsNativeTools = (p) => CLOUD.has(p);
1060
+ var supportsStructuredOutput = (p) => CLOUD.has(p);
1061
+ var directBrowserOk = (p) => p === "google" || p === "gateway" || p === "openai-compatible" || p === "anthropic";
1062
+
1063
+ // src/storage/db.ts
1064
+ import { openDB } from "idb";
1065
+ var KEYS_STORE = "keys";
1066
+ var SECRETS_STORE = "secrets";
1067
+ var SESSIONS_STORE = "sessions";
1068
+ var cache = /* @__PURE__ */ new Map();
1069
+ var openAgentWebDB = (opts = {}) => {
1070
+ const name = opts.dbName ?? "agent-web";
1071
+ let p = cache.get(name);
1072
+ if (!p) {
1073
+ p = openDB(name, 1, {
1074
+ upgrade(db) {
1075
+ if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE);
1076
+ if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE);
1077
+ if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE);
1078
+ }
1079
+ });
1080
+ cache.set(name, p);
1081
+ }
1082
+ return p;
1083
+ };
1084
+
1085
+ // src/secrets/crypto.ts
1086
+ var KEY_ID = "vault-key-v1";
1087
+ var webcrypto = () => {
1088
+ const c = globalThis.crypto;
1089
+ if (!c?.subtle || !c.getRandomValues) {
1090
+ throw new Error("WebCrypto (crypto.subtle) is unavailable in this environment");
1091
+ }
1092
+ return c;
1093
+ };
1094
+ var getOrCreateVaultKey = async (dbName) => {
1095
+ const db = await openAgentWebDB({ dbName });
1096
+ const existing = await db.get(KEYS_STORE, KEY_ID);
1097
+ if (existing) return existing;
1098
+ const key = await webcrypto().subtle.generateKey(
1099
+ { name: "AES-GCM", length: 256 },
1100
+ /* extractable */
1101
+ false,
1102
+ ["encrypt", "decrypt"]
1103
+ );
1104
+ await db.put(KEYS_STORE, key, KEY_ID);
1105
+ return key;
1106
+ };
1107
+ var textEncoder = new TextEncoder();
1108
+ var textDecoder = new TextDecoder();
1109
+ var encryptJSON = async (key, value) => {
1110
+ const c = webcrypto();
1111
+ const iv = c.getRandomValues(new Uint8Array(12));
1112
+ const data = textEncoder.encode(JSON.stringify(value));
1113
+ const ciphertext = await c.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
1114
+ return { iv, ciphertext };
1115
+ };
1116
+ var decryptJSON = async (key, blob) => {
1117
+ const iv = new Uint8Array(blob.iv);
1118
+ const plain = await webcrypto().subtle.decrypt({ name: "AES-GCM", iv }, key, blob.ciphertext);
1119
+ return JSON.parse(textDecoder.decode(plain));
1120
+ };
1121
+
1122
+ // src/secrets/vault.ts
1123
+ var IndexedDBVault = class {
1124
+ dbName;
1125
+ constructor(opts = {}) {
1126
+ this.dbName = opts.dbName;
1127
+ }
1128
+ key() {
1129
+ return getOrCreateVaultKey(this.dbName);
1130
+ }
1131
+ /** Encrypt and store a secret string under `id`. */
1132
+ async setSecret(id, value) {
1133
+ const blob = await encryptJSON(await this.key(), value);
1134
+ const db = await openAgentWebDB({ dbName: this.dbName });
1135
+ await db.put(SECRETS_STORE, blob, id);
1136
+ }
1137
+ /** Fetch and decrypt a secret, or undefined if absent. */
1138
+ async getSecret(id) {
1139
+ const db = await openAgentWebDB({ dbName: this.dbName });
1140
+ const blob = await db.get(SECRETS_STORE, id);
1141
+ if (!blob) return void 0;
1142
+ return decryptJSON(await this.key(), blob);
1143
+ }
1144
+ async deleteSecret(id) {
1145
+ const db = await openAgentWebDB({ dbName: this.dbName });
1146
+ await db.delete(SECRETS_STORE, id);
1147
+ }
1148
+ async listSecretIds() {
1149
+ const db = await openAgentWebDB({ dbName: this.dbName });
1150
+ return (await db.getAllKeys(SECRETS_STORE)).map(String);
1151
+ }
1152
+ };
1153
+
1154
+ // src/secrets/store.ts
1155
+ var VaultCredentialStore = class {
1156
+ vault;
1157
+ constructor(opts = {}) {
1158
+ this.vault = new IndexedDBVault(opts);
1159
+ }
1160
+ getApiKey(ref) {
1161
+ return this.vault.getSecret(ref);
1162
+ }
1163
+ setApiKey(ref, key) {
1164
+ return this.vault.setSecret(ref, key);
1165
+ }
1166
+ deleteApiKey(ref) {
1167
+ return this.vault.deleteSecret(ref);
1168
+ }
1169
+ };
1170
+ var MemoryCredentialStore = class {
1171
+ map = /* @__PURE__ */ new Map();
1172
+ constructor(initial) {
1173
+ if (initial) for (const [k, v] of Object.entries(initial)) this.map.set(k, v);
1174
+ }
1175
+ async getApiKey(ref) {
1176
+ return this.map.get(ref);
1177
+ }
1178
+ async setApiKey(ref, key) {
1179
+ this.map.set(ref, key);
1180
+ }
1181
+ async deleteApiKey(ref) {
1182
+ this.map.delete(ref);
1183
+ }
1184
+ };
1185
+
1186
+ // src/tools/define.ts
1187
+ import { tool } from "ai";
1188
+ var defineTool = (config) => {
1189
+ const { promptHint, ...rest } = config;
1190
+ const t = tool(rest);
1191
+ if (promptHint) t.promptHint = promptHint;
1192
+ return t;
1193
+ };
1194
+
1195
+ // src/memory/store.ts
1196
+ var MemoryStore = class {
1197
+ data = /* @__PURE__ */ new Map();
1198
+ async load(sessionId) {
1199
+ return (this.data.get(sessionId) ?? []).slice();
1200
+ }
1201
+ async append(sessionId, message) {
1202
+ const arr = this.data.get(sessionId) ?? [];
1203
+ arr.push(message);
1204
+ this.data.set(sessionId, arr);
1205
+ }
1206
+ async replace(sessionId, messages) {
1207
+ this.data.set(sessionId, messages.slice());
1208
+ }
1209
+ async clear(sessionId) {
1210
+ this.data.delete(sessionId);
1211
+ }
1212
+ };
1213
+
1214
+ // src/memory/sessions.ts
1215
+ var IndexedDBStore = class {
1216
+ dbName;
1217
+ constructor(opts = {}) {
1218
+ this.dbName = opts.dbName;
1219
+ }
1220
+ async load(sessionId) {
1221
+ const db = await openAgentWebDB({ dbName: this.dbName });
1222
+ const value = await db.get(SESSIONS_STORE, sessionId);
1223
+ return Array.isArray(value) ? value : [];
1224
+ }
1225
+ async replace(sessionId, messages) {
1226
+ const db = await openAgentWebDB({ dbName: this.dbName });
1227
+ await db.put(SESSIONS_STORE, messages, sessionId);
1228
+ }
1229
+ async append(sessionId, message) {
1230
+ const current = await this.load(sessionId);
1231
+ current.push(message);
1232
+ await this.replace(sessionId, current);
1233
+ }
1234
+ async clear(sessionId) {
1235
+ const db = await openAgentWebDB({ dbName: this.dbName });
1236
+ await db.delete(SESSIONS_STORE, sessionId);
1237
+ }
1238
+ /** List all stored session ids. */
1239
+ async listSessions() {
1240
+ const db = await openAgentWebDB({ dbName: this.dbName });
1241
+ return (await db.getAllKeys(SESSIONS_STORE)).map(String);
1242
+ }
1243
+ };
1244
+ export {
1245
+ IndexedDBStore,
1246
+ IndexedDBVault,
1247
+ KEYS_STORE,
1248
+ MemoryCredentialStore,
1249
+ MemoryStore,
1250
+ PlanSchema,
1251
+ PlanStepSchema,
1252
+ ReplanSchema,
1253
+ SECRETS_STORE,
1254
+ SESSIONS_STORE,
1255
+ VaultCredentialStore,
1256
+ buildModelFromStage,
1257
+ compressHistory,
1258
+ createAgent,
1259
+ createPlan,
1260
+ createWebLLMModel,
1261
+ decideReplan,
1262
+ decryptJSON,
1263
+ defaultPrompts,
1264
+ defineTool,
1265
+ directBrowserOk,
1266
+ dispatch,
1267
+ encryptJSON,
1268
+ executeStep,
1269
+ generate,
1270
+ generateStructured,
1271
+ getOrCreateVaultKey,
1272
+ isDirectModel,
1273
+ isProviderSpec,
1274
+ isWebGPUAvailable,
1275
+ looksLikeJson,
1276
+ normalizeSteps,
1277
+ normalizeUsage,
1278
+ openAgentWebDB,
1279
+ parseExecutorResponse,
1280
+ parsePlainText,
1281
+ parsePlannerResponse,
1282
+ parseReplannerResponse,
1283
+ promptHintOf,
1284
+ renderCatalog,
1285
+ resolveConfig,
1286
+ resolveModel,
1287
+ resolveStage,
1288
+ runToolLoop,
1289
+ selectToolMode,
1290
+ shouldReplan,
1291
+ splitBlocker,
1292
+ stream,
1293
+ supportsNativeTools,
1294
+ supportsStructuredOutput,
1295
+ synthesizeAnswer,
1296
+ withSystem
1297
+ };
1298
+ //# sourceMappingURL=index.js.map