@cubicecho/agent-core 2.23.0 → 2.25.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.
Files changed (71) hide show
  1. package/README.md +14 -9
  2. package/dist/agent-loop.d.ts +84 -204
  3. package/dist/agent-loop.js +416 -643
  4. package/dist/calibration.d.ts +14 -10
  5. package/dist/calibration.js +30 -22
  6. package/dist/capabilities.d.ts +140 -35
  7. package/dist/capabilities.js +345 -152
  8. package/dist/client.d.ts +104 -35
  9. package/dist/client.js +210 -78
  10. package/dist/compaction.d.ts +103 -60
  11. package/dist/compaction.js +163 -93
  12. package/dist/config.d.ts +48 -12
  13. package/dist/config.js +20 -1
  14. package/dist/continuation.d.ts +25 -17
  15. package/dist/continuation.js +70 -47
  16. package/dist/digest.d.ts +7 -0
  17. package/dist/digest.js +18 -0
  18. package/dist/errors.d.ts +24 -15
  19. package/dist/errors.js +23 -15
  20. package/dist/events.d.ts +108 -58
  21. package/dist/events.js +265 -128
  22. package/dist/guards.d.ts +53 -0
  23. package/dist/guards.js +55 -0
  24. package/dist/hook-events.d.ts +38 -0
  25. package/dist/hook-events.js +36 -0
  26. package/dist/hooks.d.ts +105 -83
  27. package/dist/hooks.js +224 -139
  28. package/dist/index.d.ts +30 -25
  29. package/dist/index.js +29 -23
  30. package/dist/ledger.d.ts +62 -30
  31. package/dist/ledger.js +163 -56
  32. package/dist/platform.d.ts +13 -0
  33. package/dist/platform.js +13 -0
  34. package/dist/preselect.d.ts +222 -0
  35. package/dist/preselect.js +290 -0
  36. package/dist/request-body.d.ts +32 -0
  37. package/dist/request-body.js +47 -0
  38. package/dist/reset.d.ts +1 -0
  39. package/dist/reset.js +7 -6
  40. package/dist/retry.d.ts +40 -143
  41. package/dist/retry.js +79 -285
  42. package/dist/run-calls.d.ts +123 -0
  43. package/dist/run-calls.js +351 -0
  44. package/dist/run-turn.d.ts +44 -16
  45. package/dist/run-turn.js +74 -37
  46. package/dist/runtime.d.ts +23 -17
  47. package/dist/runtime.js +34 -24
  48. package/dist/schema-compat.d.ts +51 -11
  49. package/dist/schema-compat.js +375 -193
  50. package/dist/scope.d.ts +28 -5
  51. package/dist/scope.js +37 -10
  52. package/dist/side-task.d.ts +127 -39
  53. package/dist/side-task.js +189 -100
  54. package/dist/snapshot.d.ts +12 -7
  55. package/dist/snapshot.js +87 -65
  56. package/dist/spec.d.ts +66 -36
  57. package/dist/spec.js +514 -265
  58. package/dist/stream.d.ts +73 -16
  59. package/dist/stream.js +228 -98
  60. package/dist/thinking.d.ts +23 -8
  61. package/dist/thinking.js +96 -46
  62. package/dist/tokens.d.ts +171 -6
  63. package/dist/tokens.js +326 -6
  64. package/dist/tool-calls.d.ts +89 -15
  65. package/dist/tool-calls.js +205 -85
  66. package/dist/tool-loading.d.ts +109 -187
  67. package/dist/tool-loading.js +286 -372
  68. package/dist/wire.d.ts +81 -0
  69. package/dist/wire.js +73 -0
  70. package/llms.txt +87 -43
  71. package/package.json +4 -4
package/dist/snapshot.js CHANGED
@@ -1,5 +1,7 @@
1
- import { capabilitiesById, knownCapabilities, modelCapabilitiesFor } from "./capabilities.js";
2
- import { hintKey, refusedHints } from "./side-task.js";
1
+ import { capabilitiesById, knownCapabilities, MODEL_FLAGS, modelCapabilitiesFor, OPTIMISTIC_MODEL, } from './capabilities.js';
2
+ import { modelKey } from './client.js';
3
+ import { isRecord } from './guards.js';
4
+ import { refusedHints } from './side-task.js';
3
5
  /**
4
6
  * What endpoints and models refused, carried across a restart.
5
7
  *
@@ -10,27 +12,52 @@ import { hintKey, refusedHints } from "./side-task.js";
10
12
  */
