@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 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
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanhara/hara",
3
- "version": "0.109.4",
3
+ "version": "0.109.5",
4
4
  "description": "hara — a coding agent CLI that runs like an engineering org.",
5
5
  "bin": {
6
6
  "hara": "dist/index.js"