@cubicecho/agent-core 2.21.1 → 2.23.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.
@@ -0,0 +1,120 @@
1
+ import { preselect, runAgentLoop } from "./agent-loop.ts";
2
+ import { calibrate, charsPerTokenFor, resetCalibration } from "./calibration.ts";
3
+ import { capabilitiesFor, expireCapabilities, resetCapabilities } from "./capabilities.ts";
4
+ import { type ClientPoolOptions, configureClients, contextLimitFor, getClient, listModels, resetClients, servedWindow } from "./client.ts";
5
+ import { compactTranscript, runCompaction, summariser } from "./compaction.ts";
6
+ import { continueTurn } from "./continuation.ts";
7
+ import { configureEvents, type EventBusOptions, emit, endRun, history, resetEvents, watch } from "./events.ts";
8
+ import { assembleContext, configureHooks, consult, gather, type HookOptions, notify, resetHooks, withContext } from "./hooks.ts";
9
+ import { resetAll } from "./reset.ts";
10
+ import { runTurn } from "./run-turn.ts";
11
+ import { ask, askJson, resetHints, tryAsk } from "./side-task.ts";
12
+ import { exportCapabilities, importCapabilities } from "./snapshot.ts";
13
+ /**
14
+ * Every exported function that reads or writes what a runtime remembers, itself or through what
15
+ * it calls. The rest of the package is pure, and is the same function whichever runtime it is
16
+ * called beside.
17
+ */
18
+ declare const STATEFUL: {
19
+ runAgentLoop: typeof runAgentLoop;
20
+ preselect: typeof preselect;
21
+ runTurn: typeof runTurn;
22
+ continueTurn: typeof continueTurn;
23
+ ask: typeof ask;
24
+ askJson: typeof askJson;
25
+ tryAsk: typeof tryAsk;
26
+ summariser: typeof summariser;
27
+ runCompaction: typeof runCompaction;
28
+ compactTranscript: typeof compactTranscript;
29
+ getClient: typeof getClient;
30
+ listModels: typeof listModels;
31
+ contextLimitFor: typeof contextLimitFor;
32
+ servedWindow: typeof servedWindow;
33
+ configureClients: typeof configureClients;
34
+ capabilitiesFor: typeof capabilitiesFor;
35
+ expireCapabilities: typeof expireCapabilities;
36
+ exportCapabilities: typeof exportCapabilities;
37
+ importCapabilities: typeof importCapabilities;
38
+ calibrate: typeof calibrate;
39
+ charsPerTokenFor: typeof charsPerTokenFor;
40
+ emit: typeof emit;
41
+ endRun: typeof endRun;
42
+ history: typeof history;
43
+ watch: typeof watch;
44
+ configureEvents: typeof configureEvents;
45
+ configureHooks: typeof configureHooks;
46
+ gather: typeof gather;
47
+ notify: typeof notify;
48
+ consult: typeof consult;
49
+ assembleContext: typeof assembleContext;
50
+ withContext: typeof withContext;
51
+ resetClients: typeof resetClients;
52
+ resetCapabilities: typeof resetCapabilities;
53
+ resetCalibration: typeof resetCalibration;
54
+ resetHints: typeof resetHints;
55
+ resetEvents: typeof resetEvents;
56
+ resetHooks: typeof resetHooks;
57
+ resetAll: typeof resetAll;
58
+ };
59
+ /** What a runtime starts out configured with. Every field optional; see `createRuntime`. */
60
+ export interface RuntimeOptions {
61
+ /** The client pool's bounds, as `configureClients` takes them. */
62
+ clients?: ClientPoolOptions;
63
+ /** The event bus's bounds, as `configureEvents` takes them. */
64
+ events?: EventBusOptions;
65
+ /** The hooks' budget and preface, as `configureHooks` takes them. */
66
+ hooks?: HookOptions;
67
+ }
68
+ /**
69
+ * One set of the package's caches, with the functions that use them as methods.
70
+ *
71
+ * Each method is the top-level function of the same name, with the same signature and doc
72
+ * comment, run against this runtime's state instead of the process's: its pooled clients and
73
+ * model listings, latched capabilities, measured characters per token, no-thinking hints, event
74
+ * bus, and hook settings. `resetAll` and the six narrower resets clear this runtime alone.
75
+ */
76
+ export type Runtime = typeof STATEFUL & {
77
+ /**
78
+ * Runs `fn` with this runtime current, so every top-level function it calls — directly, after
79
+ * an `await`, or from a timer it sets — uses this runtime's state. What the methods do, for
80
+ * code that calls the top-level functions itself.
81
+ *
82
+ * It does not reach a callback that outlives `fn` and is called from elsewhere — a generator
83
+ * resumed later, a function handed back and called by someone else. Call a method there.
84
+ *
85
+ * @param fn What to run. Its return value, or what it throws, is handed straight back.
86
+ */
87
+ run<T>(fn: () => T): T;
88
+ };
89
+ /**
90
+ * A runtime of its own: separate clients, listings, capability latches, hints, event bus and hook
91
+ * settings from the process's and from every other runtime's.
92
+ *
93
+ * For a host that must not share them — one that mints a key per tenant, where a shared pool has
94
+ * tenants evicting each other's clients and a shared bus has one tenant's run id readable by
95
+ * another — and for tests, where a runtime per case needs no `resetAll` between them and can run
96
+ * in parallel. A host with one deployment's worth of endpoints does not need one: the top-level
97
+ * functions are the default runtime's methods.
98
+ *
99
+ * The runtime is carried by async context rather than by argument, so `runAgentLoop` called as a
100
+ * method uses this runtime all the way down, and so does a top-level function the host calls from
101
+ * inside it — from `dispatch`, a hook, `onEvent`. Outside a method or `run`, a top-level function
102
+ * is the default runtime's again; a request handler that emits to a run a runtime started calls
103
+ * `runtime.emit`.
104
+ *
105
+ * Nothing needs closing. A runtime is garbage once nothing holds it; its one timer, the event
106
+ * bus's sweep, is unreferenced and stops rescheduling when its last run has been swept.
107
+ *
108
+ * @param options What to configure it with before first use. A part left out keeps the
109
+ * defaults — not the default runtime's settings, which a new runtime does not inherit.
110
+ */
111
+ export declare function createRuntime(options?: RuntimeOptions): Runtime;
112
+ /**
113
+ * The runtime the top-level functions use when no other is running.
114
+ *
115
+ * `defaultRuntime.getClient(endpoint)` and `getClient(endpoint)` are the same call outside any
116
+ * runtime. Inside one they differ: the top-level function follows the runtime that is running,
117
+ * and this always reaches the process-wide state.
118
+ */
119
+ export declare const defaultRuntime: Runtime;
120
+ export {};
@@ -0,0 +1,113 @@
1
+ import { preselect, runAgentLoop } from "./agent-loop.js";
2
+ import { calibrate, charsPerTokenFor, resetCalibration } from "./calibration.js";
3
+ import { capabilitiesFor, expireCapabilities, resetCapabilities } from "./capabilities.js";
4
+ import { configureClients, contextLimitFor, getClient, listModels, resetClients, servedWindow, } from "./client.js";
5
+ import { compactTranscript, runCompaction, summariser } from "./compaction.js";
6
+ import { continueTurn } from "./continuation.js";
7
+ import { configureEvents, emit, endRun, history, resetEvents, watch, } from "./events.js";
8
+ import { assembleContext, configureHooks, consult, gather, notify, resetHooks, withContext, } from "./hooks.js";
9
+ import { resetAll } from "./reset.js";
10
+ import { runTurn } from "./run-turn.js";
11
+ import { inScope, rootScope } from "./scope.js";
12
+ import { ask, askJson, resetHints, tryAsk } from "./side-task.js";
13
+ import { exportCapabilities, importCapabilities } from "./snapshot.js";
14
+ /**
15
+ * Every exported function that reads or writes what a runtime remembers, itself or through what
16
+ * it calls. The rest of the package is pure, and is the same function whichever runtime it is
17
+ * called beside.
18
+ */
19
+ const STATEFUL = {
20
+ runAgentLoop,
21
+ preselect,
22
+ runTurn,
23
+ continueTurn,
24
+ ask,
25
+ askJson,
26
+ tryAsk,
27
+ summariser,
28
+ runCompaction,
29
+ compactTranscript,
30
+ getClient,
31
+ listModels,
32
+ contextLimitFor,
33
+ servedWindow,
34
+ configureClients,
35
+ capabilitiesFor,
36
+ expireCapabilities,
37
+ exportCapabilities,
38
+ importCapabilities,
39
+ calibrate,
40
+ charsPerTokenFor,
41
+ emit,
42
+ endRun,
43
+ history,
44
+ watch,
45
+ configureEvents,
46
+ configureHooks,
47
+ gather,
48
+ notify,
49
+ consult,
50
+ assembleContext,
51
+ withContext,
52
+ resetClients,
53
+ resetCapabilities,
54
+ resetCalibration,
55
+ resetHints,
56
+ resetEvents,
57
+ resetHooks,
58
+ resetAll,
59
+ };
60
+ /** Makes the methods of the runtime whose state is `scope`. */
61
+ function bind(scope) {
62
+ const within = (fn) => (...args) => inScope(scope, () => fn(...args));
63
+ const methods = Object.fromEntries(Object.entries(STATEFUL).map(([name, fn]) => [
64
+ name,
65
+ within(fn),
66
+ ]));
67
+ return {
68
+ ...methods,
69
+ // What it hands back is called later, by `compactTranscript` or by the host, and asks then.
70
+ summariser: (...args) => within(summariser(...args)),
71
+ run: (fn) => inScope(scope, fn),
72
+ };
73
+ }
74
+ /**
75
+ * A runtime of its own: separate clients, listings, capability latches, hints, event bus and hook
76
+ * settings from the process's and from every other runtime's.
77
+ *
78
+ * For a host that must not share them — one that mints a key per tenant, where a shared pool has
79
+ * tenants evicting each other's clients and a shared bus has one tenant's run id readable by
80
+ * another — and for tests, where a runtime per case needs no `resetAll` between them and can run
81
+ * in parallel. A host with one deployment's worth of endpoints does not need one: the top-level
82
+ * functions are the default runtime's methods.
83
+ *
84
+ * The runtime is carried by async context rather than by argument, so `runAgentLoop` called as a
85
+ * method uses this runtime all the way down, and so does a top-level function the host calls from
86
+ * inside it — from `dispatch`, a hook, `onEvent`. Outside a method or `run`, a top-level function
87
+ * is the default runtime's again; a request handler that emits to a run a runtime started calls
88
+ * `runtime.emit`.
89
+ *
90
+ * Nothing needs closing. A runtime is garbage once nothing holds it; its one timer, the event
91
+ * bus's sweep, is unreferenced and stops rescheduling when its last run has been swept.
92
+ *
93
+ * @param options What to configure it with before first use. A part left out keeps the
94
+ * defaults — not the default runtime's settings, which a new runtime does not inherit.
95
+ */
96
+ export function createRuntime(options = {}) {
97
+ const runtime = bind(new Map());
98
+ if (options.clients)
99
+ runtime.configureClients(options.clients);
100
+ if (options.events)
101
+ runtime.configureEvents(options.events);
102
+ if (options.hooks)
103
+ runtime.configureHooks(options.hooks);
104
+ return runtime;
105
+ }
106
+ /**
107
+ * The runtime the top-level functions use when no other is running.
108
+ *
109
+ * `defaultRuntime.getClient(endpoint)` and `getClient(endpoint)` are the same call outside any
110
+ * runtime. Inside one they differ: the top-level function follows the runtime that is running,
111
+ * and this always reaches the process-wide state.
112
+ */
113
+ export const defaultRuntime = bind(rootScope);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Everything one runtime remembers: each module's state, under a key only that module holds.
3
+ *
4
+ * A map rather than a record of named caches, so a module that grows a cache declares it where
5
+ * it is used and `runtime.ts` does not have to hear about it.
6
+ */
7
+ export type Scope = Map<symbol, unknown>;
8
+ /** The default runtime's scope: what the top-level functions use when no runtime is running. */
9
+ export declare const rootScope: Scope;
10
+ /** The scope of the runtime whose method is running, or the default one outside any. */
11
+ export declare const currentScope: () => Scope;
12
+ /**
13
+ * Runs `fn` with `scope` as the current one, for it and for everything it awaits or schedules.
14
+ *
15
+ * @param scope The runtime's state. Nested calls replace it for their own duration only.
16
+ * @param fn What to run. Its return value, or what it throws, is handed straight back.
17
+ */
18
+ export declare const inScope: <T>(scope: Scope, fn: () => T) => T;
19
+ /** One module's state, read from whichever scope is current. See `scoped`. */
20
+ export interface Scoped<T> {
21
+ /** The current scope's copy, made on first use. */
22
+ (): T;
23
+ /** Drops the current scope's copy, so the next read makes a fresh one. */
24
+ reset(): void;
25
+ }
26
+ /**
27
+ * Declares a piece of module state that every runtime has its own copy of.
28
+ *
29
+ * What used to be `const cache = new Map()` at the top of a module is `scoped(() => new Map())`,
30
+ * and each use reads `cache()`. The copy is made lazily, so a runtime pays only for the modules
31
+ * it touches.
32
+ *
33
+ * @param create Builds a fresh, empty copy. Called once per scope, and again after `reset`.
34
+ */
35
+ export declare function scoped<T>(create: () => T): Scoped<T>;
package/dist/scope.js ADDED
@@ -0,0 +1,42 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ /**
3
+ * Which scope the code running now belongs to, carried across every `await` beneath a runtime's
4
+ * method. Async context rather than a parameter, because the state is read five calls down —
5
+ * `runAgentLoop` to `runTurn` to `negotiate` to `capabilitiesFor` — and threading it would put a
6
+ * runtime argument on every exported function in between.
7
+ */
8
+ const active = new AsyncLocalStorage();
9
+ /** The default runtime's scope: what the top-level functions use when no runtime is running. */
10
+ export const rootScope = new Map();
11
+ /** The scope of the runtime whose method is running, or the default one outside any. */
12
+ export const currentScope = () => active.getStore() ?? rootScope;
13
+ /**
14
+ * Runs `fn` with `scope` as the current one, for it and for everything it awaits or schedules.
15
+ *
16
+ * @param scope The runtime's state. Nested calls replace it for their own duration only.
17
+ * @param fn What to run. Its return value, or what it throws, is handed straight back.
18
+ */
19
+ export const inScope = (scope, fn) => active.run(scope, fn);
20
+ /**
21
+ * Declares a piece of module state that every runtime has its own copy of.
22
+ *
23
+ * What used to be `const cache = new Map()` at the top of a module is `scoped(() => new Map())`,
24
+ * and each use reads `cache()`. The copy is made lazily, so a runtime pays only for the modules
25
+ * it touches.
26
+ *
27
+ * @param create Builds a fresh, empty copy. Called once per scope, and again after `reset`.
28
+ */
29
+ export function scoped(create) {
30
+ const key = Symbol();
31
+ const read = () => {
32
+ const scope = currentScope();
33
+ if (!scope.has(key))
34
+ scope.set(key, create());
35
+ return scope.get(key);
36
+ };
37
+ return Object.assign(read, {
38
+ reset: () => {
39
+ currentScope().delete(key);
40
+ },
41
+ });
42
+ }
@@ -20,10 +20,22 @@ export declare const resetHints: () => void;
20
20
  export type SideTaskInput = string | OpenAI.ChatCompletionContentPart[];
