zames_pro 2.82.18 → 2.82.20

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,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.82.20] - 2026-10-22
11
+
12
+ ### Fixed
13
+
14
+ - **qwen:** unwrap a paginated chat-list wrapper
15
+
16
+ ## [2.82.19] - 2026-10-22
17
+
18
+ ### Fixed
19
+
20
+ - **glm:** unwrap a nested chats wrapper in the chat list
21
+
22
+ ### Changed
23
+
24
+ - **glm:** drop guessed role fields from the history parser
25
+
10
26
  ## [2.82.18] - 2026-10-22
11
27
 
12
28
  ### Fixed
package/dist/glm-chats.js CHANGED
@@ -91,6 +91,13 @@ export function parseGlmChatList(json) {
91
91
  // A wrapper key wins; otherwise the object itself is an id-keyed MAP
92
92
  // of chats (a real alternative shape), not an empty list.
93
93
  arr = o.data ?? o.chats ?? o.list ?? o.items ?? arr;
94
+ // A paginated endpoint wraps TWICE (`{data:{chats:[…]}}`); leaving the
95
+ // inner wrapper in place made its KEY look like a chat id (`'chats'`) with
96
+ // an empty title, so /chats listed one fake chat and hid the real ones.
97
+ if (arr && typeof arr === 'object' && !Array.isArray(arr)) {
98
+ const inner = arr;
99
+ arr = inner.chats ?? inner.list ?? inner.items ?? arr;
100
+ }
94
101
  }
95
102
  const keyed = Array.isArray(arr)
96
103
  ? null
@@ -154,30 +161,25 @@ export function parseGlmHistory(json) {
154
161
  .sort((a, b) => (Number(a.timestamp) || 0) - (Number(b.timestamp) || 0));
155
162
  const out = [];
156
163
  for (const m of sorted) {
157
- // The role has been `role` (string, case-insensitive) or `from`
158
- // (`human`/`assistant`, z.ai's older spelling). Reading only `role`
159
- // attributed a `from: 'human'` turn to the model.
164
+ // The role comes from `role` (confirmed by the live fixture). Only the
165
+ // values the API actually uses are matched: a numeric flag or a `from`
166
+ // field was never observed on z.ai and guessing at it would silently
167
+ // re-attribute turns.
160
168
  const roleStr = String(m.role ?? '').toLowerCase();
161
- const fromStr = String(m.from ?? '').toLowerCase();
162
169
  // A SYSTEM/TOOL turn is neither the operator nor the model's answer: z.ai's
163
170
  // own system prompt and the sandbox output of its agent runtime used to be
164
171
  // restored as model REPLIES (the Qwen parser drops those roles too).
165
- if (/^(system|tool)$/.test(roleStr) || /^(system|tool)$/.test(fromStr)) {
172
+ if (/^(system|tool)$/.test(roleStr))
166
173
  continue;
167
- }
168
- // A numeric flag uses 1 = user, 2 = assistant (the same convention as
169
- // Qwen's history); anything else is matched by name.
170
- const isUser = roleStr === 'user' ||
171
- roleStr === 'human' ||
172
- roleStr === '1' ||
173
- fromStr === 'user' ||
174
- fromStr === 'human';
174
+ const isUser = roleStr === 'user' || roleStr === 'human';
175
175
  const role = isUser ? 'user' : 'assistant';
176
176
  let text = '';
177
177
  if (typeof m.content === 'string')
178
178
  text = m.content;
179
179
  else if (typeof m.text === 'string')
180
180
  text = m.text;
181
+ // `fragments` is the DeepSeek-style payload (kept as a safety net for a
182
+ // build that reuses it): REQUEST is the user turn, RESPONSE the answer.
181
183
  else if (Array.isArray(m.fragments)) {
182
184
  const want = role === 'assistant' ? 'RESPONSE' : 'REQUEST';
183
185
  for (const fr of m.fragments) {
@@ -91,6 +91,26 @@ export function scrapeQwenChatMessages(opts) {
91
91
  * { data: [ {id,title} ] }, { data: { chats: [...] } }, { data: { list: [...] } }.
92
92
  * Returns [] for anything unrecognizable (never throws).
93
93
  */
94
+ /**
95
+ * Unwrap the list out of its wrappers: `{chats:[…]}`, `{list:{page,data:[…]}}`.
96
+ * A paginated reply nests the wrapper INSIDE the wrapper, and treating that
97
+ * inner object as an id-keyed map made its own key (`'data'`, `'list'`) look
98
+ * like a chat id. A plain id-keyed map (no known wrapper key) is returned
99
+ * as-is, because that is a legitimate shape too.
100
+ */
101
+ function unwrapChatList(data) {
102
+ let cur = data;
103
+ for (let depth = 0; depth < 3; depth++) {
104
+ if (!cur || typeof cur !== 'object' || Array.isArray(cur))
105
+ return cur;
106
+ const o = cur;
107
+ const inner = o.chats ?? o.list ?? o.items ?? o.data;
108
+ if (inner === undefined)
109
+ return cur;
110
+ cur = inner;
111
+ }
112
+ return cur;
113
+ }
94
114
  export function parseQwenChatList(json) {
95
115
  const root = json;
96
116
  // A BARE array (no `data` wrapper) is the simplest shape the endpoint can
@@ -105,10 +125,7 @@ export function parseQwenChatList(json) {
105
125
  ? // A bare map keyed by chat id (the same convention as the history
106
126
  // shape) has no `chats`/`list`/`items` wrapper: without this fallback
107
127
  // a legitimate list was rejected and /chats printed "(none)".
108
- (data.chats ??
109
- data.list ??
110
- data.items ??
111
- data)
128
+ unwrapChatList(data)
112
129
  : null;
113
130
  const keyed = Array.isArray(arr)
114
131
  ? null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.82.18",
3
+ "version": "2.82.20",
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": {