@nanhara/hara 0.112.2 → 0.112.3
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 +13 -0
- package/dist/providers/openai.js +33 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to `@nanhara/hara`.
|
|
|
5
5
|
> Versioning (pre-1.0, SemVer-style): the **minor** (middle) number bumps for a **new feature**; the
|
|
6
6
|
> **patch** (last) number bumps for **optimizations/fixes of existing features**.
|
|
7
7
|
|
|
8
|
+
## 0.112.3 — big file write no longer traps the model in a "params not passed" loop
|
|
9
|
+
|
|
10
|
+
- **Fixed the loop where a model (glm-5 / qwen via DashScope) repeats `write_file` / `bash` with empty or
|
|
11
|
+
`undefined` arguments.** Two problems compounded: the OpenAI-compatible path capped output at
|
|
12
|
+
`max_tokens: 8192`, so a large `write_file` (e.g. a 64-hexagram data file) ran PAST the limit and its
|
|
13
|
+
tool-call-arguments JSON was cut off — and hara then **silently swallowed the unparseable JSON into an
|
|
14
|
+
empty `{}`**. So the tool ran with no path/content (`bash` got `command: undefined` →
|
|
15
|
+
`/bin/sh: undefined: command not found`), the model saw the failure, "fixed" it by calling again, and
|
|
16
|
+
looped — never realizing its *output* had been truncated. Now: (1) `max_tokens` is raised to 32000
|
|
17
|
+
(glm-5/qwen accept it), and (2) **truncated/malformed tool arguments surface as an actionable error**
|
|
18
|
+
("the model hit its output-length limit mid tool-call — write the file in smaller parts") instead of a
|
|
19
|
+
silent `{}`, so the loop breaks and the fix is obvious.
|
|
20
|
+
|
|
8
21
|
## 0.112.2 — moving/resizing the window no longer garbles the UI
|
|
9
22
|
|
|
10
23
|
- **Terminal resize no longer stacks the status row + input box into a garble.** ink 6.8's resize
|
package/dist/providers/openai.js
CHANGED
|
@@ -2,6 +2,35 @@ import OpenAI from "openai";
|
|
|
2
2
|
import { imageToBase64 } from "../images.js";
|
|
3
3
|
import { reasoningParams } from "./reasoning.js";
|
|
4
4
|
import { resolvePlatform } from "./registry.js";
|
|
5
|
+
/** Assemble streamed tool-call fragments into tool uses. CRITICAL: non-empty arguments that don't parse
|
|
6
|
+
* mean the model was cut off MID tool-call (almost always the output-length limit on a big write_file /
|
|
7
|
+
* bash). Silently substituting `{}` makes the model loop forever — it calls write_file with no path, bash
|
|
8
|
+
* with `command: undefined` (`/bin/sh: undefined: command not found`), sees the failure, and retries,
|
|
9
|
+
* never realizing its OUTPUT was truncated. So we surface it as an actionable error instead. Exported for
|
|
10
|
+
* tests. */
|
|
11
|
+
export function assembleToolCalls(entries, finish) {
|
|
12
|
+
let truncated = false;
|
|
13
|
+
const toolUses = entries
|
|
14
|
+
.filter((t) => t.id && t.name)
|
|
15
|
+
.map((t) => {
|
|
16
|
+
let input = {};
|
|
17
|
+
const raw = (t.args || "").trim();
|
|
18
|
+
if (raw) {
|
|
19
|
+
try {
|
|
20
|
+
input = JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
truncated = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { id: t.id, name: t.name, input };
|
|
27
|
+
});
|
|
28
|
+
if (truncated) {
|
|
29
|
+
const why = finish === "length" ? "the model hit its output-length limit mid tool-call" : "the model emitted malformed tool-call arguments";
|
|
30
|
+
return { toolUses: [], error: `Tool call dropped — ${why}, so its arguments were incomplete. For a large file, write it in smaller parts (several write_file/edit calls) rather than one giant call.` };
|
|
31
|
+
}
|
|
32
|
+
return { toolUses };
|
|
33
|
+
}
|
|
5
34
|
// Re-exported for callers that still import it from here (the reasoning-family check now lives in reasoning.ts).
|
|
6
35
|
export { isReasoningModel } from "./reasoning.js";
|
|
7
36
|
/** Build OpenAI chat-completions messages from neutral history. */
|
|
@@ -63,7 +92,7 @@ export function createOpenAIProvider(opts) {
|
|
|
63
92
|
const params = {
|
|
64
93
|
model: opts.model,
|
|
65
94
|
messages: toOpenAI(system, history),
|
|
66
|
-
max_tokens: 8192
|
|
95
|
+
max_tokens: 32000, // was 8192 — too small: a big write_file's args got truncated → unparseable → loop
|
|
67
96
|
stream: true,
|
|
68
97
|
stream_options: { include_usage: true },
|
|
69
98
|
};
|
|
@@ -118,18 +147,9 @@ export function createOpenAIProvider(opts) {
|
|
|
118
147
|
return { text: "", toolUses: [], stop: "error", errorMsg: "interrupted" };
|
|
119
148
|
return { text: "", toolUses: [], stop: "error", errorMsg: `${e?.status ?? ""} ${e?.message ?? e}` };
|
|
120
149
|
}
|
|
121
|
-
const toolUses = [...acc.values()]
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let input = {};
|
|
125
|
-
try {
|
|
126
|
-
input = JSON.parse(t.args || "{}");
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
input = {};
|
|
130
|
-
}
|
|
131
|
-
return { id: t.id, name: t.name, input };
|
|
132
|
-
});
|
|
150
|
+
const { toolUses, error: argsError } = assembleToolCalls([...acc.values()], finish);
|
|
151
|
+
if (argsError)
|
|
152
|
+
return { text, toolUses: [], stop: "error", errorMsg: argsError, usage };
|
|
133
153
|
const stop = finish === "tool_calls" || toolUses.length ? "tool_use" : "end";
|
|
134
154
|
return { text, toolUses, stop, usage };
|
|
135
155
|
},
|