@dudko.dev/agent-web 0.0.4 → 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
@@ -576,7 +596,7 @@ var dispatch = async (action, tools, ctx = {}) => {
576
596
  };
577
597
 
578
598
  // src/llm/tool-loop.ts
579
- var import_ai2 = require("ai");
599
+ var import_ai3 = require("ai");
580
600
 
581
601
  // src/parse.ts
582
602
  var stripFences = (text) => (text || "").replace(/```(?:json)?/gi, "").trim();
@@ -725,11 +745,11 @@ var runToolLoop = (model, opts) => opts.mode === "prompted" ? runPrompted(model,
725
745
  var runNative = async (model, opts) => {
726
746
  const toolCalls = [];
727
747
  const pending = /* @__PURE__ */ new Map();
728
- const result = (0, import_ai2.streamText)({
748
+ const result = (0, import_ai3.streamText)({
729
749
  model,
730
750
  tools: opts.tools,
731
751
  ...opts.activeTools ? { activeTools: opts.activeTools } : {},
732
- stopWhen: (0, import_ai2.stepCountIs)(opts.maxSteps ?? 4),
752
+ stopWhen: (0, import_ai3.stepCountIs)(opts.maxSteps ?? 4),
733
753
  system: opts.system,
734
754
  ...promptOf(opts),
735
755
  maxOutputTokens: opts.maxOutputTokens,
@@ -777,7 +797,7 @@ var runNative = async (model, opts) => {
777
797
  return { text: text.trim(), toolCalls, usage: normalizeUsage(usage) };
778
798
  };
779
799
  var runPrompted = async (model, opts) => {
780
- const result = await (0, import_ai2.generateText)({
800
+ const result = await (0, import_ai3.generateText)({
781
801
  model,
782
802
  system: opts.system,
783
803
  ...promptOf(opts),
@@ -846,10 +866,17 @@ var executeStep = async (ctx, goal, step, index, total, done) => {
846
866
  timeoutMs: ctx.config.chatTimeoutMs,
847
867
  callbacks: {
848
868
  onTextDelta: (delta) => ctx.emit({ type: "step.text-delta", step, delta }),
849
- onToolCall: (name, input) => ctx.emit({ type: "step.tool-call", step, name, input }),
850
- 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
+ }
851
877
  }
852
878
  });
879
+ ctx.log.debug("executor text:", loop.text);
853
880
  const { summary, blocked } = splitBlocker(loop.text);
854
881
  return {
855
882
  result: {
@@ -897,11 +924,14 @@ var toSteps = (raw) => raw.filter((s) => s.description && s.description.trim()).
897
924
  expectedOutcome: s.expectedOutcome,
898
925
  suggestedTools: s.suggestedTools
899
926
  }));
900
- var createPlan = async (ctx, goal) => {
927
+ var createPlan = async (ctx, goal, nudge) => {
901
928
  const state = await ctx.state();
929
+ const effectiveGoal = nudge ? `${goal}
930
+
931
+ ${nudge}` : goal;
902
932
  const commonFor = (mode) => {
903
933
  const parts = ctx.prompts.planner({
904
- goal,
934
+ goal: effectiveGoal,
905
935
  state,
906
936
  toolCatalog: ctx.toolCatalog,
907
937
  mode,
@@ -919,16 +949,20 @@ var createPlan = async (ctx, goal) => {
919
949
  if (ctx.plannerMode === "native") {
920
950
  try {
921
951
  const result2 = await generateStructured(ctx.plannerModel, PlanSchema, commonFor("native"));
952
+ ctx.log.debug("planner (native):", result2.object);
922
953
  return {
923
954
  plan: { thought: result2.object.thought, steps: toSteps(result2.object.steps) },
924
955
  usage: normalizeUsage(result2.usage)
925
956
  };
926
957
  } catch (err) {
958
+ ctx.log.warn("planner: native structured output failed, salvaging:", asMessage(err));
927
959
  ctx.emit({ type: "retry", phase: "plan", attempt: 1, error: asMessage(err) });
928
960
  }
929
961
  }
930
962
  const result = await generate(ctx.plannerModel, commonFor("prompted"));
931
963
  const parsed = parsePlannerResponse(result.text);
964
+ ctx.log.debug("planner (prompted) raw:", result.text);
965
+ ctx.log.debug("planner (prompted) parsed:", parsed);
932
966
  return {
933
967
  plan: {
934
968
  thought: parsed.reply,
@@ -1011,6 +1045,27 @@ var synthesizeAnswer = async (ctx, goal, done) => {
1011
1045
  };
1012
1046
  };
1013
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
+
1014
1069
  // src/agent/runner.ts
1015
1070
  var errMessage = (err) => err instanceof Error ? err.message : String(err);
1016
1071
  var filterTools = (all, available, excluded) => {
@@ -1041,6 +1096,10 @@ var createAgent = async (config) => {
1041
1096
  );
1042
1097
  const toolCatalog = renderCatalog(tools);
1043
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
+ );
1044
1103
  const run = async (goal, opts = {}) => {
1045
1104
  const emit = (event) => {
1046
1105
  try {
@@ -1096,14 +1155,34 @@ var createAgent = async (config) => {
1096
1155
  toolCatalog,
1097
1156
  prompts,
1098
1157
  emit,
1158
+ log,
1099
1159
  signal: opts.signal,
1100
1160
  state,
1101
1161
  history
1102
1162
  };
1103
1163
  await remember({ role: "user", content: text });
1164
+ log.info("run:", text);
1104
1165
  try {
1105
- const planned = await createPlan(ctx, text);
1166
+ let planned = await createPlan(ctx, text);
1106
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
+ }
1107
1186
  result.plan = planned.plan;
1108
1187
  if (isAborted()) {
1109
1188
  emit({ type: "stopped" });
@@ -1111,12 +1190,17 @@ var createAgent = async (config) => {
1111
1190
  return result;
1112
1191
  }
1113
1192
  if (planned.plan.steps.length === 0) {
1193
+ log.info("no plan \u2014 answering directly (no tools will run)");
1114
1194
  const answer = planned.plan.thought.trim() || "Tell me what you'd like to do.";
1115
1195
  emit({ type: "final", text: answer });
1116
1196
  await remember({ role: "assistant", content: answer });
1117
1197
  result.final = answer;
1118
1198
  return result;
1119
1199
  }
1200
+ log.info(
1201
+ `plan (${planned.plan.steps.length} steps):`,
1202
+ planned.plan.steps.map((s) => s.description)
1203
+ );
1120
1204
  emit({ type: "plan.created", plan: planned.plan });
1121
1205
  planned.plan.steps.forEach((step, index) => emit({ type: "plan.step-added", step, index }));
1122
1206
  const done = [];
@@ -1153,6 +1237,14 @@ var createAgent = async (config) => {
1153
1237
  const failed = stepResult.toolCalls.length - applied;
1154
1238
  result.applied += applied;
1155
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);
1156
1248
  emit({ type: "step.complete", step, result: stepResult });
1157
1249
  done.push(
1158
1250
  `${step.description} \u2014 ${applied} applied${failed ? `, ${failed} failed` : ""}${stepResult.blocked ? ", blocked" : ""}`
@@ -1192,6 +1284,7 @@ var createAgent = async (config) => {
1192
1284
  } catch {
1193
1285
  }
1194
1286
  }
1287
+ log.info(`done \u2014 ${result.applied} tool call(s) applied over ${result.steps} step(s)`);
1195
1288
  emit({ type: "final", text: summary });
1196
1289
  await remember({ role: "assistant", content: summary });
1197
1290
  result.final = summary;
@@ -1384,10 +1477,10 @@ var MemoryCredentialStore = class {
1384
1477
  };
1385
1478
 
1386
1479
  // src/tools/define.ts
1387
- var import_ai3 = require("ai");
1480
+ var import_ai4 = require("ai");
1388
1481
  var defineTool = (config) => {
1389
1482
  const { promptHint, ...rest } = config;
1390
- const t = (0, import_ai3.tool)(rest);
1483
+ const t = (0, import_ai4.tool)(rest);
1391
1484
  if (promptHint) t.promptHint = promptHint;
1392
1485
  return t;
1393
1486
  };
@@ -1457,6 +1550,7 @@ var IndexedDBStore = class {
1457
1550
  buildModelFromStage,
1458
1551
  compressHistory,
1459
1552
  createAgent,
1553
+ createLogger,
1460
1554
  createPlan,
1461
1555
  createWebLLMModel,
1462
1556
  decideReplan,
@@ -1481,6 +1575,7 @@ var IndexedDBStore = class {
1481
1575
  parsePlainText,
1482
1576
  parsePlannerResponse,
1483
1577
  parseReplannerResponse,
1578
+ preloadWebLLMModel,
1484
1579
  promptHintOf,
1485
1580
  renderCatalog,
1486
1581
  resolveConfig,
@@ -1494,6 +1589,7 @@ var IndexedDBStore = class {
1494
1589
  supportsNativeTools,
1495
1590
  supportsStructuredOutput,
1496
1591
  synthesizeAnswer,
1592
+ unloadWebLLMModel,
1497
1593
  withSystem
1498
1594
  });
1499
1595
  //# sourceMappingURL=index.cjs.map