@orkestrel/agent 0.0.35 → 0.0.36
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 +9 -2
- package/dist/src/core/index.cjs +1527 -515
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1739 -227
- package/dist/src/core/index.d.ts +1739 -227
- package/dist/src/core/index.js +1490 -517
- package/dist/src/core/index.js.map +1 -1
- package/package.json +20 -20
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
3
|
let _orkestrel_budget = require("@orkestrel/budget");
|
|
4
|
+
let _orkestrel_tool = require("@orkestrel/tool");
|
|
4
5
|
let _orkestrel_ndjson = require("@orkestrel/ndjson");
|
|
5
6
|
let _orkestrel_timeout = require("@orkestrel/timeout");
|
|
6
7
|
let _orkestrel_database = require("@orkestrel/database");
|
|
7
8
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
8
|
-
let _orkestrel_tool = require("@orkestrel/tool");
|
|
9
9
|
let _orkestrel_workspace = require("@orkestrel/workspace");
|
|
10
10
|
let _orkestrel_queue = require("@orkestrel/queue");
|
|
11
11
|
let _orkestrel_workflow = require("@orkestrel/workflow");
|
|
@@ -105,6 +105,23 @@ var toolCallShape = (0, _orkestrel_contract.objectShape)({
|
|
|
105
105
|
arguments: (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)())
|
|
106
106
|
});
|
|
107
107
|
/**
|
|
108
|
+
* Describes a message input's JSON projection: a {@link messageShape} with no `id`, which the
|
|
109
|
+
* storing conversation mints.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* A conversation's `add` checks a hoisted hand guard on every message and reads this shape's `audit`
|
|
113
|
+
* only to fill a refusal's faults. A job's seed messages cross it at `build`. The role literal admits
|
|
114
|
+
* `system`, so the conversation's `ROLE` refusal explains that case.
|
|
115
|
+
*/
|
|
116
|
+
var messageInputShape = (0, _orkestrel_contract.objectShape)({
|
|
117
|
+
role: (0, _orkestrel_contract.literalShape)(MESSAGE_ROLES),
|
|
118
|
+
content: (0, _orkestrel_contract.stringShape)(),
|
|
119
|
+
calls: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(toolCallShape)),
|
|
120
|
+
call: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
|
|
121
|
+
images: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())),
|
|
122
|
+
thinking: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)())
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
108
125
|
* Describes a conversation message's JSON wire projection.
|
|
109
126
|
*
|
|
110
127
|
* @remarks
|
|
@@ -121,20 +138,83 @@ var messageShape = (0, _orkestrel_contract.objectShape)({
|
|
|
121
138
|
thinking: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)())
|
|
122
139
|
});
|
|
123
140
|
/**
|
|
141
|
+
* Describes the text or JSON a judge reads: a non-empty string, a non-empty JSON array, or a JSON record.
|
|
142
|
+
*
|
|
143
|
+
* @remarks
|
|
144
|
+
* A shape has no `minProperties`, so this one admits the empty record that `isJudgeEntry` refuses;
|
|
145
|
+
* the guard is the door, and this shape only fills a refusal's faults.
|
|
146
|
+
*/
|
|
147
|
+
var judgeEntryShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { min: 1 }), (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)()));
|
|
148
|
+
/**
|
|
149
|
+
* Describes a judge question: one exact object per form, the schema an LLM-facing wire can publish.
|
|
150
|
+
*
|
|
151
|
+
* @remarks
|
|
152
|
+
* `isJudgeQuestion` is the door; this shape fills a refusal's faults. A choice needing at least 2
|
|
153
|
+
* options is the guard's alone, because a shape has no `minProperties`.
|
|
154
|
+
*/
|
|
155
|
+
var judgeQuestionShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
|
|
156
|
+
form: (0, _orkestrel_contract.literalShape)(["choice"]),
|
|
157
|
+
instructions: (0, _orkestrel_contract.optionalShape)(judgeEntryShape),
|
|
158
|
+
criteria: (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.nullableShape)(judgeEntryShape))
|
|
159
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
160
|
+
form: (0, _orkestrel_contract.literalShape)(["score"]),
|
|
161
|
+
instructions: (0, _orkestrel_contract.optionalShape)(judgeEntryShape),
|
|
162
|
+
criteria: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.nullableShape)(judgeEntryShape), { min: 2 })
|
|
163
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
164
|
+
form: (0, _orkestrel_contract.literalShape)(["noul"]),
|
|
165
|
+
instructions: (0, _orkestrel_contract.optionalShape)(judgeEntryShape),
|
|
166
|
+
criteria: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
|
|
167
|
+
true: (0, _orkestrel_contract.optionalShape)(judgeEntryShape),
|
|
168
|
+
false: (0, _orkestrel_contract.optionalShape)(judgeEntryShape)
|
|
169
|
+
}))
|
|
170
|
+
}));
|
|
171
|
+
/**
|
|
172
|
+
* Describes a judge answer: one exact object per form, each probability in `[0, 1]`.
|
|
173
|
+
*
|
|
174
|
+
* @remarks
|
|
175
|
+
* The ties an answer holds to its question run at the write doors through `scanJudgmentTies`, which a
|
|
176
|
+
* shape over the answer alone cannot state.
|
|
177
|
+
*/
|
|
178
|
+
var judgeAnswerShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
|
|
179
|
+
form: (0, _orkestrel_contract.literalShape)(["choice"]),
|
|
180
|
+
probabilities: (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.numberShape)({
|
|
181
|
+
min: 0,
|
|
182
|
+
max: 1
|
|
183
|
+
}))
|
|
184
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
185
|
+
form: (0, _orkestrel_contract.literalShape)(["score"]),
|
|
186
|
+
probabilities: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.numberShape)({
|
|
187
|
+
min: 0,
|
|
188
|
+
max: 1
|
|
189
|
+
}))
|
|
190
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
191
|
+
form: (0, _orkestrel_contract.literalShape)(["noul"]),
|
|
192
|
+
noul: (0, _orkestrel_contract.numberShape)({
|
|
193
|
+
min: 0,
|
|
194
|
+
max: 1
|
|
195
|
+
})
|
|
196
|
+
}));
|
|
197
|
+
/**
|
|
124
198
|
* Describes a token usage record's JSON wire projection — its `prompt`, `completion`, and `total`
|
|
125
199
|
* counts, shared by the provider result and the agent frame wires.
|
|
126
200
|
*
|
|
127
201
|
* @remarks
|
|
128
|
-
* The guard refuses extra members; the contract parser drops them.
|
|
202
|
+
* Each count is a nonnegative number. The guard refuses extra members; the contract parser drops them.
|
|
129
203
|
*/
|
|
130
204
|
var tokenUsageShape = (0, _orkestrel_contract.objectShape)({
|
|
131
|
-
prompt: (0, _orkestrel_contract.numberShape)(),
|
|
132
|
-
completion: (0, _orkestrel_contract.numberShape)(),
|
|
133
|
-
total: (0, _orkestrel_contract.numberShape)()
|
|
205
|
+
prompt: (0, _orkestrel_contract.numberShape)({ min: 0 }),
|
|
206
|
+
completion: (0, _orkestrel_contract.numberShape)({ min: 0 }),
|
|
207
|
+
total: (0, _orkestrel_contract.numberShape)({ min: 0 })
|
|
134
208
|
});
|
|
135
209
|
// src/core/contracts.ts
|
|
136
210
|
/** Validates and projects conversation messages at a JSON wire boundary. */
|
|
137
211
|
var messageContract = (0, _orkestrel_contract.createContract)(messageShape);
|
|
212
|
+
/** Reads the faults of a refused judge question. */
|
|
213
|
+
var judgeQuestionContract = (0, _orkestrel_contract.createContract)(judgeQuestionShape);
|
|
214
|
+
/** Reads the faults of a refused judge answer. */
|
|
215
|
+
var judgeAnswerContract = (0, _orkestrel_contract.createContract)(judgeAnswerShape);
|
|
216
|
+
/** Reads the faults of a refused message input and validates a job's seed messages. */
|
|
217
|
+
var messageInputContract = (0, _orkestrel_contract.createContract)(messageInputShape);
|
|
138
218
|
// src/core/cloners.ts
|
|
139
219
|
/**
|
|
140
220
|
* Owns a value by serializing it to JSON and parsing the text, so the copy shares nothing with its source.
|
|
@@ -281,6 +361,168 @@ function stripThinking(messages, replay) {
|
|
|
281
361
|
});
|
|
282
362
|
}
|
|
283
363
|
/**
|
|
364
|
+
* Estimates the context-token footprint of a string — the deterministic `ceil(length / 4)`
|
|
365
|
+
* character heuristic {@link import('./agents/helpers.js').estimateMessages} sums over a conversation's
|
|
366
|
+
* messages (the default context-budget estimator).
|
|
367
|
+
*
|
|
368
|
+
* @remarks
|
|
369
|
+
* Approximates `ceil(length / 4)` (4 characters per token), so the same input always yields the same estimate (no model round-trip).
|
|
370
|
+
* Empty text is `0`. This is a planning heuristic for reasoning about how much a turn's
|
|
371
|
+
* messages cost the next request, not an exact tokenizer count — it never calls a provider,
|
|
372
|
+
* so the agent layer stays provider-agnostic and synchronous where it can be.
|
|
373
|
+
*
|
|
374
|
+
* @param text - The text to estimate (a section summary, a message's content)
|
|
375
|
+
* @returns The estimated token count (`ceil(text.length / 4)`; `0` for empty text)
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* estimateTokens('') // 0
|
|
380
|
+
* estimateTokens('hello') // 2 (ceil(5 / 4))
|
|
381
|
+
* estimateTokens('a'.repeat(40)) // 10
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
function estimateTokens(text) {
|
|
385
|
+
return Math.ceil(text.length / 4);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Describes a snapshot a store refuses, naming its id, the first fault, and what a host does about it.
|
|
389
|
+
*
|
|
390
|
+
* @remarks
|
|
391
|
+
* A store's row is the host's table, so the text tells the host to read the row itself and rewrite it,
|
|
392
|
+
* or to delete it, rather than reading a refused row as absent, which would let `open` overwrite it.
|
|
393
|
+
* The id and each fault segment are JSON-encoded and length-capped.
|
|
394
|
+
*
|
|
395
|
+
* @param noun - What the snapshot holds, such as `conversation`
|
|
396
|
+
* @param id - The id the row is keyed by
|
|
397
|
+
* @param faults - The faults of the snapshot's shape
|
|
398
|
+
* @returns The refusal's text
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* describeSnapshotRefusal('conversation', 'c1', [{ reason: 'extra', path: ['summary'] }])
|
|
402
|
+
* // 'conversation "c1" holds a snapshot this version refuses (extra key "summary" at the root; ...'
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
function describeSnapshotRefusal(noun, id, faults) {
|
|
406
|
+
const [first] = faults;
|
|
407
|
+
const path = first === void 0 ? [] : (0, _orkestrel_contract.isString)(first.path) ? [first.path] : first.path;
|
|
408
|
+
const held = (0, _orkestrel_contract.preview)(id);
|
|
409
|
+
return `${noun} ${held} holds a snapshot this version refuses (${first?.reason === "extra" ? `extra key ${(0, _orkestrel_contract.preview)(path.at(-1))} at ${path.length === 1 ? "the root" : (0, _orkestrel_contract.preview)(path.slice(0, -1).join("."))}` : first === void 0 ? "a value its shape cannot name" : `${first.reason} at ${path.length === 0 ? "the root" : (0, _orkestrel_contract.preview)(path.join("."))}`}; a snapshot written before this change can carry it); read the row through your own table and ${first?.reason === "extra" ? "rewrite it without the key" : "rewrite it to the declared shape"}, or delete it with store.delete(${held})`;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Describes why a judge question refuses, in the text both of its doors throw.
|
|
413
|
+
*
|
|
414
|
+
* @remarks
|
|
415
|
+
* The rule travels in the text because the question shape cannot state it: a record has no minimum
|
|
416
|
+
* size, so a single-option choice refuses at the guard with no fault for the list to name.
|
|
417
|
+
*
|
|
418
|
+
* @param id - The question's id
|
|
419
|
+
* @param faults - What the question shape's audit found
|
|
420
|
+
* @param schema - The question shape's JSON Schema
|
|
421
|
+
* @returns The refusal text, with the fault list on the lines after it
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* describeQuestionRefusal('team', [], judgeQuestionContract.schema)
|
|
425
|
+
* // 'question team is malformed; a choice names two or more criteria, a score two or more levels, and a noul only true and false'
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
function describeQuestionRefusal(id, faults, schema) {
|
|
429
|
+
const refusal = `question ${id} is malformed; a choice names two or more criteria, a score two or more levels, and a noul only true and false`;
|
|
430
|
+
return faults.length === 0 ? refusal : `${refusal}\n${(0, _orkestrel_contract.describeFaults)(faults, schema)}`;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Scans what a judge's answer must hold to its question: the same form, every probability in
|
|
434
|
+
* `[0, 1]`, a choice's keys equal to the criteria keys, and a score as long as its levels.
|
|
435
|
+
*
|
|
436
|
+
* @remarks
|
|
437
|
+
* These ties run where a judgment is written, at `JudgmentManager.add` and the `AgentJudge` result
|
|
438
|
+
* ingestion. A read of a stored record checks only exact keys, literal forms, and number types,
|
|
439
|
+
* because records written before the ties can miss them.
|
|
440
|
+
*
|
|
441
|
+
* @param question - The question that was asked
|
|
442
|
+
* @param answer - The answer the judge gave
|
|
443
|
+
* @returns Every broken tie as a fault, or an empty list
|
|
444
|
+
* @example
|
|
445
|
+
* ```ts
|
|
446
|
+
* scanJudgmentTies({ form: 'noul' }, { form: 'noul', noul: 2 })
|
|
447
|
+
* // [{ reason: 'constraint', path: ['answer', 'noul'], expected: 'number', constraint: 'max', limit: 1, received: '2' }]
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
function scanJudgmentTies(question, answer) {
|
|
451
|
+
if (question.form === "noul" && answer.form === "noul") return scanProbability(answer.noul, ["answer", "noul"]);
|
|
452
|
+
if (question.form === "choice" && answer.form === "choice") {
|
|
453
|
+
const wanted = Object.keys(question.criteria);
|
|
454
|
+
const held = Object.keys(answer.probabilities);
|
|
455
|
+
return [
|
|
456
|
+
...wanted.filter((key) => !Object.hasOwn(answer.probabilities, key)).map((key) => ({
|
|
457
|
+
reason: "missing",
|
|
458
|
+
path: [
|
|
459
|
+
"answer",
|
|
460
|
+
"probabilities",
|
|
461
|
+
key
|
|
462
|
+
],
|
|
463
|
+
expected: "number"
|
|
464
|
+
})),
|
|
465
|
+
...held.filter((key) => !Object.hasOwn(question.criteria, key)).map((key) => ({
|
|
466
|
+
reason: "extra",
|
|
467
|
+
path: [
|
|
468
|
+
"answer",
|
|
469
|
+
"probabilities",
|
|
470
|
+
key
|
|
471
|
+
]
|
|
472
|
+
})),
|
|
473
|
+
...held.flatMap((key) => scanProbability(answer.probabilities[key], [
|
|
474
|
+
"answer",
|
|
475
|
+
"probabilities",
|
|
476
|
+
key
|
|
477
|
+
]))
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
if (question.form === "score" && answer.form === "score") {
|
|
481
|
+
const levels = question.criteria.length;
|
|
482
|
+
const count = answer.probabilities.length;
|
|
483
|
+
return [...count === levels ? [] : [{
|
|
484
|
+
reason: "constraint",
|
|
485
|
+
path: ["answer", "probabilities"],
|
|
486
|
+
expected: "array",
|
|
487
|
+
constraint: count < levels ? "min" : "max",
|
|
488
|
+
limit: levels,
|
|
489
|
+
received: String(count)
|
|
490
|
+
}], ...answer.probabilities.flatMap((value, index) => scanProbability(value, [
|
|
491
|
+
"answer",
|
|
492
|
+
"probabilities",
|
|
493
|
+
String(index)
|
|
494
|
+
]))];
|
|
495
|
+
}
|
|
496
|
+
return [{
|
|
497
|
+
reason: "type",
|
|
498
|
+
path: ["answer", "form"],
|
|
499
|
+
expected: "literal",
|
|
500
|
+
received: answer.form
|
|
501
|
+
}];
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Scans one probability against `[0, 1]`.
|
|
505
|
+
*
|
|
506
|
+
* @param value - The probability
|
|
507
|
+
* @param path - Where it sits
|
|
508
|
+
* @returns A constraint fault when it is out of bounds, or an empty list
|
|
509
|
+
* @example
|
|
510
|
+
* ```ts
|
|
511
|
+
* scanProbability(0.5, ['answer', 'noul']) // []
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
function scanProbability(value, path) {
|
|
515
|
+
if (value !== void 0 && value >= 0 && value <= 1) return [];
|
|
516
|
+
return [{
|
|
517
|
+
reason: "constraint",
|
|
518
|
+
path,
|
|
519
|
+
expected: "number",
|
|
520
|
+
constraint: value !== void 0 && value < 0 ? "min" : "max",
|
|
521
|
+
limit: value !== void 0 && value < 0 ? 0 : 1,
|
|
522
|
+
received: String(value)
|
|
523
|
+
}];
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
284
526
|
* Sanitizes one reported token count into a non-negative integer — a non-finite or
|
|
285
527
|
* non-positive value becomes `0`, and a positive fractional value floors down.
|
|
286
528
|
*
|
|
@@ -299,7 +541,8 @@ function sanitizeToken(value) {
|
|
|
299
541
|
/**
|
|
300
542
|
* Sanitizes a {@link TokenUsage} into non-negative integers — the guard an agent's
|
|
301
543
|
* abort-usage path applies to a provider's partial usage before it is charged against a
|
|
302
|
-
* budget or folded into the run total
|
|
544
|
+
* budget or folded into the run total, because a cancelled stream has no settled result to refuse;
|
|
545
|
+
* a settled result's usage crosses the provider-result door instead.
|
|
303
546
|
*
|
|
304
547
|
* @remarks
|
|
305
548
|
* Per field (`prompt` / `completion` / `total`): a non-finite value (`NaN`, `+Infinity`,
|
|
@@ -579,14 +822,16 @@ function extractLiterals(text) {
|
|
|
579
822
|
}
|
|
580
823
|
// src/core/validators.ts
|
|
581
824
|
/**
|
|
582
|
-
* Checks whether a value is a judge entry: a string, a JSON record, or a
|
|
825
|
+
* Checks whether a value is a judge entry: a non-empty string, a non-empty JSON record, or a non-empty
|
|
826
|
+
* JSON array.
|
|
583
827
|
*
|
|
584
828
|
* @remarks
|
|
585
|
-
* Total: a cycle, a non-JSON member, a class instance, and an untrusted input return false. `
|
|
586
|
-
*
|
|
829
|
+
* Total: a cycle, a non-JSON member, a class instance, and an untrusted input return false. `''`,
|
|
830
|
+
* `[]`, and `{}` give a judge nothing to read, so they return false. `null` is not an entry; a
|
|
831
|
+
* criteria guard admits it where the protocol keeps it.
|
|
587
832
|
*
|
|
588
833
|
* @param value - The unknown entry candidate
|
|
589
|
-
* @returns True if the value is a string or JSON structure the model can read; false otherwise
|
|
834
|
+
* @returns True if the value is a non-empty string or JSON structure the model can read; false otherwise
|
|
590
835
|
* @example
|
|
591
836
|
* ```ts
|
|
592
837
|
* isJudgeEntry('Customer asks for a refund') // true
|
|
@@ -596,8 +841,9 @@ function extractLiterals(text) {
|
|
|
596
841
|
* ```
|
|
597
842
|
*/
|
|
598
843
|
function isJudgeEntry(value) {
|
|
599
|
-
if ((0, _orkestrel_contract.isString)(value)) return
|
|
600
|
-
|
|
844
|
+
if ((0, _orkestrel_contract.isString)(value)) return value.length > 0;
|
|
845
|
+
if ((0, _orkestrel_contract.isArray)(value)) return (0, _orkestrel_contract.isJSONValue)(value) && value.length > 0;
|
|
846
|
+
return (0, _orkestrel_contract.isRecord)(value) && (0, _orkestrel_contract.isJSONValue)(value) && Object.keys(value).length > 0;
|
|
601
847
|
}
|
|
602
848
|
/**
|
|
603
849
|
* Checks whether a value is a well-formed judge question of the choice, score, or noul form.
|
|
@@ -605,8 +851,10 @@ function isJudgeEntry(value) {
|
|
|
605
851
|
* @remarks
|
|
606
852
|
* Total: an untrusted input returns false. A choice needs at least 2 options and a score at least 2
|
|
607
853
|
* levels, because the confidence formula divides by the candidate count; a description or a level
|
|
608
|
-
* can be `null`. `instructions` and noul `criteria` are omitted when absent and never `null`.
|
|
609
|
-
*
|
|
854
|
+
* can be `null`. `instructions` and noul `criteria` are omitted when absent and never `null`. The
|
|
855
|
+
* guard is exact on the question's own keys: an unknown member, and an own `undefined` optional,
|
|
856
|
+
* return false, as `judgeQuestionContract` does. Server limits on option, level, and question counts
|
|
857
|
+
* are left to the server.
|
|
610
858
|
*
|
|
611
859
|
* @param value - The unknown question candidate
|
|
612
860
|
* @returns True if the value is a question the judge engine can send; false otherwise
|
|
@@ -623,16 +871,60 @@ function isJudgeQuestion(value) {
|
|
|
623
871
|
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
624
872
|
const { form, instructions, criteria } = value;
|
|
625
873
|
if (instructions !== void 0 && !isJudgeEntry(instructions)) return false;
|
|
874
|
+
const owned = 1 + (instructions === void 0 ? 0 : 1) + (criteria === void 0 ? 0 : 1);
|
|
875
|
+
if (Object.keys(value).length !== owned) return false;
|
|
626
876
|
if (form === "choice") return (0, _orkestrel_contract.isRecord)(criteria) && Object.keys(criteria).length >= 2 && Object.values(criteria).every((0, _orkestrel_contract.nullableOf)(isJudgeEntry));
|
|
627
877
|
if (form === "score") return (0, _orkestrel_contract.arrayOf)((0, _orkestrel_contract.nullableOf)(isJudgeEntry))(criteria) && criteria.length >= 2;
|
|
628
878
|
if (form !== "noul") return false;
|
|
629
879
|
if (criteria === void 0) return true;
|
|
630
880
|
if (!(0, _orkestrel_contract.isRecord)(criteria)) return false;
|
|
631
|
-
|
|
881
|
+
const held = (criteria.true === void 0 ? 0 : 1) + (criteria.false === void 0 ? 0 : 1);
|
|
882
|
+
return Object.keys(criteria).length === held && (0, _orkestrel_contract.optionalOf)(isJudgeEntry)(criteria.true) && (0, _orkestrel_contract.optionalOf)(isJudgeEntry)(criteria.false);
|
|
632
883
|
});
|
|
633
884
|
return checked.success && checked.value;
|
|
634
885
|
}
|
|
635
886
|
/**
|
|
887
|
+
* Checks whether a value is a stored judge answer of the choice, score, or noul form.
|
|
888
|
+
*
|
|
889
|
+
* @remarks
|
|
890
|
+
* Total: an untrusted input returns false. The guard is exact on the answer's own keys and reads each
|
|
891
|
+
* probability as a finite number, with noul bounded to `[0, 1]`. Whether the answer fits
|
|
892
|
+
* its question belongs to the write. Every
|
|
893
|
+
* judgment of a hydrate reads this guard, so it counts keys by hand rather than through a built one,
|
|
894
|
+
* and `judgeAnswerContract` holds it in lockstep: an answer the contract accepts passes here.
|
|
895
|
+
*
|
|
896
|
+
* @param value - The value to test
|
|
897
|
+
* @returns True if the value has the shape of a {@link JudgeAnswer}; false otherwise
|
|
898
|
+
* @example
|
|
899
|
+
* ```ts
|
|
900
|
+
* isJudgeAnswer({ form: 'noul', noul: 0.9 }) // true
|
|
901
|
+
* isJudgeAnswer({ form: 'choice', probabilities: { billing: 1 } }) // true
|
|
902
|
+
* isJudgeAnswer({ form: 'noul', noul: 0.9, note: 'extra' }) // false
|
|
903
|
+
* ```
|
|
904
|
+
*/
|
|
905
|
+
function isJudgeAnswer(value) {
|
|
906
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
907
|
+
if (!(0, _orkestrel_contract.isRecord)(value) || Object.keys(value).length !== 2) return false;
|
|
908
|
+
const { form, noul, probabilities } = value;
|
|
909
|
+
if (form === "noul") return (0, _orkestrel_contract.isFiniteNumber)(noul) && noul >= 0 && noul <= 1;
|
|
910
|
+
if (form === "score") return (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isFiniteNumber)(probabilities);
|
|
911
|
+
return form === "choice" && (0, _orkestrel_contract.isRecord)(probabilities) && Object.values(probabilities).every(_orkestrel_contract.isFiniteNumber);
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Checks whether a value is a positive safe integer.
|
|
916
|
+
* @param value - The value to inspect
|
|
917
|
+
* @returns True if the value is a positive safe integer; false otherwise
|
|
918
|
+
* @example
|
|
919
|
+
* ```ts
|
|
920
|
+
* isPositiveSafeInteger(4096) // true
|
|
921
|
+
* isPositiveSafeInteger(0) // false
|
|
922
|
+
* ```
|
|
923
|
+
*/
|
|
924
|
+
function isPositiveSafeInteger(value) {
|
|
925
|
+
return (0, _orkestrel_contract.isFiniteNumber)(value) && Number.isSafeInteger(value) && value > 0;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
636
928
|
* Checks whether a value is a non-negative safe integer, `-0` included.
|
|
637
929
|
*
|
|
638
930
|
* @remarks
|
|
@@ -792,7 +1084,7 @@ function isProviderAbortError(value) {
|
|
|
792
1084
|
*
|
|
793
1085
|
* @remarks
|
|
794
1086
|
* The provider base throws this error for a non-OK HTTP response, a missing response
|
|
795
|
-
* body,
|
|
1087
|
+
* body, a stream that ended with no settled result and no terminal record, a result or delta the published contract refuses, and with code `'PROTOCOL'`
|
|
796
1088
|
* when the framing seam's `parse` or `finish` throws anything but a provider error, with the throw
|
|
797
1089
|
* as `cause`. Concrete wire decoders also use it for malformed records and relayed provider
|
|
798
1090
|
* failures.
|
|
@@ -803,12 +1095,15 @@ var ProviderError = class extends Error {
|
|
|
803
1095
|
code;
|
|
804
1096
|
/** Holds the response status for an HTTP failure, or undefined for other codes. */
|
|
805
1097
|
status;
|
|
1098
|
+
/** Holds what the refusal found, such as its faults, or undefined when it carries none. */
|
|
1099
|
+
context;
|
|
806
1100
|
constructor(code, message, options) {
|
|
807
1101
|
super(message, options);
|
|
808
1102
|
this.name = "ProviderError";
|
|
809
1103
|
_orkestrel_contract.INTRINSICS.define(this, Symbol.for("@orkestrel/agent.ProviderError"), { value: this });
|
|
810
1104
|
this.code = code;
|
|
811
1105
|
this.status = options?.status;
|
|
1106
|
+
this.context = options?.context;
|
|
812
1107
|
}
|
|
813
1108
|
};
|
|
814
1109
|
/**
|
|
@@ -827,7 +1122,8 @@ function isProviderError(value) {
|
|
|
827
1122
|
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
828
1123
|
if (!(code === "HTTP" || code === "PROTOCOL" || code === "PROVIDER")) return false;
|
|
829
1124
|
const status = _orkestrel_contract.INTRINSICS.reflect.read(value, "status");
|
|
830
|
-
|
|
1125
|
+
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
1126
|
+
return (status === void 0 || (0, _orkestrel_contract.isNumber)(status)) && (context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context));
|
|
831
1127
|
});
|
|
832
1128
|
}
|
|
833
1129
|
/**
|
|
@@ -844,12 +1140,15 @@ var JudgeError = class extends Error {
|
|
|
844
1140
|
code;
|
|
845
1141
|
/** Holds the response status for an HTTP failure, or undefined for other codes. */
|
|
846
1142
|
status;
|
|
1143
|
+
/** Holds the values that locate the failure, such as the `faults` of a refused question, or `undefined` when none were supplied. */
|
|
1144
|
+
context;
|
|
847
1145
|
constructor(code, message, options) {
|
|
848
1146
|
super(message, options);
|
|
849
1147
|
this.name = "JudgeError";
|
|
850
1148
|
_orkestrel_contract.INTRINSICS.define(this, Symbol.for("@orkestrel/agent.JudgeError"), { value: this });
|
|
851
1149
|
this.code = code;
|
|
852
1150
|
this.status = options?.status;
|
|
1151
|
+
this.context = options?.context;
|
|
853
1152
|
}
|
|
854
1153
|
};
|
|
855
1154
|
/**
|
|
@@ -868,7 +1167,8 @@ function isJudgeError(value) {
|
|
|
868
1167
|
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
869
1168
|
if (!(code === "HTTP" || code === "PROTOCOL" || code === "QUESTION")) return false;
|
|
870
1169
|
const status = _orkestrel_contract.INTRINSICS.reflect.read(value, "status");
|
|
871
|
-
|
|
1170
|
+
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
1171
|
+
return (status === void 0 || (0, _orkestrel_contract.isNumber)(status)) && (context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context));
|
|
872
1172
|
});
|
|
873
1173
|
}
|
|
874
1174
|
// src/core/providers/shapers.ts
|
|
@@ -934,6 +1234,100 @@ var providerRequestContract = (0, _orkestrel_contract.createContract)(providerRe
|
|
|
934
1234
|
var providerResultContract = (0, _orkestrel_contract.createContract)(providerResultShape);
|
|
935
1235
|
/** Validates and projects channel-discriminated relay frames at a JSON wire boundary. */
|
|
936
1236
|
var relayFrameContract = (0, _orkestrel_contract.createContract)(relayFrameShape);
|
|
1237
|
+
// src/core/providers/validators.ts
|
|
1238
|
+
/**
|
|
1239
|
+
* Checks whether a value carries the members {@link ProviderResult} publishes: a string `content`, and
|
|
1240
|
+
* `thinking`, `tools`, `usage`, and `truncated` absent or of their published types.
|
|
1241
|
+
*
|
|
1242
|
+
* @remarks
|
|
1243
|
+
* A provider is a foreign interface, so the guard is open: unknown members and a class instance pass,
|
|
1244
|
+
* and each `usage` count is a finite nonnegative number with no integer check. An unreadable member
|
|
1245
|
+
* returns false. {@link import('./helpers.js').requireProviderResult} runs it on an owned copy, the one
|
|
1246
|
+
* door a run's result crosses.
|
|
1247
|
+
*
|
|
1248
|
+
* @param value - The unknown result candidate
|
|
1249
|
+
* @returns True if the value satisfies the published result; false otherwise
|
|
1250
|
+
*
|
|
1251
|
+
* @example
|
|
1252
|
+
* ```ts
|
|
1253
|
+
* isProviderResult({ content: 'answer', extra: true }) // true
|
|
1254
|
+
* isProviderResult({}) // false
|
|
1255
|
+
* isProviderResult({ content: 'answer', usage: { prompt: -1, completion: 0, total: 0 } }) // false
|
|
1256
|
+
* ```
|
|
1257
|
+
*/
|
|
1258
|
+
var isProviderResult = (0, _orkestrel_contract.objectOf)({
|
|
1259
|
+
content: _orkestrel_contract.isString,
|
|
1260
|
+
thinking: _orkestrel_contract.isString,
|
|
1261
|
+
tools: (0, _orkestrel_contract.arrayOf)(_orkestrel_tool.isToolCall),
|
|
1262
|
+
usage: (0, _orkestrel_contract.objectOf)({
|
|
1263
|
+
prompt: _orkestrel_budget.isBudgetAmount,
|
|
1264
|
+
completion: _orkestrel_budget.isBudgetAmount,
|
|
1265
|
+
total: _orkestrel_budget.isBudgetAmount
|
|
1266
|
+
}),
|
|
1267
|
+
truncated: _orkestrel_contract.isBoolean
|
|
1268
|
+
}, [
|
|
1269
|
+
"thinking",
|
|
1270
|
+
"tools",
|
|
1271
|
+
"usage",
|
|
1272
|
+
"truncated"
|
|
1273
|
+
]);
|
|
1274
|
+
/**
|
|
1275
|
+
* Checks whether a value is a System One response envelope with optional model and usage.
|
|
1276
|
+
*
|
|
1277
|
+
* @remarks
|
|
1278
|
+
* Answers remain unknown until checked against their questions. Missing and null usage counts
|
|
1279
|
+
* are accepted. Extra members are ignored, and unreadable fields return false.
|
|
1280
|
+
*
|
|
1281
|
+
* @param value - The unknown response candidate
|
|
1282
|
+
* @returns True if the envelope fields have their wire types; false otherwise
|
|
1283
|
+
* @example
|
|
1284
|
+
* ```ts
|
|
1285
|
+
* isSystemOneResponse({ answers: {}, usage: { input_tokens: null } }) // true
|
|
1286
|
+
* ```
|
|
1287
|
+
*/
|
|
1288
|
+
function isSystemOneResponse(value) {
|
|
1289
|
+
const checked = (0, _orkestrel_contract.attempt)(() => {
|
|
1290
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
1291
|
+
const { model, answers, usage } = value;
|
|
1292
|
+
if (!(0, _orkestrel_contract.optionalOf)(_orkestrel_contract.isString)(model) || !(0, _orkestrel_contract.isRecord)(answers)) return false;
|
|
1293
|
+
if (usage === void 0) return true;
|
|
1294
|
+
if (!(0, _orkestrel_contract.isRecord)(usage)) return false;
|
|
1295
|
+
return (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.nullableOf)(_orkestrel_contract.isNumber))(usage.input_tokens) && (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.nullableOf)(_orkestrel_contract.isNumber))(usage.output_tokens);
|
|
1296
|
+
});
|
|
1297
|
+
return checked.success && checked.value;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Checks whether a value is a System One answer whose type and distribution the wire can read.
|
|
1301
|
+
*
|
|
1302
|
+
* @remarks
|
|
1303
|
+
* The guard checks `type` and the distribution the wire dereferences: the `noul` number for a
|
|
1304
|
+
* noul, the `probabilities` map for a choice, and the `probabilities` map or dense array for a
|
|
1305
|
+
* score, each probability a finite number in [0, 1]. The `choice`, `score`, `confidence`, and
|
|
1306
|
+
* `legend` members are carried unchecked, because the wire never reads them, so a value this
|
|
1307
|
+
* guard accepts can hold any value in those members. Distribution sums are not constrained;
|
|
1308
|
+
* values are never normalized. A read that throws returns false.
|
|
1309
|
+
*
|
|
1310
|
+
* @param value - The unknown answer candidate
|
|
1311
|
+
* @returns True if the type and the distribution satisfy the wire contract; false otherwise
|
|
1312
|
+
* @example
|
|
1313
|
+
* ```ts
|
|
1314
|
+
* isSystemOneAnswer({ type: 'noul', noul: 0.9 }) // true
|
|
1315
|
+
* isSystemOneAnswer({ type: 'score', probabilities: [0.2, 0.8] }) // true
|
|
1316
|
+
* isSystemOneAnswer({ type: 'noul', noul: 1.1 }) // false
|
|
1317
|
+
* ```
|
|
1318
|
+
*/
|
|
1319
|
+
function isSystemOneAnswer(value) {
|
|
1320
|
+
const checked = (0, _orkestrel_contract.attempt)(() => {
|
|
1321
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
1322
|
+
const { type } = value;
|
|
1323
|
+
if (type === "noul") return (0, _orkestrel_contract.boundsOf)(0, 1)(value.noul);
|
|
1324
|
+
if (type !== "choice" && type !== "score") return false;
|
|
1325
|
+
const { probabilities } = value;
|
|
1326
|
+
if (type === "score" && (0, _orkestrel_contract.isArray)(probabilities)) return (0, _orkestrel_contract.arrayOf)((0, _orkestrel_contract.boundsOf)(0, 1))(probabilities);
|
|
1327
|
+
return (0, _orkestrel_contract.isRecord)(probabilities) && Object.values(probabilities).every((0, _orkestrel_contract.boundsOf)(0, 1));
|
|
1328
|
+
});
|
|
1329
|
+
return checked.success && checked.value;
|
|
1330
|
+
}
|
|
937
1331
|
// src/core/providers/helpers.ts
|
|
938
1332
|
/**
|
|
939
1333
|
* Assembles a provider result with only populated optional fields.
|
|
@@ -959,6 +1353,76 @@ function buildProviderResult(content, thinking, tools, usage, truncated) {
|
|
|
959
1353
|
};
|
|
960
1354
|
}
|
|
961
1355
|
/**
|
|
1356
|
+
* Reads a foreign delta's two members once into an owned copy, the value a run checks and consumes.
|
|
1357
|
+
*
|
|
1358
|
+
* @remarks
|
|
1359
|
+
* A stream checks every delta, so this is a hand read rather than a record guard. Reading each member
|
|
1360
|
+
* once means a getter can't pass the check with one value and hand the run another. Unknown members
|
|
1361
|
+
* and a class instance pass, and an unreadable member reads as no delta.
|
|
1362
|
+
*
|
|
1363
|
+
* @param value - The value the stream yielded
|
|
1364
|
+
* @returns The owned delta, or undefined when the value is not one
|
|
1365
|
+
*
|
|
1366
|
+
* @example
|
|
1367
|
+
* ```ts
|
|
1368
|
+
* readProviderDelta({ channel: 'content', text: 'Hi' }) // { channel: 'content', text: 'Hi' }
|
|
1369
|
+
* readProviderDelta({ channel: 'audio', text: 'Hi' }) // undefined
|
|
1370
|
+
* ```
|
|
1371
|
+
*/
|
|
1372
|
+
function readProviderDelta(value) {
|
|
1373
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1374
|
+
try {
|
|
1375
|
+
const { channel, text } = value;
|
|
1376
|
+
return (channel === "content" || channel === "thinking") && typeof text === "string" ? {
|
|
1377
|
+
channel,
|
|
1378
|
+
text
|
|
1379
|
+
} : void 0;
|
|
1380
|
+
} catch {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Owns a provider's settled result: reads each published member of the foreign value once into a fresh
|
|
1386
|
+
* record, checks that copy, and returns it, or throws a coded error the run rejects with.
|
|
1387
|
+
*
|
|
1388
|
+
* @remarks
|
|
1389
|
+
* The check is {@link isProviderResult}, so a class instance carrying the published members passes and
|
|
1390
|
+
* an unknown member is left behind. `usage` is copied count by count; `tools` keeps the array the
|
|
1391
|
+
* provider returned. An absent `usage` stays absent, which the loop bills as its estimate.
|
|
1392
|
+
*
|
|
1393
|
+
* @param value - The value a provider's stream returned
|
|
1394
|
+
* @returns The owned result
|
|
1395
|
+
* @throws {ProviderError} Thrown when the value does not carry the published members (code `PROTOCOL`)
|
|
1396
|
+
*
|
|
1397
|
+
* @example
|
|
1398
|
+
* ```ts
|
|
1399
|
+
* requireProviderResult({ content: 'answer', extra: true }) // { content: 'answer' }
|
|
1400
|
+
* requireProviderResult({}) // throws ProviderError ('PROTOCOL')
|
|
1401
|
+
* ```
|
|
1402
|
+
*/
|
|
1403
|
+
function requireProviderResult(value) {
|
|
1404
|
+
const owned = (0, _orkestrel_contract.attempt)(() => {
|
|
1405
|
+
if (!(0, _orkestrel_contract.isObject)(value) && !(0, _orkestrel_contract.isFunction)(value)) return void 0;
|
|
1406
|
+
const thinking = Reflect.get(value, "thinking");
|
|
1407
|
+
const tools = Reflect.get(value, "tools");
|
|
1408
|
+
const usage = Reflect.get(value, "usage");
|
|
1409
|
+
const truncated = Reflect.get(value, "truncated");
|
|
1410
|
+
return {
|
|
1411
|
+
content: Reflect.get(value, "content"),
|
|
1412
|
+
...thinking === void 0 ? {} : { thinking },
|
|
1413
|
+
...tools === void 0 ? {} : { tools },
|
|
1414
|
+
...usage === void 0 ? {} : { usage: (0, _orkestrel_contract.isObject)(usage) ? {
|
|
1415
|
+
prompt: Reflect.get(usage, "prompt"),
|
|
1416
|
+
completion: Reflect.get(usage, "completion"),
|
|
1417
|
+
total: Reflect.get(usage, "total")
|
|
1418
|
+
} : usage },
|
|
1419
|
+
...truncated === void 0 ? {} : { truncated }
|
|
1420
|
+
};
|
|
1421
|
+
});
|
|
1422
|
+
if (owned.success && isProviderResult(owned.value)) return owned.value;
|
|
1423
|
+
throw new ProviderError("PROTOCOL", `provider error: a provider must return string content, with thinking, tools, usage, and truncated absent or of their published types, and usage counts finite and nonnegative; received ${(0, _orkestrel_contract.preview)(owned.success ? owned.value : value)}`, owned.success ? void 0 : { cause: owned.error });
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
962
1426
|
* Cancels a stream reader and releases its lock, swallowing a cancellation failure so the
|
|
963
1427
|
* caller's own outcome stands.
|
|
964
1428
|
*
|
|
@@ -1779,7 +2243,8 @@ var ThinkSplitter = class {
|
|
|
1779
2243
|
* have no size limit. A qwen3 implicit-open reclassification corrects the final
|
|
1780
2244
|
* content while content deltas already yielded cannot be recalled. A subclass fills
|
|
1781
2245
|
* `name`, `frame`, `encode`, `read`, and `finish`; the constructor takes the `split`
|
|
1782
|
-
* and `strict` switches to control reasoning separation and settled
|
|
2246
|
+
* and `strict` switches to control reasoning separation and whether a stream must end with a settled
|
|
2247
|
+
* `result` or a `done` record, which it must by default.
|
|
1783
2248
|
* The `replay` input names which stored thinking the agent and relay send back. Default: `'none'`.
|
|
1784
2249
|
* A throw from the framing seam (`parse`, or `finish`) becomes a `ProviderError` with the
|
|
1785
2250
|
* `PROTOCOL` code whose `cause` is the throw, unless it is already a `ProviderError` or
|
|
@@ -1811,7 +2276,8 @@ var ThinkSplitter = class {
|
|
|
1811
2276
|
* class TextProvider extends AgentProvider<string> {
|
|
1812
2277
|
* readonly name = 'text'
|
|
1813
2278
|
* constructor(options: TextOptions) {
|
|
1814
|
-
*
|
|
2279
|
+
* // Raw text has no terminal record, so a stream that stops reads as complete.
|
|
2280
|
+
* super({ ...options, path: '/generate', strict: false })
|
|
1815
2281
|
* }
|
|
1816
2282
|
* frame(): ProviderParserInterface<string> {
|
|
1817
2283
|
* return new TextFrame()
|
|
@@ -1847,7 +2313,7 @@ var AgentProvider = class {
|
|
|
1847
2313
|
this.#transport = input.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1848
2314
|
this.#headers = input.headers;
|
|
1849
2315
|
this.#split = input.split ?? true;
|
|
1850
|
-
this.#strict = input.strict ??
|
|
2316
|
+
this.#strict = input.strict ?? true;
|
|
1851
2317
|
}
|
|
1852
2318
|
/** Exposes the instance's minted UUID. */
|
|
1853
2319
|
get id() {
|
|
@@ -1942,7 +2408,7 @@ var AgentProvider = class {
|
|
|
1942
2408
|
text: tail
|
|
1943
2409
|
};
|
|
1944
2410
|
combined.throwIfAborted();
|
|
1945
|
-
if (this.#strict) throw new ProviderError("PROTOCOL", "provider error:
|
|
2411
|
+
if (this.#strict && increments.at(-1)?.done !== true) throw new ProviderError("PROTOCOL", "provider error: the stream ended with no settled result and no terminal record; the response was cut, or a wire with no terminal record passes strict: false");
|
|
1946
2412
|
return this.#assemble(splitter, increments);
|
|
1947
2413
|
} catch (error) {
|
|
1948
2414
|
throw this.#translate(error, combined, splitter, increments);
|
|
@@ -2130,7 +2596,9 @@ var RelayProvider = class extends AgentProvider {
|
|
|
2130
2596
|
* order. `ask` validates the request before any call, builds every body before the first call so
|
|
2131
2597
|
* a wire limit refuses the request before inference, and runs the calls one after another, each
|
|
2132
2598
|
* under its own deadline folded with the caller's signal. A cancel throws a `JudgeAbortError`
|
|
2133
|
-
* whose `partial` merges the calls that completed. A transport error reaches the caller unchanged.
|
|
2599
|
+
* whose `partial` merges the calls that completed. A transport error reaches the caller unchanged. An
|
|
2600
|
+
* answer that does not fit its question throws code `PROTOCOL`, and a call whose `read` reports no
|
|
2601
|
+
* usage bills the estimate of its response text.
|
|
2134
2602
|
*
|
|
2135
2603
|
* @example Writing a judge wire
|
|
2136
2604
|
* ```ts
|
|
@@ -2208,7 +2676,13 @@ var AgentJudge = class {
|
|
|
2208
2676
|
if (questions.length === 0) throw new JudgeError("QUESTION", "judge error: no questions");
|
|
2209
2677
|
const entries = questions.map(([id, question]) => {
|
|
2210
2678
|
const copy = copyJSON(question);
|
|
2211
|
-
if (!isJudgeQuestion(copy))
|
|
2679
|
+
if (!isJudgeQuestion(copy)) {
|
|
2680
|
+
const faults = judgeQuestionContract.audit(copy);
|
|
2681
|
+
throw new JudgeError("QUESTION", `judge error: ${describeQuestionRefusal(id, faults, judgeQuestionContract.schema)}`, { context: {
|
|
2682
|
+
question: id,
|
|
2683
|
+
faults
|
|
2684
|
+
} });
|
|
2685
|
+
}
|
|
2212
2686
|
return [id, copy];
|
|
2213
2687
|
});
|
|
2214
2688
|
const owned = {
|
|
@@ -2250,7 +2724,24 @@ var AgentJudge = class {
|
|
|
2250
2724
|
if (value === void 0) throw new JudgeError("PROTOCOL", "judge error: invalid JSON body");
|
|
2251
2725
|
const result = this.read(value, request);
|
|
2252
2726
|
combined.throwIfAborted();
|
|
2253
|
-
|
|
2727
|
+
for (const [id, answer] of Object.entries(result.answers)) {
|
|
2728
|
+
const question = Object.hasOwn(request.questions, id) ? request.questions[id] : void 0;
|
|
2729
|
+
const faults = question === void 0 ? [] : scanJudgmentTies(question, answer);
|
|
2730
|
+
if (faults.length > 0) throw new JudgeError("PROTOCOL", `judge error: the answer to question ${id} does not fit it\n${(0, _orkestrel_contract.describeFaults)(faults, judgeAnswerContract.schema)}`, { context: {
|
|
2731
|
+
question: id,
|
|
2732
|
+
faults
|
|
2733
|
+
} });
|
|
2734
|
+
}
|
|
2735
|
+
if (result.usage !== void 0) return result;
|
|
2736
|
+
const estimate = estimateTokens(text.text);
|
|
2737
|
+
return {
|
|
2738
|
+
...result,
|
|
2739
|
+
usage: {
|
|
2740
|
+
prompt: 0,
|
|
2741
|
+
completion: estimate,
|
|
2742
|
+
total: estimate
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2254
2745
|
} catch (error) {
|
|
2255
2746
|
if (combined.aborted) throw new JudgeAbortError(buildJudgeResult(this.#model, completed), error === combined.reason ? void 0 : { cause: error });
|
|
2256
2747
|
throw error;
|
|
@@ -2259,64 +2750,6 @@ var AgentJudge = class {
|
|
|
2259
2750
|
}
|
|
2260
2751
|
}
|
|
2261
2752
|
};
|
|
2262
|
-
// src/core/providers/validators.ts
|
|
2263
|
-
/**
|
|
2264
|
-
* Checks whether a value is a System One response envelope with optional model and usage.
|
|
2265
|
-
*
|
|
2266
|
-
* @remarks
|
|
2267
|
-
* Answers remain unknown until checked against their questions. Missing and null usage counts
|
|
2268
|
-
* are accepted. Extra members are ignored, and unreadable fields return false.
|
|
2269
|
-
*
|
|
2270
|
-
* @param value - The unknown response candidate
|
|
2271
|
-
* @returns True if the envelope fields have their wire types; false otherwise
|
|
2272
|
-
* @example
|
|
2273
|
-
* ```ts
|
|
2274
|
-
* isSystemOneResponse({ answers: {}, usage: { input_tokens: null } }) // true
|
|
2275
|
-
* ```
|
|
2276
|
-
*/
|
|
2277
|
-
function isSystemOneResponse(value) {
|
|
2278
|
-
const checked = (0, _orkestrel_contract.attempt)(() => {
|
|
2279
|
-
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
2280
|
-
const { model, answers, usage } = value;
|
|
2281
|
-
if (!(0, _orkestrel_contract.optionalOf)(_orkestrel_contract.isString)(model) || !(0, _orkestrel_contract.isRecord)(answers)) return false;
|
|
2282
|
-
if (usage === void 0) return true;
|
|
2283
|
-
if (!(0, _orkestrel_contract.isRecord)(usage)) return false;
|
|
2284
|
-
return (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.nullableOf)(_orkestrel_contract.isNumber))(usage.input_tokens) && (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.nullableOf)(_orkestrel_contract.isNumber))(usage.output_tokens);
|
|
2285
|
-
});
|
|
2286
|
-
return checked.success && checked.value;
|
|
2287
|
-
}
|
|
2288
|
-
/**
|
|
2289
|
-
* Checks whether a value is a System One answer whose type and distribution the wire can read.
|
|
2290
|
-
*
|
|
2291
|
-
* @remarks
|
|
2292
|
-
* The guard checks `type` and the distribution the wire dereferences: the `noul` number for a
|
|
2293
|
-
* noul, the `probabilities` map for a choice, and the `probabilities` map or dense array for a
|
|
2294
|
-
* score, each probability a finite number in [0, 1]. The `choice`, `score`, `confidence`, and
|
|
2295
|
-
* `legend` members are carried unchecked, because the wire never reads them, so a value this
|
|
2296
|
-
* guard accepts can hold any value in those members. Distribution sums are not constrained;
|
|
2297
|
-
* values are never normalized. A read that throws returns false.
|
|
2298
|
-
*
|
|
2299
|
-
* @param value - The unknown answer candidate
|
|
2300
|
-
* @returns True if the type and the distribution satisfy the wire contract; false otherwise
|
|
2301
|
-
* @example
|
|
2302
|
-
* ```ts
|
|
2303
|
-
* isSystemOneAnswer({ type: 'noul', noul: 0.9 }) // true
|
|
2304
|
-
* isSystemOneAnswer({ type: 'score', probabilities: [0.2, 0.8] }) // true
|
|
2305
|
-
* isSystemOneAnswer({ type: 'noul', noul: 1.1 }) // false
|
|
2306
|
-
* ```
|
|
2307
|
-
*/
|
|
2308
|
-
function isSystemOneAnswer(value) {
|
|
2309
|
-
const checked = (0, _orkestrel_contract.attempt)(() => {
|
|
2310
|
-
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
2311
|
-
const { type } = value;
|
|
2312
|
-
if (type === "noul") return (0, _orkestrel_contract.boundsOf)(0, 1)(value.noul);
|
|
2313
|
-
if (type !== "choice" && type !== "score") return false;
|
|
2314
|
-
const { probabilities } = value;
|
|
2315
|
-
if (type === "score" && (0, _orkestrel_contract.isArray)(probabilities)) return (0, _orkestrel_contract.arrayOf)((0, _orkestrel_contract.boundsOf)(0, 1))(probabilities);
|
|
2316
|
-
return (0, _orkestrel_contract.isRecord)(probabilities) && Object.values(probabilities).every((0, _orkestrel_contract.boundsOf)(0, 1));
|
|
2317
|
-
});
|
|
2318
|
-
return checked.success && checked.value;
|
|
2319
|
-
}
|
|
2320
2753
|
// src/core/providers/SystemOneJudge.ts
|
|
2321
2754
|
/**
|
|
2322
2755
|
* Carries judge questions over the System One protocol and derives answers from server distributions.
|
|
@@ -2401,11 +2834,11 @@ var SystemOneJudge = class extends AgentJudge {
|
|
|
2401
2834
|
* the message names the question id of an invalid answer
|
|
2402
2835
|
*/
|
|
2403
2836
|
read(value, request) {
|
|
2404
|
-
if (!isSystemOneResponse(value)) throw new JudgeError("PROTOCOL",
|
|
2837
|
+
if (!isSystemOneResponse(value)) throw new JudgeError("PROTOCOL", `judge error: invalid System One response envelope; received ${(0, _orkestrel_contract.preview)(value)}`);
|
|
2405
2838
|
const entries = Object.entries(request.questions).map(([id, question]) => {
|
|
2406
2839
|
if (!Object.hasOwn(value.answers, id)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has no System One answer`);
|
|
2407
2840
|
const wire = value.answers[id];
|
|
2408
|
-
if (!isSystemOneAnswer(wire)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has an invalid System One answer`);
|
|
2841
|
+
if (!isSystemOneAnswer(wire)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has an invalid System One answer; received ${(0, _orkestrel_contract.preview)(wire)}`);
|
|
2409
2842
|
const answer = extractSystemOneAnswer(wire, question);
|
|
2410
2843
|
if (answer === void 0) throw new JudgeError("PROTOCOL", `judge error: question ${id} has a mismatched or incomplete System One answer`);
|
|
2411
2844
|
return [id, answer];
|
|
@@ -2625,12 +3058,12 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
|
|
|
2625
3058
|
// src/core/conversations/errors.ts
|
|
2626
3059
|
/**
|
|
2627
3060
|
* Reports a conversation with no {@link ConversationSummaryHandler} to fold its messages with, a
|
|
2628
|
-
* `sections` cap
|
|
3061
|
+
* `sections` cap that is not a positive safe integer, a `keep` that is not a nonnegative safe integer, a summarizer digest
|
|
2629
3062
|
* whose trimmed text is empty, a judgment or judge request that JSON cannot carry, a stored or
|
|
2630
3063
|
* selected message whose role is not a conversation role, or a malformed snapshot — thrown by the
|
|
2631
3064
|
* `compact()` and `add` methods of a {@link ConversationInterface}, its construction, or its
|
|
2632
3065
|
* judgment store, or returned as the `fault` of an agent selection, carrying the machine-readable
|
|
2633
|
-
* `code` `'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT' | 'KEEP' | 'SUMMARY' | 'ROLE' | 'SNAPSHOT'`.
|
|
3066
|
+
* `code` `'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT' | 'KEEP' | 'SUMMARY' | 'ROLE' | 'MESSAGE' | 'SNAPSHOT' | 'ID'`.
|
|
2634
3067
|
*
|
|
2635
3068
|
* @remarks
|
|
2636
3069
|
* Compaction requires a summarizer (it digests the folded slice into a section summary); a
|
|
@@ -2638,7 +3071,7 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
|
|
|
2638
3071
|
* is a programmer error and throws this with `'SUMMARIZER'`.
|
|
2639
3072
|
* A `sections` cap (on {@link import('./types.js').ConversationOptions} /
|
|
2640
3073
|
* {@link import('./types.js').ConversationManagerOptions} /
|
|
2641
|
-
* {@link import('./types.js').CompactOptions}) must be
|
|
3074
|
+
* {@link import('./types.js').CompactOptions}) must be a positive safe integer, and cannot combine with
|
|
2642
3075
|
* `cumulative` — either is a programmer error and throws this with `'SECTIONS'`. A `keep` on the same options must be a nonnegative
|
|
2643
3076
|
* safe integer — any other value is a programmer error and throws this with `'KEEP'`. A judgment
|
|
2644
3077
|
* record or a judge request that JSON cannot carry, or whose copy fails its guard, is a
|
|
@@ -2646,13 +3079,16 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
|
|
|
2646
3079
|
* where one exists. A summarizer digest whose trimmed text is empty rejects the `compact()` with
|
|
2647
3080
|
* `'SUMMARY'`. A stored message plays a `ConversationRole`: `add` refuses any other role with
|
|
2648
3081
|
* `'ROLE'` before it stores any message of the batch, a hydrating snapshot that holds one throws
|
|
2649
|
-
* `'ROLE'`, and an agent's selection that holds one becomes a `'ROLE'` fault.
|
|
2650
|
-
*
|
|
2651
|
-
*
|
|
3082
|
+
* `'ROLE'`, and an agent's selection that holds one becomes a `'ROLE'` fault. `add` refuses an input
|
|
3083
|
+
* whose other members fail `isMessageInput` with `'MESSAGE'`, its `context` holding the `faults`,
|
|
3084
|
+
* before it stores any message of the batch. A hydrating snapshot that fails
|
|
3085
|
+
* `isConversationSnapshot` or repeats an id, and a store row or written snapshot that does, throw
|
|
3086
|
+
* `'SNAPSHOT'`; a judgment the guard admits but JSON cannot carry still throws `'JUDGMENT'`. A
|
|
3087
|
+
* manager's `add` of an id it already holds throws `'ID'`. Narrow a caught value with
|
|
2652
3088
|
* {@link isConversationError} and branch on `error.code`.
|
|
2653
3089
|
*/
|
|
2654
3090
|
var ConversationError = class extends Error {
|
|
2655
|
-
/** Names the machine-readable condition — `'SUMMARIZER'`: a `compact()` with no summarizer; `'SECTIONS'`: a
|
|
3091
|
+
/** Names the machine-readable condition — `'SUMMARIZER'`: a `compact()` with no summarizer; `'SECTIONS'`: a `sections` cap that is not a positive safe integer, or a cap combined with `cumulative`; `'JUDGMENT'`: a judgment or judge request that is not JSON; `'KEEP'`: a `keep` that is not a nonnegative safe integer; `'SUMMARY'`: a summarizer digest whose trimmed text is empty; `'ROLE'`: a stored or selected message whose role is not a `ConversationRole`; `'MESSAGE'`: a message input whose other members fail `isMessageInput`; `'SNAPSHOT'`: a snapshot that fails `isConversationSnapshot` or repeats an id; `'ID'`: a manager's `add` of an id it already holds. */
|
|
2656
3092
|
code;
|
|
2657
3093
|
/** Holds the values that locate the failure, such as the `question` id of a malformed question, or `undefined` when none were supplied. */
|
|
2658
3094
|
context;
|
|
@@ -2691,11 +3127,66 @@ function isConversationError(value) {
|
|
|
2691
3127
|
return (0, _orkestrel_contract.holds)(() => {
|
|
2692
3128
|
if (!(value instanceof Error && _orkestrel_contract.INTRINSICS.prototype(value) !== Error.prototype && _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.ConversationError"))?.value === value)) return false;
|
|
2693
3129
|
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
2694
|
-
if (!(code === "SUMMARIZER" || code === "SECTIONS" || code === "JUDGMENT" || code === "KEEP" || code === "SUMMARY" || code === "ROLE" || code === "SNAPSHOT")) return false;
|
|
3130
|
+
if (!(code === "SUMMARIZER" || code === "SECTIONS" || code === "JUDGMENT" || code === "KEEP" || code === "SUMMARY" || code === "ROLE" || code === "MESSAGE" || code === "SNAPSHOT" || code === "ID")) return false;
|
|
2695
3131
|
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
2696
3132
|
return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
|
|
2697
3133
|
});
|
|
2698
3134
|
}
|
|
3135
|
+
// src/core/conversations/shapers.ts
|
|
3136
|
+
/**
|
|
3137
|
+
* Describes a stored judgment: exactly one of an answer or a refusal, with its question, sources,
|
|
3138
|
+
* state, model, and storage time.
|
|
3139
|
+
*
|
|
3140
|
+
* @remarks
|
|
3141
|
+
* `isJudgment` is the door a hydrate reads and this shape fills a refusal's faults, because hydrate is
|
|
3142
|
+
* a per-element path. A lockstep test holds that `is` implies the guard.
|
|
3143
|
+
*/
|
|
3144
|
+
var judgmentShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
|
|
3145
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
3146
|
+
question: judgeQuestionShape,
|
|
3147
|
+
model: (0, _orkestrel_contract.stringShape)(),
|
|
3148
|
+
sources: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
|
|
3149
|
+
state: (0, _orkestrel_contract.stringShape)(),
|
|
3150
|
+
time: (0, _orkestrel_contract.numberShape)(),
|
|
3151
|
+
usage: (0, _orkestrel_contract.optionalShape)(tokenUsageShape),
|
|
3152
|
+
answer: judgeAnswerShape
|
|
3153
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
3154
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
3155
|
+
question: judgeQuestionShape,
|
|
3156
|
+
model: (0, _orkestrel_contract.stringShape)(),
|
|
3157
|
+
sources: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
|
|
3158
|
+
state: (0, _orkestrel_contract.stringShape)(),
|
|
3159
|
+
time: (0, _orkestrel_contract.numberShape)(),
|
|
3160
|
+
usage: (0, _orkestrel_contract.optionalShape)(tokenUsageShape),
|
|
3161
|
+
refusal: (0, _orkestrel_contract.objectShape)({ missing: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()) })
|
|
3162
|
+
}));
|
|
3163
|
+
/** Describes a compacted section: its id, its summary, and the originals it folded. */
|
|
3164
|
+
var sectionShape = (0, _orkestrel_contract.objectShape)({
|
|
3165
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
3166
|
+
summary: (0, _orkestrel_contract.stringShape)(),
|
|
3167
|
+
messages: (0, _orkestrel_contract.arrayShape)(messageShape)
|
|
3168
|
+
});
|
|
3169
|
+
/**
|
|
3170
|
+
* Describes a conversation snapshot exactly: an unknown key refuses, so a key a later version adds never
|
|
3171
|
+
* reads as absent.
|
|
3172
|
+
*
|
|
3173
|
+
* @remarks
|
|
3174
|
+
* `isConversationSnapshot` is the door a hydrate and a store read, and this shape fills a refusal's
|
|
3175
|
+
* faults. A lockstep test holds that `is` implies the guard, and on the snapshot fixtures the converse.
|
|
3176
|
+
*/
|
|
3177
|
+
var conversationSnapshotShape = (0, _orkestrel_contract.objectShape)({
|
|
3178
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
3179
|
+
sections: (0, _orkestrel_contract.arrayShape)(sectionShape),
|
|
3180
|
+
messages: (0, _orkestrel_contract.arrayShape)(messageShape),
|
|
3181
|
+
judgments: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(judgmentShape))
|
|
3182
|
+
});
|
|
3183
|
+
// src/core/conversations/contracts.ts
|
|
3184
|
+
/** Reads the faults of a refused judgment. */
|
|
3185
|
+
var judgmentContract = (0, _orkestrel_contract.createContract)(judgmentShape);
|
|
3186
|
+
/** Reads the faults of a refused section. */
|
|
3187
|
+
var sectionContract = (0, _orkestrel_contract.createContract)(sectionShape);
|
|
3188
|
+
/** Reads the faults of a refused conversation snapshot. */
|
|
3189
|
+
var conversationSnapshotContract = (0, _orkestrel_contract.createContract)(conversationSnapshotShape);
|
|
2699
3190
|
// src/core/conversations/validators.ts
|
|
2700
3191
|
/**
|
|
2701
3192
|
* Checks whether a value satisfies the domain conversation-message contract.
|
|
@@ -2703,8 +3194,9 @@ function isConversationError(value) {
|
|
|
2703
3194
|
* @remarks
|
|
2704
3195
|
* Roles belong to MessageRole, image elements are strings, `thinking` is a string, and `call` is a string on any
|
|
2705
3196
|
* role, as the flat Message type admits. Tool arguments may carry non-JSON values, as the
|
|
2706
|
-
* domain type permits; the message wire contract is narrower.
|
|
2707
|
-
*
|
|
3197
|
+
* domain type permits; the message wire contract is narrower. The guard is exact on the message's own
|
|
3198
|
+
* keys: an unknown member, and an own `undefined` optional, return false. Unreadable fields and
|
|
3199
|
+
* untrusted inputs return false.
|
|
2708
3200
|
*
|
|
2709
3201
|
* @param value - The unknown message candidate
|
|
2710
3202
|
* @returns True if the domain message fields are valid; false otherwise
|
|
@@ -2725,7 +3217,33 @@ function isMessage(value) {
|
|
|
2725
3217
|
if (calls !== void 0 && !(0, _orkestrel_contract.arrayOf)(_orkestrel_tool.isToolCall)(calls)) return false;
|
|
2726
3218
|
if (call !== void 0 && !(0, _orkestrel_contract.isString)(call)) return false;
|
|
2727
3219
|
if (images !== void 0 && !(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(images)) return false;
|
|
2728
|
-
|
|
3220
|
+
if (thinking !== void 0 && !(0, _orkestrel_contract.isString)(thinking)) return false;
|
|
3221
|
+
return Object.keys(value).length === 3 + (calls === void 0 ? 0 : 1) + (call === void 0 ? 0 : 1) + (images === void 0 ? 0 : 1) + (thinking === void 0 ? 0 : 1);
|
|
3222
|
+
});
|
|
3223
|
+
return checked.success && checked.value;
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Checks whether a value is a message input a conversation can store: a string `content`, and `calls`,
|
|
3227
|
+
* `call`, `images`, and `thinking` absent or of their declared types.
|
|
3228
|
+
*
|
|
3229
|
+
* @remarks
|
|
3230
|
+
* A hand guard, because `add` checks every message: it reads the members once and composes no record
|
|
3231
|
+
* guard. The role is `requireRole`'s to refuse, so its text can name the role received. Unknown members
|
|
3232
|
+
* are left behind by the store. An unreadable member returns false.
|
|
3233
|
+
*
|
|
3234
|
+
* @param value - The unknown input candidate
|
|
3235
|
+
* @returns True if the input's members are valid; false otherwise
|
|
3236
|
+
* @example
|
|
3237
|
+
* ```ts
|
|
3238
|
+
* isMessageInput({ role: 'user', content: 'hi' }) // true
|
|
3239
|
+
* isMessageInput({ role: 'user' }) // false
|
|
3240
|
+
* ```
|
|
3241
|
+
*/
|
|
3242
|
+
function isMessageInput(value) {
|
|
3243
|
+
const checked = (0, _orkestrel_contract.attempt)(() => {
|
|
3244
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
3245
|
+
const { content, calls, call, images, thinking } = value;
|
|
3246
|
+
return (0, _orkestrel_contract.isString)(content) && (calls === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_tool.isToolCall)(calls)) && (call === void 0 || (0, _orkestrel_contract.isString)(call)) && (images === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(images)) && (thinking === void 0 || (0, _orkestrel_contract.isString)(thinking));
|
|
2729
3247
|
});
|
|
2730
3248
|
return checked.success && checked.value;
|
|
2731
3249
|
}
|
|
@@ -2733,11 +3251,13 @@ function isMessage(value) {
|
|
|
2733
3251
|
* Checks whether a stored judgment carries a valid question and exactly one answer or refusal.
|
|
2734
3252
|
*
|
|
2735
3253
|
* @remarks
|
|
2736
|
-
* Unknown members, malformed members, and unreadable inputs return false
|
|
2737
|
-
* the root judge value types; this storage guard does not
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
3254
|
+
* Unknown members, malformed members, and unreadable inputs return false, the answer's and the
|
|
3255
|
+
* refusal's own included. Answer fields follow the root judge value types; this storage guard does not
|
|
3256
|
+
* impose a wire's probability or candidate limits, and the ties an answer holds to its question run
|
|
3257
|
+
* where a judgment is written, because records written before them can miss them. The `time`, `noul`,
|
|
3258
|
+
* and probability numbers must be finite, because a stored record is owned through JSON, which turns
|
|
3259
|
+
* `NaN` and the infinities into `null`; so every record this guard accepts hydrates through a
|
|
3260
|
+
* `JudgmentManager`.
|
|
2741
3261
|
*
|
|
2742
3262
|
* @param value - The unknown stored record
|
|
2743
3263
|
* @returns True if the record satisfies the judgment contract; false otherwise
|
|
@@ -2755,16 +3275,7 @@ var isJudgment = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.record
|
|
|
2755
3275
|
state: _orkestrel_contract.isString,
|
|
2756
3276
|
time: _orkestrel_contract.isFiniteNumber,
|
|
2757
3277
|
usage: _orkestrel_budget.isTokenUsage,
|
|
2758
|
-
answer:
|
|
2759
|
-
form: (0, _orkestrel_contract.literalOf)("noul"),
|
|
2760
|
-
noul: _orkestrel_contract.isFiniteNumber
|
|
2761
|
-
}), (0, _orkestrel_contract.objectOf)({
|
|
2762
|
-
form: (0, _orkestrel_contract.literalOf)("score"),
|
|
2763
|
-
probabilities: (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isFiniteNumber)
|
|
2764
|
-
}), (0, _orkestrel_contract.objectOf)({
|
|
2765
|
-
form: (0, _orkestrel_contract.literalOf)("choice"),
|
|
2766
|
-
probabilities: (0, _orkestrel_contract.whereOf)(_orkestrel_contract.isRecord, (value) => Object.values(value).every(_orkestrel_contract.isFiniteNumber))
|
|
2767
|
-
})))
|
|
3278
|
+
answer: isJudgeAnswer
|
|
2768
3279
|
}, ["usage"]), (0, _orkestrel_contract.recordOf)({
|
|
2769
3280
|
id: _orkestrel_contract.isString,
|
|
2770
3281
|
question: isJudgeQuestion,
|
|
@@ -2773,7 +3284,7 @@ var isJudgment = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.record
|
|
|
2773
3284
|
state: _orkestrel_contract.isString,
|
|
2774
3285
|
time: _orkestrel_contract.isFiniteNumber,
|
|
2775
3286
|
usage: _orkestrel_budget.isTokenUsage,
|
|
2776
|
-
refusal: (0, _orkestrel_contract.
|
|
3287
|
+
refusal: (0, _orkestrel_contract.recordOf)({ missing: (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString) })
|
|
2777
3288
|
}, ["usage"]));
|
|
2778
3289
|
/**
|
|
2779
3290
|
* Checks whether an `unknown` is structurally a {@link Section} record — a `string` `id` and
|
|
@@ -2785,8 +3296,8 @@ var isJudgment = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.record
|
|
|
2785
3296
|
* A total guard (it never throws — untrusted input, a throwing getter included, returns
|
|
2786
3297
|
* `false`). It checks the shape of the section: a record with a `string` `id`, a `string`
|
|
2787
3298
|
* `summary`, and a dense `messages` array every element of which is a valid {@link Message}
|
|
2788
|
-
* record ({@link isMessage}); a sparse `messages` array is refused. Enough to
|
|
2789
|
-
* the {@link Section} type at a storage boundary without a cast.
|
|
3299
|
+
* record ({@link isMessage}), and no other member; a sparse `messages` array is refused. Enough to
|
|
3300
|
+
* safely impose the {@link Section} type at a storage boundary without a cast.
|
|
2790
3301
|
*
|
|
2791
3302
|
* @param value - The value to test (one element of a snapshot's `sections` array)
|
|
2792
3303
|
* @returns True if `value` has the structural shape of a {@link Section}; false otherwise
|
|
@@ -2799,11 +3310,11 @@ var isJudgment = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.record
|
|
|
2799
3310
|
* ```
|
|
2800
3311
|
*/
|
|
2801
3312
|
function isSection(value) {
|
|
2802
|
-
return (0, _orkestrel_contract.
|
|
2803
|
-
|
|
2804
|
-
summary
|
|
2805
|
-
|
|
2806
|
-
})
|
|
3313
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
3314
|
+
if (!(0, _orkestrel_contract.isRecord)(value) || Object.keys(value).length !== 3) return false;
|
|
3315
|
+
const { id, summary, messages } = value;
|
|
3316
|
+
return (0, _orkestrel_contract.isString)(id) && (0, _orkestrel_contract.isString)(summary) && (0, _orkestrel_contract.arrayOf)(isMessage)(messages);
|
|
3317
|
+
});
|
|
2807
3318
|
}
|
|
2808
3319
|
/**
|
|
2809
3320
|
* Narrows an `unknown` to a {@link ConversationSnapshot} — a `string` `id` and valid
|
|
@@ -2819,12 +3330,12 @@ function isSection(value) {
|
|
|
2819
3330
|
* valid {@link Message} ({@link isMessage}), and optional valid `judgments` — enough to safely
|
|
2820
3331
|
* impose the
|
|
2821
3332
|
* {@link ConversationSnapshot} type at a storage boundary without a cast. The structural twin of
|
|
2822
|
-
* {@link import('@orkestrel/workspace').isWorkspaceSnapshot}.
|
|
2823
|
-
* 0.0.
|
|
2824
|
-
* a missing / non-string `id`, a non-array `sections` /
|
|
2825
|
-
* element)
|
|
3333
|
+
* {@link import('@orkestrel/workspace').isWorkspaceSnapshot}. The guard is exact: an unknown member, a
|
|
3334
|
+
* 0.0.35 snapshot's conversation `summary` included, and an own `undefined` `judgments` return
|
|
3335
|
+
* `false`, as a malformed blob (a non-record, a missing / non-string `id`, a non-array `sections` /
|
|
3336
|
+
* `messages`, or any malformed element) does, so a
|
|
2826
3337
|
* {@link import('./stores/DatabaseConversationStore.js').DatabaseConversationStore}
|
|
2827
|
-
* read
|
|
3338
|
+
* read refuses it rather than seating a broken conversation. The guard is structural: it admits a
|
|
2828
3339
|
* stored `system` role and a repeated id, which `requireConversationSnapshot` and the
|
|
2829
3340
|
* {@link import('./Conversation.js').Conversation} constructor then refuse as the stored-data
|
|
2830
3341
|
* check a conversation runs at hydrate.
|
|
@@ -2840,12 +3351,11 @@ function isSection(value) {
|
|
|
2840
3351
|
* ```
|
|
2841
3352
|
*/
|
|
2842
3353
|
function isConversationSnapshot(value) {
|
|
2843
|
-
return (0, _orkestrel_contract.
|
|
2844
|
-
|
|
2845
|
-
sections
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
}, ["judgments"])(value);
|
|
3354
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
3355
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
3356
|
+
const { id, sections, messages, judgments } = value;
|
|
3357
|
+
return Object.keys(value).length === (judgments === void 0 ? 3 : 4) && (0, _orkestrel_contract.isString)(id) && (0, _orkestrel_contract.arrayOf)(isSection)(sections) && (0, _orkestrel_contract.arrayOf)(isMessage)(messages) && (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.arrayOf)(isJudgment))(judgments);
|
|
3358
|
+
});
|
|
2849
3359
|
}
|
|
2850
3360
|
/**
|
|
2851
3361
|
* Checks whether a value carries the {@link ConversationInterface} members a run reads: a string
|
|
@@ -2873,6 +3383,50 @@ function isConversation(value) {
|
|
|
2873
3383
|
}
|
|
2874
3384
|
// src/core/conversations/helpers.ts
|
|
2875
3385
|
/**
|
|
3386
|
+
* Reads what a judge result holds for one question, from its own keys alone.
|
|
3387
|
+
*
|
|
3388
|
+
* @param result - The reported answers and refusals
|
|
3389
|
+
* @param id - The question's id
|
|
3390
|
+
* @returns The answer and the refusal, each undefined when the result holds none
|
|
3391
|
+
* @example
|
|
3392
|
+
* ```ts
|
|
3393
|
+
* readJudgeOutcome({ model: 'm', answers: {} }, 'q') // { answer: undefined, refusal: undefined }
|
|
3394
|
+
* ```
|
|
3395
|
+
*/
|
|
3396
|
+
function readJudgeOutcome(result, id) {
|
|
3397
|
+
return {
|
|
3398
|
+
answer: Object.hasOwn(result.answers, id) ? result.answers[id] : void 0,
|
|
3399
|
+
refusal: result.refusals !== void 0 && Object.hasOwn(result.refusals, id) ? result.refusals[id] : void 0
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Returns after checking a complete judge result answered or refused every question it was asked, or
|
|
3404
|
+
* throws a coded error naming those it left out.
|
|
3405
|
+
*
|
|
3406
|
+
* @remarks
|
|
3407
|
+
* A cancelled call's partial is not complete, so `JudgmentManager` reads this on a settled result only,
|
|
3408
|
+
* and {@link buildJudgments} stores whatever a partial holds.
|
|
3409
|
+
*
|
|
3410
|
+
* An undefined value is no answer, as {@link buildJudgments} reads it, so a key alone doesn't count.
|
|
3411
|
+
*
|
|
3412
|
+
* @param request - The request whose question keys must be answered
|
|
3413
|
+
* @param result - The result the judge settled
|
|
3414
|
+
* @throws ConversationError Thrown when a question has neither an answer nor a refusal (code `'JUDGMENT'`), naming them under `context.questions`
|
|
3415
|
+
* @example
|
|
3416
|
+
* ```ts
|
|
3417
|
+
* requireJudgeResult({ state: 'text', questions: { a: { form: 'noul' } } }, { model: 'm', answers: {} })
|
|
3418
|
+
* // throws ConversationError ('JUDGMENT')
|
|
3419
|
+
* ```
|
|
3420
|
+
*/
|
|
3421
|
+
function requireJudgeResult(request, result) {
|
|
3422
|
+
const left = Object.keys(request.questions).filter((id) => {
|
|
3423
|
+
const { answer, refusal } = readJudgeOutcome(result, id);
|
|
3424
|
+
return answer === void 0 && refusal === void 0;
|
|
3425
|
+
});
|
|
3426
|
+
if (left.length === 0) return;
|
|
3427
|
+
throw new ConversationError("JUDGMENT", `conversation error: the judge left ${left.map((id) => (0, _orkestrel_contract.preview)(id)).join(", ")} neither answered nor refused; a judge must answer or refuse every question it is asked`, { context: { questions: left } });
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
2876
3430
|
* Builds records for answered or refused request keys, attaching usage only for a single question.
|
|
2877
3431
|
*
|
|
2878
3432
|
* @param request - The request whose question keys define record order
|
|
@@ -2889,8 +3443,7 @@ function isConversation(value) {
|
|
|
2889
3443
|
function buildJudgments(request, result, sources, state, model) {
|
|
2890
3444
|
const single = Object.keys(request.questions).length === 1;
|
|
2891
3445
|
return Object.entries(request.questions).flatMap(([id, question]) => {
|
|
2892
|
-
const answer =
|
|
2893
|
-
const refusal = result.refusals !== void 0 && Object.hasOwn(result.refusals, id) ? result.refusals[id] : void 0;
|
|
3446
|
+
const { answer, refusal } = readJudgeOutcome(result, id);
|
|
2894
3447
|
if (answer === void 0 && refusal === void 0) return [];
|
|
2895
3448
|
return [{
|
|
2896
3449
|
id,
|
|
@@ -3002,43 +3555,74 @@ function requireRole(role) {
|
|
|
3002
3555
|
return stored;
|
|
3003
3556
|
}
|
|
3004
3557
|
/**
|
|
3558
|
+
* Returns a message input after checking its role and its members, or throws a coded error.
|
|
3559
|
+
*
|
|
3560
|
+
* @remarks
|
|
3561
|
+
* The role goes first, through {@link requireRole}, so its refusal names what it received. The members
|
|
3562
|
+
* cross {@link isMessageInput}, and `audit` runs only after it refused, so a stored message costs one
|
|
3563
|
+
* guard read. Tool-call arguments must also be JSON values before a message is stored.
|
|
3564
|
+
*
|
|
3565
|
+
* @param input - The message input to check
|
|
3566
|
+
* @param contract - The message input contract, whose `audit` is read only on a refusal
|
|
3567
|
+
* @returns The input, narrowed
|
|
3568
|
+
* @throws ConversationError Thrown when the role is not a `ConversationRole` (code `'ROLE'`) or a member fails `isMessageInput` (code `'MESSAGE'`), with the faults under `context.faults`
|
|
3569
|
+
*
|
|
3570
|
+
* @example
|
|
3571
|
+
* ```ts
|
|
3572
|
+
* requireMessageInput({ role: 'user', content: 'hi' }, messageInputContract) // the input
|
|
3573
|
+
* requireMessageInput({ role: 'user' }, messageInputContract) // throws ConversationError ('MESSAGE')
|
|
3574
|
+
* ```
|
|
3575
|
+
*/
|
|
3576
|
+
function requireMessageInput(input, contract) {
|
|
3577
|
+
requireRole((0, _orkestrel_contract.isRecord)(input) ? input.role : void 0);
|
|
3578
|
+
if (isMessageInput(input) && (input.calls === void 0 || input.calls.every((call) => (0, _orkestrel_contract.isJSONValue)(call.arguments)))) return input;
|
|
3579
|
+
const faults = contract.audit(input);
|
|
3580
|
+
throw new ConversationError("MESSAGE", `a message input must carry string content, and calls, call, images, and thinking of their declared types\n${(0, _orkestrel_contract.describeFaults)(faults, contract.schema)}`, { context: { faults } });
|
|
3581
|
+
}
|
|
3582
|
+
/**
|
|
3005
3583
|
* Returns a conversation snapshot after checking its shape, its stored roles, and its ids, or
|
|
3006
3584
|
* throws a coded error.
|
|
3007
3585
|
*
|
|
3008
3586
|
* @remarks
|
|
3009
|
-
* The check reads
|
|
3010
|
-
*
|
|
3011
|
-
*
|
|
3012
|
-
*
|
|
3013
|
-
* `ConversationRole
|
|
3587
|
+
* The check reads every own member of `value` once and builds a candidate from those reads: each
|
|
3588
|
+
* array member becomes a fresh array holding the same element objects, and a member that is not an
|
|
3589
|
+
* array stays as read, so the guard refuses it. The candidate must pass `isConversationSnapshot`, which
|
|
3590
|
+
* is exact, so an unknown key refuses rather than being dropped; every section message and live message
|
|
3591
|
+
* must play a `ConversationRole`; and no id may repeat across section ids, section message ids, and live
|
|
3014
3592
|
* message ids. The `'ROLE'` refusal of a `system` message names the row a snapshot written before
|
|
3015
3593
|
* 0.0.35 can hold, and each `'SNAPSHOT'` refusal asks for what `snapshot()` returned. The candidate
|
|
3016
3594
|
* is returned, never `value`, so the check and the seating share one read. Element objects are kept
|
|
3017
3595
|
* by reference, so a host is trusted not to edit a seated message object or to back its members
|
|
3018
|
-
* with accessors.
|
|
3596
|
+
* with accessors. The contract's `audit` runs only after the guard refused, to fill `context.faults`.
|
|
3019
3597
|
*
|
|
3020
3598
|
* @param value - The snapshot to check, typically a stored read
|
|
3021
|
-
* @
|
|
3022
|
-
* @
|
|
3599
|
+
* @param contract - The snapshot contract, whose `audit` is read only on a refusal
|
|
3600
|
+
* @returns The candidate snapshot
|
|
3601
|
+
* @throws ConversationError Thrown when the value fails `isConversationSnapshot` (code `'SNAPSHOT'`, its faults under `context.faults`), repeats an id (code `'SNAPSHOT'`), or holds a message whose role is not a `ConversationRole` (code `'ROLE'`)
|
|
3023
3602
|
*
|
|
3024
3603
|
* @example
|
|
3025
3604
|
* ```ts
|
|
3026
|
-
* requireConversationSnapshot({ id: 'c1', sections: [], messages: [] }) // { id: 'c1', sections: [], messages: [] }
|
|
3027
|
-
* requireConversationSnapshot({ id: 'c1', sections: 'nope', messages: [] }) // throws ConversationError ('SNAPSHOT')
|
|
3605
|
+
* requireConversationSnapshot({ id: 'c1', sections: [], messages: [] }, conversationSnapshotContract) // { id: 'c1', sections: [], messages: [] }
|
|
3606
|
+
* requireConversationSnapshot({ id: 'c1', sections: 'nope', messages: [] }, conversationSnapshotContract) // throws ConversationError ('SNAPSHOT')
|
|
3028
3607
|
* ```
|
|
3029
3608
|
*/
|
|
3030
|
-
function requireConversationSnapshot(value) {
|
|
3609
|
+
function requireConversationSnapshot(value, contract) {
|
|
3031
3610
|
const candidate = (0, _orkestrel_contract.attempt)(() => {
|
|
3032
3611
|
if (!(0, _orkestrel_contract.isRecord)(value)) return void 0;
|
|
3033
|
-
const
|
|
3612
|
+
const read = { ...value };
|
|
3613
|
+
const { sections, messages, judgments } = read;
|
|
3034
3614
|
return {
|
|
3035
|
-
|
|
3615
|
+
...read,
|
|
3036
3616
|
sections: (0, _orkestrel_contract.isArray)(sections) ? Array.from(sections) : sections,
|
|
3037
3617
|
messages: (0, _orkestrel_contract.isArray)(messages) ? Array.from(messages) : messages,
|
|
3038
3618
|
...judgments === void 0 ? {} : { judgments: (0, _orkestrel_contract.isArray)(judgments) ? Array.from(judgments) : judgments }
|
|
3039
3619
|
};
|
|
3040
3620
|
});
|
|
3041
|
-
if (!candidate.success
|
|
3621
|
+
if (!candidate.success) throw new ConversationError("SNAPSHOT", "a conversation snapshot must be readable; pass what snapshot() returned", { cause: candidate.error });
|
|
3622
|
+
if (!isConversationSnapshot(candidate.value)) {
|
|
3623
|
+
const faults = contract.audit(candidate.value);
|
|
3624
|
+
throw new ConversationError("SNAPSHOT", `a conversation snapshot must carry a string id, valid sections and messages, valid judgments when present, and no other member; pass what snapshot() returned\n${(0, _orkestrel_contract.describeFaults)(faults, contract.schema)}`, { context: { faults } });
|
|
3625
|
+
}
|
|
3042
3626
|
const snapshot = candidate.value;
|
|
3043
3627
|
snapshot.sections.flatMap((section) => section.messages).concat(snapshot.messages).forEach((message) => {
|
|
3044
3628
|
if (message.role === "system") throw new ConversationError("ROLE", "a conversation snapshot holds a system message; a snapshot written before 0.0.35 can hold a system row; remove it before loading");
|
|
@@ -3056,15 +3640,15 @@ function requireConversationSnapshot(value) {
|
|
|
3056
3640
|
return snapshot;
|
|
3057
3641
|
}
|
|
3058
3642
|
/**
|
|
3059
|
-
* Returns a `sections` cap after checking
|
|
3643
|
+
* Returns a `sections` cap after checking it is a positive safe integer, or throws a coded error.
|
|
3060
3644
|
*
|
|
3061
3645
|
* @remarks
|
|
3062
|
-
* A
|
|
3063
|
-
* is
|
|
3646
|
+
* A fraction, positive infinity, and `NaN` are refused with `0` and a negative, because a count of
|
|
3647
|
+
* sections is a whole number. An absent cap leaves the sections list unbounded.
|
|
3064
3648
|
*
|
|
3065
3649
|
* @param cap - The cap to check, or `undefined` for no cap
|
|
3066
3650
|
* @returns The cap unchanged, or `undefined` when none was supplied
|
|
3067
|
-
* @throws ConversationError Thrown when the cap
|
|
3651
|
+
* @throws ConversationError Thrown when the cap is not a positive safe integer (code `'SECTIONS'`)
|
|
3068
3652
|
*
|
|
3069
3653
|
* @example
|
|
3070
3654
|
* ```ts
|
|
@@ -3074,7 +3658,7 @@ function requireConversationSnapshot(value) {
|
|
|
3074
3658
|
* ```
|
|
3075
3659
|
*/
|
|
3076
3660
|
function requireSectionsCap(cap) {
|
|
3077
|
-
if (cap !== void 0 && !(cap
|
|
3661
|
+
if (cap !== void 0 && !isPositiveSafeInteger(cap)) throw new ConversationError("SECTIONS", "a sections cap must be a positive safe integer", { context: { sections: cap } });
|
|
3078
3662
|
return cap;
|
|
3079
3663
|
}
|
|
3080
3664
|
/**
|
|
@@ -3232,9 +3816,16 @@ var JudgmentManager = class {
|
|
|
3232
3816
|
const rendered = typeof state === "string" ? state : JSON.stringify(state);
|
|
3233
3817
|
const origins = [...sources];
|
|
3234
3818
|
const entries = (0, _orkestrel_contract.isRecord)(request.questions) ? Object.entries(request.questions) : [];
|
|
3819
|
+
if (entries.length === 0) throw new ConversationError("JUDGMENT", "conversation error: a judge request needs at least one question, keyed by id");
|
|
3235
3820
|
const checked = entries.map(([id, raw]) => {
|
|
3236
3821
|
const question = copyJSON(raw);
|
|
3237
|
-
if (!isJudgeQuestion(question))
|
|
3822
|
+
if (!isJudgeQuestion(question)) {
|
|
3823
|
+
const faults = judgeQuestionContract.audit(question);
|
|
3824
|
+
throw new ConversationError("JUDGMENT", `conversation error: ${describeQuestionRefusal(id, faults, judgeQuestionContract.schema)}`, { context: {
|
|
3825
|
+
question: id,
|
|
3826
|
+
faults
|
|
3827
|
+
} });
|
|
3828
|
+
}
|
|
3238
3829
|
return [id, question];
|
|
3239
3830
|
});
|
|
3240
3831
|
const matched = new Map(checked.flatMap(([id, question]) => {
|
|
@@ -3259,6 +3850,7 @@ var JudgmentManager = class {
|
|
|
3259
3850
|
});
|
|
3260
3851
|
try {
|
|
3261
3852
|
const result = await judge.ask(unmatched, signal);
|
|
3853
|
+
requireJudgeResult(unmatched, result);
|
|
3262
3854
|
return this.add(buildJudgments(unmatched, result, origins, rendered, judge.model));
|
|
3263
3855
|
} catch (error) {
|
|
3264
3856
|
if (isJudgeAbortError(error)) this.add(buildJudgments(unmatched, error.partial, origins, rendered, judge.model));
|
|
@@ -3267,10 +3859,19 @@ var JudgmentManager = class {
|
|
|
3267
3859
|
}
|
|
3268
3860
|
#store(input) {
|
|
3269
3861
|
const copy = copyJSON(input);
|
|
3862
|
+
if ((0, _orkestrel_contract.isRecord)(copy) && copy.answer !== void 0 && !judgeAnswerContract.is(copy.answer)) {
|
|
3863
|
+
const faults = judgeAnswerContract.audit(copy.answer);
|
|
3864
|
+
throw new ConversationError("JUDGMENT", `conversation error: a judgment answer must satisfy its shape\n${(0, _orkestrel_contract.describeFaults)(faults, judgeAnswerContract.schema)}`, { context: { faults } });
|
|
3865
|
+
}
|
|
3270
3866
|
const judgment = requireJudgment(freezeJSON((0, _orkestrel_contract.isRecord)(copy) ? {
|
|
3271
3867
|
...copy,
|
|
3272
3868
|
time: Date.now()
|
|
3273
3869
|
} : copy));
|
|
3870
|
+
const faults = judgment.answer === void 0 ? [] : scanJudgmentTies(judgment.question, judgment.answer);
|
|
3871
|
+
if (faults.length > 0) throw new ConversationError("JUDGMENT", `conversation error: judgment ${judgment.id} answers its question wrongly\n${(0, _orkestrel_contract.describeFaults)(faults, judgeAnswerContract.schema)}`, { context: {
|
|
3872
|
+
question: judgment.id,
|
|
3873
|
+
faults
|
|
3874
|
+
} });
|
|
3274
3875
|
this.#judgments.set(judgment.id, judgment);
|
|
3275
3876
|
return judgment;
|
|
3276
3877
|
}
|
|
@@ -3355,7 +3956,7 @@ var Conversation = class {
|
|
|
3355
3956
|
#pending = /* @__PURE__ */ new Set();
|
|
3356
3957
|
constructor(options) {
|
|
3357
3958
|
const supplied = options?.snapshot;
|
|
3358
|
-
const snapshot = supplied === void 0 ? void 0 : requireConversationSnapshot(supplied);
|
|
3959
|
+
const snapshot = supplied === void 0 ? void 0 : requireConversationSnapshot(supplied, conversationSnapshotContract);
|
|
3359
3960
|
this.#judgments = new JudgmentManager(snapshot?.judgments);
|
|
3360
3961
|
this.#id = snapshot?.id ?? options?.id ?? crypto.randomUUID();
|
|
3361
3962
|
this.#emitter = new _orkestrel_emitter.Emitter(options);
|
|
@@ -3387,10 +3988,10 @@ var Conversation = class {
|
|
|
3387
3988
|
}
|
|
3388
3989
|
add(input) {
|
|
3389
3990
|
if ((0, _orkestrel_contract.isArray)(input)) {
|
|
3390
|
-
input.forEach((one) =>
|
|
3991
|
+
input.forEach((one) => requireMessageInput(one, messageInputContract));
|
|
3391
3992
|
return input.map((one) => this.#create(one));
|
|
3392
3993
|
}
|
|
3393
|
-
return this.#create(input);
|
|
3994
|
+
return this.#create(requireMessageInput(input, messageInputContract));
|
|
3394
3995
|
}
|
|
3395
3996
|
message(id) {
|
|
3396
3997
|
return this.#messages.get(id);
|
|
@@ -3499,9 +4100,13 @@ var Conversation = class {
|
|
|
3499
4100
|
#create(input) {
|
|
3500
4101
|
const message = {
|
|
3501
4102
|
id: crypto.randomUUID(),
|
|
3502
|
-
role:
|
|
4103
|
+
role: input.role,
|
|
3503
4104
|
content: input.content,
|
|
3504
|
-
...input.calls === void 0 ? {} : { calls: input.calls }
|
|
4105
|
+
...input.calls === void 0 ? {} : { calls: input.calls.map(({ id, name, arguments: arguments_ }) => ({
|
|
4106
|
+
id,
|
|
4107
|
+
name,
|
|
4108
|
+
arguments: arguments_
|
|
4109
|
+
})) },
|
|
3505
4110
|
...input.call === void 0 ? {} : { call: input.call },
|
|
3506
4111
|
...input.images === void 0 ? {} : { images: input.images },
|
|
3507
4112
|
...input.thinking === void 0 ? {} : { thinking: input.thinking }
|
|
@@ -3523,7 +4128,8 @@ var Conversation = class {
|
|
|
3523
4128
|
* - **Registry.** Conversations live in an insertion-ordered `Map` keyed by `id`. `add(input?)`
|
|
3524
4129
|
* mints a {@link Conversation} (its `id` from `input` or `crypto.randomUUID()`), flowing the
|
|
3525
4130
|
* default `#summarize` / `#keep` of the manager in unless the `input` overrides them, and stores
|
|
3526
|
-
* it (an already-present `id`
|
|
4131
|
+
* it (an already-present `id` throws a `ConversationError` coded `'ID'`, because replacing a live
|
|
4132
|
+
* conversation would orphan its turns). `count` is the map size,
|
|
3527
4133
|
* `conversation(id)` looks one up, `conversations()` lists them in insertion order.
|
|
3528
4134
|
* - **Active pointer.** `active` is the active conversation (the conversation an agent run binds
|
|
3529
4135
|
* when it names none), `undefined` until the first `add` (which auto-activates it — a registry
|
|
@@ -3572,6 +4178,8 @@ var ConversationManager = class {
|
|
|
3572
4178
|
return [...this.#conversations.values()];
|
|
3573
4179
|
}
|
|
3574
4180
|
add(input) {
|
|
4181
|
+
const held = input?.id ?? input?.snapshot?.id;
|
|
4182
|
+
if (held !== void 0 && this.#conversations.has(held)) throw new ConversationError("ID", `conversation ${(0, _orkestrel_contract.preview)(held)} is already live; pick another id, or remove it first`, { context: { conversation: held } });
|
|
3575
4183
|
const sections = input?.sections ?? this.#sections;
|
|
3576
4184
|
const summarize = input?.summarize ?? this.#summarize;
|
|
3577
4185
|
const conversation = new Conversation({
|
|
@@ -3607,7 +4215,8 @@ var ConversationManager = class {
|
|
|
3607
4215
|
this.#active = id;
|
|
3608
4216
|
return registered;
|
|
3609
4217
|
}
|
|
3610
|
-
if (snapshot === void 0
|
|
4218
|
+
if (snapshot === void 0) return void 0;
|
|
4219
|
+
if (snapshot.id !== id) throw new ConversationError("SNAPSHOT", `the store answered ${(0, _orkestrel_contract.preview)(id)} with the snapshot of ${(0, _orkestrel_contract.preview)(snapshot.id)}; a store returns only the row of the id it is asked`);
|
|
3611
4220
|
const conversation = this.add({ snapshot });
|
|
3612
4221
|
this.#active = conversation.id;
|
|
3613
4222
|
return conversation;
|
|
@@ -3740,8 +4349,9 @@ var MemoryConversationStore = class {
|
|
|
3740
4349
|
* the row `{ id: snapshot.id, snapshot }`.
|
|
3741
4350
|
* - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
|
|
3742
4351
|
* a {@link ConversationSnapshot} ({@link import('../validators.js').isConversationSnapshot} — the
|
|
3743
|
-
* total guard for an untrusted storage read), or `undefined`
|
|
3744
|
-
* malformed
|
|
4352
|
+
* total guard for an untrusted storage read), or `undefined` only when no row is stored. A row whose
|
|
4353
|
+
* blob is malformed or names another id throws a `ConversationError` coded `'SNAPSHOT'`, so `open`
|
|
4354
|
+
* and `create` stop rather than overwrite it, and `delete` still removes it.
|
|
3745
4355
|
* - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
|
|
3746
4356
|
*
|
|
3747
4357
|
* Unlike a session store there is no idle-TTL / eviction — a persisted conversation lives until an
|
|
@@ -3780,18 +4390,35 @@ var DatabaseConversationStore = class {
|
|
|
3780
4390
|
*
|
|
3781
4391
|
* @remarks
|
|
3782
4392
|
* The column is an untrusted storage read, so a blob that fails
|
|
3783
|
-
* {@link import('../validators.js').isConversationSnapshot}, or whose own `id` is not `id`,
|
|
3784
|
-
*
|
|
4393
|
+
* {@link import('../validators.js').isConversationSnapshot}, or whose own `id` is not `id`, throws
|
|
4394
|
+
* rather than reading as absent, which would let a later `set` overwrite it.
|
|
3785
4395
|
*
|
|
3786
4396
|
* @param id - The conversation id to resolve (the row key)
|
|
3787
|
-
* @returns The persisted snapshot, or `undefined` when no row is stored
|
|
3788
|
-
*
|
|
4397
|
+
* @returns The persisted snapshot, or `undefined` when no row is stored
|
|
4398
|
+
* @throws {ConversationError} Thrown when the row's blob is malformed or names another id (code `'SNAPSHOT'`), with the faults under `context.faults`
|
|
3789
4399
|
*/
|
|
3790
4400
|
async get(id) {
|
|
3791
4401
|
const row = await this.#table.get(id);
|
|
3792
4402
|
if (row === void 0) return void 0;
|
|
3793
4403
|
const snapshot = row.snapshot;
|
|
3794
|
-
|
|
4404
|
+
if (isConversationSnapshot(snapshot)) {
|
|
4405
|
+
if (snapshot.id === id) return snapshot;
|
|
4406
|
+
const faults = [{
|
|
4407
|
+
reason: "type",
|
|
4408
|
+
path: ["id"],
|
|
4409
|
+
expected: "literal",
|
|
4410
|
+
received: (0, _orkestrel_contract.preview)(snapshot.id)
|
|
4411
|
+
}];
|
|
4412
|
+
throw new ConversationError("SNAPSHOT", describeSnapshotRefusal("conversation", id, faults), { context: {
|
|
4413
|
+
id,
|
|
4414
|
+
faults
|
|
4415
|
+
} });
|
|
4416
|
+
}
|
|
4417
|
+
const faults = conversationSnapshotContract.audit(snapshot);
|
|
4418
|
+
throw new ConversationError("SNAPSHOT", describeSnapshotRefusal("conversation", id, faults), { context: {
|
|
4419
|
+
id,
|
|
4420
|
+
faults
|
|
4421
|
+
} });
|
|
3795
4422
|
}
|
|
3796
4423
|
/**
|
|
3797
4424
|
* Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is
|
|
@@ -3799,8 +4426,14 @@ var DatabaseConversationStore = class {
|
|
|
3799
4426
|
*
|
|
3800
4427
|
* @param snapshot - The snapshot to store, keyed by its `id`
|
|
3801
4428
|
* @returns A promise that resolves after the row is written
|
|
4429
|
+
* @throws {ConversationError} Thrown before any write when the snapshot fails the guard `get` reads (code `'SNAPSHOT'`), so no row is written that `get` refuses
|
|
3802
4430
|
*/
|
|
3803
4431
|
async set(snapshot) {
|
|
4432
|
+
if (!isConversationSnapshot(snapshot)) {
|
|
4433
|
+
const unchecked = snapshot;
|
|
4434
|
+
const faults = conversationSnapshotContract.audit(unchecked);
|
|
4435
|
+
throw new ConversationError("SNAPSHOT", `a conversation store writes only what snapshot() returned\n${(0, _orkestrel_contract.describeFaults)(faults, conversationSnapshotContract.schema)}`, { context: { faults } });
|
|
4436
|
+
}
|
|
3804
4437
|
await this.#table.set({
|
|
3805
4438
|
id: snapshot.id,
|
|
3806
4439
|
snapshot
|
|
@@ -3886,7 +4519,7 @@ function createConversation(options) {
|
|
|
3886
4519
|
* @remarks
|
|
3887
4520
|
* Starts empty; `add(input?)` mints a {@link ConversationInterface} (its `id` from the input
|
|
3888
4521
|
* or a random UUID), flowing the manager's default `summarize` / `keep` in unless the input
|
|
3889
|
-
* overrides them, and stores it (an already-present `id`
|
|
4522
|
+
* overrides them, and stores it (an already-present `id` throws, code `'ID'`) — and
|
|
3890
4523
|
* auto-activates the first one (a registry with conversations always has one `active`); a later
|
|
3891
4524
|
* `add` leaves `active` unchanged. `switch(id)` re-points `active` (an unknown `id` returns
|
|
3892
4525
|
* `undefined`, leaving `active` unchanged — lenient, never throws); `conversation(id)` /
|
|
@@ -4028,18 +4661,21 @@ var SKILL_SECTION_HEADER = "## Skills";
|
|
|
4028
4661
|
// src/core/contexts/errors.ts
|
|
4029
4662
|
/**
|
|
4030
4663
|
* Reports a context snapshot that `isMemoSnapshot` refuses, thrown by the construction of a
|
|
4031
|
-
* {@link import('./memos/MemoManager.js').MemoManager} before it seats any state
|
|
4032
|
-
* machine-readable `code`
|
|
4664
|
+
* {@link import('./memos/MemoManager.js').MemoManager} before it seats any state or by a memo store,
|
|
4665
|
+
* or image files a build has no user turn to carry, carrying the machine-readable `code`
|
|
4666
|
+
* `'SNAPSHOT' | 'IMAGES'`.
|
|
4033
4667
|
*
|
|
4034
4668
|
* @remarks
|
|
4035
4669
|
* A `snapshot` option crosses a foreign boundary (a client, a store, a file), so the manager copies
|
|
4036
4670
|
* it through JSON and checks the copy: a value JSON cannot carry, a revision missing a field, a
|
|
4037
4671
|
* non-finite `time`, a sparse `memos` array, a non-string `id`, or a non-array `memos` is a
|
|
4038
|
-
* programmer error and throws this with `'SNAPSHOT'
|
|
4039
|
-
*
|
|
4672
|
+
* programmer error and throws this with `'SNAPSHOT'`, its `context` holding the `faults`. A build
|
|
4673
|
+
* over a conversation that holds turns and no user turn, with image files in the active workspace,
|
|
4674
|
+
* throws `'IMAGES'` rather than dropping them. Narrow a caught value with {@link isContextError} and
|
|
4675
|
+
* branch on `error.code`.
|
|
4040
4676
|
*/
|
|
4041
4677
|
var ContextError = class extends Error {
|
|
4042
|
-
/** Names the machine-readable condition — `'SNAPSHOT'`: a snapshot
|
|
4678
|
+
/** Names the machine-readable condition — `'SNAPSHOT'`: a snapshot that is not a memo snapshot; `'IMAGES'`: image files with no user turn to carry them. */
|
|
4043
4679
|
code;
|
|
4044
4680
|
/** Holds the values that locate the failure, or `undefined` when none were supplied. */
|
|
4045
4681
|
context;
|
|
@@ -4077,7 +4713,8 @@ var ContextError = class extends Error {
|
|
|
4077
4713
|
function isContextError(value) {
|
|
4078
4714
|
return (0, _orkestrel_contract.holds)(() => {
|
|
4079
4715
|
if (!(value instanceof Error && _orkestrel_contract.INTRINSICS.prototype(value) !== Error.prototype && _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.ContextError"))?.value === value)) return false;
|
|
4080
|
-
|
|
4716
|
+
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
4717
|
+
if (code !== "SNAPSHOT" && code !== "IMAGES") return false;
|
|
4081
4718
|
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
4082
4719
|
return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
|
|
4083
4720
|
});
|
|
@@ -4310,13 +4947,14 @@ function attachImages(message, payloads) {
|
|
|
4310
4947
|
* @remarks
|
|
4311
4948
|
* Pure and total: the conversation and its messages are never mutated, and the returned array
|
|
4312
4949
|
* replaces exactly the one target message with the copy {@link attachImages} builds. Empty
|
|
4313
|
-
* data returns the conversation unchanged
|
|
4314
|
-
*
|
|
4315
|
-
*
|
|
4950
|
+
* data returns the conversation unchanged. Data with no user message to carry it refuses, an empty
|
|
4951
|
+
* conversation included, because leaving the images out would run without them. A hole in a sparse
|
|
4952
|
+
* conversation is not a user message.
|
|
4316
4953
|
*
|
|
4317
4954
|
* @param conversation - The messages to attach into (left unchanged)
|
|
4318
4955
|
* @param payloads - The base64 image data to attach
|
|
4319
4956
|
* @returns The conversation with its last user message replaced by the carrying copy
|
|
4957
|
+
* @throws ContextError Thrown when there is data and no user message to carry it (code `'IMAGES'`)
|
|
4320
4958
|
*
|
|
4321
4959
|
* @example
|
|
4322
4960
|
* ```ts
|
|
@@ -4327,7 +4965,7 @@ function attachImages(message, payloads) {
|
|
|
4327
4965
|
function attachUserImages(conversation, payloads) {
|
|
4328
4966
|
if (payloads.length === 0) return conversation;
|
|
4329
4967
|
const target = conversation.findLastIndex((message) => message?.role === "user");
|
|
4330
|
-
if (target === -1)
|
|
4968
|
+
if (target === -1) throw new ContextError("IMAGES", `the active workspace holds ${payloads.length} image file(s) and the conversation holds no user turn to carry them; add a user message or scope the files out`);
|
|
4331
4969
|
return conversation.map((message, index) => index === target ? attachImages(message, payloads) : message);
|
|
4332
4970
|
}
|
|
4333
4971
|
/**
|
|
@@ -4401,16 +5039,70 @@ function intersectKeys(parent, child) {
|
|
|
4401
5039
|
function renderIndexEntry(entry) {
|
|
4402
5040
|
return `- ${entry.name}: ${entry.description}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
|
|
4403
5041
|
}
|
|
5042
|
+
// src/core/contexts/shapers.ts
|
|
5043
|
+
/** Describes a stored memo revision exactly. */
|
|
5044
|
+
var memoShape = (0, _orkestrel_contract.objectShape)({
|
|
5045
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
5046
|
+
name: (0, _orkestrel_contract.stringShape)(),
|
|
5047
|
+
description: (0, _orkestrel_contract.stringShape)(),
|
|
5048
|
+
content: (0, _orkestrel_contract.stringShape)(),
|
|
5049
|
+
time: (0, _orkestrel_contract.numberShape)(),
|
|
5050
|
+
origin: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
|
|
5051
|
+
conversation: (0, _orkestrel_contract.stringShape)(),
|
|
5052
|
+
message: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)())
|
|
5053
|
+
}))
|
|
5054
|
+
});
|
|
5055
|
+
/**
|
|
5056
|
+
* Describes a stored memo set exactly: an unknown key refuses.
|
|
5057
|
+
*
|
|
5058
|
+
* @remarks
|
|
5059
|
+
* `isMemoSnapshot` is the door a hydrate and a store read, and this shape fills a refusal's faults. A
|
|
5060
|
+
* lockstep test holds that `is` implies the guard, and on the snapshot fixtures the converse.
|
|
5061
|
+
*/
|
|
5062
|
+
var memoSnapshotShape = (0, _orkestrel_contract.objectShape)({
|
|
5063
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
5064
|
+
memos: (0, _orkestrel_contract.arrayShape)(memoShape)
|
|
5065
|
+
});
|
|
5066
|
+
// src/core/contexts/contracts.ts
|
|
5067
|
+
/** Reads the faults of a refused memo revision. */
|
|
5068
|
+
var memoContract = (0, _orkestrel_contract.createContract)(memoShape);
|
|
5069
|
+
/** Reads the faults of a refused memo snapshot. */
|
|
5070
|
+
var memoSnapshotContract = (0, _orkestrel_contract.createContract)(memoSnapshotShape);
|
|
4404
5071
|
// src/core/contexts/validators.ts
|
|
4405
5072
|
/**
|
|
5073
|
+
* Checks whether a value is a memo's origin: a record with a string `conversation` and an optional
|
|
5074
|
+
* string `message`.
|
|
5075
|
+
*
|
|
5076
|
+
* @remarks
|
|
5077
|
+
* The guard is exact: an unknown member and an own `undefined` `message` return false. Malformed and
|
|
5078
|
+
* unreadable inputs return false.
|
|
5079
|
+
*
|
|
5080
|
+
* @param value - The value to test
|
|
5081
|
+
* @returns True if `value` has the shape of a {@link MemoOrigin}; false otherwise
|
|
5082
|
+
* @example
|
|
5083
|
+
* ```ts
|
|
5084
|
+
* isMemoOrigin({ conversation: 'c1', message: 'm1' }) // true
|
|
5085
|
+
* isMemoOrigin({ conversation: 'c1', extra: true }) // false
|
|
5086
|
+
* ```
|
|
5087
|
+
*/
|
|
5088
|
+
function isMemoOrigin(value) {
|
|
5089
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
5090
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
5091
|
+
const { conversation, message } = value;
|
|
5092
|
+
return (0, _orkestrel_contract.isString)(conversation) && (message === void 0 || (0, _orkestrel_contract.isString)(message)) && Object.keys(value).length === (message === void 0 ? 1 : 2);
|
|
5093
|
+
});
|
|
5094
|
+
}
|
|
5095
|
+
/**
|
|
4406
5096
|
* Checks whether a value is a stored memo revision: a record with a string `id`, `name`,
|
|
4407
5097
|
* `description`, and `content`, a finite `time`, and an optional `origin`.
|
|
4408
5098
|
*
|
|
4409
5099
|
* @remarks
|
|
4410
5100
|
* An `origin`, when present, is a record with a string `conversation` and an optional string
|
|
4411
|
-
* `message`.
|
|
4412
|
-
* is owned through JSON, which turns `NaN` and the
|
|
4413
|
-
* unreadable inputs, a throwing getter or a revoked proxy
|
|
5101
|
+
* `message`. The guard is exact: an unknown member and an own `undefined` optional return false. The
|
|
5102
|
+
* `time` must be finite, because a stored revision is owned through JSON, which turns `NaN` and the
|
|
5103
|
+
* infinities into `null`. Malformed and unreadable inputs, a throwing getter or a revoked proxy
|
|
5104
|
+
* included, return false. Every revision of a hydrate reads this guard, so it counts keys by hand
|
|
5105
|
+
* rather than through a built one, and `memoSnapshotContract` holds it in lockstep.
|
|
4414
5106
|
*
|
|
4415
5107
|
* @param value - The value to test
|
|
4416
5108
|
* @returns True if `value` has the shape of a {@link Memo}; false otherwise
|
|
@@ -4423,24 +5115,20 @@ function renderIndexEntry(entry) {
|
|
|
4423
5115
|
* isMemo({ id: 'm1', name: 'tone', description: 'Prefers short replies.', content: 'Answer briefly.', time: Number.NaN }) // false
|
|
4424
5116
|
* ```
|
|
4425
5117
|
*/
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
conversation: _orkestrel_contract.isString,
|
|
4434
|
-
message: _orkestrel_contract.isString
|
|
4435
|
-
}, ["message"]))
|
|
4436
|
-
}, ["origin"]));
|
|
5118
|
+
function isMemo(value) {
|
|
5119
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
5120
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
5121
|
+
const { id, name, description, content, time, origin } = value;
|
|
5122
|
+
return (0, _orkestrel_contract.isString)(id) && (0, _orkestrel_contract.isString)(name) && (0, _orkestrel_contract.isString)(description) && (0, _orkestrel_contract.isString)(content) && (0, _orkestrel_contract.isFiniteNumber)(time) && (origin === void 0 || isMemoOrigin(origin)) && Object.keys(value).length === (origin === void 0 ? 5 : 6);
|
|
5123
|
+
});
|
|
5124
|
+
}
|
|
4437
5125
|
/**
|
|
4438
5126
|
* Checks whether a value is a stored memo set: a record with a string `id` and a dense `memos`
|
|
4439
5127
|
* array of {@link isMemo} values.
|
|
4440
5128
|
*
|
|
4441
5129
|
* @remarks
|
|
4442
|
-
*
|
|
4443
|
-
* inputs, a throwing getter or a revoked proxy included,
|
|
5130
|
+
* The guard is exact: an unknown member returns false, as a sparse `memos` array, a malformed
|
|
5131
|
+
* revision, and unreadable inputs, a throwing getter or a revoked proxy included, do.
|
|
4444
5132
|
*
|
|
4445
5133
|
* @param value - The value to test (an opaque storage read)
|
|
4446
5134
|
* @returns True if `value` has the shape of a {@link MemoSnapshot}; false otherwise
|
|
@@ -4454,10 +5142,11 @@ var isMemo = (0, _orkestrel_contract.andOf)(_orkestrel_contract.isRecord, (0, _o
|
|
|
4454
5142
|
* ```
|
|
4455
5143
|
*/
|
|
4456
5144
|
function isMemoSnapshot(value) {
|
|
4457
|
-
return (0, _orkestrel_contract.
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
5145
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
5146
|
+
if (!(0, _orkestrel_contract.isRecord)(value) || Object.keys(value).length !== 2) return false;
|
|
5147
|
+
const { id, memos } = value;
|
|
5148
|
+
return (0, _orkestrel_contract.isString)(id) && (0, _orkestrel_contract.arrayOf)(isMemo)(memos);
|
|
5149
|
+
});
|
|
4461
5150
|
}
|
|
4462
5151
|
// src/core/contexts/memos/MemoManager.ts
|
|
4463
5152
|
/**
|
|
@@ -4509,7 +5198,10 @@ var MemoManager = class {
|
|
|
4509
5198
|
const supplied = options?.snapshot;
|
|
4510
5199
|
const owned = supplied === void 0 ? void 0 : copyJSON(supplied);
|
|
4511
5200
|
const snapshot = isMemoSnapshot(owned) ? owned : void 0;
|
|
4512
|
-
if (supplied !== void 0 && snapshot === void 0)
|
|
5201
|
+
if (supplied !== void 0 && snapshot === void 0) {
|
|
5202
|
+
const faults = memoSnapshotContract.audit(owned);
|
|
5203
|
+
throw new ContextError("SNAPSHOT", `context error: memo snapshot is malformed; pass what snapshot() returned\n${(0, _orkestrel_contract.describeFaults)(faults, memoSnapshotContract.schema)}`, { context: { faults } });
|
|
5204
|
+
}
|
|
4513
5205
|
this.#id = snapshot?.id ?? options?.id ?? crypto.randomUUID();
|
|
4514
5206
|
this.#emitter = new _orkestrel_emitter.Emitter(options);
|
|
4515
5207
|
this.#format = options?.format;
|
|
@@ -4900,8 +5592,9 @@ var MemoryMemoStore = class {
|
|
|
4900
5592
|
* the row `{ id: snapshot.id, snapshot }`.
|
|
4901
5593
|
* - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
|
|
4902
5594
|
* a {@link MemoSnapshot} ({@link import('../validators.js').isMemoSnapshot} — the total guard for
|
|
4903
|
-
* an untrusted storage read), or `undefined`
|
|
4904
|
-
*
|
|
5595
|
+
* an untrusted storage read), or `undefined` only when no row is stored. A row whose blob is malformed
|
|
5596
|
+
* or names another id throws a `ContextError` coded `'SNAPSHOT'`, so a host never overwrites it
|
|
5597
|
+
* unawares, and `delete` still removes it.
|
|
4905
5598
|
* - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
|
|
4906
5599
|
*
|
|
4907
5600
|
* @example
|
|
@@ -4934,18 +5627,35 @@ var DatabaseMemoStore = class {
|
|
|
4934
5627
|
*
|
|
4935
5628
|
* @remarks
|
|
4936
5629
|
* The column is an untrusted storage read, so a blob that fails
|
|
4937
|
-
* {@link import('../validators.js').isMemoSnapshot}, or whose own `id` is not `id`,
|
|
4938
|
-
*
|
|
5630
|
+
* {@link import('../validators.js').isMemoSnapshot}, or whose own `id` is not `id`, throws rather
|
|
5631
|
+
* than reading as absent.
|
|
4939
5632
|
*
|
|
4940
5633
|
* @param id - The memo set id to resolve (the row key)
|
|
4941
|
-
* @returns The stored snapshot, or `undefined` when no row is stored
|
|
4942
|
-
* the blob names another id
|
|
5634
|
+
* @returns The stored snapshot, or `undefined` when no row is stored
|
|
5635
|
+
* @throws {ContextError} Thrown when the row's blob is malformed or names another id (code `'SNAPSHOT'`), with the faults under `context.faults`
|
|
4943
5636
|
*/
|
|
4944
5637
|
async get(id) {
|
|
4945
5638
|
const row = await this.#table.get(id);
|
|
4946
5639
|
if (row === void 0) return void 0;
|
|
4947
5640
|
const snapshot = row.snapshot;
|
|
4948
|
-
|
|
5641
|
+
if (isMemoSnapshot(snapshot)) {
|
|
5642
|
+
if (snapshot.id === id) return snapshot;
|
|
5643
|
+
const faults = [{
|
|
5644
|
+
reason: "type",
|
|
5645
|
+
path: ["id"],
|
|
5646
|
+
expected: "literal",
|
|
5647
|
+
received: (0, _orkestrel_contract.preview)(snapshot.id)
|
|
5648
|
+
}];
|
|
5649
|
+
throw new ContextError("SNAPSHOT", describeSnapshotRefusal("memo set", id, faults), { context: {
|
|
5650
|
+
id,
|
|
5651
|
+
faults
|
|
5652
|
+
} });
|
|
5653
|
+
}
|
|
5654
|
+
const faults = memoSnapshotContract.audit(snapshot);
|
|
5655
|
+
throw new ContextError("SNAPSHOT", describeSnapshotRefusal("memo set", id, faults), { context: {
|
|
5656
|
+
id,
|
|
5657
|
+
faults
|
|
5658
|
+
} });
|
|
4949
5659
|
}
|
|
4950
5660
|
/**
|
|
4951
5661
|
* Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is
|
|
@@ -4953,8 +5663,14 @@ var DatabaseMemoStore = class {
|
|
|
4953
5663
|
*
|
|
4954
5664
|
* @param snapshot - The snapshot to store, keyed by its `id`
|
|
4955
5665
|
* @returns A promise that resolves after the row is written
|
|
5666
|
+
* @throws {ContextError} Thrown before any write when the snapshot fails the guard `get` reads (code `'SNAPSHOT'`), so no row is written that `get` refuses
|
|
4956
5667
|
*/
|
|
4957
5668
|
async set(snapshot) {
|
|
5669
|
+
if (!isMemoSnapshot(snapshot)) {
|
|
5670
|
+
const unchecked = snapshot;
|
|
5671
|
+
const faults = memoSnapshotContract.audit(unchecked);
|
|
5672
|
+
throw new ContextError("SNAPSHOT", `a memo store writes only what snapshot() returned\n${(0, _orkestrel_contract.describeFaults)(faults, memoSnapshotContract.schema)}`, { context: { faults } });
|
|
5673
|
+
}
|
|
4958
5674
|
await this.#table.set({
|
|
4959
5675
|
id: snapshot.id,
|
|
4960
5676
|
snapshot
|
|
@@ -5443,10 +6159,6 @@ function createAgentContext(options) {
|
|
|
5443
6159
|
*/
|
|
5444
6160
|
var DEFAULT_AGENT_LIMIT = 10;
|
|
5445
6161
|
/**
|
|
5446
|
-
* Names the default zone for an unmatched tool call allowed by an authority.
|
|
5447
|
-
*/
|
|
5448
|
-
var DEFAULT_AUTHORITY_ZONE = "default";
|
|
5449
|
-
/**
|
|
5450
6162
|
* Holds the model-facing texts the loop writes after a tool name in a denial result — the
|
|
5451
6163
|
* `denied` note for a deny with no reason, an evaluate throw, an invalid decision, or a tool
|
|
5452
6164
|
* changed after its decision; the `refused` note for an ask the approver refused; and the
|
|
@@ -5551,13 +6263,16 @@ function isAgentJobError(value) {
|
|
|
5551
6263
|
});
|
|
5552
6264
|
}
|
|
5553
6265
|
/**
|
|
5554
|
-
* Reports an `ask` on an authority that has no approver or that its approver refuses, a run sharing accounting or a conversation with a live run, a `limit` that is not a
|
|
6266
|
+
* Reports an `ask` on an authority that has no approver or that its approver refuses, an authority missing its fallback or a rule's verdict, tools set with no authority, a run sharing accounting or a conversation with a live run, a `limit` that is not a positive safe integer, a name absent from a registry pool, a strict run's answer that fails its schema, or a strict run whose request exceeds its window. Agent construction, the `stream` and `generate` methods, and registry accessors throw synchronously, before returning a handle or promise; a `'WINDOW'` refusal rejects the run's `result` instead, and an `'APPROVER'` refusal rejects the promise the `ask` method of an `Authority` or the `ask` handler of an `Approver` returns. Await the call inside `try`/`catch` to catch it.
|
|
5555
6267
|
*
|
|
5556
6268
|
* @remarks
|
|
5557
6269
|
* `'APPROVER'` reports an `ask` on an `Authority` built with no `ask` handler, or an ask an
|
|
5558
6270
|
* `Approver` refuses: a call framed while no ask waited for it, a second ask for a waiting call, or
|
|
5559
6271
|
* arguments it cannot render. The agent loop reads it as an unanswered ask and denies the call. Its
|
|
5560
6272
|
* `context` names the asked `tool`.
|
|
6273
|
+
* `'AUTHORITY'` reports an `Authority` built with no valid `fallback`, a rule with no valid `verdict`
|
|
6274
|
+
* (its `context` names the `rule` index), and an agent built with `tools` and no `authority`. Each
|
|
6275
|
+
* throws at construction, with the options a host passes to correct it in the message.
|
|
5561
6276
|
* `'CONCURRENCY'` reports a run started while another run on the same agent is live, when the
|
|
5562
6277
|
* agent carries a `window` (a shared context budget) or when the run's effective `budget` or
|
|
5563
6278
|
* `allowance` is an instance a live run holds — the second run's `start()` would replace that
|
|
@@ -5566,17 +6281,18 @@ function isAgentJobError(value) {
|
|
|
5566
6281
|
* `agent` id, and the `conversation` id for a shared conversation. For concurrent runs, use
|
|
5567
6282
|
* separate agents, or one agent with no `window`, distinct budget and allowance instances per
|
|
5568
6283
|
* run, and a distinct conversation per run.
|
|
5569
|
-
* `'LIMIT'` reports a construction or per-run `limit` that is not a
|
|
6284
|
+
* `'LIMIT'` reports a construction or per-run `limit` that is not a positive safe integer,
|
|
5570
6285
|
* including one a rehydrated job carries into `build`. `'REGISTRY'` reports a rehydration name
|
|
5571
|
-
* absent from its registry pool, on `provider` / `tool` / `authority` / `scheduler` / `build
|
|
5572
|
-
*
|
|
6286
|
+
* absent from its registry pool, on `provider` / `tool` / `authority` / `scheduler` / `build`,
|
|
6287
|
+
* whose `context` names the `category` and the `name`, and a job that names `tools` and no
|
|
6288
|
+
* `authority` at `build`, whose `context` names the `tools`. `'WINDOW'` reports a `strict` run whose
|
|
5573
6289
|
* complete provider request is still over the `window` budget, equal to its `max` included, after
|
|
5574
6290
|
* the one permitted fold or with no fold possible; it settles the run `error` before the provider
|
|
5575
6291
|
* is called, and its `context` holds the `agent` id, the measured `consumed`, and the window `max`.
|
|
5576
6292
|
* Narrow a caught value with {@link isAgentError} and branch on `error.code`.
|
|
5577
6293
|
*/
|
|
5578
6294
|
var AgentError = class extends Error {
|
|
5579
|
-
/** Names the machine-readable condition — `'APPROVER'`: an `ask` on an authority that has no approver, or one its approver refuses; `'CONCURRENCY'`: a run sharing accounting or a conversation with a live run; `'LIMIT'`: a `limit` that is not a
|
|
6295
|
+
/** Names the machine-readable condition — `'APPROVER'`: an `ask` on an authority that has no approver, or one its approver refuses; `'AUTHORITY'`: an authority missing its fallback or a rule's verdict, or tools set with no authority; `'CONCURRENCY'`: a run sharing accounting or a conversation with a live run; `'LIMIT'`: a `limit` that is not a positive safe integer; `'REGISTRY'`: a rehydration name absent from its registry pool; `'WINDOW'`: a strict run whose complete request is still over its window budget. */
|
|
5580
6296
|
code;
|
|
5581
6297
|
/** Holds the values that locate the failure, such as the `agent` id of a refused run, or `undefined` when none were supplied. */
|
|
5582
6298
|
context;
|
|
@@ -5615,13 +6331,34 @@ function isAgentError(value) {
|
|
|
5615
6331
|
return (0, _orkestrel_contract.holds)(() => {
|
|
5616
6332
|
if (!(value instanceof Error && _orkestrel_contract.INTRINSICS.prototype(value) !== Error.prototype && _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.AgentError"))?.value === value)) return false;
|
|
5617
6333
|
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
5618
|
-
if (!(code === "APPROVER" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "WINDOW")) return false;
|
|
6334
|
+
if (!(code === "APPROVER" || code === "AUTHORITY" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "SCHEMA" || code === "WINDOW")) return false;
|
|
5619
6335
|
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
5620
6336
|
return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
|
|
5621
6337
|
});
|
|
5622
6338
|
}
|
|
5623
6339
|
// src/core/agents/shapers.ts
|
|
5624
6340
|
/**
|
|
6341
|
+
* Describes a durable agent job — the record `AgentRegistry.build` checks exactly, so a misspelled key
|
|
6342
|
+
* refuses instead of running an agent without it.
|
|
6343
|
+
*
|
|
6344
|
+
* @remarks
|
|
6345
|
+
* `children` holds JSON records, because each child crosses this shape at its own `build`. `limit`
|
|
6346
|
+
* reads as a number here and as a positive safe integer at the agent's construction, which owns that
|
|
6347
|
+
* refusal.
|
|
6348
|
+
*/
|
|
6349
|
+
var agentJobShape = (0, _orkestrel_contract.objectShape)({
|
|
6350
|
+
provider: (0, _orkestrel_contract.stringShape)(),
|
|
6351
|
+
messages: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(messageInputShape)),
|
|
6352
|
+
system: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
|
|
6353
|
+
tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())),
|
|
6354
|
+
authority: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
|
|
6355
|
+
scheduler: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
|
|
6356
|
+
limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
|
|
6357
|
+
timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
|
|
6358
|
+
budget: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
|
|
6359
|
+
children: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)())))
|
|
6360
|
+
});
|
|
6361
|
+
/**
|
|
5625
6362
|
* Describes the agent frame wire — one exact object per `AgentFrame` arm, discriminated by
|
|
5626
6363
|
* `category`.
|
|
5627
6364
|
*
|
|
@@ -5693,72 +6430,10 @@ var agentAnswerShape = (0, _orkestrel_contract.objectShape)({
|
|
|
5693
6430
|
var agentFrameContract = (0, _orkestrel_contract.createContract)(agentFrameShape);
|
|
5694
6431
|
/** Validates and projects agent prompts at the run's `prompt` option and a page's request body. */
|
|
5695
6432
|
var agentPromptContract = (0, _orkestrel_contract.createContract)(agentPromptShape);
|
|
6433
|
+
/** Checks a durable agent job exactly at `AgentRegistry.build`. */
|
|
6434
|
+
var agentJobContract = (0, _orkestrel_contract.createContract)(agentJobShape);
|
|
5696
6435
|
/** Validates and projects a page's answers to `ask` frames at a JSON wire boundary. */
|
|
5697
6436
|
var agentAnswerContract = (0, _orkestrel_contract.createContract)(agentAnswerShape);
|
|
5698
|
-
// src/core/agents/Authority.ts
|
|
5699
|
-
/**
|
|
5700
|
-
* Gates the agent loop's tool calls — the synchronous policy consulted before each call runs,
|
|
5701
|
-
* turning one {@link AuthorityContext} into an {@link AuthorityDecision} by walking the ordered
|
|
5702
|
-
* rules first-match-wins and falling back to a configurable default, which allows an unmatched
|
|
5703
|
-
* call unless its `fallback` decides otherwise, and the approver that answers an `ask` verdict.
|
|
5704
|
-
*
|
|
5705
|
-
* @remarks
|
|
5706
|
-
* - **Ordered, first-match-wins.** `evaluate` walks the configured rules in order and
|
|
5707
|
-
* returns the first whose `match(context)` is true as
|
|
5708
|
-
* `{ zone, verdict: rule.verdict ?? 'allow', reason }` — a matched rule with no `verdict`
|
|
5709
|
-
* allows.
|
|
5710
|
-
* - **Fallback.** When no rule matches, `evaluate` returns the configured `fallback`.
|
|
5711
|
-
* Default: `{ zone: DEFAULT_AUTHORITY_ZONE, verdict: 'allow' }` (allow-unmatched),
|
|
5712
|
-
* so a rules list of denials behaves as a denylist. To make the gate deny-by-default
|
|
5713
|
-
* (an allowlist — only matched rules that allow get through), pass a `verdict: 'deny'`
|
|
5714
|
-
* `fallback`.
|
|
5715
|
-
* - **Consulted before each tool call.** The agent loop calls `evaluate({ call })` for
|
|
5716
|
-
* every {@link import('@orkestrel/tool').ToolCall} the model emits; a denied call is fed
|
|
5717
|
-
* back to the model as a denial {@link import('@orkestrel/tool').ToolResult} (a `tool`
|
|
5718
|
-
* chunk + a tool message) instead of being executed — no tool execution —
|
|
5719
|
-
* so the model receives the denial and can react.
|
|
5720
|
-
* - **Approver.** `ask` answers an `ask` verdict through the `ask` option's
|
|
5721
|
-
* {@link AuthorityHandler}, as a promise for a synchronous or an async handler; a handler
|
|
5722
|
-
* throw rejects it. With no handler it rejects with an `AgentError` coded `'APPROVER'`, which
|
|
5723
|
-
* the loop reads as an unanswered ask. It never throws synchronously.
|
|
5724
|
-
* - **Synchronous.** `evaluate` returns the verdict directly; only `ask` is async.
|
|
5725
|
-
* - **Event-free.** A purely functional gate — no Emitter, no events.
|
|
5726
|
-
*
|
|
5727
|
-
* @example
|
|
5728
|
-
* ```ts
|
|
5729
|
-
* // A denylist: deny the `delete` tool, allow everything else (default fallback).
|
|
5730
|
-
* const authority = new Authority({
|
|
5731
|
-
* rules: [{ match: (c) => c.call.name === 'delete', zone: 'restricted', verdict: 'deny' }],
|
|
5732
|
-
* })
|
|
5733
|
-
* authority.evaluate({ call: { id: '1', name: 'delete', arguments: {} } }) // { zone: 'restricted', verdict: 'deny' }
|
|
5734
|
-
* authority.evaluate({ call: { id: '2', name: 'add', arguments: {} } }) // { zone: 'default', verdict: 'allow' }
|
|
5735
|
-
* ```
|
|
5736
|
-
*/
|
|
5737
|
-
var Authority = class {
|
|
5738
|
-
#rules;
|
|
5739
|
-
#fallback;
|
|
5740
|
-
#handler;
|
|
5741
|
-
constructor(options) {
|
|
5742
|
-
this.#rules = options?.rules ?? [];
|
|
5743
|
-
this.#fallback = options?.fallback ?? {
|
|
5744
|
-
zone: "default",
|
|
5745
|
-
verdict: "allow"
|
|
5746
|
-
};
|
|
5747
|
-
this.#handler = options?.ask;
|
|
5748
|
-
}
|
|
5749
|
-
evaluate(context) {
|
|
5750
|
-
for (const rule of this.#rules) if (rule.match(context)) return {
|
|
5751
|
-
zone: rule.zone,
|
|
5752
|
-
verdict: rule.verdict ?? "allow",
|
|
5753
|
-
...rule.reason === void 0 ? {} : { reason: rule.reason }
|
|
5754
|
-
};
|
|
5755
|
-
return this.#fallback;
|
|
5756
|
-
}
|
|
5757
|
-
async ask(context, decision, signal) {
|
|
5758
|
-
if (this.#handler === void 0) throw new AgentError("APPROVER", "an asked call needs an approver and none is set", { context: { tool: context.call.name } });
|
|
5759
|
-
return this.#handler(context, decision, signal);
|
|
5760
|
-
}
|
|
5761
|
-
};
|
|
5762
6437
|
// src/core/agents/ChannelCell.ts
|
|
5763
6438
|
/**
|
|
5764
6439
|
* Holds one value a `Channel` buffers and the cell pushed after it.
|
|
@@ -5883,20 +6558,61 @@ var Channel = class {
|
|
|
5883
6558
|
});
|
|
5884
6559
|
}
|
|
5885
6560
|
};
|
|
6561
|
+
// src/core/agents/validators.ts
|
|
6562
|
+
/**
|
|
6563
|
+
* Checks whether a value is an authority verdict: `allow`, `ask`, or `deny`.
|
|
6564
|
+
*
|
|
6565
|
+
* @param value - The unknown verdict candidate
|
|
6566
|
+
* @returns True if the value names a verdict; false otherwise
|
|
6567
|
+
*
|
|
6568
|
+
* @example
|
|
6569
|
+
* ```ts
|
|
6570
|
+
* isAuthorityVerdict('ask') // true
|
|
6571
|
+
* isAuthorityVerdict('maybe') // false
|
|
6572
|
+
* ```
|
|
6573
|
+
*/
|
|
6574
|
+
function isAuthorityVerdict(value) {
|
|
6575
|
+
return value === "allow" || value === "ask" || value === "deny";
|
|
6576
|
+
}
|
|
6577
|
+
/**
|
|
6578
|
+
* Checks whether a value is an authority decision: an object whose `zone` is a string, whose
|
|
6579
|
+
* `verdict` is `allow`, `ask`, or `deny`, and whose `reason` is absent or a string.
|
|
6580
|
+
*
|
|
6581
|
+
* @remarks
|
|
6582
|
+
* Total: each member is read once, unknown members are admitted, and a non-object, a 0.0.34
|
|
6583
|
+
* `{ zone, allowed }` decision, an unknown verdict, a throwing getter, and a revoked or throwing
|
|
6584
|
+
* `Proxy` return false rather than throw.
|
|
6585
|
+
*
|
|
6586
|
+
* @param value - The unknown decision candidate
|
|
6587
|
+
* @returns True if the value satisfies the decision contract; false otherwise
|
|
6588
|
+
*
|
|
6589
|
+
* @example
|
|
6590
|
+
* ```ts
|
|
6591
|
+
* isAuthorityDecision({ zone: 'default', verdict: 'ask', reason: 'confirm' }) // true
|
|
6592
|
+
* isAuthorityDecision({ zone: 'default', allowed: true }) // false
|
|
6593
|
+
* ```
|
|
6594
|
+
*/
|
|
6595
|
+
function isAuthorityDecision(value) {
|
|
6596
|
+
return (0, _orkestrel_contract.objectOf)({
|
|
6597
|
+
zone: _orkestrel_contract.isString,
|
|
6598
|
+
verdict: isAuthorityVerdict,
|
|
6599
|
+
reason: _orkestrel_contract.isString
|
|
6600
|
+
}, ["reason"])(value);
|
|
6601
|
+
}
|
|
5886
6602
|
// src/core/agents/helpers.ts
|
|
5887
6603
|
/**
|
|
5888
6604
|
* Projects an unknown value onto a fresh, exact `JSONValue` representation of an
|
|
5889
6605
|
* {@link AgentResult} — capturing each structural field once through a total boundary,
|
|
5890
|
-
* accepting conforming accessors and inherited properties, preserving
|
|
5891
|
-
*
|
|
5892
|
-
*
|
|
6606
|
+
* accepting conforming accessors and inherited properties, preserving fractional usage counts,
|
|
6607
|
+
* dropping extras, and resolving `undefined` for a malformed field, a negative or non-finite usage
|
|
6608
|
+
* number, a throwing getter, or a malformed or revoked proxy.
|
|
5893
6609
|
*
|
|
5894
6610
|
* @remarks
|
|
5895
6611
|
* This is a total projection that reads untrusted input. Each structural field is captured once
|
|
5896
6612
|
* through Contract's sanctioned exception boundary, so conforming accessors and inherited
|
|
5897
6613
|
* properties are supported while a throwing getter or revoked proxy returns `undefined`.
|
|
5898
|
-
* Present usage counts must be finite
|
|
5899
|
-
*
|
|
6614
|
+
* Present usage counts must be finite and nonnegative; fractional values are preserved, not
|
|
6615
|
+
* normalized. The result is exact `{ content, thinking?, usage?, partial, stop? }`. A present
|
|
5900
6616
|
* `stop` is read once, then its `layer`, `id`, `max`, `consumed`, `estimate`, and `usage` once each
|
|
5901
6617
|
* in that order, `max` and `consumed` only on a `meter` or `budget` arm; the arm is rebuilt
|
|
5902
6618
|
* exactly, omitting absent readings, and a malformed `stop` refuses the whole value. Extra input
|
|
@@ -5963,9 +6679,9 @@ function agentResultToJSON(value) {
|
|
|
5963
6679
|
}
|
|
5964
6680
|
/**
|
|
5965
6681
|
* Projects an unknown value onto a fresh, exact `TokenUsage` — capturing `prompt`, `completion`, and
|
|
5966
|
-
* `total` once each through a total boundary, preserving
|
|
5967
|
-
*
|
|
5968
|
-
*
|
|
6682
|
+
* `total` once each through a total boundary, preserving fractional counts, dropping extras, and
|
|
6683
|
+
* resolving `undefined` for a non-object, a missing, negative, or non-finite count, a throwing getter,
|
|
6684
|
+
* or a malformed or revoked proxy.
|
|
5969
6685
|
*
|
|
5970
6686
|
* @remarks
|
|
5971
6687
|
* The capture runs inside Contract's sanctioned exception boundary, so conforming accessors and
|
|
@@ -5974,13 +6690,14 @@ function agentResultToJSON(value) {
|
|
|
5974
6690
|
* before it projects, because this function refuses `undefined` like any other non-object.
|
|
5975
6691
|
*
|
|
5976
6692
|
* @param value - The unknown value to project
|
|
5977
|
-
* @returns A fresh `{ prompt, completion, total }`, or `undefined` when the value is not a finite usage triple
|
|
6693
|
+
* @returns A fresh `{ prompt, completion, total }`, or `undefined` when the value is not a finite nonnegative usage triple
|
|
5978
6694
|
*
|
|
5979
6695
|
* @example
|
|
5980
6696
|
* ```ts
|
|
5981
6697
|
* import { tokenUsageToJSON } from '@orkestrel/agent'
|
|
5982
6698
|
*
|
|
5983
|
-
* tokenUsageToJSON({ prompt: 2, completion:
|
|
6699
|
+
* tokenUsageToJSON({ prompt: 2, completion: 1, total: 1.5, cached: 4 }) // { prompt: 2, completion: 1, total: 1.5 }
|
|
6700
|
+
* tokenUsageToJSON({ prompt: 2, completion: -1, total: 1 }) // undefined
|
|
5984
6701
|
* tokenUsageToJSON({ prompt: 2, completion: Infinity, total: 3 }) // undefined
|
|
5985
6702
|
* ```
|
|
5986
6703
|
*/
|
|
@@ -5990,7 +6707,7 @@ function tokenUsageToJSON(value) {
|
|
|
5990
6707
|
const prompt = Reflect.get(value, "prompt");
|
|
5991
6708
|
const completion = Reflect.get(value, "completion");
|
|
5992
6709
|
const total = Reflect.get(value, "total");
|
|
5993
|
-
return (0,
|
|
6710
|
+
return (0, _orkestrel_budget.isBudgetAmount)(prompt) && (0, _orkestrel_budget.isBudgetAmount)(completion) && (0, _orkestrel_budget.isBudgetAmount)(total) ? {
|
|
5994
6711
|
prompt,
|
|
5995
6712
|
completion,
|
|
5996
6713
|
total
|
|
@@ -6099,7 +6816,11 @@ function agentResultToFrame(result) {
|
|
|
6099
6816
|
* @example
|
|
6100
6817
|
* ```ts
|
|
6101
6818
|
* const approver = createApprover()
|
|
6102
|
-
* const authority = createAuthority({
|
|
6819
|
+
* const authority = createAuthority({
|
|
6820
|
+
* rules,
|
|
6821
|
+
* fallback: { zone: 'default', verdict: 'deny' },
|
|
6822
|
+
* ask: approver.ask,
|
|
6823
|
+
* })
|
|
6103
6824
|
* const agent = createAgent(provider, { tools, authority })
|
|
6104
6825
|
* for await (const frame of agentStreamToFrames(agent.stream({ prompt }), approver)) {
|
|
6105
6826
|
* send(JSON.stringify(frame))
|
|
@@ -6175,30 +6896,6 @@ function inferStop(reason, allowance, budget, provider, turn) {
|
|
|
6175
6896
|
};
|
|
6176
6897
|
}
|
|
6177
6898
|
/**
|
|
6178
|
-
* Estimates the context-token footprint of a string — the deterministic `ceil(length / 4)`
|
|
6179
|
-
* character heuristic {@link estimateMessages} sums over a conversation's messages (the default
|
|
6180
|
-
* context-budget estimator).
|
|
6181
|
-
*
|
|
6182
|
-
* @remarks
|
|
6183
|
-
* Approximates `ceil(length / 4)` (4 characters per token), so the same input always yields the same estimate (no model round-trip).
|
|
6184
|
-
* Empty text is `0`. This is a planning heuristic for reasoning about how much a turn's
|
|
6185
|
-
* messages cost the next request, not an exact tokenizer count — it never calls a provider,
|
|
6186
|
-
* so the agent layer stays provider-agnostic and synchronous where it can be.
|
|
6187
|
-
*
|
|
6188
|
-
* @param text - The text to estimate (a section summary, a message's content)
|
|
6189
|
-
* @returns The estimated token count (`ceil(text.length / 4)`; `0` for empty text)
|
|
6190
|
-
*
|
|
6191
|
-
* @example
|
|
6192
|
-
* ```ts
|
|
6193
|
-
* estimateTokens('') // 0
|
|
6194
|
-
* estimateTokens('hello') // 2 (ceil(5 / 4))
|
|
6195
|
-
* estimateTokens('a'.repeat(40)) // 10
|
|
6196
|
-
* ```
|
|
6197
|
-
*/
|
|
6198
|
-
function estimateTokens(text) {
|
|
6199
|
-
return Math.ceil(text.length / 4);
|
|
6200
|
-
}
|
|
6201
|
-
/**
|
|
6202
6899
|
* Estimates the context-token footprint of a batch of messages — each message's content plus
|
|
6203
6900
|
* {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}, a tool-call JSON estimate, a thinking estimate, and
|
|
6204
6901
|
* {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} for each attached image. The {@link estimateRequest} message term
|
|
@@ -6493,6 +7190,84 @@ function resolveDecision(decision, definition) {
|
|
|
6493
7190
|
};
|
|
6494
7191
|
}
|
|
6495
7192
|
/**
|
|
7193
|
+
* Checks a run's final answer against its schema: the content must parse as JSON and satisfy the
|
|
7194
|
+
* schema, or the call throws a coded error carrying the faults.
|
|
7195
|
+
*
|
|
7196
|
+
* @remarks
|
|
7197
|
+
* The schema enters through the lenient `schemaToShape`, which widens every keyword it cannot express
|
|
7198
|
+
* and never narrows, because a provider constrains its own output and a false refusal would discard a
|
|
7199
|
+
* good answer. The check is `is`, because `explain` alone passes what `parse` would repair. Each call
|
|
7200
|
+
* compiles once, and a run has one final answer.
|
|
7201
|
+
*
|
|
7202
|
+
* @param content - The final answer's text
|
|
7203
|
+
* @param schema - The run's JSON Schema
|
|
7204
|
+
* @throws {AgentError} Thrown when the content is not JSON, misses the schema, or the schema does not compile (code `'SCHEMA'`), with the faults under `context.faults` for the first two
|
|
7205
|
+
*
|
|
7206
|
+
* @example
|
|
7207
|
+
* ```ts
|
|
7208
|
+
* requireAnswer('{"a":5}', { type: 'object', properties: { a: { type: 'number' } } })
|
|
7209
|
+
* requireAnswer('nope', { type: 'object' }) // throws AgentError ('SCHEMA')
|
|
7210
|
+
* ```
|
|
7211
|
+
*/
|
|
7212
|
+
function requireAnswer(content, schema) {
|
|
7213
|
+
const compiled = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema)));
|
|
7214
|
+
if (!compiled.success) throw new AgentError("SCHEMA", "the run schema does not compile; pass a JSON Schema built from plain values", { cause: compiled.error });
|
|
7215
|
+
const parsed = (0, _orkestrel_contract.parseJSON)(content);
|
|
7216
|
+
const faults = parsed === void 0 ? [{
|
|
7217
|
+
reason: "type",
|
|
7218
|
+
path: [],
|
|
7219
|
+
expected: "json",
|
|
7220
|
+
received: (0, _orkestrel_contract.preview)(content)
|
|
7221
|
+
}] : compiled.value.is(parsed) ? [] : compiled.value.audit(parsed);
|
|
7222
|
+
if (faults.length > 0) throw new AgentError("SCHEMA", `the final answer does not satisfy the run schema; pass strict: false to createAgent to take the raw content from a provider that ignores schemas\n${(0, _orkestrel_contract.describeFaults)(faults, compiled.value.schema)}`, { context: { faults } });
|
|
7223
|
+
}
|
|
7224
|
+
/**
|
|
7225
|
+
* Returns an authority's no-match decision after checking the options carry one, or throws a coded
|
|
7226
|
+
* error naming the two fallbacks a host chooses between.
|
|
7227
|
+
*
|
|
7228
|
+
* @remarks
|
|
7229
|
+
* A rules list says nothing about the calls it does not match, so no fallback is assumed: a denylist
|
|
7230
|
+
* and an allowlist differ only here. The check is {@link isAuthorityDecision}, a total guard, so a
|
|
7231
|
+
* missing options object and a malformed fallback refuse alike.
|
|
7232
|
+
*
|
|
7233
|
+
* @param options - The authority's options as a host passed them
|
|
7234
|
+
* @returns The fallback, narrowed to an {@link AuthorityDecision}
|
|
7235
|
+
* @throws {AgentError} Thrown when the options carry no valid `fallback` (code `'AUTHORITY'`)
|
|
7236
|
+
*
|
|
7237
|
+
* @example
|
|
7238
|
+
* ```ts
|
|
7239
|
+
* requireFallback({ fallback: { zone: 'default', verdict: 'deny' } }) // { zone: 'default', verdict: 'deny' }
|
|
7240
|
+
* ```
|
|
7241
|
+
*/
|
|
7242
|
+
function requireFallback(options) {
|
|
7243
|
+
const fallback = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.isObject)(options) ? Reflect.get(options, "fallback") : void 0);
|
|
7244
|
+
if (fallback.success && isAuthorityDecision(fallback.value)) return fallback.value;
|
|
7245
|
+
throw new AgentError("AUTHORITY", "an authority needs a fallback for the calls no rule matches; pass fallback: { zone: 'default', verdict: 'deny' } for an allowlist, or fallback: { zone: 'default', verdict: 'allow' } for a denylist");
|
|
7246
|
+
}
|
|
7247
|
+
/**
|
|
7248
|
+
* Returns an authority's rules after checking each one names a verdict, or throws a coded error naming
|
|
7249
|
+
* the first rule that does not.
|
|
7250
|
+
*
|
|
7251
|
+
* @remarks
|
|
7252
|
+
* Absent rules return an empty list. A rule's verdict decides a tool call, so a rule with none is
|
|
7253
|
+
* refused rather than read as an allow. The error's `context` carries the rule's index as `rule`.
|
|
7254
|
+
*
|
|
7255
|
+
* @param rules - The ordered rules, or `undefined` for none
|
|
7256
|
+
* @returns The rules unchanged, or an empty list when none were supplied
|
|
7257
|
+
* @throws {AgentError} Thrown when a rule has no valid `verdict` (code `'AUTHORITY'`)
|
|
7258
|
+
*
|
|
7259
|
+
* @example
|
|
7260
|
+
* ```ts
|
|
7261
|
+
* requireRules([{ match: () => true, zone: 'files', verdict: 'ask' }]).length // 1
|
|
7262
|
+
* ```
|
|
7263
|
+
*/
|
|
7264
|
+
function requireRules(rules) {
|
|
7265
|
+
if (rules === void 0) return [];
|
|
7266
|
+
const refused = Array.from(rules).findIndex((rule) => !(0, _orkestrel_contract.objectOf)({ verdict: isAuthorityVerdict })(rule));
|
|
7267
|
+
if (refused !== -1) throw new AgentError("AUTHORITY", `rule ${refused} has no verdict; set verdict: 'allow' | 'ask' | 'deny'`, { context: { rule: refused } });
|
|
7268
|
+
return rules;
|
|
7269
|
+
}
|
|
7270
|
+
/**
|
|
6496
7271
|
* Consumes a reported usage against a budget over what was already charged, so a turn's total
|
|
6497
7272
|
* draw is never less than the report and nothing is charged twice.
|
|
6498
7273
|
*
|
|
@@ -6522,26 +7297,25 @@ function chargeUsage(budget, usage, charged) {
|
|
|
6522
7297
|
});
|
|
6523
7298
|
}
|
|
6524
7299
|
/**
|
|
6525
|
-
* Returns a tool-iteration cap after checking it is a
|
|
6526
|
-
* error.
|
|
7300
|
+
* Returns a tool-iteration cap after checking it is a positive safe integer, or throws a coded error.
|
|
6527
7301
|
*
|
|
6528
|
-
* @remarks
|
|
6529
|
-
* The check is {@link import('../validators.js').
|
|
6530
|
-
*
|
|
6531
|
-
* carries the refused value as `limit`.
|
|
7302
|
+
* @remarks
|
|
7303
|
+
* The check is {@link import('../validators.js').isPositiveSafeInteger}, so `0`, `-0`, `NaN`, a
|
|
7304
|
+
* fraction, a negative, and either infinity are refused, because a run of no turns asks the provider
|
|
7305
|
+
* nothing and answers nothing. The error's `context` carries the refused value as `limit`.
|
|
6532
7306
|
*
|
|
6533
7307
|
* @param limit - The tool-iteration cap to check
|
|
6534
7308
|
* @returns The cap unchanged
|
|
6535
|
-
* @throws {AgentError} Thrown when `limit` is not a
|
|
7309
|
+
* @throws {AgentError} Thrown when `limit` is not a positive safe integer (code `'LIMIT'`)
|
|
6536
7310
|
*
|
|
6537
7311
|
* @example
|
|
6538
7312
|
* ```ts
|
|
6539
7313
|
* requireLimit(3) // 3
|
|
6540
|
-
* requireLimit(
|
|
7314
|
+
* requireLimit(0) // throws AgentError ('LIMIT')
|
|
6541
7315
|
* ```
|
|
6542
7316
|
*/
|
|
6543
7317
|
function requireLimit(limit) {
|
|
6544
|
-
if (!
|
|
7318
|
+
if (!isPositiveSafeInteger(limit)) throw new AgentError("LIMIT", "a limit must be a positive safe integer, because a run of no turns answers nothing", { context: { limit } });
|
|
6545
7319
|
return limit;
|
|
6546
7320
|
}
|
|
6547
7321
|
/**
|
|
@@ -6606,32 +7380,69 @@ function requireConversation(value) {
|
|
|
6606
7380
|
});
|
|
6607
7381
|
}
|
|
6608
7382
|
/**
|
|
6609
|
-
* Renders a tool's output as the string content of its tool message
|
|
7383
|
+
* Renders a tool's output as the string content of its tool message, or returns `undefined` when JSON
|
|
7384
|
+
* cannot carry it.
|
|
6610
7385
|
*
|
|
6611
7386
|
* @remarks
|
|
6612
7387
|
* A string passes through unchanged and `undefined` renders as `''`. Any other value renders as
|
|
6613
7388
|
* its `JSON.stringify` text, byte for byte, when JSON can carry it. A value JSON cannot carry (a
|
|
6614
|
-
* `BigInt`, a cycle, a symbol, a throwing `toJSON`)
|
|
6615
|
-
*
|
|
7389
|
+
* `BigInt`, a cycle, a symbol, a function, a throwing `toJSON`) returns `undefined`, which
|
|
7390
|
+
* {@link settleToolResult} turns into a tool failure.
|
|
6616
7391
|
*
|
|
6617
7392
|
* @param value - The tool's output (`ToolSuccess.value`, which may be `undefined`)
|
|
6618
|
-
* @returns The message content
|
|
7393
|
+
* @returns The message content, or `undefined` when JSON cannot carry the value
|
|
6619
7394
|
*
|
|
6620
7395
|
* @example
|
|
6621
7396
|
* ```ts
|
|
6622
7397
|
* renderToolValue('sunny') // 'sunny'
|
|
6623
7398
|
* renderToolValue({ count: 2 }) // '{"count":2}'
|
|
6624
7399
|
* renderToolValue(undefined) // ''
|
|
6625
|
-
* renderToolValue(10n) //
|
|
7400
|
+
* renderToolValue(10n) // undefined
|
|
6626
7401
|
* ```
|
|
6627
7402
|
*/
|
|
6628
7403
|
function renderToolValue(value) {
|
|
6629
7404
|
if (typeof value === "string") return value;
|
|
6630
7405
|
if (value === void 0) return "";
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
7406
|
+
return renderJSON(value);
|
|
7407
|
+
}
|
|
7408
|
+
/**
|
|
7409
|
+
* Settles one dispatched result into the result the loop reports and the content of its tool message,
|
|
7410
|
+
* turning a success whose value JSON cannot carry into a tool failure the model reads.
|
|
7411
|
+
*
|
|
7412
|
+
* @remarks
|
|
7413
|
+
* A failure passes through with its `error` as the content. A success renders through
|
|
7414
|
+
* {@link renderToolValue}. A success that renders to `undefined` becomes the failure arm with the
|
|
7415
|
+
* call's `id` and `name`, whose `error` names the tool and the remedy.
|
|
7416
|
+
*
|
|
7417
|
+
* @param result - The result the tool manager returned for one call
|
|
7418
|
+
* @returns The result to report and the tool message content
|
|
7419
|
+
*
|
|
7420
|
+
* @example
|
|
7421
|
+
* ```ts
|
|
7422
|
+
* settleToolResult({ success: true, id: '1', name: 'read', value: { count: 2 } }).content // '{"count":2}'
|
|
7423
|
+
* settleToolResult({ success: true, id: '1', name: 'read', value: 10n }).outcome.success // false
|
|
7424
|
+
* ```
|
|
7425
|
+
*/
|
|
7426
|
+
function settleToolResult(result) {
|
|
7427
|
+
if (!result.success) return {
|
|
7428
|
+
outcome: result,
|
|
7429
|
+
content: result.error
|
|
7430
|
+
};
|
|
7431
|
+
const content = renderToolValue(result.value);
|
|
7432
|
+
if (content !== void 0) return {
|
|
7433
|
+
outcome: result,
|
|
7434
|
+
content
|
|
7435
|
+
};
|
|
7436
|
+
const error = `${(0, _orkestrel_contract.preview)(result.name)} returned a value JSON cannot carry; return strings, numbers, booleans, null, arrays, and plain records`;
|
|
7437
|
+
return {
|
|
7438
|
+
outcome: {
|
|
7439
|
+
success: false,
|
|
7440
|
+
id: result.id,
|
|
7441
|
+
name: result.name,
|
|
7442
|
+
error
|
|
7443
|
+
},
|
|
7444
|
+
content: error
|
|
7445
|
+
};
|
|
6635
7446
|
}
|
|
6636
7447
|
/**
|
|
6637
7448
|
* Returns a registered value or throws when its name is absent.
|
|
@@ -6639,7 +7450,7 @@ function renderToolValue(value) {
|
|
|
6639
7450
|
* @param category - The registry category named in an error
|
|
6640
7451
|
* @param name - The requested key
|
|
6641
7452
|
* @returns The registered value
|
|
6642
|
-
* @throws {AgentError} Thrown when the name is absent, with code REGISTRY and a `context` naming the `category` and the `name`.
|
|
7453
|
+
* @throws {AgentError} Thrown when the name is absent, with code REGISTRY, the name encoded and capped in its text because a tool refusal reaches a model, and a `context` naming the `category` and the `name`.
|
|
6643
7454
|
* @example
|
|
6644
7455
|
* ```ts
|
|
6645
7456
|
* requireEntry(new Map([['main', provider]]), 'provider', 'main') // provider
|
|
@@ -6647,7 +7458,7 @@ function renderToolValue(value) {
|
|
|
6647
7458
|
*/
|
|
6648
7459
|
function requireEntry(pool, category, name) {
|
|
6649
7460
|
const value = pool.get(name);
|
|
6650
|
-
if (value === void 0) throw new AgentError("REGISTRY", `unknown ${category}: ${name}`, { context: {
|
|
7461
|
+
if (value === void 0) throw new AgentError("REGISTRY", `unknown ${category}: ${(0, _orkestrel_contract.preview)(name)}`, { context: {
|
|
6651
7462
|
category,
|
|
6652
7463
|
name
|
|
6653
7464
|
} });
|
|
@@ -6670,32 +7481,6 @@ function extractQueueOptions(options) {
|
|
|
6670
7481
|
...timeout === void 0 ? {} : { timeout }
|
|
6671
7482
|
};
|
|
6672
7483
|
}
|
|
6673
|
-
// src/core/agents/validators.ts
|
|
6674
|
-
/**
|
|
6675
|
-
* Checks whether a value is an authority decision: an object whose `zone` is a string, whose
|
|
6676
|
-
* `verdict` is `allow`, `ask`, or `deny`, and whose `reason` is absent or a string.
|
|
6677
|
-
*
|
|
6678
|
-
* @remarks
|
|
6679
|
-
* Total: each member is read once, unknown members are admitted, and a non-object, a 0.0.34
|
|
6680
|
-
* `{ zone, allowed }` decision, an unknown verdict, a throwing getter, and a revoked or throwing
|
|
6681
|
-
* `Proxy` return false rather than throw.
|
|
6682
|
-
*
|
|
6683
|
-
* @param value - The unknown decision candidate
|
|
6684
|
-
* @returns True if the value satisfies the decision contract; false otherwise
|
|
6685
|
-
*
|
|
6686
|
-
* @example
|
|
6687
|
-
* ```ts
|
|
6688
|
-
* isAuthorityDecision({ zone: 'default', verdict: 'ask', reason: 'confirm' }) // true
|
|
6689
|
-
* isAuthorityDecision({ zone: 'default', allowed: true }) // false
|
|
6690
|
-
* ```
|
|
6691
|
-
*/
|
|
6692
|
-
function isAuthorityDecision(value) {
|
|
6693
|
-
return (0, _orkestrel_contract.objectOf)({
|
|
6694
|
-
zone: _orkestrel_contract.isString,
|
|
6695
|
-
verdict: (0, _orkestrel_contract.literalOf)("allow", "ask", "deny"),
|
|
6696
|
-
reason: _orkestrel_contract.isString
|
|
6697
|
-
}, ["reason"])(value);
|
|
6698
|
-
}
|
|
6699
7484
|
// src/core/agents/Agent.ts
|
|
6700
7485
|
/**
|
|
6701
7486
|
* Composes a {@link ProviderInterface}, an {@link AgentContext}, and a
|
|
@@ -6763,6 +7548,7 @@ var Agent = class {
|
|
|
6763
7548
|
#settled = "idle";
|
|
6764
7549
|
#runs = /* @__PURE__ */ new Map();
|
|
6765
7550
|
constructor(provider, options) {
|
|
7551
|
+
if (options?.tools !== void 0 && options.authority === void 0) throw new AgentError("AUTHORITY", "tools are set and no authority is; pass authority: createAuthority({ fallback: { zone: 'default', verdict: 'allow' } }) to allow every call, or rules with a deny fallback");
|
|
6766
7552
|
this.#limit = requireLimit(options?.limit ?? 10);
|
|
6767
7553
|
this.#id = crypto.randomUUID();
|
|
6768
7554
|
this.#provider = provider;
|
|
@@ -6783,9 +7569,9 @@ var Agent = class {
|
|
|
6783
7569
|
this.#allowance = options?.allowance;
|
|
6784
7570
|
this.#scheduler = options?.scheduler;
|
|
6785
7571
|
this.#signal = options?.signal;
|
|
6786
|
-
this.#authority = options?.authority
|
|
7572
|
+
this.#authority = options?.authority;
|
|
6787
7573
|
this.#window = options?.window;
|
|
6788
|
-
this.#strict = options?.strict ??
|
|
7574
|
+
this.#strict = options?.strict ?? true;
|
|
6789
7575
|
this.#emitter = new _orkestrel_emitter.Emitter(options);
|
|
6790
7576
|
}
|
|
6791
7577
|
get id() {
|
|
@@ -6805,14 +7591,17 @@ var Agent = class {
|
|
|
6805
7591
|
}
|
|
6806
7592
|
stream(options) {
|
|
6807
7593
|
const prompt = options?.prompt;
|
|
6808
|
-
if (prompt !== void 0 && !agentPromptContract.is(prompt))
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
7594
|
+
if (prompt !== void 0 && !agentPromptContract.is(prompt)) {
|
|
7595
|
+
const faults = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.describeFaults)(agentPromptContract.audit(prompt), agentPromptContract.schema));
|
|
7596
|
+
throw new _orkestrel_contract.ContractError(`Agent: prompt must be an exact AgentPrompt${faults.success ? `\n${faults.value}` : ""}`, {
|
|
7597
|
+
code: "placement",
|
|
7598
|
+
context: {
|
|
7599
|
+
path: ["options", "prompt"],
|
|
7600
|
+
limit: "AgentPrompt",
|
|
7601
|
+
received: (0, _orkestrel_contract.preview)(prompt)
|
|
7602
|
+
}
|
|
7603
|
+
});
|
|
7604
|
+
}
|
|
6816
7605
|
const limit = options?.limit === void 0 ? this.#limit : requireLimit(options.limit);
|
|
6817
7606
|
const budget = requireBudget(options?.budget ?? this.#budget, "budget");
|
|
6818
7607
|
const allowance = requireBudget(options?.allowance ?? this.#allowance, "allowance");
|
|
@@ -6900,7 +7689,6 @@ var Agent = class {
|
|
|
6900
7689
|
...schema === void 0 ? {} : { schema }
|
|
6901
7690
|
};
|
|
6902
7691
|
this.#window?.clear();
|
|
6903
|
-
const cancelled = abort.signal.aborted;
|
|
6904
7692
|
const turns = [];
|
|
6905
7693
|
while (turns.length < limit && (turns.at(-1)?.outcome ?? "tools") === "tools") {
|
|
6906
7694
|
const index = turns.length;
|
|
@@ -6975,12 +7763,11 @@ var Agent = class {
|
|
|
6975
7763
|
estimate: settled.estimate
|
|
6976
7764
|
};
|
|
6977
7765
|
}
|
|
6978
|
-
if (
|
|
6979
|
-
else if (result.tools !== void 0 && result.tools.length > 0) {
|
|
7766
|
+
if (result.tools !== void 0 && result.tools.length > 0) {
|
|
6980
7767
|
const admittedCalls = filterAllowList(names, result.tools, (call) => call.name);
|
|
6981
7768
|
const authorization = this.#authorize(result.tools, admittedCalls, advertised, held);
|
|
6982
7769
|
if (authorization.asked.length > 0 && !abort.signal.aborted) yield* this.#approve(tools, authorization, abort.signal);
|
|
6983
|
-
if (!abort.signal.aborted) this.#confirm(tools, authorization);
|
|
7770
|
+
if (!abort.signal.aborted) this.#confirm(tools, authorization, advertised);
|
|
6984
7771
|
if (abort.signal.aborted) {
|
|
6985
7772
|
turns.push({
|
|
6986
7773
|
...settled,
|
|
@@ -6995,11 +7782,12 @@ var Agent = class {
|
|
|
6995
7782
|
calls: result.tools,
|
|
6996
7783
|
...recorded
|
|
6997
7784
|
});
|
|
6998
|
-
const results = await this.#dispatch(tools, result.tools, abort.signal, authorization);
|
|
7785
|
+
const results = await this.#dispatch(tools, result.tools, abort.signal, authorization, advertised);
|
|
6999
7786
|
const replies = [];
|
|
7000
7787
|
for (const [position, call] of result.tools.entries()) {
|
|
7001
|
-
const
|
|
7002
|
-
if (
|
|
7788
|
+
const dispatched = results[position];
|
|
7789
|
+
if (dispatched === void 0) continue;
|
|
7790
|
+
const { outcome, content } = settleToolResult(dispatched);
|
|
7003
7791
|
this.#emitter.emit("tool", call, outcome);
|
|
7004
7792
|
yield {
|
|
7005
7793
|
category: "tool",
|
|
@@ -7008,7 +7796,7 @@ var Agent = class {
|
|
|
7008
7796
|
};
|
|
7009
7797
|
replies.push(conversation.add({
|
|
7010
7798
|
role: "tool",
|
|
7011
|
-
content
|
|
7799
|
+
content,
|
|
7012
7800
|
call: call.id
|
|
7013
7801
|
}));
|
|
7014
7802
|
}
|
|
@@ -7024,6 +7812,13 @@ var Agent = class {
|
|
|
7024
7812
|
});
|
|
7025
7813
|
continue;
|
|
7026
7814
|
}
|
|
7815
|
+
if (schema !== void 0 && result.truncated !== true && !abort.signal.aborted) {
|
|
7816
|
+
const answered = (0, _orkestrel_contract.attempt)(() => requireAnswer(result.content, schema));
|
|
7817
|
+
if (!answered.success) {
|
|
7818
|
+
if (this.#strict) throw answered.error;
|
|
7819
|
+
this.#emitter.emit("fault", answered.error);
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7027
7822
|
conversation.add({
|
|
7028
7823
|
role: "assistant",
|
|
7029
7824
|
content: result.content,
|
|
@@ -7047,7 +7842,7 @@ var Agent = class {
|
|
|
7047
7842
|
entry.usage,
|
|
7048
7843
|
...turns.map((turn) => turn.spent)
|
|
7049
7844
|
].reduce((total, one) => one === void 0 ? total : sumUsage(total, one), void 0),
|
|
7050
|
-
partial: final?.partial ??
|
|
7845
|
+
partial: final?.partial ?? abort.signal.aborted,
|
|
7051
7846
|
exhausted: final?.outcome === "tools" && !abort.signal.aborted,
|
|
7052
7847
|
stop: inferStop(abort.signal.reason, allowance, budget, this.#provider.id, final)
|
|
7053
7848
|
};
|
|
@@ -7059,8 +7854,7 @@ var Agent = class {
|
|
|
7059
7854
|
const thinking = surfaced !== void 0 && surfaced.length > 0 ? surfaced : void 0;
|
|
7060
7855
|
const content = streamed.join("");
|
|
7061
7856
|
const estimate = estimateTokens(content);
|
|
7062
|
-
const
|
|
7063
|
-
const usage = reported === void 0 ? void 0 : sanitizeUsage(reported);
|
|
7857
|
+
const usage = provided.success ? provided.value.usage : partial?.usage === void 0 ? void 0 : sanitizeUsage(partial.usage);
|
|
7064
7858
|
const charged = allowance === budget ? meter?.charged ?? 0 : 0;
|
|
7065
7859
|
const billed = usage ?? (allowance === budget ? void 0 : {
|
|
7066
7860
|
prompt: 0,
|
|
@@ -7259,6 +8053,11 @@ var Agent = class {
|
|
|
7259
8053
|
return (0, _orkestrel_contract.failure)(denial);
|
|
7260
8054
|
}
|
|
7261
8055
|
const authority = this.#authority;
|
|
8056
|
+
if (authority === void 0) {
|
|
8057
|
+
const denial = denyCall(call, void 0);
|
|
8058
|
+
this.#emitter.emit("deny", call, "no authority is set", void 0);
|
|
8059
|
+
return (0, _orkestrel_contract.failure)(denial);
|
|
8060
|
+
}
|
|
7262
8061
|
const evaluated = (0, _orkestrel_contract.attempt)(() => {
|
|
7263
8062
|
const decision = authority.evaluate({ call });
|
|
7264
8063
|
if (!(0, _orkestrel_contract.isObject)(decision)) return void 0;
|
|
@@ -7290,7 +8089,7 @@ var Agent = class {
|
|
|
7290
8089
|
this.#emitter.emit("ask", call, decision);
|
|
7291
8090
|
if (signal.aborted) return;
|
|
7292
8091
|
const answer = new Promise((resolve) => {
|
|
7293
|
-
const ask = authority
|
|
8092
|
+
const ask = authority?.ask;
|
|
7294
8093
|
resolve(ask === void 0 ? {
|
|
7295
8094
|
approved: false,
|
|
7296
8095
|
reason: void 0,
|
|
@@ -7324,21 +8123,33 @@ var Agent = class {
|
|
|
7324
8123
|
}
|
|
7325
8124
|
}
|
|
7326
8125
|
}
|
|
7327
|
-
#
|
|
8126
|
+
#changed(tools, call, tool, advertised) {
|
|
8127
|
+
const same = (0, _orkestrel_contract.attempt)(() => {
|
|
8128
|
+
const current = tools.tool(call.name);
|
|
8129
|
+
const definition = advertised.find((entry) => entry.name === call.name);
|
|
8130
|
+
return current === tool && current?.annotations?.consequential === true === (definition?.annotations?.consequential === true);
|
|
8131
|
+
});
|
|
8132
|
+
return !same.success || !same.value;
|
|
8133
|
+
}
|
|
8134
|
+
#confirm(tools, authorization, advertised) {
|
|
7328
8135
|
for (const [index, { call, tool }] of authorization.allowed) {
|
|
7329
|
-
if (tools
|
|
8136
|
+
if (!this.#changed(tools, call, tool, advertised)) continue;
|
|
7330
8137
|
authorization.allowed.delete(index);
|
|
7331
8138
|
authorization.denials.set(index, denyCall(call, AUTHORITY_NOTES.denied));
|
|
7332
8139
|
this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
|
|
7333
|
-
this.#confirm(tools, authorization);
|
|
8140
|
+
this.#confirm(tools, authorization, advertised);
|
|
7334
8141
|
return;
|
|
7335
8142
|
}
|
|
7336
8143
|
}
|
|
7337
|
-
async #dispatch(tools, calls, signal, authorization) {
|
|
8144
|
+
async #dispatch(tools, calls, signal, authorization, advertised) {
|
|
7338
8145
|
const { allowed, denials } = authorization;
|
|
7339
8146
|
const entries = [...allowed].sort(([left], [right]) => left - right);
|
|
7340
|
-
const
|
|
7341
|
-
|
|
8147
|
+
const started = entries.map(([, { call, tool }]) => {
|
|
8148
|
+
if (!this.#changed(tools, call, tool, advertised)) return tools.execute(call, { signal });
|
|
8149
|
+
this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
|
|
8150
|
+
return denyCall(call, AUTHORITY_NOTES.denied);
|
|
8151
|
+
});
|
|
8152
|
+
const executed = started.length > 0 ? await Promise.all(started) : [];
|
|
7342
8153
|
const results = new Map([...denials, ...entries.flatMap(([index], offset) => {
|
|
7343
8154
|
const result = executed[offset];
|
|
7344
8155
|
return result === void 0 ? [] : [[index, result]];
|
|
@@ -7352,9 +8163,13 @@ var Agent = class {
|
|
|
7352
8163
|
const next = await generator.next();
|
|
7353
8164
|
if (next.done === true) {
|
|
7354
8165
|
const result = next.value;
|
|
7355
|
-
return (0, _orkestrel_contract.attempt)(() => result);
|
|
8166
|
+
return (0, _orkestrel_contract.attempt)(() => requireProviderResult(result));
|
|
8167
|
+
}
|
|
8168
|
+
const delta = readProviderDelta(next.value);
|
|
8169
|
+
if (delta === void 0) {
|
|
8170
|
+
generator.return({ content: "" }).catch(() => {});
|
|
8171
|
+
throw new ProviderError("PROTOCOL", `provider error: a delta must carry a channel of 'content' or 'thinking' and string text; received ${(0, _orkestrel_contract.preview)(next.value)}`);
|
|
7356
8172
|
}
|
|
7357
|
-
const delta = next.value;
|
|
7358
8173
|
if (delta.channel === "content") {
|
|
7359
8174
|
streamed.push(delta.text);
|
|
7360
8175
|
meter?.charge(delta.text);
|
|
@@ -7444,6 +8259,67 @@ var AgentFanout = class {
|
|
|
7444
8259
|
return settleAgentJob(this.#registry.build(controller.input, controller.signal), this.#partial);
|
|
7445
8260
|
}
|
|
7446
8261
|
};
|
|
8262
|
+
// src/core/agents/Authority.ts
|
|
8263
|
+
/**
|
|
8264
|
+
* Gates the agent loop's tool calls — the synchronous policy consulted before each call runs,
|
|
8265
|
+
* turning one {@link AuthorityContext} into an {@link AuthorityDecision} by walking the ordered
|
|
8266
|
+
* rules first-match-wins and falling back to the required `fallback`, and the approver that answers
|
|
8267
|
+
* an `ask` verdict.
|
|
8268
|
+
*
|
|
8269
|
+
* @remarks
|
|
8270
|
+
* - **Refuses at construction.** The constructor throws an `AgentError` coded `'AUTHORITY'` when the
|
|
8271
|
+
* `fallback` is missing or malformed, or when a rule has no valid `verdict`, so a policy with a
|
|
8272
|
+
* hole in it never runs.
|
|
8273
|
+
* - **Ordered, first-match-wins.** `evaluate` walks the configured rules in order and
|
|
8274
|
+
* returns the first whose `match(context)` is true as `{ zone, verdict, reason }`.
|
|
8275
|
+
* - **Fallback.** When no rule matches, `evaluate` returns the configured `fallback`: a
|
|
8276
|
+
* `verdict: 'deny'` fallback makes an allowlist (only matched rules that allow get through), and a
|
|
8277
|
+
* `verdict: 'allow'` fallback makes a denylist.
|
|
8278
|
+
* - **Consulted before each tool call.** The agent loop calls `evaluate({ call })` for
|
|
8279
|
+
* every {@link import('@orkestrel/tool').ToolCall} the model emits; a denied call is fed
|
|
8280
|
+
* back to the model as a denial {@link import('@orkestrel/tool').ToolResult} (a `tool`
|
|
8281
|
+
* chunk + a tool message) instead of being executed — no tool execution —
|
|
8282
|
+
* so the model receives the denial and can react.
|
|
8283
|
+
* - **Approver.** `ask` answers an `ask` verdict through the `ask` option's
|
|
8284
|
+
* {@link AuthorityHandler}, as a promise for a synchronous or an async handler; a handler
|
|
8285
|
+
* throw rejects it. With no handler it rejects with an `AgentError` coded `'APPROVER'`, which
|
|
8286
|
+
* the loop reads as an unanswered ask. It never throws synchronously.
|
|
8287
|
+
* - **Synchronous.** `evaluate` returns the verdict directly; only `ask` is async.
|
|
8288
|
+
* - **Event-free.** A purely functional gate — no Emitter, no events.
|
|
8289
|
+
*
|
|
8290
|
+
* @example
|
|
8291
|
+
* ```ts
|
|
8292
|
+
* // A denylist: deny the `delete` tool, allow everything else.
|
|
8293
|
+
* const authority = new Authority({
|
|
8294
|
+
* rules: [{ match: (c) => c.call.name === 'delete', zone: 'restricted', verdict: 'deny' }],
|
|
8295
|
+
* fallback: { zone: 'default', verdict: 'allow' },
|
|
8296
|
+
* })
|
|
8297
|
+
* authority.evaluate({ call: { id: '1', name: 'delete', arguments: {} } }) // { zone: 'restricted', verdict: 'deny' }
|
|
8298
|
+
* authority.evaluate({ call: { id: '2', name: 'add', arguments: {} } }) // { zone: 'default', verdict: 'allow' }
|
|
8299
|
+
* ```
|
|
8300
|
+
*/
|
|
8301
|
+
var Authority = class {
|
|
8302
|
+
#rules;
|
|
8303
|
+
#fallback;
|
|
8304
|
+
#handler;
|
|
8305
|
+
constructor(options) {
|
|
8306
|
+
this.#fallback = requireFallback(options);
|
|
8307
|
+
this.#rules = requireRules(options.rules);
|
|
8308
|
+
this.#handler = options.ask;
|
|
8309
|
+
}
|
|
8310
|
+
evaluate(context) {
|
|
8311
|
+
for (const rule of this.#rules) if (rule.match(context)) return {
|
|
8312
|
+
zone: rule.zone,
|
|
8313
|
+
verdict: rule.verdict,
|
|
8314
|
+
...rule.reason === void 0 ? {} : { reason: rule.reason }
|
|
8315
|
+
};
|
|
8316
|
+
return this.#fallback;
|
|
8317
|
+
}
|
|
8318
|
+
async ask(context, decision, signal) {
|
|
8319
|
+
if (this.#handler === void 0) throw new AgentError("APPROVER", "an asked call needs an approver and none is set", { context: { tool: context.call.name } });
|
|
8320
|
+
return this.#handler(context, decision, signal);
|
|
8321
|
+
}
|
|
8322
|
+
};
|
|
7447
8323
|
// src/core/agents/Approver.ts
|
|
7448
8324
|
/**
|
|
7449
8325
|
* Holds a server's waiting asks between the run that asked them and the page that answers them — an
|
|
@@ -7465,7 +8341,11 @@ var AgentFanout = class {
|
|
|
7465
8341
|
* @example
|
|
7466
8342
|
* ```ts
|
|
7467
8343
|
* const approver = new Approver()
|
|
7468
|
-
* const authority = createAuthority({
|
|
8344
|
+
* const authority = createAuthority({
|
|
8345
|
+
* rules,
|
|
8346
|
+
* fallback: { zone: 'default', verdict: 'deny' },
|
|
8347
|
+
* ask: approver.ask,
|
|
8348
|
+
* })
|
|
7469
8349
|
* approver.answer({ id, approved: true }) // true while that ask waits
|
|
7470
8350
|
* ```
|
|
7471
8351
|
*/
|
|
@@ -7547,13 +8427,14 @@ var Approver = class {
|
|
|
7547
8427
|
* rehydrated job is a programmer / config error that must fail loudly at build time, never
|
|
7548
8428
|
* silently resolve to `undefined` and run an agent missing a dependency.
|
|
7549
8429
|
* - **`build` rehydrates.** Resolve the job's `provider`; assemble a fresh
|
|
7550
|
-
* {@link ToolManager} from the `tools` names
|
|
8430
|
+
* {@link ToolManager} from the `tools` names, which refuses with `code: 'REGISTRY'` when the job
|
|
8431
|
+
* names no `authority`; rebuild the token `budget` from its
|
|
7551
8432
|
* ceiling (`createTokenBudget({ max })`); resolve the optional `authority` /
|
|
7552
8433
|
* `scheduler` names; construct the {@link Agent} with `system` / `limit` / `timeout` /
|
|
7553
8434
|
* the threaded `signal`; seed its context with the job's `messages`; return it. The
|
|
7554
8435
|
* `signal` is the queue attempt's / runner unit's cancel, so a bounded abort propagates
|
|
7555
8436
|
* into the agent (which commits a partial — the job's `partial` policy then
|
|
7556
|
-
* decides success vs. retry). A job `limit` that is not a
|
|
8437
|
+
* decides success vs. retry). A job `limit` that is not a positive safe integer throws an
|
|
7557
8438
|
* {@link AgentError} carrying `code: 'LIMIT'` from the agent's construction.
|
|
7558
8439
|
* - **Event-free.** A pure resolver — no Emitter, no events.
|
|
7559
8440
|
*
|
|
@@ -7591,14 +8472,18 @@ var AgentRegistry = class {
|
|
|
7591
8472
|
return requireEntry(this.#schedulers, "scheduler", name);
|
|
7592
8473
|
}
|
|
7593
8474
|
build(input, signal) {
|
|
8475
|
+
const faults = agentJobContract.audit(input);
|
|
8476
|
+
if (faults.length > 0) throw new AgentError("REGISTRY", `a job must hold only the declared members of AgentJobInput\n${(0, _orkestrel_contract.describeFaults)(faults, agentJobContract.schema)}`, { context: { faults } });
|
|
7594
8477
|
const agent = new Agent(this.provider(input.provider), this.#buildOptions(input, signal));
|
|
7595
|
-
for (const message of input.messages) agent.context.messages.add(message);
|
|
8478
|
+
for (const message of input.messages ?? []) agent.context.messages.add(message);
|
|
7596
8479
|
return agent;
|
|
7597
8480
|
}
|
|
7598
8481
|
#buildOptions(input, signal) {
|
|
8482
|
+
const names = input.tools ?? [];
|
|
8483
|
+
if (names.length > 0 && input.authority === void 0) throw new AgentError("REGISTRY", "a job that names tools and no authority cannot run them; add authority: 'NAME' to the job and register that NAME under authorities", { context: { tools: names } });
|
|
7599
8484
|
const budget = input.budget === void 0 ? void 0 : (0, _orkestrel_budget.createTokenBudget)({ max: input.budget });
|
|
7600
8485
|
return {
|
|
7601
|
-
tools: this.#buildToolManager(
|
|
8486
|
+
...names.length === 0 ? {} : { tools: this.#buildToolManager(names) },
|
|
7602
8487
|
...input.system === void 0 ? {} : { system: input.system },
|
|
7603
8488
|
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
7604
8489
|
...input.timeout === void 0 ? {} : { timeout: input.timeout },
|
|
@@ -7611,7 +8496,7 @@ var AgentRegistry = class {
|
|
|
7611
8496
|
}
|
|
7612
8497
|
#buildToolManager(names) {
|
|
7613
8498
|
const manager = new _orkestrel_tool.ToolManager();
|
|
7614
|
-
|
|
8499
|
+
for (const name of names) manager.add(this.tool(name));
|
|
7615
8500
|
return manager;
|
|
7616
8501
|
}
|
|
7617
8502
|
};
|
|
@@ -7693,20 +8578,20 @@ function createChannel() {
|
|
|
7693
8578
|
/**
|
|
7694
8579
|
* Creates a policy gate — an {@link AuthorityInterface} the agent loop consults before
|
|
7695
8580
|
* each tool call runs, evaluating the ordered rules first-match-wins and falling back
|
|
7696
|
-
* to the
|
|
8581
|
+
* to the required `fallback` when none match.
|
|
7697
8582
|
*
|
|
7698
8583
|
* @remarks
|
|
7699
|
-
* `rules` are evaluated in order — the first whose `match` is true decides
|
|
7700
|
-
*
|
|
7701
|
-
*
|
|
7702
|
-
*
|
|
7703
|
-
*
|
|
7704
|
-
*
|
|
7705
|
-
*
|
|
7706
|
-
* can react. Synchronous — `evaluate` returns the verdict directly; only `ask` is async.
|
|
8584
|
+
* `rules` are evaluated in order — the first whose `match` is true decides, and each rule names its
|
|
8585
|
+
* `verdict`. When no rule matches, the `fallback` decides: a `verdict: 'deny'` fallback makes an
|
|
8586
|
+
* allowlist, and a `verdict: 'allow'` fallback makes a denylist. `ask` answers each `ask` verdict;
|
|
8587
|
+
* with none, every ask is refused as unanswered. Wire the result into `createAgent` through
|
|
8588
|
+
* `AgentOptions.authority`: a denied call is fed back to the model as a denial `ToolResult` (not
|
|
8589
|
+
* executed), so the model can react. Synchronous — `evaluate` returns the verdict directly; only
|
|
8590
|
+
* `ask` is async.
|
|
7707
8591
|
*
|
|
7708
|
-
* @param options -
|
|
8592
|
+
* @param options - The `fallback`, and optional `rules` (ordered) and `ask` (see {@link AuthorityOptions})
|
|
7709
8593
|
* @returns A working {@link AuthorityInterface}
|
|
8594
|
+
* @throws {AgentError} Thrown when the `fallback` is missing or malformed, or a rule has no valid `verdict` (code `'AUTHORITY'`)
|
|
7710
8595
|
*
|
|
7711
8596
|
* @example
|
|
7712
8597
|
* ```ts
|
|
@@ -7715,6 +8600,7 @@ function createChannel() {
|
|
|
7715
8600
|
* // Deny the `delete` tool, allow everything else.
|
|
7716
8601
|
* const authority = createAuthority({
|
|
7717
8602
|
* rules: [{ match: (c) => c.call.name === 'delete', zone: 'restricted', verdict: 'deny' }],
|
|
8603
|
+
* fallback: { zone: 'default', verdict: 'allow' },
|
|
7718
8604
|
* })
|
|
7719
8605
|
* const agent = createAgent(provider, { tools, authority })
|
|
7720
8606
|
* ```
|
|
@@ -7736,7 +8622,11 @@ function createAuthority(options) {
|
|
|
7736
8622
|
* @example
|
|
7737
8623
|
* ```ts
|
|
7738
8624
|
* const approver = createApprover()
|
|
7739
|
-
* const authority = createAuthority({
|
|
8625
|
+
* const authority = createAuthority({
|
|
8626
|
+
* rules,
|
|
8627
|
+
* fallback: { zone: 'default', verdict: 'deny' },
|
|
8628
|
+
* ask: approver.ask,
|
|
8629
|
+
* })
|
|
7740
8630
|
* const agent = createAgent(provider, { tools, authority })
|
|
7741
8631
|
* const stream = agent.stream({ prompt: { content: 'Tidy the notes.' } })
|
|
7742
8632
|
* for await (const frame of agentStreamToFrames(stream, approver)) {
|
|
@@ -7765,16 +8655,29 @@ function createApprover() {
|
|
|
7765
8655
|
*
|
|
7766
8656
|
* @example
|
|
7767
8657
|
* ```ts
|
|
7768
|
-
* import { createAgentRegistry } from '@orkestrel/agent'
|
|
8658
|
+
* import { createAgentRegistry, createAuthority } from '@orkestrel/agent'
|
|
7769
8659
|
* import type { ProviderInterface } from '@orkestrel/agent'
|
|
8660
|
+
* import { numberShape, objectShape } from '@orkestrel/contract'
|
|
7770
8661
|
* import { createTool } from '@orkestrel/tool'
|
|
7771
8662
|
*
|
|
7772
8663
|
* declare const provider: ProviderInterface // any concrete implementation supplied by the host app
|
|
7773
8664
|
* const registry = createAgentRegistry({
|
|
7774
8665
|
* providers: { main: provider },
|
|
7775
|
-
* tools: {
|
|
8666
|
+
* tools: {
|
|
8667
|
+
* add: createTool({
|
|
8668
|
+
* name: 'add',
|
|
8669
|
+
* contract: objectShape({ x: numberShape(), y: numberShape() }),
|
|
8670
|
+
* execute: (a) => Number(a.x) + Number(a.y),
|
|
8671
|
+
* }),
|
|
8672
|
+
* },
|
|
8673
|
+
* authorities: { open: createAuthority({ fallback: { zone: 'default', verdict: 'allow' } }) },
|
|
8674
|
+
* })
|
|
8675
|
+
* const agent = registry.build({
|
|
8676
|
+
* provider: 'main',
|
|
8677
|
+
* messages: [{ role: 'user', content: 'Hi.' }],
|
|
8678
|
+
* tools: ['add'],
|
|
8679
|
+
* authority: 'open',
|
|
7776
8680
|
* })
|
|
7777
|
-
* const agent = registry.build({ provider: 'main', messages: [{ role: 'user', content: 'Hi.' }] })
|
|
7778
8681
|
* ```
|
|
7779
8682
|
*/
|
|
7780
8683
|
function createAgentRegistry(options) {
|
|
@@ -7999,10 +8902,10 @@ var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
|
|
|
7999
8902
|
* carrying the machine-readable `code`.
|
|
8000
8903
|
*
|
|
8001
8904
|
* @remarks
|
|
8002
|
-
* A threshold, share, capacity, limit, topic, lookup, or gauge outside its bounds is a
|
|
8003
|
-
* error and throws this with the matching {@link LedgerErrorCode}. A calibration call that reports
|
|
8905
|
+
* A threshold, share, capacity, limit, topic, lookup, question, or gauge outside its bounds is a
|
|
8906
|
+
* programmer error and throws this with the matching {@link LedgerErrorCode}. A calibration call that reports
|
|
8004
8907
|
* no prompt usage throws this with `'GAUGE'`. A snapshot option that `isLedgerSnapshot` refuses
|
|
8005
|
-
* throws this with `'SNAPSHOT'`. Narrow a caught value with {@link isLedgerError} and
|
|
8908
|
+
* throws this with `'SNAPSHOT'`, its `context` holding the `faults`. Narrow a caught value with {@link isLedgerError} and
|
|
8006
8909
|
* branch on `error.code`. A selection without an active `respond` call, for a conversation other
|
|
8007
8910
|
* than the ledger's, or for a request other than that call's request or a ledger note, reports
|
|
8008
8911
|
* `'REQUEST'` through the selection's fault.
|
|
@@ -8010,17 +8913,21 @@ var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
|
|
|
8010
8913
|
var LedgerError = class extends Error {
|
|
8011
8914
|
/** Names the machine-readable condition; the {@link LedgerErrorCode} union describes each code. */
|
|
8012
8915
|
code;
|
|
8916
|
+
/** Holds the values that locate the failure, such as the `faults` of a refused snapshot, or `undefined` when none were supplied. */
|
|
8917
|
+
context;
|
|
8013
8918
|
/**
|
|
8014
8919
|
* Records the failure condition and its description.
|
|
8015
8920
|
*
|
|
8016
8921
|
* @param code - The machine-readable condition, exposed as `error.code`
|
|
8017
8922
|
* @param message - The human-readable description of the failure
|
|
8923
|
+
* @param options - The optional {@link CodedErrorOptions}; `cause` reaches `Error.cause` only when present
|
|
8018
8924
|
*/
|
|
8019
|
-
constructor(code, message) {
|
|
8020
|
-
super(message);
|
|
8925
|
+
constructor(code, message, options) {
|
|
8926
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
8021
8927
|
this.name = "LedgerError";
|
|
8022
8928
|
_orkestrel_contract.INTRINSICS.define(this, Symbol.for("@orkestrel/agent.LedgerError"), { value: this });
|
|
8023
8929
|
this.code = code;
|
|
8930
|
+
this.context = options?.context;
|
|
8024
8931
|
}
|
|
8025
8932
|
};
|
|
8026
8933
|
/**
|
|
@@ -8039,8 +8946,9 @@ function isLedgerError(value) {
|
|
|
8039
8946
|
return (0, _orkestrel_contract.holds)(() => {
|
|
8040
8947
|
if (!(value instanceof Error && _orkestrel_contract.INTRINSICS.prototype(value) !== Error.prototype && _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.LedgerError"))?.value === value)) return false;
|
|
8041
8948
|
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
8042
|
-
if (!(code === "REQUEST" || code === "THRESHOLD" || code === "SHARE" || code === "CAPACITY" || code === "LIMIT" || code === "TOPIC" || code === "LOOKUP" || code === "GAUGE" || code === "REFERENCE" || code === "SNAPSHOT")) return false;
|
|
8043
|
-
|
|
8949
|
+
if (!(code === "REQUEST" || code === "THRESHOLD" || code === "SHARE" || code === "CAPACITY" || code === "LIMIT" || code === "TOPIC" || code === "LOOKUP" || code === "GAUGE" || code === "REFERENCE" || code === "SNAPSHOT" || code === "QUESTION")) return false;
|
|
8950
|
+
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
8951
|
+
return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
|
|
8044
8952
|
});
|
|
8045
8953
|
}
|
|
8046
8954
|
// src/core/ledgers/helpers.ts
|
|
@@ -9085,16 +9993,18 @@ var Classifier = class {
|
|
|
9085
9993
|
* Asks the judge every question the filing still lacks, in the measured order.
|
|
9086
9994
|
* @param requests - The request ids, which skip category questions and receive only enabled request-topic questions
|
|
9087
9995
|
* @param signal - The caller's signal; an abort returns a fault with completed judgments and usage
|
|
9088
|
-
* @returns The judgment keys, summed usage, and one attempt per logical question, in asking order; a throw from the assign or entities handler, a scheduler rejection, or a caller abort returns the partial result with `fault`; a judge rejection under a live signal leaves that question undecided and sets
|
|
9996
|
+
* @returns The judgment keys, summed usage, and one attempt per logical question, in asking order; a throw from the assign or entities handler, a scheduler rejection, or a caller abort returns the partial result with `fault`; a judge rejection under a live signal leaves that question undecided and sets `failure`
|
|
9089
9997
|
*/
|
|
9090
9998
|
async classify(requests, signal) {
|
|
9091
9999
|
const results = [];
|
|
9092
10000
|
const fault = await this.#gather(requests, signal, results).then(() => void 0, (error) => (0, _orkestrel_contract.isError)(error) ? error : new Error((0, _orkestrel_contract.errorToMessage)(error)));
|
|
9093
10001
|
const usage = results.reduce((total, result) => result.usage === void 0 ? total : sumUsage(total, result.usage), void 0);
|
|
10002
|
+
const failure = results.find((result) => result.failure !== void 0)?.failure;
|
|
9094
10003
|
return {
|
|
9095
10004
|
judgments: results.flatMap((result) => result.judgments),
|
|
9096
10005
|
attempts: results.flatMap((result) => result.attempts),
|
|
9097
10006
|
...fault === void 0 ? {} : { fault },
|
|
10007
|
+
...failure === void 0 ? {} : { failure },
|
|
9098
10008
|
...usage === void 0 ? {} : { usage }
|
|
9099
10009
|
};
|
|
9100
10010
|
}
|
|
@@ -9402,78 +10312,17 @@ var Classifier = class {
|
|
|
9402
10312
|
judgments: [],
|
|
9403
10313
|
attempts
|
|
9404
10314
|
});
|
|
9405
|
-
if (isJudgeError(error) && error.code === "QUESTION" || DETERMINISTIC_JUDGE_ERROR.test(renderCauseChain(error))) this.#failed.add(fingerprint);
|
|
9406
10315
|
if (signal.aborted) throw error;
|
|
10316
|
+
const failure = (0, _orkestrel_contract.isError)(error) ? error : new Error((0, _orkestrel_contract.errorToMessage)(error));
|
|
10317
|
+
if (this.#options.strict === false && (isJudgeError(error) && error.code === "QUESTION" || DETERMINISTIC_JUDGE_ERROR.test(renderCauseChain(error)))) this.#failed.add(fingerprint);
|
|
9407
10318
|
return {
|
|
9408
10319
|
judgments: [],
|
|
9409
|
-
attempts: []
|
|
10320
|
+
attempts: [],
|
|
10321
|
+
failure
|
|
9410
10322
|
};
|
|
9411
10323
|
}
|
|
9412
10324
|
}
|
|
9413
10325
|
};
|
|
9414
|
-
// src/core/ledgers/validators.ts
|
|
9415
|
-
/**
|
|
9416
|
-
* Checks whether a value is finite and lies in the interval (0, 1].
|
|
9417
|
-
* @param value - The value to inspect
|
|
9418
|
-
* @returns True if the value is a valid ledger fraction; false otherwise
|
|
9419
|
-
* @example
|
|
9420
|
-
* ```ts
|
|
9421
|
-
* isFraction(0.8) // true
|
|
9422
|
-
* ```
|
|
9423
|
-
*/
|
|
9424
|
-
function isFraction(value) {
|
|
9425
|
-
return (0, _orkestrel_contract.isFiniteNumber)(value) && value > 0 && value <= 1;
|
|
9426
|
-
}
|
|
9427
|
-
/**
|
|
9428
|
-
* Checks whether a value is a positive safe integer.
|
|
9429
|
-
* @param value - The value to inspect
|
|
9430
|
-
* @returns True if the value is a positive safe integer; false otherwise
|
|
9431
|
-
* @example
|
|
9432
|
-
* ```ts
|
|
9433
|
-
* isPositiveSafeInteger(4096) // true
|
|
9434
|
-
* ```
|
|
9435
|
-
*/
|
|
9436
|
-
function isPositiveSafeInteger(value) {
|
|
9437
|
-
return (0, _orkestrel_contract.isFiniteNumber)(value) && Number.isSafeInteger(value) && value > 0;
|
|
9438
|
-
}
|
|
9439
|
-
/**
|
|
9440
|
-
* Checks whether a value is a ledger snapshot whose marked ids name messages of its own conversation.
|
|
9441
|
-
*
|
|
9442
|
-
* @remarks
|
|
9443
|
-
* `conversation` must pass `isConversationSnapshot`, and `requests`, `notes`, and `failures` must be
|
|
9444
|
-
* dense arrays of strings. Each request and note id must name a `user` message of
|
|
9445
|
-
* `conversation.messages` and each failure id a `tool` message, so an id only a section original
|
|
9446
|
-
* holds is refused. A `gauge`, when present, must hold a finite `scale` greater than 0 and a finite
|
|
9447
|
-
* `overhead` of at least 0. Unknown members are admitted. Unreadable members and untrusted inputs
|
|
9448
|
-
* return false. The guard is structural: it admits a conversation holding a stored `system` role
|
|
9449
|
-
* or a repeated id, which `requireConversationSnapshot` and the `Conversation` constructor then
|
|
9450
|
-
* refuse as the stored-data check a ledger's hydrate runs.
|
|
9451
|
-
*
|
|
9452
|
-
* @param value - The value to inspect, such as a parsed checkpoint
|
|
9453
|
-
* @returns True if the value has the shape of a ledger snapshot whose marked ids name messages of its own conversation; false otherwise
|
|
9454
|
-
* @example
|
|
9455
|
-
* ```ts
|
|
9456
|
-
* const conversation = { id: 'c1', sections: [], messages: [{ id: 'm1', role: 'user', content: 'Check FX-111.' }] }
|
|
9457
|
-
* isLedgerSnapshot({ conversation, requests: ['m1'], notes: [], failures: [] }) // true
|
|
9458
|
-
* isLedgerSnapshot({ conversation, requests: [], notes: [], failures: ['m1'] }) // false
|
|
9459
|
-
* ```
|
|
9460
|
-
*/
|
|
9461
|
-
function isLedgerSnapshot(value) {
|
|
9462
|
-
return (0, _orkestrel_contract.holds)(() => {
|
|
9463
|
-
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
9464
|
-
const { conversation, requests, notes, failures, gauge } = value;
|
|
9465
|
-
if (!isConversationSnapshot(conversation)) return false;
|
|
9466
|
-
const isIds = (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString);
|
|
9467
|
-
if (!isIds(requests) || !isIds(notes) || !isIds(failures)) return false;
|
|
9468
|
-
if (gauge !== void 0) {
|
|
9469
|
-
if (!(0, _orkestrel_contract.isRecord)(gauge)) return false;
|
|
9470
|
-
const { scale, overhead } = gauge;
|
|
9471
|
-
if (!(0, _orkestrel_contract.isFiniteNumber)(scale) || scale <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(overhead) || overhead < 0) return false;
|
|
9472
|
-
}
|
|
9473
|
-
const roles = new Map(conversation.messages.map((message) => [message.id, message.role]));
|
|
9474
|
-
return requests.every((id) => roles.get(id) === "user") && notes.every((id) => roles.get(id) === "user") && failures.every((id) => roles.get(id) === "tool");
|
|
9475
|
-
});
|
|
9476
|
-
}
|
|
9477
10326
|
// src/core/ledgers/Gauge.ts
|
|
9478
10327
|
/**
|
|
9479
10328
|
* Prices prompts in tokens from a measured scale and overhead cost, and measures the room a request
|
|
@@ -9734,6 +10583,7 @@ var LedgerPass = class {
|
|
|
9734
10583
|
var LedgerRequest = class {
|
|
9735
10584
|
#content;
|
|
9736
10585
|
#limit;
|
|
10586
|
+
#faults = /* @__PURE__ */ new Set();
|
|
9737
10587
|
#message;
|
|
9738
10588
|
#plan;
|
|
9739
10589
|
#passes = [];
|
|
@@ -9839,6 +10689,12 @@ var LedgerRequest = class {
|
|
|
9839
10689
|
if (message?.thinking === void 0) return;
|
|
9840
10690
|
pass.think(computeThinking(message, completion));
|
|
9841
10691
|
}
|
|
10692
|
+
fault(error) {
|
|
10693
|
+
this.#faults.add(error);
|
|
10694
|
+
}
|
|
10695
|
+
faulted(error) {
|
|
10696
|
+
return this.#faults.has(error);
|
|
10697
|
+
}
|
|
9842
10698
|
file(attempts, plan) {
|
|
9843
10699
|
this.#attempts = [...this.#attempts, ...attempts];
|
|
9844
10700
|
if (plan !== void 0) this.#plan = plan;
|
|
@@ -9971,6 +10827,108 @@ var LedgerScan = class {
|
|
|
9971
10827
|
return this.#positions.get(id);
|
|
9972
10828
|
}
|
|
9973
10829
|
};
|
|
10830
|
+
// src/core/ledgers/shapers.ts
|
|
10831
|
+
/**
|
|
10832
|
+
* Describes a ledger snapshot exactly: an unknown key refuses, so a key a later version adds never reads
|
|
10833
|
+
* as absent.
|
|
10834
|
+
*
|
|
10835
|
+
* @remarks
|
|
10836
|
+
* `isLedgerSnapshot` is the door a hydrate reads, and this shape fills a refusal's faults. What the shape
|
|
10837
|
+
* cannot state, that each marked id names a message of the snapshot's own conversation, is the guard's
|
|
10838
|
+
* alone.
|
|
10839
|
+
*/
|
|
10840
|
+
var ledgerSnapshotShape = (0, _orkestrel_contract.objectShape)({
|
|
10841
|
+
conversation: conversationSnapshotShape,
|
|
10842
|
+
requests: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
|
|
10843
|
+
notes: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
|
|
10844
|
+
failures: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
|
|
10845
|
+
gauge: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
|
|
10846
|
+
scale: (0, _orkestrel_contract.numberShape)(),
|
|
10847
|
+
overhead: (0, _orkestrel_contract.numberShape)()
|
|
10848
|
+
}))
|
|
10849
|
+
});
|
|
10850
|
+
// src/core/ledgers/contracts.ts
|
|
10851
|
+
/** Reads the faults of a refused ledger snapshot. */
|
|
10852
|
+
var ledgerSnapshotContract = (0, _orkestrel_contract.createContract)(ledgerSnapshotShape);
|
|
10853
|
+
// src/core/ledgers/validators.ts
|
|
10854
|
+
/**
|
|
10855
|
+
* Checks whether a value is finite and lies in the interval (0, 1].
|
|
10856
|
+
* @param value - The value to inspect
|
|
10857
|
+
* @returns True if the value is a valid ledger fraction; false otherwise
|
|
10858
|
+
* @example
|
|
10859
|
+
* ```ts
|
|
10860
|
+
* isFraction(0.8) // true
|
|
10861
|
+
* ```
|
|
10862
|
+
*/
|
|
10863
|
+
function isFraction(value) {
|
|
10864
|
+
return (0, _orkestrel_contract.isFiniteNumber)(value) && value > 0 && value <= 1;
|
|
10865
|
+
}
|
|
10866
|
+
/**
|
|
10867
|
+
* Checks whether a value holds the wording a ledger files with: a choice `category` question whose
|
|
10868
|
+
* criteria describe every category, a non-empty `topic` string, and noul `amends` and `supersedes`
|
|
10869
|
+
* questions.
|
|
10870
|
+
*
|
|
10871
|
+
* @remarks
|
|
10872
|
+
* A ledger asks these on every message, so a hole in them would otherwise surface as filing that quietly
|
|
10873
|
+
* stays undecided. Total: an unreadable member returns false.
|
|
10874
|
+
*
|
|
10875
|
+
* @param value - The value to inspect
|
|
10876
|
+
* @returns True if the value holds the wording the ledger's questions read; false otherwise
|
|
10877
|
+
* @example
|
|
10878
|
+
* ```ts
|
|
10879
|
+
* isLedgerQuestion(LEDGER_QUESTIONS) // true
|
|
10880
|
+
* isLedgerQuestion({ topic: 'Does it concern this?' }) // false
|
|
10881
|
+
* ```
|
|
10882
|
+
*/
|
|
10883
|
+
function isLedgerQuestion(value) {
|
|
10884
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
10885
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
10886
|
+
const { category, topic, amends, supersedes } = value;
|
|
10887
|
+
if (!isJudgeQuestion(category) || category.form !== "choice") return false;
|
|
10888
|
+
const { criteria } = category;
|
|
10889
|
+
return LEDGER_CATEGORIES.every((one) => isJudgeEntry(criteria[one])) && (0, _orkestrel_contract.isString)(topic) && topic.trim() !== "" && isJudgeQuestion(amends) && amends.form === "noul" && isJudgeQuestion(supersedes) && supersedes.form === "noul";
|
|
10890
|
+
});
|
|
10891
|
+
}
|
|
10892
|
+
/**
|
|
10893
|
+
* Checks whether a value is a ledger snapshot whose marked ids name messages of its own conversation.
|
|
10894
|
+
*
|
|
10895
|
+
* @remarks
|
|
10896
|
+
* `conversation` must pass `isConversationSnapshot`, and `requests`, `notes`, and `failures` must be
|
|
10897
|
+
* dense arrays of strings. Each request and note id must name a `user` message of
|
|
10898
|
+
* `conversation.messages` and each failure id a `tool` message, so an id only a section original
|
|
10899
|
+
* holds is refused. A `gauge`, when present, must hold a finite `scale` greater than 0 and a finite
|
|
10900
|
+
* `overhead` of at least 0. The guard is exact: an unknown member and an own `undefined` `gauge` return
|
|
10901
|
+
* false, at the snapshot and in its `gauge`. Unreadable members and untrusted inputs return false. The
|
|
10902
|
+
* guard is structural: it admits a conversation holding a stored `system` role
|
|
10903
|
+
* or a repeated id, which `requireConversationSnapshot` and the `Conversation` constructor then
|
|
10904
|
+
* refuse as the stored-data check a ledger's hydrate runs.
|
|
10905
|
+
*
|
|
10906
|
+
* @param value - The value to inspect, such as a parsed checkpoint
|
|
10907
|
+
* @returns True if the value has the shape of a ledger snapshot whose marked ids name messages of its own conversation; false otherwise
|
|
10908
|
+
* @example
|
|
10909
|
+
* ```ts
|
|
10910
|
+
* const conversation = { id: 'c1', sections: [], messages: [{ id: 'm1', role: 'user', content: 'Check FX-111.' }] }
|
|
10911
|
+
* isLedgerSnapshot({ conversation, requests: ['m1'], notes: [], failures: [] }) // true
|
|
10912
|
+
* isLedgerSnapshot({ conversation, requests: [], notes: [], failures: ['m1'] }) // false
|
|
10913
|
+
* ```
|
|
10914
|
+
*/
|
|
10915
|
+
function isLedgerSnapshot(value) {
|
|
10916
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
10917
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
10918
|
+
const { conversation, requests, notes, failures, gauge } = value;
|
|
10919
|
+
if (Object.keys(value).length !== (gauge === void 0 ? 4 : 5)) return false;
|
|
10920
|
+
if (!isConversationSnapshot(conversation)) return false;
|
|
10921
|
+
const isIds = (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString);
|
|
10922
|
+
if (!isIds(requests) || !isIds(notes) || !isIds(failures)) return false;
|
|
10923
|
+
if (gauge !== void 0) {
|
|
10924
|
+
if (!(0, _orkestrel_contract.isRecord)(gauge) || Object.keys(gauge).length !== 2) return false;
|
|
10925
|
+
const { scale, overhead } = gauge;
|
|
10926
|
+
if (!(0, _orkestrel_contract.isFiniteNumber)(scale) || scale <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(overhead) || overhead < 0) return false;
|
|
10927
|
+
}
|
|
10928
|
+
const roles = new Map(conversation.messages.map((message) => [message.id, message.role]));
|
|
10929
|
+
return requests.every((id) => roles.get(id) === "user") && notes.every((id) => roles.get(id) === "user") && failures.every((id) => roles.get(id) === "tool");
|
|
10930
|
+
});
|
|
10931
|
+
}
|
|
9974
10932
|
// src/core/ledgers/Ledger.ts
|
|
9975
10933
|
/**
|
|
9976
10934
|
* Serves one conversation through classified records, a bounded copy of each request turn that carries them, and a final answer pass.
|
|
@@ -10004,7 +10962,8 @@ var Ledger = class {
|
|
|
10004
10962
|
* @param options - The filing policy, capacity, and request bounds
|
|
10005
10963
|
* @throws {LedgerError} Thrown when a threshold, share, capacity, limit, topic, lookup, or gauge is invalid
|
|
10006
10964
|
* @throws {LedgerError} Thrown when `predict` is not a nonnegative safe integer less than `capacity` (code `'CAPACITY'`)
|
|
10007
|
-
* @throws {LedgerError} Thrown when `
|
|
10965
|
+
* @throws {LedgerError} Thrown when `questions` fails `isLedgerQuestion` (code `'QUESTION'`)
|
|
10966
|
+
* @throws {LedgerError} Thrown when `snapshot` is malformed or marks an id its conversation does not hold (code `'SNAPSHOT'`, its faults under `context.faults`)
|
|
10008
10967
|
* @throws {ConversationError} Thrown when `isLedgerSnapshot` admits `snapshot` but its conversation holds a `system` message (code `'ROLE'`) or repeats an id (code `'SNAPSHOT'`)
|
|
10009
10968
|
*/
|
|
10010
10969
|
constructor(provider, options) {
|
|
@@ -10021,13 +10980,18 @@ var Ledger = class {
|
|
|
10021
10980
|
for (const value of Object.values(options.share ?? {})) if (!isFraction(value)) throw new LedgerError("SHARE", "shares must be finite and in (0, 1]");
|
|
10022
10981
|
if (!isPositiveSafeInteger(options.capacity)) throw new LedgerError("CAPACITY", "capacity must be a positive safe integer");
|
|
10023
10982
|
this.#predict = resolvePredict(options.predict, options.capacity);
|
|
10024
|
-
|
|
10983
|
+
if (options.recall?.limit !== void 0 && !isNonNegativeSafeInteger(options.recall.limit)) throw new LedgerError("LIMIT", "a recall limit must be a nonnegative safe integer");
|
|
10984
|
+
if (options.agent?.limit !== void 0 && !isPositiveSafeInteger(options.agent.limit)) throw new LedgerError("LIMIT", "an agent limit must be a positive safe integer");
|
|
10025
10985
|
const topics = Array.from(options.topics, (topic) => topic.name);
|
|
10026
10986
|
if (topics.some((name) => name.trim() === "") || new Set(topics).size !== topics.length) throw new LedgerError("TOPIC", "topic names must be nonempty and unique");
|
|
10027
10987
|
const lookups = ["recall", ...Array.from(options.lookups ?? [], (lookup) => lookup.tool.name)];
|
|
10028
10988
|
if (new Set(lookups).size !== lookups.length) throw new LedgerError("LOOKUP", "lookup names must be unique and cannot be recall");
|
|
10989
|
+
if (!isLedgerQuestion(options.questions)) throw new LedgerError("QUESTION", "questions must hold a choice category question whose criteria describe every category, a non-empty topic string, and noul amends and supersedes questions; pass the LEDGER_QUESTIONS constant for the measured wording");
|
|
10029
10990
|
const snapshot = options.snapshot;
|
|
10030
|
-
if (snapshot !== void 0 && !isLedgerSnapshot(snapshot))
|
|
10991
|
+
if (snapshot !== void 0 && !isLedgerSnapshot(snapshot)) {
|
|
10992
|
+
const faults = ledgerSnapshotContract.audit(snapshot);
|
|
10993
|
+
throw new LedgerError("SNAPSHOT", `snapshot must be well formed and mark only messages of its conversation; pass what snapshot() returned${faults.length === 0 ? "" : `\n${(0, _orkestrel_contract.describeFaults)(faults, ledgerSnapshotContract.schema)}`}`, { context: { faults } });
|
|
10994
|
+
}
|
|
10031
10995
|
this.#provider = provider;
|
|
10032
10996
|
this.#replay = provider.replay ?? "none";
|
|
10033
10997
|
this.#options = options;
|
|
@@ -10058,7 +11022,8 @@ var Ledger = class {
|
|
|
10058
11022
|
return matchEntities(scan?.index ?? buildLedgerIndex(collectRegistry(this.#readLookups(this.#conversation.messages()))), text, partial);
|
|
10059
11023
|
},
|
|
10060
11024
|
...options.correction === void 0 ? {} : { correction: options.correction },
|
|
10061
|
-
...options.agent?.scheduler === void 0 ? {} : { scheduler: options.agent.scheduler }
|
|
11025
|
+
...options.agent?.scheduler === void 0 ? {} : { scheduler: options.agent.scheduler },
|
|
11026
|
+
...options.agent?.strict === void 0 ? {} : { strict: options.agent.strict }
|
|
10062
11027
|
});
|
|
10063
11028
|
const tools = (0, _orkestrel_tool.createToolManager)();
|
|
10064
11029
|
for (const lookup of options.lookups ?? []) tools.add((0, _orkestrel_tool.createTool)({
|
|
@@ -10073,14 +11038,10 @@ var Ledger = class {
|
|
|
10073
11038
|
tools.add((0, _orkestrel_tool.createTool)({
|
|
10074
11039
|
name: "recall",
|
|
10075
11040
|
description: `Recall what the full conversation record holds on a topic: an owner name, an id, or one of the topics ${renderTopicNames(options.topics)}. Returns source lines, newest first.`,
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
description: "An owner name, an id, or a topic"
|
|
10081
|
-
} },
|
|
10082
|
-
required: ["topic"]
|
|
10083
|
-
},
|
|
11041
|
+
contract: (0, _orkestrel_contract.objectShape)({ topic: (0, _orkestrel_contract.stringShape)({
|
|
11042
|
+
min: 1,
|
|
11043
|
+
description: "An owner name, an id, or a topic"
|
|
11044
|
+
}) }),
|
|
10084
11045
|
execute: (args) => this.#recall(args)
|
|
10085
11046
|
}));
|
|
10086
11047
|
this.#agent = createAgent(provider, {
|
|
@@ -10088,8 +11049,19 @@ var Ledger = class {
|
|
|
10088
11049
|
conversations,
|
|
10089
11050
|
system: options.system,
|
|
10090
11051
|
tools,
|
|
11052
|
+
authority: createAuthority({
|
|
11053
|
+
rules: [{
|
|
11054
|
+
match: (context) => lookups.includes(context.call.name),
|
|
11055
|
+
zone: "ledger",
|
|
11056
|
+
verdict: "allow"
|
|
11057
|
+
}],
|
|
11058
|
+
fallback: {
|
|
11059
|
+
zone: "ledger",
|
|
11060
|
+
verdict: "deny",
|
|
11061
|
+
reason: "the ledger advertises no such tool"
|
|
11062
|
+
}
|
|
11063
|
+
}),
|
|
10091
11064
|
limit: options.agent?.limit ?? 8,
|
|
10092
|
-
strict: false,
|
|
10093
11065
|
select: (conversation, request, signal) => this.#select(conversation, request, signal)
|
|
10094
11066
|
});
|
|
10095
11067
|
this.#agent.emitter.on("tool", (_call, result) => {
|
|
@@ -10104,6 +11076,7 @@ var Ledger = class {
|
|
|
10104
11076
|
if (isLedgerError(selection.fault) && selection.fault.code === "REQUEST") return;
|
|
10105
11077
|
this.#current()?.adopt(selection, this.#conversation.messages().length);
|
|
10106
11078
|
});
|
|
11079
|
+
this.#agent.emitter.on("fault", (error) => this.#current()?.fault(error));
|
|
10107
11080
|
this.#agent.emitter.on("turn", () => this.#observeTurn());
|
|
10108
11081
|
this.#agent.emitter.on("usage", (usage) => this.#current()?.spend(usage));
|
|
10109
11082
|
}
|
|
@@ -10169,7 +11142,7 @@ var Ledger = class {
|
|
|
10169
11142
|
/**
|
|
10170
11143
|
* Appends a request and serves it, recovering an unfinished or rejected first pass with one answer pass.
|
|
10171
11144
|
* A calibration call that reports no prompt usage, or a prompt usage of 0 or less, rejects with `LedgerError` code `'GAUGE'`; a provider error during calibration rejects with that error.
|
|
10172
|
-
* When the answer pass rejects, `respond` rejects with that error.
|
|
11145
|
+
* When the answer pass rejects, `respond` rejects with that error, and under the default `strict` a judge failure or another selection failure rejects it too. A rejection belongs to its own call: the ledger stays free, and the next call asks the judge again about whatever is still unanswered.
|
|
10173
11146
|
* @param content - The request text
|
|
10174
11147
|
* @param signal - The caller's cancellation signal; an abort during calibration rejects with its reason
|
|
10175
11148
|
* @returns The final pass, the usage of every pass, and the filing attempts of this request alone
|
|
@@ -10186,11 +11159,14 @@ var Ledger = class {
|
|
|
10186
11159
|
if (this.#gauge === void 0) await this.#measureGauge(caller);
|
|
10187
11160
|
const entered = request.enter(this.#conversation);
|
|
10188
11161
|
this.#requests.add(entered.id);
|
|
10189
|
-
const first = await this.#generatePass(request, caller, this.#options.think).catch(() =>
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
11162
|
+
const first = await this.#generatePass(request, caller, this.#options.think).catch((error) => {
|
|
11163
|
+
if (request.faulted(error)) throw error;
|
|
11164
|
+
return {
|
|
11165
|
+
content: "",
|
|
11166
|
+
partial: true,
|
|
11167
|
+
...request.usage === void 0 ? {} : { usage: request.usage }
|
|
11168
|
+
};
|
|
11169
|
+
});
|
|
10194
11170
|
const passes = [first];
|
|
10195
11171
|
if (!caller.aborted && (first.partial || first.content.trim() === "")) {
|
|
10196
11172
|
const digest = this.#buildDigest(request, entered);
|
|
@@ -10243,18 +11219,14 @@ var Ledger = class {
|
|
|
10243
11219
|
}
|
|
10244
11220
|
async #measureGauge(signal) {
|
|
10245
11221
|
try {
|
|
10246
|
-
const messages = stripThinking(
|
|
10247
|
-
...this.#
|
|
10248
|
-
messages: [],
|
|
10249
|
-
judgments: []
|
|
10250
|
-
}),
|
|
10251
|
-
...this.#conversation.view(),
|
|
10252
|
-
{
|
|
11222
|
+
const messages = stripThinking(this.#agent.context.build({
|
|
11223
|
+
messages: [...this.#conversation.view(), {
|
|
10253
11224
|
id: "calibration",
|
|
10254
11225
|
role: "user",
|
|
10255
11226
|
content: ""
|
|
10256
|
-
}
|
|
10257
|
-
|
|
11227
|
+
}],
|
|
11228
|
+
judgments: []
|
|
11229
|
+
}), this.#replay).slice(0, -1);
|
|
10258
11230
|
const priced = await this.#provider.generate(messages, signal, this.#agent.context.tools.definitions(), { think: false });
|
|
10259
11231
|
const bare = await this.#provider.generate(messages, signal, void 0, { think: false });
|
|
10260
11232
|
if (!(0, _orkestrel_contract.isFiniteNumber)(priced.usage?.prompt) || priced.usage.prompt <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(bare.usage?.prompt) || bare.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from both calls");
|
|
@@ -10431,12 +11403,13 @@ var Ledger = class {
|
|
|
10431
11403
|
return scan;
|
|
10432
11404
|
}
|
|
10433
11405
|
#file(classified, request, selected, message) {
|
|
10434
|
-
const { attempts, ...filed } = classified;
|
|
11406
|
+
const { attempts, failure, ...filed } = classified;
|
|
10435
11407
|
const planned = (0, _orkestrel_contract.attempt)(() => {
|
|
10436
11408
|
if (filed.fault !== void 0) throw filed.fault;
|
|
10437
11409
|
return {
|
|
10438
11410
|
...this.#plan(selected, this.#rescan()),
|
|
10439
|
-
...filed
|
|
11411
|
+
...filed,
|
|
11412
|
+
...failure === void 0 ? {} : { fault: failure }
|
|
10440
11413
|
};
|
|
10441
11414
|
});
|
|
10442
11415
|
request.file(attempts, planned.success ? planned.value : void 0);
|
|
@@ -10774,7 +11747,6 @@ exports.ConversationError = ConversationError;
|
|
|
10774
11747
|
exports.ConversationManager = ConversationManager;
|
|
10775
11748
|
exports.DECISIVE_CATEGORIES = DECISIVE_CATEGORIES;
|
|
10776
11749
|
exports.DEFAULT_AGENT_LIMIT = DEFAULT_AGENT_LIMIT;
|
|
10777
|
-
exports.DEFAULT_AUTHORITY_ZONE = DEFAULT_AUTHORITY_ZONE;
|
|
10778
11750
|
exports.DEFAULT_CONVERSATION_KEEP = DEFAULT_CONVERSATION_KEEP;
|
|
10779
11751
|
exports.DEFAULT_LEDGER_LIMIT = DEFAULT_LEDGER_LIMIT;
|
|
10780
11752
|
exports.DEFAULT_LEDGER_SHARE = DEFAULT_LEDGER_SHARE;
|
|
@@ -10839,6 +11811,8 @@ exports.agentAnswerShape = agentAnswerShape;
|
|
|
10839
11811
|
exports.agentChunkToFrame = agentChunkToFrame;
|
|
10840
11812
|
exports.agentFrameContract = agentFrameContract;
|
|
10841
11813
|
exports.agentFrameShape = agentFrameShape;
|
|
11814
|
+
exports.agentJobContract = agentJobContract;
|
|
11815
|
+
exports.agentJobShape = agentJobShape;
|
|
10842
11816
|
exports.agentPromptContract = agentPromptContract;
|
|
10843
11817
|
exports.agentPromptShape = agentPromptShape;
|
|
10844
11818
|
exports.agentResultToFrame = agentResultToFrame;
|
|
@@ -10872,6 +11846,8 @@ exports.collectProjectionIds = collectProjectionIds;
|
|
|
10872
11846
|
exports.collectRegistry = collectRegistry;
|
|
10873
11847
|
exports.collectToolGroups = collectToolGroups;
|
|
10874
11848
|
exports.computeThinking = computeThinking;
|
|
11849
|
+
exports.conversationSnapshotContract = conversationSnapshotContract;
|
|
11850
|
+
exports.conversationSnapshotShape = conversationSnapshotShape;
|
|
10875
11851
|
exports.copyJSON = copyJSON;
|
|
10876
11852
|
exports.createAgent = createAgent;
|
|
10877
11853
|
exports.createAgentContext = createAgentContext;
|
|
@@ -10900,7 +11876,9 @@ exports.createSystemOneJudge = createSystemOneJudge;
|
|
|
10900
11876
|
exports.createThinkSplitter = createThinkSplitter;
|
|
10901
11877
|
exports.cutListing = cutListing;
|
|
10902
11878
|
exports.denyCall = denyCall;
|
|
11879
|
+
exports.describeQuestionRefusal = describeQuestionRefusal;
|
|
10903
11880
|
exports.describeRelayRefusal = describeRelayRefusal;
|
|
11881
|
+
exports.describeSnapshotRefusal = describeSnapshotRefusal;
|
|
10904
11882
|
exports.estimateMessages = estimateMessages;
|
|
10905
11883
|
exports.estimateRequest = estimateRequest;
|
|
10906
11884
|
exports.estimateTokens = estimateTokens;
|
|
@@ -10922,29 +11900,44 @@ exports.intersectKeys = intersectKeys;
|
|
|
10922
11900
|
exports.isAgentError = isAgentError;
|
|
10923
11901
|
exports.isAgentJobError = isAgentJobError;
|
|
10924
11902
|
exports.isAuthorityDecision = isAuthorityDecision;
|
|
11903
|
+
exports.isAuthorityVerdict = isAuthorityVerdict;
|
|
10925
11904
|
exports.isContextError = isContextError;
|
|
10926
11905
|
exports.isConversation = isConversation;
|
|
10927
11906
|
exports.isConversationError = isConversationError;
|
|
10928
11907
|
exports.isConversationSnapshot = isConversationSnapshot;
|
|
10929
11908
|
exports.isFraction = isFraction;
|
|
10930
11909
|
exports.isJudgeAbortError = isJudgeAbortError;
|
|
11910
|
+
exports.isJudgeAnswer = isJudgeAnswer;
|
|
10931
11911
|
exports.isJudgeEntry = isJudgeEntry;
|
|
10932
11912
|
exports.isJudgeError = isJudgeError;
|
|
10933
11913
|
exports.isJudgeQuestion = isJudgeQuestion;
|
|
10934
11914
|
exports.isJudgment = isJudgment;
|
|
10935
11915
|
exports.isLedgerError = isLedgerError;
|
|
11916
|
+
exports.isLedgerQuestion = isLedgerQuestion;
|
|
10936
11917
|
exports.isLedgerSnapshot = isLedgerSnapshot;
|
|
10937
11918
|
exports.isMemo = isMemo;
|
|
11919
|
+
exports.isMemoOrigin = isMemoOrigin;
|
|
10938
11920
|
exports.isMemoSnapshot = isMemoSnapshot;
|
|
10939
11921
|
exports.isMessage = isMessage;
|
|
11922
|
+
exports.isMessageInput = isMessageInput;
|
|
10940
11923
|
exports.isNonNegativeSafeInteger = isNonNegativeSafeInteger;
|
|
10941
11924
|
exports.isPositiveSafeInteger = isPositiveSafeInteger;
|
|
10942
11925
|
exports.isProviderAbortError = isProviderAbortError;
|
|
10943
11926
|
exports.isProviderError = isProviderError;
|
|
11927
|
+
exports.isProviderResult = isProviderResult;
|
|
10944
11928
|
exports.isSection = isSection;
|
|
10945
11929
|
exports.isSystemOneAnswer = isSystemOneAnswer;
|
|
10946
11930
|
exports.isSystemOneResponse = isSystemOneResponse;
|
|
10947
11931
|
exports.joinThinking = joinThinking;
|
|
11932
|
+
exports.judgeAnswerContract = judgeAnswerContract;
|
|
11933
|
+
exports.judgeAnswerShape = judgeAnswerShape;
|
|
11934
|
+
exports.judgeEntryShape = judgeEntryShape;
|
|
11935
|
+
exports.judgeQuestionContract = judgeQuestionContract;
|
|
11936
|
+
exports.judgeQuestionShape = judgeQuestionShape;
|
|
11937
|
+
exports.judgmentContract = judgmentContract;
|
|
11938
|
+
exports.judgmentShape = judgmentShape;
|
|
11939
|
+
exports.ledgerSnapshotContract = ledgerSnapshotContract;
|
|
11940
|
+
exports.ledgerSnapshotShape = ledgerSnapshotShape;
|
|
10948
11941
|
exports.linkOwners = linkOwners;
|
|
10949
11942
|
exports.matchEntities = matchEntities;
|
|
10950
11943
|
exports.matchesCutLine = matchesCutLine;
|
|
@@ -10953,7 +11946,13 @@ exports.matchesJudgment = matchesJudgment;
|
|
|
10953
11946
|
exports.measureAnswer = measureAnswer;
|
|
10954
11947
|
exports.measureMessage = measureMessage;
|
|
10955
11948
|
exports.measureRequest = measureRequest;
|
|
11949
|
+
exports.memoContract = memoContract;
|
|
11950
|
+
exports.memoShape = memoShape;
|
|
11951
|
+
exports.memoSnapshotContract = memoSnapshotContract;
|
|
11952
|
+
exports.memoSnapshotShape = memoSnapshotShape;
|
|
10956
11953
|
exports.messageContract = messageContract;
|
|
11954
|
+
exports.messageInputContract = messageInputContract;
|
|
11955
|
+
exports.messageInputShape = messageInputShape;
|
|
10957
11956
|
exports.messageShape = messageShape;
|
|
10958
11957
|
exports.normalizeArgument = normalizeArgument;
|
|
10959
11958
|
exports.normalizeWord = normalizeWord;
|
|
@@ -10968,6 +11967,8 @@ exports.rankLedgerCut = rankLedgerCut;
|
|
|
10968
11967
|
exports.readBodyText = readBodyText;
|
|
10969
11968
|
exports.readChunks = readChunks;
|
|
10970
11969
|
exports.readFailure = readFailure;
|
|
11970
|
+
exports.readJudgeOutcome = readJudgeOutcome;
|
|
11971
|
+
exports.readProviderDelta = readProviderDelta;
|
|
10971
11972
|
exports.relayFrameContract = relayFrameContract;
|
|
10972
11973
|
exports.relayFrameShape = relayFrameShape;
|
|
10973
11974
|
exports.releaseReader = releaseReader;
|
|
@@ -10986,15 +11987,21 @@ exports.renderStub = renderStub;
|
|
|
10986
11987
|
exports.renderToolValue = renderToolValue;
|
|
10987
11988
|
exports.renderTopicNames = renderTopicNames;
|
|
10988
11989
|
exports.requestToRelay = requestToRelay;
|
|
11990
|
+
exports.requireAnswer = requireAnswer;
|
|
10989
11991
|
exports.requireBudget = requireBudget;
|
|
10990
11992
|
exports.requireConversation = requireConversation;
|
|
10991
11993
|
exports.requireConversationSnapshot = requireConversationSnapshot;
|
|
10992
11994
|
exports.requireCumulative = requireCumulative;
|
|
10993
11995
|
exports.requireEntry = requireEntry;
|
|
11996
|
+
exports.requireFallback = requireFallback;
|
|
11997
|
+
exports.requireJudgeResult = requireJudgeResult;
|
|
10994
11998
|
exports.requireJudgment = requireJudgment;
|
|
10995
11999
|
exports.requireKeep = requireKeep;
|
|
10996
12000
|
exports.requireLimit = requireLimit;
|
|
12001
|
+
exports.requireMessageInput = requireMessageInput;
|
|
12002
|
+
exports.requireProviderResult = requireProviderResult;
|
|
10997
12003
|
exports.requireRole = requireRole;
|
|
12004
|
+
exports.requireRules = requireRules;
|
|
10998
12005
|
exports.requireSectionsCap = requireSectionsCap;
|
|
10999
12006
|
exports.requireSummary = requireSummary;
|
|
11000
12007
|
exports.resolveDecision = resolveDecision;
|
|
@@ -11002,8 +12009,13 @@ exports.resolvePredict = resolvePredict;
|
|
|
11002
12009
|
exports.sanitizeToken = sanitizeToken;
|
|
11003
12010
|
exports.sanitizeUsage = sanitizeUsage;
|
|
11004
12011
|
exports.scanAmendments = scanAmendments;
|
|
12012
|
+
exports.scanJudgmentTies = scanJudgmentTies;
|
|
12013
|
+
exports.scanProbability = scanProbability;
|
|
12014
|
+
exports.sectionContract = sectionContract;
|
|
12015
|
+
exports.sectionShape = sectionShape;
|
|
11005
12016
|
exports.selectRecords = selectRecords;
|
|
11006
12017
|
exports.settleAgentJob = settleAgentJob;
|
|
12018
|
+
exports.settleToolResult = settleToolResult;
|
|
11007
12019
|
exports.splitResultLines = splitResultLines;
|
|
11008
12020
|
exports.splitSentences = splitSentences;
|
|
11009
12021
|
exports.splitTopic = splitTopic;
|