@dudko.dev/agent-web 0.0.3 → 0.0.5

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.
@@ -0,0 +1,38 @@
1
+ // src/providers/webllm.ts
2
+ import { generateText } from "ai";
3
+ var isWebGPUAvailable = () => typeof navigator !== "undefined" && "gpu" in navigator;
4
+ var preloadWebLLMModel = async (model) => {
5
+ await generateText({ model, prompt: "ok", maxOutputTokens: 1 });
6
+ };
7
+ var unloadWebLLMModel = async (model) => {
8
+ const engine = model.engine;
9
+ try {
10
+ await engine?.unload?.();
11
+ } catch {
12
+ }
13
+ };
14
+ var createWebLLMModel = async (model, options) => {
15
+ let mod;
16
+ try {
17
+ mod = await import("@browser-ai/web-llm");
18
+ } catch {
19
+ throw new Error(
20
+ 'Local models require "@browser-ai/web-llm" and its peer "@mlc-ai/web-llm". Install: npm install @browser-ai/web-llm @mlc-ai/web-llm'
21
+ );
22
+ }
23
+ const { preload = true, ...settings } = options ?? {};
24
+ const languageModel = mod.webLLM(
25
+ model,
26
+ settings
27
+ );
28
+ if (preload) await preloadWebLLMModel(languageModel);
29
+ return languageModel;
30
+ };
31
+
32
+ export {
33
+ isWebGPUAvailable,
34
+ preloadWebLLMModel,
35
+ unloadWebLLMModel,
36
+ createWebLLMModel
37
+ };
38
+ //# sourceMappingURL=chunk-IT65EW77.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/webllm.ts"],"sourcesContent":["import { generateText, type LanguageModel } from 'ai'\n\n/** WebGPU feature-detect — WebLLM requires it. Call before offering local models. */\nexport const isWebGPUAvailable = (): boolean =>\n typeof navigator !== 'undefined' && 'gpu' in navigator\n\nexport interface WebLLMModelOptions {\n /**\n * Progress callback for weight download / engine init. `report.progress` is\n * 0..1. Forwarded to WebLLM's `initProgressCallback`.\n */\n initProgressCallback?: (report: { progress: number; text: string }) => void\n /**\n * Download the weights and initialize the engine NOW (default true).\n *\n * The underlying provider initializes lazily on the first generation — with\n * a multi-GB model that means the download would silently happen on the\n * user's first message, with `initProgressCallback` never firing at a moment\n * the host can show it. Preloading runs a 1-token warm-up so the returned\n * model is truly ready and progress is reported during creation. Re-creating\n * an already-downloaded model is fast (weights live in the browser cache).\n *\n * Set to `false` to keep the lazy behavior.\n */\n preload?: boolean\n /** Any other @browser-ai/web-llm setting (temperature, worker handler, …). */\n [k: string]: unknown\n}\n\n/**\n * Force a model to download its weights and initialize its engine by running\n * a 1-token generation. Exposed for hosts that create models with\n * `preload: false` and want to warm them up later (e.g. behind their own UI).\n */\nexport const preloadWebLLMModel = async (model: LanguageModel): Promise<void> => {\n await generateText({ model, prompt: 'ok', maxOutputTokens: 1 })\n}\n\n/**\n * Best-effort release of a model's GPU memory (call when switching models —\n * an abandoned engine otherwise holds its buffers until the page reloads).\n *\n * `@browser-ai/web-llm` keeps its MLCEngine private and exposes no public\n * `unload()`, so this reaches into the instance defensively; it is a no-op on\n * an uninitialized model or if the provider's internals change shape.\n */\nexport const unloadWebLLMModel = async (model: LanguageModel): Promise<void> => {\n const engine = (model as { engine?: { unload?: () => Promise<void> } }).engine\n try {\n await engine?.unload?.()\n } catch {\n /* best-effort */\n }\n}\n\n/**\n * Build an AI SDK `LanguageModel` backed by WebLLM (WebGPU) via the community\n * provider `@browser-ai/web-llm` (an optional peer, dynamically imported so it\n * is code-split and never loads until a local model is actually used). Accepts\n * any WebLLM prebuilt model id, e.g. \"Llama-3.2-3B-Instruct-q4f16_1-MLC\".\n *\n * By default the weights are downloaded eagerly with progress (see\n * `WebLLMModelOptions.preload`).\n */\nexport const createWebLLMModel = async (\n model: string,\n options?: WebLLMModelOptions,\n): Promise<LanguageModel> => {\n let mod: typeof import('@browser-ai/web-llm')\n try {\n mod = await import('@browser-ai/web-llm')\n } catch {\n throw new Error(\n 'Local models require \"@browser-ai/web-llm\" and its peer \"@mlc-ai/web-llm\". Install: npm install @browser-ai/web-llm @mlc-ai/web-llm',\n )\n }\n const { preload = true, ...settings } = options ?? {}\n // webLLM(modelId, settings) returns a LanguageModel. The model id is a strict\n // union in the provider's types; we accept any string and cast at the seam.\n const languageModel = mod.webLLM(\n model as Parameters<typeof mod.webLLM>[0],\n settings as Parameters<typeof mod.webLLM>[1],\n )\n if (preload) await preloadWebLLMModel(languageModel)\n return languageModel\n}\n"],"mappings":";AAAA,SAAS,oBAAwC;AAG1C,IAAM,oBAAoB,MAC/B,OAAO,cAAc,eAAe,SAAS;AA8BxC,IAAM,qBAAqB,OAAO,UAAwC;AAC/E,QAAM,aAAa,EAAE,OAAO,QAAQ,MAAM,iBAAiB,EAAE,CAAC;AAChE;AAUO,IAAM,oBAAoB,OAAO,UAAwC;AAC9E,QAAM,SAAU,MAAwD;AACxE,MAAI;AACF,UAAM,QAAQ,SAAS;AAAA,EACzB,QAAQ;AAAA,EAER;AACF;AAWO,IAAM,oBAAoB,OAC/B,OACA,YAC2B;AAC3B,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,OAAO,qBAAqB;AAAA,EAC1C,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,EAAE,UAAU,MAAM,GAAG,SAAS,IAAI,WAAW,CAAC;AAGpD,QAAM,gBAAgB,IAAI;AAAA,IACxB;AAAA,IACA;AAAA,EACF;AACA,MAAI,QAAS,OAAM,mBAAmB,aAAa;AACnD,SAAO;AACT;","names":[]}
package/dist/index.cjs CHANGED
@@ -34,13 +34,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
34
34
  var webllm_exports = {};
