openzoo 0.43.7 → 0.43.8

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/lib/grokui.mjs CHANGED
@@ -561,6 +561,25 @@ const APP_HTML = `<!doctype html>
561
561
  .bubble { padding: 11px 16px; border-radius: 20px; white-space: pre-wrap; word-break: break-word;
562
562
  -webkit-user-select: text; user-select: text; cursor: text; }
563
563
  .bubble a { color: #6ab0ff; text-decoration: underline; cursor: pointer; }
564
+ /* rendered markdown. The bubble is pre-wrap for plain text, but block
565
+ elements carry their own spacing — leaving pre-wrap on would add the
566
+ source newlines back on top of it and double every gap. */
567
+ .bubble:has(> p, > .md-h, > .md-table, > .md-list, > .md-pre) { white-space: normal; }
568
+ .bubble > p { margin: 0 0 10px; }
569
+ .bubble > p:last-child { margin-bottom: 0; }
570
+ .md-h { margin: 14px 0 8px; font-size: 15px; font-weight: 600; line-height: 1.3; }
571
+ .md-h:first-child { margin-top: 0; }
572
+ .md-hr { border: 0; border-top: 1px solid #3a3a3c; margin: 14px 0; }
573
+ .md-list { margin: 0 0 10px; padding-left: 22px; }
574
+ .md-list li { margin: 3px 0; }
575
+ .bubble code { background: #1c1c1e; border: 1px solid #333; border-radius: 5px;
576
+ padding: 1px 5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }
577
+ .md-pre { background: #1c1c1e; border: 1px solid #333; border-radius: 10px; padding: 10px 12px;
578
+ overflow-x: auto; margin: 0 0 10px; }
579
+ .md-pre code { background: none; border: 0; padding: 0; font-size: 12.5px; line-height: 1.45; }
580
+ .md-table { border-collapse: collapse; margin: 0 0 10px; font-size: 13px; display: block; overflow-x: auto; }
581
+ .md-table th, .md-table td { border: 1px solid #3a3a3c; padding: 5px 10px; text-align: left; vertical-align: top; }
582
+ .md-table th { background: #1c1c1e; font-weight: 600; }
564
583
  .bubble-images { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
565
584
  .bubble-images img { max-width: 160px; max-height: 160px; border-radius: 12px; display: block; }
566
585
  .runcard { background: #1c1c1e; border: 1px solid #333; border-radius: 14px; padding: 12px 14px; max-width: 100%; }
@@ -754,11 +773,78 @@ const APP_HTML = `<!doctype html>
754
773
  }
755
774
 
756
775
  function escapeHtml(s) { return s.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c])); }
