@animalabs/agent-framework 0.22.3 → 0.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.
- package/README.md +105 -3
- package/dist/src/agent.d.ts +36 -10
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +76 -32
- package/dist/src/agent.js.map +1 -1
- package/dist/src/code-execution/index.d.ts +1 -1
- package/dist/src/code-execution/index.d.ts.map +1 -1
- package/dist/src/code-execution/index.js.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts +34 -2
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js +72 -16
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/code-execution/runtime-py.d.ts +3 -2
- package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
- package/dist/src/code-execution/runtime-py.js +47 -7
- package/dist/src/code-execution/runtime-py.js.map +1 -1
- package/dist/src/code-execution/tool-definition.d.ts +12 -7
- package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
- package/dist/src/code-execution/tool-definition.js +59 -11
- package/dist/src/code-execution/tool-definition.js.map +1 -1
- package/dist/src/config-checks.d.ts +2 -2
- package/dist/src/config-checks.d.ts.map +1 -1
- package/dist/src/config-checks.js +6 -2
- package/dist/src/config-checks.js.map +1 -1
- package/dist/src/framework.d.ts +744 -120
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3546 -906
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +13 -6
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/held-writes.d.ts +16 -0
- package/dist/src/held-writes.d.ts.map +1 -0
- package/dist/src/held-writes.js +56 -0
- package/dist/src/held-writes.js.map +1 -0
- package/dist/src/index.d.ts +7 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-grant.d.ts +4 -0
- package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
- package/dist/src/mcpl/capability-grant.js +1 -1
- package/dist/src/mcpl/capability-grant.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +95 -18
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +578 -150
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +2 -2
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +6 -7
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +5 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +3 -1
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +4 -2
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +6 -7
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/log-value.d.ts +7 -0
- package/dist/src/mcpl/log-value.d.ts.map +1 -1
- package/dist/src/mcpl/log-value.js +9 -0
- package/dist/src/mcpl/log-value.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +227 -0
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -0
- package/dist/src/mcpl/modern-connection.js +1088 -0
- package/dist/src/mcpl/modern-connection.js.map +1 -0
- package/dist/src/mcpl/prefix-rule.d.ts +18 -0
- package/dist/src/mcpl/prefix-rule.d.ts.map +1 -0
- package/dist/src/mcpl/prefix-rule.js +25 -0
- package/dist/src/mcpl/prefix-rule.js.map +1 -0
- package/dist/src/mcpl/protocol-family.d.ts +179 -0
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -0
- package/dist/src/mcpl/protocol-family.js +697 -0
- package/dist/src/mcpl/protocol-family.js.map +1 -0
- package/dist/src/mcpl/push-coalescer.d.ts +38 -19
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +52 -23
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +66 -15
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +243 -76
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +63 -18
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +117 -32
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +326 -30
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +995 -212
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts +25 -5
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/server-registry.js +51 -12
- package/dist/src/mcpl/server-registry.js.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.d.ts +54 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.js +232 -0
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +56 -13
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +206 -70
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +66 -20
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +2 -1
- package/dist/src/mcpl/visible-content.d.ts.map +1 -1
- package/dist/src/mcpl/visible-content.js +2 -1
- package/dist/src/mcpl/visible-content.js.map +1 -1
- package/dist/src/module-registry.d.ts +16 -6
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +65 -6
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +3 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts +42 -2
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +268 -65
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +27 -5
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +366 -76
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/discord/refusal.d.ts +14 -0
- package/dist/src/modules/discord/refusal.d.ts.map +1 -0
- package/dist/src/modules/discord/refusal.js +14 -0
- package/dist/src/modules/discord/refusal.js.map +1 -0
- package/dist/src/modules/discord/types.d.ts +17 -7
- package/dist/src/modules/discord/types.d.ts.map +1 -1
- package/dist/src/modules/health/index.d.ts.map +1 -1
- package/dist/src/modules/health/index.js +6 -2
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/index.d.ts +1 -1
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js +1 -1
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +25 -2
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +45 -5
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +2 -1
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +19 -9
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +214 -42
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +667 -113
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +240 -8
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +554 -24
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +10 -1
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +42 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +130 -25
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +112 -39
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +100 -29
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/tool-args.d.ts +40 -0
- package/dist/src/tool-args.d.ts.map +1 -0
- package/dist/src/tool-args.js +207 -0
- package/dist/src/tool-args.js.map +1 -0
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +9 -2
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/types/agent.d.ts +27 -17
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +8 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +17 -2
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +54 -10
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +2 -2
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -1
- package/src/agent.ts +99 -36
- package/src/code-execution/index.ts +1 -0
- package/src/code-execution/py-runner.ts +104 -17
- package/src/code-execution/runtime-py.ts +47 -7
- package/src/code-execution/tool-definition.ts +65 -11
- package/src/config-checks.ts +4 -2
- package/src/framework.ts +3633 -956
- package/src/gate/event-gate.ts +14 -6
- package/src/held-writes.ts +59 -0
- package/src/index.ts +19 -4
- package/src/mcpl/capability-grant.ts +1 -1
- package/src/mcpl/channel-registry.ts +613 -160
- package/src/mcpl/convert-block.ts +15 -7
- package/src/mcpl/index.ts +14 -1
- package/src/mcpl/inference-router.ts +8 -7
- package/src/mcpl/log-value.ts +10 -0
- package/src/mcpl/modern-connection.ts +1182 -0
- package/src/mcpl/prefix-rule.ts +32 -0
- package/src/mcpl/protocol-family.ts +691 -0
- package/src/mcpl/push-coalescer.ts +67 -29
- package/src/mcpl/reference-fetcher.ts +288 -82
- package/src/mcpl/references.ts +123 -33
- package/src/mcpl/server-connection.ts +1050 -221
- package/src/mcpl/server-registry.ts +50 -12
- package/src/mcpl/tool-result-normalize.ts +294 -0
- package/src/mcpl/transport.ts +204 -62
- package/src/mcpl/types.ts +67 -20
- package/src/mcpl/visible-content.ts +2 -1
- package/src/module-registry.ts +68 -13
- package/src/modules/api/index.ts +3 -2
- package/src/modules/discord/discord-js-client.ts +288 -72
- package/src/modules/discord/index.ts +380 -77
- package/src/modules/discord/refusal.ts +13 -0
- package/src/modules/discord/types.ts +17 -9
- package/src/modules/health/index.ts +6 -2
- package/src/modules/index.ts +1 -1
- package/src/modules/workspace/disk-agreement.ts +46 -5
- package/src/modules/workspace/effects.ts +18 -7
- package/src/modules/workspace/index.ts +722 -124
- package/src/modules/workspace/observe.ts +591 -22
- package/src/modules/workspace/types.ts +10 -1
- package/src/modules/workspace/watcher.ts +126 -25
- package/src/silent-heartbeat.ts +184 -59
- package/src/tool-args.ts +213 -0
- package/src/tool-presentation.ts +9 -2
- package/src/types/agent.ts +27 -17
- package/src/types/events.ts +8 -0
- package/src/types/framework.ts +17 -2
- package/src/types/module.ts +55 -10
- package/src/types/trace.ts +2 -2
package/src/tool-args.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { ToolDefinition } from './types/index.js';
|
|
2
|
+
|
|
3
|
+
/** A run of 16 or more digits, which membrane's XML parser passes as text for
|
|
4
|
+
* a number or integer parameter: some such runs would lose precision as a
|
|
5
|
+
* JavaScript number, so membrane keeps every one as text, and so does this
|
|
6
|
+
* check. */
|
|
7
|
+
const LARGE_INTEGER_TEXT = /^\d{16,}$/;
|
|
8
|
+
|
|
9
|
+
/** The JSON kind of a value, as a schema's `type` names it. */
|
|
10
|
+
function kindOf(value: unknown): string {
|
|
11
|
+
if (Array.isArray(value)) return 'array';
|
|
12
|
+
if (value === null) return 'null';
|
|
13
|
+
return typeof value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A kind with its article, for a sentence. */
|
|
17
|
+
function withArticle(kind: string): string {
|
|
18
|
+
if (kind === 'null') return 'null';
|
|
19
|
+
return /^[aeiou]/.test(kind) ? `an ${kind}` : `a ${kind}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The types JSON Schema names. A `type` naming anything else, such as
|
|
23
|
+
* `any`, is one the check can't read. */
|
|
24
|
+
const JSON_TYPES = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']);
|
|
25
|
+
|
|
26
|
+
/** The kinds a declaration's type admits, each once, or none when it declares
|
|
27
|
+
* none it can read. A type list naming anything JSON Schema doesn't have is
|
|
28
|
+
* one it can't read as a whole, as membrane reads it, rather than the names
|
|
29
|
+
* in it that it knows. OpenAPI's `nullable: true`, the form Gemini's dialect
|
|
30
|
+
* uses, admits `null` beside its type, as a type list naming `null` does. */
|
|
31
|
+
function admittedKinds(param: { type?: unknown; nullable?: unknown } | undefined): string[] {
|
|
32
|
+
const type = param?.type;
|
|
33
|
+
const types = Array.isArray(type) ? type : type === undefined ? [] : [type];
|
|
34
|
+
if (!types.every((t) => typeof t === 'string' && JSON_TYPES.has(t))) return [];
|
|
35
|
+
const kinds = new Set((types as string[]).map((t) => (t === 'integer' ? 'number' : t)));
|
|
36
|
+
if (kinds.size > 0 && param?.nullable === true) kinds.add('null');
|
|
37
|
+
return [...kinds];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Whether `null` is a value for a key so declared: where the declaration
|
|
41
|
+
* admits it (a type list naming it, or `nullable: true`), or declares no
|
|
42
|
+
* type at all. A type the check can't read says nothing about `null`, so
|
|
43
|
+
* there `null` is no value unless the declaration names it. */
|
|
44
|
+
function takesNull(param: { type?: unknown; nullable?: unknown } | undefined): boolean {
|
|
45
|
+
const type = param?.type;
|
|
46
|
+
if (type === undefined || param?.nullable === true) return true;
|
|
47
|
+
return Array.isArray(type) ? type.includes('null') : type === 'null';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A value as the call gave it, briefly: the first 80 characters of its JSON
|
|
51
|
+
* text, then an ellipsis when there are more, cut between characters rather
|
|
52
|
+
* than inside one. The whole value is serialized before the cut. */
|
|
53
|
+
function shown(value: unknown): string {
|
|
54
|
+
let text: string;
|
|
55
|
+
try {
|
|
56
|
+
text = JSON.stringify(value) ?? String(value);
|
|
57
|
+
} catch {
|
|
58
|
+
text = '(a value that could not be shown)';
|
|
59
|
+
}
|
|
60
|
+
let head = '';
|
|
61
|
+
let count = 0;
|
|
62
|
+
for (const char of text) {
|
|
63
|
+
if (count === 80) return `${head}…`;
|
|
64
|
+
head += char;
|
|
65
|
+
count++;
|
|
66
|
+
}
|
|
67
|
+
return head;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A list in prose: `a`, `a or b`, `a, b or c`. */
|
|
71
|
+
function oneOf(items: string[]): string {
|
|
72
|
+
return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} or ${items[items.length - 1]}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A list in prose: `a`, `a and b`, `a, b and c`. */
|
|
76
|
+
function allOf(items: string[]): string {
|
|
77
|
+
return items.length <= 1 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** At most this many keys a call gave that its schema doesn't declare are named. */
|
|
81
|
+
const UNDECLARED_SHOWN = 5;
|
|
82
|
+
|
|
83
|
+
/** An own property, so a key such as `constructor` isn't found on the prototype. */
|
|
84
|
+
function own(obj: Record<string, unknown>, key: string): unknown {
|
|
85
|
+
return Object.hasOwn(obj, key) ? obj[key] : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** An object that isn't a list: a map of keys, as `properties` should be. */
|
|
89
|
+
function isMap(value: unknown): value is Record<string, unknown> {
|
|
90
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Every root keyword in JSON Schema's drafts 4 to 2020-12, besides
|
|
94
|
+
* `properties` and `required`, that can accept, require or name an object's
|
|
95
|
+
* keys, except the two read by declaresKeysPlainly (`additionalProperties`
|
|
96
|
+
* and `unevaluatedProperties`, which count when `true` or a schema object). The
|
|
97
|
+
* reading is cautious: a root with any of them names no key, whatever its
|
|
98
|
+
* subschemas hold, since naming a key the schema takes would mislead the
|
|
99
|
+
* agent more than a hint left out. So `not` is here too, since a negation
|
|
100
|
+
* can require a key (`not: {properties: {y: {type: 'string'}}}` refuses an
|
|
101
|
+
* object without a non-string `y`), and so is `if` with or without `then`
|
|
102
|
+
* and `else`. `then` and `else` act only beside `if`, and a keyword JSON
|
|
103
|
+
* Schema doesn't have declares nothing. */
|
|
104
|
+
const OTHER_KEY_DECLARERS = [
|
|
105
|
+
'allOf', 'anyOf', 'oneOf', 'not', 'if', '$ref', '$dynamicRef', '$recursiveRef',
|
|
106
|
+
'dependentSchemas', 'dependencies', 'dependentRequired', 'patternProperties',
|
|
107
|
+
'propertyNames', 'enum', 'const',
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
/** Whether a schema's root declares its keys only in `properties` and
|
|
111
|
+
* `required`, so that a key in neither isn't one of its parameters. A root
|
|
112
|
+
* with any keyword in OTHER_KEY_DECLARERS can declare keys another way. A
|
|
113
|
+
* schema object for `additionalProperties` or `unevaluatedProperties`, `{}`
|
|
114
|
+
* included, or `true`, which means the same there, says the root takes keys
|
|
115
|
+
* `properties` doesn't list, as a map's input does, so a key in neither list
|
|
116
|
+
* may be meant. `false` there says it takes none, so it names. Left out,
|
|
117
|
+
* the keyword is JSON Schema's default, which says nothing of what the
|
|
118
|
+
* author meant, and most schemas leave it out without welcoming other keys,
|
|
119
|
+
* so a root without it names them too. */
|
|
120
|
+
function declaresKeysPlainly(schema: Record<string, unknown>): boolean {
|
|
121
|
+
return !OTHER_KEY_DECLARERS.some((key) => Object.hasOwn(schema, key))
|
|
122
|
+
&& !takesOtherKeys(schema.additionalProperties)
|
|
123
|
+
&& !takesOtherKeys(schema.unevaluatedProperties);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function takesOtherKeys(value: unknown): boolean {
|
|
127
|
+
return value === true || isMap(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* What's wrong with a call's input against its tool's declared inputSchema,
|
|
132
|
+
* checked shallowly: the input is an object, and each root `required` key has
|
|
133
|
+
* a value. With `values`, also each declared key's value: a type and an enum
|
|
134
|
+
* value its declaration admits. It isn't a JSON-Schema engine: nested shapes,
|
|
135
|
+
* unions and references are the handler's to check, as they always were.
|
|
136
|
+
* Every problem is reported at once, each naming the key, what the schema
|
|
137
|
+
* wants and what the call gave. An empty list means the call can go to its
|
|
138
|
+
* handler.
|
|
139
|
+
*
|
|
140
|
+
* - An absent input (`undefined` or `null`) gives no arguments: it's refused
|
|
141
|
+
* only for the keys the schema requires.
|
|
142
|
+
* - A required key given as `null` has no value, the same as one left out,
|
|
143
|
+
* unless its declaration admits `null` (a type list naming it, or
|
|
144
|
+
* `nullable: true`), or has no `type` at all: a key with no `type` takes
|
|
145
|
+
* `null` as a value, as it takes any other. One whose `type` the check
|
|
146
|
+
* can't read, such as Gemini's `STRING`, or a list naming one, takes
|
|
147
|
+
* `null` only where the declaration names it, and its values aren't
|
|
148
|
+
* checked against the type. An empty string is a value.
|
|
149
|
+
* - A schema whose `required` isn't a list requires nothing here, and a key
|
|
150
|
+
* it lists twice is reported once.
|
|
151
|
+
* - Where a required key is missing and the call gave keys the schema
|
|
152
|
+
* declares neither in its `properties` nor in its `required` list, those
|
|
153
|
+
* are named too, since a key sent under another name is one reason a
|
|
154
|
+
* required key goes missing. They're never refused for themselves. A schema
|
|
155
|
+
* whose root can declare keys another way names none
|
|
156
|
+
* (declaresKeysPlainly).
|
|
157
|
+
*
|
|
158
|
+
* A module tool's call is checked without `values`: a handler may take more
|
|
159
|
+
* than its schema can say on every provider, such as one name where a list
|
|
160
|
+
* is declared, and refusing that would refuse calls that work. The `utils`
|
|
161
|
+
* tool checks a utility's values too, as it always has. `field` names what
|
|
162
|
+
* the input is to the caller ("input" unless given).
|
|
163
|
+
*/
|
|
164
|
+
export function toolArgsProblems(
|
|
165
|
+
input: unknown,
|
|
166
|
+
schema: ToolDefinition['inputSchema'] | undefined,
|
|
167
|
+
options: { values?: boolean; field?: string } = {},
|
|
168
|
+
): string[] {
|
|
169
|
+
if (!schema) return [];
|
|
170
|
+
const absent = input === undefined || input === null;
|
|
171
|
+
if (!absent && (typeof input !== 'object' || Array.isArray(input))) {
|
|
172
|
+
return [`its ${options.field ?? 'input'} must be an object, and this call gave ${withArticle(kindOf(input))}`];
|
|
173
|
+
}
|
|
174
|
+
const obj = (absent ? {} : input) as Record<string, unknown>;
|
|
175
|
+
const properties = (isMap(schema.properties) ? schema.properties : {}) as Record<string, { type?: unknown; nullable?: unknown; enum?: unknown } | undefined>;
|
|
176
|
+
const declared = (key: string) => (Object.hasOwn(properties, key) ? properties[key] : undefined);
|
|
177
|
+
const problems: string[] = [];
|
|
178
|
+
const missing = new Set<string>();
|
|
179
|
+
const required: unknown[] = Array.isArray(schema.required) ? schema.required : [];
|
|
180
|
+
for (const key of required) {
|
|
181
|
+
if (typeof key !== 'string' || missing.has(key)) continue;
|
|
182
|
+
const value = own(obj, key);
|
|
183
|
+
const kinds = admittedKinds(declared(key));
|
|
184
|
+
if (value !== undefined && (value !== null || takesNull(declared(key)))) continue;
|
|
185
|
+
missing.add(key);
|
|
186
|
+
const wanted = kinds.length > 0 ? `"${key}" (${oneOf(kinds.map(withArticle))})` : `"${key}"`;
|
|
187
|
+
problems.push(`needs ${wanted}, and this call gave ${value === null ? 'null' : 'none'}`);
|
|
188
|
+
}
|
|
189
|
+
if (missing.size > 0 && declaresKeysPlainly(schema as Record<string, unknown>)) {
|
|
190
|
+
const undeclared = Object.keys(obj).filter((key) => !Object.hasOwn(properties, key) && !required.includes(key));
|
|
191
|
+
if (undeclared.length > 0) {
|
|
192
|
+
const named = undeclared.slice(0, UNDECLARED_SHOWN).map(shown);
|
|
193
|
+
if (undeclared.length > UNDECLARED_SHOWN) named.push(`${undeclared.length - UNDECLARED_SHOWN} more`);
|
|
194
|
+
problems.push(undeclared.length === 1 ? `${named[0]} isn't one of its parameters` : `${allOf(named)} aren't among its parameters`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!options.values) return problems;
|
|
198
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
199
|
+
const param = declared(key);
|
|
200
|
+
if (!param || value === undefined || missing.has(key)) continue;
|
|
201
|
+
const kinds = admittedKinds(param);
|
|
202
|
+
const kind = kindOf(value);
|
|
203
|
+
const largeInteger = kind === 'string' && kinds.includes('number') && LARGE_INTEGER_TEXT.test(value as string);
|
|
204
|
+
if (kinds.length > 0 && !kinds.includes(kind) && !largeInteger) {
|
|
205
|
+
problems.push(`"${key}" must be ${oneOf(kinds.map(withArticle))}, and this call gave ${withArticle(kind)}`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (Array.isArray(param.enum) && !param.enum.includes(value)) {
|
|
209
|
+
problems.push(`"${key}" must be one of ${oneOf(param.enum.map(shown))}, and this call gave ${shown(value)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return problems;
|
|
213
|
+
}
|
package/src/tool-presentation.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { fchmodSync, closeSync, fstatSync, lstatSync, openSync, readSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { constants, fchmodSync, closeSync, fstatSync, lstatSync, openSync, readSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { closestNames } from './closest-names.js';
|
|
4
4
|
import { boundedErrorMessage, errorMessage } from './error-message.js';
|
|
5
5
|
import type { ToolDefinition, ToolResult } from './types/events.js';
|
|
@@ -24,6 +24,13 @@ export interface PresentationSnapshot {
|
|
|
24
24
|
entries: { name: string; source: string; visible: boolean; originalDescription: string; description: string; descriptionSource?: string; inputSchema: ToolDefinition['inputSchema'] }[];
|
|
25
25
|
}
|
|
26
26
|
const MAX_BYTES = 256 * 1024;
|
|
27
|
+
/**
|
|
28
|
+
* Opened with each read of a presentation file, as observe.ts opens workspace
|
|
29
|
+
* files: a FIFO at the operator's path opens at once and fails the regular-file
|
|
30
|
+
* check, where a blocking open would hold the whole process (it's synchronous)
|
|
31
|
+
* until something opened the FIFO for writing.
|
|
32
|
+
*/
|
|
33
|
+
const NONBLOCK = typeof constants.O_NONBLOCK === 'number' ? constants.O_NONBLOCK : 0;
|
|
27
34
|
const MAX_DESCRIPTION_CHARS = 32768;
|
|
28
35
|
const protectedNames = new Set(['set_tool_visibility', 'set_tool_description', 'workspace--read']);
|
|
29
36
|
export const isPresentationTool = (name: string): boolean => name === 'set_tool_visibility' || name === 'set_tool_description';
|
|
@@ -173,7 +180,7 @@ export class ToolPresentation {
|
|
|
173
180
|
constructor(readonly config: ToolPresentationConfig) {}
|
|
174
181
|
private read(path = this.config.path, allowMissing = true): string {
|
|
175
182
|
let fd: number;
|
|
176
|
-
try { fd = openSync(path,
|
|
183
|
+
try { fd = openSync(path, constants.O_RDONLY | NONBLOCK); }
|
|
177
184
|
catch (error) {
|
|
178
185
|
const code = (error as NodeJS.ErrnoException).code;
|
|
179
186
|
if (code === 'ENOENT') {
|
package/src/types/agent.ts
CHANGED
|
@@ -14,7 +14,12 @@ export type SameRoundThinkTextPolicySource =
|
|
|
14
14
|
*/
|
|
15
15
|
export interface AgentConfig {
|
|
16
16
|
toolPresentation?: import("../tool-presentation.js").ToolPresentationConfig;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Unique name for this agent: create() refuses two configured agents of one
|
|
19
|
+
* name. Names compare exactly, so `Dup` beside `dup` is two agents, though
|
|
20
|
+
* the history tool's author filter matches their own turns without letter
|
|
21
|
+
* case and can't tell them apart there.
|
|
22
|
+
*/
|
|
18
23
|
name: string;
|
|
19
24
|
|
|
20
25
|
/** Model to use (e.g., 'claude-sonnet-4-20250514') */
|
|
@@ -27,9 +32,19 @@ export interface AgentConfig {
|
|
|
27
32
|
strategy?: ContextStrategy;
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
35
|
+
* Enforced at dispatch for registered agent callers; omitted means 'all'.
|
|
31
36
|
* - 'all': all available tools
|
|
32
|
-
* - string[]:
|
|
37
|
+
* - string[]: exact tool names, including module--utility names for utilities.
|
|
38
|
+
* Utilities aren't tools of their own: an agent's model and its scripts are
|
|
39
|
+
* offered them only through 'utils', whose run checks the utility's own
|
|
40
|
+
* name. An agent that should use a utility lists both 'utils' and
|
|
41
|
+
* 'module--utility'. The name alone passes the dispatch check for any call
|
|
42
|
+
* by that name, whether the host makes it with executeToolCall on the
|
|
43
|
+
* agent's behalf or the model or a script makes it without being offered
|
|
44
|
+
* it. The slot is hidden when no utility is permitted.
|
|
45
|
+
* Trusted module calls are exempt. Genuinely unidentified public callers,
|
|
46
|
+
* including unknown legacy names, remain unrestricted. Framework-created
|
|
47
|
+
* identities have tool authority only while registered.
|
|
33
48
|
*/
|
|
34
49
|
allowedTools?: 'all' | string[];
|
|
35
50
|
|
|
@@ -172,12 +187,6 @@ export interface AgentConfig {
|
|
|
172
187
|
autoRewind?: boolean;
|
|
173
188
|
/** Max consecutive rewinds before giving up a turn. Default 3. */
|
|
174
189
|
maxRewinds?: number;
|
|
175
|
-
/**
|
|
176
|
-
* When the rewound turn is a *human* message (not a machine tool result),
|
|
177
|
-
* announce the withholding on the conversational surface (Discord) rather
|
|
178
|
-
* than dropping it silently. Default true.
|
|
179
|
-
*/
|
|
180
|
-
announceHumanTurns?: boolean;
|
|
181
190
|
};
|
|
182
191
|
|
|
183
192
|
/** Opt-in tool-output admission and one guarded retry after a provider
|
|
@@ -197,7 +206,8 @@ export interface AgentConfig {
|
|
|
197
206
|
* that publication envelope through the same authorized resolver.
|
|
198
207
|
* See docs/hybrid-prose-routing.md.
|
|
199
208
|
* - 'disabled': generated prose is never published. Only explicit tools may
|
|
200
|
-
* send externally; prose remains in Chronicle
|
|
209
|
+
* send externally; prose remains in Chronicle, and a turn that wrote plain
|
|
210
|
+
* speech ends with a private `[delivered] plain speech → no channel` receipt.
|
|
201
211
|
*/
|
|
202
212
|
proseRouting?: 'locus' | 'explicit' | 'hybrid' | 'disabled';
|
|
203
213
|
|
|
@@ -372,7 +382,8 @@ export interface InferenceRequest {
|
|
|
372
382
|
* replies in the channel it is answering — not the process-global most-recent-
|
|
373
383
|
* inbound locus, which a concurrent message elsewhere can hijack (item-3
|
|
374
384
|
* redux). Undefined for non-channel wakes (heartbeat, timers, module events),
|
|
375
|
-
* which correctly fall back to the global default channel
|
|
385
|
+
* which correctly fall back to the global default channel, except an
|
|
386
|
+
* ephemeral run's (a subagent's), which takes no global fallback.
|
|
376
387
|
*/
|
|
377
388
|
channelId?: string;
|
|
378
389
|
/**
|
|
@@ -406,11 +417,10 @@ export interface InferenceRequest {
|
|
|
406
417
|
/** Suppress every automatic plain-prose delivery for this logical turn.
|
|
407
418
|
* Explicit tool calls remain available. Used by authenticated silent wakes. */
|
|
408
419
|
suppressProse?: boolean;
|
|
409
|
-
/**
|
|
410
|
-
*
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
*
|
|
414
|
-
* builds render a request-only separator before the tick's first row. */
|
|
420
|
+
/** The authenticated silent heartbeat tick this turn answers. The rows the
|
|
421
|
+
* turn's reply stores (its assistant responses and tool results) are
|
|
422
|
+
* stamped `metadata.silentHeartbeat` with it, and request builds render a
|
|
423
|
+
* request-only separator carrying the tick's instruction before the tick's
|
|
424
|
+
* first surviving row (`SILENT_HEARTBEAT_SEPARATOR`). */
|
|
415
425
|
silentHeartbeat?: SilentHeartbeatTick;
|
|
416
426
|
}
|
package/src/types/events.ts
CHANGED
|
@@ -192,6 +192,14 @@ export interface ToolResult {
|
|
|
192
192
|
error?: string;
|
|
193
193
|
/** Whether this was an error (for LLM) */
|
|
194
194
|
isError?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* An MCP tool's `structuredContent`: its machine-readable result, beside
|
|
197
|
+
* `data`, which is the content view. Present exactly when the server sent
|
|
198
|
+
* one, so `false`, `0` and `null` are values here, not absence. Programs
|
|
199
|
+
* read it (scripts get it in their result object); the model sees content,
|
|
200
|
+
* and sees this rendered as JSON only when the content carries no text.
|
|
201
|
+
*/
|
|
202
|
+
structured?: unknown;
|
|
195
203
|
/**
|
|
196
204
|
* When true, the framework saves tool_use + tool_result messages to context,
|
|
197
205
|
* cancels the active stream, and resets the agent to idle.
|
package/src/types/framework.ts
CHANGED
|
@@ -24,8 +24,6 @@ export interface ModuleProcessResponse {
|
|
|
24
24
|
export interface ProcessLoggingConfig {
|
|
25
25
|
/** Persist process logs to Chronicle (default: false) */
|
|
26
26
|
persist?: boolean;
|
|
27
|
-
/** Broadcast process:completed trace events (default: false) */
|
|
28
|
-
broadcast?: boolean;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
/**
|
|
@@ -76,6 +74,23 @@ export interface CodeExecutionConfig {
|
|
|
76
74
|
/** Max wake_agent() calls per background script (default 100); further
|
|
77
75
|
* calls raise RuntimeError inside the script. */
|
|
78
76
|
maxWakesPerScript?: number;
|
|
77
|
+
/** Variables for the python interpreter beyond the operating allowlist it
|
|
78
|
+
* inherits (see CHILD_ENV_ALLOWLIST), such as PYTHONPATH. A recipe can
|
|
79
|
+
* `${VAR}`-substitute them from .env. Other host variables aren't
|
|
80
|
+
* inherited, which limits what model-authored code inherits, not what a
|
|
81
|
+
* process running as the same user can read. The runner sets
|
|
82
|
+
* PYTHONUNBUFFERED and PYTHONIOENCODING last, over any value given here,
|
|
83
|
+
* since its protocol needs both. The interpreter's working directory is
|
|
84
|
+
* its own runtime directory, so a relative path given here, as in
|
|
85
|
+
* PYTHONPATH, resolves there: give absolute paths. */
|
|
86
|
+
env?: Record<string, string>;
|
|
87
|
+
/** The interpreter inherits the host's whole environment instead of the
|
|
88
|
+
* allowlist: every host variable, provider keys included, reaches
|
|
89
|
+
* model-authored code and every subprocess a script starts. Listing what
|
|
90
|
+
* scripts need in `env` is the safer route. With code execution enabled,
|
|
91
|
+
* `create()` refuses any value but true, false or null, which counts as
|
|
92
|
+
* not set. */
|
|
93
|
+
inheritEnv?: boolean;
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
/** See `FrameworkConfig.providerHold`. */
|
package/src/types/module.ts
CHANGED
|
@@ -19,15 +19,39 @@ export interface MessageQuery {
|
|
|
19
19
|
externalIds?: string[];
|
|
20
20
|
/** Filter by participant name */
|
|
21
21
|
participant?: string;
|
|
22
|
-
/** Filter by metadata fields
|
|
22
|
+
/** Filter by metadata fields: each key a path, dot-separated for a nested
|
|
23
|
+
* field (such as 'external.channelId'), its value compared by strict
|
|
24
|
+
* equality */
|
|
23
25
|
metadata?: Record<string, unknown>;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* A message as a module context's reads answer it: a stored row, or a row
|
|
30
|
+
* for a write the framework holds (`held`).
|
|
31
|
+
*/
|
|
32
|
+
export type ModuleMessage = StoredMessage & {
|
|
33
|
+
/**
|
|
34
|
+
* True while the framework holds the message's write, as it does while
|
|
35
|
+
* the write's agent has a turn alive or the host is quiesced, until a
|
|
36
|
+
* later boundary stores it. The row's `id` is then a handle, which
|
|
37
|
+
* getMessage, editMessage and removeMessage take as they take a stored
|
|
38
|
+
* id, and which names the stored row once the write lands. Its `sequence`
|
|
39
|
+
* and `timestamp` are provisional: the sequence comes after every stored
|
|
40
|
+
* row's, the timestamp is when the framework held the write (for a write
|
|
41
|
+
* an older release held, when its queue was read back), and both change
|
|
42
|
+
* when it lands. Absent on a stored row.
|
|
43
|
+
*/
|
|
44
|
+
held?: true;
|
|
45
|
+
};
|
|
46
|
+
|
|
26
47
|
/**
|
|
27
48
|
* Result of a message query.
|
|
28
49
|
*/
|
|
29
50
|
export interface MessageQueryResult {
|
|
30
|
-
|
|
51
|
+
/** The matching stored rows, then, as the newest, the matching rows of
|
|
52
|
+
* writes the framework holds (ModuleMessage.held): before the stored rows
|
|
53
|
+
* in a reversed query */
|
|
54
|
+
messages: ModuleMessage[];
|
|
31
55
|
totalCount: number;
|
|
32
56
|
}
|
|
33
57
|
|
|
@@ -249,7 +273,14 @@ export interface ModuleContext {
|
|
|
249
273
|
getModule<T extends Module>(name: string): T | null;
|
|
250
274
|
|
|
251
275
|
/**
|
|
252
|
-
* Add a message to the conversation.
|
|
276
|
+
* Add a message to the conversation, and answer its id. While the
|
|
277
|
+
* framework holds the write (the agent's turn is alive, or the host is
|
|
278
|
+
* quiesced), the id is a handle, `held:` and the write's id, which
|
|
279
|
+
* getMessage, editMessage and removeMessage take as they take a stored
|
|
280
|
+
* id, and which names the stored row once the write lands
|
|
281
|
+
* (ModuleMessage.held). The writes a process held, and keeps across a
|
|
282
|
+
* restart, show to the next process's modules once create() has restored
|
|
283
|
+
* them, which is after every module's start().
|
|
253
284
|
*/
|
|
254
285
|
addMessage(
|
|
255
286
|
participant: string,
|
|
@@ -258,28 +289,40 @@ export interface ModuleContext {
|
|
|
258
289
|
): MessageId;
|
|
259
290
|
|
|
260
291
|
/**
|
|
261
|
-
* Edit a message.
|
|
292
|
+
* Edit a message, by its id or a held write's handle. A held write is
|
|
293
|
+
* stored with the new content.
|
|
262
294
|
*/
|
|
263
295
|
editMessage(id: MessageId, content: ContentBlock[]): void;
|
|
264
296
|
|
|
265
297
|
/**
|
|
266
|
-
* Remove a message.
|
|
298
|
+
* Remove a message, by its id or a held write's handle. A held write is
|
|
299
|
+
* dropped, so the agent never reads it.
|
|
267
300
|
*/
|
|
268
301
|
removeMessage(id: MessageId): void;
|
|
269
302
|
|
|
270
303
|
/**
|
|
271
|
-
* Find
|
|
304
|
+
* Find the message for an external ID this context added a message under:
|
|
305
|
+
* the row it added, while the store holds it. For a write the framework
|
|
306
|
+
* held, whose id this context doesn't keep, the first row the store shows
|
|
307
|
+
* under that external ID, held rows included, a held row by its handle.
|
|
308
|
+
* Null when this context added none under it, when the row it added is
|
|
309
|
+
* gone, or when the store shows none for a held write, such as one the
|
|
310
|
+
* last process held, looked up in start().
|
|
272
311
|
*/
|
|
273
312
|
findMessageByExternalId(source: string, externalId: string): MessageId | null;
|
|
274
313
|
|
|
275
314
|
/**
|
|
276
|
-
* Get a message by
|
|
315
|
+
* Get a message by its id or a held write's handle.
|
|
277
316
|
*/
|
|
278
|
-
getMessage(id: MessageId):
|
|
317
|
+
getMessage(id: MessageId): ModuleMessage | null;
|
|
279
318
|
|
|
280
319
|
/**
|
|
281
320
|
* Query messages by filter criteria.
|
|
282
321
|
* Useful for finding messages from external sources, by participant, etc.
|
|
322
|
+
* The rows of writes the framework holds come after the stored rows, as
|
|
323
|
+
* the newest, or before them in a reversed query, marked `held`
|
|
324
|
+
* (ModuleMessage). Those the last process held show once create() has
|
|
325
|
+
* restored them, after every module's start() (addMessage).
|
|
283
326
|
*/
|
|
284
327
|
queryMessages(filter: MessageQuery): MessageQueryResult;
|
|
285
328
|
|
|
@@ -447,12 +490,14 @@ export interface EventResponse {
|
|
|
447
490
|
addMessages?: NewMessage[];
|
|
448
491
|
|
|
449
492
|
/**
|
|
450
|
-
* Messages to edit
|
|
493
|
+
* Messages to edit, each by its id or a held write's handle
|
|
494
|
+
* (ModuleContext.editMessage).
|
|
451
495
|
*/
|
|
452
496
|
editMessages?: MessageEdit[];
|
|
453
497
|
|
|
454
498
|
/**
|
|
455
|
-
* Messages to remove
|
|
499
|
+
* Messages to remove, each by its id or a held write's handle
|
|
500
|
+
* (ModuleContext.removeMessage).
|
|
456
501
|
*/
|
|
457
502
|
removeMessages?: MessageId[];
|
|
458
503
|
|
package/src/types/trace.ts
CHANGED
|
@@ -632,7 +632,7 @@ export type TraceEvent =
|
|
|
632
632
|
| (TraceEventBase & {
|
|
633
633
|
type: 'mcpl:orphaned-response';
|
|
634
634
|
serverId: string;
|
|
635
|
-
/** The JSON-RPC id of the late response whose
|
|
635
|
+
/** The JSON-RPC id of the late response, whose request had already timed out or been ended by its transport's close. */
|
|
636
636
|
responseId: string | number;
|
|
637
637
|
/** Original request method when retained for mandatory-deadline visibility. */
|
|
638
638
|
method?: string;
|
|
@@ -674,7 +674,7 @@ export type TraceEvent =
|
|
|
674
674
|
parkedAdmissions?: number;
|
|
675
675
|
/** True when undrained turns were force-cancelled (abandon). */
|
|
676
676
|
abandoned?: boolean;
|
|
677
|
-
/** Agents whose turn abandon
|
|
677
|
+
/** Agents whose turn the abandon had not ended when its grace ran out: a token held with no stream and no check ahead (a puppet tool turn, a store surgery's reservation, a turn finishing its last delivery), or a turn that hadn't reached its next check, the tool-result guard's wait for its speech included. A turn whose stream the abandon cancelled while it was still streaming isn't listed, even one still running when the grace ran out. */
|
|
678
678
|
unabandonable?: string[];
|
|
679
679
|
})
|
|
680
680
|
| (TraceEventBase & {
|