@cubicecho/agent-core 2.8.1 → 2.9.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 CHANGED
@@ -504,6 +504,18 @@ Either one given something that is not a number above zero keeps what was there,
504
504
  `configureEvents` does, so a `0` threaded through for "no opinion" does not switch recall off.
505
505
  `resetHooks` (and `resetAll`) puts the default back.
506
506
 
507
+ The preface said above the blocks moves the same way. `HOOK_PREFACE` names no host, so a host that
508
+ wants its own name says so once with `configureHooks({ preface })`, and a `preface` passed to
509
+ `withContext` (or on `runAgentLoop`'s `hooks`) wins over it for one request. An empty string is a
510
+ preface of nothing — the blocks lead the question on their own, with no blank line above them —
511
+ and anything that is not a string keeps what was there:
512
+
513
+ ```ts
514
+ configureHooks({ preface: "Added by my-host's hooks — background, not the user's words:" });
515
+ const request = withContext(messages, messages.length - 1, gathered.context); // says it
516
+ withContext(messages, messages.length - 1, gathered.context, ""); // says nothing
517
+ ```
518
+
507
519
  Neither function rejects. A hook failing is an outcome, and a runner that throws outright is
508
520
  noted once for its event and costs only that event's context. `notify` takes no signal: a reader
509
521
  who leaves once the turn is answered has not asked for it not to be remembered.
@@ -105,7 +105,7 @@ export interface AgentLoopHooks {
105
105
  events?: readonly HookEvent[];
106
106
  /** The shared context budget. Absent is `configureHooks`'s. */
107
107
  maxTokens?: number;
108
- /** Said above the context blocks. Absent is `HOOK_PREFACE`. */
108
+ /** Said above the context blocks. Absent is `configureHooks`'s; empty is none. */
109
109
  preface?: string;
110
110
  /** Hears each note, from before the request and from `afterTurn`. */
111
111
  onNote?: (note: HookNote) => void;
package/dist/hooks.d.ts CHANGED
@@ -129,6 +129,12 @@ export interface Gathered {
129
129
  * `configureHooks` moves it for a process, and `gather` and `assembleContext` for one request.
130
130
  */
131
131
  export declare const HOOK_CONTEXT_TOKENS = 2000;
132
+ /**
133
+ * Said once, above the blocks, so the model reads them as background rather than instructions.
134
+ * Names no host; `configureHooks` sets another for a process that wants to, and `withContext`
135
+ * for one request.
136
+ */
137
+ export declare const HOOK_PREFACE: string;
132
138
  /** What hooks are held to across a process. Every field optional; see `configureHooks`. */
133
139
  export interface HookOptions {
134
140
  /**
@@ -136,31 +142,34 @@ export interface HookOptions {
136
142
  * held to its own `maxTokens` inside it.
137
143
  */
138
144
  contextTokens?: number;
145
+ /**
146
+ * Said above the context blocks, when a call does not give its own. Empty says nothing, and the
147
+ * blocks lead the question on their own.
148
+ */
149
+ preface?: string;
139
150
  }
140
151
  /**
141
152
  * Changes what hooks are held to, for a process whose windows are not the size these defaults
142
- * were chosen for.
153
+ * were chosen for, or whose host wants its own name above the context.
143
154
  *
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.
155
+ * Module-level for the same reason `configureEvents` is: a budget and a preface are a deployment's
156
+ * settings, said once at startup. A caller that sizes the budget per model or per agent — a 128k
157
+ * window can afford more recall than an 8k one — passes `maxTokens` to `gather` instead, and a
158
+ * `preface` passed to `withContext` or `runAgentLoop`'s hooks wins over this one the same way.
147
159
  *
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.
160
+ * @param options The settings to change. A field left out keeps what it has, and so does one given
161
+ * the wrong kind of value `contextTokens` anything but a number above zero, `preface` anything
162
+ * but a string so a half-built config narrows nothing. `Infinity` is a number above zero, and
163
+ * lifts the shared budget entirely. An empty `preface` is a string, and turns the preface off.
151
164
  * @returns Everything in force afterwards, including what this call did not change.
152
165
  */
153
166
  export declare function configureHooks(options?: HookOptions): Required<HookOptions>;
154
167
  /**
155
- * Test seam: puts `configureHooks` back to the defaults, so one test's budget is not the next's.
168
+ * Test seam: puts `configureHooks` back to the defaults, so one test's budget or preface is not the
169
+ * next's.
156
170
  * `resetAll` calls it.
157
171
  */
158
172
  export declare const resetHooks: () => void;
159
- /**
160
- * Said once, above the blocks, so the model reads them as background rather than instructions.
161
- * Names no host; `withContext` takes another for one that wants to.
162
- */
163
- export declare const HOOK_PREFACE: string;
164
173
  /**
165
174
  * Builds the context a set of outcomes adds and the notes that go with it.
166
175
  *
@@ -192,7 +201,8 @@ export declare function assembleContext(outcomes: readonly HookOutcome[], maxTok
192
201
  * session once a compaction has folded the head into a summary. Anything but a user message there
193
202
  * leaves the request as it was.
194
203
  * @param context What `assembleContext` built. Empty returns `history` itself.
195
- * @param preface Said above the blocks. Defaults to `HOOK_PREFACE`.
204
+ * @param preface Said above the blocks. Absent is what `configureHooks` last set — `HOOK_PREFACE`
205
+ * unless something moved it. Empty says nothing, rather than leaving a blank line where it was.
196
206
  * @returns `history` when there was nothing to add or nowhere to add it, otherwise a new array.
197
207
  */
198
208
  export declare function withContext(history: OpenAI.ChatCompletionMessageParam[], index: number, context: string, preface?: string): OpenAI.ChatCompletionMessageParam[];
package/dist/hooks.js CHANGED
@@ -23,49 +23,58 @@ export const INJECT_EVENTS = new Set(["sessionStart", "beforeTurn"]);
23
23
  * `configureHooks` moves it for a process, and `gather` and `assembleContext` for one request.
24
24
  */
25
25
  export const HOOK_CONTEXT_TOKENS = 2000;
26
- /** The numbers this module was written with. */
27
- const HOOK_DEFAULTS = { contextTokens: HOOK_CONTEXT_TOKENS };
26
+ /**
27
+ * Said once, above the blocks, so the model reads them as background rather than instructions.
28
+ * Names no host; `configureHooks` sets another for a process that wants to, and `withContext`
29
+ * for one request.
30
+ */
31
+ export const HOOK_PREFACE = "The <context> blocks below were added for this message by the host's hooks. They are " +
32
+ "background the user did not write and may not be relevant. The user's message follows them.";
33
+ /** The settings this module was written with. */
34
+ const HOOK_DEFAULTS = {
35
+ contextTokens: HOOK_CONTEXT_TOKENS,
36
+ preface: HOOK_PREFACE,
37
+ };
28
38
  /** What is in force now. Read where it is used, so a change applies from the next request. */
29
- let hookLimits = { ...HOOK_DEFAULTS };
39
+ let hookSettings = { ...HOOK_DEFAULTS };
30
40
  /**
31
41
  * Changes what hooks are held to, for a process whose windows are not the size these defaults
32
- * were chosen for.
42
+ * were chosen for, or whose host wants its own name above the context.
33
43
  *
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.
44
+ * Module-level for the same reason `configureEvents` is: a budget and a preface are a deployment's
45
+ * settings, said once at startup. A caller that sizes the budget per model or per agent — a 128k
46
+ * window can afford more recall than an 8k one — passes `maxTokens` to `gather` instead, and a
47
+ * `preface` passed to `withContext` or `runAgentLoop`'s hooks wins over this one the same way.
37
48
  *
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.
49
+ * @param options The settings to change. A field left out keeps what it has, and so does one given
50
+ * the wrong kind of value `contextTokens` anything but a number above zero, `preface` anything
51
+ * but a string so a half-built config narrows nothing. `Infinity` is a number above zero, and
52
+ * lifts the shared budget entirely. An empty `preface` is a string, and turns the preface off.
41
53
  * @returns Everything in force afterwards, including what this call did not change.
42
54
  */
43
55
  export function configureHooks(options = {}) {
44
- for (const [name, value] of Object.entries(options)) {
45
- if (typeof value === "number" && value > 0)
46
- hookLimits[name] = value;
56
+ const { contextTokens, preface } = options;
57
+ if (typeof contextTokens === "number" && contextTokens > 0) {
58
+ hookSettings.contextTokens = contextTokens;
47
59
  }
48
- return { ...hookLimits };
60
+ if (typeof preface === "string")
61
+ hookSettings.preface = preface;
62
+ return { ...hookSettings };
49
63
  }
50
64
  /**
51
- * Test seam: puts `configureHooks` back to the defaults, so one test's budget is not the next's.
65
+ * Test seam: puts `configureHooks` back to the defaults, so one test's budget or preface is not the
66
+ * next's.
52
67
  * `resetAll` calls it.
53
68
  */
54
69
  export const resetHooks = () => {
55
- hookLimits = { ...HOOK_DEFAULTS };
70
+ hookSettings = { ...HOOK_DEFAULTS };
56
71
  };
57
72
  /**
58
73
  * The budget a call is held to: its own when it gave a usable one, the process's otherwise. The
59
74
  * same rule `configureHooks` applies, so a `0` threaded through for "no opinion" does not quietly
60
75
  * turn every hook's context off.
61
76
  */
62
- const budget = (given) => typeof given === "number" && given > 0 ? given : hookLimits.contextTokens;
63
- /**
64
- * Said once, above the blocks, so the model reads them as background rather than instructions.
65
- * Names no host; `withContext` takes another for one that wants to.
66
- */
67
- export const HOOK_PREFACE = "The <context> blocks below were added for this message by the host's hooks. They are " +
68
- "background the user did not write and may not be relevant. The user's message follows them.";
77
+ const budget = (given) => typeof given === "number" && given > 0 ? given : hookSettings.contextTokens;
69
78
  const attribute = (text) => text.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;");
70
79
  /**
71
80
  * Builds the context a set of outcomes adds and the notes that go with it.
@@ -124,14 +133,15 @@ export function assembleContext(outcomes, maxTokens) {
124
133
  * session once a compaction has folded the head into a summary. Anything but a user message there
125
134
  * leaves the request as it was.
126
135
  * @param context What `assembleContext` built. Empty returns `history` itself.
127
- * @param preface Said above the blocks. Defaults to `HOOK_PREFACE`.
136
+ * @param preface Said above the blocks. Absent is what `configureHooks` last set — `HOOK_PREFACE`
137
+ * unless something moved it. Empty says nothing, rather than leaving a blank line where it was.
128
138
  * @returns `history` when there was nothing to add or nowhere to add it, otherwise a new array.
129
139
  */
130
- export function withContext(history, index, context, preface = HOOK_PREFACE) {
140
+ export function withContext(history, index, context, preface = hookSettings.preface) {
131
141
  const message = history[index];
132
142
  if (!context || message?.role !== "user")
133
143
  return history;
134
- const lead = `${preface}\n\n${context}\n\n`;
144
+ const lead = preface ? `${preface}\n\n${context}\n\n` : `${context}\n\n`;
135
145
  const content = typeof message.content === "string"
136
146
  ? `${lead}${message.content}`
137
147
  : [{ type: "text", text: lead }, ...message.content];
package/dist/reset.d.ts CHANGED
@@ -4,9 +4,9 @@
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, 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.
7
+ * hints, the event bus, and the hooks' configured budget and preface. `resetClients`,
8
+ * `resetCapabilities`, `resetHints`, `resetEvents` and `resetHooks` stay exported, because a
9
+ * test that means to clear one thing should say so.
10
10
  *
11
11
  * This is for the other case, which is every teardown. What they hold is *latched
12
12
  * refusals* — a fact one test taught the process about an endpoint, still true as far as the
package/dist/reset.js CHANGED
@@ -9,9 +9,9 @@ import { resetHints } from "./side-task.js";
9
9
  * Five modules here keep state for the life of the process, each for a good reason and each
10
10
  * with its own seam: the pooled clients and their model listings, the endpoints that turned
11
11
  * out not to take `stream_options` or a grammar, the models that refused the no-thinking
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.
12
+ * hints, the event bus, and the hooks' configured budget and preface. `resetClients`,
13
+ * `resetCapabilities`, `resetHints`, `resetEvents` and `resetHooks` stay exported, because a
14
+ * test that means to clear one thing should say so.
15
15
  *
16
16
  * This is for the other case, which is every teardown. What they hold is *latched
17
17
  * refusals* — a fact one test taught the process about an endpoint, still true as far as the
package/llms.txt CHANGED
@@ -108,7 +108,7 @@ What a run is doing, while it is doing it.
108
108
  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.
109
109
 
110
110
  - `assembleContext` — Builds the context a set of outcomes adds and the notes that go with it.
111
- - `configureHooks` — Changes what hooks are held to, for a process whose windows are not the size these defaults were chosen for.
111
+ - `configureHooks` — Changes what hooks are held to, for a process whose windows are not the size these defaults were chosen for, or whose host wants its own name above the context.
112
112
  - `Gathered` (type) — The context a set of outcomes adds to a request, and a note for each hook worth mentioning.
113
113
  - `gather` — Runs the hooks ahead of a request and builds what they add to it.
114
114
  - `HOOK_CONTEXT_TOKENS` — The most context all of a request's hooks add between them by default, in estimated tokens.
@@ -123,7 +123,7 @@ Lifecycle hooks, from the host's side: what a session looks like to them, where
123
123
  - `HookRunner` (type) — Runs one event's hooks.
124
124
  - `INJECT_EVENTS` — The events whose hooks run before a request, and so the only ones whose output can reach it.
125
125
  - `notify` — Runs the hooks for an event that reads what happened and adds nothing to a request.
126
- - `resetHooks` — Test seam: puts `configureHooks` back to the defaults, so one test's budget is not the next's.
126
+ - `resetHooks` — Test seam: puts `configureHooks` back to the defaults, so one test's budget or preface is not the next's.
127
127
  - `turnIndex` — Which turn of a session begins at a point, from 0: the user messages ahead of it.
128
128
  - `turnMessages` — A stretch of a transcript as a hook reads it: what the user and the assistant said, and nothing else.
129
129
  - `UNTRUSTED_PREFACE` — One sentence for a system prompt, saying what an `untrusted` block is.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubicecho/agent-core",
3
- "version": "2.8.1",
3
+ "version": "2.9.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",