@cubicecho/agent-core 2.13.0 → 2.15.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/README.md +71 -7
- package/dist/agent-loop.d.ts +18 -4
- package/dist/agent-loop.js +8 -5
- package/dist/capabilities.d.ts +46 -2
- package/dist/capabilities.js +48 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/retry.d.ts +60 -0
- package/dist/retry.js +83 -0
- package/dist/snapshot.d.ts +11 -2
- package/dist/snapshot.js +18 -4
- package/llms.txt +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ only, Node >=22.
|
|
|
29
29
|
| `hooks` | The host's side of lifecycle hooks: `gather` before a request and `notify` after, the shared context budget, `withContext` to put what they add on the turn's question, `untrusted` to fence text nobody vouched for, and `turnMessages` to hand them a transcript. Running a hook is a runner the caller passes. |
|
|
30
30
|
| `events` | The in-memory bus a watcher reads while a run happens: `emit`, `watch`, `history`, `fold`, and `runMetrics` for what a run cost. A watcher's backlog is capped and reports its own gaps. |
|
|
31
31
|
| `client` | A pooled `OpenAI` client per endpoint, plus the context window: the served one where a local server says, the listed one otherwise, and their caches. |
|
|
32
|
-
| `retry` | What to do when a request is lost, refused or too big: `isTransient`, `isModelLoading`, `backoffMs`, `ContextOverflow`, `EndpointSilent`, `requestTokens`. |
|
|
32
|
+
| `retry` | What to do when a request is lost, refused or too big: `isTransient`, `isModelLoading`, `backoffMs`, `ContextOverflow`, `EndpointSilent`, `requestTokens`, `contextTokens`. |
|
|
33
33
|
| `calibration` | How many characters a token is worth on one model, learned from the prompt counts its endpoint reports: `charsPerTokenFor`, `calibrate`. |
|
|
34
34
|
| `continuation` | `continueTurn`: carries on an answer the token ceiling cut off, by prefilling it as a trailing assistant message. |
|
|
35
35
|
| `config` | The structural interfaces every function here asks for. |
|
|
@@ -314,6 +314,35 @@ for up to `loadingTimeoutMs` (two minutes by default, zero to turn it off) witho
|
|
|
314
314
|
`maxRetries`, with one notice at the start. `runAgentLoop` reads it as `loadingTimeoutSeconds` off
|
|
315
315
|
the config. A 503 that says nothing about loading stays on the ordinary backoff.
|
|
316
316
|
|
|
317
|
+
### What is filling the window
|
|
318
|
+
|
|
319
|
+
A total tells an operator a run is close to the edge and nothing about what to do next, so
|
|
320
|
+
`contextTokens` cuts the same body four ways, along the four levers there are: `system` is the
|
|
321
|
+
system and developer messages, which means shortening the prompt; `tools` is the declared schemas,
|
|
322
|
+
which means loading them on demand instead of declaring them whole; `toolResults` is exactly the
|
|
323
|
+
`tool` messages, which is precisely what `pruneToolResults` shrinks; and `history` is everything
|
|
324
|
+
else, which is what compaction folds, the arguments of the calls in it included.
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const { system, tools, history, toolResults, total } = contextTokens(body, {
|
|
328
|
+
charsPerToken: charsPerTokenFor(supports, config.model),
|
|
329
|
+
// Optional: what the endpoint said the prompt cost, once a turn has come back.
|
|
330
|
+
promptTokens: turn.usage?.prompt_tokens,
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The parts are shares of one total rather than four separate estimates, because a readout whose
|
|
335
|
+
parts do not add up to the number beside them is one nobody trusts; the largest part absorbs the
|
|
336
|
+
rounding, so they sum exactly. Nothing in the round trip reports anything finer than a prompt
|
|
337
|
+
count — a completion says how many tokens it read and not a word about where they came from — so
|
|
338
|
+
the proportions are a guess whatever the total is, and `contextChars` is there for a caller that
|
|
339
|
+
wants the exact characters underneath them.
|
|
340
|
+
|
|
341
|
+
Given a `promptTokens` the total is what was charged and every part is a share of it. Without one
|
|
342
|
+
the total is `requestTokens`, and the tool block is counted the way `requestTokens` and
|
|
343
|
+
`TurnMetrics.toolSchemaTokens` count it rather than shared out, so the breakdown and the metrics
|
|
344
|
+
line cannot disagree about the same tool list.
|
|
345
|
+
|
|
317
346
|
## The loop
|
|
318
347
|
|
|
319
348
|
`runAgentLoop` is the part of an agent that three servers had each written, and that had drifted
|
|
@@ -341,10 +370,17 @@ const { turn, messages, usage, loaded } = await runAgentLoop({
|
|
|
341
370
|
|
|
342
371
|
Each step is one `runTurn` with the body from `buildBody`, so everything `negotiate` answers is
|
|
343
372
|
answered here too, and a request that is too big throws `ContextOverflow` whichever side found
|
|
344
|
-
out. Between steps the loop runs the calls: sequentially by default, or together with
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
retry. What a tool throws is what the model
|
|
373
|
+
out. Between steps the loop runs the calls: sequentially by default, or together with `parallel: true`.
|
|
374
|
+
Either way an identical call — the same name and arguments, byte for byte — is made once and its
|
|
375
|
+
answer handed to the repeat, and two still in flight share the request. A call that threw is
|
|
376
|
+
forgotten rather than cached, so asking again is a real retry. What a tool throws is what the model
|
|
377
|
+
reads, and so are arguments that did not parse.
|
|
378
|
+
|
|
379
|
+
The scope is the step, not the run: between steps other tools have run, and the file the model read
|
|
380
|
+
may be the file it has since written. `dedupeToolCalls: false` dispatches everything, and a
|
|
381
|
+
predicate is asked per call — which is how `send_email` opts out, since twice is two emails and
|
|
382
|
+
nothing in an OpenAI tool definition says which tools those are. A pool that reads the MCP
|
|
383
|
+
`readOnlyHint` and `idempotentHint` annotations can answer it; this package cannot.
|
|
348
384
|
|
|
349
385
|
Arguments go through `parseToolArguments`, which is lenient where the model's meaning is plain:
|
|
350
386
|
JSON held in a string is opened, and the almost-JSON local models write — single quotes, Python's
|
|
@@ -353,7 +389,7 @@ string. What still is not an object throws a `ToolArgumentsError` whose `kind` i
|
|
|
353
389
|
the turn stopped at the ceiling, with a message telling the model so, and `malformed` otherwise.
|
|
354
390
|
The repaired JSON is what the transcript keeps, and an unreadable call is replayed as `{}`, because
|
|
355
391
|
a server that parses replayed arguments refuses the originals on every later request. `dispatch`
|
|
356
|
-
is still handed the model's own text as `raw`, and the
|
|
392
|
+
is still handed the model's own text as `raw`, and the dedupe compares repaired arguments,
|
|
357
393
|
so `{'a': 1}` and `{"a": 1}` are one call.
|
|
358
394
|
|
|
359
395
|
A server whose tool-call parser was written for another template streams the model's call as
|
|
@@ -799,7 +835,35 @@ A snapshot names endpoints by `endpointId`, a SHA-256 digest of the URL and key,
|
|
|
799
835
|
written to a settings row or a file without a credential going with it. Importing merges and only
|
|
800
836
|
latches off, the same as a refusal does. A snapshot of another `version` is ignored. How old is too
|
|
801
837
|
old is left to the consumer, who can read `savedAt` first: a server upgraded between boots may
|
|
802
|
-
accept what it used to refuse, and nothing latched ever unlatches
|
|
838
|
+
accept what it used to refuse, and nothing latched ever unlatches on its own.
|
|
839
|
+
|
|
840
|
+
It unlatches when you say so. A server upgraded behind the same URL — a newer llama.cpp that
|
|
841
|
+
compiles the grammar, a proxy that has learned `stream_options` — keeps being sent the downgraded
|
|
842
|
+
request until something forgets what it refused, and `resetCapabilities` takes the endpoint to
|
|
843
|
+
forget:
|
|
844
|
+
|
|
845
|
+
```ts
|
|
846
|
+
resetCapabilities({ baseUrl: row.baseUrl, apiKey: row.apiKey }); // this one changed
|
|
847
|
+
expireCapabilities(6 * 60 * 60_000); // anything half a day old
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
`resetCapabilities` with no argument still clears every endpoint, which is what `resetAll` and a
|
|
851
|
+
test mean by it; with one it clears that endpoint alone, so an upgraded local box does not cost the
|
|
852
|
+
cloud endpoint beside it its latches, and returns whether there was anything to forget. Call it
|
|
853
|
+
where the host already knows something changed: a settings row saved, a health check reading a new
|
|
854
|
+
build string, an operator pressing a button.
|
|
855
|
+
|
|
856
|
+
`expireCapabilities(maxAgeMs)` covers the case where nobody knows, dropping every endpoint older
|
|
857
|
+
than that and returning how many. An expiry does not probe anything — it stops suppressing, so the
|
|
858
|
+
next request carries the field again and a server that still refuses it refuses it once, which
|
|
859
|
+
`negotiate` answers as it always did. At an age measured in hours that is a few extra round trips a
|
|
860
|
+
day against a downgrade that would otherwise last as long as the process. Nothing calls it on a
|
|
861
|
+
timer; when to sweep is yours, the same way how stale a snapshot is too stale is.
|
|
862
|
+
|
|
863
|
+
An endpoint's age is when it was first met, not when a flag latched, and `exportCapabilities`
|
|
864
|
+
carries it in the snapshot so an imported latch keeps its real age instead of being born again on
|
|
865
|
+
every boot. Importing takes the older of the two ages, and a snapshot written before this field
|
|
866
|
+
existed reads as met now.
|
|
803
867
|
|
|
804
868
|
## Where the merged behaviour came from
|
|
805
869
|
|
package/dist/agent-loop.d.ts
CHANGED
|
@@ -152,12 +152,26 @@ export interface AgentLoopOptions {
|
|
|
152
152
|
/** Runs one tool call and returns what the model reads. What it throws, the model reads too. */
|
|
153
153
|
dispatch: (call: ToolCallRequest, signal?: AbortSignal) => Promise<string>;
|
|
154
154
|
/**
|
|
155
|
-
* Runs a step's calls together rather than one after another
|
|
156
|
-
*
|
|
157
|
-
* answer. A call that threw is not an answer and is made again. Results still go into the
|
|
158
|
-
* transcript in the order the model asked.
|
|
155
|
+
* Runs a step's calls together rather than one after another. Results still go into the
|
|
156
|
+
* transcript in the order the model asked. See `dedupeToolCalls`, which applies either way.
|
|
159
157
|
*/
|
|
160
158
|
parallel?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Answers an identical repeat of a call — the same name and the same arguments, word for word —
|
|
161
|
+
* within one step from the first one, rather than dispatching it again.
|
|
162
|
+
*
|
|
163
|
+
* On by default, and on whether or not the calls run in `parallel`: a model that asks the same
|
|
164
|
+
* question twice in one reply gets one answer, and two that are still in flight share the
|
|
165
|
+
* request. A call that threw is not an answer and is made again. The scope is the step and not
|
|
166
|
+
* the run, because between steps other tools have run and the file the model read may be the
|
|
167
|
+
* file it has since written.
|
|
168
|
+
*
|
|
169
|
+
* `false` dispatches every call. A predicate is asked per call and is how a tool that does
|
|
170
|
+
* something rather than reads something opts out — `send_email` twice is two emails, and this
|
|
171
|
+
* package cannot tell which tools those are. A pool that reads the MCP `readOnlyHint` and
|
|
172
|
+
* `idempotentHint` annotations can answer it; nothing in an OpenAI tool definition can.
|
|
173
|
+
*/
|
|
174
|
+
dedupeToolCalls?: boolean | ((call: ToolCallRequest) => boolean);
|
|
161
175
|
/** Hooks gathered onto the question before the first request, and told the reply after. */
|
|
162
176
|
hooks?: AgentLoopHooks;
|
|
163
177
|
/**
|
package/dist/agent-loop.js
CHANGED
|
@@ -202,7 +202,8 @@ function cacheDiagnosis(previous, messages, tools, usage) {
|
|
|
202
202
|
*/
|
|
203
203
|
export async function runAgentLoop(options) {
|
|
204
204
|
const { config, system = "", tools = [], catalog = [], dispatch, hooks, signal } = options;
|
|
205
|
-
const { onTurn, beforeStep, parallel = false, recoverToolCalls: recover = true, maxContinuations = 0, toolOrder = true, } = options;
|
|
205
|
+
const { onTurn, beforeStep, parallel = false, recoverToolCalls: recover = true, maxContinuations = 0, toolOrder = true, dedupeToolCalls = true, } = options;
|
|
206
|
+
const dedupable = typeof dedupeToolCalls === "function" ? dedupeToolCalls : () => dedupeToolCalls;
|
|
206
207
|
const started = Date.now();
|
|
207
208
|
// What the loop emitted, less the token deltas, for `runMetrics` at the end. Stamped here rather
|
|
208
209
|
// than by the bus, which the loop does not know about.
|
|
@@ -259,7 +260,6 @@ export async function runAgentLoop(options) {
|
|
|
259
260
|
: { context: "", notes: [] };
|
|
260
261
|
const usage = { prompt: 0, completion: 0, total: 0, cached: 0 };
|
|
261
262
|
const toolCalls = [];
|
|
262
|
-
const answered = new Map();
|
|
263
263
|
const loads = { toolsLoaded: 0, redundantLoads: 0, unknownToolNames: 0 };
|
|
264
264
|
let previous;
|
|
265
265
|
for (let step = 0; step < config.maxToolIterations; step++) {
|
|
@@ -411,6 +411,9 @@ export async function runAgentLoop(options) {
|
|
|
411
411
|
},
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
|
+
// Per step, not per run: the answer to a call made two steps ago was true before the tools in
|
|
415
|
+
// between ran, and the file the model read may be the file it has since written.
|
|
416
|
+
const answered = new Map();
|
|
414
417
|
const run = async ({ call, args, error: unreadable, normal }) => {
|
|
415
418
|
const { name, arguments: raw } = call.function;
|
|
416
419
|
onEvent({ kind: "tool-call", name, text: preview(raw) });
|
|
@@ -436,7 +439,7 @@ export async function runAgentLoop(options) {
|
|
|
436
439
|
loaded.add(name);
|
|
437
440
|
used.add(name);
|
|
438
441
|
const request = { id: call.id, name, args, raw };
|
|
439
|
-
content =
|
|
442
|
+
content = dedupable(request)
|
|
440
443
|
? await once(answered, `${name}\0${normal}`, () => dispatch(request, signal))
|
|
441
444
|
: await dispatch(request, signal);
|
|
442
445
|
}
|
|
@@ -470,8 +473,8 @@ export async function runAgentLoop(options) {
|
|
|
470
473
|
}
|
|
471
474
|
/**
|
|
472
475
|
* Makes a call at most once per key, sharing the in-flight promise so two identical calls in one
|
|
473
|
-
* step make one request between them
|
|
474
|
-
* real retry rather than a replayed failure.
|
|
476
|
+
* step make one request between them whether they run together or one after the other. A call
|
|
477
|
+
* that rejected is forgotten, so asking again is a real retry rather than a replayed failure.
|
|
475
478
|
*/
|
|
476
479
|
async function once(answered, key, make) {
|
|
477
480
|
const previous = answered.get(key);
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -35,6 +35,17 @@ export interface Capabilities {
|
|
|
35
35
|
* stopped. Empty until `modelCapabilitiesFor` is asked about a model.
|
|
36
36
|
*/
|
|
37
37
|
models: Map<string, ModelCapabilities>;
|
|
38
|
+
/**
|
|
39
|
+
* When this entry was opened, as epoch milliseconds: first contact with the endpoint in this
|
|
40
|
+
* process, or the age an imported snapshot gave it. What `expireCapabilities` measures.
|
|
41
|
+
*
|
|
42
|
+
* Not when a flag latched. A flag that latches at all almost always does so on the first
|
|
43
|
+
* request or two, and an entry that latched nothing has nothing to expire, so the difference
|
|
44
|
+
* costs at most one retried field somewhat earlier than it was due — which is the direction to
|
|
45
|
+
* be wrong in. Stamping each latch instead would mean stamping it in four modules and
|
|
46
|
+
* remembering to in the fifth.
|
|
47
|
+
*/
|
|
48
|
+
since: number;
|
|
38
49
|
}
|
|
39
50
|
/**
|
|
40
51
|
* What one model on that endpoint turned out not to support. All start optimistic and only ever
|
|
@@ -124,8 +135,41 @@ export declare const knownCapabilities: () => ReadonlyMap<string, Capabilities>;
|
|
|
124
135
|
* `importCapabilities` reaches an endpoint it has only a digest for.
|
|
125
136
|
*/
|
|
126
137
|
export declare function capabilitiesById(id: string): Capabilities;
|
|
127
|
-
/**
|
|
128
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Forgets what one endpoint refused, or every endpoint's when told none.
|
|
140
|
+
*
|
|
141
|
+
* A latch never unlatches on its own, so a server upgraded behind the same URL — a newer
|
|
142
|
+
* llama.cpp that compiles the grammar, a proxy that has learned `stream_options` — keeps being
|
|
143
|
+
* sent the downgraded request for the life of the process. This is the seam for a consumer that
|
|
144
|
+
* *knows* it changed: a settings row saved, a health check that reads a new build string, an
|
|
145
|
+
* operator pressing a button. See `expireCapabilities` for the case where nobody knows.
|
|
146
|
+
*
|
|
147
|
+
* @param endpoint Whose to forget, by the same identity `capabilitiesFor` takes. Absent clears
|
|
148
|
+
* every endpoint, which is what tests and `resetAll` mean by it.
|
|
149
|
+
* @returns Whether there was anything to forget.
|
|
150
|
+
*/
|
|
151
|
+
export declare function resetCapabilities(endpoint?: {
|
|
152
|
+
baseUrl: string;
|
|
153
|
+
apiKey?: string;
|
|
154
|
+
}): boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Forgets every endpoint whose entry is older than this, so the next request finds out again.
|
|
157
|
+
*
|
|
158
|
+
* The other half of the problem `resetCapabilities` solves: a server upgraded behind the same URL
|
|
159
|
+
* with nobody to notice. What an expiry costs is one round trip per endpoint and model — the next
|
|
160
|
+
* request carries the field again, and a server that still refuses it refuses it once and
|
|
161
|
+
* `negotiate` re-sends — so at an age measured in hours it is a few requests a day against a
|
|
162
|
+
* downgrade that would otherwise last as long as the process. That is the trade `exportCapabilities`
|
|
163
|
+
* exists to avoid paying *per restart*; paying it per day is a different bargain.
|
|
164
|
+
*
|
|
165
|
+
* Nothing calls this on a timer. When to sweep is the consumer's, the same way how stale a
|
|
166
|
+
* snapshot is too stale is, and a sweep costs a walk of one settings row's worth of entries.
|
|
167
|
+
*
|
|
168
|
+
* @param maxAgeMs How old an entry may be. Zero or less expires everything.
|
|
169
|
+
* @param now The clock, for tests.
|
|
170
|
+
* @returns How many endpoints were forgotten.
|
|
171
|
+
*/
|
|
172
|
+
export declare function expireCapabilities(maxAgeMs: number, now?: number): number;
|
|
129
173
|
/** What `negotiate` takes besides the request. All optional. */
|
|
130
174
|
export interface NegotiateOptions {
|
|
131
175
|
/**
|
package/dist/capabilities.js
CHANGED
|
@@ -71,14 +71,58 @@ export const knownCapabilities = () => capabilities;
|
|
|
71
71
|
export function capabilitiesById(id) {
|
|
72
72
|
let known = capabilities.get(id);
|
|
73
73
|
if (!known) {
|
|
74
|
-
known = { strictSchemas: true, usageInStream: true, models: new Map() };
|
|
74
|
+
known = { strictSchemas: true, usageInStream: true, models: new Map(), since: Date.now() };
|
|
75
75
|
capabilities.set(id, known);
|
|
76
76
|
}
|
|
77
77
|
return known;
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Forgets what one endpoint refused, or every endpoint's when told none.
|
|
81
|
+
*
|
|
82
|
+
* A latch never unlatches on its own, so a server upgraded behind the same URL — a newer
|
|
83
|
+
* llama.cpp that compiles the grammar, a proxy that has learned `stream_options` — keeps being
|
|
84
|
+
* sent the downgraded request for the life of the process. This is the seam for a consumer that
|
|
85
|
+
* *knows* it changed: a settings row saved, a health check that reads a new build string, an
|
|
86
|
+
* operator pressing a button. See `expireCapabilities` for the case where nobody knows.
|
|
87
|
+
*
|
|
88
|
+
* @param endpoint Whose to forget, by the same identity `capabilitiesFor` takes. Absent clears
|
|
89
|
+
* every endpoint, which is what tests and `resetAll` mean by it.
|
|
90
|
+
* @returns Whether there was anything to forget.
|
|
91
|
+
*/
|
|
92
|
+
export function resetCapabilities(endpoint) {
|
|
93
|
+
if (!endpoint) {
|
|
94
|
+
const held = capabilities.size > 0;
|
|
95
|
+
capabilities.clear();
|
|
96
|
+
return held;
|
|
97
|
+
}
|
|
98
|
+
return capabilities.delete(endpointId(endpoint));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Forgets every endpoint whose entry is older than this, so the next request finds out again.
|
|
102
|
+
*
|
|
103
|
+
* The other half of the problem `resetCapabilities` solves: a server upgraded behind the same URL
|
|
104
|
+
* with nobody to notice. What an expiry costs is one round trip per endpoint and model — the next
|
|
105
|
+
* request carries the field again, and a server that still refuses it refuses it once and
|
|
106
|
+
* `negotiate` re-sends — so at an age measured in hours it is a few requests a day against a
|
|
107
|
+
* downgrade that would otherwise last as long as the process. That is the trade `exportCapabilities`
|
|
108
|
+
* exists to avoid paying *per restart*; paying it per day is a different bargain.
|
|
109
|
+
*
|
|
110
|
+
* Nothing calls this on a timer. When to sweep is the consumer's, the same way how stale a
|
|
111
|
+
* snapshot is too stale is, and a sweep costs a walk of one settings row's worth of entries.
|
|
112
|
+
*
|
|
113
|
+
* @param maxAgeMs How old an entry may be. Zero or less expires everything.
|
|
114
|
+
* @param now The clock, for tests.
|
|
115
|
+
* @returns How many endpoints were forgotten.
|
|
116
|
+
*/
|
|
117
|
+
export function expireCapabilities(maxAgeMs, now = Date.now()) {
|
|
118
|
+
let dropped = 0;
|
|
119
|
+
for (const [id, known] of capabilities) {
|
|
120
|
+
if (now - known.since < maxAgeMs)
|
|
121
|
+
continue;
|
|
122
|
+
capabilities.delete(id);
|
|
123
|
+
dropped++;
|
|
124
|
+
}
|
|
125
|
+
return dropped;
|
|
82
126
|
}
|
|
83
127
|
/**
|
|
84
128
|
* Every latching flag in play on one attempt, endpoint and model together, in a stable order.
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { type AgentLoopHooks, type AgentLoopOptions, type AgentLoopResult, buildBody, preselect, preview, resolveApiKey, runAgentLoop, type ToolCallOutcome, type ToolCallRequest, } from "./agent-loop.ts";
|
|
13
13
|
export { calibrate, charsPerTokenFor, resetCalibration } from "./calibration.ts";
|
|
14
|
-
export { type Capabilities, capabilitiesFor, type ModelCapabilities, modelCapabilitiesFor, type NegotiateOptions, negotiate, resetCapabilities, } from "./capabilities.ts";
|
|
14
|
+
export { type Capabilities, capabilitiesFor, expireCapabilities, type ModelCapabilities, modelCapabilitiesFor, type NegotiateOptions, negotiate, resetCapabilities, } from "./capabilities.ts";
|
|
15
15
|
export type { CatalogServer } from "./catalog.ts";
|
|
16
16
|
export { type ClientPoolOptions, configureClients, contextLimitFor, endpointId, endpointKey, FIRST_TOKEN_FACTOR, firstTokenMs, getClient, listModels, type ModelInfo, NO_KEY, resetClients, servedWindow, timeoutMs, } from "./client.ts";
|
|
17
17
|
export { applyCompaction, COMPACT_AT, type CompactionOptions, type CompactionPlan, type CompactionRecord, type CompactionRunOptions, compactTranscript, KEEP_RATIO, type PruneOptions, planCompaction, pruneToolResults, requestIndex, runCompaction, SUMMARY_LEAD, SUMMARY_PROMPT, summariser, summaryInput, } from "./compaction.ts";
|
|
@@ -21,7 +21,7 @@ export { errorMessage } from "./errors.ts";
|
|
|
21
21
|
export { configureEvents, type EventBusOptions, emit, endRun, fold, history, type RunEvent, type RunEventInput, type RunEventKind, type RunMetrics, type RunMetricsOptions, type RunUsage, resetEvents, runMetrics, type TurnReport, watch, } from "./events.ts";
|
|
22
22
|
export { assembleContext, configureHooks, consult, type Gathered, gather, HOOK_CONTEXT_TOKENS, HOOK_EVENTS, HOOK_PREFACE, type HookContext, type HookEvent, type HookMessage, type HookNote, type HookOptions, type HookOutcome, type HookRunner, INJECT_EVENTS, notify, resetHooks, turnIndex, turnMessages, UNTRUSTED_PREFACE, untrusted, withContext, } from "./hooks.ts";
|
|
23
23
|
export { resetAll } from "./reset.ts";
|
|
24
|
-
export { backoffMs, CHARS_PER_TOKEN, ContextOverflow, compact, EndpointSilent, isModelLoading, isOverflow, isTransient, LOADING_POLL_MS, LOADING_TIMEOUT_MS, messageTokens, requestChars, requestTokens, SMALLEST_LIKELY_WINDOW, sleep, type TokenEstimateOptions, toolsChars, } from "./retry.ts";
|
|
24
|
+
export { backoffMs, CHARS_PER_TOKEN, type ContextBreakdown, type ContextBreakdownOptions, ContextOverflow, compact, contextChars, contextTokens, EndpointSilent, isModelLoading, isOverflow, isTransient, LOADING_POLL_MS, LOADING_TIMEOUT_MS, messageTokens, requestChars, requestTokens, SMALLEST_LIKELY_WINDOW, sleep, type TokenEstimateOptions, toolsChars, } from "./retry.ts";
|
|
25
25
|
export { type RunTurnOptions, runTurn } from "./run-turn.ts";
|
|
26
26
|
export { isGrammarError, relaxTools, sanitizeTools } from "./schema-compat.ts";
|
|
27
27
|
export { type AskJsonOptions, ask, askJson, clean, listLines, parseJson, resetHints, type SideTaskInput, type SideTaskOptions, tryAsk, } from "./side-task.ts";
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { buildBody, preselect, preview, resolveApiKey, runAgentLoop, } from "./agent-loop.js";
|
|
13
13
|
export { calibrate, charsPerTokenFor, resetCalibration } from "./calibration.js";
|
|
14
|
-
export { capabilitiesFor, modelCapabilitiesFor, negotiate, resetCapabilities, } from "./capabilities.js";
|
|
14
|
+
export { capabilitiesFor, expireCapabilities, modelCapabilitiesFor, negotiate, resetCapabilities, } from "./capabilities.js";
|
|
15
15
|
export { configureClients, contextLimitFor, endpointId, endpointKey, FIRST_TOKEN_FACTOR, firstTokenMs, getClient, listModels, NO_KEY, resetClients, servedWindow, timeoutMs, } from "./client.js";
|
|
16
16
|
export { applyCompaction, COMPACT_AT, compactTranscript, KEEP_RATIO, planCompaction, pruneToolResults, requestIndex, runCompaction, SUMMARY_LEAD, SUMMARY_PROMPT, summariser, summaryInput, } from "./compaction.js";
|
|
17
17
|
export { continueTurn, isContinuable, } from "./continuation.js";
|
|
@@ -19,7 +19,7 @@ export { errorMessage } from "./errors.js";
|
|
|
19
19
|
export { configureEvents, emit, endRun, fold, history, resetEvents, runMetrics, watch, } from "./events.js";
|
|
20
20
|
export { assembleContext, configureHooks, consult, gather, HOOK_CONTEXT_TOKENS, HOOK_EVENTS, HOOK_PREFACE, INJECT_EVENTS, notify, resetHooks, turnIndex, turnMessages, UNTRUSTED_PREFACE, untrusted, withContext, } from "./hooks.js";
|
|
21
21
|
export { resetAll } from "./reset.js";
|
|
22
|
-
export { backoffMs, CHARS_PER_TOKEN, ContextOverflow, compact, EndpointSilent, isModelLoading, isOverflow, isTransient, LOADING_POLL_MS, LOADING_TIMEOUT_MS, messageTokens, requestChars, requestTokens, SMALLEST_LIKELY_WINDOW, sleep, toolsChars, } from "./retry.js";
|
|
22
|
+
export { backoffMs, CHARS_PER_TOKEN, ContextOverflow, compact, contextChars, contextTokens, EndpointSilent, isModelLoading, isOverflow, isTransient, LOADING_POLL_MS, LOADING_TIMEOUT_MS, messageTokens, requestChars, requestTokens, SMALLEST_LIKELY_WINDOW, sleep, toolsChars, } from "./retry.js";
|
|
23
23
|
export { runTurn } from "./run-turn.js";
|
|
24
24
|
export { isGrammarError, relaxTools, sanitizeTools } from "./schema-compat.js";
|
|
25
25
|
export { ask, askJson, clean, listLines, parseJson, resetHints, tryAsk, } from "./side-task.js";
|
package/dist/retry.d.ts
CHANGED
|
@@ -79,6 +79,66 @@ export declare function requestChars(body: OpenAI.ChatCompletionCreateParamsStre
|
|
|
79
79
|
* @param options The divisor, `CHARS_PER_TOKEN` when none is given.
|
|
80
80
|
*/
|
|
81
81
|
export declare const requestTokens: (body: OpenAI.ChatCompletionCreateParamsStreaming, { charsPerToken }?: TokenEstimateOptions) => number;
|
|
82
|
+
/**
|
|
83
|
+
* What a request is made of, by the part of it a consumer can actually do something about.
|
|
84
|
+
*
|
|
85
|
+
* The question an operator asks is not how big the request is — the total already answers that —
|
|
86
|
+
* but what is filling the window, and the only useful answer names a lever: a system prompt to
|
|
87
|
+
* shorten, a tool list to load on demand instead of declaring whole, a transcript to compact,
|
|
88
|
+
* results to prune. So the cut follows the levers rather than the roles: `toolResults` is exactly
|
|
89
|
+
* what `pruneToolResults` can shrink, and `history` is everything `planCompaction` folds, the
|
|
90
|
+
* arguments of the calls in it included.
|
|
91
|
+
*/
|
|
92
|
+
export interface ContextBreakdown {
|
|
93
|
+
/** Every system and developer message, wherever it sits in the transcript. */
|
|
94
|
+
system: number;
|
|
95
|
+
/** The declared tool schemas, which a chat template renders ahead of the system prompt. */
|
|
96
|
+
tools: number;
|
|
97
|
+
/** What was said: the user and assistant messages, and the calls the assistant asked for. */
|
|
98
|
+
history: number;
|
|
99
|
+
/** What the tools handed back — the `tool` messages, and nothing else. */
|
|
100
|
+
toolResults: number;
|
|
101
|
+
/** The four above, summed. */
|
|
102
|
+
total: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* What each part of a request is worth in characters, by the same walk `requestTokens` divides.
|
|
106
|
+
*
|
|
107
|
+
* Exact and additive: the parts sum to `total`, which is `requestChars` plus `toolsChars`. The
|
|
108
|
+
* conversion to tokens is `contextTokens`' business, because that is where an estimate and a
|
|
109
|
+
* reported count have to be told apart.
|
|
110
|
+
*
|
|
111
|
+
* @param body The request as it will be sent, tools included.
|
|
112
|
+
*/
|
|
113
|
+
export declare function contextChars(body: OpenAI.ChatCompletionCreateParamsStreaming): ContextBreakdown;
|
|
114
|
+
/** What `contextTokens` takes besides the request. */
|
|
115
|
+
export interface ContextBreakdownOptions extends TokenEstimateOptions {
|
|
116
|
+
/**
|
|
117
|
+
* The prompt count the endpoint reported for this request, if it has answered. Given one, the
|
|
118
|
+
* parts are shares of it and the breakdown sums to what was actually charged rather than to an
|
|
119
|
+
* estimate; left out, they are shares of `requestTokens`.
|
|
120
|
+
*/
|
|
121
|
+
promptTokens?: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* What each part of a request costs the window, in tokens, adding up to the whole.
|
|
125
|
+
*
|
|
126
|
+
* Shares rather than four independent estimates, because a readout whose parts do not add up to
|
|
127
|
+
* the total beside them is a readout nobody trusts. Nothing in the round trip reports anything
|
|
128
|
+
* finer than a prompt count — a completion says how many tokens it read and not a word about
|
|
129
|
+
* where they came from — so the proportions are an estimate whatever the total is.
|
|
130
|
+
*
|
|
131
|
+
* Without a reported count the total is `requestTokens`, and the tools are counted the way it and
|
|
132
|
+
* `TurnMetrics.toolSchemaTokens` count them rather than shared out, so the two agree by
|
|
133
|
+
* construction and an operator does not read one number for the tool block in the metrics and a
|
|
134
|
+
* different one here. With a reported count every part is a share of it, the tools included:
|
|
135
|
+
* that number is the server's, and the point of using it is that the parts sum to what was
|
|
136
|
+
* charged.
|
|
137
|
+
*
|
|
138
|
+
* @param body The request as it will be sent, tools included.
|
|
139
|
+
* @param options The divisor, and the reported prompt count when there is one.
|
|
140
|
+
*/
|
|
141
|
+
export declare function contextTokens(body: OpenAI.ChatCompletionCreateParamsStreaming, { charsPerToken, promptTokens }?: ContextBreakdownOptions): ContextBreakdown;
|
|
82
142
|
/**
|
|
83
143
|
* One message's estimated tokens, by the same count `requestTokens` sums for a whole request.
|
|
84
144
|
*
|
package/dist/retry.js
CHANGED
|
@@ -183,6 +183,89 @@ export const requestTokens = (body, { charsPerToken } = {}) => {
|
|
|
183
183
|
const per = divisor(charsPerToken);
|
|
184
184
|
return Math.ceil(requestChars(body) / per) + Math.ceil(toolsChars(body.tools ?? []) / per);
|
|
185
185
|
};
|
|
186
|
+
/** The parts, in the order a readout reads them. */
|
|
187
|
+
const PARTS = ["system", "tools", "history", "toolResults"];
|
|
188
|
+
/**
|
|
189
|
+
* What each part of a request is worth in characters, by the same walk `requestTokens` divides.
|
|
190
|
+
*
|
|
191
|
+
* Exact and additive: the parts sum to `total`, which is `requestChars` plus `toolsChars`. The
|
|
192
|
+
* conversion to tokens is `contextTokens`' business, because that is where an estimate and a
|
|
193
|
+
* reported count have to be told apart.
|
|
194
|
+
*
|
|
195
|
+
* @param body The request as it will be sent, tools included.
|
|
196
|
+
*/
|
|
197
|
+
export function contextChars(body) {
|
|
198
|
+
const out = {
|
|
199
|
+
system: 0,
|
|
200
|
+
tools: toolsChars(body.tools ?? []),
|
|
201
|
+
history: 0,
|
|
202
|
+
toolResults: 0,
|
|
203
|
+
total: 0,
|
|
204
|
+
};
|
|
205
|
+
for (const message of body.messages) {
|
|
206
|
+
const chars = messageChars(message);
|
|
207
|
+
// Every system message and not just the leading one: a host that appends guidance, or a
|
|
208
|
+
// hook that injects a preface, has put more of the window there and wants to be told so.
|
|
209
|
+
if (message.role === "system" || message.role === "developer")
|
|
210
|
+
out.system += chars;
|
|
211
|
+
else if (message.role === "tool")
|
|
212
|
+
out.toolResults += chars;
|
|
213
|
+
else
|
|
214
|
+
out.history += chars;
|
|
215
|
+
}
|
|
216
|
+
out.total = out.system + out.tools + out.history + out.toolResults;
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Shares `total` out over these parts by their character counts, the largest absorbing the
|
|
221
|
+
* rounding so they add up to it exactly rather than to within a few tokens of it.
|
|
222
|
+
*/
|
|
223
|
+
function share(chars, over, total) {
|
|
224
|
+
const out = { system: 0, tools: 0, history: 0, toolResults: 0, total };
|
|
225
|
+
const measured = over.reduce((sum, part) => sum + chars[part], 0);
|
|
226
|
+
if (measured <= 0 || total <= 0)
|
|
227
|
+
return out;
|
|
228
|
+
const absorber = over.reduce((a, b) => (chars[b] > chars[a] ? b : a));
|
|
229
|
+
let assigned = 0;
|
|
230
|
+
for (const part of over) {
|
|
231
|
+
if (part === absorber)
|
|
232
|
+
continue;
|
|
233
|
+
out[part] = Math.round((chars[part] / measured) * total);
|
|
234
|
+
assigned += out[part];
|
|
235
|
+
}
|
|
236
|
+
out[absorber] = Math.max(0, total - assigned);
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* What each part of a request costs the window, in tokens, adding up to the whole.
|
|
241
|
+
*
|
|
242
|
+
* Shares rather than four independent estimates, because a readout whose parts do not add up to
|
|
243
|
+
* the total beside them is a readout nobody trusts. Nothing in the round trip reports anything
|
|
244
|
+
* finer than a prompt count — a completion says how many tokens it read and not a word about
|
|
245
|
+
* where they came from — so the proportions are an estimate whatever the total is.
|
|
246
|
+
*
|
|
247
|
+
* Without a reported count the total is `requestTokens`, and the tools are counted the way it and
|
|
248
|
+
* `TurnMetrics.toolSchemaTokens` count them rather than shared out, so the two agree by
|
|
249
|
+
* construction and an operator does not read one number for the tool block in the metrics and a
|
|
250
|
+
* different one here. With a reported count every part is a share of it, the tools included:
|
|
251
|
+
* that number is the server's, and the point of using it is that the parts sum to what was
|
|
252
|
+
* charged.
|
|
253
|
+
*
|
|
254
|
+
* @param body The request as it will be sent, tools included.
|
|
255
|
+
* @param options The divisor, and the reported prompt count when there is one.
|
|
256
|
+
*/
|
|
257
|
+
export function contextTokens(body, { charsPerToken, promptTokens } = {}) {
|
|
258
|
+
const chars = contextChars(body);
|
|
259
|
+
if (promptTokens !== undefined && promptTokens > 0)
|
|
260
|
+
return share(chars, PARTS, promptTokens);
|
|
261
|
+
const per = divisor(charsPerToken);
|
|
262
|
+
const tools = Math.ceil(chars.tools / per);
|
|
263
|
+
const rest = Math.ceil((chars.total - chars.tools) / per);
|
|
264
|
+
const out = share(chars, ["system", "history", "toolResults"], rest);
|
|
265
|
+
out.tools = tools;
|
|
266
|
+
out.total = rest + tools;
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
186
269
|
/**
|
|
187
270
|
* One message's estimated tokens, by the same count `requestTokens` sums for a whole request.
|
|
188
271
|
*
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface EndpointSnapshot {
|
|
|
28
28
|
strictSchemas: boolean;
|
|
29
29
|
usageInStream: boolean;
|
|
30
30
|
models: Record<string, ModelSnapshot>;
|
|
31
|
+
/**
|
|
32
|
+
* When this endpoint was first met, as epoch milliseconds, so `expireCapabilities` measures a
|
|
33
|
+
* latch from when it was learned rather than from the boot that imported it. Absent in a snapshot
|
|
34
|
+
* taken before it was written, which reads as met now — the behaviour of every release until this
|
|
35
|
+
* one, and the reason no version bump is owed.
|
|
36
|
+
*/
|
|
37
|
+
since?: number;
|
|
31
38
|
}
|
|
32
39
|
/** Every latched refusal in the process, JSON-safe. See `exportCapabilities`. */
|
|
33
40
|
export interface CapabilitySnapshot {
|
|
@@ -43,7 +50,8 @@ export interface CapabilitySnapshot {
|
|
|
43
50
|
* Covers what `negotiate` latches on endpoints and models and the models `ask` found refusing the
|
|
44
51
|
* no-thinking hints. Only what was actually refused is in it, so a snapshot of a process that met
|
|
45
52
|
* no refusals has no endpoints. Endpoints are named by digest rather than URL and key, since the
|
|
46
|
-
* blob is meant to be written somewhere and a key must not be written with it.
|
|
53
|
+
* blob is meant to be written somewhere and a key must not be written with it. Each carries the
|
|
54
|
+
* `since` it was learned at, so importing it does not make an old latch young again.
|
|
47
55
|
*/
|
|
48
56
|
export declare function exportCapabilities(): CapabilitySnapshot;
|
|
49
57
|
/**
|
|
@@ -53,7 +61,8 @@ export declare function exportCapabilities(): CapabilitySnapshot;
|
|
|
53
61
|
* off whatever the snapshot says, and one the snapshot has off is turned off. A snapshot of another
|
|
54
62
|
* version, or anything that is not one, is ignored — a stale shape costs the refused requests it
|
|
55
63
|
* would have saved, which is what a restart cost before. How old is too old is the consumer's call,
|
|
56
|
-
* made on `savedAt` before importing,
|
|
64
|
+
* made on `savedAt` before importing, or afterwards per endpoint with `expireCapabilities`, since a
|
|
65
|
+
* server behind a URL can be upgraded between boots.
|
|
57
66
|
*
|
|
58
67
|
* @param snapshot What `exportCapabilities` returned, as stored. Read defensively: a field of the
|
|
59
68
|
* wrong type is skipped rather than trusted.
|
package/dist/snapshot.js
CHANGED
|
@@ -32,13 +32,21 @@ const refusedAnything = (model) => !model.reasoningEffort ||
|
|
|
32
32
|
* Covers what `negotiate` latches on endpoints and models and the models `ask` found refusing the
|
|
33
33
|
* no-thinking hints. Only what was actually refused is in it, so a snapshot of a process that met
|
|
34
34
|
* no refusals has no endpoints. Endpoints are named by digest rather than URL and key, since the
|
|
35
|
-
* blob is meant to be written somewhere and a key must not be written with it.
|
|
35
|
+
* blob is meant to be written somewhere and a key must not be written with it. Each carries the
|
|
36
|
+
* `since` it was learned at, so importing it does not make an old latch young again.
|
|
36
37
|
*/
|
|
37
38
|
export function exportCapabilities() {
|
|
38
39
|
const endpoints = {};
|
|
39
40
|
const entry = (id) => {
|
|
40
|
-
endpoints[id]
|
|
41
|
-
|
|
41
|
+
const held = endpoints[id];
|
|
42
|
+
if (held)
|
|
43
|
+
return held;
|
|
44
|
+
const fresh = { strictSchemas: true, usageInStream: true, models: {} };
|
|
45
|
+
const since = knownCapabilities().get(id)?.since;
|
|
46
|
+
if (since !== undefined)
|
|
47
|
+
fresh.since = since;
|
|
48
|
+
endpoints[id] = fresh;
|
|
49
|
+
return fresh;
|
|
42
50
|
};
|
|
43
51
|
for (const [id, supports] of knownCapabilities()) {
|
|
44
52
|
const models = {};
|
|
@@ -79,7 +87,8 @@ const isRecord = (value) => typeof value === "object" && value !== null && !Arra
|
|
|
79
87
|
* off whatever the snapshot says, and one the snapshot has off is turned off. A snapshot of another
|
|
80
88
|
* version, or anything that is not one, is ignored — a stale shape costs the refused requests it
|
|
81
89
|
* would have saved, which is what a restart cost before. How old is too old is the consumer's call,
|
|
82
|
-
* made on `savedAt` before importing,
|
|
90
|
+
* made on `savedAt` before importing, or afterwards per endpoint with `expireCapabilities`, since a
|
|
91
|
+
* server behind a URL can be upgraded between boots.
|
|
83
92
|
*
|
|
84
93
|
* @param snapshot What `exportCapabilities` returned, as stored. Read defensively: a field of the
|
|
85
94
|
* wrong type is skipped rather than trusted.
|
|
@@ -98,6 +107,11 @@ export function importCapabilities(snapshot) {
|
|
|
98
107
|
supports.strictSchemas = false;
|
|
99
108
|
if (endpoint.usageInStream === false)
|
|
100
109
|
supports.usageInStream = false;
|
|
110
|
+
// Older of the two, so a snapshot ages an entry and never rejuvenates one: importing must not
|
|
111
|
+
// be a way to keep a latch from ever reaching `expireCapabilities`.
|
|
112
|
+
if (typeof endpoint.since === "number" && endpoint.since < supports.since) {
|
|
113
|
+
supports.since = endpoint.since;
|
|
114
|
+
}
|
|
101
115
|
if (!isRecord(endpoint.models))
|
|
102
116
|
continue;
|
|
103
117
|
for (const [name, model] of Object.entries(endpoint.models)) {
|
package/llms.txt
CHANGED
|
@@ -38,11 +38,12 @@ What an endpoint turned out not to support, and answering it when it says so.
|
|
|
38
38
|
|
|
39
39
|
- `Capabilities` (type) — What one endpoint turned out not to support.
|
|
40
40
|
- `capabilitiesFor` — What this endpoint is known not to support.
|
|
41
|
+
- `expireCapabilities` — Forgets every endpoint whose entry is older than this, so the next request finds out again.
|
|
41
42
|
- `ModelCapabilities` (type) — What one model on that endpoint turned out not to support.
|
|
42
43
|
- `modelCapabilitiesFor` — What this model on this endpoint is known not to support.
|
|
43
44
|
- `NegotiateOptions` (type) — What `negotiate` takes besides the request.
|
|
44
45
|
- `negotiate` — Sends a request, re-sending it each time the answer is this endpoint refusing something the request can do without.
|
|
45
|
-
- `resetCapabilities` — Forgets every endpoint's
|
|
46
|
+
- `resetCapabilities` — Forgets what one endpoint refused, or every endpoint's when told none.
|
|
46
47
|
|
|
47
48
|
### catalog
|
|
48
49
|
|
|
@@ -170,8 +171,12 @@ Everything about a request failing that is not about what the request said.
|
|
|
170
171
|
|
|
171
172
|
- `backoffMs` — Exponential, with jitter so several tasks failing at once do not return in lockstep.
|
|
172
173
|
- `CHARS_PER_TOKEN` — The divisor behind `estimateTokens`, applied here to a character count rather than a string.
|
|
174
|
+
- `ContextBreakdown` (type) — What a request is made of, by the part of it a consumer can actually do something about.
|
|
175
|
+
- `ContextBreakdownOptions` (type) — What `contextTokens` takes besides the request.
|
|
173
176
|
- `ContextOverflow` — The request was bigger than the model will read.
|
|
174
177
|
- `compact` — 1234 → "1.2k".
|
|
178
|
+
- `contextChars` — What each part of a request is worth in characters, by the same walk `requestTokens` divides.
|
|
179
|
+
- `contextTokens` — What each part of a request costs the window, in tokens, adding up to the whole.
|
|
175
180
|
- `EndpointSilent` — The endpoint stopped answering mid-request.
|
|
176
181
|
- `isModelLoading` — Whether a failure is a local server still loading the model, rather than one failing to serve.
|
|
177
182
|
- `isOverflow` — Whether a refusal means the request was too big, rather than merely refused.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubicecho/agent-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.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",
|