@seanhogg/builderforce-memory 2026.9.31 → 2026.9.32
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/evermind/index.d.ts +6 -0
- package/dist/evermind/index.d.ts.map +1 -1
- package/dist/evermind/index.js +5 -0
- package/dist/evermind/index.js.map +1 -1
- package/dist/evermind/runtime.d.ts +158 -0
- package/dist/evermind/runtime.d.ts.map +1 -0
- package/dist/evermind/runtime.js +236 -0
- package/dist/evermind/runtime.js.map +1 -0
- package/dist/evermind/textCoherence.d.ts +55 -0
- package/dist/evermind/textCoherence.d.ts.map +1 -0
- package/dist/evermind/textCoherence.js +218 -0
- package/dist/evermind/textCoherence.js.map +1 -0
- package/dist/evermind/toolCall.d.ts +175 -0
- package/dist/evermind/toolCall.d.ts.map +1 -0
- package/dist/evermind/toolCall.js +398 -0
- package/dist/evermind/toolCall.js.map +1 -0
- package/dist/evermind/wordLexicon.d.ts +103 -0
- package/dist/evermind/wordLexicon.d.ts.map +1 -0
- package/dist/evermind/wordLexicon.js +361 -0
- package/dist/evermind/wordLexicon.js.map +1 -0
- package/package.json +3 -3
- package/src/evermind/index.ts +48 -0
- package/src/evermind/runtime.ts +364 -0
- package/src/evermind/textCoherence.ts +248 -0
- package/src/evermind/toolCall.ts +523 -0
- package/src/evermind/wordLexicon.ts +395 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolCall — function calling for Evermind, a project's OWN SSM.
|
|
3
|
+
*
|
|
4
|
+
* Evermind used to REFUSE every tool-bearing request, on the reasoning that a head
|
|
5
|
+
* which generates raw text cannot emit `tool_calls` and that narrating a call is
|
|
6
|
+
* worse than an error. The refusal was right about the failure mode and wrong about
|
|
7
|
+
* the remedy: "can the architecture emit a JSON tool call?" is not a property of the
|
|
8
|
+
* weights at all, it is a property of the DECODING STRATEGY. A tiny under-trained
|
|
9
|
+
* head asked to free-generate `{"name":…,"arguments":{…}}` will indeed produce
|
|
10
|
+
* malformed junk — so this module never asks it to.
|
|
11
|
+
*
|
|
12
|
+
* The approach is schema-driven CONSTRAINED DECODING (the jsonformer/outlines shape):
|
|
13
|
+
* the JSON is ASSEMBLED HERE as a real JS object from the tool's JSON Schema, and the
|
|
14
|
+
* model is only ever consulted for the leaves it is qualified to fill:
|
|
15
|
+
*
|
|
16
|
+
* • a finite choice (which tool, an `enum`, a boolean) → every candidate is SCORED
|
|
17
|
+
* by likelihood and the argmax wins, so the answer is always a legal value;
|
|
18
|
+
* • a free value (a string, a number) → generated, then parsed/escaped by us.
|
|
19
|
+
*
|
|
20
|
+
* Structural validity is therefore guaranteed BY CONSTRUCTION — the braces, quotes,
|
|
21
|
+
* commas and key names are never sampled, so there is no such thing as a malformed
|
|
22
|
+
* Evermind tool call. `JSON.stringify` of a real object cannot be invalid JSON.
|
|
23
|
+
*
|
|
24
|
+
* What that does NOT buy is a good CHOICE, and this is where the original file's
|
|
25
|
+
* instinct is kept rather than discarded. A head that cannot tell `read_file` from
|
|
26
|
+
* `open_pull_request` will still pick one — structurally perfect, semantically a coin
|
|
27
|
+
* flip — and an agent loop acting on coin flips writes files for no reason. So the
|
|
28
|
+
* planner reports the MARGIN between its best and runner-up candidate, and the vendor
|
|
29
|
+
* refuses (400 → cascade) when the head is measurably guessing. Fitness to tool-call
|
|
30
|
+
* is now measured, the same way {@link ./textCoherence} measures fitness to answer in
|
|
31
|
+
* prose, instead of being assumed absent.
|
|
32
|
+
*
|
|
33
|
+
* Pure + port-based (no engine, no R2, no db imports) so the whole planner is unit
|
|
34
|
+
* testable against a fake decoder; the model-backed adapter lives in
|
|
35
|
+
* {@link ./runtime}.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Minimum separation (in mean log-prob) between the winning candidate and the
|
|
39
|
+
* runner-up for a choice to count as a DECISION rather than a coin flip.
|
|
40
|
+
*
|
|
41
|
+
* This is the tool-calling analogue of the coherence bar: it is what stops a
|
|
42
|
+
* structurally-perfect-but-meaningless call from driving an agent loop. Tuned low
|
|
43
|
+
* on purpose — the job is to catch a head with no preference at all (margins at
|
|
44
|
+
* ~1e-3), not to demand confidence a small model never shows.
|
|
45
|
+
*/
|
|
46
|
+
export const TOOL_CHOICE_MIN_MARGIN = 0.02;
|
|
47
|
+
/**
|
|
48
|
+
* The margin bar in force for a raw override (the host reads it from its own config —
|
|
49
|
+
* the gateway's `EVERMIND_TOOL_CHOICE_MIN_MARGIN`). ONE resolver, shared by the
|
|
50
|
+
* serve-time gate and the bench, so the bench can never report a bar the gateway is
|
|
51
|
+
* not applying.
|
|
52
|
+
*
|
|
53
|
+
* The default is an analytic placeholder and documented as one; the override is what
|
|
54
|
+
* lets it be CALIBRATED from logged margins without a deploy per candidate value.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveToolChoiceMinMargin(override) {
|
|
57
|
+
const text = override?.trim();
|
|
58
|
+
// Empty is ABSENT, checked before the numeric parse: `Number('')` is 0, not NaN, so
|
|
59
|
+
// a declared-but-unset variable — the normal shape of a secret nobody filled in —
|
|
60
|
+
// would otherwise set the bar to zero and silently turn the confidence gate off.
|
|
61
|
+
if (!text)
|
|
62
|
+
return TOOL_CHOICE_MIN_MARGIN;
|
|
63
|
+
const raw = Number(text);
|
|
64
|
+
// A non-numeric or negative override is ignored rather than obeyed: a bar of NaN
|
|
65
|
+
// compares false against everything, which is the same silent disabling.
|
|
66
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : TOOL_CHOICE_MIN_MARGIN;
|
|
67
|
+
}
|
|
68
|
+
/** Ceiling on calls emitted in ONE turn. Each additional call costs a full tool vote
|
|
69
|
+
* plus an argument fill, so this bounds the CPU an agent loop can spend on a single
|
|
70
|
+
* planning turn — and a head that wants more than this is not planning, it is
|
|
71
|
+
* looping. */
|
|
72
|
+
const MAX_PARALLEL_TOOL_CALLS = 4;
|
|
73
|
+
/** Hard caps so a hostile or recursive schema cannot spin the decoder. */
|
|
74
|
+
const MAX_SCHEMA_DEPTH = 4;
|
|
75
|
+
const MAX_ARRAY_ITEMS = 4;
|
|
76
|
+
const MAX_STRING_CHARS = 400;
|
|
77
|
+
const STRING_VALUE_TOKENS = 48;
|
|
78
|
+
const NUMBER_VALUE_TOKENS = 8;
|
|
79
|
+
/** The sentinel candidate representing "don't call a tool, just answer". Scored
|
|
80
|
+
* alongside the real tools in `auto` mode so one primitive makes the decision. */
|
|
81
|
+
const ANSWER_DIRECTLY = 'answer the user directly without calling a tool';
|
|
82
|
+
// ── Wire-shape normalization ─────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Normalize the OpenAI `tools` array into {@link NormalizedTool}s, accepting both the
|
|
85
|
+
* nested chat-completions shape (`{ type:'function', function:{…} }`) and the already
|
|
86
|
+
* flattened Responses shape (`{ type:'function', name, … }`) — the gateway carries
|
|
87
|
+
* both, and a vendor that understood only one would silently see zero tools.
|
|
88
|
+
* Entries without a usable name are dropped rather than half-registered.
|
|
89
|
+
*/
|
|
90
|
+
export function normalizeEvermindTools(tools) {
|
|
91
|
+
if (!Array.isArray(tools))
|
|
92
|
+
return [];
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const raw of tools) {
|
|
95
|
+
// A caller's `tools` array is untrusted wire data — a null/scalar entry must be
|
|
96
|
+
// skipped, not dereferenced.
|
|
97
|
+
if (!raw || typeof raw !== 'object')
|
|
98
|
+
continue;
|
|
99
|
+
const tool = raw;
|
|
100
|
+
const fn = (tool.function ?? tool);
|
|
101
|
+
const name = typeof fn.name === 'string' ? fn.name.trim() : '';
|
|
102
|
+
if (!name)
|
|
103
|
+
continue;
|
|
104
|
+
out.push({
|
|
105
|
+
name,
|
|
106
|
+
description: typeof fn.description === 'string' ? fn.description : '',
|
|
107
|
+
parameters: (fn.parameters && typeof fn.parameters === 'object' ? fn.parameters : { type: 'object' }),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the caller's `tool_choice` against the tools actually on offer. A pinned
|
|
114
|
+
* function that isn't in the list degrades to `required` rather than forcing a name
|
|
115
|
+
* the caller never declared (which would produce a call no agent loop can dispatch).
|
|
116
|
+
*/
|
|
117
|
+
export function resolveEvermindToolChoice(toolChoice, tools) {
|
|
118
|
+
if (tools.length === 0)
|
|
119
|
+
return { mode: 'none' };
|
|
120
|
+
if (toolChoice === 'none')
|
|
121
|
+
return { mode: 'none' };
|
|
122
|
+
if (toolChoice === 'required')
|
|
123
|
+
return { mode: 'required' };
|
|
124
|
+
if (toolChoice === 'auto' || toolChoice == null)
|
|
125
|
+
return { mode: 'auto' };
|
|
126
|
+
const pinned = toolChoice;
|
|
127
|
+
if (pinned && typeof pinned === 'object' && pinned.type === 'function') {
|
|
128
|
+
// Accept both the nested and flattened spellings, exactly as `normalizeEvermindTools` does.
|
|
129
|
+
const name = typeof pinned.function?.name === 'string' ? pinned.function.name
|
|
130
|
+
: typeof pinned.name === 'string' ? pinned.name
|
|
131
|
+
: '';
|
|
132
|
+
if (name && tools.some((t) => t.name === name))
|
|
133
|
+
return { mode: 'forced', name };
|
|
134
|
+
return { mode: 'required' };
|
|
135
|
+
}
|
|
136
|
+
return { mode: 'auto' };
|
|
137
|
+
}
|
|
138
|
+
// ── Prompt rendering ─────────────────────────────────────────────────────────
|
|
139
|
+
/** One line per tool: the signature the model is choosing between. */
|
|
140
|
+
function renderToolSignature(tool) {
|
|
141
|
+
const props = tool.parameters?.properties ?? {};
|
|
142
|
+
const required = new Set(tool.parameters?.required ?? []);
|
|
143
|
+
const args = Object.entries(props)
|
|
144
|
+
.map(([key, schema]) => `${key}${required.has(key) ? '' : '?'}: ${schemaTypeName(schema)}`)
|
|
145
|
+
.join(', ');
|
|
146
|
+
return `- ${tool.name}(${args})${tool.description ? ` — ${tool.description}` : ''}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The tool catalogue, rendered into the prompt so the head's choice is INFORMED by
|
|
150
|
+
* the descriptions rather than made from the tool names alone. Shared by the decision
|
|
151
|
+
* vote and every argument fill so the model sees one consistent context.
|
|
152
|
+
*/
|
|
153
|
+
export function renderToolsPreamble(tools) {
|
|
154
|
+
return `Available tools:\n${tools.map(renderToolSignature).join('\n')}`;
|
|
155
|
+
}
|
|
156
|
+
/** Human-readable type for a schema node, used in the rendered signatures. */
|
|
157
|
+
function schemaTypeName(schema) {
|
|
158
|
+
if (!schema)
|
|
159
|
+
return 'string';
|
|
160
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0)
|
|
161
|
+
return schema.enum.map((v) => JSON.stringify(v)).join('|');
|
|
162
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
163
|
+
if (type === 'array')
|
|
164
|
+
return `${schemaTypeName(schema.items)}[]`;
|
|
165
|
+
return type ?? 'string';
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Pick the most likely candidate and report the separation. This is the ONE place a
|
|
169
|
+
* finite choice is made — the tool vote, `enum` values and booleans all route through
|
|
170
|
+
* it, so "how does Evermind choose?" has a single answer and a single margin
|
|
171
|
+
* definition. A single candidate is unopposed (`Infinity`): there is nothing to
|
|
172
|
+
* second-guess, so it must not be judged as a low-confidence guess.
|
|
173
|
+
*/
|
|
174
|
+
function decide(decoder, prompt, candidates) {
|
|
175
|
+
if (candidates.length === 0)
|
|
176
|
+
throw new Error('decide() requires at least one candidate');
|
|
177
|
+
const first = candidates[0];
|
|
178
|
+
if (candidates.length === 1)
|
|
179
|
+
return { value: first.value, margin: Infinity };
|
|
180
|
+
let best = first;
|
|
181
|
+
let bestScore = -Infinity;
|
|
182
|
+
let runnerUp = -Infinity;
|
|
183
|
+
for (const candidate of candidates) {
|
|
184
|
+
const score = decoder.score(prompt, candidate.text);
|
|
185
|
+
if (score > bestScore) {
|
|
186
|
+
runnerUp = bestScore;
|
|
187
|
+
bestScore = score;
|
|
188
|
+
best = candidate;
|
|
189
|
+
}
|
|
190
|
+
else if (score > runnerUp) {
|
|
191
|
+
runnerUp = score;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { value: best.value, margin: bestScore - runnerUp };
|
|
195
|
+
}
|
|
196
|
+
// ── Argument filling ─────────────────────────────────────────────────────────
|
|
197
|
+
/** The prompt a leaf value is generated/scored against: conversation + catalogue +
|
|
198
|
+
* exactly which argument of which tool is being filled. */
|
|
199
|
+
function valuePrompt(base, tool, path, schema) {
|
|
200
|
+
const described = schema?.description ? ` (${schema.description})` : '';
|
|
201
|
+
return `${base}\nCalling tool: ${tool.name}\nValue for argument "${path}"${described}, as ${schemaTypeName(schema)}:`;
|
|
202
|
+
}
|
|
203
|
+
/** First line of a generated value, unquoted and bounded. Free text is never parsed
|
|
204
|
+
* as JSON — it is escaped by `JSON.stringify` at assembly time, so any bytes the
|
|
205
|
+
* head emits are representable. */
|
|
206
|
+
function cleanStringValue(raw, schema) {
|
|
207
|
+
const firstLine = (raw ?? '').split('\n').find((line) => line.trim().length > 0) ?? '';
|
|
208
|
+
const unquoted = firstLine.trim().replace(/^["'`]+/, '').replace(/["'`,]+$/, '').trim();
|
|
209
|
+
const cap = Math.min(schema?.maxLength ?? MAX_STRING_CHARS, MAX_STRING_CHARS);
|
|
210
|
+
return unquoted.slice(0, cap);
|
|
211
|
+
}
|
|
212
|
+
/** First numeric literal in a generated value, clamped to the schema's bounds.
|
|
213
|
+
* Falls back to `default` → `minimum` → 0 when the head emitted no number at all. */
|
|
214
|
+
function cleanNumberValue(raw, schema, integer) {
|
|
215
|
+
const match = (raw ?? '').match(/-?\d+(\.\d+)?/);
|
|
216
|
+
const fallback = typeof schema?.default === 'number' ? schema.default : schema?.minimum ?? 0;
|
|
217
|
+
let value = match ? Number(match[0]) : fallback;
|
|
218
|
+
if (!Number.isFinite(value))
|
|
219
|
+
value = fallback;
|
|
220
|
+
if (typeof schema?.minimum === 'number')
|
|
221
|
+
value = Math.max(value, schema.minimum);
|
|
222
|
+
if (typeof schema?.maximum === 'number')
|
|
223
|
+
value = Math.min(value, schema.maximum);
|
|
224
|
+
return integer ? Math.round(value) : value;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Fill ONE schema node by dispatching on its type — the recursive core of the
|
|
228
|
+
* constrained decoder. Every branch returns a value that is legal for the node, so
|
|
229
|
+
* the assembled object always validates against the shape the tool declared.
|
|
230
|
+
*/
|
|
231
|
+
function fillValue(decoder, base, tool, path, schema, depth, margins) {
|
|
232
|
+
// Depth guard: a self-referential schema would otherwise recurse forever. A string
|
|
233
|
+
// is the universally representable leaf, so that is where we bottom out.
|
|
234
|
+
if (depth > MAX_SCHEMA_DEPTH) {
|
|
235
|
+
return cleanStringValue(decoder.generate(valuePrompt(base, tool, path, schema), STRING_VALUE_TOKENS), schema);
|
|
236
|
+
}
|
|
237
|
+
// An `enum` is a finite choice regardless of the declared type — vote, never generate.
|
|
238
|
+
if (Array.isArray(schema?.enum) && schema.enum.length > 0) {
|
|
239
|
+
const decision = decide(decoder, valuePrompt(base, tool, path, schema), schema.enum.map((value) => ({ value, text: String(value) })));
|
|
240
|
+
margins.push(decision.margin);
|
|
241
|
+
return decision.value;
|
|
242
|
+
}
|
|
243
|
+
const type = Array.isArray(schema?.type) ? schema.type[0] : schema?.type;
|
|
244
|
+
switch (type) {
|
|
245
|
+
case 'boolean': {
|
|
246
|
+
const decision = decide(decoder, valuePrompt(base, tool, path, schema), [
|
|
247
|
+
{ value: true, text: 'true' },
|
|
248
|
+
{ value: false, text: 'false' },
|
|
249
|
+
]);
|
|
250
|
+
margins.push(decision.margin);
|
|
251
|
+
return decision.value;
|
|
252
|
+
}
|
|
253
|
+
case 'integer':
|
|
254
|
+
return cleanNumberValue(decoder.generate(valuePrompt(base, tool, path, schema), NUMBER_VALUE_TOKENS), schema, true);
|
|
255
|
+
case 'number':
|
|
256
|
+
return cleanNumberValue(decoder.generate(valuePrompt(base, tool, path, schema), NUMBER_VALUE_TOKENS), schema, false);
|
|
257
|
+
case 'object':
|
|
258
|
+
return fillObject(decoder, base, tool, path, schema, depth, margins);
|
|
259
|
+
case 'array': {
|
|
260
|
+
const items = [];
|
|
261
|
+
const min = Math.max(0, schema?.minItems ?? 0);
|
|
262
|
+
const max = Math.min(schema?.maxItems ?? MAX_ARRAY_ITEMS, MAX_ARRAY_ITEMS);
|
|
263
|
+
for (let i = 0; i < max; i++) {
|
|
264
|
+
if (i >= min) {
|
|
265
|
+
// Length is itself a decision: keep going only while the head prefers it.
|
|
266
|
+
const more = decide(decoder, `${valuePrompt(base, tool, `${path}[${i}]`, schema?.items)}\nIs there another item?`, [
|
|
267
|
+
{ value: true, text: 'yes' },
|
|
268
|
+
{ value: false, text: 'no' },
|
|
269
|
+
]);
|
|
270
|
+
if (!more.value)
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
items.push(fillValue(decoder, base, tool, `${path}[${i}]`, schema?.items, depth + 1, margins));
|
|
274
|
+
}
|
|
275
|
+
return items;
|
|
276
|
+
}
|
|
277
|
+
case 'null':
|
|
278
|
+
return null;
|
|
279
|
+
default:
|
|
280
|
+
// `string` and anything unrecognised. Generating text is always safe: it is
|
|
281
|
+
// escaped at assembly, so an unknown schema keyword degrades to a filled
|
|
282
|
+
// string rather than to invalid JSON.
|
|
283
|
+
return cleanStringValue(decoder.generate(valuePrompt(base, tool, path, schema), STRING_VALUE_TOKENS), schema);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Fill an object node. Required properties are always present (that is what
|
|
288
|
+
* `required` means); each optional property is a yes/no decision, so the head can
|
|
289
|
+
* produce a minimal call instead of being forced to invent every argument.
|
|
290
|
+
*/
|
|
291
|
+
function fillObject(decoder, base, tool, path, schema, depth, margins) {
|
|
292
|
+
const properties = schema?.properties ?? {};
|
|
293
|
+
const required = new Set(schema?.required ?? []);
|
|
294
|
+
const out = {};
|
|
295
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
296
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
297
|
+
if (!required.has(key)) {
|
|
298
|
+
const include = decide(decoder, `${valuePrompt(base, tool, childPath, propSchema)}\nShould this optional argument be provided?`, [
|
|
299
|
+
{ value: true, text: 'yes' },
|
|
300
|
+
{ value: false, text: 'no' },
|
|
301
|
+
]);
|
|
302
|
+
if (!include.value)
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
out[key] = fillValue(decoder, base, tool, childPath, propSchema, depth + 1, margins);
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Choose a tool and fill its arguments under the caller's `tool_choice` contract.
|
|
311
|
+
*
|
|
312
|
+
* The decision and every argument come from the model; the JSON structure comes from
|
|
313
|
+
* the schema. Returns `{ call: null }` only in `auto` mode, and only when answering
|
|
314
|
+
* directly out-scored every tool — `required`/`forced` always produce a call.
|
|
315
|
+
*/
|
|
316
|
+
export function planEvermindToolCall(decoder, basePrompt, tools, choice) {
|
|
317
|
+
if (choice.mode === 'none' || tools.length === 0)
|
|
318
|
+
return { call: null, calls: [], margin: Infinity };
|
|
319
|
+
const base = `${basePrompt}\n\n${renderToolsPreamble(tools)}`;
|
|
320
|
+
const margins = [];
|
|
321
|
+
// 1) Which tool (or none)? `forced` skips the vote entirely — the caller already decided.
|
|
322
|
+
let tool;
|
|
323
|
+
let choiceMargin = Infinity;
|
|
324
|
+
if (choice.mode === 'forced') {
|
|
325
|
+
tool = tools.find((t) => t.name === choice.name);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// In `auto`, "answer directly" competes as a peer candidate, so one vote settles
|
|
329
|
+
// both questions (call vs. answer, and which call) with one comparable margin.
|
|
330
|
+
const candidates = tools.map((t) => ({
|
|
331
|
+
value: t,
|
|
332
|
+
text: `${t.name} — ${t.description || t.name}`,
|
|
333
|
+
}));
|
|
334
|
+
if (choice.mode === 'auto')
|
|
335
|
+
candidates.push({ value: null, text: ANSWER_DIRECTLY });
|
|
336
|
+
const decision = decide(decoder, `${base}\nThe best next action is: `, candidates);
|
|
337
|
+
choiceMargin = decision.margin;
|
|
338
|
+
if (!decision.value)
|
|
339
|
+
return { call: null, calls: [], margin: choiceMargin };
|
|
340
|
+
tool = decision.value;
|
|
341
|
+
}
|
|
342
|
+
// A `forced` name is validated by `resolveEvermindToolChoice`, so this is a
|
|
343
|
+
// belt-and-braces fallback rather than a reachable branch.
|
|
344
|
+
if (!tool)
|
|
345
|
+
return { call: null, calls: [], margin: choiceMargin };
|
|
346
|
+
// 2) Fill the arguments object against the tool's own schema.
|
|
347
|
+
const calls = [
|
|
348
|
+
{ name: tool.name, arguments: fillObject(decoder, base, tool, '', tool.parameters, 0, margins) },
|
|
349
|
+
];
|
|
350
|
+
// 3) Additional PARALLEL calls. `forced` names exactly one tool, so it stops here —
|
|
351
|
+
// asking for more would emit calls the caller never requested. Otherwise the head is
|
|
352
|
+
// asked whether it wants another, exactly as the array filler asks whether a list has
|
|
353
|
+
// another item, so a model with nothing more to do still emits exactly one call.
|
|
354
|
+
if (choice.mode !== 'forced') {
|
|
355
|
+
while (calls.length < MAX_PARALLEL_TOOL_CALLS) {
|
|
356
|
+
const rendered = calls.map((c) => `${c.name}(${JSON.stringify(c.arguments)})`).join('\n');
|
|
357
|
+
const more = decide(decoder, `${base}\nAlready calling:\n${rendered}\nIs there another tool call to make?`, [
|
|
358
|
+
{ value: true, text: 'yes' },
|
|
359
|
+
{ value: false, text: 'no' },
|
|
360
|
+
]);
|
|
361
|
+
// A TIE means no. `decide` breaks ties by candidate order, so requiring a
|
|
362
|
+
// positive margin is what keeps "one call" the default: an indifferent head
|
|
363
|
+
// must not have a second call read into its silence.
|
|
364
|
+
if (!more.value || !(more.margin > 0))
|
|
365
|
+
break;
|
|
366
|
+
// Counted ONLY when the vote said yes. A head that is indifferent about making
|
|
367
|
+
// a SECOND call has guessed nothing — folding that indifference into the plan
|
|
368
|
+
// margin would let an optional extra drag a confidently-chosen first call below
|
|
369
|
+
// the confidence gate and get the whole turn refused. A coin-flip "yes",
|
|
370
|
+
// though, does put a guessed call in the plan, and counts.
|
|
371
|
+
margins.push(more.margin);
|
|
372
|
+
const next = decide(decoder, `${base}\nAlready calling:\n${rendered}\nThe next tool to call is: `, tools.map((t) => ({ value: t, text: `${t.name} — ${t.description || t.name}` })));
|
|
373
|
+
margins.push(next.margin);
|
|
374
|
+
if (!next.value)
|
|
375
|
+
break;
|
|
376
|
+
const nextCall = {
|
|
377
|
+
name: next.value.name,
|
|
378
|
+
arguments: fillObject(decoder, base, next.value, '', next.value.parameters, 0, margins),
|
|
379
|
+
};
|
|
380
|
+
// An identical repeat is the head LOOPING, not planning parallel work. Stop
|
|
381
|
+
// rather than emit a duplicate an agent loop would faithfully execute twice.
|
|
382
|
+
const duplicate = calls.some((c) => c.name === nextCall.name && JSON.stringify(c.arguments) === JSON.stringify(nextCall.arguments));
|
|
383
|
+
if (duplicate)
|
|
384
|
+
break;
|
|
385
|
+
calls.push(nextCall);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// The reported margin is the WEAKEST link in the whole plan: a confident tool
|
|
389
|
+
// choice whose enum argument was a coin flip is still a coin flip overall.
|
|
390
|
+
const weakest = Math.min(choiceMargin, ...margins);
|
|
391
|
+
return { call: calls[0], calls, margin: Number.isFinite(weakest) ? weakest : Infinity };
|
|
392
|
+
}
|
|
393
|
+
/** Wrap a planned call in the OpenAI `tool_calls` wire shape. `id` is supplied by the
|
|
394
|
+
* caller so the completion builder controls id generation (and tests stay stable). */
|
|
395
|
+
export function toOpenAIToolCall(call, id) {
|
|
396
|
+
return { id, type: 'function', function: { name: call.name, arguments: JSON.stringify(call.arguments) } };
|
|
397
|
+
}
|
|
398
|
+
//# sourceMappingURL=toolCall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolCall.js","sourceRoot":"","sources":["../../src/evermind/toolCall.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wordLexicon — a compact, zero-dependency lexicon + language guard used by the text
|
|
3
|
+
* coherence gate to catch the ONE failure mode the structural checks cannot see: an
|
|
4
|
+
* under-trained byte-BPE head emitting fluent-SHAPED prose made of INVENTED WORDS that
|
|
5
|
+
* balances its punctuation and never repeats itself ("…oredionisiing chats code related
|
|
6
|
+
* tot, bound reposea this inatic exie. The cainstiel.ts, ore…").
|
|
7
|
+
*
|
|
8
|
+
* Every structural signal (repetition, dominant-token collapse, replacement chars,
|
|
9
|
+
* orphaned delimiters) is blind to that text, and — measured on real samples —
|
|
10
|
+
* no dictionary-free feature separates it: long-token share, type-token ratio and
|
|
11
|
+
* character-cluster anomalies all score German/technical prose *worse* than the
|
|
12
|
+
* gibberish. So this module supplies the missing signal: **is this token a word?**
|
|
13
|
+
*
|
|
14
|
+
* Three deliberate design constraints, because a false positive here silently drops a
|
|
15
|
+
* legitimate reply:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Language guard.** The lexicon is English-only, so it is consulted ONLY when the
|
|
18
|
+
* text is Latin-script AND English wins the function-word vote against es/fr/de/pt/it.
|
|
19
|
+
* A Spanish, French, German or Chinese reply is never judged by it. See
|
|
20
|
+
* {@link detectLatinLanguage}.
|
|
21
|
+
* 2. **Small lexicon + generous escapes.** ~2.5k high-frequency stems (plus suffix
|
|
22
|
+
* stripping) can't possibly cover domain jargon, so a token also counts as known
|
|
23
|
+
* when it REPEATS in the text or appears in the caller's context (the prompt/question).
|
|
24
|
+
* Real jargon echoes the question or recurs; invented gibberish words are each
|
|
25
|
+
* different — that asymmetry is the observed signature.
|
|
26
|
+
* 3. **Code is excluded, not judged.** Identifiers, paths, URLs, numbers and fenced
|
|
27
|
+
* blocks are stripped before scoring — they are legitimately non-words.
|
|
28
|
+
*
|
|
29
|
+
* Zero-dep and pure (no db/env/engine imports) so it can sit under `textCoherence`,
|
|
30
|
+
* which every surface imports.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Is `word` a plausible English word — either present in the lexicon directly, or a
|
|
34
|
+
* regular inflection of a stem that is? Lowercase input; punctuation already stripped.
|
|
35
|
+
* Deliberately GENEROUS (it decides whether to *accuse* text of being gibberish).
|
|
36
|
+
*/
|
|
37
|
+
export declare function isKnownEnglishWord(word: string): boolean;
|
|
38
|
+
/** The verdict of the language guard — which Latin language won, and how strongly. */
|
|
39
|
+
export interface LanguageVerdict {
|
|
40
|
+
/** Winning language code, or null when the text isn't Latin-script or no language wins. */
|
|
41
|
+
language: string | null;
|
|
42
|
+
/** Share of tokens that are function words of the winning language (0..1). */
|
|
43
|
+
share: number;
|
|
44
|
+
/** Share of tokens that are function words of ANY supported language (0..1). */
|
|
45
|
+
anyShare: number;
|
|
46
|
+
/** True when the text is predominantly Latin-script (so the guard even applies). */
|
|
47
|
+
latin: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* True when English is the LEADING candidate — no other supported language scored
|
|
50
|
+
* strictly more function-word hits. This, not {@link language}, is what gates the
|
|
51
|
+
* English lexicon, because the two things it must separate are "a genuine Spanish
|
|
52
|
+
* reply" and "English-shaped gibberish", and only the former votes decisively for
|
|
53
|
+
* another language. Gibberish sprays short tokens that happen to be Spanish or
|
|
54
|
+
* French function words (`se`, `te`, `al`, `mis`), which is enough to TIE the vote
|
|
55
|
+
* or squeak past English — and treating that ambiguity as "not English, don't
|
|
56
|
+
* judge it" is exactly how invented-word output escapes the dictionary.
|
|
57
|
+
*/
|
|
58
|
+
englishLeads: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Decide which supported Latin-script language a text is in, by function-word vote.
|
|
62
|
+
* Returns `language: null` for non-Latin scripts (CJK, Cyrillic, Arabic, …) and when
|
|
63
|
+
* no language clears the floor — in both cases the English lexicon must NOT be applied.
|
|
64
|
+
*/
|
|
65
|
+
export declare function detectLatinLanguage(words: readonly string[], text: string): LanguageVerdict;
|
|
66
|
+
/**
|
|
67
|
+
* Is this token a function word in ANY supported Latin-script language?
|
|
68
|
+
*
|
|
69
|
+
* Used to tell CONTENT words from grammar. Repetition is what function words are FOR
|
|
70
|
+
* ("the … the … the"), so counting them as evidence of a collapsed head is a category
|
|
71
|
+
* error — and the length-based proxy that stood in for this (any token of 3+ letters)
|
|
72
|
+
* quietly classified `the`, `and`, `for`, `was` and `not` as content.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isFunctionWord(word: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Is this raw token code rather than prose? Used to EXCLUDE tokens from word scoring —
|
|
77
|
+
* `projectEvermindRef`, `api/src/foo.ts`, `v2.1`, `https://…` are all legitimately
|
|
78
|
+
* absent from any dictionary.
|
|
79
|
+
*/
|
|
80
|
+
export declare function isCodeishToken(raw: string): boolean;
|
|
81
|
+
/** The measured outcome of {@link scoreEnglishWordiness}. */
|
|
82
|
+
export interface WordinessScore {
|
|
83
|
+
/** True when the scorer actually ran (English text, enough eligible tokens). */
|
|
84
|
+
scored: boolean;
|
|
85
|
+
/** Eligible content tokens considered. */
|
|
86
|
+
eligible: number;
|
|
87
|
+
/** How many of them matched nothing — not the lexicon, not the context, not a repeat. */
|
|
88
|
+
unknown: number;
|
|
89
|
+
/** unknown / eligible (0 when not scored). */
|
|
90
|
+
unknownShare: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Score what fraction of a text's CONTENT words are unrecognisable. Only meaningful for
|
|
94
|
+
* confidently-English text — the caller must consult {@link detectLatinLanguage} first.
|
|
95
|
+
*
|
|
96
|
+
* A token counts as KNOWN when any of these hold, in order of cost:
|
|
97
|
+
* - it is code-ish (identifier / path / acronym) → excluded from scoring entirely;
|
|
98
|
+
* - the lexicon recognises it (with inflection stripping);
|
|
99
|
+
* - it appears in `context` (the prompt/question) — domain jargon echoes the ask;
|
|
100
|
+
* - it occurs more than once in the text — real jargon recurs, invented words don't.
|
|
101
|
+
*/
|
|
102
|
+
export declare function scoreEnglishWordiness(words: readonly string[], rawTokens: readonly string[], context?: string): WordinessScore;
|
|
103
|
+
//# sourceMappingURL=wordLexicon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wordLexicon.d.ts","sourceRoot":"","sources":["../../src/evermind/wordLexicon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAqJH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxD;AA4BD,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC9B,2FAA2F;IAC3F,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;;;;OASG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAYD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CA6C3F;AAMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAKD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,MAAM,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CA8BhB"}
|