21
21
  /** What a side task may be given. All optional — one given none of them still runs. */
22
22
  export interface SideTaskOptions {
23
- /** Ceiling on the reply, default 512. These answers are meant to be short. */
23
+ /**
24
+ * Ceiling on the reply, default 512. These answers are meant to be short. Zero or less sends
25
+ * none and leaves it to the server, which is what `maxTokens: 0` means on the main model too.
26
+ */
24
27
  maxTokens?: number;
25
28
  /** Sampling temperature, default 0.3. Naming and classifying want the same answer twice. */
26
29
  temperature?: number;
30
+ /**
31
+ * A reasoning effort to ask for in place of the no-thinking hints, for a side task an operator
32
+ * has pointed at a model that should deliberate — a compaction summary is the usual one.
33
+ *
34
+ * Absent, `""` and `"off"` all keep the hints, because off is what they already ask for. Any
35
+ * other value is sent as `reasoning_effort`, stepped up by `effortFor` where the model has
36
+ * refused it by value, and `chat_template_kwargs` is left out of the request altogether.
37
+ */
38
+ reasoningEffort?: string;
27
39
  /** Abandons the call, usually because the run it supports has gone away. */
28
40
  signal?: AbortSignal;
29
41
  /**
@@ -40,6 +52,27 @@ export interface SideTaskOptions {
40
52
  */
41
53
  onNotice?: (message: string) => void;
42
54
  }
