@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.
- package/README.md +14 -9
- package/dist/agent-loop.d.ts +84 -204
- package/dist/agent-loop.js +416 -643
- package/dist/calibration.d.ts +14 -10
- package/dist/calibration.js +30 -22
- package/dist/capabilities.d.ts +140 -35
- package/dist/capabilities.js +345 -152
- package/dist/client.d.ts +104 -35
- package/dist/client.js +210 -78
- package/dist/compaction.d.ts +103 -60
- package/dist/compaction.js +163 -93
- package/dist/config.d.ts +48 -12
- package/dist/config.js +20 -1
- package/dist/continuation.d.ts +25 -17
- package/dist/continuation.js +70 -47
- package/dist/digest.d.ts +7 -0
- package/dist/digest.js +18 -0
- package/dist/errors.d.ts +24 -15
- package/dist/errors.js +23 -15
- package/dist/events.d.ts +108 -58
- package/dist/events.js +265 -128
- package/dist/guards.d.ts +53 -0
- package/dist/guards.js +55 -0
- package/dist/hook-events.d.ts +38 -0
- package/dist/hook-events.js +36 -0
- package/dist/hooks.d.ts +105 -83
- package/dist/hooks.js +224 -139
- package/dist/index.d.ts +30 -25
- package/dist/index.js +29 -23
- package/dist/ledger.d.ts +62 -30
- package/dist/ledger.js +163 -56
- package/dist/platform.d.ts +13 -0
- package/dist/platform.js +13 -0
- package/dist/preselect.d.ts +222 -0
- package/dist/preselect.js +290 -0
- package/dist/request-body.d.ts +32 -0
- package/dist/request-body.js +47 -0
- package/dist/reset.d.ts +1 -0
- package/dist/reset.js +7 -6
- package/dist/retry.d.ts +40 -143
- package/dist/retry.js +79 -285
- package/dist/run-calls.d.ts +123 -0
- package/dist/run-calls.js +351 -0
- package/dist/run-turn.d.ts +44 -16
- package/dist/run-turn.js +74 -37
- package/dist/runtime.d.ts +23 -17
- package/dist/runtime.js +34 -24
- package/dist/schema-compat.d.ts +51 -11
- package/dist/schema-compat.js +375 -193
- package/dist/scope.d.ts +28 -5
- package/dist/scope.js +37 -10
- package/dist/side-task.d.ts +127 -39
- package/dist/side-task.js +189 -100
- package/dist/snapshot.d.ts +12 -7
- package/dist/snapshot.js +87 -65
- package/dist/spec.d.ts +66 -36
- package/dist/spec.js +514 -265
- package/dist/stream.d.ts +73 -16
- package/dist/stream.js +228 -98
- package/dist/thinking.d.ts +23 -8
- package/dist/thinking.js +96 -46
- package/dist/tokens.d.ts +171 -6
- package/dist/tokens.js +326 -6
- package/dist/tool-calls.d.ts +89 -15
- package/dist/tool-calls.js +205 -85
- package/dist/tool-loading.d.ts +109 -187
- package/dist/tool-loading.js +286 -372
- package/dist/wire.d.ts +81 -0
- package/dist/wire.js +73 -0
- package/llms.txt +87 -43
- package/package.json +4 -4
package/dist/snapshot.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { capabilitiesById, knownCapabilities, modelCapabilitiesFor } from
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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 ===
|
|
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
|
-
|
|
157
|
-
|
|
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
|
|
2
|
-
import
|
|
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
|
|
14
|
-
*
|
|
15
|
-
* it
|
|
16
|
-
* where an agent comes from. It is also
|
|
17
|
-
* and `hooks.ts` are not: a host validating
|
|
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 [
|
|
32
|
+
export declare const AGENT_TASKS: readonly ['compaction', 'toolSelect', 'title', 'followups'];
|
|
31
33
|
/**
|
|
32
|
-
* Every event a hook may be bound to
|
|
34
|
+
* Every event a hook may be bound to: `HOOK_EVENTS`, under the name a document's reader knows.
|
|
33
35
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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:
|
|
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?:
|
|
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
|
-
/**
|
|
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.
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
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
|
-
/**
|
|
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.
|
|
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
|
|
329
|
-
* nothing outside `bundle` is touched: the format has no
|
|
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;
|