@hasna/coders 0.1.2 → 0.1.4

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/cli.mjs CHANGED
@@ -61872,9 +61872,16 @@ async function runAgentLoop(initialMessages, options2) {
61872
61872
  throw error;
61873
61873
  }
61874
61874
  if (aborted) break;
61875
+ const cleanedBlocks = contentBlocks.map((b) => {
61876
+ if (b.type === "tool_use") {
61877
+ const { _inputParseFailed, _rawInputJson, ...clean } = b;
61878
+ return clean;
61879
+ }
61880
+ return b;
61881
+ });
61875
61882
  const assistantMessage = {
61876
61883
  role: "assistant",
61877
- content: contentBlocks
61884
+ content: cleanedBlocks
61878
61885
  };
61879
61886
  messages.push(assistantMessage);
61880
61887
  const toolUseBlocks = contentBlocks.filter(
@@ -70172,21 +70179,18 @@ function useSpinner(active) {
70172
70179
  }, [active]);
70173
70180
  return active ? FRAMES[i] : " ";
70174
70181
  }
70175
- function createToolHandlers(onToolStart, onToolEnd, requestPermission) {
70182
+ function createToolHandlers() {
70176
70183
  const tools = [bashTool, readTool, editTool, writeTool, globTool, grepTool];
70177
70184
  const permCtx = createDefaultPermissionContext();
70178
70185
  const appState = { toolPermissionContext: permCtx, verbose: false };
70179
- const alwaysAllowed = /* @__PURE__ */ new Set();
70180
70186
  return tools.map((tool) => ({
70181
70187
  name: tool.name,
70182
- description: typeof tool.description === "function" ? `Tool: ${tool.name}` : String(tool.description),
70183
- inputSchema: { type: "object", properties: {} },
70188
+ description: TOOL_DESCRIPTIONS[tool.name] ?? `Tool: ${tool.name}`,
70189
+ inputSchema: TOOL_JSON_SCHEMAS[tool.name] ?? { type: "object", properties: {} },
70184
70190
  isReadOnly: tool.isReadOnly(),
70185
70191
  isConcurrencySafe: tool.isConcurrencySafe(),
70186
70192
  call: async (input, ctx) => {
70187
70193
  const summary = toolSummary(tool.name, input);
70188
- const toolId = `tool-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
70189
- onToolStart(toolId, tool.name, summary);
70190
70194
  const t0 = performance.now();
70191
70195
  try {
70192
70196
  const toolCtx = {
@@ -70196,18 +70200,16 @@ function createToolHandlers(onToolStart, onToolEnd, requestPermission) {
70196
70200
  options: { mainLoopModel: "sonnet", thinkingConfig: { type: "disabled" }, isNonInteractiveSession: false, tools: [], agentDefinitions: { activeAgents: [] } }
70197
70201
  };
70198
70202
  const result = await tool.call(input, toolCtx);
70199
- const block2 = tool.mapToolResultToToolResultBlockParam(result.data, toolId);
70203
+ const block2 = tool.mapToolResultToToolResultBlockParam(result.data, ctx.toolUseId ?? "");
70200
70204
  const dur = performance.now() - t0;
70201
- onToolEnd(toolId, block2.content.slice(0, 500), block2.is_error ? block2.content : void 0, dur);
70202
70205
  try {
70203
70206
  dbRun("INSERT INTO audit_log (tool_name, input_summary, result_summary, duration_ms, was_allowed) VALUES (?, ?, ?, ?, 1)", [tool.name, summary, block2.content.slice(0, 200), dur]);
70204
70207
  } catch {
70205
70208
  }
70206
- return { data: block2.content };
70209
+ return block2.is_error ? { data: block2.content, error: block2.content, isError: true } : { data: block2.content };
70207
70210
  } catch (err) {
70208
70211
  const dur = performance.now() - t0;
70209
70212
  const errMsg = err instanceof Error ? err.message : String(err);
70210
- onToolEnd(toolId, "", errMsg, dur);
70211
70213
  try {
70212
70214
  dbRun("INSERT INTO audit_log (tool_name, input_summary, result_summary, duration_ms, was_allowed) VALUES (?, ?, ?, ?, 1)", [tool.name, summary, errMsg.slice(0, 200), dur]);
70213
70215
  } catch {
@@ -70390,11 +70392,23 @@ function App2({ model, mode, initialPrompt }) {
70390
70392
  setActiveTools((prev) => [...prev, { id, name, summary, status: "running" }]);
70391
70393
  }, []);
70392
70394
  const onToolEnd = (0, import_react22.useCallback)((id, result, error, durationMs) => {
70393
- setActiveTools(
70394
- (prev) => prev.map(
70395
- (t) => t.id === id ? { ...t, status: error ? "error" : "done", result, error, durationMs } : t
70396
- )
70397
- );
70395
+ const tool = activeToolsRef.current.find((t) => t.id === id);
70396
+ if (!tool) return;
70397
+ const completed = {
70398
+ ...tool,
70399
+ status: error ? "error" : "done",
70400
+ result,
70401
+ error,
70402
+ durationMs
70403
+ };
70404
+ setMsgs((prev) => [...prev, {
70405
+ id: `td-${id}`,
70406
+ role: "assistant",
70407
+ content: "",
70408
+ timestamp: Date.now(),
70409
+ tools: [completed]
70410
+ }]);
70411
+ setActiveTools((prev) => prev.filter((t) => t.id !== id));
70398
70412
  }, []);
70399
70413
  const submit = (0, import_react22.useCallback)(async (text) => {
70400
70414
  if (!text.trim() || busy) return;
@@ -70416,8 +70430,9 @@ function App2({ model, mode, initialPrompt }) {
70416
70430
  setActiveTools([]);
70417
70431
  const t0 = performance.now();
70418
70432
  try {
70419
- const toolHandlers = createToolHandlers(onToolStart, onToolEnd, requestPermission);
70433
+ const toolHandlers = createToolHandlers();
70420
70434
  const permCtx = createDefaultPermissionContext();
70435
+ const toolStartTimes = /* @__PURE__ */ new Map();
70421
70436
  const result = await runAgentLoop(
70422
70437
  newHistory,
70423
70438
  {
@@ -70434,7 +70449,7 @@ function App2({ model, mode, initialPrompt }) {
70434
70449
  permissionContext: permCtx,
70435
70450
  maxTurns: 10,
70436
70451
  onTextDelta: (text2) => {
70437
- if (activeToolsRef.current.length === 0 || activeToolsRef.current.every((t) => t.status === "done" || t.status === "error")) {
70452
+ if (activeToolsRef.current.length === 0) {
70438
70453
  setStreaming((prev) => prev + text2);
70439
70454
  }
70440
70455
  },
@@ -70442,11 +70457,14 @@ function App2({ model, mode, initialPrompt }) {
70442
70457
  },
70443
70458
  onToolUseStart: (name, id, toolInput) => {
70444
70459
  setStreaming("");
70460
+ toolStartTimes.set(id, performance.now());
70445
70461
  onToolStart(id, name, toolSummary(name, toolInput));
70446
70462
  },
70447
70463
  onToolUseEnd: (name, id, toolResult) => {
70448
70464
  const isErr = toolResult.isError || !!toolResult.error;
70449
- onToolEnd(id, String(toolResult.data ?? "").slice(0, 500), isErr ? toolResult.error : void 0);
70465
+ const startTime = toolStartTimes.get(id);
70466
+ const dur2 = startTime ? performance.now() - startTime : void 0;
70467
+ onToolEnd(id, String(toolResult.data ?? "").slice(0, 500), isErr ? toolResult.error : void 0, dur2);
70450
70468
  }
70451
70469
  }
70452
70470
  );
@@ -70466,19 +70484,27 @@ function App2({ model, mode, initialPrompt }) {
70466
70484
  );
70467
70485
  setCost((p) => p + c.totalCostUsd);
70468
70486
  setTokens((p) => p + result.usage.totalInputTokens + result.usage.totalOutputTokens);
70469
- const frozenTools = [...activeToolsRef.current].map(
70470
- (t) => t.status === "running" ? { ...t, status: "done" } : t
70471
- );
70472
70487
  const verb = VERBS[Math.floor(Math.random() * VERBS.length)];
70473
- setMsgs((p) => [...p, {
70474
- id: `a${Date.now()}`,
70475
- role: "assistant",
70476
- content: finalText || streaming || "(no response)",
70477
- timestamp: Date.now(),
70478
- tools: frozenTools.length > 0 ? frozenTools : void 0,
70479
- durationMs: dur,
70480
- durationVerb: verb
70481
- }]);
70488
+ const textContent = finalText || streaming || "";
70489
+ if (textContent && textContent !== "(no response)") {
70490
+ setMsgs((p) => [...p, {
70491
+ id: `a${Date.now()}`,
70492
+ role: "assistant",
70493
+ content: textContent,
70494
+ timestamp: Date.now(),
70495
+ durationMs: dur,
70496
+ durationVerb: verb
70497
+ }]);
70498
+ } else if (dur > 1e3) {
70499
+ setMsgs((p) => [...p, {
70500
+ id: `a${Date.now()}`,
70501
+ role: "assistant",
70502
+ content: "",
70503
+ timestamp: Date.now(),
70504
+ durationMs: dur,
70505
+ durationVerb: verb
70506
+ }]);
70507
+ }
70482
70508
  setHistory(result.messages.filter((m) => m.role === "user" || m.role === "assistant"));
70483
70509
  setStreaming("");
70484
70510
  setActiveTools([]);
@@ -70488,7 +70514,7 @@ function App2({ model, mode, initialPrompt }) {
70488
70514
  } finally {
70489
70515
  setBusy(false);
70490
70516
  }
70491
- }, [busy, history, model, exit, onToolStart, onToolEnd, requestPermission, streaming, activeTools]);
70517
+ }, [busy, history, model, exit, onToolStart, onToolEnd, streaming, activeTools]);
70492
70518
  (0, import_react22.useEffect)(() => {
70493
70519
  if (initialPrompt) submit(initialPrompt);
70494
70520
  }, []);
@@ -70541,47 +70567,49 @@ function App2({ model, mode, initialPrompt }) {
70541
70567
  } else if (key.escape) setInput("");
70542
70568
  else if (!key.ctrl && !key.meta && ch) setInput((p) => p + ch);
70543
70569
  });
70544
- const recentTools = activeTools.slice(-2);
70545
70570
  const cols = stdout?.columns ?? 80;
70546
70571
  const sep = "\u2500".repeat(Math.min(cols, 120));
70572
+ const hasRunningTools = activeTools.some((t) => t.status === "running");
70547
70573
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
70548
70574
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Static, { items: msgs, children: (msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageView, { msg }) }, msg.id) }),
70549
- busy && recentTools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", children: recentTools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolItem, { tool: t }, t.id)) }),
70550
- busy && streaming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70551
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "green", children: "\u25CF " }),
70552
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: streaming.split("\n").filter((l) => l.trim()).slice(-3).join("\n").slice(-200) })
70553
- ] }),
70554
- busy && !streaming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70555
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerDot, {}),
70556
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
70557
- " ",
70558
- recentTools.some((t) => t.status === "running") ? "Working" : "Thinking",
70559
- "..."
70560
- ] })
70561
- ] }),
70562
70575
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", children: [
70563
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70564
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70565
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: "cyan", bold: true, children: [
70566
- PROMPT,
70567
- " "
70568
- ] }),
70569
- input.startsWith("/") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "magenta", children: input }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: input }),
70570
- !busy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "gray", children: "\u258E" })
70576
+ busy && activeTools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", children: activeTools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolItem, { tool: t }, t.id)) }),
70577
+ busy && streaming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "green", children: "\u25CF " }),
70579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: streaming.split("\n").filter((l) => l.trim()).slice(-3).join("\n").slice(-200) })
70580
+ ] }),
70581
+ busy && !streaming && activeTools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerDot, {}),
70583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: " Thinking..." })
70584
+ ] }),
70585
+ busy && hasRunningTools && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerDot, {}),
70587
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: " Working..." })
70571
70588
  ] }),
70572
- showSlashMenu && filteredCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: filteredCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70573
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: i === slashSelected ? "cyan" : void 0, bold: i === slashSelected, children: i === slashSelected ? "\u25B8 " : " " }),
70574
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: i === slashSelected ? "cyan" : "blue", children: [
70575
- "/",
70576
- cmd.name.padEnd(16)
70589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", children: [
70590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: "cyan", bold: true, children: [
70593
+ PROMPT,
70594
+ " "
70595
+ ] }),
70596
+ input.startsWith("/") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "magenta", children: input }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: input }),
70597
+ !busy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "gray", children: "\u258E" })
70577
70598
  ] }),
70578
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
70579
- " ",
70580
- cmd.description
70581
- ] })
70582
- ] }, cmd.name)) }),
70583
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70584
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, { model, mode, cost, tokens })
70599
+ showSlashMenu && filteredCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: filteredCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: i === slashSelected ? "cyan" : void 0, bold: i === slashSelected, children: i === slashSelected ? "\u25B8 " : " " }),
70601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: i === slashSelected ? "cyan" : "blue", children: [
70602
+ "/",
70603
+ cmd.name.padEnd(16)
70604
+ ] }),
70605
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
70606
+ " ",
70607
+ cmd.description
70608
+ ] })
70609
+ ] }, cmd.name)) }),
70610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, { model, mode, cost, tokens })
70612
+ ] })
70585
70613
  ] })
70586
70614
  ] });
70587
70615
  }
@@ -70634,7 +70662,7 @@ function fmtTok(n) {
70634
70662
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
70635
70663
  return `${(n / 1e6).toFixed(2)}M`;
70636
70664
  }
70637
- var import_react22, import_jsx_runtime, CONN, PROMPT, FRAMES, VERBS;
70665
+ var import_react22, import_jsx_runtime, CONN, PROMPT, FRAMES, VERBS, TOOL_JSON_SCHEMAS, TOOL_DESCRIPTIONS;
70638
70666
  var init_app = __esm({
70639
70667
  async "src/ui/app.tsx"() {
70640
70668
  "use strict";
@@ -70662,6 +70690,82 @@ var init_app = __esm({
70662
70690
  PROMPT = "\u276F";
70663
70691
  FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
70664
70692
  VERBS = ["Baked", "Brewed", "Churned", "Cogitated", "Cooked", "Crunched", "Saut\xE9ed", "Worked"];
70693
+ TOOL_JSON_SCHEMAS = {
70694
+ Bash: {
70695
+ type: "object",
70696
+ properties: {
70697
+ command: { type: "string", description: "The command to execute" },
70698
+ description: { type: "string", description: "Clear, concise description of what this command does" },
70699
+ timeout: { type: "number", description: "Optional timeout in milliseconds (max 600000)" },
70700
+ run_in_background: { type: "boolean", description: "Set to true to run in the background" }
70701
+ },
70702
+ required: ["command"]
70703
+ },
70704
+ Read: {
70705
+ type: "object",
70706
+ properties: {
70707
+ file_path: { type: "string", description: "The absolute path to the file to read" },
70708
+ offset: { type: "number", description: "The line number to start reading from" },
70709
+ limit: { type: "number", description: "The number of lines to read" },
70710
+ pages: { type: "string", description: "Page range for PDF files (e.g. '1-5')" }
70711
+ },
70712
+ required: ["file_path"]
70713
+ },
70714
+ Edit: {
70715
+ type: "object",
70716
+ properties: {
70717
+ file_path: { type: "string", description: "The absolute path to the file to modify" },
70718
+ old_string: { type: "string", description: "The text to replace" },
70719
+ new_string: { type: "string", description: "The text to replace it with (must be different from old_string)" },
70720
+ replace_all: { type: "boolean", description: "Replace all occurrences of old_string (default false)", default: false }
70721
+ },
70722
+ required: ["file_path", "old_string", "new_string"]
70723
+ },
70724
+ Write: {
70725
+ type: "object",
70726
+ properties: {
70727
+ file_path: { type: "string", description: "The absolute path to the file to write" },
70728
+ content: { type: "string", description: "The content to write to the file" }
70729
+ },
70730
+ required: ["file_path", "content"]
70731
+ },
70732
+ Glob: {
70733
+ type: "object",
70734
+ properties: {
70735
+ pattern: { type: "string", description: "The glob pattern to match files against" },
70736
+ path: { type: "string", description: "The directory to search in. Defaults to current working directory." }
70737
+ },
70738
+ required: ["pattern"]
70739
+ },
70740
+ Grep: {
70741
+ type: "object",
70742
+ properties: {
70743
+ pattern: { type: "string", description: "The regular expression pattern to search for" },
70744
+ path: { type: "string", description: "File or directory to search in. Defaults to cwd." },
70745
+ glob: { type: "string", description: "Glob pattern to filter files (e.g. '*.js', '*.{ts,tsx}')" },
70746
+ type: { type: "string", description: "File type to search (js, py, rust, go, etc.)" },
70747
+ output_mode: { type: "string", enum: ["content", "files_with_matches", "count"], description: "Output mode. Defaults to 'files_with_matches'." },
70748
+ "-A": { type: "number", description: "Lines to show after each match" },
70749
+ "-B": { type: "number", description: "Lines to show before each match" },
70750
+ "-C": { type: "number", description: "Context lines before and after each match" },
70751
+ context: { type: "number", description: "Alias for -C" },
70752
+ "-i": { type: "boolean", description: "Case insensitive search" },
70753
+ "-n": { type: "boolean", description: "Show line numbers (default true)" },
70754
+ multiline: { type: "boolean", description: "Enable multiline mode" },
70755
+ head_limit: { type: "number", description: "Limit output to first N entries" },
70756
+ offset: { type: "number", description: "Skip first N entries" }
70757
+ },
70758
+ required: ["pattern"]
70759
+ }
70760
+ };
70761
+ TOOL_DESCRIPTIONS = {
70762
+ Bash: "Executes a bash command and returns its output. Use for system commands, builds, and terminal operations.",
70763
+ Read: "Reads a file from the filesystem. Returns contents with line numbers. Supports text, PDF, images, and notebooks.",
70764
+ Edit: "Performs exact string replacements in files. Use old_string/new_string for targeted edits.",
70765
+ Write: "Writes content to a file, creating it if needed or overwriting if it exists.",
70766
+ Glob: "Fast file pattern matching. Returns matching file paths sorted by modification time.",
70767
+ Grep: "Content search powered by ripgrep. Supports regex, file type filters, and multiple output modes."
70768
+ };
70665
70769
  }
70666
70770
  });
70667
70771
 
@@ -70879,7 +70983,7 @@ var VERSION, BUILD_TIME, PACKAGE_NAME2, ISSUES_URL2, startupTimestamps, original
70879
70983
  var init_index = __esm({
70880
70984
  "src/cli/index.ts"() {
70881
70985
  VERSION = "0.1.2";
70882
- BUILD_TIME = "2026-03-20T13:23:30.894Z";
70986
+ BUILD_TIME = "2026-03-20T14:03:23.252Z";
70883
70987
  PACKAGE_NAME2 = "@hasna/coders";
70884
70988
  ISSUES_URL2 = "https://github.com/hasnaxyz/open-coders/issues";
70885
70989
  startupTimestamps = {};