776
+ // Inline span-level markdown. Runs AFTER escapeHtml, so every tag below is
777
+ // one we created — model output can never inject its own.
778
+ function mdInline(s) {
779
+ let o = escapeHtml(s);
780
+ o = o.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
781
+ o = o.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');
782
+ o = o.replace(/(^|[^*])\\*([^*\\n]+)\\*/g, '$1<em>$2</em>');
783
+ o = o.replace(/\\[([^\\]]+)\\]\\((https?:[^)\\s]+)\\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
784
+ // bare URLs, but only at a boundary — inside href="..." the preceding
785
+ // char is a quote, so links we just built are left alone
786
+ o = o.replace(/(^|[\\s(])(https?:\\/\\/[^\\s<)]+)/g, '$1<a href="$2" target="_blank" rel="noopener">$2</a>');
787
+ o = o.replace(/@(\\w+)/g, '<span class="mention">\u{1F465} $1</span>');
788
+ return o;
789
+ }
790
+
791
+ // Block-level markdown: fenced code, tables, headings, lists. Models answer
792
+ // in markdown by default, and rendering it as literal "## " and "| --- |"
793
+ // made every structured answer unreadable.
757
794
  function renderMentions(text) {
758
- let out = escapeHtml(text);
759
- out = out.replace(/(https?:\\/\\/[^\\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>');
760
- out = out.replace(/@(\\w+)/g, '<span class="mention">\u{1F465} $1</span>');
761
- return out;
795
+ const fences = [];
796
+ const src = String(text).replace(/\`\`\`([\\w-]*)\\n?([\\s\\S]*?)\`\`\`/g, (m, lang, code) => {
797
+ fences.push('<pre class="md-pre"><code>' + escapeHtml(code.replace(/\\n$/, '')) + '</code></pre>');
798
+ return '\\u0000F' + (fences.length - 1) + '\\u0000';
799
+ });
800
+ const lines = src.split('\\n');
801
+ const out = [];
802
+ let list = null, tbl = null, para = [];
803
+ const flushPara = () => {
804
+ if (!para.length) return;
805
+ out.push('<p>' + para.map(mdInline).join('<br>') + '</p>');
806
+ para = [];
807
+ };
808
+ const closeList = () => { if (list) { out.push('</' + list + '>'); list = null; } };
809
+ const closeTbl = () => {
810
+ if (!tbl) return;
811
+ let h = '<table class="md-table"><thead><tr>'
812
+ + tbl[0].map((c) => '<th>' + mdInline(c) + '</th>').join('') + '</tr></thead><tbody>';
813
+ for (const r of tbl.slice(1)) h += '<tr>' + r.map((c) => '<td>' + mdInline(c) + '</td>').join('') + '</tr>';
814
+ tbl = null;
815
+ out.push(h + '</tbody></table>');
816
+ };
817
+ const cells = (l) => l.replace(/^\\s*\\|/, '').replace(/\\|\\s*$/, '').split('|').map((c) => c.trim());
818
+ for (const line of lines) {
819
+ if (/^\\s*\\|.*\\|\\s*$/.test(line)) { // table row
820
+ flushPara(); closeList();
821
+ if (/^[\\s|:-]+$/.test(line)) continue; // |---|---| separator
822
+ (tbl = tbl || []).push(cells(line));
823
+ continue;
824
+ }
825
+ closeTbl();
826
+ const h = /^(#{1,4})\\s+(.*)$/.exec(line);
827
+ if (h) { flushPara(); closeList(); out.push('<h' + (h[1].length + 2) + ' class="md-h">' + mdInline(h[2]) + '</h' + (h[1].length + 2) + '>'); continue; }
828
+ if (/^\\s*([-*_])\\s*\\1\\s*\\1[\\s\\-*_]*$/.test(line)) { flushPara(); closeList(); out.push('<hr class="md-hr">'); continue; }
829
+ const ul = /^\\s*[-*]\\s+(.*)$/.exec(line);
830
+ const ol = /^\\s*\\d+[.)]\\s+(.*)$/.exec(line);
831
+ if (ul || ol) {
832
+ flushPara();
833
+ const want = ul ? 'ul' : 'ol';
834
+ if (list && list !== want) closeList();
835
+ if (!list) { list = want; out.push('<' + want + ' class="md-list">'); }
836
+ out.push('<li>' + mdInline((ul || ol)[1]) + '</li>');
837
+ continue;
838
+ }
839
+ closeList();
840
+ if (!line.trim()) { flushPara(); continue; }
841
+ // a fence placeholder is a BLOCK — letting it fall into a paragraph
842
+ // emits <p><pre>…</pre></p>, which browsers silently split apart
843
+ if (/^\\u0000F\\d+\\u0000$/.test(line.trim())) { flushPara(); out.push(line.trim()); continue; }
844
+ para.push(line);
845
+ }
846
+ flushPara(); closeList(); closeTbl();
847
+ return out.join('').replace(/\\u0000F(\\d+)\\u0000/g, (m, i) => fences[Number(i)]);
762
848
  }
763
849
 
764
850
  let lastSpeaker = null;
@@ -1077,8 +1163,10 @@ const APP_HTML = `<!doctype html>
1077
1163
  const mult = direct / spent;
1078
1164
  // honest either way: >=1x is a real saving vs a naked direct call,
1079
1165
  // <1x means you're currently paying MORE than direct would cost —
1080
- // don't dress that up as green when it isn't one
1081
- savedEl.textContent = mult.toFixed(2) + 'x';
1166
+ // don't dress that up as green when it isn't one.
1167
+ // Asking a bound corpus makes this genuinely large (the counterfactual
1168
+ // is shipping the WHOLE corpus), so 2dp would read as noise up there.
1169
+ savedEl.textContent = (mult >= 100 ? Math.round(mult) : mult.toFixed(mult >= 10 ? 1 : 2)) + 'x';
1082
1170
  savedEl.className = mult >= 1 ? 'hlime' : 'hember';
1083
1171
  } else {
1084
1172
  savedEl.textContent = '—';
package/lib/proxy.js CHANGED
@@ -678,15 +678,22 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
678
678
  // billedUsd = cogs * markup on a straight-markup call. Close enough
679
679
  // on a counterfactual (leCore-discounted) call too since markup is
680
680
  // still the ceiling those get capped against.
681
- sessionCogs += receipt.billedUsd / MARKUP;
682
- // direct: savesVsDirect = direct / billedUsd is on the receipt when
683
- // leCore compression engaged (server derives it from real token
684
- // counts) exact, not estimated. When absent, nothing was
685
- // compressed, so direct === what was paid (same reasoning as the
686
- // like-for-like fix: no compression, no saving, not zero).
687
- sessionDirect += typeof receipt.savesVsDirect === 'number'
688
- ? receipt.savesVsDirect * receipt.billedUsd
689
- : receipt.billedUsd;
681
+ // Prefer the gateway's own cogsUsd. Deriving it as billedUsd/MARKUP
682
+ // is only correct on a straight-markup call: under counterfactual
683
+ // pricing billedUsd is min(direct×discount, markupUsd), so the
684
+ // division understates cost and overstates margin.
685
+ sessionCogs += typeof receipt.cogsUsd === 'number'
686
+ ? receipt.cogsUsd
687
+ : receipt.billedUsd / MARKUP;
688
+ // direct = what answering this WITHOUT the zoo would have cost. On an
689
+ // attach call that is the whole bound corpus, which is why it can be
690
+ // orders of magnitude above what was billed. directUsd is exact and
691
+ // always present; savesVsDirect is the same number as a ratio.
692
+ sessionDirect += typeof receipt.directUsd === 'number'
693
+ ? receipt.directUsd
694
+ : typeof receipt.savesVsDirect === 'number'
695
+ ? receipt.savesVsDirect * receipt.billedUsd
696
+ : receipt.billedUsd;
690
697
  // The public-URL ceiling meters only public-origin spend — your own
691
698
  // local calls never eat into it.
692
699
  if (viaTunnel) tunnelSpent += receipt.billedUsd;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.43.7",
3
+ "version": "0.43.8",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",