35
35
  __export(webllm_exports, {
36
36
  createWebLLMModel: () => createWebLLMModel,
37
- isWebGPUAvailable: () => isWebGPUAvailable
37
+ isWebGPUAvailable: () => isWebGPUAvailable,
38
+ preloadWebLLMModel: () => preloadWebLLMModel,
39
+ unloadWebLLMModel: () => unloadWebLLMModel
38
40
  });
39
- var isWebGPUAvailable, createWebLLMModel;
41
+ var import_ai2, isWebGPUAvailable, preloadWebLLMModel, unloadWebLLMModel, createWebLLMModel;
40
42
  var init_webllm = __esm({
41
43
  "src/providers/webllm.ts"() {
42
44
  "use strict";
45
+ import_ai2 = require("ai");
43
46
  isWebGPUAvailable = () => typeof navigator !== "undefined" && "gpu" in navigator;
47
+ preloadWebLLMModel = async (model) => {
48
+ await (0, import_ai2.generateText)({ model, prompt: "ok", maxOutputTokens: 1 });
49
+ };
50
+ unloadWebLLMModel = async (model) => {
51
+ const engine = model.engine;
52
+ try {
53
+ await engine?.unload?.();
54
+ } catch {
55
+ }
56
+ };
44
57
  createWebLLMModel = async (model, options) => {
45
58
  let mod;
46
59
  try {
@@ -50,10 +63,13 @@ var init_webllm = __esm({
50
63
  'Local models require "@browser-ai/web-llm" and its peer "@mlc-ai/web-llm". Install: npm install @browser-ai/web-llm @mlc-ai/web-llm'
51
64
  );
52
65
  }
53
- return mod.webLLM(
66
+ const { preload = true, ...settings } = options ?? {};
67
+ const languageModel = mod.webLLM(
54
68
  model,
55
- options
69
+ settings
56
70
  );
71
+ if (preload) await preloadWebLLMModel(languageModel);
72
+ return languageModel;
57
73
  };
58
74
  }
59
75
  });
@@ -75,6 +91,7 @@ __export(src_exports, {
75
91
  buildModelFromStage: () => buildModelFromStage,
76
92
  compressHistory: () => compressHistory,
77
93
  createAgent: () => createAgent,
94
+ createLogger: () => createLogger,
78
95
  createPlan: () => createPlan,
79
96
  createWebLLMModel: () => createWebLLMModel,
80
97
  decideReplan: () => decideReplan,
@@ -99,6 +116,7 @@ __export(src_exports, {
99
116
  parsePlainText: () => parsePlainText,
100
117
  parsePlannerResponse: () => parsePlannerResponse,
101
118
  parseReplannerResponse: () => parseReplannerResponse,
119
+ preloadWebLLMModel: () => preloadWebLLMModel,
102
120
  promptHintOf: () => promptHintOf,
103
121
  renderCatalog: () => renderCatalog,
104
122
  resolveConfig: () => resolveConfig,
@@ -112,6 +130,7 @@ __export(src_exports, {
112
130
  supportsNativeTools: () => supportsNativeTools,
113
131
  supportsStructuredOutput: () => supportsStructuredOutput,
114
132
  synthesizeAnswer: () => synthesizeAnswer,
133
+ unloadWebLLMModel: () => unloadWebLLMModel,
115
134
  withSystem: () => withSystem
116
135
  });
117
136
  module.exports = __toCommonJS(src_exports);
@@ -135,7 +154,8 @@ var resolveConfig = (c) => ({
135
154
  temperature: c.temperature,
136
155
  replan: c.replan ?? true,
137
156
  synthesize: c.synthesize ?? true,
138
- compressAfterChars: c.compressAfterChars ?? 12e3
157
+ compressAfterChars: c.compressAfterChars ?? 12e3,
158
+ logLevel: c.logLevel ?? "warn"
139
159
  });
140
160
 
141
161
  // src/llm/generate.ts
@@ -239,6 +259,7 @@ var PROVIDER_PACKAGE = {
239
259
  var loadProvider = async (pkg) => {
240
260
  try {
241
261
  return await import(
262
+ /* webpackIgnore: true */
242
263
  /* @vite-ignore */
243
264
  pkg
244
265
  );
@@ -575,7 +596,7 @@ var dispatch = async (action, tools, ctx = {}) => {
575
596
  };
576
597
 
577
598
  // src/llm/tool-loop.ts
578
- var import_ai2 = require("ai");
599
+ var import_ai3 = require("ai");
579
600
 
580
601
  // src/parse.ts
581
602
  var stripFences = (text) => (text || "").replace(/```(?:json)?/gi, "").trim();
@@ -724,11 +745,11 @@ var runToolLoop = (model, opts) => opts.mode === "prompted" ? runPrompted(model,
724
745
  var runNative = async (model, opts) => {
725
746
  const toolCalls = [];
726
747
  const pending = /* @__PURE__ */ new Map();
727
- const result = (0, import_ai2.streamText)({
748
+ const result = (0, import_ai3.streamText)({
728
749
  model,
729
750
  tools: opts.tools,
730
751
  ...opts.activeTools ? { activeTools: opts.activeTools } : {},
731
- stopWhen: (0, import_ai2.stepCountIs)(opts.maxSteps ?? 4),
752
+ stopWhen: (0, import_ai3.stepCountIs)(opts.maxSteps ?? 4),
732
753
  system: opts.system,
733
754
  ...promptOf(opts),
734
755
  maxOutputTokens: opts.maxOutputTokens,
@@ -776,7 +797,7 @@ var runNative = async (model, opts) => {
776
797
  return { text: text.trim(), toolCalls, usage: normalizeUsage(usage) };
777
798
  };
778
799
  var runPrompted = async (model, opts) => {
779
- const result = await (0, import_ai2.generateText)({
800
+ const result = await (0, import_ai3.generateText)({
780
801
  model,
781
802
  system: opts.system,
782
803
  ...promptOf(opts),
@@ -845,10 +866,17 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
845
866
  timeoutMs: ctx.config.chatTimeoutMs,
846
867
  callbacks: {
847
868
  onTextDelta: (delta) => ctx.emit({ type: "step.text-delta", step, delta }),
848
- onToolCall: (name, input) => ctx.emit({ type: "step.tool-call", step, name, input }),
849
- onToolResult: (name, output, ok) => ctx.emit({ type: "step.tool-result", step, name, output, ok })
869
+ onToolCall: (name, input) => {
870
+ ctx.log.info(`tool call: ${name}`, input);
871
+ ctx.emit({ type: "step.tool-call", step, name, input });
872
+ },
873
+ onToolResult: (name, output, ok) => {
874
+ ctx.log.debug(`tool result: ${name} ${ok ? "ok" : "FAILED"}`, output);
875
+ ctx.emit({ type: "step.tool-result", step, name, output, ok });
876
+ }
850
877
  }
851
878
  });
879
+ ctx.log.debug("executor text:", loop.text);
852
880
  const { summary, blocked } = splitBlocker(loop.text);
853
881
  return {
854
882
  result: {
@@ -896,11 +924,14 @@ var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).
896
924
  expectedOutcome: s.expectedOutcome,
897
925
  suggestedTools: s.suggestedTools
898
926
  }));
899
- var createPlan = async (ctx, goal) => {
927
+ var createPlan = async (ctx, goal, nudge) => {
900
928
  const state = await ctx.state();
929
+ const effectiveGoal = nudge ? `${goal}
930
+
931
+ ${nudge}` : goal;
901
932
  const commonFor = (mode) => {
902
933
  const parts = ctx.prompts.planner({
903
- goal,
934
+ goal: effectiveGoal,
904
935
  state,
905
936
  toolCatalog: ctx.toolCatalog,
906
937
  mode,
@@ -918,16 +949,20 @@ var createPlan = async (ctx, goal) => {
918
949
  if (ctx.plannerMode === "native") {
919
950
  try {
920
951
  const result2 = await generateStructured(ctx.plannerModel, PlanSchema, commonFor("native"));
952
+ ctx.log.debug("planner (native):", result2.object);
921
953
  return {
922
954
  plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
923
955
  usage: normalizeUsage(result2.usage)
924
956
  };
925
957
  } catch (err) {
958
+ ctx.log.warn("planner: native structured output failed, salvaging:", asMessage(err));
926
959
  ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
927
960
  }
928
961
  }
929
962
  const result = await generate(ctx.plannerModel, commonFor("prompted"));
930
963
  const parsed = parsePlannerResponse(result.text);
964
+ ctx.log.debug("planner (prompted) raw:", result.text);
965
+ ctx.log.debug("planner (prompted) parsed:", parsed);
931
966
  return {
932
967
  plan: {
933
968
  thought: parsed.reply,
@@ -1010,6 +1045,27 @@ var synthesizeAnswer = async (ctx, goal, done) => {
1010
1045
  };
1011
1046
  };
1012
1047
 
1048
+ // src/logger.ts
1049
+ var WEIGHT = {
1050
+ silent: 0,
1051
+ error: 1,
1052
+ warn: 2,
1053
+ info: 3,
1054
+ debug: 4
1055
+ };
1056
+ var PREFIX = "[agent-web]";
1057
+ var noop = () => void 0;
1058
+ var createLogger = (level = "warn", sink = console) => {
1059
+ const at = (min, fn) => WEIGHT[level] >= WEIGHT[min] ? (...args) => fn.call(sink, PREFIX, ...args) : noop;
1060
+ return {
1061
+ level,
1062
+ error: at("error", sink.error),
1063
+ warn: at("warn", sink.warn),
1064
+ info: at("info", sink.info),
1065
+ debug: at("debug", sink.debug)
1066
+ };
1067
+ };
1068
+
1013
1069
  // src/agent/runner.ts
1014
1070
  var errMessage = (err) => err instanceof Error ? err.message : String(err);
1015
1071
  var filterTools = (all, available, excluded) => {
@@ -1040,6 +1096,10 @@ var createAgent = async (config) => {
1040
1096
  );
1041
1097
  const toolCatalog = renderCatalog(tools);
1042
1098
  const prompts = { ...defaultPrompts, ...config.prompts };
1099
+ const log = createLogger(cfg.logLevel, config.logger);
1100
+ log.info(
1101
+ `agent ready \u2014 tools: [${Object.keys(tools).join(", ") || "none"}], planner mode: ${plannerMode}, executor mode: ${executorMode}`
1102
+ );
1043
1103
  const run = async (goal, opts = {}) => {
1044
1104
  const emit = (event) => {
1045
1105
  try {
@@ -1095,14 +1155,34 @@ var createAgent = async (config) => {
1095
1155
  toolCatalog,
1096
1156
  prompts,
1097
1157
  emit,
1158
+ log,
1098
1159
  signal: opts.signal,
1099
1160
  state,
1100
1161
  history
1101
1162
  };
1102
1163
  await remember({ role: "user", content: text });
1164
+ log.info("run:", text);
1103
1165
  try {
1104
- const planned = await createPlan(ctx, text);
1166
+ let planned = await createPlan(ctx, text);
1105
1167
  bumpUsage(planned.usage, "plan");
1168
+ if (isAborted()) {
1169
+ emit({ type: "stopped" });
1170
+ result.stopped = true;
1171
+ return result;
1172
+ }
1173
+ if (planned.plan.steps.length === 0 && text.split(/\s+/).length > 2) {
1174
+ log.warn(
1175
+ "planner returned no steps for a multi-word goal \u2014 retrying with a nudge. thought:",
1176
+ planned.plan.thought
1177
+ );
1178
+ const retried = await createPlan(
1179
+ ctx,
1180
+ text,
1181
+ "NOTE: If the message above asks to build, add, change, clear or delete ANYTHING, you MUST output 1-6 concrete steps. Output an empty plan ONLY for a pure greeting or question."
1182
+ );
1183
+ bumpUsage(retried.usage, "plan");
1184
+ if (retried.plan.steps.length > 0) planned = retried;
1185
+ }
1106
1186
  result.plan = planned.plan;
1107
1187
  if (isAborted()) {
1108
1188
  emit({ type: "stopped" });
@@ -1110,12 +1190,17 @@ var createAgent = async (config) => {
1110
1190
  return result;
1111
1191
  }
1112
1192
  if (planned.plan.steps.length === 0) {
1193
+ log.info("no plan \u2014 answering directly (no tools will run)");
1113
1194
  const answer = planned.plan.thought.trim() || "Tell me what you'd like to do.";
1114
1195
  emit({ type: "final", text: answer });
1115
1196
  await remember({ role: "assistant", content: answer });
1116
1197
  result.final = answer;
1117
1198
  return result;
1118
1199
  }
1200
+ log.info(
1201
+ `plan (${planned.plan.steps.length} steps):`,
1202
+ planned.plan.steps.map((s) => s.description)
1203
+ );
1119
1204
  emit({ type: "plan.created", plan: planned.plan });
1120
1205
  planned.plan.steps.forEach((step, index) => emit({ type: "plan.step-added", step, index }));
1121
1206
  const done = [];
@@ -1152,6 +1237,14 @@ var createAgent = async (config) => {
1152
1237
  const failed = stepResult.toolCalls.length - applied;
1153
1238
  result.applied += applied;
1154
1239
  result.steps = stepNo;
1240
+ log.info(
1241
+ `step ${stepNo}/${total} "${step.description}" \u2014 ${stepResult.toolCalls.length} tool call(s), ${applied} ok${failed ? `, ${failed} FAILED` : ""}${stepResult.blocked ? ", BLOCKED" : ""}`
1242
+ );
1243
+ if (stepResult.toolCalls.length === 0) {
1244
+ log.warn(`step ${stepNo} made no tool calls \u2014 nothing changed in this step`);
1245
+ }
1246
+ stepResult.toolCalls.filter((c) => !c.ok).forEach((c) => log.warn(`tool ${c.name} failed:`, c.output));
1247
+ log.debug(`step ${stepNo} detail:`, stepResult);
1155
1248
  emit({ type: "step.complete", step, result: stepResult });
1156
1249
  done.push(
1157
1250
  `${step.description} \u2014 ${applied} applied${failed ? `, ${failed} failed` : ""}${stepResult.blocked ? ", blocked" : ""}`
@@ -1191,6 +1284,7 @@ var createAgent = async (config) => {
1191
1284
  } catch {
1192
1285
  }
1193
1286
  }
1287
+ log.info(`done \u2014 ${result.applied} tool call(s) applied over ${result.steps} step(s)`);
1194
1288
  emit({ type: "final", text: summary });
1195
1289
  await remember({ role: "assistant", content: summary });
1196
1290
  result.final = summary;
@@ -1383,10 +1477,10 @@ var MemoryCredentialStore = class {
1383
1477
  };
1384
1478
 
1385
1479
  // src/tools/define.ts
1386
- var import_ai3 = require("ai");
1480
+ var import_ai4 = require("ai");
1387
1481
  var defineTool = (config) => {
1388
1482
  const { promptHint, ...rest } = config;
1389
- const t = (0, import_ai3.tool)(rest);
1483
+ const t = (0, import_ai4.tool)(rest);
1390
1484
  if (promptHint) t.promptHint = promptHint;
1391
1485
  return t;
1392
1486
  };
@@ -1456,6 +1550,7 @@ var IndexedDBStore = class {
1456
1550
  buildModelFromStage,
1457
1551
  compressHistory,
1458
1552
  createAgent,
1553
+ createLogger,
1459
1554
  createPlan,
1460
1555
  createWebLLMModel,
1461
1556
  decideReplan,
@@ -1480,6 +1575,7 @@ var IndexedDBStore = class {
1480
1575
  parsePlainText,
1481
1576
  parsePlannerResponse,
1482
1577
  parseReplannerResponse,
1578
+ preloadWebLLMModel,
1483
1579
  promptHintOf,
1484
1580
  renderCatalog,
1485
1581
  resolveConfig,
@@ -1493,6 +1589,7 @@ var IndexedDBStore = class {
1493
1589
  supportsNativeTools,
1494
1590
  supportsStructuredOutput,
1495
1591
  synthesizeAnswer,
1592
+ unloadWebLLMModel,
1496
1593
  withSystem
1497
1594
  });
1498
1595
  //# sourceMappingURL=index.cjs.map