55
+ /**
56
+ * One side task's own settings, as a resolved agent spec carries them under `tasks.<key>`.
57
+ *
58
+ * Declared here rather than imported from `spec.ts`, whose `ResolvedTask` it matches field for
59
+ * field: the spec module is one nothing else in `src/` imports, and a host that is not on the spec
60
+ * can write one of these by hand. An absent setting is the side task's own default — 0.3, the
61
+ * entry point's ceiling, thinking off — and never the main model's, so raising the agent's
62
+ * temperature for the chat does not make its titler creative. See `taskCall`.
63
+ */
64
+ export interface SideTask {
65
+ /** The model that does the task. */
66
+ model: string;
67
+ /** Where that model is reached, which need not be where the agent's is. */
68
+ endpoint: Endpoint;
69
+ /** The reply's ceiling. Absent is the entry point's default; zero sends none. */
70
+ maxTokens?: number;
71
+ /** Absent is 0.3, whatever the agent's own temperature is. Zero means zero. */
72
+ temperature?: number;
73
+ /** As `SideTaskOptions.reasoningEffort`: a level replaces the no-thinking hints. */
74
+ reasoningEffort?: string;
75
+ }
43
76
  /**
44
77
  * Runs a side task and returns the reply text, thinking stripped. Throws like any request.
45
78
  *
@@ -47,7 +80,7 @@ export interface SideTaskOptions {
47
80
  * @param model The model to ask, usually smaller than the one running the work.
48
81
  * @param system The instruction.
49
82
  * @param user The input it applies to. Content parts where the model is being shown an image.
50
- * @param options Reply ceiling, temperature, cancellation, notices.
83
+ * @param options Reply ceiling, temperature, a reasoning effort, cancellation, notices.
51
84
  */
