@nanhara/hara 0.109.4 → 0.109.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 +24 -0
- package/dist/providers/openai.js +20 -0
- package/dist/tui/App.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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.109.5 — Enter enters the conversation instantly + reasoning off truly disables DashScope thinking
|
|
9
|
+
|
|
10
|
+
- **Pressing Enter now enters the conversation flow instantly — the message no longer sits stuck in the
|
|
11
|
+
input box.** A turn's synchronous prep (reading an inlined `@file`, base64-encoding pasted images) used
|
|
12
|
+
to run *before* ink could paint, so with a heavy message (a big spec + two images) Enter looked dead
|
|
13
|
+
for seconds ("回车一直不动") until the work finished. The submit now yields one tick so the UI paints
|
|
14
|
+
the committed message + cleared input + spinner FIRST, then does the prep and the (often slow) model
|
|
15
|
+
call. Instant feedback regardless of how heavy the turn or how slow the first token.
|
|
16
|
+
- **On DashScope, `reasoning off` now actually stops the thinking phase — not just hides it.** DashScope
|
|
17
|
+
models (Qwen, GLM, …) stream a "thinking" pass *before* the answer; that generation is the main latency
|
|
18
|
+
there (measured: **qwen3.7-plus ~14s with thinking → ~1.6s without**). hara previously assumed chat
|
|
19
|
+
models "can't be silenced server-side" and merely dropped the reasoning from the UI — so the model
|
|
20
|
+
still spent the time. It *can* be silenced: reasoning **off** now sends `enable_thinking: false` (fast),
|
|
21
|
+
low/medium/high sends `true`, and the dial **UNSET leaves the request untouched** (model default — zero
|
|
22
|
+
impact, the safe default). Detected by the **DashScope endpoint** (built-in `qwen`/`qwen-oauth`, or a
|
|
23
|
+
custom baseURL on `dashscope.aliyuncs.com`), not the model name — so a custom `qwen3.7-plus` profile is
|
|
24
|
+
covered. Set it with `HARA_REASONING_EFFORT=off` or `reasoningEffort: "off"` in `~/.hara/config.json`.
|
|
25
|
+
(A runtime `/reasoning` toggle — adjust it mid-session like Claude Code — is coming next.)
|
|
26
|
+
- Note: `enable_thinking:false` is reliable on qwen3.x-plus; on some other DashScope models it only
|
|
27
|
+
suppresses the reasoning without the full speedup, which is why it's opt-in, not a forced default.
|
|
28
|
+
- Context on prompt caching (from 0.109.1): that path only ever applied to the raw **Anthropic** provider.
|
|
29
|
+
DashScope/GLM/DeepSeek/gateway go through the OpenAI-compatible path, where caching is the provider's own
|
|
30
|
+
automatic prefix cache — and hara's system prompt is stable across turns, so it engages on its own.
|
|
31
|
+
|
|
8
32
|
## 0.109.4 — a dropped file path is read, not "Unknown command"
|
|
9
33
|
|
|
10
34
|
- **Dragging/pasting a file into the prompt no longer errors.** A dropped file pastes as an absolute path
|
package/dist/providers/openai.js
CHANGED
|
@@ -51,6 +51,21 @@ export function toOpenAI(system, history) {
|
|
|
51
51
|
export function isReasoningModel(model) {
|
|
52
52
|
return /^(o1|o3|o4|gpt-5)/i.test(model);
|
|
53
53
|
}
|
|
54
|
+
/** DashScope (Alibaba — serves Qwen, GLM, …) runs a "thinking" phase that streams reasoning BEFORE the
|
|
55
|
+
* answer — the main latency there (measured: qwen3.7-plus ~14s thinking vs ~1.6s without). It can be
|
|
56
|
+
* silenced SERVER-SIDE with `enable_thinking:false` (actually stops generating the reasoning, not just
|
|
57
|
+
* hides it). So map hara's reasoning dial onto it: an explicit **off** → thinking off (fast);
|
|
58
|
+
* low/medium/high → on. Returns the flag to send, or `undefined` = leave the request UNTOUCHED — when
|
|
59
|
+
* the dial is UNSET (keep the model's own default; zero impact — the safe default the user asked for)
|
|
60
|
+
* or the endpoint isn't DashScope. Detected by the DashScope ENDPOINT (built-in qwen/qwen-oauth
|
|
61
|
+
* providers, OR a custom baseURL on dashscope — which is how the reporter's `custom:qwen3.7-plus`
|
|
62
|
+
* profile is set up), NOT the model name, so it covers custom Qwen/GLM profiles too. Exported for tests. */
|
|
63
|
+
export function dashscopeThinking(effort, baseURL, label) {
|
|
64
|
+
const isDashscope = /dashscope\.aliyuncs\.com/i.test(baseURL ?? "") || label === "qwen" || label === "qwen-oauth";
|
|
65
|
+
if (!isDashscope || effort === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
return effort !== "off";
|
|
68
|
+
}
|
|
54
69
|
/** OpenAI-compatible provider (works with OpenAI, Qwen/DashScope, GLM, Kimi, …). */
|
|
55
70
|
export function createOpenAIProvider(opts) {
|
|
56
71
|
const client = new OpenAI({ apiKey: opts.apiKey, maxRetries: 4, ...(opts.baseURL ? { baseURL: opts.baseURL } : {}) });
|
|
@@ -78,6 +93,11 @@ export function createOpenAIProvider(opts) {
|
|
|
78
93
|
if (opts.reasoningEffort && opts.reasoningEffort !== undefined && isReasoningModel(opts.model)) {
|
|
79
94
|
params.reasoning_effort = opts.reasoningEffort === "off" ? "minimal" : opts.reasoningEffort;
|
|
80
95
|
}
|
|
96
|
+
// DashScope: reasoning "off" REALLY disables the thinking phase (big speedup), not just hides it.
|
|
97
|
+
// Only fires on the DashScope endpoint + when the dial is set — UNSET stays untouched (zero impact).
|
|
98
|
+
const dsThink = dashscopeThinking(opts.reasoningEffort, opts.baseURL, opts.label);
|
|
99
|
+
if (dsThink !== undefined)
|
|
100
|
+
params.enable_thinking = dsThink;
|
|
81
101
|
// Stream: emit text deltas live; accumulate tool-call args by index; grab usage from the tail chunk.
|
|
82
102
|
let text = "";
|
|
83
103
|
const acc = new Map();
|
package/dist/tui/App.js
CHANGED
|
@@ -515,6 +515,11 @@ export function App({ initialStatus, model, cwd, header, onSubmit, cycleApproval
|
|
|
515
515
|
return askTextFn(question);
|
|
516
516
|
};
|
|
517
517
|
const setApprovalFn = (m) => setStatus((s) => ({ ...s, approval: m }));
|
|
518
|
+
// Enter the conversation flow INSTANTLY: yield one macrotask so ink paints the committed message +
|
|
519
|
+
// cleared input + spinner BEFORE the turn's synchronous prep runs (reading @-files, base64-encoding
|
|
520
|
+
// images) and before the model's slow first token. Without this, that sync prep blocks ink's flush,
|
|
521
|
+
// so pressing Enter leaves the message stuck in the input box for seconds ("回车一直不动"). One tick.
|
|
522
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
518
523
|
try {
|
|
519
524
|
await onSubmit(t, { sink, confirm: confirmFn, select: selectFn, ask: askFn, setApproval: setApprovalFn, signal: ctrl.signal, exit, approval: statusRef.current.approval, drainQueue }, images);
|
|
520
525
|
}
|