@tiens.nguyen/gu-cli 1.0.783 → 1.0.785

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.
@@ -103,3 +103,50 @@ export function completedFlush({
103
103
  const steps = tail.slice(0, close);
104
104
  return steps ? { kind: "steps", text: steps } : { kind: "none", text: "" };
105
105
  }
106
+
107
+ /**
108
+ * WHILE THE JOB IS STILL RUNNING: which of the newly arrived text is the turn's WORK (step lines
109
+ * and thoughts, inside <think>…</think>) and which is the ANSWER (outside it). Task #326.
110
+ *
111
+ * An agent turn's stream is `<think>steps…</think>answer`. The answer is the caller's to print —
112
+ * once, wrapped, from the completed resultText. completedFlush has always stopped at </think> for
113
+ * that reason, but the RUNNING poll did not: it fed everything to the step printer. So whenever
114
+ * the answer's chunk was polled before the job flipped to "completed" (any answer long enough to
115
+ * be posted on its own), every paragraph of it was printed as a "●" step bullet — and then the
116
+ * whole answer again under "recap:". Reported as the answer appearing twice; short answers escaped
117
+ * only because their chunk and the completion arrived in the same poll.
118
+ *
119
+ * `state` is carried from poll to poll: { inThink, held }. `held` is the start of a tag cut in two
120
+ * by a poll boundary ("</thi" + "nk>"), kept back until the rest arrives.
121
+ * → { parts: [{ inside, text }], state } — the tags themselves are dropped.
122
+ */
123
+ export function splitLiveStream(fresh, state = {}) {
124
+ let text = String(state.held ?? "") + String(fresh ?? "");
125
+ let inThink = Boolean(state.inThink);
126
+ const parts = [];
127
+ const push = (inside, t) => {
128
+ if (!t) return;
129
+ const last = parts[parts.length - 1];
130
+ if (last && last.inside === inside) last.text += t;
131
+ else parts.push({ inside, text: t });
132
+ };
133
+ // Hold back a tail that could be the beginning of a tag.
134
+ let held = "";
135
+ const lt = text.lastIndexOf("<");
136
+ if (lt >= 0 && lt > text.length - 8) {
137
+ const tail = text.slice(lt).toLowerCase();
138
+ if ("<think>".startsWith(tail) || "</think>".startsWith(tail)) {
139
+ held = text.slice(lt);
140
+ text = text.slice(0, lt);
141
+ }
142
+ }
143
+ const re = /<(\/?)think>/gi;
144
+ let at = 0;
145
+ for (let m; (m = re.exec(text)); ) {
146
+ push(inThink, text.slice(at, m.index));
147
+ inThink = m[1] !== "/";
148
+ at = m.index + m[0].length;
149
+ }
150
+ push(inThink, text.slice(at));
151
+ return { parts, state: { inThink, held } };
152
+ }
@@ -7782,7 +7782,7 @@ def run_agent_chat(cfg):
7782
7782
  _skill_call = _skills_mod.invocation(task_text, _skills)
7783
7783
  if _skill_call:
7784
7784
  _sk, _sk_body, _sk_req = _skill_call
7785
- task_text = (_skills_mod.render(_sk, _sk_body) + "\n\nThe user's request: "
7785
+ task_text = (_skills_mod.render(_sk, _sk_body, _sk_req) + "\n\nThe user's request: "
7786
7786
  + (_sk_req or "(nothing more — do what the skill describes)"))
7787
7787
  _log(f"skill invoked: {_sk['name']}")
7788
7788
 
package/gu-cli.mjs CHANGED
@@ -20,7 +20,7 @@ import { execFile as execFileCallback, spawn } from "node:child_process";
20
20
  import { createHash, randomUUID } from "node:crypto";
21
21
  import { writeFileSync, unlinkSync } from "node:fs";
22
22
  import { createTaskPool, createFolderQueue } from "./job-pools.mjs";
23
- import { NO_STEP, foldStepTokens, withStepTokens } from "./step-tokens.mjs";
23
+ import { NO_STEP, foldStepTokens, withStepTokens, NO_CACHE, foldTurnCache, withTurnCache } from "./step-tokens.mjs";
24
24
  import { agentStoppedLine, jobEnded, jobStatusFrom409, stopGracefully } from "./agent-stop.mjs";
25
25
  import { ragJobConfig } from "./rag-job-config.mjs";
26
26
  import { codingKeyFallbackLine, isOllamaCloudUrl } from "./coding-key-prompt.mjs";
@@ -4138,6 +4138,9 @@ async function runAgentChatJob(job) {
4138
4138
  // Task #298: the CURRENT model call's own ↑ in / ↓ out, for the thinking line (the bar
4139
4139
  // already shows the totals). Rides on the breakdown — see step-tokens.mjs.
4140
4140
  let stepTokens = NO_STEP;
4141
+ // #323: the turn's cached prompt tokens (when the provider reports them) and its first step's
4142
+ // prompt — what the terminal needs to say what the turn COST. Rides on the breakdown too.
4143
+ let turnCache = NO_CACHE;
4141
4144
  // Raw model-stream tokens (event.type === "stream") are Python code + `Thought:`
4142
4145
  // prose + <code>/<|"|> fragments. The web Thought panel renders reasoning as
4143
4146
  // markdown, which turns `#`-prefixed code comments into <h1> headings and
@@ -4305,17 +4308,20 @@ async function runAgentChatJob(job) {
4305
4308
  if (breakdown) latestPromptBreakdown = breakdown;
4306
4309
  const prevStep = stepTokens;
4307
4310
  stepTokens = foldStepTokens(stepTokens, event);
4311
+ const prevCache = turnCache;
4312
+ turnCache = foldTurnCache(turnCache, event);
4313
+ const cacheChanged = turnCache.cached !== prevCache.cached || turnCache.firstIn !== prevCache.firstIn || turnCache.cacheReported !== prevCache.cacheReported;
4308
4314
  const stepChanged =
4309
4315
  stepTokens.step !== prevStep.step ||
4310
4316
  stepTokens.stepIn !== prevStep.stepIn ||
4311
4317
  stepTokens.stepOut !== prevStep.stepOut;
4312
- if (latestPromptBreakdown) latestPromptBreakdown = withStepTokens(latestPromptBreakdown, stepTokens);
4318
+ if (latestPromptBreakdown) latestPromptBreakdown = withTurnCache(withStepTokens(latestPromptBreakdown, stepTokens), turnCache);
4313
4319
  if (
4314
4320
  nextTotal !== latestCodeTokens ||
4315
4321
  nextMax !== latestMaxCodeTokens ||
4316
4322
  nextOut !== latestCodeOutputTokens ||
4317
4323
  breakdown ||
4318
- (stepChanged && latestPromptBreakdown)
4324
+ ((stepChanged || cacheChanged) && latestPromptBreakdown)
4319
4325
  ) {
4320
4326
  latestCodeTokens = nextTotal;
4321
4327
  latestMaxCodeTokens = nextMax;