52
85
  export declare function ask(config: Endpoint, model: string, system: string, user: SideTaskInput, options?: SideTaskOptions): Promise<string>;
53
86
  /** What `askJson` takes besides a side task's options. */
package/dist/side-task.js CHANGED
@@ -4,6 +4,7 @@ import { endpointId, getClient } from "./client.js";
4
4
  import { errorMessage } from "./errors.js";
5
5
  import { isTransient } from "./retry.js";
6
6
  import { relaxTools, sanitizeTools } from "./schema-compat.js";
7
+ import { scoped } from "./scope.js";
7
8
  import { stripThinking } from "./thinking.js";
8
9
  /**
9
10
  * One-shot calls that support a run without being one: picking tools, naming a session,
@@ -39,13 +40,13 @@ const NO_THINKING = { chat_template_kwargs: { enable_thinking: false } };
39
40
  * Only the `chat_template_kwargs` half is here. `reasoning_effort` is `negotiate`'s to latch, on
40
41
  * the same (endpoint, model) pair, where a run on that model can read it too.
41
42
  */
42
- const noHints = new Set();
43
+ const noHints = scoped(() => new Set());
43
44
  /** An (endpoint, model) pair as `noHints` holds it: `[endpointId, model]`, stringified. */
44
45
  export const hintKey = (endpoint, model) => JSON.stringify([endpoint, model]);