11
13
  /** The version `importCapabilities` accepts. Raised when what is latched changes shape. */
12
14
  export const CAPABILITY_SNAPSHOT_VERSION = 1;
15
+ /**
16
+ * A model's snapshot as it starts out: every flag on, and both refused lists empty.
17
+ *
18
+ * @returns A fresh object each time, since `exportCapabilities` writes onto what it is given.
19
+ */
13
20
  const optimisticModel = () => ({
14
- reasoningEffort: true,
15
- legacyTokenLimit: true,
16
- chosenTemperature: true,
17
- refusedFields: [],
18
- structuredOutput: true,
19
- assistantPrefill: true,
21
+ ...OPTIMISTIC_MODEL,
20
22
  thinkingHints: true,
23
+ refusedFields: [],
21
24
  refusedEfforts: [],
22
25
  });
23
- const refusedAnything = (model) => !model.reasoningEffort ||
24
- !model.legacyTokenLimit ||
25
- !model.chosenTemperature ||
26
+ /**
27
+ * Whether a model's snapshot holds a refusal, which is what earns it a place in the blob.
28
+ *
29
+ * @param model - The snapshot. Its `supportedEfforts` is not read: a published list latches
30
+ * nothing.
31
+ * @returns `true` where a flag is off, the hints were refused, or either refused list has an entry.
32
+ */
33
+ const refusedAnything = (model) => MODEL_FLAGS.some((flag) => !model[flag]) ||
26
34
  !model.thinkingHints ||
27
- !model.structuredOutput ||
28
- !model.assistantPrefill ||
29
35
  model.refusedFields.length > 0 ||
30
36
  (model.refusedEfforts?.length ?? 0) > 0;
