@cubicecho/agent-core 2.3.0 → 2.4.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 +16 -0
- package/dist/hooks.d.ts +33 -3
- package/dist/hooks.js +45 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/reset.d.ts +6 -5
- package/dist/reset.js +8 -5
- package/llms.txt +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -273,6 +273,22 @@ host that stores what the user typed never stores the context as something they
|
|
|
273
273
|
injecting hook shares `HOOK_CONTEXT_TOKENS` (2000) by default, each held to its own `maxTokens`
|
|
274
274
|
inside that, so a generous hook cannot crowd out the conversation it was meant to inform.
|
|
275
275
|
|
|
276
|
+
The budget is the caller's to move, at either level. `configureHooks` sets it for the process, and
|
|
277
|
+
`maxTokens` on `gather` (or the second argument to `assembleContext`) sets it for one request and
|
|
278
|
+
wins over it — the one to reach for when the budget follows the model, since a 128k window can
|
|
279
|
+
afford more recall than an 8k one:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { configureHooks, gather } from "@cubicecho/agent-core";
|
|
283
|
+
|
|
284
|
+
configureHooks({ contextTokens: 4000 });
|
|
285
|
+
const gathered = await gather(run, ["beforeTurn"], context, { maxTokens: contextLimit / 20 });
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Either one given something that is not a number above zero keeps what was there, as
|
|
289
|
+
`configureEvents` does, so a `0` threaded through for "no opinion" does not switch recall off.
|
|
290
|
+
`resetHooks` (and `resetAll`) puts the default back.
|
|
291
|
+
|
|
276
292
|
Neither function rejects. A hook failing is an outcome, and a runner that throws outright is
|
|
277
293
|
noted once for its event and costs only that event's context. `notify` takes no signal: a reader
|
|
278
294
|
who leaves once the turn is answered has not asked for it not to be remembered.
|
package/dist/hooks.d.ts
CHANGED
|
@@ -122,12 +122,40 @@ export interface Gathered {
|
|
|
122
122
|
notes: HookNote[];
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
* The most context all of a request's hooks add between them, in estimated tokens.
|
|
125
|
+
* The most context all of a request's hooks add between them by default, in estimated tokens.
|
|
126
126
|
*
|
|
127
127
|
* Enough for a handful of recalled memories, and small against any window worth running an agent
|
|
128
128
|
* in. The point is that a generous hook cannot crowd out the conversation it was meant to inform.
|
|
129
|
+
* `configureHooks` moves it for a process, and `gather` and `assembleContext` for one request.
|
|
129
130
|
*/
|
|
130
131
|
export declare const HOOK_CONTEXT_TOKENS = 2000;
|
|
132
|
+
/** What hooks are held to across a process. Every field optional; see `configureHooks`. */
|
|
133
|
+
export interface HookOptions {
|
|
134
|
+
/**
|
|
135
|
+
* The budget every injecting hook shares, when a call does not give its own. Each hook is still
|
|
136
|
+
* held to its own `maxTokens` inside it.
|
|
137
|
+
*/
|
|
138
|
+
contextTokens?: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Changes what hooks are held to, for a process whose windows are not the size these defaults
|
|
142
|
+
* were chosen for.
|
|
143
|
+
*
|
|
144
|
+
* Module-level for the same reason `configureEvents` is: a budget is a deployment's setting, said
|
|
145
|
+
* once at startup. A caller that sizes it per model or per agent — a 128k window can afford more
|
|
146
|
+
* recall than an 8k one — passes `maxTokens` to `gather` instead, which wins over this.
|
|
147
|
+
*
|
|
148
|
+
* @param options The limits to change. A field left out — or given anything that is not a number
|
|
149
|
+
* above zero — keeps what it has, so a half-built config narrows nothing. `Infinity` is a number
|
|
150
|
+
* above zero, and lifts the shared budget entirely.
|
|
151
|
+
* @returns Everything in force afterwards, including what this call did not change.
|
|
152
|
+
*/
|
|
153
|
+
export declare function configureHooks(options?: HookOptions): Required<HookOptions>;
|
|
154
|
+
/**
|
|
155
|
+
* Test seam: puts `configureHooks` back to the defaults, so one test's budget is not the next's.
|
|
156
|
+
* `resetAll` calls it.
|
|
157
|
+
*/
|
|
158
|
+
export declare const resetHooks: () => void;
|
|
131
159
|
/**
|
|
132
160
|
* Said once, above the blocks, so the model reads them as background rather than instructions.
|
|
133
161
|
* Names no host; `withContext` takes another for one that wants to.
|
|
@@ -148,7 +176,8 @@ export declare const HOOK_PREFACE: string;
|
|
|
148
176
|
*
|
|
149
177
|
* @param outcomes What the runners returned. An injecting outcome on an event that cannot inject
|
|
150
178
|
* adds nothing; a failed one is noted wherever it falls, including past the budget.
|
|
151
|
-
* @param maxTokens The budget every block shares.
|
|
179
|
+
* @param maxTokens The budget every block shares. Absent, or not a number above zero, is what
|
|
180
|
+
* `configureHooks` last set — `HOOK_CONTEXT_TOKENS` unless something moved it.
|
|
152
181
|
*/
|
|
153
182
|
export declare function assembleContext(outcomes: readonly HookOutcome[], maxTokens?: number): Gathered;
|
|
154
183
|
/**
|
|
@@ -217,7 +246,8 @@ export declare const turnIndex: (messages: readonly {
|
|
|
217
246
|
* @param context What the hooks are told.
|
|
218
247
|
* @param options `signal` is handed to the runner, and should be the turn's own: a user who
|
|
219
248
|
* stopped the turn stopped its recall. `onNote` hears each note as the whole is assembled.
|
|
220
|
-
* `maxTokens` is the shared budget, `
|
|
249
|
+
* `maxTokens` is the shared budget for this request, read as `assembleContext` reads it: absent
|
|
250
|
+
* or unusable is the process's, from `configureHooks`.
|
|
221
251
|
*/
|
|
222
252
|
export declare function gather(run: HookRunner, events: readonly HookEvent[], context: HookContext, { signal, onNote, maxTokens, }?: {
|
|
223
253
|
signal?: AbortSignal;
|
package/dist/hooks.js
CHANGED
|
@@ -16,12 +16,50 @@ export const HOOK_EVENTS = [
|
|
|
16
16
|
*/
|
|
17
17
|
export const INJECT_EVENTS = new Set(["sessionStart", "beforeTurn"]);
|
|
18
18
|
/**
|
|
19
|
-
* The most context all of a request's hooks add between them, in estimated tokens.
|
|
19
|
+
* The most context all of a request's hooks add between them by default, in estimated tokens.
|
|
20
20
|
*
|
|
21
21
|
* Enough for a handful of recalled memories, and small against any window worth running an agent
|
|
22
22
|
* in. The point is that a generous hook cannot crowd out the conversation it was meant to inform.
|
|
23
|
+
* `configureHooks` moves it for a process, and `gather` and `assembleContext` for one request.
|
|
23
24
|
*/
|
|
24
25
|
export const HOOK_CONTEXT_TOKENS = 2000;
|
|
26
|
+
/** The numbers this module was written with. */
|
|
27
|
+
const HOOK_DEFAULTS = { contextTokens: HOOK_CONTEXT_TOKENS };
|
|
28
|
+
/** What is in force now. Read where it is used, so a change applies from the next request. */
|
|
29
|
+
let hookLimits = { ...HOOK_DEFAULTS };
|
|
30
|
+
/**
|
|
31
|
+
* Changes what hooks are held to, for a process whose windows are not the size these defaults
|
|
32
|
+
* were chosen for.
|
|
33
|
+
*
|
|
34
|
+
* Module-level for the same reason `configureEvents` is: a budget is a deployment's setting, said
|
|
35
|
+
* once at startup. A caller that sizes it per model or per agent — a 128k window can afford more
|
|
36
|
+
* recall than an 8k one — passes `maxTokens` to `gather` instead, which wins over this.
|
|
37
|
+
*
|
|
38
|
+
* @param options The limits to change. A field left out — or given anything that is not a number
|
|
39
|
+
* above zero — keeps what it has, so a half-built config narrows nothing. `Infinity` is a number
|
|
40
|
+
* above zero, and lifts the shared budget entirely.
|
|
41
|
+
* @returns Everything in force afterwards, including what this call did not change.
|
|
42
|
+
*/
|
|
43
|
+
export function configureHooks(options = {}) {
|
|
44
|
+
for (const [name, value] of Object.entries(options)) {
|
|
45
|
+
if (typeof value === "number" && value > 0)
|
|
46
|
+
hookLimits[name] = value;
|
|
47
|
+
}
|
|
48
|
+
return { ...hookLimits };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Test seam: puts `configureHooks` back to the defaults, so one test's budget is not the next's.
|
|
52
|
+
* `resetAll` calls it.
|
|
53
|
+
*/
|
|
54
|
+
export const resetHooks = () => {
|
|
55
|
+
hookLimits = { ...HOOK_DEFAULTS };
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The budget a call is held to: its own when it gave a usable one, the process's otherwise. The
|
|
59
|
+
* same rule `configureHooks` applies, so a `0` threaded through for "no opinion" does not quietly
|
|
60
|
+
* turn every hook's context off.
|
|
61
|
+
*/
|
|
62
|
+
const budget = (given) => typeof given === "number" && given > 0 ? given : hookLimits.contextTokens;
|
|
25
63
|
/**
|
|
26
64
|
* Said once, above the blocks, so the model reads them as background rather than instructions.
|
|
27
65
|
* Names no host; `withContext` takes another for one that wants to.
|
|
@@ -44,12 +82,13 @@ const attribute = (text) => text.replaceAll("&", "&").replaceAll('"', ""
|
|
|
44
82
|
*
|
|
45
83
|
* @param outcomes What the runners returned. An injecting outcome on an event that cannot inject
|
|
46
84
|
* adds nothing; a failed one is noted wherever it falls, including past the budget.
|
|
47
|
-
* @param maxTokens The budget every block shares.
|
|
85
|
+
* @param maxTokens The budget every block shares. Absent, or not a number above zero, is what
|
|
86
|
+
* `configureHooks` last set — `HOOK_CONTEXT_TOKENS` unless something moved it.
|
|
48
87
|
*/
|
|
49
|
-
export function assembleContext(outcomes, maxTokens
|
|
88
|
+
export function assembleContext(outcomes, maxTokens) {
|
|
50
89
|
const blocks = [];
|
|
51
90
|
const notes = [];
|
|
52
|
-
let remaining = maxTokens;
|
|
91
|
+
let remaining = budget(maxTokens);
|
|
53
92
|
for (const outcome of outcomes) {
|
|
54
93
|
const base = { event: outcome.event, source: outcome.label, hookId: outcome.hookId };
|
|
55
94
|
if (!outcome.ok) {
|
|
@@ -182,7 +221,8 @@ const runSafely = (run, event, context, signal) => Promise.resolve()
|
|
|
182
221
|
* @param context What the hooks are told.
|
|
183
222
|
* @param options `signal` is handed to the runner, and should be the turn's own: a user who
|
|
184
223
|
* stopped the turn stopped its recall. `onNote` hears each note as the whole is assembled.
|
|
185
|
-
* `maxTokens` is the shared budget, `
|
|
224
|
+
* `maxTokens` is the shared budget for this request, read as `assembleContext` reads it: absent
|
|
225
|
+
* or unusable is the process's, from `configureHooks`.
|
|
186
226
|
*/
|
|
187
227
|
export async function gather(run, events, context, { signal, onNote, maxTokens, } = {}) {
|
|
188
228
|
const outcomes = await Promise.all(events.map((event) => runSafely(run, event, context, signal)));
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { contextLimitFor, getClient, listModels, type ModelInfo, NO_KEY, resetCl
|
|
|
15
15
|
export type { AgentConfig, Endpoint, ModelParams, RetryPolicy, ToolPolicy, } from "./config.ts";
|
|
16
16
|
export { errorMessage } from "./errors.ts";
|
|
17
17
|
export { configureEvents, type EventBusOptions, emit, endRun, fold, history, type RunEvent, type RunEventInput, type RunEventKind, type RunUsage, resetEvents, watch, } from "./events.ts";
|
|
18
|
-
export { assembleContext, type Gathered, gather, HOOK_CONTEXT_TOKENS, HOOK_EVENTS, HOOK_PREFACE, type HookContext, type HookEvent, type HookMessage, type HookNote, type HookOutcome, type HookRunner, INJECT_EVENTS, notify, turnIndex, turnMessages, withContext, } from "./hooks.ts";
|
|
18
|
+
export { assembleContext, configureHooks, 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, withContext, } from "./hooks.ts";
|
|
19
19
|
export { resetAll } from "./reset.ts";
|
|
20
20
|
export { backoffMs, ContextOverflow, compact, EndpointSilent, isOverflow, isTransient, requestTokens, SMALLEST_LIKELY_WINDOW, sleep, } from "./retry.ts";
|
|
21
21
|
export { type RunTurnOptions, runTurn } from "./run-turn.ts";
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export { capabilitiesFor, modelCapabilitiesFor, negotiate, resetCapabilities, }
|
|
|
13
13
|
export { contextLimitFor, getClient, listModels, NO_KEY, resetClients, timeoutMs, } from "./client.js";
|
|
14
14
|
export { errorMessage } from "./errors.js";
|
|
15
15
|
export { configureEvents, emit, endRun, fold, history, resetEvents, watch, } from "./events.js";
|
|
16
|
-
export { assembleContext, gather, HOOK_CONTEXT_TOKENS, HOOK_EVENTS, HOOK_PREFACE, INJECT_EVENTS, notify, turnIndex, turnMessages, withContext, } from "./hooks.js";
|
|
16
|
+
export { assembleContext, configureHooks, gather, HOOK_CONTEXT_TOKENS, HOOK_EVENTS, HOOK_PREFACE, INJECT_EVENTS, notify, resetHooks, turnIndex, turnMessages, withContext, } from "./hooks.js";
|
|
17
17
|
export { resetAll } from "./reset.js";
|
|
18
18
|
export { backoffMs, ContextOverflow, compact, EndpointSilent, isOverflow, isTransient, requestTokens, SMALLEST_LIKELY_WINDOW, sleep, } from "./retry.js";
|
|
19
19
|
export { runTurn } from "./run-turn.js";
|
package/dist/reset.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Forgets everything this package remembers between calls.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five modules here keep state for the life of the process, each for a good reason and each
|
|
5
5
|
* with its own seam: the pooled clients and their model listings, the endpoints that turned
|
|
6
6
|
* out not to take `stream_options` or a grammar, the models that refused the no-thinking
|
|
7
|
-
* hints, and the
|
|
8
|
-
* exported, because a test that means to clear
|
|
7
|
+
* hints, the event bus, and the hooks' configured budget. `resetClients`, `resetCapabilities`,
|
|
8
|
+
* `resetHints`, `resetEvents` and `resetHooks` stay exported, because a test that means to clear
|
|
9
|
+
* one thing should say so.
|
|
9
10
|
*
|
|
10
|
-
* This is for the other case, which is every teardown. What
|
|
11
|
+
* This is for the other case, which is every teardown. What they hold is *latched
|
|
11
12
|
* refusals* — a fact one test taught the process about an endpoint, still true as far as the
|
|
12
13
|
* next test can tell. Miss one and the suite becomes order-dependent in the way that passes
|
|
13
14
|
* locally and fails in CI on a different shard: the test that latched it still passes, and the
|
|
14
15
|
* one that reads the latch fails only when it happens to run second. `tests/side-task-hints.test.ts`
|
|
15
16
|
* was written that way and only passed because every case had been handed a hostname of its own.
|
|
16
17
|
*
|
|
17
|
-
* It is also the seam that does not need finding again. A
|
|
18
|
+
* It is also the seam that does not need finding again. A sixth module with a cache is a sixth
|
|
18
19
|
* line here, rather than an edit to the teardown of three consumers who will not all notice.
|
|
19
20
|
*/
|
|
20
21
|
export declare function resetAll(): void;
|
package/dist/reset.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { resetCapabilities } from "./capabilities.js";
|
|
2
2
|
import { resetClients } from "./client.js";
|
|
3
3
|
import { resetEvents } from "./events.js";
|
|
4
|
+
import { resetHooks } from "./hooks.js";
|
|
4
5
|
import { resetHints } from "./side-task.js";
|
|
5
6
|
/**
|
|
6
7
|
* Forgets everything this package remembers between calls.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
+
* Five modules here keep state for the life of the process, each for a good reason and each
|
|
9
10
|
* with its own seam: the pooled clients and their model listings, the endpoints that turned
|
|
10
11
|
* out not to take `stream_options` or a grammar, the models that refused the no-thinking
|
|
11
|
-
* hints, and the
|
|
12
|
-
* exported, because a test that means to clear
|
|
12
|
+
* hints, the event bus, and the hooks' configured budget. `resetClients`, `resetCapabilities`,
|
|
13
|
+
* `resetHints`, `resetEvents` and `resetHooks` stay exported, because a test that means to clear
|
|
14
|
+
* one thing should say so.
|
|
13
15
|
*
|
|
14
|
-
* This is for the other case, which is every teardown. What
|
|
16
|
+
* This is for the other case, which is every teardown. What they hold is *latched
|
|
15
17
|
* refusals* — a fact one test taught the process about an endpoint, still true as far as the
|
|
16
18
|
* next test can tell. Miss one and the suite becomes order-dependent in the way that passes
|
|
17
19
|
* locally and fails in CI on a different shard: the test that latched it still passes, and the
|
|
18
20
|
* one that reads the latch fails only when it happens to run second. `tests/side-task-hints.test.ts`
|
|
19
21
|
* was written that way and only passed because every case had been handed a hostname of its own.
|
|
20
22
|
*
|
|
21
|
-
* It is also the seam that does not need finding again. A
|
|
23
|
+
* It is also the seam that does not need finding again. A sixth module with a cache is a sixth
|
|
22
24
|
* line here, rather than an edit to the teardown of three consumers who will not all notice.
|
|
23
25
|
*/
|
|
24
26
|
export function resetAll() {
|
|
@@ -26,4 +28,5 @@ export function resetAll() {
|
|
|
26
28
|
resetCapabilities();
|
|
27
29
|
resetHints();
|
|
28
30
|
resetEvents();
|
|
31
|
+
resetHooks();
|
|
29
32
|
}
|
package/llms.txt
CHANGED
|
@@ -73,19 +73,22 @@ What a run is doing, while it is doing it.
|
|
|
73
73
|
Lifecycle hooks, from the host's side: what a session looks like to them, where their context lands in a request, and what is said about each one.
|
|
74
74
|
|
|
75
75
|
- `assembleContext` — Builds the context a set of outcomes adds and the notes that go with it.
|
|
76
|
+
- `configureHooks` — Changes what hooks are held to, for a process whose windows are not the size these defaults were chosen for.
|
|
76
77
|
- `Gathered` (type) — The context a set of outcomes adds to a request, and a note for each hook worth mentioning.
|
|
77
78
|
- `gather` — Runs the hooks ahead of a request and builds what they add to it.
|
|
78
|
-
- `HOOK_CONTEXT_TOKENS` — The most context all of a request's hooks add between them, in estimated tokens.
|
|
79
|
+
- `HOOK_CONTEXT_TOKENS` — The most context all of a request's hooks add between them by default, in estimated tokens.
|
|
79
80
|
- `HOOK_EVENTS` — Every event a hook can be bound to, in the order a session meets them.
|
|
80
81
|
- `HOOK_PREFACE` — Said once, above the blocks, so the model reads them as background rather than instructions.
|
|
81
82
|
- `HookContext` (type) — What a host knows at an event.
|
|
82
83
|
- `HookEvent` (type) — A point in a session a hook can be bound to.
|
|
83
84
|
- `HookMessage` (type) — One message of a session as a hook is handed it.
|
|
84
85
|
- `HookNote` (type) — One hook's line for whoever is watching: the context it added, or why it added none.
|
|
86
|
+
- `HookOptions` (type) — What hooks are held to across a process.
|
|
85
87
|
- `HookOutcome` (type) — What one hook did.
|
|
86
88
|
- `HookRunner` (type) — Runs one event's hooks.
|
|
87
89
|
- `INJECT_EVENTS` — The events whose hooks run before a request, and so the only ones whose output can reach it.
|
|
88
90
|
- `notify` — Runs the hooks for an event that reads what happened and adds nothing to a request.
|
|
91
|
+
- `resetHooks` — Test seam: puts `configureHooks` back to the defaults, so one test's budget is not the next's.
|
|
89
92
|
- `turnIndex` — Which turn of a session begins at a point, from 0: the user messages ahead of it.
|
|
90
93
|
- `turnMessages` — A stretch of a transcript as a hook reads it: what the user and the assistant said, and nothing else.
|
|
91
94
|
- `withContext` — The request, with the hooks' context added to this turn's question.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubicecho/agent-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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",
|