45
46
  /** The pairs that refused the hints, the live set, for `exportCapabilities` and `importCapabilities`. */
46
- export const refusedHints = () => noHints;
47
+ export const refusedHints = () => noHints();
47
48
  /** Test seam, alongside `resetClients` and `resetAll`: forget which models refused the hints. */
48
- export const resetHints = () => noHints.clear();
49
+ export const resetHints = () => noHints().clear();
49
50
  /**
50
51
  * Whether a failure is the server complaining about the request, rather than failing to answer.
51
52
  *
@@ -72,7 +73,7 @@ function rejectedTheRequest(error) {
72
73
  * @param model The model to ask, usually smaller than the one running the work.
73
74
  * @param system The instruction.
74
75
  * @param user The input it applies to. Content parts where the model is being shown an image.
75
- * @param options Reply ceiling, temperature, cancellation, notices.
76
+ * @param options Reply ceiling, temperature, a reasoning effort, cancellation, notices.
76
77
  */
77
78
  export function ask(config, model, system, user, options = {}) {
78
79
  return complete(config, model, system, user, options);
@@ -81,22 +82,29 @@ export function ask(config, model, system, user, options = {}) {
81
82
  * The request `ask` and `askJson` share, with `format` deciding the extra body fields from what
82
83
  * the model and the endpoint have refused, rebuilt on every re-send.
83
84
  */
84
- async function complete(config, model, system, user, { maxTokens = 512, temperature = 0.3, signal, onNotice }, format) {
85
+ async function complete(config, model, system, user, { maxTokens = 512, temperature = 0.3, reasoningEffort, signal, onNotice }, format) {
86
+ // The effort the caller chose, or empty where it chose none: `""` and `"off"` read as absent,
87
+ // since off is what the hints below already ask for.
88
+ const level = effortFor(undefined, reasoningEffort);
85
89
  // Whether the last request carried an effort, which `negotiate` decides and not this function.
86
90
  let sentEffort = false;
87
91
  const send = (hints, effort, supports, refused) => {
88
92
  // `none` is what a side task wants and not always what the model offers: OpenAI's reasoning
89
93
  // models refuse it by value and list `minimal` as their floor. `effortFor` answers with the
90
94
  // cheapest rung this one takes, which `negotiate` has been stepping up as it was refused.
91
- const asked = effort ? effortFor(refused, "none") : "";
95
+ // A level the caller chose goes through the same ladder, which only ever steps up from it.
96
+ const asked = effort ? effortFor(refused, level || "none") : "";
92
97
  sentEffort = asked !== "";
93
98
  return getClient(config).chat.completions.create({
94
99
  model,
95
100
  // The reasoning models want the ceiling spelled the other way, and they are exactly the
96
- // models a side task most wants to stop deliberating.
97
- ...(refused && !refused.legacyTokenLimit
98
- ? { max_completion_tokens: maxTokens }
99
- : { max_tokens: maxTokens }),
101
+ // models a side task most wants to stop deliberating. Zero is no ceiling, as it is on the
102
+ // main model: a spec's `maxTokens: 0` sent as `max_tokens: 0` asks for an empty reply.
103
+ ...(maxTokens > 0
104
+ ? refused && !refused.legacyTokenLimit
105
+ ? { max_completion_tokens: maxTokens }
106
+ : { max_tokens: maxTokens }
107
+ : {}),
100
108
  // One that will only run at the temperature it was built with is sent none: a side task
101
109
  // wants the same answer twice, and 1.0 from that model is as close as it gets.
102
110
  ...(refused && !refused.chosenTemperature ? {} : { temperature }),
@@ -121,7 +129,9 @@ async function complete(config, model, system, user, { maxTokens = 512, temperat
121
129
  onNotice,
122
130
  });
123
131
  const key = hintKey(endpointId(config), model);
124
- const hints = !noHints.has(key);
132
+ // A chosen effort replaces the template hint rather than riding beside it: `enable_thinking:
133
+ // false` next to `reasoning_effort: "high"` is a request that contradicts itself.
134
+ const hints = !level && !noHints().has(key);
125
135
  let response;
126
136
  try {
127
137
  response = await attempt(hints, true);
@@ -134,7 +144,9 @@ async function complete(config, model, system, user, { maxTokens = 512, temperat
134
144
  const effort = sentEffort;
135
145
  if (!(hints || effort) || !rejectedTheRequest(error))
136
146
  throw error;
137
- onNotice?.(`${model} rejected a request carrying the no-thinking hints; retrying without them`);
147
+ onNotice?.(level
148
+ ? `${model} rejected a request carrying a reasoning effort; retrying without it`
149
+ : `${model} rejected a request carrying the no-thinking hints; retrying without them`);
138
150
  response = await attempt(false, false);
139
151
  // Latched on the finding, not the hypothesis: a context overflow is a 400 `negotiate` does
140
152
  // not recognise too, and it fails the retry the same way, leaving the next call to try the
@@ -142,7 +154,7 @@ async function complete(config, model, system, user, { maxTokens = 512, temperat
142
154
  // cannot latch is blamed; if it was the effort after all, the next call is left with only
143
155
  // the effort to drop and latches that instead.
144
156
  if (hints)
145
- noHints.add(key);
157
+ noHints().add(key);
146
158
  else
147
159
  modelCapabilitiesFor(supports, model).reasoningEffort = false;
148
160
  }
package/dist/spec.d.ts CHANGED
@@ -132,8 +132,14 @@ export interface TaskSpec {
132
132
  model?: string;
133
133
  /** Where that model lives, when it is not on the agent's endpoint. */
134
134
  endpoint?: EndpointSpec;
135
+ /** The reply's ceiling. Absent is the side task's own default, not the agent's `maxTokens`. */
135
136
  maxTokens?: number;
137
+ /** Absent is the side task's own default, 0.3, whatever the agent's temperature is. */
136
138
  temperature?: number;
139
+ /**
140
+ * A level has the task deliberate. Absent and `"off"` leave it with the no-thinking hints a
141
+ * side task is sent by default; see `SideTaskOptions.reasoningEffort`.
142
+ */
137
143
  reasoningEffort?: string;
138
144
  }
139
145
  /** A tool call bound to a lifecycle event, run by the host rather than the model. */
@@ -178,7 +184,13 @@ export interface SpecBundle {
178
184
  */
179
185
  mcpServers?: SpecServer[];
180
186
  }
181
- /** One side task, resolved: a model, and the endpoint it is reached through. */
187
+ /**
188
+ * One side task, resolved: a model, and the endpoint it is reached through.
189
+ *
190
+ * The three settings are present only where a layer stated them, and nothing is filled in from
191
+ * the agent's own model — `taskCall` turns one into a call, and what is absent there is left to
192
+ * the side task's default.
193
+ */
182
194
  export interface ResolvedTask {
183
195
  model: string;
184
196
  /** This task's endpoint, which is the agent's unless a layer named another. */
package/llms.txt CHANGED
@@ -26,6 +26,7 @@ The loop above a turn: send, run the tools the model asked for, send again, unti
26
26
  - `ToolCallOutcome` (type) — What one tool call did, in the order the model asked.
27
27
  - `ToolCallRequest` (type) — One call the model made, as `dispatch` and `onToolCall` are handed it.
28
28
  - `ToolCallResult` (type) — One call's whole answer, as `onToolResult` is handed it.
29
+ - `taskCall` — A side task's settings as the call that honours them: its endpoint, its model, and the options `ask`, `askJson`, `summariser` and `preselect` take last.
29
30
 
30
31
  ### calibration
31
32
 
@@ -221,6 +222,13 @@ One turn, given as many attempts as the caller allows.
221
222
  - `RunTurnOptions` (type) — A retry is not the same event as a downgrade, but a watcher wants to be told about both.
222
223
  - `runTurn` — `request` is a callback rather than a body because the body has to be rebuilt from whatever the last attempt latched off: the tools it sends depend on `strictSchemas`, and `relaxTools` has to apply to the schemas that were just sanitised.
223
224
 
225
+ ### runtime
226
+
227
+ - `createRuntime` — A runtime of its own: separate clients, listings, capability latches, hints, event bus and hook settings from the process's and from every other runtime's.
228
+ - `defaultRuntime` — The runtime the top-level functions use when no other is running.
229
+ - `Runtime` (type) — One set of the package's caches, with the functions that use them as methods.
230
+ - `RuntimeOptions` (type) — What a runtime starts out configured with.
231
+
224
232
  ### schema-compat
225
233
 
226
234
  JSON Schema compatibility for llama.cpp-backed servers.
@@ -240,6 +248,7 @@ One-shot calls that support a run without being one: picking tools, naming a ses
240
248
  - `listLines` — A list-shaped reply, one item per line, cleaned of the bullets and quotes models decorate them with.
241
249
  - `parseJson` — Models are asked for JSON and often answer with prose around it, or a fenced block.
242
250
  - `resetHints` — Test seam, alongside `resetClients` and `resetAll`: forget which models refused the hints.
251
+ - `SideTask` (type) — One side task's own settings, as a resolved agent spec carries them under `tasks.<key>`.
243
252
  - `SideTaskInput` (type) — The input a side task applies its instruction to: text, or the content parts a vision model reads.
244
253
  - `SideTaskOptions` (type) — What a side task may be given.
245
254
  - `tryAsk` — A side task is never worth failing the work it supports.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubicecho/agent-core",
3
- "version": "2.21.1",
3
+ "version": "2.23.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",