@datalayer/agent-runtimes 1.3.74 → 1.3.75
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/lib/chat/ChatFloating.d.ts +17 -1
- package/lib/chat/ChatFloating.js +52 -5
- package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
- package/lib/chat/assistant/AssistantContextMenu.js +68 -0
- package/lib/chat/assistant/AssistantStage.d.ts +95 -1
- package/lib/chat/assistant/AssistantStage.js +227 -7
- package/lib/chat/assistant/BalloonParts.d.ts +15 -1
- package/lib/chat/assistant/BalloonParts.js +26 -9
- package/lib/chat/assistant/BalloonVisual.d.ts +10 -2
- package/lib/chat/assistant/BalloonVisual.js +3 -3
- package/lib/chat/assistant/SpeechBalloon.d.ts +47 -1
- package/lib/chat/assistant/SpeechBalloon.js +60 -8
- package/lib/chat/base/ChatBase.js +51 -1
- package/lib/chat/indicators/TypingDots.d.ts +13 -0
- package/lib/chat/indicators/TypingDots.js +21 -0
- package/lib/chat/messages/ChatMessageList.js +1 -15
- package/lib/components/inspector/AgentInspector.d.ts +59 -0
- package/lib/components/inspector/AgentInspector.js +220 -0
- package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
- package/lib/components/inspector/AgentInspectorDialog.js +9 -0
- package/lib/components/inspector/JsonTree.d.ts +24 -0
- package/lib/components/inspector/JsonTree.js +89 -0
- package/lib/components/inspector/a2aInspect.d.ts +56 -0
- package/lib/components/inspector/a2aInspect.js +478 -0
- package/lib/components/inspector/agentInspector.d.ts +179 -0
- package/lib/components/inspector/agentInspector.js +267 -0
- package/lib/components/inspector/chatInspect.d.ts +38 -0
- package/lib/components/inspector/chatInspect.js +65 -0
- package/lib/components/inspector/classify.d.ts +19 -0
- package/lib/components/inspector/classify.js +42 -0
- package/lib/components/inspector/index.d.ts +14 -0
- package/lib/components/inspector/index.js +18 -0
- package/lib/components/teams/A2ATeamGraph.d.ts +42 -1
- package/lib/components/teams/A2ATeamGraph.js +24 -7
- package/lib/components/teams/NotebookPreviewView.js +9 -1
- package/lib/components/teams/TeamNotebook.d.ts +3 -1
- package/lib/components/teams/TeamNotebook.js +3 -3
- package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
- package/lib/components/teams/jupyterThemeVariables.js +441 -0
- package/lib/components/teams/teamConnections.d.ts +9 -1
- package/lib/components/teams/teamConnections.js +20 -0
- package/lib/components/teams/useA2ATeam.d.ts +17 -4
- package/lib/components/teams/useA2ATeam.js +87 -23
- package/lib/examples/AgentA2ATeamExample.d.ts +13 -0
- package/lib/examples/AgentA2ATeamExample.js +61 -13
- package/lib/examples/AssistantExample.js +62 -2
- package/lib/loop/apps/AppRenderer.js +1 -1
- package/lib/loop/core/index.d.ts +17 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/plugins/assistant/index.d.ts +1 -1
- package/lib/loop/plugins/assistant/index.js +8 -3
- package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
- package/lib/types/chat.d.ts +7 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent Inspector's record: what an agent does, as it does it, one entry
|
|
3
|
+
* a thing — an A2A request or event, an MCP tool call, a skill, a frontend or
|
|
4
|
+
* backend tool, a model turn, an approval — each with its time, the agent
|
|
5
|
+
* that did it (`actor`), where it came from (`source`), its kind, its name,
|
|
6
|
+
* its status and, once it ends, its duration, a line that says it, and its
|
|
7
|
+
* payload whole.
|
|
8
|
+
*
|
|
9
|
+
* A call's start and its end are one entry: {@link AgentInspectorSink.start}
|
|
10
|
+
* opens it under a key (a tool call's id), {@link AgentInspectorSink.end}
|
|
11
|
+
* closes it with its result. Anything can push into a sink: the A2A capture
|
|
12
|
+
* (`inspectA2AFetch`), a team (`useA2ATeam`'s `inspector`), a chat
|
|
13
|
+
* (`ChatBase`, under {@link AgentInspectorProvider}). `AgentInspector` draws
|
|
14
|
+
* it.
|
|
15
|
+
*
|
|
16
|
+
* @module components/inspector/agentInspector
|
|
17
|
+
*/
|
|
18
|
+
import { type JSX, type ReactNode } from 'react';
|
|
19
|
+
/** Where an entry comes from. */
|
|
20
|
+
export type AgentInspectorSource = 'a2a' | 'mcp' | 'skill' | 'frontend-tool' | 'backend-tool' | 'model' | 'approval';
|
|
21
|
+
/** Every source, in the order the filters list them. */
|
|
22
|
+
export declare const AGENT_INSPECTOR_SOURCES: readonly AgentInspectorSource[];
|
|
23
|
+
/** A source, in words. */
|
|
24
|
+
export declare const AGENT_INSPECTOR_SOURCE_LABELS: Record<AgentInspectorSource, string>;
|
|
25
|
+
export type AgentInspectorStatus = 'running' | 'done' | 'failed';
|
|
26
|
+
/** What A2A says of an entry: its JSON-RPC method and id, its task and context, its state. */
|
|
27
|
+
export type AgentInspectorA2A = {
|
|
28
|
+
method?: string;
|
|
29
|
+
rpcId?: string | number;
|
|
30
|
+
taskId?: string;
|
|
31
|
+
contextId?: string;
|
|
32
|
+
/** `TASK_STATE_WORKING`, … */
|
|
33
|
+
state?: string;
|
|
34
|
+
/** The tool step a status carries: its call's id, and whether it is its end. */
|
|
35
|
+
step?: {
|
|
36
|
+
toolId?: string;
|
|
37
|
+
name: string;
|
|
38
|
+
ended: boolean;
|
|
39
|
+
};
|
|
40
|
+
/** The artifact an artifact update is a chunk of. */
|
|
41
|
+
artifactId?: string;
|
|
42
|
+
/** How many chunks an artifact entry holds. */
|
|
43
|
+
chunks?: number;
|
|
44
|
+
};
|
|
45
|
+
export type AgentInspectorEntry = {
|
|
46
|
+
id: string;
|
|
47
|
+
/** When it happened, or started: ms since the epoch. */
|
|
48
|
+
at: number;
|
|
49
|
+
/** When it ended, for a call. */
|
|
50
|
+
endedAt?: number;
|
|
51
|
+
/** Which agent did it. */
|
|
52
|
+
actor: string;
|
|
53
|
+
source: AgentInspectorSource;
|
|
54
|
+
/** `request`, `status-update`, `artifact-update`, `task`, `message`, `tool-call`, `turn`, … */
|
|
55
|
+
kind: string;
|
|
56
|
+
/** The tool's, skill's or method's name. */
|
|
57
|
+
name?: string;
|
|
58
|
+
/** Its mark, as `SpecMark` draws it. */
|
|
59
|
+
icon?: string;
|
|
60
|
+
emoji?: string;
|
|
61
|
+
status?: AgentInspectorStatus;
|
|
62
|
+
/** One line that says it. */
|
|
63
|
+
summary: string;
|
|
64
|
+
/** Which way it went, between two agents. */
|
|
65
|
+
direction?: {
|
|
66
|
+
from: string;
|
|
67
|
+
to: string;
|
|
68
|
+
};
|
|
69
|
+
a2a?: AgentInspectorA2A;
|
|
70
|
+
/** What was sent: a message, or a call's arguments. */
|
|
71
|
+
payload?: unknown;
|
|
72
|
+
/** What came back, for a call. */
|
|
73
|
+
result?: unknown;
|
|
74
|
+
error?: string;
|
|
75
|
+
/** How large `payload` and `result` are, as JSON, in bytes. */
|
|
76
|
+
bytes: number;
|
|
77
|
+
/** The key a call's start and end share. */
|
|
78
|
+
key?: string;
|
|
79
|
+
/** Entries this one goes with: a tool call's A2A statuses, … */
|
|
80
|
+
links: string[];
|
|
81
|
+
/** What a caller made of it: the semantic events attached (`A2APeerEvent`, …). */
|
|
82
|
+
events: unknown[];
|
|
83
|
+
};
|
|
84
|
+
/** What is pushed: an entry, less what the sink works out. */
|
|
85
|
+
export type AgentInspectorInput = Omit<AgentInspectorEntry, 'id' | 'at' | 'bytes' | 'links' | 'events'> & {
|
|
86
|
+
at?: number;
|
|
87
|
+
links?: string[];
|
|
88
|
+
};
|
|
89
|
+
/** How a call ended. */
|
|
90
|
+
export type AgentInspectorOutcome = {
|
|
91
|
+
status?: Exclude<AgentInspectorStatus, 'running'>;
|
|
92
|
+
result?: unknown;
|
|
93
|
+
error?: string;
|
|
94
|
+
at?: number;
|
|
95
|
+
summary?: string;
|
|
96
|
+
links?: string[];
|
|
97
|
+
};
|
|
98
|
+
/** A session, exported. */
|
|
99
|
+
export type AgentInspectorExport = {
|
|
100
|
+
exportedAt: string;
|
|
101
|
+
entries: AgentInspectorEntry[];
|
|
102
|
+
};
|
|
103
|
+
export interface AgentInspectorSink {
|
|
104
|
+
/** Record something that happened at once; returns its id. */
|
|
105
|
+
push(input: AgentInspectorInput): string;
|
|
106
|
+
/**
|
|
107
|
+
* Open a call under `key`; a call already open under it has its payload
|
|
108
|
+
* and summary updated instead (arguments that stream in).
|
|
109
|
+
*/
|
|
110
|
+
start(input: AgentInspectorInput & {
|
|
111
|
+
key: string;
|
|
112
|
+
}): string;
|
|
113
|
+
/** Close the call open under `key`; `undefined` when none is. */
|
|
114
|
+
end(key: string, outcome?: AgentInspectorOutcome): string | undefined;
|
|
115
|
+
/** Change an entry. */
|
|
116
|
+
update(id: string, patch: (entry: AgentInspectorEntry) => Partial<AgentInspectorEntry>): void;
|
|
117
|
+
/**
|
|
118
|
+
* Attach a semantic event to the newest entry `match` takes, or to the
|
|
119
|
+
* first that arrives and does (a few are kept waiting).
|
|
120
|
+
*/
|
|
121
|
+
attach(match: (entry: AgentInspectorEntry) => boolean, event: unknown): void;
|
|
122
|
+
/** Whether a call is open under `key`. */
|
|
123
|
+
running(key: string): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Say what a caller made of something (an `A2APeerEvent`): it is attached
|
|
126
|
+
* to the entry it came from, as the first linker that knows it says
|
|
127
|
+
* (`addLinker`; the A2A capture adds one). Nothing knows it: dropped.
|
|
128
|
+
*/
|
|
129
|
+
note(event: unknown): void;
|
|
130
|
+
/** Teach the sink to link a kind of event to its entry; returns its removal. */
|
|
131
|
+
addLinker(linker: AgentInspectorLinker): () => void;
|
|
132
|
+
clear(): void;
|
|
133
|
+
entries(): readonly AgentInspectorEntry[];
|
|
134
|
+
subscribe(listener: () => void): () => void;
|
|
135
|
+
exportSession(): AgentInspectorExport;
|
|
136
|
+
}
|
|
137
|
+
/** The entry an event came from, as a matcher; `undefined` for an event it does not know. */
|
|
138
|
+
export type AgentInspectorLinker = (event: unknown) => ((entry: AgentInspectorEntry) => boolean) | undefined;
|
|
139
|
+
/** How large a value is, as JSON, in bytes. */
|
|
140
|
+
export declare function jsonBytes(value: unknown): number;
|
|
141
|
+
/** A size, in words: `812 B`, `12.3 kB`, `1.2 MB`. */
|
|
142
|
+
export declare function formatBytes(bytes: number): string;
|
|
143
|
+
/** A line short enough for a row. */
|
|
144
|
+
export declare function shortLine(text: string, length?: number): string;
|
|
145
|
+
export type AgentInspectorOptions = {
|
|
146
|
+
/** The clock: `Date.now` unless a test sets it. */
|
|
147
|
+
now?: () => number;
|
|
148
|
+
/** The most entries kept: the oldest go first. */
|
|
149
|
+
limit?: number;
|
|
150
|
+
};
|
|
151
|
+
/** Make a sink: the record, and who listens to it. */
|
|
152
|
+
export declare function createAgentInspector(options?: AgentInspectorOptions): AgentInspectorSink;
|
|
153
|
+
/** A sink's entries, kept current; none without a sink. */
|
|
154
|
+
export declare function useAgentInspectorEntries(sink: AgentInspectorSink | null | undefined): readonly AgentInspectorEntry[];
|
|
155
|
+
/**
|
|
156
|
+
* A sink for a page, made once (or the one given), and its entries: what a
|
|
157
|
+
* page passes to its team, its chat and its `AgentInspector`.
|
|
158
|
+
*/
|
|
159
|
+
export declare function useAgentInspector(given?: AgentInspectorSink | null): {
|
|
160
|
+
sink: AgentInspectorSink;
|
|
161
|
+
entries: readonly AgentInspectorEntry[];
|
|
162
|
+
};
|
|
163
|
+
/** The sink the chats under it push into. */
|
|
164
|
+
export declare const AgentInspectorContext: import("react").Context<AgentInspectorSink | null>;
|
|
165
|
+
/** The sink of the nearest {@link AgentInspectorProvider}, if any. */
|
|
166
|
+
export declare function useAgentInspectorSink(): AgentInspectorSink | null;
|
|
167
|
+
/** Every chat under it records what its agent does into `sink`. */
|
|
168
|
+
export declare function AgentInspectorProvider({ sink, children, }: {
|
|
169
|
+
sink: AgentInspectorSink;
|
|
170
|
+
children?: ReactNode;
|
|
171
|
+
}): JSX.Element;
|
|
172
|
+
/** What a tool call is, for the inspector: its source and its mark. */
|
|
173
|
+
export type ToolClass = {
|
|
174
|
+
source: AgentInspectorSource;
|
|
175
|
+
icon?: string;
|
|
176
|
+
emoji?: string;
|
|
177
|
+
};
|
|
178
|
+
/** A call's arguments, in a few words: `kind=customer, limit=100`. */
|
|
179
|
+
export declare function argumentsLine(args: unknown, length?: number): string;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Agent Inspector's record: what an agent does, as it does it, one entry
|
|
7
|
+
* a thing — an A2A request or event, an MCP tool call, a skill, a frontend or
|
|
8
|
+
* backend tool, a model turn, an approval — each with its time, the agent
|
|
9
|
+
* that did it (`actor`), where it came from (`source`), its kind, its name,
|
|
10
|
+
* its status and, once it ends, its duration, a line that says it, and its
|
|
11
|
+
* payload whole.
|
|
12
|
+
*
|
|
13
|
+
* A call's start and its end are one entry: {@link AgentInspectorSink.start}
|
|
14
|
+
* opens it under a key (a tool call's id), {@link AgentInspectorSink.end}
|
|
15
|
+
* closes it with its result. Anything can push into a sink: the A2A capture
|
|
16
|
+
* (`inspectA2AFetch`), a team (`useA2ATeam`'s `inspector`), a chat
|
|
17
|
+
* (`ChatBase`, under {@link AgentInspectorProvider}). `AgentInspector` draws
|
|
18
|
+
* it.
|
|
19
|
+
*
|
|
20
|
+
* @module components/inspector/agentInspector
|
|
21
|
+
*/
|
|
22
|
+
import { createContext, createElement, useContext, useState, useSyncExternalStore, } from 'react';
|
|
23
|
+
/** Every source, in the order the filters list them. */
|
|
24
|
+
export const AGENT_INSPECTOR_SOURCES = [
|
|
25
|
+
'a2a',
|
|
26
|
+
'mcp',
|
|
27
|
+
'skill',
|
|
28
|
+
'frontend-tool',
|
|
29
|
+
'backend-tool',
|
|
30
|
+
'model',
|
|
31
|
+
'approval',
|
|
32
|
+
];
|
|
33
|
+
/** A source, in words. */
|
|
34
|
+
export const AGENT_INSPECTOR_SOURCE_LABELS = {
|
|
35
|
+
a2a: 'A2A',
|
|
36
|
+
mcp: 'MCP',
|
|
37
|
+
skill: 'Skill',
|
|
38
|
+
'frontend-tool': 'Frontend tool',
|
|
39
|
+
'backend-tool': 'Backend tool',
|
|
40
|
+
model: 'Model',
|
|
41
|
+
approval: 'Approval',
|
|
42
|
+
};
|
|
43
|
+
/** How large a value is, as JSON, in bytes. */
|
|
44
|
+
export function jsonBytes(value) {
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const text = JSON.stringify(value);
|
|
50
|
+
return text === undefined ? 0 : new TextEncoder().encode(text).length;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** A size, in words: `812 B`, `12.3 kB`, `1.2 MB`. */
|
|
57
|
+
export function formatBytes(bytes) {
|
|
58
|
+
if (bytes < 1000) {
|
|
59
|
+
return `${bytes} B`;
|
|
60
|
+
}
|
|
61
|
+
if (bytes < 1_000_000) {
|
|
62
|
+
return `${(bytes / 1000).toFixed(1)} kB`;
|
|
63
|
+
}
|
|
64
|
+
return `${(bytes / 1_000_000).toFixed(1)} MB`;
|
|
65
|
+
}
|
|
66
|
+
/** A line short enough for a row. */
|
|
67
|
+
export function shortLine(text, length = 160) {
|
|
68
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
69
|
+
return flat.length > length ? `${flat.slice(0, length - 1)}…` : flat;
|
|
70
|
+
}
|
|
71
|
+
/** How many semantic events wait for their entry. */
|
|
72
|
+
const PENDING_LIMIT = 32;
|
|
73
|
+
/** Make a sink: the record, and who listens to it. */
|
|
74
|
+
export function createAgentInspector(options = {}) {
|
|
75
|
+
const now = options.now ?? Date.now;
|
|
76
|
+
const limit = options.limit ?? 2000;
|
|
77
|
+
let list = [];
|
|
78
|
+
let counter = 0;
|
|
79
|
+
const open = new Map();
|
|
80
|
+
const listeners = new Set();
|
|
81
|
+
let pending = [];
|
|
82
|
+
let scheduled = false;
|
|
83
|
+
const linkers = new Set();
|
|
84
|
+
const notify = () => {
|
|
85
|
+
if (scheduled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
scheduled = true;
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
scheduled = false;
|
|
91
|
+
listeners.forEach(listener => listener());
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const replace = (id, next) => {
|
|
95
|
+
list = list.map(entry => (entry.id === id ? next : entry));
|
|
96
|
+
};
|
|
97
|
+
const settlePending = (entry) => {
|
|
98
|
+
if (!pending.length) {
|
|
99
|
+
return entry;
|
|
100
|
+
}
|
|
101
|
+
const taken = pending.filter(waiting => waiting.match(entry));
|
|
102
|
+
if (!taken.length) {
|
|
103
|
+
return entry;
|
|
104
|
+
}
|
|
105
|
+
pending = pending.filter(waiting => !taken.includes(waiting));
|
|
106
|
+
return {
|
|
107
|
+
...entry,
|
|
108
|
+
events: [...entry.events, ...taken.map(waiting => waiting.event)],
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
const push = (input) => {
|
|
112
|
+
counter += 1;
|
|
113
|
+
const id = `e${counter}`;
|
|
114
|
+
const { links, at, ...rest } = input;
|
|
115
|
+
const entry = settlePending({
|
|
116
|
+
...rest,
|
|
117
|
+
id,
|
|
118
|
+
at: at ?? now(),
|
|
119
|
+
bytes: jsonBytes(input.payload) + jsonBytes(input.result),
|
|
120
|
+
links: links ?? [],
|
|
121
|
+
events: [],
|
|
122
|
+
});
|
|
123
|
+
list = [...list, entry];
|
|
124
|
+
if (list.length > limit) {
|
|
125
|
+
list = list.slice(list.length - limit);
|
|
126
|
+
}
|
|
127
|
+
notify();
|
|
128
|
+
return id;
|
|
129
|
+
};
|
|
130
|
+
const find = (id) => list.find(entry => entry.id === id);
|
|
131
|
+
const sink = {
|
|
132
|
+
push,
|
|
133
|
+
start(input) {
|
|
134
|
+
const known = open.get(input.key);
|
|
135
|
+
const entry = known ? find(known) : undefined;
|
|
136
|
+
if (entry) {
|
|
137
|
+
const payload = input.payload ?? entry.payload;
|
|
138
|
+
replace(entry.id, {
|
|
139
|
+
...entry,
|
|
140
|
+
payload,
|
|
141
|
+
summary: input.summary || entry.summary,
|
|
142
|
+
links: [...entry.links, ...(input.links ?? [])],
|
|
143
|
+
bytes: jsonBytes(payload) + jsonBytes(entry.result),
|
|
144
|
+
});
|
|
145
|
+
notify();
|
|
146
|
+
return entry.id;
|
|
147
|
+
}
|
|
148
|
+
const id = push({ ...input, status: 'running' });
|
|
149
|
+
open.set(input.key, id);
|
|
150
|
+
return id;
|
|
151
|
+
},
|
|
152
|
+
end(key, outcome = {}) {
|
|
153
|
+
const id = open.get(key);
|
|
154
|
+
const entry = id ? find(id) : undefined;
|
|
155
|
+
if (!id || !entry) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
open.delete(key);
|
|
159
|
+
const result = outcome.result ?? entry.result;
|
|
160
|
+
replace(id, {
|
|
161
|
+
...entry,
|
|
162
|
+
status: outcome.status ?? (outcome.error ? 'failed' : 'done'),
|
|
163
|
+
endedAt: outcome.at ?? now(),
|
|
164
|
+
result,
|
|
165
|
+
error: outcome.error ?? entry.error,
|
|
166
|
+
summary: outcome.summary ?? entry.summary,
|
|
167
|
+
links: [...entry.links, ...(outcome.links ?? [])],
|
|
168
|
+
bytes: jsonBytes(entry.payload) + jsonBytes(result),
|
|
169
|
+
});
|
|
170
|
+
notify();
|
|
171
|
+
return id;
|
|
172
|
+
},
|
|
173
|
+
update(id, patch) {
|
|
174
|
+
const entry = find(id);
|
|
175
|
+
if (!entry) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const next = { ...entry, ...patch(entry) };
|
|
179
|
+
replace(id, {
|
|
180
|
+
...next,
|
|
181
|
+
bytes: jsonBytes(next.payload) + jsonBytes(next.result),
|
|
182
|
+
});
|
|
183
|
+
notify();
|
|
184
|
+
},
|
|
185
|
+
attach(match, event) {
|
|
186
|
+
for (let index = list.length - 1; index >= 0; index -= 1) {
|
|
187
|
+
const entry = list[index];
|
|
188
|
+
if (match(entry) && !entry.events.includes(event)) {
|
|
189
|
+
replace(entry.id, { ...entry, events: [...entry.events, event] });
|
|
190
|
+
notify();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
pending = [...pending, { match, event }].slice(-PENDING_LIMIT);
|
|
195
|
+
},
|
|
196
|
+
running(key) {
|
|
197
|
+
return open.has(key);
|
|
198
|
+
},
|
|
199
|
+
note(event) {
|
|
200
|
+
for (const linker of linkers) {
|
|
201
|
+
const match = linker(event);
|
|
202
|
+
if (match) {
|
|
203
|
+
sink.attach(match, event);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
addLinker(linker) {
|
|
209
|
+
linkers.add(linker);
|
|
210
|
+
return () => {
|
|
211
|
+
linkers.delete(linker);
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
clear() {
|
|
215
|
+
list = [];
|
|
216
|
+
open.clear();
|
|
217
|
+
pending = [];
|
|
218
|
+
notify();
|
|
219
|
+
},
|
|
220
|
+
entries() {
|
|
221
|
+
return list;
|
|
222
|
+
},
|
|
223
|
+
subscribe(listener) {
|
|
224
|
+
listeners.add(listener);
|
|
225
|
+
return () => {
|
|
226
|
+
listeners.delete(listener);
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
exportSession() {
|
|
230
|
+
return { exportedAt: new Date(now()).toISOString(), entries: [...list] };
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
return sink;
|
|
234
|
+
}
|
|
235
|
+
const NO_ENTRIES = [];
|
|
236
|
+
const NO_SUBSCRIPTION = () => () => undefined;
|
|
237
|
+
/** A sink's entries, kept current; none without a sink. */
|
|
238
|
+
export function useAgentInspectorEntries(sink) {
|
|
239
|
+
return useSyncExternalStore(sink ? sink.subscribe : NO_SUBSCRIPTION, () => (sink ? sink.entries() : NO_ENTRIES), () => (sink ? sink.entries() : NO_ENTRIES));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* A sink for a page, made once (or the one given), and its entries: what a
|
|
243
|
+
* page passes to its team, its chat and its `AgentInspector`.
|
|
244
|
+
*/
|
|
245
|
+
export function useAgentInspector(given) {
|
|
246
|
+
const [own] = useState(() => given ?? createAgentInspector());
|
|
247
|
+
const sink = given ?? own;
|
|
248
|
+
return { sink, entries: useAgentInspectorEntries(sink) };
|
|
249
|
+
}
|
|
250
|
+
/** The sink the chats under it push into. */
|
|
251
|
+
export const AgentInspectorContext = createContext(null);
|
|
252
|
+
/** The sink of the nearest {@link AgentInspectorProvider}, if any. */
|
|
253
|
+
export function useAgentInspectorSink() {
|
|
254
|
+
return useContext(AgentInspectorContext);
|
|
255
|
+
}
|
|
256
|
+
/** Every chat under it records what its agent does into `sink`. */
|
|
257
|
+
export function AgentInspectorProvider({ sink, children, }) {
|
|
258
|
+
return createElement(AgentInspectorContext.Provider, { value: sink }, children);
|
|
259
|
+
}
|
|
260
|
+
/** A call's arguments, in a few words: `kind=customer, limit=100`. */
|
|
261
|
+
export function argumentsLine(args, length = 100) {
|
|
262
|
+
if (!args || typeof args !== 'object') {
|
|
263
|
+
return args === undefined ? '' : shortLine(String(args), length);
|
|
264
|
+
}
|
|
265
|
+
const parts = Object.entries(args).map(([key, value]) => `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`);
|
|
266
|
+
return shortLine(parts.join(', '), length);
|
|
267
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A chat's agent, as the Agent Inspector records it: each turn of its model
|
|
3
|
+
* (the prompt, the answer, the tokens), and each tool call from its start to
|
|
4
|
+
* its end — an MCP server's, a skill, a frontend tool the page runs, a tool
|
|
5
|
+
* the runtime runs — whatever transport the chat speaks (AG-UI, Vercel AI,
|
|
6
|
+
* A2A, the in-browser harness): `ChatBase` tells it what its adapter told.
|
|
7
|
+
*
|
|
8
|
+
* @module components/inspector/chatInspect
|
|
9
|
+
*/
|
|
10
|
+
import type { MarkedMcpServer } from '../../chat/marks/toolMarks';
|
|
11
|
+
import { type AgentInspectorSink } from './agentInspector';
|
|
12
|
+
export type ChatInspectorRecorder = {
|
|
13
|
+
turnStarted: (prompt: string, model?: string) => void;
|
|
14
|
+
turnEnded: (outcome: {
|
|
15
|
+
text?: string;
|
|
16
|
+
usage?: {
|
|
17
|
+
promptTokens?: number;
|
|
18
|
+
completionTokens?: number;
|
|
19
|
+
totalTokens?: number;
|
|
20
|
+
};
|
|
21
|
+
error?: string;
|
|
22
|
+
}) => void;
|
|
23
|
+
toolStarted: (call: {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
args: Record<string, unknown>;
|
|
27
|
+
}) => void;
|
|
28
|
+
toolEnded: (id: string, outcome: {
|
|
29
|
+
result?: unknown;
|
|
30
|
+
error?: string;
|
|
31
|
+
}) => void;
|
|
32
|
+
};
|
|
33
|
+
/** A recorder for one chat's agent (`actor`) into `sink`. */
|
|
34
|
+
export declare function chatInspectorRecorder(sink: AgentInspectorSink, actor: () => string, options?: {
|
|
35
|
+
/** The page's own tools, by name: frontend tools. */
|
|
36
|
+
frontendTools?: () => readonly string[];
|
|
37
|
+
mcpServers?: () => readonly MarkedMcpServer[];
|
|
38
|
+
}): ChatInspectorRecorder;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { argumentsLine, shortLine, } from './agentInspector';
|
|
6
|
+
import { classifyToolCall } from './classify';
|
|
7
|
+
/** A recorder for one chat's agent (`actor`) into `sink`. */
|
|
8
|
+
export function chatInspectorRecorder(sink, actor, options = {}) {
|
|
9
|
+
let turns = 0;
|
|
10
|
+
let turnKey;
|
|
11
|
+
return {
|
|
12
|
+
turnStarted(prompt, model) {
|
|
13
|
+
turns += 1;
|
|
14
|
+
turnKey = `chat-turn:${turns}`;
|
|
15
|
+
sink.start({
|
|
16
|
+
key: turnKey,
|
|
17
|
+
actor: actor(),
|
|
18
|
+
source: 'model',
|
|
19
|
+
kind: 'turn',
|
|
20
|
+
...(model ? { name: model } : {}),
|
|
21
|
+
summary: shortLine(prompt),
|
|
22
|
+
payload: { prompt, ...(model ? { model } : {}) },
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
turnEnded({ text, usage, error }) {
|
|
26
|
+
if (!turnKey) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const total = usage?.totalTokens ??
|
|
30
|
+
(usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0);
|
|
31
|
+
sink.end(turnKey, {
|
|
32
|
+
...(error ? { status: 'failed', error } : {}),
|
|
33
|
+
result: {
|
|
34
|
+
...(text ? { text } : {}),
|
|
35
|
+
...(usage ? { usage } : {}),
|
|
36
|
+
},
|
|
37
|
+
...(total ? { summary: `${total} tokens` } : {}),
|
|
38
|
+
});
|
|
39
|
+
turnKey = undefined;
|
|
40
|
+
},
|
|
41
|
+
toolStarted({ id, name, args }) {
|
|
42
|
+
const kind = classifyToolCall(name, args, {
|
|
43
|
+
frontendTools: options.frontendTools?.(),
|
|
44
|
+
mcpServers: options.mcpServers?.(),
|
|
45
|
+
});
|
|
46
|
+
sink.start({
|
|
47
|
+
key: `chat-tool:${id}`,
|
|
48
|
+
actor: actor(),
|
|
49
|
+
source: kind.source,
|
|
50
|
+
...(kind.icon ? { icon: kind.icon } : {}),
|
|
51
|
+
...(kind.emoji ? { emoji: kind.emoji } : {}),
|
|
52
|
+
kind: 'tool-call',
|
|
53
|
+
name,
|
|
54
|
+
summary: argumentsLine(args),
|
|
55
|
+
payload: args,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
toolEnded(id, { result, error }) {
|
|
59
|
+
sink.end(`chat-tool:${id}`, {
|
|
60
|
+
...(error ? { status: 'failed', error } : {}),
|
|
61
|
+
result,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a tool call comes from, for the Agent Inspector, by what the
|
|
3
|
+
* catalogue says of its name. Apart from the record itself, which a page can
|
|
4
|
+
* keep without loading the catalogues.
|
|
5
|
+
*
|
|
6
|
+
* @module components/inspector/classify
|
|
7
|
+
*/
|
|
8
|
+
import { type MarkedMcpServer } from '../../chat/marks/toolMarks';
|
|
9
|
+
import type { AgentInspectorSource, ToolClass } from './agentInspector';
|
|
10
|
+
/**
|
|
11
|
+
* Where a tool call comes from, by who claims its name: a skill, one of
|
|
12
|
+
* `mcpServers`, a frontend tool (the catalogue's, or one of `frontendTools`),
|
|
13
|
+
* a runtime tool; otherwise `fallback` — a tool the runtime runs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function classifyToolCall(name: string, args?: Record<string, unknown>, options?: {
|
|
16
|
+
mcpServers?: readonly MarkedMcpServer[];
|
|
17
|
+
frontendTools?: readonly string[];
|
|
18
|
+
fallback?: AgentInspectorSource;
|
|
19
|
+
}): ToolClass;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Where a tool call comes from, for the Agent Inspector, by what the
|
|
7
|
+
* catalogue says of its name. Apart from the record itself, which a page can
|
|
8
|
+
* keep without loading the catalogues.
|
|
9
|
+
*
|
|
10
|
+
* @module components/inspector/classify
|
|
11
|
+
*/
|
|
12
|
+
import { marksOfToolCall, } from '../../chat/marks/toolMarks';
|
|
13
|
+
/**
|
|
14
|
+
* Where a tool call comes from, by who claims its name: a skill, one of
|
|
15
|
+
* `mcpServers`, a frontend tool (the catalogue's, or one of `frontendTools`),
|
|
16
|
+
* a runtime tool; otherwise `fallback` — a tool the runtime runs.
|
|
17
|
+
*/
|
|
18
|
+
export function classifyToolCall(name, args, options = {}) {
|
|
19
|
+
if (options.frontendTools?.includes(name)) {
|
|
20
|
+
const marks = marksOfToolCall(name, args, options.mcpServers);
|
|
21
|
+
return {
|
|
22
|
+
source: 'frontend-tool',
|
|
23
|
+
...(marks?.icon ? { icon: marks.icon } : {}),
|
|
24
|
+
...(marks?.emoji ? { emoji: marks.emoji } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const marks = marksOfToolCall(name, args, options.mcpServers);
|
|
28
|
+
const source = marks?.kind === 'skill'
|
|
29
|
+
? 'skill'
|
|
30
|
+
: marks?.kind === 'mcp'
|
|
31
|
+
? 'mcp'
|
|
32
|
+
: marks?.kind === 'frontend'
|
|
33
|
+
? 'frontend-tool'
|
|
34
|
+
: marks?.kind === 'runtime'
|
|
35
|
+
? 'backend-tool'
|
|
36
|
+
: (options.fallback ?? 'backend-tool');
|
|
37
|
+
return {
|
|
38
|
+
source,
|
|
39
|
+
...(marks?.icon ? { icon: marks.icon } : {}),
|
|
40
|
+
...(marks?.emoji ? { emoji: marks.emoji } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent Inspector: what agents do — A2A traffic, MCP calls, skills,
|
|
3
|
+
* frontend and backend tools, model turns, approvals — recorded into a sink
|
|
4
|
+
* and drawn as rows that unfold to their payloads.
|
|
5
|
+
*
|
|
6
|
+
* @module components/inspector
|
|
7
|
+
*/
|
|
8
|
+
export * from './agentInspector';
|
|
9
|
+
export * from './classify';
|
|
10
|
+
export * from './chatInspect';
|
|
11
|
+
export * from './a2aInspect';
|
|
12
|
+
export * from './JsonTree';
|
|
13
|
+
export * from './AgentInspector';
|
|
14
|
+
export * from './AgentInspectorDialog';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Agent Inspector: what agents do — A2A traffic, MCP calls, skills,
|
|
7
|
+
* frontend and backend tools, model turns, approvals — recorded into a sink
|
|
8
|
+
* and drawn as rows that unfold to their payloads.
|
|
9
|
+
*
|
|
10
|
+
* @module components/inspector
|
|
11
|
+
*/
|
|
12
|
+
export * from './agentInspector';
|
|
13
|
+
export * from './classify';
|
|
14
|
+
export * from './chatInspect';
|
|
15
|
+
export * from './a2aInspect';
|
|
16
|
+
export * from './JsonTree';
|
|
17
|
+
export * from './AgentInspector';
|
|
18
|
+
export * from './AgentInspectorDialog';
|