@cubicecho/agent-core 4.9.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run/agent-loop.d.ts +37 -8
- package/dist/run/agent-loop.js +30 -10
- package/dist/run/events.d.ts +27 -3
- package/dist/run/events.js +43 -6
- package/dist/run/run-calls.d.ts +16 -3
- package/dist/run/run-calls.js +87 -10
- package/dist/wire/tool-calls.d.ts +74 -0
- package/dist/wire/tool-calls.js +17 -0
- package/llms.txt +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -532,6 +532,41 @@ its calls made on the error too, read off with `failedRun`.
|
|
|
532
532
|
A link meant for the model alone — one whose MCP `audience` leaves out `"user"` — is the
|
|
533
533
|
dispatcher's to drop, since that is MCP's rule and this package knows nothing of MCP.
|
|
534
534
|
|
|
535
|
+
### A call that ends the run
|
|
536
|
+
|
|
537
|
+
A tool whose call is the end of the job — handing off, asking a person — says so with `end` on
|
|
538
|
+
its `ToolAnswer`, `true` or `{ reason }`. The loop answers every call of that step, sends no
|
|
539
|
+
further request, and resolves with `ended`; aborting the run's signal from inside `dispatch` was
|
|
540
|
+
the way out before, and that is a failure the host then has to dig the run back out of.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
const result = await runAgentLoop({
|
|
544
|
+
// ...
|
|
545
|
+
dispatch: async ({ name, args }, signal) =>
|
|
546
|
+
name === "ask_person"
|
|
547
|
+
? { text: "Asked. This run ends here.", end: { reason: String(args.question) } }
|
|
548
|
+
: pool.call(name, args, { signal }),
|
|
549
|
+
});
|
|
550
|
+
if (result.ended) {
|
|
551
|
+
// { callId, name: "ask_person", reason }
|
|
552
|
+
const { callId, name, reason = "" } = result.ended;
|
|
553
|
+
emit(runId, { kind: RunEventKind.Done, ok: true, id: callId, name, text: reason });
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The step's other calls still run, whichever side of the end the model asked for them and however
|
|
558
|
+
`parallel` batches them: they were asked for in the same turn, and a model that says "write the
|
|
559
|
+
file, then hand off" wants the file written in either order. Where two calls end it, `ended` is
|
|
560
|
+
the first in the order asked. Everything else is a finished run's — `usage`, `metrics`,
|
|
561
|
+
`messages`, `artifacts`, the `afterTurn` hooks — with `metrics.outcome` `ended` and a notice
|
|
562
|
+
saying which tool ended it. `turn` is the turn that made the call, so its `content` is whatever
|
|
563
|
+
the model wrote beside its calls, often nothing; the run's answer is what the ending tool took.
|
|
564
|
+
The tool, its definition and its wording stay the host's: this is only the exit.
|
|
565
|
+
|
|
566
|
+
A host's `done` that carries the call — `id`, `name` the tool, `text` the reason — is read by
|
|
567
|
+
`runMetrics` as `ended` too, so the outcome is the same whether it is read off the result or the
|
|
568
|
+
events.
|
|
569
|
+
|
|
535
570
|
Arguments go through `parseToolArguments`, which is lenient where the model's meaning is plain:
|
|
536
571
|
JSON held in a string is opened, and the almost-JSON local models write — single quotes, Python's
|
|
537
572
|
`True` and `None`, bare keys, a trailing comma — is repaired, without touching what is inside a
|
|
@@ -623,6 +658,29 @@ a `tool-call` and a `tool-result` event, told to `onToolCall` and `onToolResult`
|
|
|
623
658
|
id is numbered by where the call lands in the transcript, so two questions do not share one. A name
|
|
624
659
|
with no definition in `tools`, or outside the catalogue, is left out, and none left is no exchange.
|
|
625
660
|
|
|
661
|
+
### What the servers said on the way
|
|
662
|
+
|
|
663
|
+
A server can log while a call runs — a warning that the cache was stale, an error it recovered
|
|
664
|
+
from — and that is often why the call went the way it did. `dispatch` is handed a third argument,
|
|
665
|
+
`report`, and `report.log(entry)` tells watchers of one such message as a `server-log` event for
|
|
666
|
+
the call: `id` the call's, `name` the tool, `level` the server's, and `text` the message's `data`,
|
|
667
|
+
as it was where a string and as JSON otherwise, cut by `preview`. The entry is MCP's
|
|
668
|
+
`notifications/message` params, `{ level, logger?, data }`, declared structurally, so
|
|
669
|
+
agent-mcp-pool's `onLog` takes `report.log` as it stands:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
dispatch: (call, signal, report) =>
|
|
673
|
+
pool.call(call.name, call.args, { servers, signal, onLog: report.log }),
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
None of it reaches the transcript: it is for the person watching, not the model. A call the loop
|
|
677
|
+
answers itself never logs, a deduplicated repeat reports nothing of its own, and a message heard
|
|
678
|
+
after the call has answered is dropped rather than landing after the result it explains.
|
|
679
|
+
`runMetrics` counts `warning` as `serverWarnings` and `error` and above as `serverErrors`, so a
|
|
680
|
+
summary can say the server complained without the events. A dispatcher written for
|
|
681
|
+
`(call, signal)` needs no change. `report` is an object so that what a call can report — progress
|
|
682
|
+
is next — grows there without `dispatch` changing again.
|
|
683
|
+
|
|
626
684
|
### What the servers say about themselves
|
|
627
685
|
|
|
628
686
|
An MCP server may send `instructions` when it connects — "resolve the library id before querying
|
package/dist/index.d.ts
CHANGED
|
@@ -39,5 +39,5 @@ export { backoffMs, ContextOverflow, EndpointSilent, isModelLoading, isOverflow,
|
|
|
39
39
|
export { CacheBreakReason, type Produced, type StreamTurnOptions, streamTurn, type Turn, type TurnUsage, } from './wire/stream.ts';
|
|
40
40
|
export { ALL_FENCES, DEFAULT_FENCES, type Fence, FenceSplitter, type FenceSplitterOptions, SPLIT_OUTPUT, SPLIT_REASONING, type Split, stripThinking, THINK_FENCE, } from './wire/thinking.ts';
|
|
41
41
|
export { type ContextBreakdown, type ContextBreakdownOptions, compact, contextChars, contextTokens, estimateTokens, messageTokens, requestChars, requestTokens, type TokenEstimateOptions, } from './wire/tokens.ts';
|
|
42
|
-
export { ARGUMENTS_MALFORMED, ARGUMENTS_TRUNCATED, parseToolArguments, type ResourceLink, type RunArtifact, recoverToolCalls, type ToolAnswer, ToolArgumentsError, type ToolCall, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult, } from './wire/tool-calls.ts';
|
|
42
|
+
export { ARGUMENTS_MALFORMED, ARGUMENTS_TRUNCATED, type EndedBy, parseToolArguments, type ResourceLink, type RunArtifact, recoverToolCalls, type ServerLogEntry, ServerLogLevel, type ToolAnswer, ToolArgumentsError, type ToolCall, type ToolCallOutcome, type ToolCallReport, type ToolCallRequest, type ToolCallResult, } from './wire/tool-calls.ts';
|
|
43
43
|
export { FinishReason, FUNCTION_TOOL, JSON_SCHEMA_FORMAT, PartType, Role, SchemaType } from './wire/wire.ts';
|
package/dist/index.js
CHANGED
|
@@ -38,5 +38,5 @@ export { backoffMs, ContextOverflow, EndpointSilent, isModelLoading, isOverflow,
|
|
|
38
38
|
export { CacheBreakReason, streamTurn, } from './wire/stream.js';
|
|
39
39
|
export { ALL_FENCES, DEFAULT_FENCES, FenceSplitter, SPLIT_OUTPUT, SPLIT_REASONING, stripThinking, THINK_FENCE, } from './wire/thinking.js';
|
|
40
40
|
export { compact, contextChars, contextTokens, estimateTokens, messageTokens, requestChars, requestTokens, } from './wire/tokens.js';
|
|
41
|
-
export { ARGUMENTS_MALFORMED, ARGUMENTS_TRUNCATED, parseToolArguments, recoverToolCalls, ToolArgumentsError, } from './wire/tool-calls.js';
|
|
41
|
+
export { ARGUMENTS_MALFORMED, ARGUMENTS_TRUNCATED, parseToolArguments, recoverToolCalls, ServerLogLevel, ToolArgumentsError, } from './wire/tool-calls.js';
|
|
42
42
|
export { FinishReason, FUNCTION_TOOL, JSON_SCHEMA_FORMAT, PartType, Role, SchemaType } from './wire/wire.js';
|
package/dist/run/agent-loop.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { type KeywordPreselectOptions } from '../tools/preselect.ts';
|
|
|
10
10
|
import { type ToolOrder } from '../tools/tool-loading.ts';
|
|
11
11
|
import { type SideTask } from '../turn/side-task.ts';
|
|
12
12
|
import { type Turn, type TurnUsage } from '../wire/stream.ts';
|
|
13
|
-
import { type RunArtifact, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
13
|
+
import { type EndedBy, type RunArtifact, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
14
14
|
import { type RunEventInput, type RunMetrics } from './events.ts';
|
|
15
15
|
import { type ToolDispatch } from './run-calls.ts';
|
|
16
16
|
/**
|
|
@@ -243,7 +243,8 @@ export interface AgentLoopOptions {
|
|
|
243
243
|
* @remarks
|
|
244
244
|
* A `ToolAnswer` in place of the string adds what the call produced: its `text` is the whole of
|
|
245
245
|
* what goes into the transcript, and its `links` become the result's `artifacts`, an `artifact`
|
|
246
|
-
* event the first time each uri is seen, and `links` on what `onToolResult` is handed.
|
|
246
|
+
* event the first time each uri is seen, and `links` on what `onToolResult` is handed. Its `end`
|
|
247
|
+
* ends the run once the step's calls are answered; see `AgentLoopResult.ended`.
|
|
247
248
|
*/
|
|
248
249
|
dispatch: ToolDispatch;
|
|
249
250
|
/**
|
|
@@ -395,8 +396,29 @@ export interface AgentLoopOptions {
|
|
|
395
396
|
}
|
|
396
397
|
/** What a finished loop hands back. */
|
|
397
398
|
export interface AgentLoopResult {
|
|
398
|
-
/**
|
|
399
|
+
/**
|
|
400
|
+
* The last turn, as `onTurn` was handed it: the one that asked for no tools, or — where a call
|
|
401
|
+
* `ended` the run — the one that made that call.
|
|
402
|
+
*
|
|
403
|
+
* @remarks
|
|
404
|
+
* Ended by a call, its `content` is what the model wrote beside its calls, which is often
|
|
405
|
+
* nothing, and its `toolCalls` are not empty. The run's answer is then whatever the ending tool
|
|
406
|
+
* took — the question it asked, the work it handed off — and not this text.
|
|
407
|
+
*/
|
|
399
408
|
turn: Turn;
|
|
409
|
+
/**
|
|
410
|
+
* The call whose answer said `end`, where one did: the first in the order the model asked, once
|
|
411
|
+
* every call of its step was answered. Absent where the run ended on a turn that asked for no
|
|
412
|
+
* tools.
|
|
413
|
+
*
|
|
414
|
+
* @remarks
|
|
415
|
+
* Every other field is as a finished run has it: `messages` close on the step's tool results
|
|
416
|
+
* with no assistant turn after them, the hooks are told `afterTurn` with `turn.content`, and
|
|
417
|
+
* `metrics.outcome` is `ended`. No `beforeStep` or `compact` runs after the end, since no request
|
|
418
|
+
* follows it. A host saying `done` passes the call on it — `id` the call, `name` the tool, `text`
|
|
419
|
+
* the reason — which is how `runMetrics` reads the same outcome off the events.
|
|
420
|
+
*/
|
|
421
|
+
ended?: EndedBy;
|
|
400
422
|
/** The transcript, with every assistant turn and tool result the run added. No system prompt. */
|
|
401
423
|
messages: OpenAI.ChatCompletionMessageParam[];
|
|
402
424
|
/** Summed over every turn of the run. */
|
|
@@ -463,14 +485,16 @@ export interface AgentLoopResult {
|
|
|
463
485
|
}
|
|
464
486
|
/**
|
|
465
487
|
* Runs a question to its answer: one `runTurn` per step, the tools it asks for between them,
|
|
466
|
-
* until a turn asks for none. Throws when `maxToolIterations` is
|
|
467
|
-
* whatever `runTurn` throws — `ContextOverflow` among them, however it
|
|
488
|
+
* until a turn asks for none or a call's answer ends the run. Throws when `maxToolIterations` is
|
|
489
|
+
* spent, when stopped, and on whatever `runTurn` throws — `ContextOverflow` among them, however it
|
|
490
|
+
* was found out.
|
|
468
491
|
*
|
|
469
492
|
* @param options - The config, transcript, tools and dispatcher, plus the optional hooks, events
|
|
470
493
|
* and cancellation. See `AgentLoopOptions`.
|
|
471
|
-
* @returns The turn that asked for no tools
|
|
472
|
-
*
|
|
473
|
-
* and the ledger. See
|
|
494
|
+
* @returns The turn that asked for no tools — or made the call that ended the run, with `ended`
|
|
495
|
+
* naming it — and the run around it: the transcript with what the run added, usage, every call,
|
|
496
|
+
* what is loaded and used, the hooks' notes and context, metrics and the ledger. See
|
|
497
|
+
* `AgentLoopResult`.
|
|
474
498
|
*
|
|
475
499
|
* @remarks
|
|
476
500
|
* What it throws carries the run as it stood — `messages`, `usage`, `toolCalls`, `loaded`, `used`
|
|
@@ -482,6 +506,11 @@ export interface AgentLoopResult {
|
|
|
482
506
|
* well-formed: the results that came back are kept, and each call without one is answered with a
|
|
483
507
|
* line saying it was stopped or never run, since a call with no result is refused on replay.
|
|
484
508
|
*
|
|
509
|
+
* A tool that finishes the job — a hand-off, a question for a person — ends the run with a
|
|
510
|
+
* `ToolAnswer` whose `end` is set, rather than by aborting it: the step's calls are all run and
|
|
511
|
+
* answered, no further request is sent, and the loop resolves with `ended`. An end in the last step
|
|
512
|
+
* `maxToolIterations` allows resolves too, since nothing was left undone.
|
|
513
|
+
*
|
|
485
514
|
* On-demand loading is handled here, `load_tools` and all: the catalogue rides on the system
|
|
486
515
|
* prompt unchanged from step to step, a load adds to the tool array — which every request sends
|
|
487
516
|
* in the stable order `toolOrder` asks for — and
|
package/dist/run/agent-loop.js
CHANGED
|
@@ -63,14 +63,16 @@ function cacheDiagnosis(previous, next, usage) {
|
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Runs a question to its answer: one `runTurn` per step, the tools it asks for between them,
|
|
66
|
-
* until a turn asks for none. Throws when `maxToolIterations` is
|
|
67
|
-
* whatever `runTurn` throws — `ContextOverflow` among them, however it
|
|
66
|
+
* until a turn asks for none or a call's answer ends the run. Throws when `maxToolIterations` is
|
|
67
|
+
* spent, when stopped, and on whatever `runTurn` throws — `ContextOverflow` among them, however it
|
|
68
|
+
* was found out.
|
|
68
69
|
*
|
|
69
70
|
* @param options - The config, transcript, tools and dispatcher, plus the optional hooks, events
|
|
70
71
|
* and cancellation. See `AgentLoopOptions`.
|
|
71
|
-
* @returns The turn that asked for no tools
|
|
72
|
-
*
|
|
73
|
-
* and the ledger. See
|
|
72
|
+
* @returns The turn that asked for no tools — or made the call that ended the run, with `ended`
|
|
73
|
+
* naming it — and the run around it: the transcript with what the run added, usage, every call,
|
|
74
|
+
* what is loaded and used, the hooks' notes and context, metrics and the ledger. See
|
|
75
|
+
* `AgentLoopResult`.
|
|
74
76
|
*
|
|
75
77
|
* @remarks
|
|
76
78
|
* What it throws carries the run as it stood — `messages`, `usage`, `toolCalls`, `loaded`, `used`
|
|
@@ -82,6 +84,11 @@ function cacheDiagnosis(previous, next, usage) {
|
|
|
82
84
|
* well-formed: the results that came back are kept, and each call without one is answered with a
|
|
83
85
|
* line saying it was stopped or never run, since a call with no result is refused on replay.
|
|
84
86
|
*
|
|
87
|
+
* A tool that finishes the job — a hand-off, a question for a person — ends the run with a
|
|
88
|
+
* `ToolAnswer` whose `end` is set, rather than by aborting it: the step's calls are all run and
|
|
89
|
+
* answered, no further request is sent, and the loop resolves with `ended`. An end in the last step
|
|
90
|
+
* `maxToolIterations` allows resolves too, since nothing was left undone.
|
|
91
|
+
*
|
|
85
92
|
* On-demand loading is handled here, `load_tools` and all: the catalogue rides on the system
|
|
86
93
|
* prompt unchanged from step to step, a load adds to the tool array — which every request sends
|
|
87
94
|
* in the stable order `toolOrder` asks for — and
|
|
@@ -471,8 +478,8 @@ function turnCalls(turn, recoverable, notice) {
|
|
|
471
478
|
* @param standing - Written to: once the steps have set themselves up, its `read` is theirs, so the
|
|
472
479
|
* caller can read the run as it stands whether they return or throw.
|
|
473
480
|
* @param [pin] - What the run's session adds to each request. Absent adds nothing.
|
|
474
|
-
* @returns The result of the turn that asked for no tools
|
|
475
|
-
* is spent.
|
|
481
|
+
* @returns The result of the turn that asked for no tools or whose calls ended the run, or
|
|
482
|
+
* `undefined` when `maxToolIterations` is spent.
|
|
476
483
|
*/
|
|
477
484
|
async function runSteps(options, standing, pin) {
|
|
478
485
|
const { config, system = '', tools = [], catalog = [], dispatch, hooks, signal } = options;
|
|
@@ -706,13 +713,27 @@ async function runSteps(options, standing, pin) {
|
|
|
706
713
|
const assistant = assistantMessage(content, parsed);
|
|
707
714
|
messages.push(assistant);
|
|
708
715
|
await announce(assistant, step, shown);
|
|
709
|
-
|
|
716
|
+
const isAnswer = calls.length === 0;
|
|
717
|
+
const ended = isAnswer ? undefined : await runCalls(calling, parsed, messages, step);
|
|
718
|
+
if (ended) {
|
|
719
|
+
const by = ended.reason ? `: ${ended.reason}` : '';
|
|
720
|
+
notice(`${ended.name} ended the run${by}`);
|
|
721
|
+
}
|
|
722
|
+
if (isAnswer || ended) {
|
|
710
723
|
// Not awaited: the answer is ready, and remembering it is not something to hold it for.
|
|
711
724
|
// Handed back instead, for a host that wants its notes.
|
|
712
725
|
const afterTurn = hooks ? tellAfterTurn(hooks, messages, questionAt(), turn.content) : Promise.resolve([]);
|
|
713
726
|
const metrics = runMetrics(recorded, { contextLength: config.contextLength });
|
|
727
|
+
let outcome = RunOutcome.Answered;
|
|
728
|
+
if (ended) {
|
|
729
|
+
outcome = RunOutcome.Ended;
|
|
730
|
+
}
|
|
731
|
+
else if (turn.finishReason === FinishReason.Length) {
|
|
732
|
+
outcome = RunOutcome.Truncated;
|
|
733
|
+
}
|
|
714
734
|
return {
|
|
715
735
|
turn: shown,
|
|
736
|
+
...(ended ? { ended } : {}),
|
|
716
737
|
messages,
|
|
717
738
|
usage,
|
|
718
739
|
toolCalls,
|
|
@@ -728,12 +749,11 @@ async function runSteps(options, standing, pin) {
|
|
|
728
749
|
...metrics,
|
|
729
750
|
...(onDemand ? loads : {}),
|
|
730
751
|
wallMs: Date.now() - started,
|
|
731
|
-
outcome
|
|
752
|
+
outcome,
|
|
732
753
|
},
|
|
733
754
|
ledger,
|
|
734
755
|
};
|
|
735
756
|
}
|
|
736
|
-
await runCalls(calling, parsed, messages, step);
|
|
737
757
|
}
|
|
738
758
|
return undefined;
|
|
739
759
|
}
|
package/dist/run/events.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TokenLedger } from '../context/ledger.ts';
|
|
2
2
|
import type { TurnUsage } from '../wire/stream.ts';
|
|
3
3
|
import { CacheBreakReason } from '../wire/stream.ts';
|
|
4
|
-
import type
|
|
4
|
+
import { type ResourceLink } from '../wire/tool-calls.ts';
|
|
5
5
|
/**
|
|
6
6
|
* What a run is doing, while it is doing it.
|
|
7
7
|
*
|
|
@@ -96,13 +96,23 @@ export declare const RunEventKind: {
|
|
|
96
96
|
* per uri per run: a later call that links the same uri again only adds to its count.
|
|
97
97
|
*/
|
|
98
98
|
readonly Artifact: 'artifact';
|
|
99
|
+
/**
|
|
100
|
+
* A tool's server logged something while a call was running, which the dispatcher passed on
|
|
101
|
+
* through `report.log`. `id` is the call's, `name` the tool, `level` the server's, and `text`
|
|
102
|
+
* the message's data — as it was where a string, as JSON otherwise — cut by `preview`. Never in
|
|
103
|
+
* the transcript, and never from a call the loop answers itself.
|
|
104
|
+
*/
|
|
105
|
+
readonly ServerLog: 'server-log';
|
|
99
106
|
/**
|
|
100
107
|
* What the run has cost so far, at the end of every turn, and what that one turn did. Sent
|
|
101
108
|
* whether or not the endpoint reported tokens, since the timings and the cache comparison are
|
|
102
109
|
* measured either way.
|
|
103
110
|
*/
|
|
104
111
|
readonly Usage: 'usage';
|
|
105
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* The run ended. Always last, and always sent. A run a tool call ended carries that call: `id`
|
|
114
|
+
* the call's, `name` the tool, and `text` the reason, as `AgentLoopResult.ended` has them.
|
|
115
|
+
*/
|
|
106
116
|
readonly Done: 'done';
|
|
107
117
|
};
|
|
108
118
|
/** Any one of the kinds in `RunEventKind`. */
|
|
@@ -164,7 +174,7 @@ export interface RunEvent {
|
|
|
164
174
|
kind: RunEventKind;
|
|
165
175
|
/** The delta, the arguments, the result, or the reason — whatever the kind carries. */
|
|
166
176
|
text: string;
|
|
167
|
-
/** Tool name on the tool kinds, otherwise empty. */
|
|
177
|
+
/** Tool name on the tool kinds, and on a `done` the tool that ended the run; otherwise empty. */
|
|
168
178
|
name: string;
|
|
169
179
|
/**
|
|
170
180
|
* The call's id on the tool kinds, which pairs a `tool-result` with its `tool-call` when one
|
|
@@ -187,6 +197,11 @@ export interface RunEvent {
|
|
|
187
197
|
* an event built before it existed still compiles.
|
|
188
198
|
*/
|
|
189
199
|
link?: ResourceLink;
|
|
200
|
+
/**
|
|
201
|
+
* The level the server logged at, on `server-log` and on no other kind. Optional for the reason
|
|
202
|
+
* `id` is.
|
|
203
|
+
*/
|
|
204
|
+
level?: string;
|
|
190
205
|
}
|
|
191
206
|
/**
|
|
192
207
|
* What `emit` is given: the run and the sequence are the bus's to assign.
|
|
@@ -314,6 +329,10 @@ export interface RunMetrics {
|
|
|
314
329
|
loadCalls: number;
|
|
315
330
|
/** `artifact` events: the distinct things the run's tool calls linked to. */
|
|
316
331
|
artifacts: number;
|
|
332
|
+
/** `server-log` events at `warning`. */
|
|
333
|
+
serverWarnings: number;
|
|
334
|
+
/** `server-log` events at `error` or above: `critical`, `alert` and `emergency` with it. */
|
|
335
|
+
serverErrors: number;
|
|
317
336
|
/**
|
|
318
337
|
* Tools `load_tools` loaded that were not loaded already. Filled by `runAgentLoop`, which sees
|
|
319
338
|
* the resolution; the events carry only its text.
|
|
@@ -374,6 +393,11 @@ export declare const RunOutcome: {
|
|
|
374
393
|
readonly Answered: 'answered';
|
|
375
394
|
/** The run did not fail, and its last turn is an answer the reply ceiling cut off. */
|
|
376
395
|
readonly Truncated: 'truncated';
|
|
396
|
+
/**
|
|
397
|
+
* The run did not fail, and a tool call ended it: `AgentLoopResult.ended` says which, and so
|
|
398
|
+
* does a `done` that names the tool.
|
|
399
|
+
*/
|
|
400
|
+
readonly Ended: 'ended';
|
|
377
401
|
/**
|
|
378
402
|
* `done` said the run was not ok. Whether that was an error, a stop or the tool budget is in
|
|
379
403
|
* the host's own `done` text and not here.
|
package/dist/run/events.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ABORT_EVENT, MS_PER_MINUTE } from '../core/platform.js';
|
|
|
3
3
|
import { assignSettings, scoped } from '../core/scope.js';
|
|
4
4
|
import { LOAD_TOOLS } from '../tools/tool-loading.js';
|
|
5
5
|
import { CacheBreakReason } from '../wire/stream.js';
|
|
6
|
+
import { ServerLogLevel } from '../wire/tool-calls.js';
|
|
6
7
|
import { FinishReason } from '../wire/wire.js';
|
|
7
8
|
/** How many minutes an unended run's events are kept, absent a `retainUnendedMs`. */
|
|
8
9
|
const RETAIN_UNENDED_MINUTES = 30;
|
|
@@ -62,13 +63,23 @@ export const RunEventKind = {
|
|
|
62
63
|
* per uri per run: a later call that links the same uri again only adds to its count.
|
|
63
64
|
*/
|
|
64
65
|
Artifact: 'artifact',
|
|
66
|
+
/**
|
|
67
|
+
* A tool's server logged something while a call was running, which the dispatcher passed on
|
|
68
|
+
* through `report.log`. `id` is the call's, `name` the tool, `level` the server's, and `text`
|
|
69
|
+
* the message's data — as it was where a string, as JSON otherwise — cut by `preview`. Never in
|
|
70
|
+
* the transcript, and never from a call the loop answers itself.
|
|
71
|
+
*/
|
|
72
|
+
ServerLog: 'server-log',
|
|
65
73
|
/**
|
|
66
74
|
* What the run has cost so far, at the end of every turn, and what that one turn did. Sent
|
|
67
75
|
* whether or not the endpoint reported tokens, since the timings and the cache comparison are
|
|
68
76
|
* measured either way.
|
|
69
77
|
*/
|
|
70
78
|
Usage: 'usage',
|
|
71
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* The run ended. Always last, and always sent. A run a tool call ended carries that call: `id`
|
|
81
|
+
* the call's, `name` the tool, and `text` the reason, as `AgentLoopResult.ended` has them.
|
|
82
|
+
*/
|
|
72
83
|
Done: 'done',
|
|
73
84
|
};
|
|
74
85
|
/**
|
|
@@ -453,12 +464,21 @@ export function fold(events) {
|
|
|
453
464
|
close();
|
|
454
465
|
return blocks;
|
|
455
466
|
}
|
|
467
|
+
/** The log levels quietest first, which is what makes "warning and above" a comparison. */
|
|
468
|
+
const SEVERITY = Object.values(ServerLogLevel);
|
|
469
|
+
/** Where `warning` sits in `SEVERITY`: a level past it is an error. */
|
|
470
|
+
const WARNING = SEVERITY.indexOf(ServerLogLevel.Warning);
|
|
456
471
|
/** How a run ended, as `runMetrics` reads it. */
|
|
457
472
|
export const RunOutcome = {
|
|
458
473
|
/** The run did not fail, and its last turn was not cut off. */
|
|
459
474
|
Answered: 'answered',
|
|
460
475
|
/** The run did not fail, and its last turn is an answer the reply ceiling cut off. */
|
|
461
476
|
Truncated: 'truncated',
|
|
477
|
+
/**
|
|
478
|
+
* The run did not fail, and a tool call ended it: `AgentLoopResult.ended` says which, and so
|
|
479
|
+
* does a `done` that names the tool.
|
|
480
|
+
*/
|
|
481
|
+
Ended: 'ended',
|
|
462
482
|
/**
|
|
463
483
|
* `done` said the run was not ok. Whether that was an error, a stop or the tool budget is in
|
|
464
484
|
* the host's own `done` text and not here.
|
|
@@ -468,15 +488,21 @@ export const RunOutcome = {
|
|
|
468
488
|
/**
|
|
469
489
|
* How a run that reported `done` ended.
|
|
470
490
|
*
|
|
471
|
-
* @param
|
|
491
|
+
* @param done - The `done` event. Only an `ok` of `false` is a failure; one that names a tool was
|
|
492
|
+
* ended by it; anything else is read off the turn.
|
|
472
493
|
* @param last - The last turn a `usage` event reported, if any did.
|
|
473
|
-
* @returns `failed` for a `done` that was not ok, `
|
|
474
|
-
* reply ceiling, and `answered` otherwise.
|
|
494
|
+
* @returns `failed` for a `done` that was not ok, `ended` for one that names the tool that ended
|
|
495
|
+
* the run, `truncated` where the last turn ran into the reply ceiling, and `answered` otherwise.
|
|
475
496
|
*/
|
|
476
|
-
function outcomeOf(ok, last) {
|
|
497
|
+
function outcomeOf({ ok, name }, last) {
|
|
477
498
|
if (ok === false) {
|
|
478
499
|
return RunOutcome.Failed;
|
|
479
500
|
}
|
|
501
|
+
// Read as a truthy string, so an event put together by hand without one is not ended.
|
|
502
|
+
const isEnded = Boolean(name);
|
|
503
|
+
if (isEnded) {
|
|
504
|
+
return RunOutcome.Ended;
|
|
505
|
+
}
|
|
480
506
|
if (last?.finishReason === FinishReason.Length) {
|
|
481
507
|
return RunOutcome.Truncated;
|
|
482
508
|
}
|
|
@@ -507,6 +533,8 @@ export function runMetrics(events, { contextLength } = {}) {
|
|
|
507
533
|
toolErrors: {},
|
|
508
534
|
loadCalls: 0,
|
|
509
535
|
artifacts: 0,
|
|
536
|
+
serverWarnings: 0,
|
|
537
|
+
serverErrors: 0,
|
|
510
538
|
promptTokens: 0,
|
|
511
539
|
completionTokens: 0,
|
|
512
540
|
cachedTokens: 0,
|
|
@@ -550,6 +578,15 @@ export function runMetrics(events, { contextLength } = {}) {
|
|
|
550
578
|
else if (event.kind === RunEventKind.Artifact) {
|
|
551
579
|
metrics.artifacts++;
|
|
552
580
|
}
|
|
581
|
+
else if (event.kind === RunEventKind.ServerLog) {
|
|
582
|
+
const severity = SEVERITY.indexOf(event.level ?? '');
|
|
583
|
+
if (severity === WARNING) {
|
|
584
|
+
metrics.serverWarnings++;
|
|
585
|
+
}
|
|
586
|
+
else if (severity > WARNING) {
|
|
587
|
+
metrics.serverErrors++;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
553
590
|
else if (event.kind === RunEventKind.Usage && event.usage?.turn) {
|
|
554
591
|
const turn = event.usage.turn;
|
|
555
592
|
last = turn;
|
|
@@ -591,7 +628,7 @@ export function runMetrics(events, { contextLength } = {}) {
|
|
|
591
628
|
}
|
|
592
629
|
}
|
|
593
630
|
else if (event.kind === RunEventKind.Done) {
|
|
594
|
-
metrics.outcome = outcomeOf(event
|
|
631
|
+
metrics.outcome = outcomeOf(event, last);
|
|
595
632
|
}
|
|
596
633
|
}
|
|
597
634
|
if (events.length > 1) {
|
package/dist/run/run-calls.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type OpenAI from 'openai';
|
|
2
2
|
import type { CatalogServer } from '../tools/catalog.ts';
|
|
3
3
|
import type { Turn } from '../wire/stream.ts';
|
|
4
|
-
import { type RunArtifact, type ToolAnswer, type ToolCall, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
4
|
+
import { type EndedBy, type RunArtifact, type ToolAnswer, type ToolCall, type ToolCallOutcome, type ToolCallReport, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
5
5
|
import type { RunEventInput } from './events.ts';
|
|
6
6
|
/**
|
|
7
7
|
* The tools between two turns: a step's calls read, run and written into the transcript.
|
|
@@ -66,8 +66,13 @@ export declare const definedAs: (definitions: ReadonlyMap<string, OpenAI.ChatCom
|
|
|
66
66
|
/**
|
|
67
67
|
* Runs one tool call and resolves to what the model reads, alone or with what the call produced;
|
|
68
68
|
* what it throws, the model reads too.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* `report` is how the call tells watchers what its server said on the way, and a dispatcher with
|
|
72
|
+
* nothing to report leaves it off: one written for `(call, signal)` is a `ToolDispatch` still.
|
|
73
|
+
* `signal` is `undefined` rather than optional so that `report` can follow it.
|
|
69
74
|
*/
|
|
70
|
-
export type ToolDispatch = (call: ToolCallRequest, signal
|
|
75
|
+
export type ToolDispatch = (call: ToolCallRequest, signal: AbortSignal | undefined, report: ToolCallReport) => Promise<string | ToolAnswer>;
|
|
71
76
|
/**
|
|
72
77
|
* What running a step's calls reads of the run and writes back into it. One per run, shared by
|
|
73
78
|
* every step: the sets and counts here are the run's own, not copies.
|
|
@@ -109,13 +114,21 @@ export interface Calling {
|
|
|
109
114
|
* @param parsed - The step's calls, with their arguments read.
|
|
110
115
|
* @param messages - The transcript the results are written into.
|
|
111
116
|
* @param step - Which step this is, for the host told of each result.
|
|
117
|
+
* @returns The first call, in the order asked, whose answer ended the run, or `undefined` where
|
|
118
|
+
* none did.
|
|
112
119
|
*
|
|
113
120
|
* @remarks
|
|
114
121
|
* A failure part-way leaves the transcript well-formed before it is rethrown: a call with no
|
|
115
122
|
* result is a transcript no endpoint takes back, so every call of the step is answered — with what
|
|
116
123
|
* it returned where it had, and otherwise with a line saying it was stopped, or never run.
|
|
124
|
+
*
|
|
125
|
+
* An answer that ends the run does not end the step. The calls after it still run, in whatever
|
|
126
|
+
* batch `parallel` puts them, because the model asked for them in the same breath and how a host
|
|
127
|
+
* schedules calls is not what they mean: a reply that says "write the file, then hand off" in one
|
|
128
|
+
* turn wants the file written whichever order it named the two in, and a call batched beside the
|
|
129
|
+
* end was running already. A tool that must have nothing after it says so in its description.
|
|
117
130
|
*/
|
|
118
|
-
export declare function runCalls(run: Calling, parsed: ReadCall[], messages: OpenAI.ChatCompletionMessageParam[], step: number): Promise<
|
|
131
|
+
export declare function runCalls(run: Calling, parsed: ReadCall[], messages: OpenAI.ChatCompletionMessageParam[], step: number): Promise<EndedBy | undefined>;
|
|
119
132
|
/**
|
|
120
133
|
* Writes a proxied preselection into the transcript as a `load_tools` exchange: the tool array is
|
|
121
134
|
* fixed, so a shortlist has nowhere else to go, and it is answered as though the model had loaded
|
package/dist/run/run-calls.js
CHANGED
|
@@ -84,13 +84,63 @@ export const assistantMessage = (content, parsed) => ({
|
|
|
84
84
|
* @returns A new array of the definitions themselves, not copies.
|
|
85
85
|
*/
|
|
86
86
|
export const definedAs = (definitions, names) => [...names].flatMap((name) => definitions.get(name) ?? []);
|
|
87
|
+
/**
|
|
88
|
+
* A server's log message as the text of its event.
|
|
89
|
+
*
|
|
90
|
+
* @param data - What the server sent: a string, or any JSON value.
|
|
91
|
+
* @returns A string as it was, anything else as JSON, cut by `preview`. What JSON cannot spell is
|
|
92
|
+
* shown as `String` shows it.
|
|
93
|
+
*/
|
|
94
|
+
function logText(data) {
|
|
95
|
+
if (typeof data === 'string') {
|
|
96
|
+
return preview(data);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return preview(JSON.stringify(data) ?? String(data));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return preview(String(data));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* What one call's dispatcher may report through, and the switch that closes it once it has answered.
|
|
107
|
+
*
|
|
108
|
+
* @param run - The run, whose watchers hear what is reported.
|
|
109
|
+
* @param id - The call's id, which every event it reports carries.
|
|
110
|
+
* @param name - The tool the call is shown as.
|
|
111
|
+
* @returns The report to hand `dispatch`, and `close`, after which it reports nothing.
|
|
112
|
+
*/
|
|
113
|
+
function reportFor(run, id, name) {
|
|
114
|
+
let isOpen = true;
|
|
115
|
+
const log = ({ level, data }) => {
|
|
116
|
+
if (isOpen) {
|
|
117
|
+
run.onEvent({ kind: RunEventKind.ServerLog, id, name, level, text: logText(data) });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
report: { log },
|
|
122
|
+
close: () => {
|
|
123
|
+
isOpen = false;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
87
127
|
/**
|
|
88
128
|
* A dispatcher's answer in the one shape, whichever it gave.
|
|
89
129
|
*
|
|
90
130
|
* @param answer - What `dispatch` resolved to.
|
|
91
|
-
* @returns The text and the
|
|
131
|
+
* @returns The text, the links and whether it ends the run, a string answer having no links and
|
|
132
|
+
* ending nothing.
|
|
92
133
|
*/
|
|
93
|
-
|
|
134
|
+
function answerOf(answer) {
|
|
135
|
+
if (typeof answer === 'string') {
|
|
136
|
+
return { text: answer, links: [], end: false };
|
|
137
|
+
}
|
|
138
|
+
const { text, links = [], end = false } = answer;
|
|
139
|
+
if (typeof end === 'object') {
|
|
140
|
+
return { text, links, end: { reason: end.reason } };
|
|
141
|
+
}
|
|
142
|
+
return { text, links, end: end ? {} : false };
|
|
143
|
+
}
|
|
94
144
|
/**
|
|
95
145
|
* What a call is shown, counted and dispatched as: a `call_tool` as the tool it names.
|
|
96
146
|
*
|
|
@@ -282,9 +332,9 @@ function batchEnd(run, ready, from, total, answered) {
|
|
|
282
332
|
* @param prepared - The call, with its request built.
|
|
283
333
|
* @param answered - The step's calls already made, by tool and arguments, for `dedupeToolCalls`.
|
|
284
334
|
* @param step - Which step this is, for the artifacts the call's links become.
|
|
285
|
-
* @returns The call's id, the name it was shown under, whether it worked, the whole
|
|
286
|
-
* model reads, and the links its answer carried, where it carried any
|
|
287
|
-
* stopped or a host callback threw.
|
|
335
|
+
* @returns The result — the call's id, the name it was shown under, whether it worked, the whole
|
|
336
|
+
* of what the model reads, and the links its answer carried, where it carried any — and whether
|
|
337
|
+
* the answer ended the run. Rejects where the run was stopped or a host callback threw.
|
|
288
338
|
*/
|
|
289
339
|
async function runCall(run, prepared, answered, step) {
|
|
290
340
|
const { catalog, onDemand, proxied, signal } = run;
|
|
@@ -293,8 +343,10 @@ async function runCall(run, prepared, answered, step) {
|
|
|
293
343
|
const { name, raw } = request;
|
|
294
344
|
run.onEvent({ kind: RunEventKind.ToolCall, id: call.id, name, text: preview(raw) });
|
|
295
345
|
run.onToolCall?.(request);
|
|
346
|
+
const { report, close } = reportFor(run, call.id, name);
|
|
296
347
|
let content;
|
|
297
348
|
let links = [];
|
|
349
|
+
let end = false;
|
|
298
350
|
let ok = true;
|
|
299
351
|
// Answered from an earlier identical call of the step, whose links are already counted.
|
|
300
352
|
let shared = false;
|
|
@@ -318,10 +370,12 @@ async function runCall(run, prepared, answered, step) {
|
|
|
318
370
|
run.used.add(name);
|
|
319
371
|
// Read before `once` looks, which it does before it awaits anything.
|
|
320
372
|
shared = dedupable && answered.has(key);
|
|
373
|
+
// A repeat answered from the first call reports nothing of its own: the first call's
|
|
374
|
+
// report is the one its dispatcher holds.
|
|
321
375
|
const answer = dedupable
|
|
322
|
-
? await once(answered, key, () => run.dispatch(request, signal))
|
|
323
|
-
: await run.dispatch(request, signal);
|
|
324
|
-
({ text: content, links } = answerOf(answer));
|
|
376
|
+
? await once(answered, key, () => run.dispatch(request, signal, report))
|
|
377
|
+
: await run.dispatch(request, signal, report);
|
|
378
|
+
({ text: content, links, end } = answerOf(answer));
|
|
325
379
|
}
|
|
326
380
|
}
|
|
327
381
|
catch (error) {
|
|
@@ -331,6 +385,9 @@ async function runCall(run, prepared, answered, step) {
|
|
|
331
385
|
content = errorMessage(error);
|
|
332
386
|
ok = false;
|
|
333
387
|
}
|
|
388
|
+
finally {
|
|
389
|
+
close();
|
|
390
|
+
}
|
|
334
391
|
run.onEvent({ kind: RunEventKind.ToolResult, id: call.id, name, ok, text: preview(content) });
|
|
335
392
|
const isFirstAnswer = shared === false;
|
|
336
393
|
if (isFirstAnswer) {
|
|
@@ -338,7 +395,7 @@ async function runCall(run, prepared, answered, step) {
|
|
|
338
395
|
}
|
|
339
396
|
const result = { id: call.id, name, ok, content, ...(links.length ? { links } : {}) };
|
|
340
397
|
run.onToolResult?.(result);
|
|
341
|
-
return result;
|
|
398
|
+
return { result, end };
|
|
342
399
|
}
|
|
343
400
|
/**
|
|
344
401
|
* Runs a step's calls and writes each result into the transcript, in the order asked.
|
|
@@ -347,11 +404,19 @@ async function runCall(run, prepared, answered, step) {
|
|
|
347
404
|
* @param parsed - The step's calls, with their arguments read.
|
|
348
405
|
* @param messages - The transcript the results are written into.
|
|
349
406
|
* @param step - Which step this is, for the host told of each result.
|
|
407
|
+
* @returns The first call, in the order asked, whose answer ended the run, or `undefined` where
|
|
408
|
+
* none did.
|
|
350
409
|
*
|
|
351
410
|
* @remarks
|
|
352
411
|
* A failure part-way leaves the transcript well-formed before it is rethrown: a call with no
|
|
353
412
|
* result is a transcript no endpoint takes back, so every call of the step is answered — with what
|
|
354
413
|
* it returned where it had, and otherwise with a line saying it was stopped, or never run.
|
|
414
|
+
*
|
|
415
|
+
* An answer that ends the run does not end the step. The calls after it still run, in whatever
|
|
416
|
+
* batch `parallel` puts them, because the model asked for them in the same breath and how a host
|
|
417
|
+
* schedules calls is not what they mean: a reply that says "write the file, then hand off" in one
|
|
418
|
+
* turn wants the file written whichever order it named the two in, and a call batched beside the
|
|
419
|
+
* end was running already. A tool that must have nothing after it says so in its description.
|
|
355
420
|
*/
|
|
356
421
|
export async function runCalls(run, parsed, messages, step) {
|
|
357
422
|
const { signal, toolCalls } = run;
|
|
@@ -360,6 +425,7 @@ export async function runCalls(run, parsed, messages, step) {
|
|
|
360
425
|
const answered = new Map();
|
|
361
426
|
// By position, so a call that came back while another was being stopped is still found.
|
|
362
427
|
const outcomes = parsed.map(() => undefined);
|
|
428
|
+
const ends = parsed.map(() => false);
|
|
363
429
|
// How many of the step's calls were handed off, and how many have their result in the
|
|
364
430
|
// transcript. Both count from the front, since results are written in the order asked.
|
|
365
431
|
let begun = 0;
|
|
@@ -390,7 +456,9 @@ export async function runCalls(run, parsed, messages, step) {
|
|
|
390
456
|
const to = batchEnd(run, ready, start, parsed.length, answered);
|
|
391
457
|
begun = to;
|
|
392
458
|
await Promise.all(parsed.slice(start, to).map(async (_, offset) => {
|
|
393
|
-
|
|
459
|
+
const { result, end } = await runCall(run, ready(start + offset), answered, step);
|
|
460
|
+
outcomes[start + offset] = result;
|
|
461
|
+
ends[start + offset] = end;
|
|
394
462
|
}));
|
|
395
463
|
for (const { id, name, ok, content } of outcomes.slice(from, to).flatMap((outcome) => outcome ?? [])) {
|
|
396
464
|
toolCalls.push({ id, name, ok });
|
|
@@ -398,6 +466,15 @@ export async function runCalls(run, parsed, messages, step) {
|
|
|
398
466
|
}
|
|
399
467
|
from = to;
|
|
400
468
|
}
|
|
469
|
+
// The first by position rather than by finishing: a batch's calls end in no order worth
|
|
470
|
+
// reporting, and a deduplicated repeat shares its answer's `end` with the call before it.
|
|
471
|
+
const at = ends.findIndex((end) => end !== false);
|
|
472
|
+
const end = ends[at];
|
|
473
|
+
const outcome = outcomes[at];
|
|
474
|
+
if (end === undefined || end === false || outcome === undefined) {
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
return { callId: outcome.id, name: outcome.name, ...(end.reason === undefined ? {} : { reason: end.reason }) };
|
|
401
478
|
}
|
|
402
479
|
catch (error) {
|
|
403
480
|
// Only a call that was made counts in `toolCalls`.
|
|
@@ -188,6 +188,80 @@ export interface ToolAnswer {
|
|
|
188
188
|
text: string;
|
|
189
189
|
/** What the call produced, in the order the tool gave them. Never sent to the model. */
|
|
190
190
|
links?: ResourceLink[];
|
|
191
|
+
/**
|
|
192
|
+
* Ends the run once this step's calls are answered: no further request is sent, and the loop
|
|
193
|
+
* resolves with `ended` rather than throwing. An object says why, for the host and its `done`.
|
|
194
|
+
*
|
|
195
|
+
* @remarks
|
|
196
|
+
* The step's other calls still run and are answered, so the transcript stays one an endpoint
|
|
197
|
+
* will take back. For a tool whose call is the end of the job — handing off, asking a person —
|
|
198
|
+
* where the alternative is an abort, which the loop reads as a failure.
|
|
199
|
+
*/
|
|
200
|
+
end?: boolean | {
|
|
201
|
+
reason: string;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** The call that ended a run through its answer's `end`, as `AgentLoopResult.ended` says it. */
|
|
205
|
+
export interface EndedBy {
|
|
206
|
+
/** The call's id. */
|
|
207
|
+
callId: string;
|
|
208
|
+
/** The tool, under the name it was announced by: for a `call_tool`, the tool it names. */
|
|
209
|
+
name: string;
|
|
210
|
+
/** Why, where the answer said: absent for `end: true`. */
|
|
211
|
+
reason?: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The levels a server's log message is sent at, quietest first: MCP's, which are syslog's.
|
|
215
|
+
*
|
|
216
|
+
* @remarks
|
|
217
|
+
* `runMetrics` reads `warning` and the four above it as a server complaining. A level outside
|
|
218
|
+
* this list is still shown, and is counted as neither.
|
|
219
|
+
*/
|
|
220
|
+
export declare const ServerLogLevel: {
|
|
221
|
+
readonly Debug: 'debug';
|
|
222
|
+
readonly Info: 'info';
|
|
223
|
+
readonly Notice: 'notice';
|
|
224
|
+
readonly Warning: 'warning';
|
|
225
|
+
readonly Error: 'error';
|
|
226
|
+
readonly Critical: 'critical';
|
|
227
|
+
readonly Alert: 'alert';
|
|
228
|
+
readonly Emergency: 'emergency';
|
|
229
|
+
};
|
|
230
|
+
/** Any one of the levels in `ServerLogLevel`. */
|
|
231
|
+
export type ServerLogLevel = (typeof ServerLogLevel)[keyof typeof ServerLogLevel];
|
|
232
|
+
/**
|
|
233
|
+
* One log message a tool's server sent while the call was running, as `ToolCallReport.log` is
|
|
234
|
+
* handed it.
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* The params of MCP's `notifications/message`, declared structurally rather than imported, as
|
|
238
|
+
* `ResourceLink` is: agent-mcp-pool's `ServerLogEntry` has these field names, so its `onLog`
|
|
239
|
+
* takes `report.log` as it stands. `level` is a plain string so a server's own word is shown
|
|
240
|
+
* rather than refused.
|
|
241
|
+
*/
|
|
242
|
+
export interface ServerLogEntry {
|
|
243
|
+
/** How serious the server says it is: one of `ServerLogLevel`, by the spec. */
|
|
244
|
+
level: string;
|
|
245
|
+
/** The server's name for the part of itself that logged, where it gave one. */
|
|
246
|
+
logger?: string;
|
|
247
|
+
/** Whatever the server sent: a string usually, any JSON value by the spec. */
|
|
248
|
+
data: unknown;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* What a dispatcher may tell the run about the call it is making, besides its answer.
|
|
252
|
+
*
|
|
253
|
+
* @remarks
|
|
254
|
+
* An object rather than a fourth and fifth argument, so what a call can report grows here —
|
|
255
|
+
* progress is the next one — without `dispatch` changing again. Every member is a bound function,
|
|
256
|
+
* so it can be handed on alone: `onLog: report.log`. What is reported reaches the run's events
|
|
257
|
+
* and never the transcript: it is for the person watching, not the model.
|
|
258
|
+
*/
|
|
259
|
+
export interface ToolCallReport {
|
|
260
|
+
/**
|
|
261
|
+
* Tells watchers of a server's log message, as a `server-log` event for this call. Heard after
|
|
262
|
+
* the call has answered, it is dropped: the event would land after the result it explains.
|
|
263
|
+
*/
|
|
264
|
+
log: (entry: ServerLogEntry) => void;
|
|
191
265
|
}
|
|
192
266
|
/**
|
|
193
267
|
* One thing a run's tool calls produced: the first link to a uri, and where it came from.
|
package/dist/wire/tool-calls.js
CHANGED
|
@@ -472,3 +472,20 @@ export function recoverToolCalls(content, { names = [] } = {}) {
|
|
|
472
472
|
rest += tail.slice(cursor);
|
|
473
473
|
return { content: `${content.slice(0, from)}${rest}`.trim(), toolCalls };
|
|
474
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* The levels a server's log message is sent at, quietest first: MCP's, which are syslog's.
|
|
477
|
+
*
|
|
478
|
+
* @remarks
|
|
479
|
+
* `runMetrics` reads `warning` and the four above it as a server complaining. A level outside
|
|
480
|
+
* this list is still shown, and is counted as neither.
|
|
481
|
+
*/
|
|
482
|
+
export const ServerLogLevel = {
|
|
483
|
+
Debug: 'debug',
|
|
484
|
+
Info: 'info',
|
|
485
|
+
Notice: 'notice',
|
|
486
|
+
Warning: 'warning',
|
|
487
|
+
Error: 'error',
|
|
488
|
+
Critical: 'critical',
|
|
489
|
+
Alert: 'alert',
|
|
490
|
+
Emergency: 'emergency',
|
|
491
|
+
};
|
package/llms.txt
CHANGED
|
@@ -163,7 +163,7 @@ The loop above a turn: send, run the tools the model asked for, send again, unti
|
|
|
163
163
|
- `PRESELECT_APPEND` — The `preselectRouting` that loads the shortlist after what was carried, and sends the first step like any other.
|
|
164
164
|
- `PRESELECT_EXCLUSIVE` — The `preselectRouting` that sends the first step the shortlist alone.
|
|
165
165
|
- `PRESELECTED_NOTICE` — What opens the notice naming the loop's own picks, ahead of the names.
|
|
166
|
-
- `runAgentLoop` — Runs a question to its answer: one `runTurn` per step, the tools it asks for between them, until a turn asks for none.
|
|
166
|
+
- `runAgentLoop` — Runs a question to its answer: one `runTurn` per step, the tools it asks for between them, until a turn asks for none or a call's answer ends the run.
|
|
167
167
|
- `StepWindow` (type) — How full the window is as a step is about to be sent, as `beforeStep` is told it.
|
|
168
168
|
|
|
169
169
|
### events
|
|
@@ -402,14 +402,18 @@ Reading what a model meant by a tool call when it did not write one cleanly.
|
|
|
402
402
|
|
|
403
403
|
- `ARGUMENTS_MALFORMED` — The `ToolArgumentsError` kind for a call the model wrote wrongly.
|
|
404
404
|
- `ARGUMENTS_TRUNCATED` — The `ToolArgumentsError` kind for a call cut off at the reply ceiling.
|
|
405
|
+
- `EndedBy` (type) — The call that ended a run through its answer's `end`, as `AgentLoopResult.ended` says it.
|
|
405
406
|
- `parseToolArguments` — A tool call's arguments as the object the tool is handed.
|
|
406
407
|
- `ResourceLink` (type) — A handle to something a tool call made or found — a document it wrote, a file it saved.
|
|
407
408
|
- `RunArtifact` (type) — One thing a run's tool calls produced: the first link to a uri, and where it came from.
|
|
408
409
|
- `recoverToolCalls` — Tool calls a model wrote into its reply as text, taken out of it and made into calls.
|
|
410
|
+
- `ServerLogEntry` (type) — One log message a tool's server sent while the call was running, as `ToolCallReport.log` is handed it.
|
|
411
|
+
- `ServerLogLevel` — Any one of the levels in `ServerLogLevel`.
|
|
409
412
|
- `ToolAnswer` (type) — A dispatcher's answer when it has more to say than the text: what the model reads, and what the call produced for the host.
|
|
410
413
|
- `ToolArgumentsError` — Tool arguments that could not be read as an object, with why.
|
|
411
414
|
- `ToolCall` (type) — A tool call as the loop handles it, recovered or streamed.
|
|
412
415
|
- `ToolCallOutcome` (type) — What one tool call did, in the order the model asked.
|
|
416
|
+
- `ToolCallReport` (type) — What a dispatcher may tell the run about the call it is making, besides its answer.
|
|
413
417
|
- `ToolCallRequest` (type) — One call the model made, as `dispatch` and `onToolCall` are handed it.
|
|
414
418
|
- `ToolCallResult` (type) — One call's whole answer, as `onToolResult` is handed it.
|
|
415
419
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubicecho/agent-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"description": "The endpoint-agnostic half of an OpenAI-compatible agent loop: tool-schema compatibility, on-demand tool loading, one-shot side tasks, run events, and a pooled client.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openai",
|