@cubicecho/agent-core 2.11.0 → 2.12.0

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/dist/stream.js CHANGED
@@ -1,5 +1,18 @@
1
1
  import { EndpointSilent } from "./retry.js";
2
2
  import { DEFAULT_FENCES, FenceSplitter } from "./thinking.js";
3
+ /**
4
+ * The fields of `timings` a turn reports, under the names `TurnUsage` gives them. A value that is
5
+ * not a finite number is left out rather than guessed at.
6
+ */
7
+ const TIMINGS = [
8
+ ["prompt_ms", "promptMs"],
9
+ ["prompt_per_second", "promptTokensPerSecond"],
10
+ ["predicted_ms", "predictedMs"],
11
+ ["predicted_per_second", "tokensPerSecond"],
12
+ ["draft_n", "draftTotal"],
13
+ ["draft_n_accepted", "draftAccepted"],
14
+ ];
15
+ const isCount = (value) => typeof value === "number" && Number.isFinite(value);
3
16
  /** The largest delay a timer takes, which is as close to none as the SDK's timeout option goes. */
4
17
  const NO_SDK_TIMEOUT = 2 ** 31 - 1;
5
18
  /**
@@ -40,7 +53,8 @@ export async function streamTurn(client, body, { signal, idleMs, firstChunkMs, p
40
53
  };
41
54
  try {
42
55
  rearm(false);
43
- return await collect();
56
+ const started = Date.now();
57
+ return await collect(started);
44
58
  }
45
59
  catch (error) {
46
60
  // The caller's own stop has to stay distinguishable from ours: one is a run that was called
@@ -57,7 +71,7 @@ export async function streamTurn(client, body, { signal, idleMs, firstChunkMs, p
57
71
  finally {
58
72
  clearTimeout(idle);
59
73
  }
60
- async function collect() {
74
+ async function collect(started) {
61
75
  // The SDK's own timer runs until the headers arrive, which for a stream is the end of
62
76
  // prefill, and it was set from the idle number. Where a watchdog is armed it covers that
63
77
  // wait already, with the allowance meant for it, so the SDK is told to leave it alone.
@@ -78,6 +92,10 @@ export async function streamTurn(client, body, { signal, idleMs, firstChunkMs, p
78
92
  // its place in the order they arrived.
79
93
  const calls = [];
80
94
  const usage = { prompt: 0, completion: 0, total: 0, cached: 0 };
95
+ // The usage block's own cache count wins over llama.cpp's, which is the same number in the
96
+ // servers that send both; kept apart so the order the two arrive in does not decide.
97
+ let cacheReport;
98
+ let cacheTimings;
81
99
  let finishReason = "";
82
100
  for await (const chunk of stream) {
83
101
  // Rearmed on every chunk, latched below on only some: a priming chunk is the endpoint
@@ -93,8 +111,22 @@ export async function streamTurn(client, body, { signal, idleMs, firstChunkMs, p
93
111
  usage.completion = chunk.usage.completion_tokens ?? 0;
94
112
  usage.total = chunk.usage.total_tokens ?? 0;
95
113
  const reported = chunk.usage;
96
- usage.cached =
97
- reported.prompt_tokens_details?.cached_tokens ?? reported.prompt_cache_hit_tokens ?? 0;
114
+ const hit = reported.prompt_tokens_details?.cached_tokens ?? reported.prompt_cache_hit_tokens;
115
+ if (isCount(hit))
116
+ cacheReport = hit;
117
+ const reasoned = reported.completion_tokens_details?.reasoning_tokens;
118
+ if (isCount(reasoned))
119
+ usage.reasoningTokens = reasoned;
120
+ }
121
+ const { timings } = chunk;
122
+ if (timings) {
123
+ for (const [from, to] of TIMINGS) {
124
+ const value = timings[from];
125
+ if (isCount(value))
126
+ usage[to] = value;
127
+ }
128
+ if (isCount(timings.cache_n))
129
+ cacheTimings = timings.cache_n;
98
130
  }
99
131
  // One choice, because that is what an agent loop asks for. A body with `n` above one
100
132
  // keeps only the first; nothing here is built to reassemble several at once.
@@ -116,8 +148,10 @@ export async function streamTurn(client, body, { signal, idleMs, firstChunkMs, p
116
148
  // has accumulated, and losing a retry is the safer half of that trade. Set before the
117
149
  // callbacks, so a watcher that throws mid-token cannot be told the same token twice.
118
150
  const carried = Boolean(thinking || delta.content || delta.tool_calls?.length);
119
- if (carried && !talking)
151
+ if (carried && !talking) {
120
152
  rearm(true);
153
+ usage.firstTokenMs = Date.now() - started;
154
+ }
121
155
  if (produced && carried)
122
156
  produced.any = true;
123
157
  if (thinking) {
@@ -174,6 +208,12 @@ export async function streamTurn(client, body, { signal, idleMs, firstChunkMs, p
174
208
  // cut off at the ceiling mid-scratchpad, it has no answer, and promoting the deliberation to
175
209
  // one is how a truncated turn gets recorded as output.
176
210
  report(splitter.finish());
211
+ const cached = cacheReport ?? cacheTimings;
212
+ if (cached !== undefined) {
213
+ usage.cached = cached;
214
+ if (usage.prompt > 0)
215
+ usage.uncached = Math.max(0, usage.prompt - cached);
216
+ }
177
217
  const minted = new Set();
178
218
  return {
179
219
  content: splitter.output,
package/llms.txt CHANGED
@@ -24,6 +24,14 @@ The loop above a turn: send, run the tools the model asked for, send again, unti
24
24
  - `ToolCallOutcome` (type) — What one tool call did, in the order the model asked.
25
25
  - `ToolCallRequest` (type) — One call the model made, as `dispatch` is handed it.
26
26
 
27
+ ### calibration
28
+
29
+ How many characters a token is worth on one model, learned from what its endpoint reports.
30
+
31
+ - `calibrate` — Takes one reading from a request that was answered, so the next one to this model is sized by it.
32
+ - `charsPerTokenFor` — The characters per token to size a request to this model with, `CHARS_PER_TOKEN` until a turn has reported one.
33
+ - `resetCalibration` — Forgets every reading, so the next request is sized at `CHARS_PER_TOKEN` again.
34
+
27
35
  ### capabilities
28
36
 
29
37
  What an endpoint turned out not to support, and answering it when it says so.
@@ -86,6 +94,14 @@ What this package needs to know about a caller's configuration.
86
94
  - `RetryPolicy` (type) — How many times a lost or refused request is worth sending again.
87
95
  - `ToolPolicy` (type) — How tools reach the model, and how long it may keep calling them.
88
96
 
97
+ ### continuation
98
+
99
+ Picking up an answer the token ceiling cut off, instead of keeping half of it.
100
+
101
+ - `ContinueTurnOptions` (type) — What `continueTurn` takes besides what `runTurn` does.
102
+ - `continueTurn` — Carries on an answer the token ceiling cut off, by sending the transcript again with the answer so far as a trailing assistant message, and joins the pieces into one turn.
103
+ - `isContinuable` — Whether a turn is one a continuation can finish: cut off at the ceiling, with an answer begun and no tool call in it.
104
+
89
105
  ### errors
90
106
 
91
107
  - `errorMessage` — What went wrong, as a string.
@@ -103,8 +119,12 @@ What a run is doing, while it is doing it.
103
119
  - `RunEvent` (type) — One thing that happened in a run, as a watcher receives it.
104
120
  - `RunEventInput` (type) — What `emit` is given: the run and the sequence are the bus's to assign.
105
121
  - `RunEventKind` (type) — Which kind of thing happened, and what `text`, `name`, `ok` and `usage` carry for it.
122
+ - `RunMetrics` (type) — A run summed and derived from its events: what it cost, where the time went, and why.
123
+ - `RunMetricsOptions` (type) — What `runMetrics` takes besides the events.
106
124
  - `RunUsage` (type) — What a run has spent, counted from the start of the run rather than for the turn that carried it: a client draws the latest one it has seen and needs no arithmetic of its own, and one lost to the backlog cap costs nothing because the next supersedes it.
107
125
  - `resetEvents` — Test seam: forget every run, so one test's events cannot be read by the next.
126
+ - `runMetrics` — A run's totals, timings and cache findings, derived from the events it emitted.
127
+ - `TurnReport` (type) — One turn's usage and measurements as a `usage` event carries them.
108
128
  - `watch` — Everything that has happened on a run, then everything that happens next, until it ends.
109
129
 
110
130
  ### hooks
@@ -113,6 +133,7 @@ Lifecycle hooks, from the host's side: what a session looks like to them, where
113
133
 
114
134
  - `assembleContext` — Builds the context a set of outcomes adds and the notes that go with it.
115
135
  - `configureHooks` — Changes what hooks are held to, for a process whose windows are not the size these defaults were chosen for, or whose host wants its own name above the context.
136
+ - `consult` — Runs an event's hooks and waits for their say, for a host that will hold off when one of them vetoes.
116
137
  - `Gathered` (type) — The context a set of outcomes adds to a request, and a note for each hook worth mentioning.
117
138
  - `gather` — Runs the hooks ahead of a request and builds what they add to it.
118
139
  - `HOOK_CONTEXT_TOKENS` — The most context all of a request's hooks add between them by default, in estimated tokens.
@@ -143,6 +164,7 @@ Lifecycle hooks, from the host's side: what a session looks like to them, where
143
164
  Everything about a request failing that is not about what the request said.
144
165
 
145
166
  - `backoffMs` — Exponential, with jitter so several tasks failing at once do not return in lockstep.
167
+ - `CHARS_PER_TOKEN` — The divisor behind `estimateTokens`, applied here to a character count rather than a string.
146
168
  - `ContextOverflow` — The request was bigger than the model will read.
147
169
  - `compact` — 1234 → "1.2k".
148
170
  - `EndpointSilent` — The endpoint stopped answering mid-request.
@@ -152,9 +174,12 @@ Everything about a request failing that is not about what the request said.
152
174
  - `LOADING_POLL_MS` — How long to wait between asking a loading server again.
153
175
  - `LOADING_TIMEOUT_MS` — How long `runTurn` waits for a model to load unless told otherwise.
154
176
  - `messageTokens` — One message's estimated tokens, by the same count `requestTokens` sums for a whole request.
177
+ - `requestChars` — How many characters a request is worth: the walk `requestTokens` divides, without the division.
155
178
  - `requestTokens` — What this request will cost the window, in tokens, near enough.
156
179
  - `SMALLEST_LIKELY_WINDOW` — The smallest window worth believing in, and the floor under `runTurn`'s guard.
157
180
  - `sleep` — A delay an abort cuts short, rejecting rather than resolving early.
181
+ - `TokenEstimateOptions` (type) — What the two token estimates below take besides what they measure.
182
+ - `toolsChars` — How many characters a tool array is worth, measured once per array.
158
183
 
159
184
  ### run-turn
160
185
 
@@ -205,7 +230,7 @@ Reading one streamed turn back into a message.
205
230
  - `StreamTurnOptions` (type) — What `streamTurn` takes besides the request body.
206
231
  - `streamTurn` — Runs one turn as a stream, reporting tokens as they arrive and assembling them back into a message.
207
232
  - `Turn` (type) — One streamed turn, put back together into the shape a loop and a transcript work with.
208
- - `TurnUsage` (type) — What a turn cost.
233
+ - `TurnUsage` (type) — What a turn cost, and how it went.
209
234
 
210
235
  ### thinking
211
236
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubicecho/agent-core",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "description": "The endpoint-agnostic half of an OpenAI-compatible agent loop: tool-schema compatibility, on-demand tool loading, one-shot side tasks, run events, and a pooled client.",
5
5
  "keywords": [
6
6
  "openai",