zames_pro 2.81.14 → 2.81.16

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.81.16] - 2026-10-21
11
+
12
+ ### Fixed
13
+
14
+ - **i18n:** /cost describes the context counter neutrally
15
+
16
+ ## [2.81.15] - 2026-10-21
17
+
18
+ ### Fixed
19
+
20
+ - **web:** a search result without a snippet no longer borrows the next one
21
+
10
22
  ## [2.81.14] - 2026-10-21
11
23
 
12
24
  ### Fixed
@@ -285,13 +285,16 @@ export const commonMessages = {
285
285
  en: 'Session renamed: {v}',
286
286
  },
287
287
  'cost.title': { ru: 'Статистика сессии:', en: 'Session stats:' },
288
+ // /cost runs on EVERY provider, so the counter is described neutrally:
289
+ // DeepSeek reports accumulated_token_usage, Qwen input_tokens, GLM
290
+ // prompt_tokens - naming one backend made the other two look broken.
288
291
  'cost.context_known': {
289
- ru: ' контекст: ~{n} токенов (DeepSeek accumulated_token_usage)',
290
- en: ' context: ~{n} tokens (DeepSeek accumulated_token_usage)',
292
+ ru: ' контекст: ~{n} токенов (счётчик провайдера)',
293
+ en: ' context: ~{n} tokens (the provider counter)',
291
294
  },
292
295
  'cost.context_unknown': {
293
- ru: ' контекст: неизвестно (DeepSeek сообщит после первого ответа в чате)',
294
- en: ' context: unknown (DeepSeek reports it after the first answer in a chat)',
296
+ ru: ' контекст: неизвестно (провайдер сообщит после первого ответа в чате)',
297
+ en: ' context: unknown (the provider reports it after the first answer in a chat)',
295
298
  },
296
299
  'cost.tasks': { ru: ' задач: {n}', en: ' tasks: {n}' },
297
300
  'cost.tool_calls': {
package/dist/web.js CHANGED
@@ -645,33 +645,47 @@ export function createWebTools() {
645
645
  // wraps links in a /l/?uddg=... redirect which must be unwrapped.
646
646
  export function parseDuckDuckGoResults(body, limit) {
647
647
  const results = [];
648
- const re = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>)?/gi;
648
+ // Parse the page ROW BY ROW: each `result__a` title opens a result, and a
649
+ // `result__snippet` belongs to the result opened most recently. The old
650
+ // single regex made the snippet group OPTIONAL with an unbounded gap, so a
651
+ // result without a snippet picked up the FOLLOWING result's snippet (wrong
652
+ // text under a URL). A row scan ties every snippet to its own result.
653
+ const anchorRe = /<a[^>]*class="[^"]*result__(a|snippet)[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
654
+ const hrefOf = (tag) => {
655
+ const m = tag.match(/href="([^"]*)"/i);
656
+ return m ? m[1] : '';
657
+ };
658
+ let cur = null;
649
659
  let m;
650
- while ((m = re.exec(body)) && results.length < limit) {
651
- let href = m[1];
652
- const uddgMatch = href.match(/[?&]uddg=([^&]+)/);
653
- if (uddgMatch) {
654
- // decodeURIComponent THROWS on a malformed percent escape (`%ZZ`, a lone
655
- // `%`). One broken href in the page must not abort the whole search: keep
656
- // the raw href, which the http(s) check below then rejects.
657
- try {
658
- href = decodeURIComponent(uddgMatch[1]);
660
+ while ((m = anchorRe.exec(body))) {
661
+ const kind = m[1].toLowerCase();
662
+ if (kind === 'a') {
663
+ if (cur && cur.title && /^https?:\/\//i.test(cur.url)) {
664
+ results.push(cur);
665
+ if (results.length >= limit)
666
+ return results;
659
667
  }
660
- catch {
661
- href = uddgMatch[1];
668
+ cur = { title: htmlToText(m[2] || '').trim(), url: '', snippet: '' };
669
+ // The href lives in the OPENING tag, which m[0] starts with.
670
+ let href = hrefOf(m[0].slice(0, m[0].indexOf('>') + 1));
671
+ const uddg = href.match(/[?&]uddg=([^&]+)/);
672
+ if (uddg) {
673
+ try {
674
+ href = decodeURIComponent(uddg[1]);
675
+ }
676
+ catch {
677
+ href = uddg[1];
678
+ }
662
679
  }
663
- }
664
- const title = htmlToText(m[2] || '').trim();
665
- const snippet = htmlToText(m[3] || '').trim();
666
- // Only an absolute http(s) link is a real result. The page can carry a
667
- // `javascript:`/`data:` href (malicious or broken markup) or a
668
- // site-relative one; handing either to the model as a result URL is wrong
669
- // (a `javascript:` URL looks fetchable, a relative one is not).
670
- if (!title || !/^https?:\/\//i.test(href))
680
+ cur.url = href;
671
681
  continue;
672
- results.push({ title, url: href, snippet });
682
+ }
683
+ if (cur && !cur.snippet)
684
+ cur.snippet = htmlToText(m[2] || '').trim();
673
685
  }
674
- return results;
686
+ if (cur && cur.title && /^https?:\/\//i.test(cur.url))
687
+ results.push(cur);
688
+ return results.slice(0, limit);
675
689
  }
676
690
  function formatResult(status, url, text, limit) {
677
691
  let out = text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.81.14",
3
+ "version": "2.81.16",
4
4
  "description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
5
5
  "type": "module",
6
6
  "bin": {