37
+ /**
38
+ * Latches every string in a stored list into a live set, skipping what is not a list of them.
39
+ *
40
+ * @param held - The live set. Written to, and only ever added to.
41
+ * @param stored - The list as the snapshot holds it. Anything but an array is skipped whole, and
42
+ * an entry that is not a string is skipped on its own.
43
+ */
44
+ const latchInto = (held, stored) => {
45
+ if (!Array.isArray(stored)) {
46
+ return;
47
+ }
48
+ for (const value of stored) {
49
+ if (typeof value === 'string') {
50
+ held.add(value);
51
+ }
52
+ }
53
+ };
31
54
  /**
32
55
  * Every refusal this process has latched, as a JSON-safe blob to store and hand back on boot.
33
56
  *
57
+ * @returns A fresh object that shares nothing with the live latches, its lists copied and the two
58
+ * refused ones sorted.
59
+ *
60
+ * @remarks
34
61
  * Covers what `negotiate` latches on endpoints and models and the models `ask` found refusing the
35
62
  * no-thinking hints. Only what was actually refused is in it, so a snapshot of a process that met
36
63
  * no refusals has no endpoints. Endpoints are named by digest rather than URL and key, since the
@@ -41,33 +68,34 @@ export function exportCapabilities() {
41
68
  const endpoints = {};
42
69
  const entry = (id) => {
43
70
  const held = endpoints[id];
44
- if (held)
71
+ if (held) {
45
72
  return held;
73
+ }
46
74
  const fresh = { strictSchemas: true, usageInStream: true, models: {} };
47
75
  const since = knownCapabilities().get(id)?.since;
48
- if (since !== undefined)
76
+ if (since !== undefined) {
49
77
  fresh.since = since;
78
+ }
50
79
  endpoints[id] = fresh;
51
80
  return fresh;
52
81
  };
53
82
  for (const [id, supports] of knownCapabilities()) {
54
83
  const models = {};
55
84
  for (const [name, refused] of supports.models) {
56
- const model = {
57
- ...optimisticModel(),
58
- reasoningEffort: refused.reasoningEffort,
59
- legacyTokenLimit: refused.legacyTokenLimit,
60
- chosenTemperature: refused.chosenTemperature,
61
- refusedFields: [...refused.refusedFields].sort(),
62
- structuredOutput: refused.structuredOutput,
63
- assistantPrefill: refused.assistantPrefill,
64
- refusedEfforts: [...refused.refusedEfforts].sort(),
65
- // Only alongside a refusal, since on its own a published list latches nothing: the model
66
- // named it while refusing a rung, and that rung is in `refusedEfforts`.
67
- ...(refused.supportedEfforts ? { supportedEfforts: [...refused.supportedEfforts] } : {}),
68
- };
69
- if (refusedAnything(model))
85
+ const model = optimisticModel();
86
+ for (const flag of MODEL_FLAGS) {
87
+ model[flag] = refused[flag];
88
+ }
89
+ model.refusedFields = [...refused.refusedFields].sort();
90
+ model.refusedEfforts = [...refused.refusedEfforts].sort();
91
+ // Only alongside a refusal, since on its own a published list latches nothing: the model
92
+ // named it while refusing a rung, and that rung is in `refusedEfforts`.
93
+ if (refused.supportedEfforts) {
94
+ model.supportedEfforts = [...refused.supportedEfforts];
95
+ }
96
+ if (refusedAnything(model)) {
70
97
  models[name] = model;
98
+ }
71
99
  }
72
100
  if (!supports.strictSchemas || !supports.usageInStream || Object.keys(models).length) {
73
101
  Object.assign(entry(id), {
@@ -85,76 +113,70 @@ export function exportCapabilities() {
85
113
  }
86
114
  return { version: CAPABILITY_SNAPSHOT_VERSION, savedAt: new Date().toISOString(), endpoints };
87
115
  }
88
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
89
116
  /**
90
117
  * Latches what a stored snapshot says was refused, on top of whatever this process has learned.
91
118
  *
119
+ * @param snapshot - What `exportCapabilities` returned, as stored. Read defensively: a field of the
120
+ * wrong type is skipped rather than trusted.
121
+ * @returns Whether the snapshot was of this version and applied.
122
+ *
123
+ * @remarks
92
124
  * Refusals only ever latch off, so importing merges rather than replaces: a flag already off stays
93
125
  * off whatever the snapshot says, and one the snapshot has off is turned off. A snapshot of another
94
126
  * version, or anything that is not one, is ignored — a stale shape costs the refused requests it
95
127
  * would have saved, which is what a restart cost before. How old is too old is the consumer's call,
96
128
  * made on `savedAt` before importing, or afterwards per endpoint with `expireCapabilities`, since a
97
129
  * server behind a URL can be upgraded between boots.
98
- *
99
- * @param snapshot What `exportCapabilities` returned, as stored. Read defensively: a field of the
100
- * wrong type is skipped rather than trusted.
101
- * @returns Whether the snapshot was of this version and applied.
102
130
  */
