@introspection-ai/recipes 0.30.1 → 0.32.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/dist/connector-tools.d.ts.map +1 -1
- package/dist/connector-tools.js +3 -2
- package/dist/connector-tools.js.map +1 -1
- package/dist/mcp/cli/core.d.ts.map +1 -1
- package/dist/mcp/cli/core.js +6 -2
- package/dist/mcp/cli/core.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +3 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp-chunks/{chunk-4B7SUIQQ.js → chunk-33YQLJYL.js} +3 -1
- package/dist/mcp-daemon.js +1 -1
- package/dist/mcp-run-worker.js +1 -1
- package/dist/pi-extension.d.ts.map +1 -1
- package/dist/pi-extension.js +1 -11
- package/dist/pi-extension.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +7 -9
- package/dist/session.js.map +1 -1
- package/dist/tool-search.d.ts +10 -18
- package/dist/tool-search.d.ts.map +1 -1
- package/dist/tool-search.js +41 -169
- package/dist/tool-search.js.map +1 -1
- package/docs/mcp-configuration.md +7 -5
- package/docs/pi-extension.md +1 -1
- package/docs/recipe-judges.md +41 -1
- package/package.json +13 -13
package/dist/tool-search.js
CHANGED
|
@@ -1,176 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createToolSearchExtension, } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export const RECIPE_TOOL_SEARCH_NAME = "tool_search";
|
|
3
3
|
export const LEGACY_MCP_TOOL_SEARCH_NAME = "mcp_search";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!value || typeof value !== "object" || visited.has(value))
|
|
16
|
-
return;
|
|
17
|
-
visited.add(value);
|
|
18
|
-
const record = value;
|
|
19
|
-
if (record.properties && typeof record.properties === "object") {
|
|
20
|
-
for (const [name, property] of Object.entries(record.properties)) {
|
|
21
|
-
propertyNames.push(name);
|
|
22
|
-
if (property && typeof property === "object") {
|
|
23
|
-
const description = property.description;
|
|
24
|
-
if (typeof description === "string") {
|
|
25
|
-
propertyDescriptions.push(description);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
visit(property);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
for (const keyword of [
|
|
32
|
-
"allOf",
|
|
33
|
-
"anyOf",
|
|
34
|
-
"oneOf",
|
|
35
|
-
"items",
|
|
36
|
-
"additionalProperties",
|
|
37
|
-
]) {
|
|
38
|
-
const nested = record[keyword];
|
|
39
|
-
if (Array.isArray(nested)) {
|
|
40
|
-
for (const entry of nested)
|
|
41
|
-
visit(entry);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
visit(nested);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
visit(schema);
|
|
49
|
-
return {
|
|
50
|
-
propertyNames: propertyNames.join(" ").toLowerCase(),
|
|
51
|
-
propertyDescriptions: propertyDescriptions.join(" ").toLowerCase(),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function scoreTool(tool, query) {
|
|
55
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
56
|
-
if (!normalizedQuery)
|
|
57
|
-
return 0;
|
|
58
|
-
const name = tool.name.toLowerCase();
|
|
59
|
-
const label = (tool.label ?? tool.name).toLowerCase();
|
|
60
|
-
const description = tool.description.toLowerCase();
|
|
61
|
-
const { propertyNames, propertyDescriptions } = schemaSearchTerms(tool.parameters);
|
|
62
|
-
let score = 0;
|
|
63
|
-
if (name === normalizedQuery)
|
|
64
|
-
score += 500;
|
|
65
|
-
if (label === normalizedQuery)
|
|
66
|
-
score += 300;
|
|
67
|
-
if (name.includes(normalizedQuery))
|
|
68
|
-
score += 120;
|
|
69
|
-
if (label.includes(normalizedQuery))
|
|
70
|
-
score += 100;
|
|
71
|
-
if (description.includes(normalizedQuery))
|
|
72
|
-
score += 60;
|
|
73
|
-
const nameTerms = new Set(words(name));
|
|
74
|
-
const labelTerms = new Set(words(label));
|
|
75
|
-
const propertyNameTerms = new Set(words(propertyNames));
|
|
76
|
-
for (const term of words(normalizedQuery)) {
|
|
77
|
-
if (nameTerms.has(term))
|
|
78
|
-
score += 40;
|
|
79
|
-
else if (name.includes(term))
|
|
80
|
-
score += 20;
|
|
81
|
-
if (labelTerms.has(term))
|
|
82
|
-
score += 30;
|
|
83
|
-
else if (label.includes(term))
|
|
84
|
-
score += 15;
|
|
85
|
-
if (description.includes(term))
|
|
86
|
-
score += 16;
|
|
87
|
-
if (propertyNameTerms.has(term))
|
|
88
|
-
score += 12;
|
|
89
|
-
else if (propertyNames.includes(term))
|
|
90
|
-
score += 6;
|
|
91
|
-
if (propertyDescriptions.includes(term))
|
|
92
|
-
score += 6;
|
|
93
|
-
}
|
|
94
|
-
return score;
|
|
95
|
-
}
|
|
96
|
-
export function createRecipeToolSearch(options) {
|
|
97
|
-
const toolsByName = new Map(options.tools.map((tool) => [tool.name, tool]));
|
|
98
|
-
const deferred = [...new Set(options.deferredToolNames)].map((name) => {
|
|
99
|
-
const tool = toolsByName.get(name);
|
|
100
|
-
if (!tool) {
|
|
101
|
-
throw new Error(`Recipe deferred tool '${name}' was not registered before tool search`);
|
|
102
|
-
}
|
|
103
|
-
return tool;
|
|
104
|
-
});
|
|
105
|
-
if (deferred.length === 0)
|
|
106
|
-
return undefined;
|
|
107
|
-
if (toolsByName.has(RECIPE_TOOL_SEARCH_NAME)) {
|
|
108
|
-
throw new Error(`Recipe tool name '${RECIPE_TOOL_SEARCH_NAME}' is reserved by the session`);
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
name: RECIPE_TOOL_SEARCH_NAME,
|
|
112
|
-
label: "Tool search",
|
|
113
|
-
description: "Search inactive tools allowed for this Recipe and enable the best matches for the next model request.",
|
|
114
|
-
parameters: Type.Object({
|
|
115
|
-
query: Type.String({
|
|
116
|
-
description: "Capability or task to find a tool for.",
|
|
117
|
-
}),
|
|
118
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 10, default: 3 })),
|
|
119
|
-
}),
|
|
120
|
-
executionMode: "sequential",
|
|
121
|
-
async execute(_toolCallId, params) {
|
|
122
|
-
const input = params;
|
|
123
|
-
const query = typeof input.query === "string" ? input.query : "";
|
|
124
|
-
const limit = typeof input.limit === "number" ? input.limit : 3;
|
|
125
|
-
const active = new Set(options.activation.getActiveTools());
|
|
126
|
-
const matches = deferred
|
|
127
|
-
.filter((tool) => !active.has(tool.name))
|
|
128
|
-
.map((tool) => ({ tool, score: scoreTool(tool, query) }))
|
|
129
|
-
.filter((match) => match.score > 0)
|
|
130
|
-
.sort((left, right) => right.score - left.score ||
|
|
131
|
-
left.tool.name.localeCompare(right.tool.name))
|
|
132
|
-
.slice(0, limit);
|
|
133
|
-
const added = matches.map((match) => match.tool.name);
|
|
134
|
-
if (added.length > 0) {
|
|
135
|
-
options.activation.setActiveTools([...active, ...added]);
|
|
136
|
-
}
|
|
137
|
-
const details = {
|
|
138
|
-
matches: matches.map(({ tool }) => ({
|
|
139
|
-
name: tool.name,
|
|
140
|
-
label: tool.label ?? tool.name,
|
|
141
|
-
description: tool.description,
|
|
142
|
-
})),
|
|
143
|
-
added,
|
|
144
|
-
};
|
|
145
|
-
const text = matches.length === 0
|
|
146
|
-
? `No inactive Recipe tools matched "${query}".`
|
|
147
|
-
: [
|
|
148
|
-
`Enabled ${matches.length} Recipe tool(s) for the next model request:`,
|
|
149
|
-
...matches.map(({ tool }) => `- ${tool.name}${tool.description ? `: ${tool.description}` : ""}`),
|
|
150
|
-
].join("\n");
|
|
151
|
-
return {
|
|
152
|
-
content: [{ type: "text", text }],
|
|
153
|
-
details,
|
|
154
|
-
};
|
|
4
|
+
/**
|
|
5
|
+
* Pi's `tool_search`, plus `mcp_search` as an alias when MCP tools are
|
|
6
|
+
* deferred. It searches tools registered with `deferred` exposure and declares
|
|
7
|
+
* the matches for the next model request.
|
|
8
|
+
*/
|
|
9
|
+
export function createRecipeToolSearchTools(tools, includeLegacyMcpAlias) {
|
|
10
|
+
let search;
|
|
11
|
+
createToolSearchExtension()({
|
|
12
|
+
...tools,
|
|
13
|
+
registerTool: (definition) => {
|
|
14
|
+
search = definition;
|
|
155
15
|
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
export function createRecipeToolSearchTools(options, includeLegacyMcpAlias) {
|
|
159
|
-
const search = createRecipeToolSearch(options);
|
|
16
|
+
});
|
|
160
17
|
if (!search)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
18
|
+
throw new Error("Pi did not provide tool_search");
|
|
19
|
+
const { defaultActive: _defaultActive, ...definition } = search;
|
|
20
|
+
return includeLegacyMcpAlias
|
|
21
|
+
? [
|
|
22
|
+
definition,
|
|
23
|
+
{
|
|
24
|
+
...definition,
|
|
25
|
+
name: LEGACY_MCP_TOOL_SEARCH_NAME,
|
|
26
|
+
label: LEGACY_MCP_TOOL_SEARCH_NAME,
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
: [definition];
|
|
30
|
+
}
|
|
31
|
+
/** Register the named tools with `deferred` exposure, so tool search can load them. */
|
|
32
|
+
export function deferTools(factory, deferredToolNames) {
|
|
33
|
+
if (deferredToolNames.length === 0)
|
|
34
|
+
return factory;
|
|
35
|
+
const deferred = new Set(deferredToolNames);
|
|
36
|
+
return (pi) => factory(new Proxy(pi, {
|
|
37
|
+
get(target, property, receiver) {
|
|
38
|
+
const value = Reflect.get(target, property, receiver);
|
|
39
|
+
if (property !== "registerTool" || typeof value !== "function") {
|
|
40
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
41
|
+
}
|
|
42
|
+
return (definition) => value.call(target, deferred.has(definition.name)
|
|
43
|
+
? { ...definition, exposure: "deferred" }
|
|
44
|
+
: definition);
|
|
173
45
|
},
|
|
174
|
-
|
|
46
|
+
}));
|
|
175
47
|
}
|
|
176
48
|
//# sourceMappingURL=tool-search.js.map
|
package/dist/tool-search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-search.js","sourceRoot":"","sources":["../src/tool-search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-search.js","sourceRoot":"","sources":["../src/tool-search.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,iCAAiC,CAAC;AAEzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,aAAa,CAAC;AACrD,MAAM,CAAC,MAAM,2BAA2B,GAAG,YAAY,CAAC;AAOxD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAA2B,EAC3B,qBAA8B;IAE9B,IAAI,MAAkC,CAAC;IACvC,yBAAyB,EAAE,CAAC;QAC1B,GAAG,KAAK;QACR,YAAY,EAAE,CAAC,UAA0B,EAAE,EAAE;YAC3C,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;KACyB,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC/D,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC;IAChE,OAAO,qBAAqB;QAC1B,CAAC,CAAC;YACE,UAAU;YACV;gBACE,GAAG,UAAU;gBACb,IAAI,EAAE,2BAA2B;gBACjC,KAAK,EAAE,2BAA2B;aACnC;SACF;QACH,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AACnB,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,UAAU,CACxB,OAAyB,EACzB,iBAAoC;IAEpC,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,EAAE,EAAE,CACZ,OAAO,CACL,IAAI,KAAK,CAAC,EAAE,EAAE;QACZ,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ;YAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACtD,IAAI,QAAQ,KAAK,cAAc,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;gBAC/D,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAClE,CAAC;YACD,OAAO,CAAC,UAA0B,EAAE,EAAE,CACpC,KAAK,CAAC,IAAI,CACR,MAAM,EACN,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;gBAC3B,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE;gBACzC,CAAC,CAAC,UAAU,CACf,CAAC;QACN,CAAC;KACF,CAAC,CACH,CAAC;AACN,CAAC"}
|
|
@@ -91,11 +91,13 @@ to hide all authorized tools for a server, then optionally list exact tools in
|
|
|
91
91
|
or a sole `"*"` selector. `eager` wins when a tool matches both fields, but
|
|
92
92
|
neither field can authorize a tool excluded by `include`/`exclude`.
|
|
93
93
|
|
|
94
|
-
Deferred tools remain authorized and discoverable.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
the
|
|
94
|
+
Deferred tools remain authorized and discoverable. Recipes registers them with
|
|
95
|
+
Pi's `deferred` exposure and, when at least one connector or MCP tool is
|
|
96
|
+
deferred, activates Pi's `tool_search`. Calling it ranks the deferred tools by
|
|
97
|
+
the words in their names, descriptions and input schemas (BM25, default limit
|
|
98
|
+
8) and declares the best matches for the next model request. Pi records the
|
|
99
|
+
change in the session, so loaded tools survive resume. It never grants access
|
|
100
|
+
beyond the Recipe manifest and agent policy. Recipes also registers `mcp_search` as a
|
|
99
101
|
compatibility alias when MCP tools are deferred.
|
|
100
102
|
|
|
101
103
|
`defer` and `eager` are invalid in CLI mode. An omitted agent `mcp` block
|
package/docs/pi-extension.md
CHANGED
package/docs/recipe-judges.md
CHANGED
|
@@ -83,6 +83,45 @@ llm:
|
|
|
83
83
|
Unknown fields are errors at every level. The obsolete top-level `model:`
|
|
84
84
|
block is rejected.
|
|
85
85
|
|
|
86
|
+
## Judge types
|
|
87
|
+
|
|
88
|
+
`type` says when a judge runs:
|
|
89
|
+
|
|
90
|
+
- `online`, the default: at the end of every runtime conversation.
|
|
91
|
+
- `offline`: at the end of each eval trial. It has the same shape as `online`.
|
|
92
|
+
- `mission`: on a request leaving the sandbox, which a route in the Recipe's
|
|
93
|
+
`policies/routes.yaml` names. Its answers reach the Recipe's Cedar policy
|
|
94
|
+
before the request is allowed.
|
|
95
|
+
|
|
96
|
+
A mission judge asks yes-or-no questions instead of grading against a rubric, and
|
|
97
|
+
the platform chooses the model:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
name: booking
|
|
101
|
+
type: mission
|
|
102
|
+
description: Is this booking what the traveller asked for?
|
|
103
|
+
attrs: [city, check_in, check_out, total_cents]
|
|
104
|
+
questions:
|
|
105
|
+
requested:
|
|
106
|
+
instructions: >
|
|
107
|
+
Is the booking something the traveller asked for, or a natural part of
|
|
108
|
+
that trip?
|
|
109
|
+
criteria:
|
|
110
|
+
"true": The same destination and dates.
|
|
111
|
+
"false": Another place, other dates, or something they never asked for.
|
|
112
|
+
personal:
|
|
113
|
+
instructions: Did the traveller describe this part of the trip as personal?
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- `questions` maps between one and eight lowercase identifiers to questions;
|
|
117
|
+
each name is the field its answer is read as.
|
|
118
|
+
- `instructions` is non-empty text. `criteria` is optional and, when present,
|
|
119
|
+
describes both `true` and `false` in non-empty text.
|
|
120
|
+
- `attrs` lists the request attributes the judge sees, and is empty to show
|
|
121
|
+
them all.
|
|
122
|
+
- `on` and `llm` belong to `online` and `offline` judges and are rejected on a
|
|
123
|
+
mission judge; `questions` and `attrs` are rejected on the others.
|
|
124
|
+
|
|
86
125
|
### LLM settings
|
|
87
126
|
|
|
88
127
|
- `provider` is a 1-64 byte lowercase slug containing ASCII letters, digits,
|
|
@@ -141,4 +180,5 @@ Invalid Recipe content is reported through the normal diagnostics model. Judge
|
|
|
141
180
|
diagnostics use stable `judge.*` codes, Recipe-relative source paths, useful
|
|
142
181
|
help text, and deterministic ordering. YAML syntax failures use
|
|
143
182
|
`judge.yaml_malformed` and include a 1-based source span when the parser
|
|
144
|
-
provides one.
|
|
183
|
+
provides one. An invalid mission judge reports `judge.mission_invalid`, and a
|
|
184
|
+
`type` outside `online`, `offline` and `mission` reports `judge.type_invalid`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@introspection-ai/recipes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "The open format for vertical agents, built on Pi.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -88,17 +88,17 @@
|
|
|
88
88
|
"yaml": "^2.9.0"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
|
-
"@earendil-works/pi-agent-core": ">=0.
|
|
92
|
-
"@earendil-works/pi-ai": ">=0.
|
|
93
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
94
|
-
"@earendil-works/pi-tui": ">=0.
|
|
91
|
+
"@earendil-works/pi-agent-core": ">=1.1.0 <2.0.0",
|
|
92
|
+
"@earendil-works/pi-ai": ">=1.1.0 <2.0.0",
|
|
93
|
+
"@earendil-works/pi-coding-agent": ">=1.1.0 <2.0.0",
|
|
94
|
+
"@earendil-works/pi-tui": ">=1.1.0 <2.0.0",
|
|
95
95
|
"typebox": "*"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
-
"@earendil-works/pi-agent-core": "
|
|
99
|
-
"@earendil-works/pi-ai": "
|
|
100
|
-
"@earendil-works/pi-coding-agent": "
|
|
101
|
-
"@earendil-works/pi-tui": "
|
|
98
|
+
"@earendil-works/pi-agent-core": "1.1.0",
|
|
99
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
100
|
+
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
101
|
+
"@earendil-works/pi-tui": "1.1.0",
|
|
102
102
|
"@types/node": "^26.1.2",
|
|
103
103
|
"esbuild": "^0.28.1",
|
|
104
104
|
"typebox": "^1.0.56",
|
|
@@ -106,10 +106,10 @@
|
|
|
106
106
|
"vitest": "^5.0.1"
|
|
107
107
|
},
|
|
108
108
|
"optionalDependencies": {
|
|
109
|
-
"@introspection-ai/mcp-client-linux-x64": "0.
|
|
110
|
-
"@introspection-ai/mcp-client-linux-arm64": "0.
|
|
111
|
-
"@introspection-ai/mcp-client-darwin-arm64": "0.
|
|
112
|
-
"@introspection-ai/mcp-client-darwin-x64": "0.
|
|
109
|
+
"@introspection-ai/mcp-client-linux-x64": "0.32.0",
|
|
110
|
+
"@introspection-ai/mcp-client-linux-arm64": "0.32.0",
|
|
111
|
+
"@introspection-ai/mcp-client-darwin-arm64": "0.32.0",
|
|
112
|
+
"@introspection-ai/mcp-client-darwin-x64": "0.32.0"
|
|
113
113
|
},
|
|
114
114
|
"scripts": {
|
|
115
115
|
"build": "pnpm build:ts && pnpm build:native",
|