@cubicecho/agent-core 2.24.0 → 2.25.1
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 +10 -7
- package/dist/agent-loop.d.ts +84 -205
- package/dist/agent-loop.js +288 -597
- package/dist/calibration.d.ts +14 -10
- package/dist/calibration.js +30 -23
- package/dist/capabilities.d.ts +111 -42
- package/dist/capabilities.js +281 -138
- package/dist/client.d.ts +104 -35
- package/dist/client.js +205 -74
- package/dist/compaction.d.ts +103 -60
- package/dist/compaction.js +160 -86
- package/dist/config.d.ts +48 -12
- package/dist/config.js +20 -1
- package/dist/continuation.d.ts +24 -16
- package/dist/continuation.js +70 -44
- package/dist/digest.d.ts +7 -0
- package/dist/digest.js +18 -0
- package/dist/errors.d.ts +24 -15
- package/dist/errors.js +23 -15
- package/dist/events.d.ts +100 -63
- package/dist/events.js +250 -120
- package/dist/guards.d.ts +49 -4
- package/dist/guards.js +60 -5
- package/dist/hook-events.d.ts +21 -5
- package/dist/hook-events.js +23 -9
- package/dist/hooks.d.ts +96 -73
- package/dist/hooks.js +202 -116
- package/dist/index.d.ts +30 -26
- package/dist/index.js +29 -24
- package/dist/ledger.d.ts +62 -30
- package/dist/ledger.js +163 -56
- package/dist/platform.d.ts +15 -0
- package/dist/platform.js +15 -0
- package/dist/preselect.d.ts +223 -0
- package/dist/preselect.js +291 -0
- package/dist/request-body.d.ts +32 -0
- package/dist/request-body.js +47 -0
- package/dist/reset.d.ts +1 -0
- package/dist/reset.js +7 -6
- package/dist/retry.d.ts +44 -138
- package/dist/retry.js +83 -281
- package/dist/run-calls.d.ts +123 -0
- package/dist/run-calls.js +351 -0
- package/dist/run-turn.d.ts +36 -15
- package/dist/run-turn.js +74 -37
- package/dist/runtime.d.ts +23 -17
- package/dist/runtime.js +34 -24
- package/dist/schema-compat.d.ts +35 -18
- package/dist/schema-compat.js +346 -176
- package/dist/scope.d.ts +28 -5
- package/dist/scope.js +37 -10
- package/dist/side-task.d.ts +124 -40
- package/dist/side-task.js +186 -66
- package/dist/snapshot.d.ts +10 -5
- package/dist/snapshot.js +73 -30
- package/dist/spec.d.ts +56 -25
- package/dist/spec.js +494 -215
- package/dist/stream.d.ts +66 -16
- package/dist/stream.js +220 -97
- package/dist/thinking.d.ts +23 -8
- package/dist/thinking.js +92 -44
- package/dist/tokens.d.ts +164 -6
- package/dist/tokens.js +319 -6
- package/dist/tool-calls.d.ts +90 -17
- package/dist/tool-calls.js +207 -84
- package/dist/tool-loading.d.ts +101 -189
- package/dist/tool-loading.js +257 -371
- package/dist/wire.d.ts +81 -0
- package/dist/wire.js +73 -0
- package/llms.txt +76 -39
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -9,27 +9,32 @@
|
|
|
9
9
|
* prompts, and whatever the run is about — because that is the caller's, and it is the part
|
|
10
10
|
* that differs between one server and the next.
|
|
11
11
|
*/
|
|
12
|
-
export {
|
|
13
|
-
export { calibrate, charsPerTokenFor, resetCalibration } from
|
|
14
|
-
export { capabilitiesFor, EFFORT_LADDER, effortFor, expireCapabilities, modelCapabilitiesFor, negotiate, resetCapabilities, } from
|
|
15
|
-
export { configureClients, contextLimitFor, endpointId, endpointKey, FIRST_TOKEN_FACTOR, firstTokenMs, getClient, listModels, NO_KEY, resetClients, servedWindow, timeoutMs, } from
|
|
16
|
-
export { applyCompaction, COMPACT_AT, compactTranscript, KEEP_RATIO, planCompaction, pruneToolResults, requestIndex, runCompaction, SUMMARY_LEAD, SUMMARY_PROMPT, summariser, summaryInput, } from
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
export {
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
export {
|
|
35
|
-
export {
|
|
12
|
+
export { PRESELECT_APPEND, PRESELECT_EXCLUSIVE, runAgentLoop, } from './agent-loop.js';
|
|
13
|
+
export { calibrate, charsPerTokenFor, resetCalibration } from './calibration.js';
|
|
14
|
+
export { capabilitiesFor, EFFORT_LADDER, EFFORT_NONE, EFFORT_OFF, effortFor, expireCapabilities, modelCapabilitiesFor, negotiate, resetCapabilities, } from './capabilities.js';
|
|
15
|
+
export { configureClients, contextLimitFor, endpointId, endpointKey, FIRST_TOKEN_FACTOR, firstTokenMs, getClient, listModels, NO_KEY, resetClients, resolveApiKey, servedWindow, timeoutMs, } from './client.js';
|
|
16
|
+
export { applyCompaction, COMPACT_AT, compactTranscript, KEEP_RATIO, planCompaction, pruneToolResults, requestIndex, runCompaction, SUMMARY_LEAD, SUMMARY_PROMPT, summariser, summaryInput, } from './compaction.js';
|
|
17
|
+
export { ToolDiscovery, } from './config.js';
|
|
18
|
+
export { continueTurn, isContinuable, } from './continuation.js';
|
|
19
|
+
export { AgentLoopError, AgentLoopOverflow, errorMessage, failedRun, ToolIterationLimit, } from './errors.js';
|
|
20
|
+
export { configureEvents, emit, endRun, fold, history, RunEventKind, RunOutcome, resetEvents, runMetrics, watch, } from './events.js';
|
|
21
|
+
export { HOOK_EVENTS, HookEvent, INJECT_EVENTS } from './hook-events.js';
|
|
22
|
+
export { assembleContext, configureHooks, consult, gather, HOOK_CONTEXT_TOKENS, HOOK_PREFACE, notify, resetHooks, turnIndex, turnMessages, UNTRUSTED_PREFACE, untrusted, withContext, } from './hooks.js';
|
|
23
|
+
export { estimateFrom, rebaseLedger, recordRequest, tokensBetween, } from './ledger.js';
|
|
24
|
+
export { KEYWORD_DROPOFF, KEYWORD_MIN_SCORE, PRESELECT_SCHEMA, PRESELECT_SYSTEM, preselect, preselectByKeywords, preselectInput, preselection, preselectSystem, } from './preselect.js';
|
|
25
|
+
export { buildBody } from './request-body.js';
|
|
26
|
+
export { resetAll } from './reset.js';
|
|
27
|
+
export { backoffMs, ContextOverflow, EndpointSilent, isModelLoading, isOverflow, isTransient, LOADING_POLL_MS, LOADING_TIMEOUT_MS, SMALLEST_LIKELY_WINDOW, sleep, } from './retry.js';
|
|
28
|
+
export { preview } from './run-calls.js';
|
|
29
|
+
export { runTurn } from './run-turn.js';
|
|
30
|
+
export { createRuntime, defaultRuntime } from './runtime.js';
|
|
31
|
+
export { isGrammarError, relaxSchema, relaxTools, sanitizeSchema, sanitizeTools, } from './schema-compat.js';
|
|
32
|
+
export { ask, askJson, clean, listLines, parseJson, resetHints, taskCall, tryAsk, } from './side-task.js';
|
|
33
|
+
export { CAPABILITY_SNAPSHOT_VERSION, exportCapabilities, importCapabilities, } from './snapshot.js';
|
|
34
|
+
export { AGENT_SPEC, AGENT_SPEC_VERSION, AGENT_TASKS, exportSpec, parseSpec, REF_FILE, REF_URL, RESOLVED_DEFAULTS, resolveAgentSpec, SPEC_EVENTS, } from './spec.js';
|
|
35
|
+
export { CacheBreakReason, streamTurn, } from './stream.js';
|
|
36
|
+
export { ALL_FENCES, DEFAULT_FENCES, FenceSplitter, SPLIT_OUTPUT, SPLIT_REASONING, stripThinking, THINK_FENCE, } from './thinking.js';
|
|
37
|
+
export { CHARS_PER_TOKEN, compact, contextChars, contextTokens, estimateTokens, messageTokens, requestChars, requestTokens, toolsChars, } from './tokens.js';
|
|
38
|
+
export { ARGUMENTS_MALFORMED, ARGUMENTS_TRUNCATED, parseToolArguments, recoverToolCalls, ToolArgumentsError, } from './tool-calls.js';
|
|
39
|
+
export { CALL_TOOL, carryOver, catalogList, catalogPrompt, expandNames, holdsDefinitions, inCatalog, LOAD_TOOLS, LOAD_TOOLS_DEFINITION, loadedTools, loadResult, MAX_CARRIED, MAX_PER_LOAD, orderTools, PROXY_TOOLS, proxiedCall, proxyCatalogPrompt, proxyLoadResult, requestedNames, shownCall, } from './tool-loading.js';
|
|
40
|
+
export { FinishReason, FUNCTION_TOOL, JSON_SCHEMA_FORMAT, PartType, Role, SchemaType } from './wire.js';
|
package/dist/ledger.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type OpenAI from
|
|
2
|
-
import {
|
|
1
|
+
import type OpenAI from 'openai';
|
|
2
|
+
import type { TurnUsage } from './stream.ts';
|
|
3
|
+
import { type TokenEstimateOptions } from './tokens.ts';
|
|
3
4
|
/**
|
|
4
5
|
* What stretches of a transcript cost, read off the prompt counts the server already reported.
|
|
5
6
|
*
|
|
@@ -17,6 +18,7 @@ type Message = OpenAI.ChatCompletionMessageParam;
|
|
|
17
18
|
/**
|
|
18
19
|
* One request's reported prompt, and how far into the transcript that request reached.
|
|
19
20
|
*
|
|
21
|
+
* @remarks
|
|
20
22
|
* Three numbers and nothing else, so it serialises as it is: a host that stores a row per message
|
|
21
23
|
* keeps `prompt` and `epoch` in two columns on the row `through` names, and one that stores a
|
|
22
24
|
* session keeps the array beside it.
|
|
@@ -39,12 +41,15 @@ export interface LedgerEntry {
|
|
|
39
41
|
}
|
|
40
42
|
/** A transcript's entries in the order their requests were sent, which is also by `through`. */
|
|
41
43
|
export type TokenLedger = LedgerEntry[];
|
|
42
|
-
/**
|
|
43
|
-
export interface
|
|
44
|
+
/** The part of a request that decides whether the next one only appended to it. */
|
|
45
|
+
export interface RequestShape {
|
|
44
46
|
/** The messages as they were sent, a system prompt and any context a hook added included. */
|
|
45
47
|
messages: Message[];
|
|
46
48
|
/** The names of the tools declared, in the order they were sent. Absent is none. */
|
|
47
49
|
tools?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/** What `recordRequest` needs to know of one request. */
|
|
52
|
+
export interface LedgerRequest extends RequestShape {
|
|
48
53
|
/** The prompt tokens the endpoint reported. Zero or less is no report, and records nothing. */
|
|
49
54
|
prompt: number;
|
|
50
55
|
/** The index, in the host's transcript, of the last message the request carried. */
|
|
@@ -53,48 +58,79 @@ export interface LedgerRequest {
|
|
|
53
58
|
/** What `tokensBetween` and `estimateFrom` take besides the ledger and the transcript. */
|
|
54
59
|
export interface LedgerEstimateOptions extends TokenEstimateOptions {
|
|
55
60
|
/**
|
|
56
|
-
* One unmeasured message's tokens.
|
|
57
|
-
*
|
|
61
|
+
* One unmeasured message's tokens.
|
|
62
|
+
*
|
|
63
|
+
* @defaultValue `messageTokens` divided by `charsPerToken` — the calibrated estimate, given
|
|
64
|
+
* `charsPerTokenFor` the model.
|
|
58
65
|
*/
|
|
59
66
|
estimate?: (message: Message) => number;
|
|
60
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Where a request stopped matching the one before it, earliest in the rendered prompt first — the
|
|
70
|
+
* tool block, then the system prompt, then the history — or `none-known` where it only appended.
|
|
71
|
+
*
|
|
72
|
+
* @param previous - The request before, as it was sent.
|
|
73
|
+
* @param next - The request being explained.
|
|
74
|
+
* @returns The first of the three that differs, so a request that changed its tools and its history
|
|
75
|
+
* is reported as `tools-changed` alone.
|
|
76
|
+
*
|
|
77
|
+
* @remarks
|
|
78
|
+
* Beside `recordRequest` because it is the same comparison asked for a different reason: that one
|
|
79
|
+
* wants to know whether two prompts subtract, and this one why a cache that should have held
|
|
80
|
+
* did not.
|
|
81
|
+
*/
|
|
82
|
+
export declare function breakReason(previous: RequestShape, next: RequestShape): NonNullable<TurnUsage['cacheBreakReason']>;
|
|
61
83
|
/**
|
|
62
84
|
* The ledger with one more request on it, in the epoch the request before it says it belongs to.
|
|
63
85
|
*
|
|
86
|
+
* @param ledger - The ledger so far. Not written to; handed back as it is when nothing is recorded.
|
|
87
|
+
* @param previous - The request the ledger's last entry was recorded from — not merely the last one
|
|
88
|
+
* sent, so that a request which reported nothing is skipped over rather than compared against.
|
|
89
|
+
* `undefined` where that is not known, which starts a new epoch.
|
|
90
|
+
* @param next - The request just answered.
|
|
91
|
+
* @returns `ledger` itself when nothing was recorded, otherwise a new array with the entry at its
|
|
92
|
+
* end. The first entry of an empty ledger is epoch 0.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
64
95
|
* The epoch carries on only where the new request declared the same tools in the same order and
|
|
65
96
|
* sent every earlier message unchanged — the case in which the two prompts subtract. Anything else
|
|
66
97
|
* starts a new one: a fold, a prune, a system prompt that moved, and a tool array that grew, whose
|
|
67
98
|
* schema would otherwise be counted as the cost of the messages beside it. A request that reported
|
|
68
99
|
* no prompt records nothing, and neither does one that reaches no further than the last entry in
|
|
69
100
|
* the same epoch, so a retry or a continuation leaves the first reading standing.
|
|
70
|
-
*
|
|
71
|
-
* @param ledger The ledger so far. Not written to; handed back as it is when nothing is recorded.
|
|
72
|
-
* @param previous The request the ledger's last entry was recorded from — not merely the last one
|
|
73
|
-
* sent, so that a request which reported nothing is skipped over rather than compared against.
|
|
74
|
-
* `undefined` where that is not known, which starts a new epoch.
|
|
75
|
-
* @param next The request just answered.
|
|
76
101
|
*/
|
|
77
102
|
export declare function recordRequest(ledger: TokenLedger, previous: LedgerRequest | undefined, next: LedgerRequest): TokenLedger;
|
|
78
103
|
/**
|
|
79
104
|
* The ledger for a transcript that has been rewritten, keeping the boundaries the rewrite left
|
|
80
105
|
* standing.
|
|
81
106
|
*
|
|
107
|
+
* @param ledger - The ledger for `before`. Not written to.
|
|
108
|
+
* @param before - The transcript the ledger's indexes are in.
|
|
109
|
+
* @param after - The transcript that replaces it. One that only appended to `before`, or is the same
|
|
110
|
+
* array, hands the ledger back as it is.
|
|
111
|
+
* @returns `ledger` itself where nothing was rewritten, otherwise a new array. The entries it keeps
|
|
112
|
+
* from the head are the same objects; the ones from the tail are copies.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
82
115
|
* A difference inside an epoch stays true after the epoch ends, since the prefix the two requests
|
|
83
116
|
* shared cancels whatever became of it. So the entries that fall in the part of the transcript
|
|
84
117
|
* still at its head keep their place, the ones in the part still at its tail move with it, and
|
|
85
118
|
* only the ones in between — the messages a fold removed or a prune stubbed — are dropped. The
|
|
86
119
|
* tail's entries move to a later epoch as well, so no difference is read across the rewrite.
|
|
87
|
-
*
|
|
88
|
-
* @param ledger The ledger for `before`. Not written to.
|
|
89
|
-
* @param before The transcript the ledger's indexes are in.
|
|
90
|
-
* @param after The transcript that replaces it. One that only appended to `before`, or is the same
|
|
91
|
-
* array, hands the ledger back as it is.
|
|
92
120
|
*/
|
|
93
121
|
export declare function rebaseLedger(ledger: TokenLedger, before: readonly Message[], after: readonly Message[]): TokenLedger;
|
|
94
122
|
/**
|
|
95
123
|
* What the messages from `from` up to, not including, `to` cost the window, measured where the
|
|
96
124
|
* ledger can say and estimated where it cannot.
|
|
97
125
|
*
|
|
126
|
+
* @param ledger - The ledger kept for `messages`.
|
|
127
|
+
* @param from - The first message counted. Below zero counts from the start.
|
|
128
|
+
* @param to - The first message not counted, as `slice` takes it. Past the end counts to the end.
|
|
129
|
+
* @param messages - The transcript the ledger's indexes are in.
|
|
130
|
+
* @param [options] - The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
131
|
+
* @returns Whole tokens, rounded once over the stretch rather than per message.
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
98
134
|
* Between two boundaries recorded in one epoch it is a subtraction and exact. A boundary falls
|
|
99
135
|
* after a question and after each step's tool results, never after an assistant message, so a
|
|
100
136
|
* stretch that starts or ends inside a group is given that group's measured total divided by
|
|
@@ -105,28 +141,24 @@ export declare function rebaseLedger(ledger: TokenLedger, before: readonly Messa
|
|
|
105
141
|
* A difference of zero or less is not trusted and is estimated instead. That is how a server
|
|
106
142
|
* reporting `prompt_tokens` net of its cache would show up, though one whose net counts still
|
|
107
143
|
* happened to rise would not be caught by it.
|
|
108
|
-
*
|
|
109
|
-
* @param ledger The ledger kept for `messages`.
|
|
110
|
-
* @param from The first message counted. Below zero counts from the start.
|
|
111
|
-
* @param to The first message not counted, as `slice` takes it. Past the end counts to the end.
|
|
112
|
-
* @param messages The transcript the ledger's indexes are in.
|
|
113
|
-
* @param options The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
114
|
-
* @returns Whole tokens, rounded once over the stretch rather than per message.
|
|
115
144
|
*/
|
|
116
145
|
export declare function tokensBetween(ledger: TokenLedger, from: number, to: number, messages: readonly Message[], options?: LedgerEstimateOptions): number;
|
|
117
146
|
/**
|
|
118
147
|
* A per-message cost for `planCompaction`'s `estimate`: the measured share where the ledger covers
|
|
119
148
|
* the message, the calibrated estimate where it does not.
|
|
120
149
|
*
|
|
150
|
+
* @param ledger - The ledger kept for `messages`.
|
|
151
|
+
* @param messages - The transcript the plan will be made for. The function it returns knows a
|
|
152
|
+
* message by identity, so it has to be asked about these objects and not copies of them; one it
|
|
153
|
+
* does not know is estimated.
|
|
154
|
+
* @param [options] - The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
155
|
+
* @returns A function giving one message's tokens, unrounded where it was measured. It holds what
|
|
156
|
+
* the ledger said when this was called, and does not see entries recorded since.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
121
159
|
* The planner asks about one message at a time, and nothing reports anything that fine, so a
|
|
122
160
|
* measured message is given its group's total by character share. The shares are not rounded,
|
|
123
161
|
* which is what makes a whole group of them add back up to the difference the server reported.
|
|
124
|
-
*
|
|
125
|
-
* @param ledger The ledger kept for `messages`.
|
|
126
|
-
* @param messages The transcript the plan will be made for. The function it returns knows a
|
|
127
|
-
* message by identity, so it has to be asked about these objects and not copies of them; one it
|
|
128
|
-
* does not know is estimated.
|
|
129
|
-
* @param options The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
130
162
|
*/
|
|
131
163
|
export declare function estimateFrom(ledger: TokenLedger, messages: readonly Message[], options?: LedgerEstimateOptions): (message: Message) => number;
|
|
132
164
|
export {};
|
package/dist/ledger.js
CHANGED
|
@@ -1,88 +1,181 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { CacheBreakReason } from './stream.js';
|
|
2
|
+
import { messageChars, messageTokens } from './tokens.js';
|
|
3
|
+
import { Role } from './wire.js';
|
|
4
|
+
/**
|
|
5
|
+
* Whether two messages say the same thing, by identity first since most of a transcript is.
|
|
6
|
+
*
|
|
7
|
+
* @param a - One message.
|
|
8
|
+
* @param b - The other. The order makes no difference.
|
|
9
|
+
* @returns `true` for the same object, or two that serialise to the same JSON — so the same fields
|
|
10
|
+
* written in another key order are not the same.
|
|
11
|
+
*/
|
|
3
12
|
const sameMessage = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
4
|
-
/**
|
|
5
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Whether two requests declared the same tools in the same order.
|
|
15
|
+
*
|
|
16
|
+
* @param previous - The request before. Absent `tools` reads as none.
|
|
17
|
+
* @param next - The request after, read the same way.
|
|
18
|
+
* @returns `true` when the names match one for one. Only names are compared, so a schema that
|
|
19
|
+
* changed under the same name is not seen.
|
|
20
|
+
*/
|
|
21
|
+
const sameTools = (previous, next) => {
|
|
6
22
|
const before = previous.tools ?? [];
|
|
7
23
|
const now = next.tools ?? [];
|
|
8
|
-
return
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
return before.length === now.length && before.every((name, at) => name === now[at]);
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Whether a request sent every message the one before it did, in the same place.
|
|
28
|
+
*
|
|
29
|
+
* @param previous - The earlier request's messages.
|
|
30
|
+
* @param next - The later one's, which may run on past them.
|
|
31
|
+
* @returns `true` when `previous` is a prefix of `next` by `sameMessage` — the two being equal
|
|
32
|
+
* included, and an empty `previous` always.
|
|
33
|
+
*/
|
|
34
|
+
const keptMessages = (previous, next) => previous.length <= next.length && previous.every((message, at) => sameMessage(message, next[at]));
|
|
35
|
+
/**
|
|
36
|
+
* Whether a request sent everything the one before it did, in the same place, and then more.
|
|
37
|
+
*
|
|
38
|
+
* @param previous - The request before.
|
|
39
|
+
* @param next - The request after.
|
|
40
|
+
* @returns `true` when the tools are the same and the earlier messages a prefix of the later —
|
|
41
|
+
* which a request that added nothing also is.
|
|
42
|
+
*/
|
|
43
|
+
const appended = (previous, next) => sameTools(previous, next) && keptMessages(previous.messages, next.messages);
|
|
44
|
+
/**
|
|
45
|
+
* The system messages a request opens with, which a template renders ahead of the history.
|
|
46
|
+
*
|
|
47
|
+
* @param messages - The request's messages. Not written to.
|
|
48
|
+
* @returns A new array of the messages ahead of the first that is not `system`: empty when the
|
|
49
|
+
* request does not open with one, and all of them when it holds nothing else. A `developer` message
|
|
50
|
+
* ends the run.
|
|
51
|
+
*/
|
|
52
|
+
const leadingSystem = (messages) => {
|
|
53
|
+
const end = messages.findIndex((message) => message.role !== Role.System);
|
|
54
|
+
return messages.slice(0, end === -1 ? messages.length : end);
|
|
12
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Where a request stopped matching the one before it, earliest in the rendered prompt first — the
|
|
58
|
+
* tool block, then the system prompt, then the history — or `none-known` where it only appended.
|
|
59
|
+
*
|
|
60
|
+
* @param previous - The request before, as it was sent.
|
|
61
|
+
* @param next - The request being explained.
|
|
62
|
+
* @returns The first of the three that differs, so a request that changed its tools and its history
|
|
63
|
+
* is reported as `tools-changed` alone.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* Beside `recordRequest` because it is the same comparison asked for a different reason: that one
|
|
67
|
+
* wants to know whether two prompts subtract, and this one why a cache that should have held
|
|
68
|
+
* did not.
|
|
69
|
+
*/
|
|
70
|
+
export function breakReason(previous, next) {
|
|
71
|
+
if (!sameTools(previous, next)) {
|
|
72
|
+
return CacheBreakReason.ToolsChanged;
|
|
73
|
+
}
|
|
74
|
+
const before = leadingSystem(previous.messages);
|
|
75
|
+
const now = leadingSystem(next.messages);
|
|
76
|
+
const systemKept = before.length === now.length && keptMessages(before, now);
|
|
77
|
+
if (!systemKept) {
|
|
78
|
+
return CacheBreakReason.SystemChanged;
|
|
79
|
+
}
|
|
80
|
+
if (!keptMessages(previous.messages, next.messages)) {
|
|
81
|
+
return CacheBreakReason.HistoryRewritten;
|
|
82
|
+
}
|
|
83
|
+
return CacheBreakReason.NoneKnown;
|
|
84
|
+
}
|
|
13
85
|
/**
|
|
14
86
|
* The ledger with one more request on it, in the epoch the request before it says it belongs to.
|
|
15
87
|
*
|
|
88
|
+
* @param ledger - The ledger so far. Not written to; handed back as it is when nothing is recorded.
|
|
89
|
+
* @param previous - The request the ledger's last entry was recorded from — not merely the last one
|
|
90
|
+
* sent, so that a request which reported nothing is skipped over rather than compared against.
|
|
91
|
+
* `undefined` where that is not known, which starts a new epoch.
|
|
92
|
+
* @param next - The request just answered.
|
|
93
|
+
* @returns `ledger` itself when nothing was recorded, otherwise a new array with the entry at its
|
|
94
|
+
* end. The first entry of an empty ledger is epoch 0.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
16
97
|
* The epoch carries on only where the new request declared the same tools in the same order and
|
|
17
98
|
* sent every earlier message unchanged — the case in which the two prompts subtract. Anything else
|
|
18
99
|
* starts a new one: a fold, a prune, a system prompt that moved, and a tool array that grew, whose
|
|
19
100
|
* schema would otherwise be counted as the cost of the messages beside it. A request that reported
|
|
20
101
|
* no prompt records nothing, and neither does one that reaches no further than the last entry in
|
|
21
102
|
* the same epoch, so a retry or a continuation leaves the first reading standing.
|
|
22
|
-
*
|
|
23
|
-
* @param ledger The ledger so far. Not written to; handed back as it is when nothing is recorded.
|
|
24
|
-
* @param previous The request the ledger's last entry was recorded from — not merely the last one
|
|
25
|
-
* sent, so that a request which reported nothing is skipped over rather than compared against.
|
|
26
|
-
* `undefined` where that is not known, which starts a new epoch.
|
|
27
|
-
* @param next The request just answered.
|
|
28
103
|
*/
|
|
29
104
|
export function recordRequest(ledger, previous, next) {
|
|
30
|
-
if (!(next.prompt > 0))
|
|
105
|
+
if (!(next.prompt > 0)) {
|
|
31
106
|
return ledger;
|
|
107
|
+
}
|
|
32
108
|
const last = ledger.at(-1);
|
|
33
109
|
if (last && previous && appended(previous, next)) {
|
|
34
|
-
if (next.through <= last.through)
|
|
110
|
+
if (next.through <= last.through) {
|
|
35
111
|
return ledger;
|
|
112
|
+
}
|
|
36
113
|
return [...ledger, { through: next.through, prompt: next.prompt, epoch: last.epoch }];
|
|
37
114
|
}
|
|
38
|
-
return [
|
|
39
|
-
...ledger,
|
|
40
|
-
{ through: next.through, prompt: next.prompt, epoch: last ? last.epoch + 1 : 0 },
|
|
41
|
-
];
|
|
115
|
+
return [...ledger, { through: next.through, prompt: next.prompt, epoch: last ? last.epoch + 1 : 0 }];
|
|
42
116
|
}
|
|
43
117
|
/**
|
|
44
118
|
* The ledger for a transcript that has been rewritten, keeping the boundaries the rewrite left
|
|
45
119
|
* standing.
|
|
46
120
|
*
|
|
121
|
+
* @param ledger - The ledger for `before`. Not written to.
|
|
122
|
+
* @param before - The transcript the ledger's indexes are in.
|
|
123
|
+
* @param after - The transcript that replaces it. One that only appended to `before`, or is the same
|
|
124
|
+
* array, hands the ledger back as it is.
|
|
125
|
+
* @returns `ledger` itself where nothing was rewritten, otherwise a new array. The entries it keeps
|
|
126
|
+
* from the head are the same objects; the ones from the tail are copies.
|
|
127
|
+
*
|
|
128
|
+
* @remarks
|
|
47
129
|
* A difference inside an epoch stays true after the epoch ends, since the prefix the two requests
|
|
48
130
|
* shared cancels whatever became of it. So the entries that fall in the part of the transcript
|
|
49
131
|
* still at its head keep their place, the ones in the part still at its tail move with it, and
|
|
50
132
|
* only the ones in between — the messages a fold removed or a prune stubbed — are dropped. The
|
|
51
133
|
* tail's entries move to a later epoch as well, so no difference is read across the rewrite.
|
|
52
|
-
*
|
|
53
|
-
* @param ledger The ledger for `before`. Not written to.
|
|
54
|
-
* @param before The transcript the ledger's indexes are in.
|
|
55
|
-
* @param after The transcript that replaces it. One that only appended to `before`, or is the same
|
|
56
|
-
* array, hands the ledger back as it is.
|
|
57
134
|
*/
|
|
58
135
|
export function rebaseLedger(ledger, before, after) {
|
|
59
|
-
if (before === after)
|
|
136
|
+
if (before === after) {
|
|
60
137
|
return ledger;
|
|
138
|
+
}
|
|
61
139
|
const shortest = Math.min(before.length, after.length);
|
|
62
140
|
let head = 0;
|
|
63
|
-
while (head < shortest && sameMessage(before[head], after[head]))
|
|
141
|
+
while (head < shortest && sameMessage(before[head], after[head])) {
|
|
64
142
|
head++;
|
|
65
|
-
|
|
143
|
+
}
|
|
144
|
+
if (head === before.length) {
|
|
66
145
|
return ledger;
|
|
146
|
+
}
|
|
67
147
|
let tail = 0;
|
|
68
|
-
while (tail < shortest - head &&
|
|
69
|
-
sameMessage(before[before.length - 1 - tail], after[after.length - 1 - tail]))
|
|
148
|
+
while (tail < shortest - head && sameMessage(before[before.length - 1 - tail], after[after.length - 1 - tail])) {
|
|
70
149
|
tail++;
|
|
150
|
+
}
|
|
71
151
|
// The boundary just ahead of a kept tail stands too: what follows it is all still there. With
|
|
72
152
|
// no tail kept there is nothing left for it to be the start of.
|
|
73
153
|
const start = tail > 0 ? before.length - tail - 1 : before.length;
|
|
74
154
|
const shift = after.length - before.length;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
155
|
+
const rebased = [];
|
|
156
|
+
for (const entry of ledger) {
|
|
157
|
+
if (entry.through < head) {
|
|
158
|
+
rebased.push(entry);
|
|
159
|
+
}
|
|
160
|
+
else if (entry.through >= start) {
|
|
161
|
+
rebased.push({ ...entry, through: entry.through + shift, epoch: entry.epoch + 1 });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return rebased;
|
|
80
165
|
}
|
|
81
166
|
/**
|
|
82
|
-
* Each measured message's share of its group's cost, by index.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
167
|
+
* Each measured message's share of its group's cost, by index.
|
|
168
|
+
*
|
|
169
|
+
* @param ledger - The entries, in the order their requests were sent.
|
|
170
|
+
* @param messages - The transcript the ledger's indexes are in. Each measured message is read for
|
|
171
|
+
* its characters.
|
|
172
|
+
* @returns Tokens by message index, unrounded, so a group's shares add back up to its difference.
|
|
173
|
+
* A message no group covers has no entry.
|
|
174
|
+
*
|
|
175
|
+
* @remarks
|
|
176
|
+
* A group is the messages between two neighbouring entries of one epoch, and is left out — for the
|
|
177
|
+
* estimate to answer — where the difference is not above zero, where the entries are out of order,
|
|
178
|
+
* or where the transcript does not reach its end.
|
|
86
179
|
*/
|
|
87
180
|
function shares(ledger, messages) {
|
|
88
181
|
const out = new Map();
|
|
@@ -93,10 +186,12 @@ function shares(ledger, messages) {
|
|
|
93
186
|
const tokens = to.prompt - from.prompt;
|
|
94
187
|
const start = from.through + 1;
|
|
95
188
|
const end = to.through + 1;
|
|
96
|
-
if (from.epoch !== to.epoch || !(tokens > 0))
|
|
189
|
+
if (from.epoch !== to.epoch || !(tokens > 0)) {
|
|
97
190
|
continue;
|
|
98
|
-
|
|
191
|
+
}
|
|
192
|
+
if (start < floor || end <= start || end > messages.length) {
|
|
99
193
|
continue;
|
|
194
|
+
}
|
|
100
195
|
floor = end;
|
|
101
196
|
const chars = [];
|
|
102
197
|
let total = 0;
|
|
@@ -110,12 +205,27 @@ function shares(ledger, messages) {
|
|
|
110
205
|
}
|
|
111
206
|
return out;
|
|
112
207
|
}
|
|
113
|
-
/**
|
|
208
|
+
/**
|
|
209
|
+
* The estimate for a message nothing measured, as the options ask for it.
|
|
210
|
+
*
|
|
211
|
+
* @param options - `estimate` is handed back as it is, and `charsPerToken` is then not read.
|
|
212
|
+
* Without one, `charsPerToken` is the divisor.
|
|
213
|
+
* @returns A function giving one message's tokens: the caller's own, or `messageTokens` at that
|
|
214
|
+
* divisor.
|
|
215
|
+
*/
|
|
114
216
|
const fallback = ({ charsPerToken, estimate }) => estimate ?? ((message) => messageTokens(message, { charsPerToken }));
|
|
115
217
|
/**
|
|
116
218
|
* What the messages from `from` up to, not including, `to` cost the window, measured where the
|
|
117
219
|
* ledger can say and estimated where it cannot.
|
|
118
220
|
*
|
|
221
|
+
* @param ledger - The ledger kept for `messages`.
|
|
222
|
+
* @param from - The first message counted. Below zero counts from the start.
|
|
223
|
+
* @param to - The first message not counted, as `slice` takes it. Past the end counts to the end.
|
|
224
|
+
* @param messages - The transcript the ledger's indexes are in.
|
|
225
|
+
* @param [options] - The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
226
|
+
* @returns Whole tokens, rounded once over the stretch rather than per message.
|
|
227
|
+
*
|
|
228
|
+
* @remarks
|
|
119
229
|
* Between two boundaries recorded in one epoch it is a subtraction and exact. A boundary falls
|
|
120
230
|
* after a question and after each step's tool results, never after an assistant message, so a
|
|
121
231
|
* stretch that starts or ends inside a group is given that group's measured total divided by
|
|
@@ -126,13 +236,6 @@ const fallback = ({ charsPerToken, estimate }) => estimate ?? ((message) => mess
|
|
|
126
236
|
* A difference of zero or less is not trusted and is estimated instead. That is how a server
|
|
127
237
|
* reporting `prompt_tokens` net of its cache would show up, though one whose net counts still
|
|
128
238
|
* happened to rise would not be caught by it.
|
|
129
|
-
*
|
|
130
|
-
* @param ledger The ledger kept for `messages`.
|
|
131
|
-
* @param from The first message counted. Below zero counts from the start.
|
|
132
|
-
* @param to The first message not counted, as `slice` takes it. Past the end counts to the end.
|
|
133
|
-
* @param messages The transcript the ledger's indexes are in.
|
|
134
|
-
* @param options The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
135
|
-
* @returns Whole tokens, rounded once over the stretch rather than per message.
|
|
136
239
|
*/
|
|
137
240
|
export function tokensBetween(ledger, from, to, messages, options = {}) {
|
|
138
241
|
const measured = shares(ledger, messages);
|
|
@@ -147,20 +250,24 @@ export function tokensBetween(ledger, from, to, messages, options = {}) {
|
|
|
147
250
|
* A per-message cost for `planCompaction`'s `estimate`: the measured share where the ledger covers
|
|
148
251
|
* the message, the calibrated estimate where it does not.
|
|
149
252
|
*
|
|
253
|
+
* @param ledger - The ledger kept for `messages`.
|
|
254
|
+
* @param messages - The transcript the plan will be made for. The function it returns knows a
|
|
255
|
+
* message by identity, so it has to be asked about these objects and not copies of them; one it
|
|
256
|
+
* does not know is estimated.
|
|
257
|
+
* @param [options] - The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
258
|
+
* @returns A function giving one message's tokens, unrounded where it was measured. It holds what
|
|
259
|
+
* the ledger said when this was called, and does not see entries recorded since.
|
|
260
|
+
*
|
|
261
|
+
* @remarks
|
|
150
262
|
* The planner asks about one message at a time, and nothing reports anything that fine, so a
|
|
151
263
|
* measured message is given its group's total by character share. The shares are not rounded,
|
|
152
264
|
* which is what makes a whole group of them add back up to the difference the server reported.
|
|
153
|
-
*
|
|
154
|
-
* @param ledger The ledger kept for `messages`.
|
|
155
|
-
* @param messages The transcript the plan will be made for. The function it returns knows a
|
|
156
|
-
* message by identity, so it has to be asked about these objects and not copies of them; one it
|
|
157
|
-
* does not know is estimated.
|
|
158
|
-
* @param options The estimate for what is not measured: a divisor, or a function of the caller's.
|
|
159
265
|
*/
|
|
160
266
|
export function estimateFrom(ledger, messages, options = {}) {
|
|
161
267
|
const estimate = fallback(options);
|
|
162
268
|
const measured = new Map();
|
|
163
|
-
for (const [at, tokens] of shares(ledger, messages))
|
|
269
|
+
for (const [at, tokens] of shares(ledger, messages)) {
|
|
164
270
|
measured.set(messages[at], tokens);
|
|
271
|
+
}
|
|
165
272
|
return (message) => measured.get(message) ?? estimate(message);
|
|
166
273
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform's own numbers and words, each under a name.
|
|
3
|
+
*
|
|
4
|
+
* A leaf with no imports. Settings are in seconds because that is what a person types, timers are
|
|
5
|
+
* in milliseconds because that is what the platform takes, and each module that crossed between
|
|
6
|
+
* the two had written the thousand out.
|
|
7
|
+
*/
|
|
8
|
+
/** How many milliseconds a second is. */
|
|
9
|
+
export declare const MS_PER_SECOND = 1000;
|
|
10
|
+
/** How many milliseconds a minute is. */
|
|
11
|
+
export declare const MS_PER_MINUTE: number;
|
|
12
|
+
/** The event an `AbortSignal` fires when it aborts. */
|
|
13
|
+
export declare const ABORT_EVENT = "abort";
|
|
14
|
+
/** The name an error carries when it is what an aborted `AbortSignal` gave as its reason. */
|
|
15
|
+
export declare const ABORT_ERROR = "AbortError";
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform's own numbers and words, each under a name.
|
|
3
|
+
*
|
|
4
|
+
* A leaf with no imports. Settings are in seconds because that is what a person types, timers are
|
|
5
|
+
* in milliseconds because that is what the platform takes, and each module that crossed between
|
|
6
|
+
* the two had written the thousand out.
|
|
7
|
+
*/
|
|
8
|
+
/** How many milliseconds a second is. */
|
|
9
|
+
export const MS_PER_SECOND = 1000;
|
|
10
|
+
/** How many milliseconds a minute is. */
|
|
11
|
+
export const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
12
|
+
/** The event an `AbortSignal` fires when it aborts. */
|
|
13
|
+
export const ABORT_EVENT = 'abort';
|
|
14
|
+
/** The name an error carries when it is what an aborted `AbortSignal` gave as its reason. */
|
|
15
|
+
export const ABORT_ERROR = 'AbortError';
|