@operatingtag/dsh-unclesam 1.3.3 → 1.3.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.
- package/CHANGELOG.md +58 -0
- package/README.ar.md +1 -1
- package/README.az.md +1 -1
- package/README.bg.md +1 -1
- package/README.bn.md +1 -1
- package/README.cs.md +1 -1
- package/README.da.md +1 -1
- package/README.de.md +1 -1
- package/README.el.md +1 -1
- package/README.es.md +1 -1
- package/README.fa.md +1 -1
- package/README.fi.md +1 -1
- package/README.fr.md +1 -1
- package/README.he.md +1 -1
- package/README.hi.md +1 -1
- package/README.hr.md +1 -1
- package/README.hu.md +1 -1
- package/README.id.md +1 -1
- package/README.it.md +1 -1
- package/README.ja.md +1 -1
- package/README.ka.md +1 -1
- package/README.kk.md +1 -1
- package/README.km.md +1 -1
- package/README.ko.md +1 -1
- package/README.lt.md +1 -1
- package/README.md +1 -1
- package/README.mr.md +1 -1
- package/README.ms.md +1 -1
- package/README.my.md +1 -1
- package/README.ne.md +1 -1
- package/README.nl.md +1 -1
- package/README.no.md +1 -1
- package/README.pl.md +1 -1
- package/README.pt.md +1 -1
- package/README.ro.md +1 -1
- package/README.ru.md +1 -1
- package/README.si.md +1 -1
- package/README.sk.md +1 -1
- package/README.sl.md +1 -1
- package/README.sr.md +1 -1
- package/README.sv.md +1 -1
- package/README.sw.md +1 -1
- package/README.ta.md +1 -1
- package/README.te.md +1 -1
- package/README.th.md +1 -1
- package/README.tl.md +1 -1
- package/README.tr.md +1 -1
- package/README.uk.md +1 -1
- package/README.ur.md +1 -1
- package/README.uz.md +1 -1
- package/README.vi.md +1 -1
- package/README.zh.md +1 -1
- package/index.js +74 -8
- package/lib/prompt.mjs +289 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,64 @@ fixes the patch.
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## 1.3.5
|
|
12
|
+
|
|
13
|
+
**A live web turn came apart into a loop, and the audit it prompted fixed three things.**
|
|
14
|
+
|
|
15
|
+
Asked to glob a workspace it had misread, the web model invented a tool format
|
|
16
|
+
(`<tool name="glob" string="true">{…}</tool>`), the parser did not accept it, nothing told the model so, and
|
|
17
|
+
it repeated the block until the transcript was full of copies.
|
|
18
|
+
|
|
19
|
+
- **The invented shape is now accepted.** `parseToolCall` recognises `<tool name="…">…</tool>` and
|
|
20
|
+
`<tool_call>…</tool_call>`, and `stripToolCall` removes them, so a model that drifts to tags still gets a
|
|
21
|
+
running call instead of a dead one.
|
|
22
|
+
- **A reply that loops is stopped, not displayed.** `looksDegenerate` fires on three `<tool` openings or
|
|
23
|
+
four identical substantial lines, and the turn ends with a plain `web_loop` error naming what happened.
|
|
24
|
+
Nothing was wrong with the page; the model was stuck, and the person no longer has to watch it write one
|
|
25
|
+
line for minutes.
|
|
26
|
+
- **Two audit findings, reproduced against the running code:**
|
|
27
|
+
- `findCallForm` treated an EMPTY tool list as "accept anything", so ordinary prose like `log(2)` and
|
|
28
|
+
`figure(2)` was silently deleted from every non-tool turn. The empty list now means "nothing is a call".
|
|
29
|
+
- A malformed call before a valid one (`read(bad) and pwsh({…})`) returned nothing, hiding the valid call
|
|
30
|
+
and leaking its text into the transcript. A bad candidate now skips to the next instead of aborting.
|
|
31
|
+
|
|
32
|
+
`dev/test-call-form.mjs` gained the loop, tag, empty-list and malformed-candidate cases; the suite is 15
|
|
33
|
+
suites, all green.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1.3.4
|
|
38
|
+
|
|
39
|
+
**A tool call is now written `name({...})` instead of a JSON envelope, so the answer can stream.**
|
|
40
|
+
|
|
41
|
+
MEASURED: a reply that is nothing but `{"tool":…}` cannot be shown while it is written — `streamSafeCut`
|
|
42
|
+
returns 0 for the whole generation — so the harness sits empty for the entire turn and looks stuck. And a
|
|
43
|
+
brace is the commonest punctuation in technical prose, so `{` is a poor delimiter: it both blocks streaming
|
|
44
|
+
and invites false positives.
|
|
45
|
+
|
|
46
|
+
`replace_note({"text":"…"})` needs no instruction, because it is the shape models already produce, and it is
|
|
47
|
+
unambiguous once anchored on a tool name the model was actually offered. Prose streams until a known name
|
|
48
|
+
opens a parenthesis, instead of stopping at the first brace anywhere.
|
|
49
|
+
|
|
50
|
+
- **`parseToolCall` walks characters, not a regex**, so an argument may contain a parenthesis, a brace, an
|
|
51
|
+
escaped quote or a newline. `read()` parses as empty arguments; `see figure(2)` does not parse at all,
|
|
52
|
+
because `figure` was never offered.
|
|
53
|
+
- **The JSON envelope is still accepted**, fenced or bare, as a fallback — a model may emit it unprompted
|
|
54
|
+
and refusing it would throw away a working call.
|
|
55
|
+
- **`stripToolCall` removes whichever shape was used**, so neither reaches the transcript.
|
|
56
|
+
- **`streamSafeCut` holds from a known name plus `(`, or from a trailing fragment of a known name of three
|
|
57
|
+
characters or more.** The first version held any fragment, so a sentence ending in `r` stalled one
|
|
58
|
+
character back — `r` prefixes `read`. Found by a test, fixed before shipping.
|
|
59
|
+
- **`dev/test-call-form.mjs`** pins ten shapes that are calls, five that are not, and the stripping and
|
|
60
|
+
streaming behaviour. Two of its own assertions were written from guesswork and failed; they now measure.
|
|
61
|
+
- **`dev/test-web-tool-write.mjs` asserts the shape**, not just the outcome: the raw reply is captured through
|
|
62
|
+
`UNCLE_SAM_RAW_REPLY` and must start with `name(`. Measured live: the page replied
|
|
63
|
+
`replace_note({"text":"…"})` and the file changed, **4,062 ms**.
|
|
64
|
+
|
|
65
|
+
The raw capture is off unless that variable names a file, so nothing is written during ordinary use.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
11
69
|
## 1.3.3
|
|
12
70
|
|
|
13
71
|
**The tool names were not enough: a call with the wrong argument name cannot be executed.**
|
package/README.ar.md
CHANGED
|
@@ -250,7 +250,7 @@ Copyright (c) 2026 Darren T. Weldon، منشور تحت اسم Operating Tag.
|
|
|
250
250
|
Uncle Sam إضافة مستقلة. غير مرتبطة بـ DeepSeek، ولا تحظى بتأييدهم أو دعمهم.
|
|
251
251
|
|
|
252
252
|
```
|
|
253
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
253
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
254
254
|
المؤلف Operating Tag
|
|
255
255
|
الترخيص CC BY-NC-ND 4.0 — مجاني للاستخدام، لا للتغيير أو المشاركة أو البيع
|
|
256
256
|
```
|
package/README.az.md
CHANGED
|
@@ -256,7 +256,7 @@ soruşun — aydın cavab və aydın qiymət alacaqsınız.
|
|
|
256
256
|
Uncle Sam müstəqil plagindir. DeepSeek ilə bağlı deyil, onlar tərəfindən təsdiqlənməyib və dəstəklənmir.
|
|
257
257
|
|
|
258
258
|
```
|
|
259
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
259
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
260
260
|
Müəllif Operating Tag
|
|
261
261
|
Lisenziya CC BY-NC-ND 4.0 — istifadə üçün pulsuz, dəyişdirmək, paylaşmaq və ya satmaq üçün deyil
|
|
262
262
|
```
|
package/README.bg.md
CHANGED
|
@@ -259,7 +259,7 @@ Copyright (c) 2026 Darren T. Weldon, публикувано под името Op
|
|
|
259
259
|
Uncle Sam е независима приставка. Не е свързана с DeepSeek, не е одобрена или поддържана от тях.
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
262
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
263
263
|
Автор Operating Tag
|
|
264
264
|
Лиценз CC BY-NC-ND 4.0 — свободно за използване, не за промяна, споделяне или продажба
|
|
265
265
|
```
|
package/README.bn.md
CHANGED
|
@@ -254,7 +254,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag নামে প্রকা
|
|
|
254
254
|
Uncle Sam একটি স্বাধীন প্লাগইন। DeepSeek-এর সঙ্গে যুক্ত নয়, তাদের অনুমোদন বা সমর্থন নেই।
|
|
255
255
|
|
|
256
256
|
```
|
|
257
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
257
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
258
258
|
লেখক Operating Tag
|
|
259
259
|
লাইসেন্স CC BY-NC-ND 4.0 — ব্যবহারে বিনামূল্যে, পরিবর্তন, শেয়ার বা বিক্রিতে নয়
|
|
260
260
|
```
|
package/README.cs.md
CHANGED
|
@@ -256,7 +256,7 @@ Závazný je soubor [LICENSE](LICENSE) a začíná stejným shrnutím v jednoduc
|
|
|
256
256
|
Uncle Sam je nezávislý plugin. Není spojený s DeepSeek, není jimi schválený ani podporovaný.
|
|
257
257
|
|
|
258
258
|
```
|
|
259
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
259
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
260
260
|
Autor Operating Tag
|
|
261
261
|
Licence CC BY-NC-ND 4.0 — zdarma k použití, ne ke změně, sdílení nebo prodeji
|
|
262
262
|
```
|
package/README.da.md
CHANGED
|
@@ -257,7 +257,7 @@ Filen [LICENSE](LICENSE) er den gældende, og den begynder med samme resumé på
|
|
|
257
257
|
Uncle Sam er et uafhængigt plugin. Ikke knyttet til DeepSeek, ikke godkendt eller støttet af dem.
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
Forfatter Operating Tag
|
|
262
262
|
Licens CC BY-NC-ND 4.0 — frit at bruge, ikke at ændre, dele eller sælge
|
|
263
263
|
```
|
package/README.de.md
CHANGED
|
@@ -270,7 +270,7 @@ Englisch.
|
|
|
270
270
|
Uncle Sam ist ein unabhängiges Plugin. Nicht mit DeepSeek verbunden, von ihnen weder unterstützt noch gefördert.
|
|
271
271
|
|
|
272
272
|
```
|
|
273
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
273
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
274
274
|
Autor Operating Tag
|
|
275
275
|
Lizenz CC BY-NC-ND 4.0 — frei zur Nutzung, nicht zum Ändern, Weitergabe oder Verkauf
|
|
276
276
|
```
|
package/README.el.md
CHANGED
|
@@ -266,7 +266,7 @@ Copyright (c) 2026 Darren T. Weldon, δημοσιευμένο με το όνομ
|
|
|
266
266
|
Το Uncle Sam είναι ανεξάρτητο πρόσθετο. Δεν συνδέεται με τη DeepSeek, δεν εγκρίνεται ούτε υποστηρίζεται από αυτούς.
|
|
267
267
|
|
|
268
268
|
```
|
|
269
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
269
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
270
270
|
Δημιουργός Operating Tag
|
|
271
271
|
Άδεια CC BY-NC-ND 4.0 — ελεύθερο για χρήση, όχι για αλλαγή, κοινή χρήση ή πώληση
|
|
272
272
|
```
|
package/README.es.md
CHANGED
|
@@ -264,7 +264,7 @@ El archivo [LICENSE](LICENSE) es el que rige, y abre con el mismo resumen en ing
|
|
|
264
264
|
Uncle Sam es un plugin independiente. No está afiliado, respaldado ni soportado por DeepSeek.
|
|
265
265
|
|
|
266
266
|
```
|
|
267
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
267
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
268
268
|
Autor Operating Tag
|
|
269
269
|
Licencia CC BY-NC-ND 4.0 — gratis para usar, no para cambiar, compartir o vender
|
|
270
270
|
```
|
package/README.fa.md
CHANGED
|
@@ -256,7 +256,7 @@ Copyright (c) 2026 Darren T. Weldon، منتشرشده با نام Operating Tag
|
|
|
256
256
|
Uncle Sam یک افزونه مستقل است. وابسته به DeepSeek نیست، تأیید یا پشتیبانی نمیشود.
|
|
257
257
|
|
|
258
258
|
```
|
|
259
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
259
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
260
260
|
نویسنده Operating Tag
|
|
261
261
|
پروانه CC BY-NC-ND 4.0 — آزاد برای استفاده، نه برای تغییر، اشتراک یا فروش
|
|
262
262
|
```
|
package/README.fi.md
CHANGED
|
@@ -258,7 +258,7 @@ Tiedosto [LICENSE](LICENSE) on ratkaiseva, ja se alkaa samalla yhteenvedolla yks
|
|
|
258
258
|
Uncle Sam on itsenäinen lisäosa. Ei sidoksissa DeepSeekiin, ei heidän hyväksymänsä tai tukemansa.
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
261
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
262
262
|
Tekijä Operating Tag
|
|
263
263
|
Lisenssi CC BY-NC-ND 4.0 — vapaa käytettäväksi, ei muutettavaksi, jaettavaksi tai myytäväksi
|
|
264
264
|
```
|
package/README.fr.md
CHANGED
|
@@ -268,7 +268,7 @@ Le fichier [LICENSE](LICENSE) fait foi, et il s'ouvre sur le même résumé en a
|
|
|
268
268
|
Uncle Sam est un plugin indépendant. Non affilié à DeepSeek, ni approuvé ni soutenu par eux.
|
|
269
269
|
|
|
270
270
|
```
|
|
271
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
271
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
272
272
|
Auteur Operating Tag
|
|
273
273
|
Licence CC BY-NC-ND 4.0 — gratuit à l'usage, pas au changement, au partage ou à la vente
|
|
274
274
|
```
|
package/README.he.md
CHANGED
|
@@ -248,7 +248,7 @@ Copyright (c) 2026 Darren T. Weldon, פורסם תחת השם Operating Tag.
|
|
|
248
248
|
Uncle Sam הוא תוסף עצמאי. לא קשור ל‑DeepSeek, לא מאושר ולא נתמך על ידם.
|
|
249
249
|
|
|
250
250
|
```
|
|
251
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
251
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
252
252
|
יוצר Operating Tag
|
|
253
253
|
רישיון CC BY-NC-ND 4.0 — חופשי לשימוש, לא לשינוי, שיתוף או מכירה
|
|
254
254
|
```
|
package/README.hi.md
CHANGED
|
@@ -259,7 +259,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag नाम से प्रक
|
|
|
259
259
|
Uncle Sam एक स्वतंत्र प्लगइन है। DeepSeek से जुड़ा नहीं, उनका समर्थन या अनुमोदन नहीं।
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
262
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
263
263
|
लेखक Operating Tag
|
|
264
264
|
लाइसेंस CC BY-NC-ND 4.0 — इस्तेमाल मुफ़्त, बदलना, बाँटना या बेचना नहीं
|
|
265
265
|
```
|
package/README.hr.md
CHANGED
|
@@ -256,7 +256,7 @@ Datoteka [LICENSE](LICENSE) je obvezujuća i počinje istim sažetkom na jednost
|
|
|
256
256
|
Uncle Sam je neovisni dodatak. Nije povezan s DeepSeekom, nije odobren ni podržan od njih.
|
|
257
257
|
|
|
258
258
|
```
|
|
259
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
259
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
260
260
|
Autor Operating Tag
|
|
261
261
|
Licencija CC BY-NC-ND 4.0 — besplatno za korištenje, ne za izmjenu, dijeljenje ili prodaju
|
|
262
262
|
```
|
package/README.hu.md
CHANGED
|
@@ -260,7 +260,7 @@ A [LICENSE](LICENSE) fájl a mérvadó, és ugyanazzal az egyszerű angol össze
|
|
|
260
260
|
Az Uncle Sam független bővítmény. Nem kapcsolódik a DeepSeekhez, ő nem hagyja jóvá és nem támogatja.
|
|
261
261
|
|
|
262
262
|
```
|
|
263
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
263
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
264
264
|
Szerző Operating Tag
|
|
265
265
|
Licenc CC BY-NC-ND 4.0 — szabadon használható, nem módosítható, osztható meg vagy adható el
|
|
266
266
|
```
|
package/README.id.md
CHANGED
|
@@ -260,7 +260,7 @@ Berkas [LICENSE](LICENSE) yang mengikat, dan ia dibuka dengan ringkasan yang sam
|
|
|
260
260
|
Uncle Sam adalah plugin independen. Tidak berafiliasi dengan DeepSeek, tidak didukung maupun disponsori oleh mereka.
|
|
261
261
|
|
|
262
262
|
```
|
|
263
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
263
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
264
264
|
Penulis Operating Tag
|
|
265
265
|
Lisensi CC BY-NC-ND 4.0 — bebas dipakai, tidak untuk diubah, dibagikan, atau dijual
|
|
266
266
|
```
|
package/README.it.md
CHANGED
|
@@ -264,7 +264,7 @@ Il file [LICENSE](LICENSE) è quello che fa fede, e si apre con lo stesso riassu
|
|
|
264
264
|
Uncle Sam è un plugin indipendente. Non affiliato a DeepSeek, né approvato né supportato da loro.
|
|
265
265
|
|
|
266
266
|
```
|
|
267
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
267
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
268
268
|
Autore Operating Tag
|
|
269
269
|
Licenza CC BY-NC-ND 4.0 — libero da usare, non da modificare, condividere o vendere
|
|
270
270
|
```
|
package/README.ja.md
CHANGED
|
@@ -259,7 +259,7 @@ Copyright (c) 2026 Darren T. Weldon、Operating Tag 名義で公開。
|
|
|
259
259
|
Uncle Sam は独立したプラグインです。DeepSeek とは関係がなく、承認も支援も受けていません。
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
262
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
263
263
|
作者 Operating Tag
|
|
264
264
|
ライセンス CC BY-NC-ND 4.0 — 使用は自由、変更・共有・販売は不可
|
|
265
265
|
```
|
package/README.ka.md
CHANGED
|
@@ -257,7 +257,7 @@ Copyright (c) 2026 Darren T. Weldon, გამოქვეყნებული
|
|
|
257
257
|
Uncle Sam დამოუკიდებელი დანამატია. არ არის დაკავშირებული DeepSeek-თან, მათ მიერ არ არის დამტკიცებული ან მხარდაჭერილი.
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
ავტორი Operating Tag
|
|
262
262
|
ლიცენზია CC BY-NC-ND 4.0 — თავისუფალი გამოსაყენებლად, არა შესაცვლელად, გასაზიარებლად ან გასაყიდად
|
|
263
263
|
```
|
package/README.kk.md
CHANGED
|
@@ -258,7 +258,7 @@ Operating Tag-тан сұраңыз — нақты жауап және нақт
|
|
|
258
258
|
Uncle Sam — тәуелсіз плагин. DeepSeek-пен байланысты емес, олар мақұлдамаған немесе қолдамаған.
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
261
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
262
262
|
Авторы Operating Tag
|
|
263
263
|
Лицензия CC BY-NC-ND 4.0 — пайдалануға тегін, өзгертуге, бөлісуге немесе сатуға емес
|
|
264
264
|
```
|
package/README.km.md
CHANGED
|
@@ -253,7 +253,7 @@ Tag — អ្នកនឹងទទួលបានចម្លើយច្បា
|
|
|
253
253
|
Uncle Sam គឺជាកម្មវិធីបន្ថែមឯករាជ្យ។ មិនពាក់ព័ន្ធនឹង DeepSeek មិនត្រូវបានអនុម័ត ឬគាំទ្រដោយពួកគេ។
|
|
254
254
|
|
|
255
255
|
```
|
|
256
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
256
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
257
257
|
អ្នកនិពន្ធ Operating Tag
|
|
258
258
|
អាជ្ញាបណ្ណ CC BY-NC-ND 4.0 — ប្រើឥតគិតថ្លៃ មិនមែនសម្រាប់កែប្រែ ចែករំលែក ឬលក់
|
|
259
259
|
```
|
package/README.ko.md
CHANGED
|
@@ -254,7 +254,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag 이름으로 공개.
|
|
|
254
254
|
Uncle Sam은 독립적인 플러그인입니다. DeepSeek과 무관하며, 그들의 보증이나 지원을 받지 않습니다.
|
|
255
255
|
|
|
256
256
|
```
|
|
257
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
257
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
258
258
|
저자 Operating Tag
|
|
259
259
|
라이선스 CC BY-NC-ND 4.0 — 사용은 자유, 변경·공유·판매는 불가
|
|
260
260
|
```
|
package/README.lt.md
CHANGED
|
@@ -257,7 +257,7 @@ Failas [LICENSE](LICENSE) yra privalomas, ir jis prasideda ta pačia santrauka p
|
|
|
257
257
|
Uncle Sam yra nepriklausomas papildinys. Nesusijęs su DeepSeek, jų nepatvirtintas ir neremiamas.
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
Autorius Operating Tag
|
|
262
262
|
Licencija CC BY-NC-ND 4.0 — nemokama naudoti, ne keisti, bendrinti ar parduoti
|
|
263
263
|
```
|
package/README.md
CHANGED
|
@@ -257,7 +257,7 @@ The [LICENSE](LICENSE) file governs, and it opens with the same summary in plain
|
|
|
257
257
|
Uncle Sam is an independent plugin. Not affiliated with, endorsed by, or supported by DeepSeek.
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
Author Operating Tag
|
|
262
262
|
Licence CC BY-NC-ND 4.0 — free to use, not to change, share or sell
|
|
263
263
|
```
|
package/README.mr.md
CHANGED
|
@@ -254,7 +254,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag नावाने प्र
|
|
|
254
254
|
Uncle Sam एक स्वतंत्र प्लगइन आहे. DeepSeek शी संबंधित नाही, त्यांची मान्यता किंवा समर्थन नाही.
|
|
255
255
|
|
|
256
256
|
```
|
|
257
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
257
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
258
258
|
लेखक Operating Tag
|
|
259
259
|
परवाना CC BY-NC-ND 4.0 — वापरण्यास मोफत, बदलण्यास, सामायिक करण्यास किंवा विकण्यास नाही
|
|
260
260
|
```
|
package/README.ms.md
CHANGED
|
@@ -261,7 +261,7 @@ mudah.
|
|
|
261
261
|
Uncle Sam ialah pemalam bebas. Tidak berkaitan dengan DeepSeek, tidak disokong atau ditaja oleh mereka.
|
|
262
262
|
|
|
263
263
|
```
|
|
264
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
264
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
265
265
|
Penulis Operating Tag
|
|
266
266
|
Lesen CC BY-NC-ND 4.0 — bebas digunakan, bukan untuk diubah, dikongsi atau dijual
|
|
267
267
|
```
|
package/README.my.md
CHANGED
|
@@ -262,7 +262,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag အမည်ဖြင့်
|
|
|
262
262
|
Uncle Sam သည် သီးခြားလွတ်လပ်သော plugin ဖြစ်သည်။ DeepSeek နှင့် မသက်ဆိုင်ပါ၊ ၎င်းတို့က အတည်မပြု၊ မပံ့ပိုးပါ။
|
|
263
263
|
|
|
264
264
|
```
|
|
265
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
265
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
266
266
|
စာရေးသူ Operating Tag
|
|
267
267
|
လိုင်စင် CC BY-NC-ND 4.0 — အသုံးပြုရန် အခမဲ့၊ ပြောင်းလဲရန်၊ မျှဝေရန် သို့မဟုတ် ရောင်းရန် မဟုတ်
|
|
268
268
|
```
|
package/README.ne.md
CHANGED
|
@@ -254,7 +254,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag नाममा प्रक
|
|
|
254
254
|
Uncle Sam स्वतन्त्र प्लगइन हो। DeepSeek सँग सम्बन्धित छैन, उनीहरूको स्वीकृति वा समर्थन छैन।
|
|
255
255
|
|
|
256
256
|
```
|
|
257
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
257
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
258
258
|
लेखक Operating Tag
|
|
259
259
|
इजाजतपत्र CC BY-NC-ND 4.0 — प्रयोग गर्न निःशुल्क, परिवर्तन, बाँड्न वा बेच्न होइन
|
|
260
260
|
```
|
package/README.nl.md
CHANGED
|
@@ -262,7 +262,7 @@ Het bestand [LICENSE](LICENSE) is bindend, en het begint met dezelfde samenvatti
|
|
|
262
262
|
Uncle Sam is een onafhankelijke plugin. Niet verbonden met DeepSeek, door hen niet goedgekeurd of ondersteund.
|
|
263
263
|
|
|
264
264
|
```
|
|
265
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
265
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
266
266
|
Auteur Operating Tag
|
|
267
267
|
Licentie CC BY-NC-ND 4.0 — vrij om te gebruiken, niet om te wijzigen, delen of verkopen
|
|
268
268
|
```
|
package/README.no.md
CHANGED
|
@@ -257,7 +257,7 @@ Filen [LICENSE](LICENSE) er den gjeldende, og den begynner med det samme sammend
|
|
|
257
257
|
Uncle Sam er en uavhengig utvidelse. Ikke knyttet til DeepSeek, ikke godkjent eller støttet av dem.
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
Forfatter Operating Tag
|
|
262
262
|
Lisens CC BY-NC-ND 4.0 — fritt å bruke, ikke å endre, dele eller selge
|
|
263
263
|
```
|
package/README.pl.md
CHANGED
|
@@ -262,7 +262,7 @@ Wiążący jest plik [LICENSE](LICENSE), a otwiera się tym samym podsumowaniem
|
|
|
262
262
|
Uncle Sam to niezależna wtyczka. Niepowiązana z DeepSeek, nie firmowana ani nie wspierana przez nich.
|
|
263
263
|
|
|
264
264
|
```
|
|
265
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
265
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
266
266
|
Autor Operating Tag
|
|
267
267
|
Licencja CC BY-NC-ND 4.0 — wolno używać, nie wolno zmieniać, udostępniać ani sprzedawać
|
|
268
268
|
```
|
package/README.pt.md
CHANGED
|
@@ -261,7 +261,7 @@ O arquivo [LICENSE](LICENSE) é o que vale, e ele abre com o mesmo resumo em ing
|
|
|
261
261
|
O Uncle Sam é um plugin independente. Não afiliado ao DeepSeek, nem endossado nem apoiado por ele.
|
|
262
262
|
|
|
263
263
|
```
|
|
264
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
264
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
265
265
|
Autor Operating Tag
|
|
266
266
|
Licença CC BY-NC-ND 4.0 — livre para usar, não para alterar, compartilhar ou vender
|
|
267
267
|
```
|
package/README.ro.md
CHANGED
|
@@ -258,7 +258,7 @@ Fișierul [LICENSE](LICENSE) este cel care guvernează și începe cu același r
|
|
|
258
258
|
Uncle Sam este un plugin independent. Neafiliat cu DeepSeek, neaprobat și nesprijinit de ei.
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
261
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
262
262
|
Autor Operating Tag
|
|
263
263
|
Licență CC BY-NC-ND 4.0 — liber de folosit, nu de modificat, partajat sau vândut
|
|
264
264
|
```
|
package/README.ru.md
CHANGED
|
@@ -265,7 +265,7 @@ International. Copyright (c) 2026 Darren T. Weldon, опубликовано п
|
|
|
265
265
|
Uncle Sam — независимый плагин. Не связан с DeepSeek, не одобрен и не поддерживается им.
|
|
266
266
|
|
|
267
267
|
```
|
|
268
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
268
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
269
269
|
Автор Operating Tag
|
|
270
270
|
Лицензия CC BY-NC-ND 4.0 — бесплатно для использования, не для изменения, передачи или продажи
|
|
271
271
|
```
|
package/README.si.md
CHANGED
|
@@ -255,7 +255,7 @@ Copyright (c) 2026 Darren T. Weldon, Operating Tag නාමයෙන් ප්
|
|
|
255
255
|
Uncle Sam ස්වාධීන ප්ලගීනයකි. DeepSeek සමඟ සම්බන්ධ නැත, ඔවුන් අනුමත හෝ අනුග්රහය නොදක්වයි.
|
|
256
256
|
|
|
257
257
|
```
|
|
258
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
258
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
259
259
|
කතෘ Operating Tag
|
|
260
260
|
බලපත්රය CC BY-NC-ND 4.0 — භාවිතයට නොමිලේ, වෙනස් කිරීමට, බෙදා ගැනීමට හෝ විකිණීමට නොවේ
|
|
261
261
|
```
|
package/README.sk.md
CHANGED
|
@@ -257,7 +257,7 @@ Záväzný je súbor [LICENSE](LICENSE) a začína rovnakým zhrnutím v jednodu
|
|
|
257
257
|
Uncle Sam je nezávislý doplnok. Nie je spojený s DeepSeek, nie je nimi schválený ani podporovaný.
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
Autor Operating Tag
|
|
262
262
|
Licencia CC BY-NC-ND 4.0 — zadarmo na používanie, nie na zmenu, zdieľanie alebo predaj
|
|
263
263
|
```
|
package/README.sl.md
CHANGED
|
@@ -258,7 +258,7 @@ Zavezujoča je datoteka [LICENSE](LICENSE) in se začne z istim povzetkom v prep
|
|
|
258
258
|
Uncle Sam je neodvisen vtičnik. Ni povezan z DeepSeek, ni odobren ali podprt z njihove strani.
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
261
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
262
262
|
Avtor Operating Tag
|
|
263
263
|
Licenca CC BY-NC-ND 4.0 — brezplačno za uporabo, ne za spreminjanje, deljenje ali prodajo
|
|
264
264
|
```
|
package/README.sr.md
CHANGED
|
@@ -258,7 +258,7 @@ Copyright (c) 2026 Darren T. Weldon, објављено под именом Oper
|
|
|
258
258
|
Uncle Sam је независни додатак. Није повезан са DeepSeek, није одобрен ни подржан од њих.
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
261
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
262
262
|
Аутор Operating Tag
|
|
263
263
|
Лиценца CC BY-NC-ND 4.0 — бесплатно за употребу, не за измену, дељење или продају
|
|
264
264
|
```
|
package/README.sv.md
CHANGED
|
@@ -259,7 +259,7 @@ Filen [LICENSE](LICENSE) är den som gäller, och den börjar med samma sammanfa
|
|
|
259
259
|
Uncle Sam är ett fristående insticksprogram. Inte knutet till DeepSeek, inte godkänt eller stött av dem.
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
262
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
263
263
|
Författare Operating Tag
|
|
264
264
|
Licens CC BY-NC-ND 4.0 — fritt att använda, inte att ändra, dela eller sälja
|
|
265
265
|
```
|
package/README.sw.md
CHANGED
|
@@ -259,7 +259,7 @@ Faili [LICENSE](LICENSE) ndiyo inayotumika, na inaanza na muhtasari huo huo kwa
|
|
|
259
259
|
Uncle Sam ni programu-jalizi huru. Haijahusishwa na DeepSeek, haijaidhinishwa wala kuungwa mkono nao.
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
262
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
263
263
|
Mwandishi Operating Tag
|
|
264
264
|
Leseni CC BY-NC-ND 4.0 — huru kutumika, si kubadilisha, kushiriki au kuuza
|
|
265
265
|
```
|
package/README.ta.md
CHANGED
|
@@ -262,7 +262,7 @@ Tag ஐக் கேளுங்கள் — தெளிவான பதில
|
|
|
262
262
|
Uncle Sam ஒரு சுயாதீனச் செருகல். DeepSeek உடன் தொடர்பில்லை, அவர்களின் ஒப்புதல் அல்லது ஆதரவு இல்லை.
|
|
263
263
|
|
|
264
264
|
```
|
|
265
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
265
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
266
266
|
ஆசிரியர் Operating Tag
|
|
267
267
|
உரிமம் CC BY-NC-ND 4.0 — பயன்படுத்த இலவசம், மாற்ற, பகிர அல்லது விற்க அல்ல
|
|
268
268
|
```
|
package/README.te.md
CHANGED
|
@@ -256,7 +256,7 @@ Tag ను అడగండి — స్పష్టమైన సమాధాన
|
|
|
256
256
|
Uncle Sam స్వతంత్ర ప్లగిన్. DeepSeek తో సంబంధం లేదు, వారి ఆమోదం లేదా మద్దతు లేదు.
|
|
257
257
|
|
|
258
258
|
```
|
|
259
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
259
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
260
260
|
రచయిత Operating Tag
|
|
261
261
|
లైసెన్స్ CC BY-NC-ND 4.0 — వాడటానికి ఉచితం, మార్చడానికి, పంచుకోవడానికి లేదా అమ్మడానికి కాదు
|
|
262
262
|
```
|
package/README.th.md
CHANGED
|
@@ -252,7 +252,7 @@ Operating Tag แล้วคุณจะได้คำตอบที่ชั
|
|
|
252
252
|
Uncle Sam เป็นปลั๊กอินอิสระ ไม่เกี่ยวข้องกับ DeepSeek ไม่ได้รับการรับรองหรือสนับสนุนจากพวกเขา
|
|
253
253
|
|
|
254
254
|
```
|
|
255
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
255
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
256
256
|
ผู้เขียน Operating Tag
|
|
257
257
|
สัญญาอนุญาต CC BY-NC-ND 4.0 — ใช้ฟรี ไม่ใช่เพื่อดัดแปลง แบ่งปัน หรือขาย
|
|
258
258
|
```
|
package/README.tl.md
CHANGED
|
@@ -265,7 +265,7 @@ Ang file na [LICENSE](LICENSE) ang namamahala, at nagsisimula ito sa parehong bu
|
|
|
265
265
|
Ang Uncle Sam ay isang malayang plugin. Hindi kaanib sa DeepSeek, hindi inendorso o sinusuportahan nila.
|
|
266
266
|
|
|
267
267
|
```
|
|
268
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
268
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
269
269
|
May-akda Operating Tag
|
|
270
270
|
Lisensya CC BY-NC-ND 4.0 — libre gamitin, hindi para baguhin, ibahagi o ibenta
|
|
271
271
|
```
|
package/README.tr.md
CHANGED
|
@@ -261,7 +261,7 @@ Uncle Sam bağımsız bir eklentidir. DeepSeek ile bağlantılı değildir, onla
|
|
|
261
261
|
desteklenmemiştir.
|
|
262
262
|
|
|
263
263
|
```
|
|
264
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
264
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
265
265
|
Yazar Operating Tag
|
|
266
266
|
Lisans CC BY-NC-ND 4.0 — kullanması serbest, değiştirmesi, paylaşması veya satması değil
|
|
267
267
|
```
|
package/README.uk.md
CHANGED
|
@@ -261,7 +261,7 @@ International. Copyright (c) 2026 Darren T. Weldon, опубліковано п
|
|
|
261
261
|
Uncle Sam — незалежний плагін. Не пов'язаний із DeepSeek, не схвалений і не підтриманий ними.
|
|
262
262
|
|
|
263
263
|
```
|
|
264
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
264
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
265
265
|
Автор Operating Tag
|
|
266
266
|
Ліцензія CC BY-NC-ND 4.0 — вільно використовувати, не змінювати, передавати чи продавати
|
|
267
267
|
```
|
package/README.ur.md
CHANGED
|
@@ -257,7 +257,7 @@ Copyright (c) 2026 Darren T. Weldon، Operating Tag نام سے شائع شدہ
|
|
|
257
257
|
Uncle Sam ایک خودمختار پلگ ان ہے۔ DeepSeek سے وابستہ نہیں، ان کی توثیق یا معاونت حاصل نہیں۔
|
|
258
258
|
|
|
259
259
|
```
|
|
260
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
260
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
261
261
|
مصنف Operating Tag
|
|
262
262
|
لائسنس CC BY-NC-ND 4.0 — استعمال کے لیے مفت، تبدیلی، اشتراک یا فروخت کے لیے نہیں
|
|
263
263
|
```
|
package/README.uz.md
CHANGED
|
@@ -258,7 +258,7 @@ Tag dan so'rang — aniq javob va aniq narx olasiz.
|
|
|
258
258
|
Uncle Sam — mustaqil plagin. DeepSeek bilan bog'liq emas, ular tomonidan ma'qullanmagan yoki qo'llab-quvvatlanmagan.
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
261
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
262
262
|
Muallif Operating Tag
|
|
263
263
|
Litsenziya CC BY-NC-ND 4.0 — foydalanish uchun bepul, o'zgartirish, ulashish yoki sotish uchun emas
|
|
264
264
|
```
|
package/README.vi.md
CHANGED
|
@@ -259,7 +259,7 @@ Tệp [LICENSE](LICENSE) là văn bản có hiệu lực, và nó mở đầu b
|
|
|
259
259
|
Uncle Sam là một plugin độc lập. Không liên kết với DeepSeek, không được họ chứng thực hay hỗ trợ.
|
|
260
260
|
|
|
261
261
|
```
|
|
262
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
262
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
263
263
|
Tác giả Operating Tag
|
|
264
264
|
Giấy phép CC BY-NC-ND 4.0 — miễn phí để dùng, không được sửa, chia sẻ hay bán
|
|
265
265
|
```
|
package/README.zh.md
CHANGED
|
@@ -244,7 +244,7 @@ Copyright (c) 2026 Darren T. Weldon,以 Operating Tag 名义发布。
|
|
|
244
244
|
Uncle Sam 是一个独立插件。与 DeepSeek 无关联,未获其背书,也未获其支持。
|
|
245
245
|
|
|
246
246
|
```
|
|
247
|
-
@operatingtag/dsh-unclesam v1.3.
|
|
247
|
+
@operatingtag/dsh-unclesam v1.3.5
|
|
248
248
|
作者 Operating Tag
|
|
249
249
|
许可 CC BY-NC-ND 4.0 —— 可免费使用,不可修改、共享或出售
|
|
250
250
|
```
|
package/index.js
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* prose instead of a call, and the harness sees no tool use that turn.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import {
|
|
31
|
-
import { buildPrompt, messageText, parseToolCall, stripToolCall, streamSafeCut } from './lib/prompt.mjs';
|
|
30
|
+
import { writeFileSync } from 'node:fs';
|
|
31
|
+
import { ChatSession, WebChatError } from './lib/engine.mjs';import { buildPrompt, looksDegenerate, messageText, parseToolCall, stripToolCall, streamSafeCut } from './lib/prompt.mjs';
|
|
32
32
|
import { SavingsMeter, estimateTokens as estimateTokensForSavings } from './lib/savings.mjs';
|
|
33
33
|
import { Locales } from './lib/i18n.mjs';
|
|
34
34
|
import { Prefs, prefsPath, profileDir } from './lib/prefs.mjs';
|
|
@@ -457,6 +457,16 @@ export function apply(ctx, config = {}) {
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
const wantTools = Array.isArray(options.tools) && options.tools.length > 0;
|
|
460
|
+
/*
|
|
461
|
+
* The names this turn offered, exactly as the page was told them.
|
|
462
|
+
*
|
|
463
|
+
* `name({...})` is only a call when `name` is one of these - that is what makes the form unambiguous.
|
|
464
|
+
* Without the list, prose like `see figure(2)` would look like a call.
|
|
465
|
+
*/
|
|
466
|
+
const toolNames = wantTools ? options.tools.map((t) => t?.name).filter(Boolean) : [];
|
|
467
|
+
|
|
468
|
+
/** Set when the reply collapsed into repetition; the turn then ends with an error, not garbage. */
|
|
469
|
+
let stoppedAsLoop = false;
|
|
460
470
|
const switches = switchesFor(model, settings);
|
|
461
471
|
// A search answer is delivered as two blocks: what was searched and where it looked, then the
|
|
462
472
|
// answer. That means the text cannot stream while search is on - the sources are only readable
|
|
@@ -556,11 +566,44 @@ export function apply(ctx, config = {}) {
|
|
|
556
566
|
if (chunk.done) {
|
|
557
567
|
assembled = chunk.text;
|
|
558
568
|
if (chunk.sources) sources = chunk.sources;
|
|
569
|
+
/*
|
|
570
|
+
* Optional raw capture, for tests that need to see the reply AS THE PAGE WROTE IT.
|
|
571
|
+
*
|
|
572
|
+
* A recovered call proves the parser worked, not which shape the model chose - and the shape is
|
|
573
|
+
* the thing being changed when the call format changes. Writing the raw reply to a path the test
|
|
574
|
+
* names turns "the call parsed" into "the model emitted this, character for character".
|
|
575
|
+
*
|
|
576
|
+
* Off unless UNCLE_SAM_RAW_REPLY names a file, so nothing is written during ordinary use.
|
|
577
|
+
*/
|
|
578
|
+
if (process.env.UNCLE_SAM_RAW_REPLY) {
|
|
579
|
+
try {
|
|
580
|
+
writeFileSync(process.env.UNCLE_SAM_RAW_REPLY, chunk.text ?? '', 'utf8');
|
|
581
|
+
} catch {
|
|
582
|
+
/* a diagnostic must never break the turn */
|
|
583
|
+
}
|
|
584
|
+
}
|
|
559
585
|
continue;
|
|
560
586
|
}
|
|
561
587
|
if (!chunk.text) continue;
|
|
562
588
|
assembled += chunk.text;
|
|
563
589
|
|
|
590
|
+
/*
|
|
591
|
+
* Stop a reply that has come apart into a loop.
|
|
592
|
+
*
|
|
593
|
+
* MEASURED, live: a web turn emitted the same malformed `<tool …>` block hundreds of times and the
|
|
594
|
+
* adapter streamed every copy, so the person watched a page write one line for minutes and
|
|
595
|
+
* reasonably concluded the plugin was broken. Nothing was wrong with the page — the model had
|
|
596
|
+
* invented a call format, the call never ran, and nothing told it so.
|
|
597
|
+
*
|
|
598
|
+
* The loop is now cut at the source: the turn ends with a plain error that says what happened,
|
|
599
|
+
* instead of displaying the wreckage. Only checked when the new text could contribute, so ordinary
|
|
600
|
+
* turns pay nothing for it.
|
|
601
|
+
*/
|
|
602
|
+
if (/<tool/i.test(chunk.text) && looksDegenerate(assembled)) {
|
|
603
|
+
stoppedAsLoop = true;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
|
|
564
607
|
if (wantSearch) {
|
|
565
608
|
// held for ordering; nothing is emitted until the sources are known
|
|
566
609
|
continue;
|
|
@@ -573,7 +616,7 @@ export function apply(ctx, config = {}) {
|
|
|
573
616
|
* grow into {"tool":...}. Ordinary prose passes straight through, braces included, so the stream
|
|
574
617
|
* stays live; it simply stops one brace early when a call might be starting.
|
|
575
618
|
*/
|
|
576
|
-
const safe = streamSafeCut(assembled);
|
|
619
|
+
const safe = streamSafeCut(assembled, toolNames);
|
|
577
620
|
if (safe > shown) {
|
|
578
621
|
const out = assembled.slice(shown, safe);
|
|
579
622
|
shown = safe;
|
|
@@ -589,6 +632,29 @@ export function apply(ctx, config = {}) {
|
|
|
589
632
|
|
|
590
633
|
// Search answers are emitted as two visible blocks so the transcript shows BOTH what was asked
|
|
591
634
|
// of the web and what came back, before the answer that used it.
|
|
635
|
+
/*
|
|
636
|
+
* A reply that collapsed into repetition ends here, with an error that says so.
|
|
637
|
+
*
|
|
638
|
+
* The alternative - what used to happen - is that the loop is streamed to the transcript in full,
|
|
639
|
+
* which reads as the plugin having broken when in fact the model is stuck. Naming it is the whole
|
|
640
|
+
* point: the person can see that the page looped, not that their harness failed.
|
|
641
|
+
*/
|
|
642
|
+
if (stoppedAsLoop) {
|
|
643
|
+
yield {
|
|
644
|
+
type: 'finish',
|
|
645
|
+
reason: {
|
|
646
|
+
kind: 'error',
|
|
647
|
+
failure: new WebChatError(
|
|
648
|
+
'The web reply collapsed into repetition - the same block written over and over - so the ' +
|
|
649
|
+
'turn was stopped. Nothing was lost: send the message again, or switch to an API model ' +
|
|
650
|
+
'for this one.',
|
|
651
|
+
'web_loop',
|
|
652
|
+
),
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
592
658
|
if (wantSearch) {
|
|
593
659
|
/*
|
|
594
660
|
* The query is the person's newest message - and ONLY the person's.
|
|
@@ -634,7 +700,7 @@ export function apply(ctx, config = {}) {
|
|
|
634
700
|
yield { type: 'text-delta', index: 0, text: summaryWithLabel };
|
|
635
701
|
yield { type: 'block-end', index: 0, block: { type: 'text', text: summaryWithLabel } };
|
|
636
702
|
|
|
637
|
-
const answerText = stripToolCall(assembled);
|
|
703
|
+
const answerText = stripToolCall(assembled, toolNames);
|
|
638
704
|
yield { type: 'block-start', index: 1, blockType: 'text' };
|
|
639
705
|
if (answerText) yield { type: 'text-delta', index: 1, text: answerText };
|
|
640
706
|
yield { type: 'block-end', index: 1, block: { type: 'text', text: answerText } };
|
|
@@ -645,7 +711,7 @@ export function apply(ctx, config = {}) {
|
|
|
645
711
|
* block is emitted after the answer, exactly as the plain path does.
|
|
646
712
|
*/
|
|
647
713
|
if (wantTools) {
|
|
648
|
-
const call = parseToolCall(assembled);
|
|
714
|
+
const call = parseToolCall(assembled, toolNames);
|
|
649
715
|
if (call) {
|
|
650
716
|
const callId = `webcall_${Date.now().toString(36)}`;
|
|
651
717
|
yield { type: 'block-start', index: 2, blockType: 'tool-call' };
|
|
@@ -689,7 +755,7 @@ export function apply(ctx, config = {}) {
|
|
|
689
755
|
}
|
|
690
756
|
|
|
691
757
|
if (wantTools) {
|
|
692
|
-
const call = parseToolCall(assembled);
|
|
758
|
+
const call = parseToolCall(assembled, toolNames);
|
|
693
759
|
if (call) {
|
|
694
760
|
// A tool call was made. Any prose the model wrapped around it belongs in the text block;
|
|
695
761
|
// the JSON itself must not reach the transcript.
|
|
@@ -732,7 +798,7 @@ export function apply(ctx, config = {}) {
|
|
|
732
798
|
emitted += WEB_LABEL;
|
|
733
799
|
yield { type: 'text-delta', index: 0, text: WEB_LABEL };
|
|
734
800
|
}
|
|
735
|
-
const tail = stripToolCall(assembled.slice(shown));
|
|
801
|
+
const tail = stripToolCall(assembled.slice(shown), toolNames);
|
|
736
802
|
if (tail) {
|
|
737
803
|
emitted += tail;
|
|
738
804
|
yield { type: 'text-delta', index: 0, text: tail };
|
|
@@ -774,7 +840,7 @@ export function apply(ctx, config = {}) {
|
|
|
774
840
|
* scanner stopped one brace short of any. Scrubbing the tail and appending it keeps the committed
|
|
775
841
|
* block equal to the deltas that were yielded, which the harness checks.
|
|
776
842
|
*/
|
|
777
|
-
const held = stripToolCall(assembled.slice(shown));
|
|
843
|
+
const held = stripToolCall(assembled.slice(shown), toolNames);
|
|
778
844
|
if (held) {
|
|
779
845
|
if (!emitted) {
|
|
780
846
|
emitted += WEB_LABEL;
|
package/lib/prompt.mjs
CHANGED
|
@@ -161,7 +161,7 @@ export function toolPreamble(tools) {
|
|
|
161
161
|
* emulation exists to work around.
|
|
162
162
|
*/
|
|
163
163
|
const HEAD = 'Tools: ';
|
|
164
|
-
const TAIL = ' · to call one reply only with {"
|
|
164
|
+
const TAIL = ' · to call one reply only with name({"arg":"value"})';
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
167
|
* Room for the list.
|
|
@@ -393,10 +393,214 @@ function finish(text, expectsToolCall, continuing) {
|
|
|
393
393
|
* that merely CONTAINS the word tool but no valid object is treated as prose, because treating it as a
|
|
394
394
|
* malformed call would throw away a perfectly good answer.
|
|
395
395
|
*/
|
|
396
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Detect a reply that has come apart into repetition.
|
|
398
|
+
*
|
|
399
|
+
* MEASURED, live: asked to glob a workspace it had misread, the web model emitted the same malformed block
|
|
400
|
+
* — `<tool name="glob" string="true">{…}</tool>` — over and over, filling the transcript with hundreds of
|
|
401
|
+
* copies. The adapter streamed every one of them, because nothing was looking for that shape of failure.
|
|
402
|
+
*
|
|
403
|
+
* A format mismatch is recoverable: the call does not run and the model is told. A LOOP is not, because the
|
|
404
|
+
* person sits watching a page write the same line for minutes and concludes the plugin is broken.
|
|
405
|
+
*
|
|
406
|
+
* WHAT COUNTS AS DEGENERATE
|
|
407
|
+
* Four identical substantial lines, or three `<tool` openings. Both are far beyond anything a real answer
|
|
408
|
+
* does; an answer that repeats a sentence twice is fine and stays fine.
|
|
409
|
+
*/
|
|
410
|
+
export function looksDegenerate(reply) {
|
|
411
|
+
const s = String(reply ?? '');
|
|
412
|
+
|
|
413
|
+
/*
|
|
414
|
+
* Tag stacking first, and without a length floor.
|
|
415
|
+
*
|
|
416
|
+
* The floor exists so a short answer cannot be judged on a handful of lines. It must not gate this check:
|
|
417
|
+
* three `<tool` openings is three calls attempted in one reply, which is wrong however short the text is.
|
|
418
|
+
* Measured: the first version returned false for a three-tag reply because it was under 200 characters -
|
|
419
|
+
* the guard missed exactly the shape it was written for.
|
|
420
|
+
*/
|
|
421
|
+
const tags = s.match(/<tool\b/gi);
|
|
422
|
+
if (tags && tags.length >= 3) return true;
|
|
423
|
+
|
|
424
|
+
/*
|
|
425
|
+
* No length floor on the line check either.
|
|
426
|
+
*
|
|
427
|
+
* It was there to stop a short answer being judged on a few lines, and it was wrong twice: a five-line
|
|
428
|
+
* loop sat under the floor and went undetected, which is precisely the case the guard exists for. Four
|
|
429
|
+
* identical lines of twenty characters or more is already far outside ordinary prose on its own.
|
|
430
|
+
*/
|
|
431
|
+
const lines = s.split('\n').map((l) => l.trim()).filter((l) => l.length >= 20);
|
|
432
|
+
const counts = new Map();
|
|
433
|
+
for (const line of lines) counts.set(line, (counts.get(line) || 0) + 1);
|
|
434
|
+
for (const n of counts.values()) if (n >= 4) return true;
|
|
435
|
+
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Find `name({...})` in a reply, where `name` is one of the tools that was offered.
|
|
441
|
+
*
|
|
442
|
+
* WHY THIS FORM IS PREFERRED OVER JSON
|
|
443
|
+
* MEASURED: a reply that is nothing but `{"tool":…}` cannot be shown while it is written, so the harness
|
|
444
|
+
* sits empty for the whole generation and looks stuck. And a brace is the commonest punctuation in
|
|
445
|
+
* technical prose, so `{` is a poor delimiter - it both blocks streaming and invites false positives.
|
|
446
|
+
*
|
|
447
|
+
* `read({"path":"x"})` needs no instruction, because it is the shape models already produce, and it is
|
|
448
|
+
* unambiguous once it is anchored on a tool name the model was actually given. `read(` when `read` is in
|
|
449
|
+
* the offered list is a far stronger signal than `{`.
|
|
450
|
+
*
|
|
451
|
+
* WHAT IT DOES NOT DO
|
|
452
|
+
* It does not JSON.parse inside strings or respect nesting by regex - it walks the characters, because an
|
|
453
|
+
* argument may contain a parenthesis or a brace inside a string and a regex would stop there.
|
|
454
|
+
*/
|
|
455
|
+
function findCallForm(text, names) {
|
|
456
|
+
/*
|
|
457
|
+
* `hasList` distinguishes "no list supplied" from "an empty list supplied", and that distinction is the
|
|
458
|
+
* whole of two bugs.
|
|
459
|
+
*
|
|
460
|
+
* An EMPTY list means "no tools are offered", so nothing can be a call - and a word followed by parentheses
|
|
461
|
+
* in ordinary prose, like `log(2)` or `figure(2)`, must be left alone. The old code collapsed both cases
|
|
462
|
+
* into `null` ("accept anything"), so a turn with no tools deleted such prose from the answer.
|
|
463
|
+
*/
|
|
464
|
+
const hasList = Array.isArray(names);
|
|
465
|
+
|
|
466
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
467
|
+
if (!/[A-Za-z_]/.test(text[i])) continue;
|
|
468
|
+
|
|
469
|
+
// Read the identifier at this position.
|
|
470
|
+
let j = i;
|
|
471
|
+
let word = '';
|
|
472
|
+
while (j < text.length && /[A-Za-z0-9_.-]/.test(text[j])) {
|
|
473
|
+
word += text[j];
|
|
474
|
+
j += 1;
|
|
475
|
+
}
|
|
476
|
+
if (!word) continue;
|
|
477
|
+
if (hasList && !names.includes(word)) {
|
|
478
|
+
i = j - 1;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Optional whitespace, then a parenthesis.
|
|
483
|
+
let k = j;
|
|
484
|
+
while (k < text.length && (text[k] === ' ' || text[k] === '\t' || text[k] === '\n')) k += 1;
|
|
485
|
+
if (text[k] !== '(') {
|
|
486
|
+
i = j - 1;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Walk to the matching parenthesis, respecting strings and escapes.
|
|
491
|
+
let depth = 0;
|
|
492
|
+
let inString = false;
|
|
493
|
+
let escaped = false;
|
|
494
|
+
let end = -1;
|
|
495
|
+
for (let p = k; p < text.length; p += 1) {
|
|
496
|
+
const ch = text[p];
|
|
497
|
+
if (inString) {
|
|
498
|
+
if (escaped) escaped = false;
|
|
499
|
+
else if (ch === '\\') escaped = true;
|
|
500
|
+
else if (ch === '"') inString = false;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (ch === '"') inString = true;
|
|
504
|
+
else if (ch === '(') depth += 1;
|
|
505
|
+
else if (ch === ')') {
|
|
506
|
+
depth -= 1;
|
|
507
|
+
if (depth === 0) {
|
|
508
|
+
end = p;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (end === -1) return null; // still being written - no complete call yet
|
|
514
|
+
|
|
515
|
+
const inside = text.slice(k + 1, end).trim();
|
|
516
|
+
let args = {};
|
|
517
|
+
if (inside) {
|
|
518
|
+
try {
|
|
519
|
+
args = JSON.parse(inside);
|
|
520
|
+
} catch {
|
|
521
|
+
/*
|
|
522
|
+
* Skip this candidate and keep scanning, instead of aborting.
|
|
523
|
+
*
|
|
524
|
+
* MEASURED: `read(bad) and pwsh({"command":"dir"})` returned nothing, because the first malformed
|
|
525
|
+
* candidate returned null for the whole reply and the valid `pwsh(...)` behind it was never reached.
|
|
526
|
+
* A bad call earlier in a reply must not hide a good one later. Resume just past this closing paren.
|
|
527
|
+
*/
|
|
528
|
+
i = end;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
name: word,
|
|
535
|
+
arguments: JSON.stringify(args),
|
|
536
|
+
start: i,
|
|
537
|
+
end: end + 1,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Recover a tool call from a plain-text reply, if the model made one.
|
|
546
|
+
*
|
|
547
|
+
* TWO SHAPES, IN ORDER
|
|
548
|
+
* 1 `name({...})` — preferred: no instruction needed, and it streams
|
|
549
|
+
* 2 `{"tool":"name","arguments":{…}}` — kept because models emit it unprompted, and because a fallback
|
|
550
|
+
* costs nothing when the primary misses
|
|
551
|
+
*
|
|
552
|
+
* Fenced blocks are searched last-first because a model that explains itself tends to put the final answer at
|
|
553
|
+
* the end. A reply that merely CONTAINS the word tool but no valid object is treated as prose, because
|
|
554
|
+
* treating it as a malformed call would throw away a perfectly good answer.
|
|
555
|
+
*/
|
|
556
|
+
export function parseToolCall(reply, names) {
|
|
397
557
|
const text = String(reply ?? '').trim();
|
|
398
558
|
if (!text) return null;
|
|
399
559
|
|
|
560
|
+
// Shape 1: name({...}).
|
|
561
|
+
const direct = findCallForm(text, names);
|
|
562
|
+
if (direct) return { name: direct.name, arguments: direct.arguments };
|
|
563
|
+
|
|
564
|
+
/*
|
|
565
|
+
* Shape 2: the tags a model reaches for when it invents its own protocol.
|
|
566
|
+
*
|
|
567
|
+
* MEASURED, live: asked to run a glob, the web model answered
|
|
568
|
+
* `<tool name="glob" string="true">{"pattern":"…"}</tool>` — a shape nothing here had ever taught it, and
|
|
569
|
+
* one the parser did not accept. The call never ran, nothing said so, and the model repeated the block
|
|
570
|
+
* until the turn was full of copies. Accepting the shape ends the loop at its cause instead of only
|
|
571
|
+
* masking the symptom.
|
|
572
|
+
*/
|
|
573
|
+
const xmlTag = /<tool\s+name\s*=\s*"([^"]+)"[^>]*>([\s\S]*?)<\/tool>/i.exec(text);
|
|
574
|
+
if (xmlTag) {
|
|
575
|
+
const name = xmlTag[1].trim();
|
|
576
|
+
if (!names?.length || names.includes(name)) {
|
|
577
|
+
let args = {};
|
|
578
|
+
const body = xmlTag[2].trim();
|
|
579
|
+
if (body) {
|
|
580
|
+
try {
|
|
581
|
+
args = JSON.parse(body);
|
|
582
|
+
} catch {
|
|
583
|
+
args = {};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return { name, arguments: JSON.stringify(args) };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const xmlBlock = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/i.exec(text);
|
|
591
|
+
if (xmlBlock) {
|
|
592
|
+
try {
|
|
593
|
+
const parsed = JSON.parse(xmlBlock[1]);
|
|
594
|
+
const name = parsed?.name ?? parsed?.tool;
|
|
595
|
+
if (typeof name === 'string' && name && (!names?.length || names.includes(name))) {
|
|
596
|
+
const raw = parsed.arguments ?? {};
|
|
597
|
+
return { name, arguments: typeof raw === 'string' ? raw : JSON.stringify(raw) };
|
|
598
|
+
}
|
|
599
|
+
} catch {
|
|
600
|
+
/* fall through to the JSON scan below */
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
400
604
|
const candidates = [];
|
|
401
605
|
// fenced blocks first
|
|
402
606
|
const fence = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
@@ -464,9 +668,74 @@ export function parseToolCall(reply) {
|
|
|
464
668
|
* It does not decide whether a call exists - `parseToolCall` does that at the end. It only answers
|
|
465
669
|
* "how far can I safely show right now", which is the question a live stream needs.
|
|
466
670
|
*/
|
|
467
|
-
export function streamSafeCut(text) {
|
|
671
|
+
export function streamSafeCut(text, names) {
|
|
468
672
|
const s = String(text ?? '');
|
|
469
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Hold from a known tool name that has opened, or is about to open, a parenthesis.
|
|
676
|
+
*
|
|
677
|
+
* This is the call-syntax half of the same question. `read(` is a call; `read` alone might be the first
|
|
678
|
+
* letters of `readable`, so it is not held - but a trailing fragment that could still become a known name
|
|
679
|
+
* (`repl` at the very end of the text) IS held, because the next characters may complete it.
|
|
680
|
+
*/
|
|
681
|
+
const wanted = Array.isArray(names) && names.length ? names : null;
|
|
682
|
+
if (wanted) {
|
|
683
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
684
|
+
if (!/[A-Za-z_]/.test(s[i])) continue;
|
|
685
|
+
let j = i;
|
|
686
|
+
let word = '';
|
|
687
|
+
while (j < s.length && /[A-Za-z0-9_.-]/.test(s[j])) {
|
|
688
|
+
word += s[j];
|
|
689
|
+
j += 1;
|
|
690
|
+
}
|
|
691
|
+
if (wanted.includes(word)) {
|
|
692
|
+
let k = j;
|
|
693
|
+
while (k < s.length && /\s/.test(s[k])) k += 1;
|
|
694
|
+
// A complete `name (` is a call being written; nothing after it may stream.
|
|
695
|
+
if (k >= s.length || s[k] === '(') return i;
|
|
696
|
+
}
|
|
697
|
+
i = j - 1;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* A name that is still arriving.
|
|
702
|
+
*
|
|
703
|
+
* The reply may end mid-identifier — `… now call replace_not` — and one more character would make it a
|
|
704
|
+
* call. Holding the fragment costs a few visible characters and stops a call's opening words from
|
|
705
|
+
* appearing in the transcript before the call does.
|
|
706
|
+
*
|
|
707
|
+
* THREE CHARACTERS MINIMUM, measured. The first version held any trailing fragment that prefixed a known
|
|
708
|
+
* name, so an ordinary sentence ending in `r` stalled one character back - `r` prefixes `read` and
|
|
709
|
+
* `replace_note`. On screen that is a reply that appears to stop mid-word and then finishes a keystroke
|
|
710
|
+
* later. A three-letter fragment is worth holding; one letter is not.
|
|
711
|
+
*/
|
|
712
|
+
const tailStart = Math.max(0, s.length - 40);
|
|
713
|
+
for (let i = tailStart; i < s.length; i += 1) {
|
|
714
|
+
const fragment = s.slice(i);
|
|
715
|
+
if (fragment.length < 3) break;
|
|
716
|
+
if (!/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(fragment)) continue;
|
|
717
|
+
if (wanted.some((n) => n.startsWith(fragment))) return i;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Hold at a tag that may be turning into `<tool …>` or `<tool_call>`.
|
|
723
|
+
*
|
|
724
|
+
* The same reasoning as the name fragment: a reply that is writing one of these must not show its opening
|
|
725
|
+
* characters first, or the transcript gains a partial tag that no parser will ever match.
|
|
726
|
+
*/
|
|
727
|
+
for (let i = s.indexOf('<'); i !== -1; i = s.indexOf('<', i + 1)) {
|
|
728
|
+
const frag = s.slice(i + 1);
|
|
729
|
+
if (frag.length === 0) return i;
|
|
730
|
+
const letters = /^[A-Za-z_]*/.exec(frag)[0];
|
|
731
|
+
if (letters.length < frag.length) {
|
|
732
|
+
// The tag name is complete; only an exact match to one we hold is interesting.
|
|
733
|
+
if (letters === 'tool' || letters === 'tool_call') return i;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if ('tool'.startsWith(letters) || 'tool_call'.startsWith(letters)) return i;
|
|
737
|
+
}
|
|
738
|
+
|
|
470
739
|
/**
|
|
471
740
|
* Could the brace at `i` still turn out to be the start of a tool object?
|
|
472
741
|
*
|
|
@@ -497,9 +766,25 @@ export function streamSafeCut(text) {
|
|
|
497
766
|
return s.length;
|
|
498
767
|
}
|
|
499
768
|
|
|
500
|
-
export function stripToolCall(reply) {
|
|
769
|
+
export function stripToolCall(reply, names) {
|
|
501
770
|
let text = String(reply ?? '');
|
|
502
771
|
|
|
772
|
+
/**
|
|
773
|
+
* Shape 1: `name({...})`, wherever it appears, prose either side.
|
|
774
|
+
*
|
|
775
|
+
* Removed by the same character walk that found it, so a call whose arguments contain a parenthesis or a
|
|
776
|
+
* brace inside a string is removed whole rather than up to the first `)`.
|
|
777
|
+
*/
|
|
778
|
+
for (let guard = 0; guard < 20; guard += 1) {
|
|
779
|
+
const found = findCallForm(text, names);
|
|
780
|
+
if (!found) break;
|
|
781
|
+
text = text.slice(0, found.start) + ' ' + text.slice(found.end);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// The tagged shapes, before anything else, so their inner JSON is never treated as prose.
|
|
785
|
+
text = text.replace(/<tool\s+name\s*=\s*"[^"]+"[^>]*>[\s\S]*?<\/tool>/gi, ' ');
|
|
786
|
+
text = text.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, ' ');
|
|
787
|
+
|
|
503
788
|
// Fenced calls first: the fence marks the whole block, so no brace counting is needed.
|
|
504
789
|
text = text.replace(/```(?:json)?\s*\{\s*"tool"[\s\S]*?\}\s*```/gi, '');
|
|
505
790
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operatingtag/dsh-unclesam",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Uncle Sam — answers through chat.deepseek.com in a real browser tab instead of the metered API. Switch between the API and the web account from the composer; web replies are signed so you always know which route answered.",
|