@cubicecho/agent-core 4.5.0 → 4.6.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 +28 -0
- package/dist/index.d.ts +2 -2
- package/dist/run/agent-loop.d.ts +15 -2
- package/dist/run/agent-loop.js +5 -0
- package/dist/run/events.d.ts +16 -2
- package/dist/run/events.js +10 -0
- package/dist/run/run-calls.d.ts +8 -3
- package/dist/run/run-calls.js +52 -7
- package/dist/wire/errors.d.ts +9 -1
- package/dist/wire/errors.js +4 -0
- package/dist/wire/tool-calls.d.ts +64 -0
- package/llms.txt +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -481,6 +481,34 @@ await runAgentLoop({
|
|
|
481
481
|
});
|
|
482
482
|
```
|
|
483
483
|
|
|
484
|
+
### What the calls made
|
|
485
|
+
|
|
486
|
+
A tool that wrote a document or saved a file can say so, and `dispatch` can pass that on: it
|
|
487
|
+
resolves to a `ToolAnswer`, `{ text, links }`, in place of the string. `text` is the whole of
|
|
488
|
+
what goes into the transcript, so the request, the prompt cache, pruning and compaction never see
|
|
489
|
+
the links. `links` are `ResourceLink`s, MCP's own shape declared structurally, so what
|
|
490
|
+
agent-mcp-pool's `call` answers with `links: true` is a dispatcher's answer as it stands.
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
const { artifacts } = await runAgentLoop({
|
|
494
|
+
// ...
|
|
495
|
+
dispatch: ({ name, args }, signal) => pool.call(name, args, { signal, links: true }),
|
|
496
|
+
});
|
|
497
|
+
// [{ link: { uri, name, title?, ... }, callId, tool, step, count }]
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The loop collects them into the run's `artifacts`: one per uri in the order first seen, each with
|
|
501
|
+
the call, tool and step that first linked to it and the number of calls that did. A link is what
|
|
502
|
+
one call answered with; an artifact is what the run made. `onToolResult` hears a call's `links`
|
|
503
|
+
beside its `content`, an `artifact` event goes out the first time each uri appears — `name` the
|
|
504
|
+
tool, `text` the title or else the uri, and `link` the link — and `runMetrics` counts those events
|
|
505
|
+
as `artifacts`. A deduplicated repeat shares the first call's answer and links but adds nothing to
|
|
506
|
+
the count, since nothing was made twice. A call that threw made nothing. A failed run carries what
|
|
507
|
+
its calls made on the error too, read off with `failedRun`.
|
|
508
|
+
|
|
509
|
+
A link meant for the model alone — one whose MCP `audience` leaves out `"user"` — is the
|
|
510
|
+
dispatcher's to drop, since that is MCP's rule and this package knows nothing of MCP.
|
|
511
|
+
|
|
484
512
|
Arguments go through `parseToolArguments`, which is lenient where the model's meaning is plain:
|
|
485
513
|
JSON held in a string is opened, and the almost-JSON local models write — single quotes, Python's
|
|
486
514
|
`True` and `None`, bare keys, a trailing comma — is repaired, without touching what is inside a
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { assembleContext, configureHooks, consult, type Gathered, gather, HOOK_C
|
|
|
21
21
|
export { type AgentLoopHooks, type AgentLoopOptions, type AgentLoopRequest, type AgentLoopResult, PRESELECT_APPEND, PRESELECT_EXCLUSIVE, PRESELECTED_NOTICE, runAgentLoop, type StepWindow, } from './run/agent-loop.ts';
|
|
22
22
|
export { configureEvents, type EventBusOptions, emit, endRun, fold, history, type RunEvent, type RunEventInput, RunEventKind, type RunMetrics, type RunMetricsOptions, RunOutcome, type RunUsage, resetEvents, runMetrics, type TurnReport, watch, } from './run/events.ts';
|
|
23
23
|
export { buildBody } from './run/request-body.ts';
|
|
24
|
-
export { preview } from './run/run-calls.ts';
|
|
24
|
+
export { preview, type ToolDispatch } from './run/run-calls.ts';
|
|
25
25
|
export { resetAll } from './runtime/reset.ts';
|
|
26
26
|
export { createRuntime, defaultRuntime, type Runtime, type RuntimeOptions } from './runtime/runtime.ts';
|
|
27
27
|
export { CAPABILITY_SNAPSHOT_VERSION, type CapabilitySnapshot, type EndpointSnapshot, exportCapabilities, importCapabilities, type ModelSnapshot, } from './runtime/snapshot.ts';
|
|
@@ -37,5 +37,5 @@ export { backoffMs, ContextOverflow, EndpointSilent, isModelLoading, isOverflow,
|
|
|
37
37
|
export { CacheBreakReason, type Produced, type StreamTurnOptions, streamTurn, type Turn, type TurnUsage, } from './wire/stream.ts';
|
|
38
38
|
export { ALL_FENCES, DEFAULT_FENCES, type Fence, FenceSplitter, type FenceSplitterOptions, SPLIT_OUTPUT, SPLIT_REASONING, type Split, stripThinking, THINK_FENCE, } from './wire/thinking.ts';
|
|
39
39
|
export { type ContextBreakdown, type ContextBreakdownOptions, compact, contextChars, contextTokens, estimateTokens, messageTokens, requestChars, requestTokens, type TokenEstimateOptions, } from './wire/tokens.ts';
|
|
40
|
-
export { ARGUMENTS_MALFORMED, ARGUMENTS_TRUNCATED, parseToolArguments, recoverToolCalls, ToolArgumentsError, type ToolCall, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult, } from './wire/tool-calls.ts';
|
|
40
|
+
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';
|
|
41
41
|
export { FinishReason, FUNCTION_TOOL, JSON_SCHEMA_FORMAT, PartType, Role, SchemaType } from './wire/wire.ts';
|
package/dist/run/agent-loop.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type KeywordPreselectOptions } from '../tools/preselect.ts';
|
|
|
9
9
|
import { type ToolOrder } from '../tools/tool-loading.ts';
|
|
10
10
|
import { type SideTask } from '../turn/side-task.ts';
|
|
11
11
|
import { type Turn, type TurnUsage } from '../wire/stream.ts';
|
|
12
|
-
import { type ToolCallOutcome, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
12
|
+
import { type RunArtifact, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
13
13
|
import { type RunEventInput, type RunMetrics } from './events.ts';
|
|
14
14
|
import { type ToolDispatch } from './run-calls.ts';
|
|
15
15
|
/**
|
|
@@ -220,7 +220,14 @@ export interface AgentLoopOptions {
|
|
|
220
220
|
* mode, where a tool is loaded only while its definition is in the history.
|
|
221
221
|
*/
|
|
222
222
|
loaded?: Iterable<string>;
|
|
223
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* Runs one tool call and returns what the model reads. What it throws, the model reads too.
|
|
225
|
+
*
|
|
226
|
+
* @remarks
|
|
227
|
+
* A `ToolAnswer` in place of the string adds what the call produced: its `text` is the whole of
|
|
228
|
+
* what goes into the transcript, and its `links` become the result's `artifacts`, an `artifact`
|
|
229
|
+
* event the first time each uri is seen, and `links` on what `onToolResult` is handed.
|
|
230
|
+
*/
|
|
224
231
|
dispatch: ToolDispatch;
|
|
225
232
|
/**
|
|
226
233
|
* Runs a step's calls together rather than one after another. Results still go into the
|
|
@@ -354,6 +361,12 @@ export interface AgentLoopResult {
|
|
|
354
361
|
* proxied preselection's among them, first.
|
|
355
362
|
*/
|
|
356
363
|
toolCalls: ToolCallOutcome[];
|
|
364
|
+
/**
|
|
365
|
+
* What the run's calls linked to, one per uri in the order first seen, each with the call and
|
|
366
|
+
* step that first linked to it and how many calls did. Empty where `dispatch` answered with
|
|
367
|
+
* strings only.
|
|
368
|
+
*/
|
|
369
|
+
artifacts: RunArtifact[];
|
|
357
370
|
/**
|
|
358
371
|
* What is loaded at the end, for `carryOver`. Empty in eager mode, and in proxied mode: a
|
|
359
372
|
* definition loaded there is already in the history, and one a compaction folded away has to be
|
package/dist/run/agent-loop.js
CHANGED
|
@@ -116,6 +116,7 @@ export async function runAgentLoop(options) {
|
|
|
116
116
|
messages: [...options.messages],
|
|
117
117
|
usage: noUsage(),
|
|
118
118
|
toolCalls: [],
|
|
119
|
+
artifacts: [],
|
|
119
120
|
loaded: [],
|
|
120
121
|
used: [],
|
|
121
122
|
preselected: [],
|
|
@@ -450,10 +451,12 @@ async function runSteps(options, standing) {
|
|
|
450
451
|
let messages = [...options.messages];
|
|
451
452
|
let usage = noUsage();
|
|
452
453
|
const toolCalls = [];
|
|
454
|
+
const artifacts = new Map();
|
|
453
455
|
standing.read = () => ({
|
|
454
456
|
messages,
|
|
455
457
|
usage,
|
|
456
458
|
toolCalls,
|
|
459
|
+
artifacts: [...artifacts.values()],
|
|
457
460
|
// As the result would have said it: proxied, nothing is loaded that the history does not hold.
|
|
458
461
|
loaded: proxied ? [] : [...loaded],
|
|
459
462
|
used: [...used],
|
|
@@ -498,6 +501,7 @@ async function runSteps(options, standing) {
|
|
|
498
501
|
loads,
|
|
499
502
|
used,
|
|
500
503
|
toolCalls,
|
|
504
|
+
artifacts,
|
|
501
505
|
dedupable,
|
|
502
506
|
dispatch,
|
|
503
507
|
parallel,
|
|
@@ -645,6 +649,7 @@ async function runSteps(options, standing) {
|
|
|
645
649
|
messages,
|
|
646
650
|
usage,
|
|
647
651
|
toolCalls,
|
|
652
|
+
artifacts: [...artifacts.values()],
|
|
648
653
|
loaded: proxied ? [] : [...loaded],
|
|
649
654
|
used: [...used],
|
|
650
655
|
preselected: [...preselected],
|
package/dist/run/events.d.ts
CHANGED
|
@@ -1,6 +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 { ResourceLink } from '../wire/tool-calls.ts';
|
|
4
5
|
/**
|
|
5
6
|
* What a run is doing, while it is doing it.
|
|
6
7
|
*
|
|
@@ -89,6 +90,12 @@ export declare const RunEventKind: {
|
|
|
89
90
|
readonly ToolResult: 'tool-result';
|
|
90
91
|
/** Something the runner did that is not the model's doing — a preselection, a retry. */
|
|
91
92
|
readonly Notice: 'notice';
|
|
93
|
+
/**
|
|
94
|
+
* A tool call's answer linked to something the run had not produced before. `id` is the call's,
|
|
95
|
+
* `name` the tool, `text` the link's title or else its uri, and `link` the link itself. Once
|
|
96
|
+
* per uri per run: a later call that links the same uri again only adds to its count.
|
|
97
|
+
*/
|
|
98
|
+
readonly Artifact: 'artifact';
|
|
92
99
|
/**
|
|
93
100
|
* What the run has cost so far, at the end of every turn, and what that one turn did. Sent
|
|
94
101
|
* whether or not the endpoint reported tokens, since the timings and the cache comparison are
|
|
@@ -136,8 +143,8 @@ export interface TurnReport extends TurnUsage {
|
|
|
136
143
|
* One thing that happened in a run, as a watcher receives it.
|
|
137
144
|
*
|
|
138
145
|
* @remarks
|
|
139
|
-
* Every field but `id` is always present — the empty ones are `""` or `null` rather
|
|
140
|
-
* missing — so a client reads it without guarding each key.
|
|
146
|
+
* Every field but `id` and `link` is always present — the empty ones are `""` or `null` rather
|
|
147
|
+
* than missing — so a client reads it without guarding each key.
|
|
141
148
|
*/
|
|
142
149
|
export interface RunEvent {
|
|
143
150
|
/** The run it belongs to. `emit` fills this in; a caller does not pass it. */
|
|
@@ -175,6 +182,11 @@ export interface RunEvent {
|
|
|
175
182
|
ok: boolean | null;
|
|
176
183
|
/** Running totals on `usage`, otherwise null. */
|
|
177
184
|
usage: RunUsage | null;
|
|
185
|
+
/**
|
|
186
|
+
* What the call produced, on `artifact` and on no other kind. Optional for the reason `id` is:
|
|
187
|
+
* an event built before it existed still compiles.
|
|
188
|
+
*/
|
|
189
|
+
link?: ResourceLink;
|
|
178
190
|
}
|
|
179
191
|
/**
|
|
180
192
|
* What `emit` is given: the run and the sequence are the bus's to assign.
|
|
@@ -300,6 +312,8 @@ export interface RunMetrics {
|
|
|
300
312
|
toolErrors: Record<string, number>;
|
|
301
313
|
/** `load_tools` calls. */
|
|
302
314
|
loadCalls: number;
|
|
315
|
+
/** `artifact` events: the distinct things the run's tool calls linked to. */
|
|
316
|
+
artifacts: number;
|
|
303
317
|
/**
|
|
304
318
|
* Tools `load_tools` loaded that were not loaded already. Filled by `runAgentLoop`, which sees
|
|
305
319
|
* the resolution; the events carry only its text.
|
package/dist/run/events.js
CHANGED
|
@@ -56,6 +56,12 @@ export const RunEventKind = {
|
|
|
56
56
|
ToolResult: 'tool-result',
|
|
57
57
|
/** Something the runner did that is not the model's doing — a preselection, a retry. */
|
|
58
58
|
Notice: 'notice',
|
|
59
|
+
/**
|
|
60
|
+
* A tool call's answer linked to something the run had not produced before. `id` is the call's,
|
|
61
|
+
* `name` the tool, `text` the link's title or else its uri, and `link` the link itself. Once
|
|
62
|
+
* per uri per run: a later call that links the same uri again only adds to its count.
|
|
63
|
+
*/
|
|
64
|
+
Artifact: 'artifact',
|
|
59
65
|
/**
|
|
60
66
|
* What the run has cost so far, at the end of every turn, and what that one turn did. Sent
|
|
61
67
|
* whether or not the endpoint reported tokens, since the timings and the cache comparison are
|
|
@@ -500,6 +506,7 @@ export function runMetrics(events, { contextLength } = {}) {
|
|
|
500
506
|
toolCalls: 0,
|
|
501
507
|
toolErrors: {},
|
|
502
508
|
loadCalls: 0,
|
|
509
|
+
artifacts: 0,
|
|
503
510
|
promptTokens: 0,
|
|
504
511
|
completionTokens: 0,
|
|
505
512
|
cachedTokens: 0,
|
|
@@ -540,6 +547,9 @@ export function runMetrics(events, { contextLength } = {}) {
|
|
|
540
547
|
add('toolMs', event.at - called);
|
|
541
548
|
}
|
|
542
549
|
}
|
|
550
|
+
else if (event.kind === RunEventKind.Artifact) {
|
|
551
|
+
metrics.artifacts++;
|
|
552
|
+
}
|
|
543
553
|
else if (event.kind === RunEventKind.Usage && event.usage?.turn) {
|
|
544
554
|
const turn = event.usage.turn;
|
|
545
555
|
last = turn;
|
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 ToolCall, type ToolCallOutcome, type ToolCallRequest, type ToolCallResult } from '../wire/tool-calls.ts';
|
|
4
|
+
import { type RunArtifact, type ToolAnswer, type ToolCall, type ToolCallOutcome, 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.
|
|
@@ -63,8 +63,11 @@ export declare const assistantMessage: (content: string, parsed: ReadCall[]) =>
|
|
|
63
63
|
* @returns A new array of the definitions themselves, not copies.
|
|
64
64
|
*/
|
|
65
65
|
export declare const definedAs: (definitions: ReadonlyMap<string, OpenAI.ChatCompletionTool>, names: Iterable<string>) => (OpenAI.ChatCompletionCustomTool | OpenAI.ChatCompletionFunctionTool)[];
|
|
66
|
-
/**
|
|
67
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Runs one tool call and resolves to what the model reads, alone or with what the call produced;
|
|
68
|
+
* what it throws, the model reads too.
|
|
69
|
+
*/
|
|
70
|
+
export type ToolDispatch = (call: ToolCallRequest, signal?: AbortSignal) => Promise<string | ToolAnswer>;
|
|
68
71
|
/**
|
|
69
72
|
* What running a step's calls reads of the run and writes back into it. One per run, shared by
|
|
70
73
|
* every step: the sets and counts here are the run's own, not copies.
|
|
@@ -86,6 +89,8 @@ export interface Calling {
|
|
|
86
89
|
};
|
|
87
90
|
used: Set<string>;
|
|
88
91
|
toolCalls: ToolCallOutcome[];
|
|
92
|
+
/** What the run's calls linked to, by uri, in the order first seen. */
|
|
93
|
+
artifacts: Map<string, RunArtifact>;
|
|
89
94
|
dedupable: (call: ToolCallRequest) => boolean;
|
|
90
95
|
dispatch: ToolDispatch;
|
|
91
96
|
parallel: boolean;
|
package/dist/run/run-calls.js
CHANGED
|
@@ -84,6 +84,13 @@ 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 dispatcher's answer in the one shape, whichever it gave.
|
|
89
|
+
*
|
|
90
|
+
* @param answer - What `dispatch` resolved to.
|
|
91
|
+
* @returns The text and the links, a string answer's being none.
|
|
92
|
+
*/
|
|
93
|
+
const answerOf = (answer) => typeof answer === 'string' ? { text: answer, links: [] } : { text: answer.text, links: answer.links ?? [] };
|
|
87
94
|
/**
|
|
88
95
|
* What a call is shown, counted and dispatched as: a `call_tool` as the tool it names.
|
|
89
96
|
*
|
|
@@ -129,6 +136,7 @@ function answerLoad(run, args) {
|
|
|
129
136
|
* Makes a call at most once per key, sharing the in-flight promise so two identical calls in one
|
|
130
137
|
* step make one request between them whether they run together or one after the other.
|
|
131
138
|
*
|
|
139
|
+
* @typeParam T - What a call answers with.
|
|
132
140
|
* @param answered - The step's calls so far, by key. Written to.
|
|
133
141
|
* @param key - What makes two calls the same one.
|
|
134
142
|
* @param make - Makes the call. Not called where the key has an answer, or one on its way.
|
|
@@ -156,6 +164,30 @@ async function once(answered, key, make) {
|
|
|
156
164
|
throw error;
|
|
157
165
|
}
|
|
158
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Adds one call's links to the run's artifacts, telling watchers of each uri the first time.
|
|
169
|
+
*
|
|
170
|
+
* @param run - The run. Written to: a new uri is added to `artifacts`, and a known one counted.
|
|
171
|
+
* @param links - What the call's answer linked to.
|
|
172
|
+
* @param from - The call they came from.
|
|
173
|
+
*/
|
|
174
|
+
function collect(run, links, from) {
|
|
175
|
+
// Per call, so a tool that lists one uri twice in an answer is counted as making it once.
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
for (const link of links) {
|
|
178
|
+
if (seen.has(link.uri)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
seen.add(link.uri);
|
|
182
|
+
const known = run.artifacts.get(link.uri);
|
|
183
|
+
if (known) {
|
|
184
|
+
known.count++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
run.artifacts.set(link.uri, { link, ...from, count: 1 });
|
|
188
|
+
run.onEvent({ kind: RunEventKind.Artifact, id: from.callId, name: from.tool, text: link.title || link.uri, link });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
159
191
|
/**
|
|
160
192
|
* Runs one call to its result, telling the host of both. A tool that throws is that call's answer
|
|
161
193
|
* rather than the run's end, unless the run was stopped.
|
|
@@ -163,10 +195,12 @@ async function once(answered, key, make) {
|
|
|
163
195
|
* @param run - The run the call belongs to.
|
|
164
196
|
* @param entry - The call, with its arguments read.
|
|
165
197
|
* @param answered - The step's calls already made, by tool and arguments, for `dedupeToolCalls`.
|
|
166
|
-
* @
|
|
167
|
-
*
|
|
198
|
+
* @param step - Which step this is, for the artifacts the call's links become.
|
|
199
|
+
* @returns The call's id, the name it was shown under, whether it worked, the whole of what the
|
|
200
|
+
* model reads, and the links its answer carried, where it carried any. Rejects where the run was
|
|
201
|
+
* stopped or a host callback threw.
|
|
168
202
|
*/
|
|
169
|
-
async function runCall(run, entry, answered) {
|
|
203
|
+
async function runCall(run, entry, answered, step) {
|
|
170
204
|
const { catalog, onDemand, proxied, signal } = run;
|
|
171
205
|
const { call, args, error: unreadable, normal } = entry;
|
|
172
206
|
const proxy = run.proxies && call.function.name === CALL_TOOL;
|
|
@@ -191,7 +225,10 @@ async function runCall(run, entry, answered) {
|
|
|
191
225
|
run.onEvent({ kind: RunEventKind.ToolCall, id: call.id, name, text: preview(raw) });
|
|
192
226
|
run.onToolCall?.(request);
|
|
193
227
|
let content;
|
|
228
|
+
let links = [];
|
|
194
229
|
let ok = true;
|
|
230
|
+
// Answered from an earlier identical call of the step, whose links are already counted.
|
|
231
|
+
let shared = false;
|
|
195
232
|
try {
|
|
196
233
|
if (!args) {
|
|
197
234
|
throw unreadable;
|
|
@@ -214,9 +251,13 @@ async function runCall(run, entry, answered) {
|
|
|
214
251
|
run.used.add(name);
|
|
215
252
|
// Keyed on the inner call, so one made through `call_tool` and one made natively share.
|
|
216
253
|
const key = `${name}\0${proxy ? JSON.stringify(inner) : normal}`;
|
|
217
|
-
|
|
254
|
+
const dedupable = run.dedupable(request);
|
|
255
|
+
// Read before `once` looks, which it does before it awaits anything.
|
|
256
|
+
shared = dedupable && answered.has(key);
|
|
257
|
+
const answer = dedupable
|
|
218
258
|
? await once(answered, key, () => run.dispatch(request, signal))
|
|
219
259
|
: await run.dispatch(request, signal);
|
|
260
|
+
({ text: content, links } = answerOf(answer));
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
catch (error) {
|
|
@@ -227,7 +268,11 @@ async function runCall(run, entry, answered) {
|
|
|
227
268
|
ok = false;
|
|
228
269
|
}
|
|
229
270
|
run.onEvent({ kind: RunEventKind.ToolResult, id: call.id, name, ok, text: preview(content) });
|
|
230
|
-
const
|
|
271
|
+
const isFirstAnswer = shared === false;
|
|
272
|
+
if (isFirstAnswer) {
|
|
273
|
+
collect(run, links, { callId: call.id, tool: name, step });
|
|
274
|
+
}
|
|
275
|
+
const result = { id: call.id, name, ok, content, ...(links.length ? { links } : {}) };
|
|
231
276
|
run.onToolResult?.(result);
|
|
232
277
|
return result;
|
|
233
278
|
}
|
|
@@ -270,7 +315,7 @@ export async function runCalls(run, parsed, messages, step) {
|
|
|
270
315
|
signal?.throwIfAborted();
|
|
271
316
|
begun = parsed.length;
|
|
272
317
|
await Promise.all(parsed.map(async (call, at) => {
|
|
273
|
-
outcomes[at] = await runCall(run, call, answered);
|
|
318
|
+
outcomes[at] = await runCall(run, call, answered, step);
|
|
274
319
|
}));
|
|
275
320
|
for (const { id, name, ok, content } of outcomes.flatMap((outcome) => outcome ?? [])) {
|
|
276
321
|
toolCalls.push({ id, name, ok });
|
|
@@ -281,7 +326,7 @@ export async function runCalls(run, parsed, messages, step) {
|
|
|
281
326
|
for (const [at, call] of parsed.entries()) {
|
|
282
327
|
signal?.throwIfAborted();
|
|
283
328
|
begun = at + 1;
|
|
284
|
-
const { id, name, ok, content } = await runCall(run, call, answered);
|
|
329
|
+
const { id, name, ok, content } = await runCall(run, call, answered, step);
|
|
285
330
|
toolCalls.push({ id, name, ok });
|
|
286
331
|
await keep(id, content);
|
|
287
332
|
}
|
package/dist/wire/errors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type OpenAI from 'openai';
|
|
2
2
|
import { ContextOverflow } from './retry.ts';
|
|
3
3
|
import type { TurnUsage } from './stream.ts';
|
|
4
|
-
import type { ToolCallOutcome } from './tool-calls.ts';
|
|
4
|
+
import type { RunArtifact, ToolCallOutcome } from './tool-calls.ts';
|
|
5
5
|
/**
|
|
6
6
|
* What went wrong, as a string.
|
|
7
7
|
*
|
|
@@ -34,6 +34,12 @@ export interface AgentLoopFailure {
|
|
|
34
34
|
usage: TurnUsage;
|
|
35
35
|
/** Every call that was made, in order. One the run was stopped during is `ok: false`. */
|
|
36
36
|
toolCalls: ToolCallOutcome[];
|
|
37
|
+
/**
|
|
38
|
+
* What the calls that came back linked to, as the result's `artifacts` would have said it: a
|
|
39
|
+
* failed run's writes were still made. `runAgentLoop` always sets it; optional so a failure
|
|
40
|
+
* built before it existed still compiles.
|
|
41
|
+
*/
|
|
42
|
+
artifacts?: RunArtifact[];
|
|
37
43
|
/** What was loaded when the run failed, for `carryOver`. Empty in eager and proxied modes. */
|
|
38
44
|
loaded: string[];
|
|
39
45
|
/** The tools the model had called, `load_tools` excluded. */
|
|
@@ -55,6 +61,7 @@ export declare class AgentLoopError extends Error implements AgentLoopFailure {
|
|
|
55
61
|
readonly messages: OpenAI.ChatCompletionMessageParam[];
|
|
56
62
|
readonly usage: TurnUsage;
|
|
57
63
|
readonly toolCalls: ToolCallOutcome[];
|
|
64
|
+
readonly artifacts: RunArtifact[];
|
|
58
65
|
readonly loaded: string[];
|
|
59
66
|
readonly used: string[];
|
|
60
67
|
readonly preselected: string[];
|
|
@@ -88,6 +95,7 @@ export declare class AgentLoopOverflow extends ContextOverflow implements AgentL
|
|
|
88
95
|
readonly messages: OpenAI.ChatCompletionMessageParam[];
|
|
89
96
|
readonly usage: TurnUsage;
|
|
90
97
|
readonly toolCalls: ToolCallOutcome[];
|
|
98
|
+
readonly artifacts: RunArtifact[];
|
|
91
99
|
readonly loaded: string[];
|
|
92
100
|
readonly used: string[];
|
|
93
101
|
readonly preselected: string[];
|
package/dist/wire/errors.js
CHANGED
|
@@ -26,6 +26,7 @@ export class AgentLoopError extends Error {
|
|
|
26
26
|
messages;
|
|
27
27
|
usage;
|
|
28
28
|
toolCalls;
|
|
29
|
+
artifacts;
|
|
29
30
|
loaded;
|
|
30
31
|
used;
|
|
31
32
|
preselected;
|
|
@@ -39,6 +40,7 @@ export class AgentLoopError extends Error {
|
|
|
39
40
|
this.messages = run.messages;
|
|
40
41
|
this.usage = run.usage;
|
|
41
42
|
this.toolCalls = run.toolCalls;
|
|
43
|
+
this.artifacts = run.artifacts ?? [];
|
|
42
44
|
this.loaded = run.loaded;
|
|
43
45
|
this.used = run.used;
|
|
44
46
|
this.preselected = run.preselected;
|
|
@@ -67,6 +69,7 @@ export class AgentLoopOverflow extends ContextOverflow {
|
|
|
67
69
|
messages;
|
|
68
70
|
usage;
|
|
69
71
|
toolCalls;
|
|
72
|
+
artifacts;
|
|
70
73
|
loaded;
|
|
71
74
|
used;
|
|
72
75
|
preselected;
|
|
@@ -80,6 +83,7 @@ export class AgentLoopOverflow extends ContextOverflow {
|
|
|
80
83
|
this.messages = run.messages;
|
|
81
84
|
this.usage = run.usage;
|
|
82
85
|
this.toolCalls = run.toolCalls;
|
|
86
|
+
this.artifacts = run.artifacts ?? [];
|
|
83
87
|
this.loaded = run.loaded;
|
|
84
88
|
this.used = run.used;
|
|
85
89
|
this.preselected = run.preselected;
|
|
@@ -146,4 +146,68 @@ export interface ToolCallResult extends ToolCallOutcome {
|
|
|
146
146
|
* `load_tools` answer, or why the arguments could not be read.
|
|
147
147
|
*/
|
|
148
148
|
content: string;
|
|
149
|
+
/**
|
|
150
|
+
* What the dispatcher's answer said the call produced, as it said it. Absent where it said
|
|
151
|
+
* nothing, and always for a call that threw. A deduplicated repeat carries the first call's.
|
|
152
|
+
*/
|
|
153
|
+
links?: ResourceLink[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A handle to something a tool call made or found — a document it wrote, a file it saved.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* MCP's `ResourceLink` less its `type`, declared structurally rather than imported, the way
|
|
160
|
+
* agent-mcp-pool declares `ToolDefinition` against `ChatCompletionTool`: the pool's own
|
|
161
|
+
* `ResourceLink` has these field names exactly, so its answer is assignable here and neither
|
|
162
|
+
* package depends on the other.
|
|
163
|
+
*/
|
|
164
|
+
export interface ResourceLink {
|
|
165
|
+
uri: string;
|
|
166
|
+
name: string;
|
|
167
|
+
title?: string;
|
|
168
|
+
mimeType?: string;
|
|
169
|
+
description?: string;
|
|
170
|
+
/** Who the link is for, how much it matters, and when it last changed — the server's word. */
|
|
171
|
+
annotations?: {
|
|
172
|
+
audience?: ('user' | 'assistant')[];
|
|
173
|
+
priority?: number;
|
|
174
|
+
lastModified?: string;
|
|
175
|
+
};
|
|
176
|
+
_meta?: Record<string, unknown>;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A dispatcher's answer when it has more to say than the text: what the model reads, and what the
|
|
180
|
+
* call produced for the host.
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* A link meant for the model alone — one whose MCP `audience` leaves out `"user"` — is the
|
|
184
|
+
* dispatcher's to drop, since that is MCP's rule and this package knows nothing of MCP.
|
|
185
|
+
*/
|
|
186
|
+
export interface ToolAnswer {
|
|
187
|
+
/** What goes into the transcript, exactly as a string answer does. */
|
|
188
|
+
text: string;
|
|
189
|
+
/** What the call produced, in the order the tool gave them. Never sent to the model. */
|
|
190
|
+
links?: ResourceLink[];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* One thing a run's tool calls produced: the first link to a uri, and where it came from.
|
|
194
|
+
*
|
|
195
|
+
* @remarks
|
|
196
|
+
* A link is what one call answered with; an artifact is what the run made, one per uri however
|
|
197
|
+
* many calls linked to it.
|
|
198
|
+
*/
|
|
199
|
+
export interface RunArtifact {
|
|
200
|
+
/** The link as the first call to give its uri gave it. A later call's says nothing new here. */
|
|
201
|
+
link: ResourceLink;
|
|
202
|
+
/** The call that first linked to it. */
|
|
203
|
+
callId: string;
|
|
204
|
+
/** That call's tool. For a `call_tool`, the tool it named. */
|
|
205
|
+
tool: string;
|
|
206
|
+
/** The loop's step that call was made in, from 0. */
|
|
207
|
+
step: number;
|
|
208
|
+
/**
|
|
209
|
+
* How many calls linked to it. A deduplicated repeat does not count, since nothing was made
|
|
210
|
+
* twice, and a call that links one uri twice counts once.
|
|
211
|
+
*/
|
|
212
|
+
count: number;
|
|
149
213
|
}
|
package/llms.txt
CHANGED
|
@@ -185,6 +185,7 @@ What a run is doing, while it is doing it.
|
|
|
185
185
|
The tools between two turns: a step's calls read, run and written into the transcript.
|
|
186
186
|
|
|
187
187
|
- `preview` — A long tool argument or result cut to what a watcher needs, with the full length said.
|
|
188
|
+
- `ToolDispatch` (type) — Runs one tool call and resolves to what the model reads, alone or with what the call produced; what it throws, the model reads too.
|
|
188
189
|
|
|
189
190
|
### reset
|
|
190
191
|
|
|
@@ -384,7 +385,10 @@ Reading what a model meant by a tool call when it did not write one cleanly.
|
|
|
384
385
|
- `ARGUMENTS_MALFORMED` — The `ToolArgumentsError` kind for a call the model wrote wrongly.
|
|
385
386
|
- `ARGUMENTS_TRUNCATED` — The `ToolArgumentsError` kind for a call cut off at the reply ceiling.
|
|
386
387
|
- `parseToolArguments` — A tool call's arguments as the object the tool is handed.
|
|
388
|
+
- `ResourceLink` (type) — A handle to something a tool call made or found — a document it wrote, a file it saved.
|
|
389
|
+
- `RunArtifact` (type) — One thing a run's tool calls produced: the first link to a uri, and where it came from.
|
|
387
390
|
- `recoverToolCalls` — Tool calls a model wrote into its reply as text, taken out of it and made into calls.
|
|
391
|
+
- `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.
|
|
388
392
|
- `ToolArgumentsError` — Tool arguments that could not be read as an object, with why.
|
|
389
393
|
- `ToolCall` (type) — A tool call as the loop handles it, recovered or streamed.
|
|
390
394
|
- `ToolCallOutcome` (type) — What one tool call did, in the order the model asked.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubicecho/agent-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.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",
|