@datalayer/agent-runtimes 1.3.95 → 1.3.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-embed/datalayer-app-main.js +6 -6
- package/dist-embed/datalayer-app.js +1 -1
- package/lib/apps/__tests__/fixtures/sceneCheckCases.json +352 -0
- package/lib/apps/apps/sceneChecks.d.ts +92 -0
- package/lib/apps/apps/sceneChecks.js +649 -0
- package/lib/apps/apps/sceneShape.d.ts +8 -0
- package/lib/apps/apps/sceneShape.js +191 -0
- package/lib/apps/apps/sceneYaml.d.ts +64 -0
- package/lib/apps/apps/sceneYaml.js +253 -0
- package/lib/apps/core/canvasParts.d.ts +28 -0
- package/lib/apps/core/canvasParts.js +14 -0
- package/lib/apps/core/index.d.ts +1 -0
- package/lib/apps/core/index.js +2 -0
- package/lib/apps/embed/loader.js +10 -0
- package/lib/apps/plugins/agents/SandboxSelector.js +13 -2
- package/lib/apps/plugins/agents/host.d.ts +2 -0
- package/lib/apps/plugins/agents/host.js +16 -0
- package/lib/apps/plugins/agents/plugin.js +10 -2
- package/lib/apps/plugins/agents/switchable.d.ts +12 -0
- package/lib/apps/plugins/agents/switchable.js +28 -0
- package/lib/apps/plugins/canvas-parts/index.d.ts +44 -0
- package/lib/apps/plugins/canvas-parts/index.js +209 -0
- package/lib/chat/base/loopStep.d.ts +2 -1
- package/lib/chat/base/loopStep.js +2 -3
- package/lib/chat/tools/ToolCallDisplay.js +2 -1
- package/lib/hooks/useAgUi.js +4 -1
- package/lib/protocols/AGUIAdapter.js +5 -1
- package/lib/protocols/agUiRefusal.d.ts +47 -0
- package/lib/protocols/agUiRefusal.js +87 -0
- package/lib/specs/agents/agents.d.ts +1 -0
- package/lib/specs/agents/agents.js +49 -0
- package/lib/specs/apps.js +20 -12
- package/lib/specs/scenes.js +103 -98
- package/lib/specs/teams/teams.js +1 -1
- package/package.json +4 -2
- package/scripts/dist-embed.mjs +121 -0
- package/scripts/record-scene-checks.py +600 -0
|
@@ -55,6 +55,18 @@ export type SandboxTargetSpec = {
|
|
|
55
55
|
};
|
|
56
56
|
/** The four, in the order the control shows them: nearest first. */
|
|
57
57
|
export declare const SANDBOX_TARGETS: readonly SandboxTarget[];
|
|
58
|
+
/**
|
|
59
|
+
* Why a target is shown but not offered here — said on it — or nothing when
|
|
60
|
+
* it is offered.
|
|
61
|
+
*
|
|
62
|
+
* *Local* is a local agent with the Jupyter server it starts beside itself,
|
|
63
|
+
* and a web page has no machine of the reader's to start them on. Decided
|
|
64
|
+
* 2026-10-10 (Eric): not offered on the web for now, and left in the switch,
|
|
65
|
+
* held, so that a reader sees what exists and why it is not here. Inside
|
|
66
|
+
* JupyterLab it is offered as before — the same line the code-sandbox picker
|
|
67
|
+
* draws for a Jupyter Server's kernels (`codeSandboxProviders`).
|
|
68
|
+
*/
|
|
69
|
+
export declare function heldTargetReason(target: SandboxTarget, serverUrl: string): string | undefined;
|
|
58
70
|
export declare const TARGET_SPECS: Record<SandboxTarget, SandboxTargetSpec>;
|
|
59
71
|
/** Whether an agent runs on this target — the question the chat asks. */
|
|
60
72
|
export declare function targetHasAgent(target: SandboxTarget): boolean;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* @module apps/plugins/agents/switchable
|
|
23
23
|
*/
|
|
24
24
|
import { computed, signal, } from '@datalayer/reactor';
|
|
25
|
+
import { insideJupyterLab } from './host';
|
|
25
26
|
import { browserSource, createBrowserSandboxService, } from './browserService';
|
|
26
27
|
import { createServerSandboxService, } from './service';
|
|
27
28
|
/**
|
|
@@ -40,6 +41,28 @@ export const SANDBOX_TARGETS = [
|
|
|
40
41
|
'jupyter',
|
|
41
42
|
'datalayer',
|
|
42
43
|
];
|
|
44
|
+
/**
|
|
45
|
+
* Why a target is shown but not offered here — said on it — or nothing when
|
|
46
|
+
* it is offered.
|
|
47
|
+
*
|
|
48
|
+
* *Local* is a local agent with the Jupyter server it starts beside itself,
|
|
49
|
+
* and a web page has no machine of the reader's to start them on. Decided
|
|
50
|
+
* 2026-10-10 (Eric): not offered on the web for now, and left in the switch,
|
|
51
|
+
* held, so that a reader sees what exists and why it is not here. Inside
|
|
52
|
+
* JupyterLab it is offered as before — the same line the code-sandbox picker
|
|
53
|
+
* draws for a Jupyter Server's kernels (`codeSandboxProviders`).
|
|
54
|
+
*/
|
|
55
|
+
export function heldTargetReason(target, serverUrl) {
|
|
56
|
+
// A host that names its own agent-runtimes server — `app.mount`, `loop apps
|
|
57
|
+
// run --web` (LOOP P-08, P-25) — is not asking for the reader's machine:
|
|
58
|
+
// Local is that server, and it is offered. Held, every message of a page
|
|
59
|
+
// served by its own server went to the visitors' runtime instead
|
|
60
|
+
// (seen 2026-10-10 on 1.3.96).
|
|
61
|
+
if (target === 'local' && !serverUrl && !insideJupyterLab()) {
|
|
62
|
+
return 'Not offered on the web for now: a local agent and the Jupyter server it starts run beside JupyterLab, not in a page.';
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
43
66
|
export const TARGET_SPECS = {
|
|
44
67
|
browser: {
|
|
45
68
|
label: 'Browser',
|
|
@@ -298,6 +321,11 @@ export function createSwitchableSandboxService({ serverUrl, initialTarget = 'loc
|
|
|
298
321
|
if (next === previous) {
|
|
299
322
|
return;
|
|
300
323
|
}
|
|
324
|
+
// Held here: refused whoever asks, not only by the switch's button.
|
|
325
|
+
const held = heldTargetReason(next, serverUrl);
|
|
326
|
+
if (held) {
|
|
327
|
+
throw new Error(held);
|
|
328
|
+
}
|
|
301
329
|
// Disconnect first: two live sandboxes would race to report status, and
|
|
302
330
|
// the reader would watch the header flicker between them.
|
|
303
331
|
disconnect?.();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's toolbox, contributed (LOOP C-20): one plugin per source of
|
|
3
|
+
* parts — the servers of the catalogue a connection reaches, its skills, its
|
|
4
|
+
* models, and the rules, tests and events an agent answers to — each
|
|
5
|
+
* contributing to `loop.canvas.part`. The toolbox is what the enabled
|
|
6
|
+
* plugins contribute, as the palette is (`canvas-blocks`, C-12).
|
|
7
|
+
*
|
|
8
|
+
* @module apps/plugins/canvas-parts
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactorPlatform, type ReactorPlugin } from '@datalayer/reactor';
|
|
11
|
+
import { LoopCanvasPart, type CanvasPartContribution } from '../../core/canvasParts';
|
|
12
|
+
type PartsReader = {
|
|
13
|
+
getContributions: (point: typeof LoopCanvasPart) => ReadonlyArray<{
|
|
14
|
+
value: CanvasPartContribution;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
/** The short ids an organization turns off, and the plugin each names. */
|
|
18
|
+
export declare const CANVAS_PART_SOURCES: readonly ["connections", "skills", "models", "rules", "tests", "events"];
|
|
19
|
+
export type CanvasPartSource = (typeof CANVAS_PART_SOURCES)[number];
|
|
20
|
+
export declare const canvasPartsPluginName: (source: CanvasPartSource) => string;
|
|
21
|
+
/** A connection to each server of the catalogue: read-only, as its owner, every tool. */
|
|
22
|
+
export declare const CONNECTION_PARTS: CanvasPartContribution[];
|
|
23
|
+
/** Each skill of the catalogue. */
|
|
24
|
+
export declare const SKILL_PARTS: CanvasPartContribution[];
|
|
25
|
+
/** Each chat model of the catalogue. */
|
|
26
|
+
export declare const MODEL_PARTS: CanvasPartContribution[];
|
|
27
|
+
/** The rules a person most often sets, each one of the four behaviours. */
|
|
28
|
+
export declare const RULE_PARTS: CanvasPartContribution[];
|
|
29
|
+
/** A test: a question, and what a good answer does. */
|
|
30
|
+
export declare const TEST_PARTS: CanvasPartContribution[];
|
|
31
|
+
/** The events an application answers (C-21): on a schedule, on an event, once. */
|
|
32
|
+
export declare const EVENT_PARTS: CanvasPartContribution[];
|
|
33
|
+
export declare const CANVAS_PART_PLUGINS: ReactorPlugin<Record<string, never>, unknown, unknown>[];
|
|
34
|
+
/** The toolbox's reactor, its named plugins disabled; a name no plugin has is refused. */
|
|
35
|
+
export declare function canvasPartsReactor(disabled?: readonly string[], plugins?: ReactorPlugin<any, any, any>[]): ReactorPlatform;
|
|
36
|
+
/**
|
|
37
|
+
* The toolbox for an organization's plugins off: the ids it turns off that
|
|
38
|
+
* name a source of parts (`skills`, …) take those parts off; the rest are the
|
|
39
|
+
* palette's.
|
|
40
|
+
*/
|
|
41
|
+
export declare function canvasPartsReactorOf(pluginsOff: readonly string[]): ReactorPlatform;
|
|
42
|
+
/** What the toolbox offers: the parts the enabled plugins contribute, in their order. */
|
|
43
|
+
export declare function canvasPartsOf(reactor: PartsReader): CanvasPartContribution[];
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The agent's toolbox, contributed (LOOP C-20): one plugin per source of
|
|
7
|
+
* parts — the servers of the catalogue a connection reaches, its skills, its
|
|
8
|
+
* models, and the rules, tests and events an agent answers to — each
|
|
9
|
+
* contributing to `loop.canvas.part`. The toolbox is what the enabled
|
|
10
|
+
* plugins contribute, as the palette is (`canvas-blocks`, C-12).
|
|
11
|
+
*
|
|
12
|
+
* @module apps/plugins/canvas-parts
|
|
13
|
+
*/
|
|
14
|
+
import { buildReactorFromPlugins, contribution, definePlugin, } from '@datalayer/reactor';
|
|
15
|
+
import { MCP_SERVER_LIBRARY } from '../../../specs/mcpServers';
|
|
16
|
+
import { listChatModels } from '../../../specs/models';
|
|
17
|
+
import { getSkillSpecs } from '../../../specs/skills';
|
|
18
|
+
import { LoopCanvasPart, } from '../../core/canvasParts';
|
|
19
|
+
/** The short ids an organization turns off, and the plugin each names. */
|
|
20
|
+
export const CANVAS_PART_SOURCES = [
|
|
21
|
+
'connections',
|
|
22
|
+
'skills',
|
|
23
|
+
'models',
|
|
24
|
+
'rules',
|
|
25
|
+
'tests',
|
|
26
|
+
'events',
|
|
27
|
+
];
|
|
28
|
+
export const canvasPartsPluginName = (source) => `@datalayer/loop-plugin-parts-${source}`;
|
|
29
|
+
const partsPlugin = (source, displayName, parts) => definePlugin({
|
|
30
|
+
name: canvasPartsPluginName(source),
|
|
31
|
+
displayName,
|
|
32
|
+
description: `${displayName} in the agent's toolbox on the Canvas.`,
|
|
33
|
+
octicon: 'package',
|
|
34
|
+
contributes: parts.map(part => contribution(LoopCanvasPart, part, { id: part.id })),
|
|
35
|
+
});
|
|
36
|
+
/** A connection to each server of the catalogue: read-only, as its owner, every tool. */
|
|
37
|
+
export const CONNECTION_PARTS = Object.values(MCP_SERVER_LIBRARY).map(server => ({
|
|
38
|
+
id: `connection:${server.id}`,
|
|
39
|
+
kind: 'connection',
|
|
40
|
+
label: server.name,
|
|
41
|
+
says: server.description ?? '',
|
|
42
|
+
emoji: server.emoji,
|
|
43
|
+
example: { server: server.id, access: 'read', as: 'owner', only: [] },
|
|
44
|
+
enabled: server.enabled,
|
|
45
|
+
}));
|
|
46
|
+
/** Each skill of the catalogue. */
|
|
47
|
+
export const SKILL_PARTS = getSkillSpecs().map(skill => ({
|
|
48
|
+
id: `skill:${skill.id}`,
|
|
49
|
+
kind: 'skill',
|
|
50
|
+
label: skill.name,
|
|
51
|
+
says: skill.description,
|
|
52
|
+
emoji: skill.emoji,
|
|
53
|
+
example: skill.id,
|
|
54
|
+
enabled: skill.enabled,
|
|
55
|
+
}));
|
|
56
|
+
/** Each chat model of the catalogue. */
|
|
57
|
+
export const MODEL_PARTS = listChatModels().map(model => ({
|
|
58
|
+
id: `model:${model.id}`,
|
|
59
|
+
kind: 'model',
|
|
60
|
+
label: model.name,
|
|
61
|
+
says: model.description,
|
|
62
|
+
example: model.id,
|
|
63
|
+
// A model's `available` says whether its key is set where the catalogue
|
|
64
|
+
// was read, not whether it may be chosen: every chat model may.
|
|
65
|
+
enabled: true,
|
|
66
|
+
}));
|
|
67
|
+
/** The rules a person most often sets, each one of the four behaviours. */
|
|
68
|
+
export const RULE_PARTS = [
|
|
69
|
+
{
|
|
70
|
+
id: 'rule:ask-before-sending',
|
|
71
|
+
kind: 'rule',
|
|
72
|
+
label: 'Ask before sending',
|
|
73
|
+
says: 'It asks you before it sends anything on your behalf.',
|
|
74
|
+
example: {
|
|
75
|
+
action: 'Send on my behalf',
|
|
76
|
+
applies_to: ['send'],
|
|
77
|
+
behaviour: 'ask_first',
|
|
78
|
+
},
|
|
79
|
+
enabled: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'rule:read-freely',
|
|
83
|
+
kind: 'rule',
|
|
84
|
+
label: 'Read freely',
|
|
85
|
+
says: 'It reads what its connections reach without asking.',
|
|
86
|
+
example: { action: 'Read', applies_to: ['read'], behaviour: 'do_it' },
|
|
87
|
+
enabled: true,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'rule:never-delete',
|
|
91
|
+
kind: 'rule',
|
|
92
|
+
label: 'Leave deleting to me',
|
|
93
|
+
says: 'It never deletes: it tells you what it would.',
|
|
94
|
+
example: {
|
|
95
|
+
action: 'Delete',
|
|
96
|
+
applies_to: ['delete'],
|
|
97
|
+
behaviour: 'leave_to_me',
|
|
98
|
+
},
|
|
99
|
+
enabled: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'rule:buy-if-asked',
|
|
103
|
+
kind: 'rule',
|
|
104
|
+
label: 'Buy only when asked',
|
|
105
|
+
says: 'It buys only when you ask it to, in so many words.',
|
|
106
|
+
example: { action: 'Buy', applies_to: ['buy'], behaviour: 'if_asked' },
|
|
107
|
+
enabled: true,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
/** A test: a question, and what a good answer does. */
|
|
111
|
+
export const TEST_PARTS = [
|
|
112
|
+
{
|
|
113
|
+
id: 'test:answers-from-sources',
|
|
114
|
+
kind: 'test',
|
|
115
|
+
label: 'Answers from its sources',
|
|
116
|
+
says: 'A question, and what a good answer does.',
|
|
117
|
+
example: {
|
|
118
|
+
ask: 'What is due this week?',
|
|
119
|
+
expect: 'It lists what is due this week, read from its sources, and says where.',
|
|
120
|
+
},
|
|
121
|
+
enabled: true,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'test:says-when-it-cannot',
|
|
125
|
+
kind: 'test',
|
|
126
|
+
label: 'Says when it cannot',
|
|
127
|
+
says: 'A question it cannot answer, and it says so.',
|
|
128
|
+
example: {
|
|
129
|
+
ask: 'What will the price be next year?',
|
|
130
|
+
expect: 'It says it cannot know, and does not guess.',
|
|
131
|
+
},
|
|
132
|
+
enabled: true,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
/** The events an application answers (C-21): on a schedule, on an event, once. */
|
|
136
|
+
export const EVENT_PARTS = [
|
|
137
|
+
{
|
|
138
|
+
id: 'event:schedule',
|
|
139
|
+
kind: 'event',
|
|
140
|
+
label: 'On a schedule',
|
|
141
|
+
says: 'Every weekday at 9:00, or any time a schedule says.',
|
|
142
|
+
example: {
|
|
143
|
+
type: 'schedule',
|
|
144
|
+
cron: '0 9 * * 1-5',
|
|
145
|
+
description: 'Every weekday at 9:00',
|
|
146
|
+
prompt: 'Summarise what came in since yesterday.',
|
|
147
|
+
},
|
|
148
|
+
enabled: true,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'event:email-received',
|
|
152
|
+
kind: 'event',
|
|
153
|
+
label: 'When an email arrives',
|
|
154
|
+
says: 'A message arriving in the inbox it reads.',
|
|
155
|
+
example: {
|
|
156
|
+
type: 'event',
|
|
157
|
+
event: 'email_received',
|
|
158
|
+
prompt: 'Read the email, and draft a reply for me to approve.',
|
|
159
|
+
},
|
|
160
|
+
enabled: true,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'event:once',
|
|
164
|
+
kind: 'event',
|
|
165
|
+
label: 'Once, at a moment',
|
|
166
|
+
says: 'One run, at a date and a time.',
|
|
167
|
+
example: {
|
|
168
|
+
type: 'once',
|
|
169
|
+
at: '2026-12-31T09:00:00',
|
|
170
|
+
prompt: 'Close the year: list what is still open.',
|
|
171
|
+
},
|
|
172
|
+
enabled: true,
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
export const CANVAS_PART_PLUGINS = [
|
|
176
|
+
partsPlugin('connections', 'Connections', CONNECTION_PARTS),
|
|
177
|
+
partsPlugin('skills', 'Skills', SKILL_PARTS),
|
|
178
|
+
partsPlugin('models', 'Models', MODEL_PARTS),
|
|
179
|
+
partsPlugin('rules', 'Rules', RULE_PARTS),
|
|
180
|
+
partsPlugin('tests', 'Tests', TEST_PARTS),
|
|
181
|
+
partsPlugin('events', 'Events', EVENT_PARTS),
|
|
182
|
+
];
|
|
183
|
+
/** The toolbox's reactor, its named plugins disabled; a name no plugin has is refused. */
|
|
184
|
+
export function canvasPartsReactor(disabled = [], plugins = CANVAS_PART_PLUGINS) {
|
|
185
|
+
const reactor = buildReactorFromPlugins(plugins);
|
|
186
|
+
reactor.start();
|
|
187
|
+
for (const name of disabled) {
|
|
188
|
+
if (!reactor.hasPlugin(name)) {
|
|
189
|
+
throw new Error(`No plugin of the toolbox is named "${name}"; they are ${plugins.map(plugin => plugin.name).join(', ') || 'none'}.`);
|
|
190
|
+
}
|
|
191
|
+
reactor.disable(name);
|
|
192
|
+
}
|
|
193
|
+
return reactor;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The toolbox for an organization's plugins off: the ids it turns off that
|
|
197
|
+
* name a source of parts (`skills`, …) take those parts off; the rest are the
|
|
198
|
+
* palette's.
|
|
199
|
+
*/
|
|
200
|
+
export function canvasPartsReactorOf(pluginsOff) {
|
|
201
|
+
const sources = new Set(CANVAS_PART_SOURCES);
|
|
202
|
+
return canvasPartsReactor(pluginsOff
|
|
203
|
+
.filter(id => sources.has(id))
|
|
204
|
+
.map(id => canvasPartsPluginName(id)));
|
|
205
|
+
}
|
|
206
|
+
/** What the toolbox offers: the parts the enabled plugins contribute, in their order. */
|
|
207
|
+
export function canvasPartsOf(reactor) {
|
|
208
|
+
return reactor.getContributions(LoopCanvasPart).map(entry => entry.value);
|
|
209
|
+
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* started_at, ended_at}` — which the AG-UI adapter hands on as an
|
|
9
9
|
* `activity`. A step is drawn as the chat draws a tool call: one row,
|
|
10
10
|
* collapsed, its input and output inside, running until it ends; a nested
|
|
11
|
-
* step says the step it is in.
|
|
11
|
+
* step says the step it is in. A step that streams its output (LOOP P-31)
|
|
12
|
+
* is said again as it writes, its output so far, and drawn in place.
|
|
12
13
|
*/
|
|
13
14
|
import type { DisplayItem, ToolCallMessage } from '../../types/chat';
|
|
14
15
|
/** The `CUSTOM` event's name. */
|
|
@@ -43,9 +43,8 @@ export function stepItem(step, inside) {
|
|
|
43
43
|
kind: step.kind,
|
|
44
44
|
...(step.input !== null ? { input: step.input } : {}),
|
|
45
45
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
: {}),
|
|
46
|
+
// Running, what it wrote so far — a step that streams (LOOP P-31).
|
|
47
|
+
...(!step.error && step.output !== null ? { result: step.output } : {}),
|
|
49
48
|
status: !step.ended ? 'executing' : step.error ? 'error' : 'complete',
|
|
50
49
|
...(step.error ? { error: step.error } : {}),
|
|
51
50
|
summary: inside ? `${kind}, inside ${inside}` : kind,
|
|
@@ -208,7 +208,8 @@ export function ToolCallDisplay({ toolCallId, toolName, args, result, status, er
|
|
|
208
208
|
wordBreak: 'break-word',
|
|
209
209
|
}, children: Object.keys(args).length > 0
|
|
210
210
|
? JSON.stringify(args, null, 2)
|
|
211
|
-
: `(${chatText.noParameters})` }) })] }), status === 'complete'
|
|
211
|
+
: `(${chatText.noParameters})` }) })] }), (status === 'complete' || status === 'executing') &&
|
|
212
|
+
result !== undefined && (_jsxs(Box, { children: [_jsx(Text, { sx: {
|
|
212
213
|
display: 'block',
|
|
213
214
|
fontSize: 0,
|
|
214
215
|
fontWeight: 'semibold',
|
package/lib/hooks/useAgUi.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { agUiRefusal, refusalBody } from '../protocols/agUiRefusal';
|
|
6
7
|
/**
|
|
7
8
|
* Hook to manage chat state with AG-UI protocol.
|
|
8
9
|
*
|
|
@@ -94,7 +95,9 @@ export function useAgUi(options = {}) {
|
|
|
94
95
|
body: JSON.stringify(requestBody),
|
|
95
96
|
});
|
|
96
97
|
if (!response.ok) {
|
|
97
|
-
|
|
98
|
+
// Why, in the page's own words (F-15): a 404 at an agent's AG-UI
|
|
99
|
+
// address means the agent is not on this runtime.
|
|
100
|
+
throw new Error(agUiRefusal(response.status, response.statusText, await refusalBody(response)));
|
|
98
101
|
}
|
|
99
102
|
// Handle streaming response
|
|
100
103
|
const reader = response.body?.getReader();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { generateMessageId, createAssistantMessage } from '../types/messages';
|
|
6
|
+
import { agUiRefusal, refusalBody } from './agUiRefusal';
|
|
6
7
|
import { BaseProtocolAdapter } from './BaseProtocolAdapter';
|
|
7
8
|
/**
|
|
8
9
|
* AG-UI protocol adapter
|
|
@@ -214,7 +215,10 @@ export class AGUIAdapter extends BaseProtocolAdapter {
|
|
|
214
215
|
signal: controller.signal,
|
|
215
216
|
});
|
|
216
217
|
if (!response.ok) {
|
|
217
|
-
|
|
218
|
+
// Why, in the page's own words (F-15): a 404 at an agent's AG-UI
|
|
219
|
+
// address means the agent is not on this runtime, which the status
|
|
220
|
+
// alone never said.
|
|
221
|
+
throw new Error(agUiRefusal(response.status, response.statusText, await refusalBody(response)));
|
|
218
222
|
}
|
|
219
223
|
// Parse SSE stream
|
|
220
224
|
await this.parseSSEStream(response);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why an AG-UI run was not answered, in the page's own words (LOOP F-15).
|
|
3
|
+
*
|
|
4
|
+
* What a person met before was `AG-UI request failed: 404` — the status and
|
|
5
|
+
* nothing else. A 404 at an agent's AG-UI address has one meaning: the agent
|
|
6
|
+
* is not on the runtime the page is addressing. The commonest way to get
|
|
7
|
+
* there is to open an application whose agent runs its code in the browser:
|
|
8
|
+
* the runtime refuses to start it (`browser_sandbox_refusal`, 422), the page
|
|
9
|
+
* addresses it anyway, and the address answers nothing. So the page says
|
|
10
|
+
* that, and says what to do about it.
|
|
11
|
+
*
|
|
12
|
+
* When the server said why — a refusal of the platform's own, carried as
|
|
13
|
+
* `detail` — that sentence is the better one and is used instead: it is
|
|
14
|
+
* about this agent, where ours is about any.
|
|
15
|
+
*
|
|
16
|
+
* Pure, so the hook, the adapter and the tests read a refusal the same way
|
|
17
|
+
* (the repo's jest/vitest rule: rules live outside components).
|
|
18
|
+
*
|
|
19
|
+
* @module protocols/agUiRefusal
|
|
20
|
+
*/
|
|
21
|
+
/** What the page says when an agent's AG-UI address answers nothing. */
|
|
22
|
+
export declare const AG_UI_NOT_ON_RUNTIME: string;
|
|
23
|
+
/** What the page says when the address refused the caller. */
|
|
24
|
+
export declare const AG_UI_NOT_ALLOWED: string;
|
|
25
|
+
/**
|
|
26
|
+
* The `detail` a platform refusal carries, or `''`.
|
|
27
|
+
*
|
|
28
|
+
* A body that is not JSON — an HTML 404 from the page's own origin, which is
|
|
29
|
+
* what a page addressing a runtime it never got answers — carries none.
|
|
30
|
+
*/
|
|
31
|
+
export declare function refusalDetail(body: string | undefined): string;
|
|
32
|
+
/**
|
|
33
|
+
* Why the run was not answered, as the page says it.
|
|
34
|
+
*
|
|
35
|
+
* @param status - The status the address answered with.
|
|
36
|
+
* @param statusText - Its text, for a status we have nothing better for.
|
|
37
|
+
* @param body - What came back, when it was read; a refusal's `detail` wins.
|
|
38
|
+
*/
|
|
39
|
+
export declare function agUiRefusal(status: number, statusText: string, body?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Read a refusal's body without letting the reading itself throw: a body
|
|
42
|
+
* already consumed, or a connection that went away, leaves us with the
|
|
43
|
+
* status alone, which is still better than nothing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function refusalBody(response: {
|
|
46
|
+
text: () => Promise<string>;
|
|
47
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Why an AG-UI run was not answered, in the page's own words (LOOP F-15).
|
|
7
|
+
*
|
|
8
|
+
* What a person met before was `AG-UI request failed: 404` — the status and
|
|
9
|
+
* nothing else. A 404 at an agent's AG-UI address has one meaning: the agent
|
|
10
|
+
* is not on the runtime the page is addressing. The commonest way to get
|
|
11
|
+
* there is to open an application whose agent runs its code in the browser:
|
|
12
|
+
* the runtime refuses to start it (`browser_sandbox_refusal`, 422), the page
|
|
13
|
+
* addresses it anyway, and the address answers nothing. So the page says
|
|
14
|
+
* that, and says what to do about it.
|
|
15
|
+
*
|
|
16
|
+
* When the server said why — a refusal of the platform's own, carried as
|
|
17
|
+
* `detail` — that sentence is the better one and is used instead: it is
|
|
18
|
+
* about this agent, where ours is about any.
|
|
19
|
+
*
|
|
20
|
+
* Pure, so the hook, the adapter and the tests read a refusal the same way
|
|
21
|
+
* (the repo's jest/vitest rule: rules live outside components).
|
|
22
|
+
*
|
|
23
|
+
* @module protocols/agUiRefusal
|
|
24
|
+
*/
|
|
25
|
+
/** What the page says when an agent's AG-UI address answers nothing. */
|
|
26
|
+
export const AG_UI_NOT_ON_RUNTIME = 'This agent is not on the runtime the page is addressing: its AG-UI address answered nothing. ' +
|
|
27
|
+
'An agent that runs its code in the browser is never started on a runtime — run it in the page, ' +
|
|
28
|
+
'or give it a sandbox a runtime has (eval, jupyter-server).';
|
|
29
|
+
/** What the page says when the address refused the caller. */
|
|
30
|
+
export const AG_UI_NOT_ALLOWED = 'This agent did not answer the page: the run was refused. ' +
|
|
31
|
+
'Sign in again, or open the application from its own address.';
|
|
32
|
+
/**
|
|
33
|
+
* The `detail` a platform refusal carries, or `''`.
|
|
34
|
+
*
|
|
35
|
+
* A body that is not JSON — an HTML 404 from the page's own origin, which is
|
|
36
|
+
* what a page addressing a runtime it never got answers — carries none.
|
|
37
|
+
*/
|
|
38
|
+
export function refusalDetail(body) {
|
|
39
|
+
if (!body) {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
let said;
|
|
43
|
+
try {
|
|
44
|
+
said = JSON.parse(body);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
if (!said || typeof said !== 'object') {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
const detail = said.detail;
|
|
53
|
+
return typeof detail === 'string' ? detail.trim() : '';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Why the run was not answered, as the page says it.
|
|
57
|
+
*
|
|
58
|
+
* @param status - The status the address answered with.
|
|
59
|
+
* @param statusText - Its text, for a status we have nothing better for.
|
|
60
|
+
* @param body - What came back, when it was read; a refusal's `detail` wins.
|
|
61
|
+
*/
|
|
62
|
+
export function agUiRefusal(status, statusText, body) {
|
|
63
|
+
const detail = refusalDetail(body);
|
|
64
|
+
if (detail) {
|
|
65
|
+
return detail;
|
|
66
|
+
}
|
|
67
|
+
if (status === 404) {
|
|
68
|
+
return AG_UI_NOT_ON_RUNTIME;
|
|
69
|
+
}
|
|
70
|
+
if (status === 401 || status === 403) {
|
|
71
|
+
return AG_UI_NOT_ALLOWED;
|
|
72
|
+
}
|
|
73
|
+
return `AG-UI request failed: ${status} ${statusText}`.trim();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read a refusal's body without letting the reading itself throw: a body
|
|
77
|
+
* already consumed, or a connection that went away, leaves us with the
|
|
78
|
+
* status alone, which is still better than nothing.
|
|
79
|
+
*/
|
|
80
|
+
export async function refusalBody(response) {
|
|
81
|
+
try {
|
|
82
|
+
return await response.text();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -20,6 +20,7 @@ export declare const EXAMPLE_AI_EXPLAINS_NOTEBOOK_OUTPUT_AGENTSPEC_0_0_1: Agents
|
|
|
20
20
|
export declare const EXAMPLE_AI_WRITES_PANDAS_CODE_AGENTSPEC_0_0_1: Agentspec;
|
|
21
21
|
export declare const EXAMPLE_ANALYZE_EXCEL_SPREADSHEET_AGENTSPEC_0_0_1: Agentspec;
|
|
22
22
|
export declare const EXAMPLE_BACKEND_TOOL_RENDERING_AGENTSPEC_0_0_1: Agentspec;
|
|
23
|
+
export declare const EXAMPLE_BLANK_AGENTSPEC_0_0_1: Agentspec;
|
|
23
24
|
export declare const EXAMPLE_BUILD_NOTEBOOK_WITH_ONE_PROMPT_AGENTSPEC_0_0_1: Agentspec;
|
|
24
25
|
export declare const EXAMPLE_CHECKPOINTS_AGENTSPEC_0_0_1: Agentspec;
|
|
25
26
|
export declare const EXAMPLE_CLOUDFLARE_AGENTSPEC_0_0_1: Agentspec;
|
|
@@ -1086,6 +1086,54 @@ Use the \`get_weather\` tool to fetch current weather data.
|
|
|
1086
1086
|
parameters: undefined,
|
|
1087
1087
|
subagents: undefined,
|
|
1088
1088
|
};
|
|
1089
|
+
export const EXAMPLE_BLANK_AGENTSPEC_0_0_1 = {
|
|
1090
|
+
id: 'example-blank',
|
|
1091
|
+
version: '0.0.1',
|
|
1092
|
+
name: 'A Blank Agent',
|
|
1093
|
+
description: `A model and nothing else: no prompt of its own, no MCP servers, no skills, no tools. The application that runs it says who it is in its instructions, and gives it its tools — so an app.py runs it in its own process, as on a runtime.`,
|
|
1094
|
+
tags: ['workflow'],
|
|
1095
|
+
domain: undefined,
|
|
1096
|
+
enabled: true,
|
|
1097
|
+
model: 'bedrock:us.anthropic.claude-sonnet-4-6',
|
|
1098
|
+
modelAdditionals: ['alibaba:qwen-max'],
|
|
1099
|
+
mcpServers: [],
|
|
1100
|
+
skills: [].filter(Boolean),
|
|
1101
|
+
backendTools: [],
|
|
1102
|
+
frontendTools: [],
|
|
1103
|
+
environmentName: 'ai-agents-env',
|
|
1104
|
+
icon: 'agent',
|
|
1105
|
+
emoji: '⬜',
|
|
1106
|
+
color: '#6B7280',
|
|
1107
|
+
suggestions: [],
|
|
1108
|
+
welcomeMessage: undefined,
|
|
1109
|
+
welcomeNotebook: undefined,
|
|
1110
|
+
welcomeDocument: undefined,
|
|
1111
|
+
sandboxVariant: 'jupyter-server',
|
|
1112
|
+
harness: 'pydantic-ai',
|
|
1113
|
+
systemPrompt: undefined,
|
|
1114
|
+
systemPromptCodemodeAddons: undefined,
|
|
1115
|
+
goal: undefined,
|
|
1116
|
+
delegable: [],
|
|
1117
|
+
protocol: undefined,
|
|
1118
|
+
uiPlugin: undefined,
|
|
1119
|
+
trigger: undefined,
|
|
1120
|
+
modelConfig: undefined,
|
|
1121
|
+
mcpServerTools: undefined,
|
|
1122
|
+
guardrails: undefined,
|
|
1123
|
+
evals: undefined,
|
|
1124
|
+
codemode: undefined,
|
|
1125
|
+
output: undefined,
|
|
1126
|
+
advanced: undefined,
|
|
1127
|
+
checkpoints: undefined,
|
|
1128
|
+
authorizationPolicy: undefined,
|
|
1129
|
+
notifications: undefined,
|
|
1130
|
+
memory: 'ephemeral',
|
|
1131
|
+
preHooks: undefined,
|
|
1132
|
+
postHooks: undefined,
|
|
1133
|
+
toolHooks: undefined,
|
|
1134
|
+
parameters: undefined,
|
|
1135
|
+
subagents: undefined,
|
|
1136
|
+
};
|
|
1089
1137
|
export const EXAMPLE_BUILD_NOTEBOOK_WITH_ONE_PROMPT_AGENTSPEC_0_0_1 = {
|
|
1090
1138
|
id: 'example-build-notebook-with-one-prompt',
|
|
1091
1139
|
version: '0.0.1',
|
|
@@ -13276,6 +13324,7 @@ export const AGENTSPECS = {
|
|
|
13276
13324
|
'example-ai-writes-pandas-code': EXAMPLE_AI_WRITES_PANDAS_CODE_AGENTSPEC_0_0_1,
|
|
13277
13325
|
'example-analyze-excel-spreadsheet': EXAMPLE_ANALYZE_EXCEL_SPREADSHEET_AGENTSPEC_0_0_1,
|
|
13278
13326
|
'example-backend-tool-rendering': EXAMPLE_BACKEND_TOOL_RENDERING_AGENTSPEC_0_0_1,
|
|
13327
|
+
'example-blank': EXAMPLE_BLANK_AGENTSPEC_0_0_1,
|
|
13279
13328
|
'example-build-notebook-with-one-prompt': EXAMPLE_BUILD_NOTEBOOK_WITH_ONE_PROMPT_AGENTSPEC_0_0_1,
|
|
13280
13329
|
'example-checkpoints': EXAMPLE_CHECKPOINTS_AGENTSPEC_0_0_1,
|
|
13281
13330
|
'example-cloudflare': EXAMPLE_CLOUDFLARE_AGENTSPEC_0_0_1,
|