103
131
  export function importCapabilities(snapshot) {
104
- if (!isRecord(snapshot) || snapshot.version !== CAPABILITY_SNAPSHOT_VERSION)
132
+ if (!isRecord(snapshot) || snapshot.version !== CAPABILITY_SNAPSHOT_VERSION) {
105
133
  return false;
106
- if (!isRecord(snapshot.endpoints))
134
+ }
135
+ if (!isRecord(snapshot.endpoints)) {
107
136
  return false;
137
+ }
108
138
  for (const [id, endpoint] of Object.entries(snapshot.endpoints)) {
109
- if (!isRecord(endpoint))
139
+ if (!isRecord(endpoint)) {
110
140
  continue;
141
+ }
111
142
  const supports = capabilitiesById(id);
112
- if (endpoint.strictSchemas === false)
143
+ if (endpoint.strictSchemas === false) {
113
144
  supports.strictSchemas = false;
114
- if (endpoint.usageInStream === false)
145
+ }
146
+ if (endpoint.usageInStream === false) {
115
147
  supports.usageInStream = false;
148
+ }
116
149
  // Older of the two, so a snapshot ages an entry and never rejuvenates one: importing must not
117
150
  // be a way to keep a latch from ever reaching `expireCapabilities`.
118
- if (typeof endpoint.since === "number" && endpoint.since < supports.since) {
151
+ if (typeof endpoint.since === 'number' && endpoint.since < supports.since) {
119
152
  supports.since = endpoint.since;
120
153
  }
121
- if (!isRecord(endpoint.models))
154
+ if (!isRecord(endpoint.models)) {
122
155
  continue;
156
+ }
123
157
  for (const [name, model] of Object.entries(endpoint.models)) {
124
- if (!isRecord(model))
158
+ if (!isRecord(model)) {
125
159
  continue;
160
+ }
126
161
  const refused = modelCapabilitiesFor(supports, name);
127
- if (model.reasoningEffort === false)
128
- refused.reasoningEffort = false;
129
- if (model.legacyTokenLimit === false)
130
- refused.legacyTokenLimit = false;
131
- if (model.chosenTemperature === false)
132
- refused.chosenTemperature = false;
133
- if (model.structuredOutput === false)
134
- refused.structuredOutput = false;
135
- if (model.assistantPrefill === false)
136
- refused.assistantPrefill = false;
137
- if (Array.isArray(model.refusedEfforts)) {
138
- for (const effort of model.refusedEfforts) {
139
- if (typeof effort === "string")
140
- refused.refusedEfforts.add(effort);
162
+ for (const flag of MODEL_FLAGS) {
163
+ if (model[flag] === false) {
164
+ refused[flag] = false;
141
165
  }
142
166
  }
167
+ latchInto(refused.refusedEfforts, model.refusedEfforts);
143
168
  // Replaced rather than merged: two lists of what one model takes are two readings of the
144
169
  // same fact, and the stored one is at least as recent as an empty absent.
145
170
  if (Array.isArray(model.supportedEfforts)) {
146
- const listed = model.supportedEfforts.filter((value) => typeof value === "string");
147
- if (listed.length)
171
+ const listed = model.supportedEfforts.filter((value) => typeof value === 'string');
172
+ if (listed.length) {
148
173
  refused.supportedEfforts = listed;
149
- }
150
- if (Array.isArray(model.refusedFields)) {
151
- for (const field of model.refusedFields) {
152
- if (typeof field === "string")
153
- refused.refusedFields.add(field);
154
174
  }
155
175
  }
156
- if (model.thinkingHints === false)
157
- refusedHints().add(hintKey(id, name));
176
+ latchInto(refused.refusedFields, model.refusedFields);
177
+ if (model.thinkingHints === false) {
178
+ refusedHints().add(modelKey(id, name));
179
+ }
158
180
  }
159
181
  }
160
182
  return true;
package/dist/spec.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AgentConfig, Endpoint } from "./config.ts";
2
- import type { HookEvent } from "./hooks.ts";
1
+ import { type AgentConfig, type Endpoint } from './config.ts';
2
+ import { HookEvent } from './hook-events.ts';
3
3
  /**
4
4
  * A JSON document that defines an agent, and the rules for reading one.
5
5
  *
@@ -10,11 +10,12 @@ import type { HookEvent } from "./hooks.ts";
10
10
  * real ceiling of zero* in the third. This is the shape they can all write and all read, argued
11
11
  * out in `docs/agent-spec.md` before any of it was code.
12
12
  *
13
- * Nothing else in `src/` imports this module, and this module imports nothing but types. That is
14
- * the same seam `config.ts` draws — the loop takes the parts it reads, not a config object — and
15
- * it is what lets the document travel between hosts without the loop growing an opinion about
16
- * where an agent comes from. It is also why this is importable in a browser, where `client.ts`
17
- * and `hooks.ts` are not: a host validating a pasted document in a form needs no `node:crypto`.
13
+ * Nothing else in `src/` imports this module, and this module imports nothing that needs Node:
14
+ * types, and leaves with no imports of their own. That is the same seam `config.ts` draws — the
15
+ * loop takes the parts it reads, not a config object — and it is what lets the document travel
16
+ * between hosts without the loop growing an opinion about where an agent comes from. It is also
17
+ * why this is importable in a browser, where `client.ts` and `hooks.ts` are not: a host validating
18
+ * a pasted document in a form needs no `node:crypto`.
18
19
  */
19
20
  /** The token a document declares itself with. A loader refuses anything else. */
20
21
  export declare const AGENT_SPEC = "cubicecho.agent/1";
@@ -24,17 +25,18 @@ export declare const AGENT_SPEC_VERSION = 1;
24
25
  * The side tasks this package has code for: documentation and a sensible UI order, not a
25
26
  * validator.
26
27
  *
28
+ * @remarks
27
29
  * `tasks` is an open record on purpose — a host may run tasks this package has never heard of,
28
30
  * and an unknown key is carried through untouched rather than warned about.
29
31
  */
30
- export declare const AGENT_TASKS: readonly ["compaction", "toolSelect", "title", "followups"];
32
+ export declare const AGENT_TASKS: readonly ['compaction', 'toolSelect', 'title', 'followups'];
31
33
  /**
32
- * Every event a hook may be bound to, restated so this module stays free of `node:crypto`.
34
+ * Every event a hook may be bound to: `HOOK_EVENTS`, under the name a document's reader knows.
33
35
  *
34
- * `hooks.ts` owns the list; importing its `HOOK_EVENTS` would pull the whole module, and its
35
- * `createHash` with it, into a file whose whole point is that a browser can load it. The `HookEvent`
36
- * type is imported and erased, so a removed event fails to compile here, and a test asserts this
37
- * list and `HOOK_EVENTS` are the same list so an added one cannot drift quietly.
36
+ * @remarks
37
+ * The same array, not a copy. It comes from `hook-events.ts` rather than `hooks.ts` because that
38
+ * module's `createHash` would otherwise come with it, into a file whose whole point is that a
39
+ * browser can load it.
38
40
  */
39
41
  export declare const SPEC_EVENTS: readonly HookEvent[];
40
42
  /** One agent, as a document. Every field but `spec` is optional, because a layer says only what it changes. */
@@ -91,6 +93,10 @@ export interface ModelSpec {
91
93
  /** Request fields the interface cannot spell. `model`, `messages`, `stream`, `tools` are dropped. */
92
94
  extraBody?: Record<string, unknown>;
93
95
  }
96
+ /** The `ref.type` of a prompt part whose text is in a file. */
97
+ export declare const REF_FILE: 'file';
98
+ /** The `ref.type` of a prompt part whose text is behind a URL. */
99
+ export declare const REF_URL: 'url';
94
100
  /** One layer of system prompt, named so a layer above can stack beside it or replace it. */
95
101
  export interface PromptPart {
96
102
  /** Names this layer: `project`, `identity`, `role`, `lane`. Merging happens on it. */
@@ -99,7 +105,7 @@ export interface PromptPart {
99
105
  text?: string;
100
106
  /** Where the text lives instead. The shape is validated; the reference is never resolved. */
101
107
  ref?: {
102
- type: "file" | "url";
108
+ type: typeof REF_FILE | typeof REF_URL;
103
109
  value: string;
104
110
  };
105
111
  }
@@ -109,7 +115,7 @@ export interface ToolsSpec {
109
115
  * How tool definitions reach the model: all of them on every request, a name-only catalogue
110
116
  * the model loads from, or that catalogue behind a fixed `load_tools` and `call_tool`.
111
117
  */
112
- discovery?: "eager" | "ondemand" | "proxy";
118
+ discovery?: 'eager' | 'ondemand' | 'proxy';
113
119
  /** The hard stop on a tool loop. At least one. */
114
120
  maxIterations?: number;
115
121
  /**
@@ -134,7 +140,11 @@ export interface TaskSpec {
134
140
  endpoint?: EndpointSpec;
135
141
  /** The reply's ceiling. Absent is the side task's own default, not the agent's `maxTokens`. */
136
142
  maxTokens?: number;
137
- /** Absent is the side task's own default, 0.3, whatever the agent's temperature is. */
143
+ /**
144
+ * Sampling temperature, whatever the agent's own is.
145
+ *
146
+ * @defaultValue `0.3`
147
+ */
138
148
  temperature?: number;
139
149
  /**
140
150
  * A level has the task deliberate. Absent and `"off"` leave it with the no-thinking hints a
@@ -163,7 +173,6 @@ export interface AgentHook {
163
173
  /** `false` keeps the hook in the document without running it. */
164
174
  enabled?: boolean;
165
175
  }
166
- /** One bundled MCP server, as far as this package reads it. */
167
176
  /** A bundled MCP server, validated only as far as a round trip needs and otherwise passed through. */
168
177
  export interface SpecServer {
169
178
  id?: string;
@@ -176,6 +185,7 @@ export interface SpecBundle {
176
185
  * Full MCP server definitions. Validated only as far as a round trip needs — an object, a
177
186
  * non-empty `id` or `slug`, no duplicate slugs — with every other key passed through untouched.
178
187
  *
188
+ * @remarks
179
189
  * This package deliberately does not restate the pool's `McpServerConfig`. Restating a union it
180
190
  * never reads is a version-skew generator: the pool carries `coerceArguments`, `maxResultChars`
181
191
  * and three separate timeouts, and a strict parse here would silently delete a server's
@@ -187,6 +197,7 @@ export interface SpecBundle {
187
197
  /**
188
198
  * One side task, resolved: a model, and the endpoint it is reached through.
189
199
  *
200
+ * @remarks
190
201
  * The three settings are present only where a layer stated them, and nothing is filled in from
191
202
  * the agent's own model — `taskCall` turns one into a call, and what is absent there is left to
192
203
  * the side task's default.
@@ -203,6 +214,7 @@ export interface ResolvedTask {
203
214
  * What layered documents resolve to: the flat shape the loop already takes, plus the parts of an
204
215
  * agent that are the host's to act on.
205
216
  *
217
+ * @remarks
206
218
  * `extends AgentConfig` is the whole point — `getClient(resolved)` and
207
219
  * `runAgentLoop({ config: resolved })` take this unchanged, and no adapter is needed.
208
220
  */
@@ -230,6 +242,7 @@ export interface ResolvedAgent extends AgentConfig {
230
242
  /**
231
243
  * What a field falls back to when no layer said anything at all.
232
244
  *
245
+ * @remarks
233
246
  * Not sentinels, and not invented: `toolDiscovery`, `maxRetries` and `contextLength` are the
234
247
  * "absent everywhere" column of the document's own table, and `temperature` and `maxToolIterations`
235
248
  * are what all three consumers store as their defaults today. A field left out here is left out of
@@ -255,13 +268,19 @@ export interface ParsedSpec {
255
268
  /** What `parseSpec` takes besides the document. */
256
269
  export interface ParseSpecOptions {
257
270
  /**
258
- * Parse `bundle` rather than dropping it. Off by default, because a bundled server carries a
259
- * command line and an environment map: loading one is equivalent to running a program, exactly
260
- * as dangerous as pasting somebody's `.mcp.json`, and a host has to make that decision rather
261
- * than inherit it from a default.
271
+ * Parse `bundle` rather than dropping it. Opt-in, because a bundled server carries a command
272
+ * line and an environment map: loading one is equivalent to running a program, exactly as
273
+ * dangerous as pasting somebody's `.mcp.json`, and a host has to make that decision rather than
274
+ * inherit it from a default.
275
+ *
276
+ * @defaultValue `false`
262
277
  */
263
278
  bundle?: boolean;
264
- /** The events this host actually fires, defaulting to all of them. A hook bound elsewhere warns. */
279
+ /**
280
+ * The events this host actually fires. A hook bound elsewhere warns.
281
+ *
282
+ * @defaultValue `SPEC_EVENTS`, which is all of them
283
+ */
265
284
  events?: readonly string[];
266
285
  /** The `extensions` keys this host understands. A `requires` entry outside it refuses the load. */
267
286
  understands?: readonly string[];
@@ -269,6 +288,14 @@ export interface ParseSpecOptions {
269
288
  /**
270
289
  * Reads a document, dropping what it cannot use and refusing only what it cannot identify.
271
290
  *
291
+ * @param document - Anything at all — this is the front door, and it is given parsed JSON from a
292
+ * form, a file or another host.
293
+ * @param [options] - Whether to parse a bundle, which events this host fires, and which extension
294
+ * keys it understands.
295
+ * @returns The document that survived — a new object, never the one handed in — or `spec: null`
296
+ * where `errors` holds anything. `warnings` is filled either way.
297
+ *
298
+ * @remarks
272
299
  * Every problem is reported at once, in the `path: what is wrong` shape `validateHooks` already
273
300
  * uses, because a host showing an operator one error at a time makes them fix a document one
274
301
  * round trip at a time.
@@ -284,16 +311,16 @@ export interface ParseSpecOptions {
284
311
  * version and returns false: degrade where the loss is performance, refuse where the loss is
285
312
  * identity. A snapshot of the wrong version costs a few refused requests; an agent of the wrong
286
313
  * version is not the agent its author described.
287
- *
288
- * @param document Anything at all — this is the front door, and it is given parsed JSON from a
289
- * form, a file or another host.
290
- * @param options Whether to parse a bundle, which events this host fires, and which extension
291
- * keys it understands.
292
314
  */
293
315
  export declare function parseSpec(document: unknown, options?: ParseSpecOptions): ParsedSpec;
294
316
  /**
295
317
  * Layers documents into the flat object the loop takes, weakest first.
296
318
  *
319
+ * @param layers - The documents, weakest first: settings, then the agent, then a task, then a step.
320
+ * @returns The resolved agent, built new. Always whole: an empty list gives empty strings and
321
+ * `RESOLVED_DEFAULTS` rather than an error, and `apiKey` is `""` whatever the layers said.
322
+ *
323
+ * @remarks
297
324
  * Absent inherits, and nothing else does: `0`, `-1`, `""` and `[]` are values that mean what they
298
325
  * say, which is the whole reason the format exists — `maxTokens: 0` means three different things
299
326
  * across the three applications this was extracted from, and `temperature: 0` is unsayable in one
@@ -308,28 +335,31 @@ export declare function parseSpec(document: unknown, options?: ParseSpecOptions)
308
335
  * empty for the same reason: a document naming somebody else's endpoint must not be able to make
309
336
  * this host send its own provider key there. A host that wants inheritance applies `resolveApiKey`
310
337
  * afterwards.
311
- *
312
- * @param layers The documents, weakest first: settings, then the agent, then a task, then a step.
313
338
  */
314
339
  export declare function resolveAgentSpec(layers: readonly AgentSpec[]): ResolvedAgent;
315
340
  /** What `exportSpec` takes besides the document. */
316
341
  export interface ExportSpecOptions {
317
342
  /**
318
- * Keep the environment and headers of bundled servers. Off by default, mirroring the pool's
343
+ * Keep the environment and headers of bundled servers. Opt-in, mirroring the pool's
319
344
  * `state({ secrets })`, because those are where a bundled server's credentials live.
345
+ *
346
+ * @defaultValue `false`
320
347
  */
321
348
  secrets?: boolean;
322
349
  }
323
350
  /**
324
351
  * A document fit to leave the host: the same agent, with the secrets of its bundled servers gone.
325
352
  *
353
+ * @param spec - A document, ordinarily one `parseSpec` returned.
354
+ * @param [options] - Whether to keep the secrets.
355
+ * @returns A copy one level deep, never `spec` itself: what is not redacted is shared with the
356
+ * original.
357
+ *
358
+ * @remarks
326
359
  * The property worth keeping is that **a redacted spec is still a valid spec** — it parses, it
327
360
  * resolves, and what comes back is the same agent pointed at the same servers, needing only the
328
- * credentials the importing host supplies itself. So this removes values rather than keys, and
329
- * nothing outside `bundle` is touched: the format has no credential field at any depth, which is
330
- * the reason there is nothing else to strip.
331
- *
332
- * @param spec A document, ordinarily one `parseSpec` returned.
333
- * @param options Whether to keep the secrets. The default is not to.
361
+ * credentials the importing host supplies itself. So a bundled server loses its `env` and its
362
+ * `headers` and nothing else, and nothing outside `bundle` is touched: the format has no
363
+ * credential field at any depth, which is the reason there is nothing else to strip.
334
364
  */
335
365
  export declare function exportSpec(spec: AgentSpec, { secrets }?: ExportSpecOptions): AgentSpec;