@datalayer/core 1.2.27 → 1.2.29

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.
Files changed (80) hide show
  1. package/lib/api/evals/__tests__/external-definition.golden.json +110 -0
  2. package/lib/api/evals/client.d.ts +13 -1
  3. package/lib/api/evals/client.js +13 -0
  4. package/lib/api/evals/types.d.ts +168 -1
  5. package/lib/api/mcp/agentAccess.d.ts +10 -0
  6. package/lib/api/mcp/agentAccess.js +28 -0
  7. package/lib/api/mcp/generated.js +20 -0
  8. package/lib/api/mcp/index.d.ts +1 -0
  9. package/lib/api/mcp/index.js +1 -0
  10. package/lib/api/mcp/observability.d.ts +1 -1
  11. package/lib/api/mcp/observability.js +2 -0
  12. package/lib/api/mcp/operations.d.ts +28 -10
  13. package/lib/api/mcp/tasks.d.ts +9 -1
  14. package/lib/api/otel/firewall.d.ts +74 -0
  15. package/lib/api/otel/firewall.js +42 -0
  16. package/lib/api/otel/index.d.ts +1 -0
  17. package/lib/api/otel/index.js +1 -0
  18. package/lib/cli-login.js +2 -2
  19. package/lib/components/anonymous/AnonymousKeyExpired.js +15 -3
  20. package/lib/components/index.d.ts +1 -0
  21. package/lib/components/index.js +1 -0
  22. package/lib/components/mcp/ScopeList.js +10 -1
  23. package/lib/components/principal/PrincipalSwitcherMenu.js +4 -4
  24. package/lib/components/sharing/ShareAccessComponent.js +33 -8
  25. package/lib/components/toast/ToastPanel.d.ts +11 -0
  26. package/lib/components/toast/ToastPanel.js +42 -0
  27. package/lib/components/toast/index.d.ts +2 -0
  28. package/lib/components/toast/index.js +6 -0
  29. package/lib/components/toast/toastQueue.d.ts +35 -0
  30. package/lib/components/toast/toastQueue.js +47 -0
  31. package/lib/hooks/index.d.ts +1 -0
  32. package/lib/hooks/index.js +1 -0
  33. package/lib/hooks/useCache.d.ts +4 -2
  34. package/lib/hooks/useCache.js +13 -4
  35. package/lib/hooks/useDatalayer.js +12 -16
  36. package/lib/hooks/useKeptScroll.d.ts +6 -0
  37. package/lib/hooks/useKeptScroll.js +54 -0
  38. package/lib/hooks/useMcp.d.ts +2 -2
  39. package/lib/hooks/useMcp.js +5 -2
  40. package/lib/hooks/useNavigate.d.ts +16 -2
  41. package/lib/hooks/useNavigate.js +65 -5
  42. package/lib/hooks/useToast.d.ts +6 -0
  43. package/lib/hooks/useToast.js +40 -1
  44. package/lib/models/McpAgentAccess.d.ts +95 -0
  45. package/lib/models/McpAgentAccess.js +5 -0
  46. package/lib/models/Usage.d.ts +13 -0
  47. package/lib/models/Usage.js +5 -0
  48. package/lib/models/index.d.ts +1 -0
  49. package/lib/models/index.js +1 -0
  50. package/lib/otel/client/OtelClient.d.ts +56 -0
  51. package/lib/otel/client/OtelClient.js +25 -0
  52. package/lib/otel/client/index.d.ts +1 -1
  53. package/lib/otel/index.d.ts +1 -1
  54. package/lib/otel/views/OtelLive.js +3 -3
  55. package/lib/otel/views/OtelLiveSpans.js +2 -2
  56. package/lib/otel/views/OtelLogsList.js +3 -3
  57. package/lib/otel/views/OtelMetricsChart.d.ts +13 -0
  58. package/lib/otel/views/OtelMetricsChart.js +109 -26
  59. package/lib/otel/views/OtelSpanDetail.js +3 -3
  60. package/lib/otel/views/OtelSqlView.js +4 -4
  61. package/lib/otel/views/OtelSystemView.js +2 -2
  62. package/lib/otel/views/OtelTimelineRangeSlider.js +2 -2
  63. package/lib/otel/views/OtelTracesList.js +2 -2
  64. package/lib/state/substates/LayoutState.js +24 -3
  65. package/lib/state/substates/NavigationState.d.ts +11 -3
  66. package/lib/state/substates/NavigationState.js +48 -5
  67. package/lib/views/mcp/AuditLog.js +1 -1
  68. package/lib/views/mcp/ConnectedAgents.d.ts +1 -1
  69. package/lib/views/mcp/ConnectedAgents.js +13 -10
  70. package/lib/views/mcp/McpDashboard.d.ts +1 -1
  71. package/lib/views/mcp/McpDashboard.js +6 -6
  72. package/lib/views/mcp/McpHome.d.ts +3 -1
  73. package/lib/views/mcp/McpHome.js +19 -9
  74. package/lib/views/mcp/McpObservability.d.ts +22 -3
  75. package/lib/views/mcp/McpObservability.js +65 -17
  76. package/lib/views/mcp/Policies.js +1 -1
  77. package/lib/views/mcp/RunDetail.js +2 -2
  78. package/lib/views/mcp/types.d.ts +8 -4
  79. package/lib/views/otel/OtelHeader.js +2 -2
  80. package/package.json +2 -2
@@ -67,23 +67,19 @@ export function useDatalayer(props = {}) {
67
67
  }
68
68
  else {
69
69
  if (notifyOnError_ && !isForbidden) {
70
- if (responseError.warnings) {
71
- responseError.warnings.forEach(warning => enqueueToast(`${warning}`, { variant: 'warning' }));
72
- }
73
- if (responseError.errors) {
74
- responseError.errors.forEach(error => enqueueToast(`${error}`, { variant: 'error' }));
75
- }
76
- if (responseError.message) {
77
- enqueueToast(`${responseError.message}`, { variant: 'error' });
78
- }
79
- else {
80
- enqueueToast(`API Error`, { variant: 'error' });
81
- }
82
- if (responseError.exceptionMessage) {
83
- enqueueToast(`${responseError.exceptionMessage}`, {
84
- variant: 'error',
85
- });
70
+ // One toast per failed request, its lines together and each once
71
+ // (2 November 2026: its message and its exception were a toast
72
+ // each, so a page that asked twice showed four).
73
+ const once = (lines) => Array.from(new Set(lines.map(line => `${line ?? ''}`.trim()).filter(Boolean))).join('\n');
74
+ const warnings = once(responseError.warnings ?? []);
75
+ if (warnings) {
76
+ enqueueToast(warnings, { variant: 'warning' });
86
77
  }
78
+ enqueueToast(once([
79
+ ...(responseError.errors ?? []),
80
+ responseError.message || 'API Error',
81
+ responseError.exceptionMessage,
82
+ ]), { variant: 'error' });
87
83
  }
88
84
  }
89
85
  const response = {
@@ -0,0 +1,6 @@
1
+ /** The nearest ancestor of `marker` that scrolls vertically, if any. */
2
+ export declare function scrollingAncestor(marker: HTMLElement | null): HTMLElement | null;
3
+ /** Where the sidebar kept under `key` was last scrolled to, if it was. */
4
+ export declare const keptScrollOf: (key: string) => number | undefined;
5
+ export declare function useKeptScroll<T extends HTMLElement = HTMLElement>(key: string): import("react").RefObject<T | null>;
6
+ export default useKeptScroll;
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright (c) 2023-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A sidebar keeps its scroll across pages: an entry pressed far down the list
7
+ * leaves the list where it was, so the next one is still under the pointer.
8
+ *
9
+ * `useKeptScroll(key)` returns a ref for a small element placed inside the
10
+ * sidebar; the nearest ancestor that scrolls is the one kept. Its position is
11
+ * remembered under `key` while it scrolls, and given back when a sidebar with
12
+ * the same key is mounted again (a layout rendered anew by the next route).
13
+ * The panel must not be one `useNavigate` brings to the top: it is not marked
14
+ * `data-scroll-root`.
15
+ *
16
+ * @module hooks/useKeptScroll
17
+ */
18
+ import { useLayoutEffect, useRef } from 'react';
19
+ const kept = new Map();
20
+ const scrollsY = (element) => {
21
+ const overflow = window.getComputedStyle(element).overflowY;
22
+ return overflow === 'auto' || overflow === 'scroll';
23
+ };
24
+ /** The nearest ancestor of `marker` that scrolls vertically, if any. */
25
+ export function scrollingAncestor(marker) {
26
+ let element = marker?.parentElement ?? null;
27
+ while (element && !scrollsY(element)) {
28
+ element = element.parentElement;
29
+ }
30
+ return element;
31
+ }
32
+ /** Where the sidebar kept under `key` was last scrolled to, if it was. */
33
+ export const keptScrollOf = (key) => kept.get(key);
34
+ export function useKeptScroll(key) {
35
+ const ref = useRef(null);
36
+ useLayoutEffect(() => {
37
+ const panel = scrollingAncestor(ref.current);
38
+ if (!panel) {
39
+ return;
40
+ }
41
+ const saved = kept.get(key);
42
+ if (saved !== undefined) {
43
+ panel.scrollTop = saved;
44
+ }
45
+ const remember = () => kept.set(key, panel.scrollTop);
46
+ panel.addEventListener('scroll', remember, { passive: true });
47
+ return () => {
48
+ remember();
49
+ panel.removeEventListener('scroll', remember);
50
+ };
51
+ }, [key]);
52
+ return ref;
53
+ }
54
+ export default useKeptScroll;
@@ -1,4 +1,4 @@
1
- import { type McpTaskAnswered, type McpActivity, type McpActivityFilters, type McpAuditFilters, type McpBindingListFilters, type McpGatewayVersion, type McpMetricsFilters, type McpMetricsSnapshot, type McpOrganizationOverview, type McpOrganizationUsage, type McpOrganizationOverviewFilters, type McpPolicyFilters, type McpRunLogs, type McpRunTrace, type McpTaskEvent, type McpTaskListFilters, type McpWorkerList, type McpWorkflowsHealth } from '../api/mcp';
1
+ import { type McpTaskAnswered, type McpTaskCancelled, type McpActivity, type McpActivityFilters, type McpAuditFilters, type McpBindingListFilters, type McpGatewayVersion, type McpMetricsFilters, type McpMetricsSnapshot, type McpOrganizationOverview, type McpOrganizationUsage, type McpOrganizationOverviewFilters, type McpPolicyFilters, type McpRunLogs, type McpRunTrace, type McpTaskEvent, type McpTaskListFilters, type McpWorkerList, type McpWorkflowsHealth } from '../api/mcp';
2
2
  import { type ConnectedAgent } from '../api/iam/connectedAgents';
3
3
  import { type OrganizationTeam } from '../api/iam/organizationTeams';
4
4
  import { setAuditSettings, type McpAuditSettings } from '../api/iam/mcpAuditSettings';
@@ -28,7 +28,7 @@ export declare const useTaskEvents: (taskUid?: string, options?: {
28
28
  enabled?: boolean;
29
29
  onEvent?: (event: McpTaskEvent) => void;
30
30
  }) => void;
31
- export declare const useCancelTask: () => import("@tanstack/react-query").UseMutationResult<McpTask, Error, string, unknown>;
31
+ export declare const useCancelTask: () => import("@tanstack/react-query").UseMutationResult<McpTaskCancelled, Error, string, unknown>;
32
32
  export declare const useAnswerTask: () => import("@tanstack/react-query").UseMutationResult<McpTaskAnswered, Error, {
33
33
  taskUid: string;
34
34
  input: Record<string, unknown>;
@@ -124,8 +124,11 @@ export const useCancelTask = () => {
124
124
  const mcpUrl = useMcpServerUrl();
125
125
  return useMutation({
126
126
  mutationFn: taskUid => cancelTask(token ?? '', taskUid, mcpUrl),
127
- onSuccess: task => {
128
- queryClient.setQueryData(queryKeys.mcp.task(task.uid), task);
127
+ // The gateway answers `{uid, cancelled, status}`, not the task: read the
128
+ // task again rather than caching that reply in its place (the run page
129
+ // lost its tool and output on a cancel, as it did on an answer).
130
+ onSuccess: (_cancelled, taskUid) => {
131
+ queryClient.invalidateQueries({ queryKey: queryKeys.mcp.task(taskUid) });
129
132
  queryClient.invalidateQueries({ queryKey: queryKeys.mcp.tasks() });
130
133
  queryClient.invalidateQueries({ queryKey: queryKeys.mcp.activity() });
131
134
  },
@@ -5,8 +5,15 @@ export interface NavigateOptions {
5
5
  state?: unknown;
6
6
  /** Open the destination in a new tab and leave this one where it is. */
7
7
  newTab?: boolean;
8
- /** Scroll to the top on arrival; on by default. */
9
- scroll?: boolean;
8
+ /**
9
+ * Where the reader lands: the top of the page when unsaid or `true`; an
10
+ * element, by its id (with or without its `#`), scrolled into view once
11
+ * the page has it; `false` to leave the scroll where it is. The page and
12
+ * every scroll panel marked `data-scroll-root` are scrolled, so a layout
13
+ * that scrolls inside its own panel lands at the top too (the user,
14
+ * 6 November 2026: the Trust page opened scrolled down).
15
+ */
16
+ scroll?: boolean | string;
10
17
  /**
11
18
  * Close the layout's side portals on the way; on by default. Only the
12
19
  * history API host has them.
@@ -40,5 +47,12 @@ export type Navigate = (to: NavigateTarget, optionsOrEvent?: NavigateOptions | N
40
47
  export declare function isNewTabClick(event: NavigateEvent): boolean;
41
48
  /** Open `to` in a new tab, without handing it this window. */
42
49
  export declare function openInNewTab(to: string): void;
50
+ /**
51
+ * Scroll on arrival: to the top (`undefined` or `true`), to an element by
52
+ * its id (a string, `#` or not), or nowhere (`false`). Done now and again
53
+ * on the next frames, since the page navigated to renders after the call
54
+ * and a layout's panel keeps its scroll across pages.
55
+ */
56
+ export declare function scrollOnArrival(target: boolean | string | undefined): void;
43
57
  export declare const useNavigate: () => Navigate;
44
58
  export default useNavigate;
@@ -62,6 +62,68 @@ function isLinkTo(event, to) {
62
62
  return false;
63
63
  }
64
64
  }
65
+ /**
66
+ * The scroll panels a layout marks, beside the page itself: `data-scroll-root`,
67
+ * or the `dla-scroll-root` class for a component that passes a class name but
68
+ * no data attribute (Primer's `PageLayout.Content`, whose own element is the
69
+ * one that scrolls; 9 November 2026, a card on /agentspecs/identities opened
70
+ * its page scrolled down).
71
+ */
72
+ const scrollRoots = () => Array.from(document.querySelectorAll('[data-scroll-root], .dla-scroll-root'));
73
+ const nextFrame = (run) => {
74
+ if (typeof requestAnimationFrame === 'function') {
75
+ requestAnimationFrame(run);
76
+ }
77
+ else {
78
+ setTimeout(run, 16);
79
+ }
80
+ };
81
+ /** How many frames an element is waited for: a page loaded lazily takes a few. */
82
+ const ARRIVAL_FRAMES = 60;
83
+ /**
84
+ * Scroll on arrival: to the top (`undefined` or `true`), to an element by
85
+ * its id (a string, `#` or not), or nowhere (`false`). Done now and again
86
+ * on the next frames, since the page navigated to renders after the call
87
+ * and a layout's panel keeps its scroll across pages.
88
+ */
89
+ export function scrollOnArrival(target) {
90
+ if (target === false || typeof document === 'undefined') {
91
+ return;
92
+ }
93
+ const id = typeof target === 'string' ? target.replace(/^#/, '') : '';
94
+ if (!id) {
95
+ const toTop = () => {
96
+ window.scrollTo(0, 0);
97
+ document.body.scrollTop = 0;
98
+ document.documentElement.scrollTop = 0;
99
+ for (const root of scrollRoots()) {
100
+ root.scrollTop = 0;
101
+ }
102
+ };
103
+ toTop();
104
+ let frames = 0;
105
+ const again = () => {
106
+ toTop();
107
+ if (++frames < 2) {
108
+ nextFrame(again);
109
+ }
110
+ };
111
+ nextFrame(again);
112
+ return;
113
+ }
114
+ let tries = 0;
115
+ const attempt = () => {
116
+ const element = document.getElementById(id);
117
+ if (element) {
118
+ element.scrollIntoView({ block: 'start' });
119
+ return;
120
+ }
121
+ if (++tries < ARRIVAL_FRAMES) {
122
+ nextFrame(attempt);
123
+ }
124
+ };
125
+ attempt();
126
+ }
65
127
  const isEvent = (value) => !!value && typeof value.preventDefault === 'function';
66
128
  function parseCall(optionsOrEvent, third, fourth) {
67
129
  if (isEvent(optionsOrEvent)) {
@@ -129,10 +191,7 @@ export const useNavigate = () => {
129
191
  return;
130
192
  }
131
193
  event?.preventDefault();
132
- if (options.scroll !== false) {
133
- window.scrollTo(0, 0);
134
- document.body.scrollTop = 0;
135
- }
194
+ scrollOnArrival(options.scroll);
136
195
  // Where the reader is now, for the shell they are in. A new tab and a
137
196
  // step back through history are both already past by here. Only for
138
197
  // somebody signed in: the shells are theirs, and a page a visitor read
@@ -152,7 +211,8 @@ export const useNavigate = () => {
152
211
  nativeNavigate(to, {
153
212
  replace: options.replace,
154
213
  state: options.state,
155
- scroll: options.scroll,
214
+ // Scrolled above, after the page has rendered.
215
+ scroll: false,
156
216
  });
157
217
  }, [rrNavigate, nativeNavigate, layoutStore]);
158
218
  };
@@ -14,6 +14,12 @@ export type ToastProps = {
14
14
  */
15
15
  variant: 'info' | 'success' | 'warning' | 'error';
16
16
  };
17
+ /**
18
+ * Toasts in JupyterLab, as they always were: its notifications, or
19
+ * react-toastify until they are loaded. Everywhere else (the SaaS, the
20
+ * desktop…) they go to `ToastPanel`, primer-addons' sliding panel, which the
21
+ * application mounts once (2 November 2026).
22
+ */
17
23
  export declare const useToast: () => Readonly<{
18
24
  /**
19
25
  * Emit a message as a toast.
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { toast } from 'react-toastify';
4
4
  import { Button } from '@primer/react';
5
5
  import { isInsideJupyterLab } from '../utils/index.js';
6
+ import { defaultAutoClose, toastQueue } from '../components/toast/toastQueue.js';
6
7
  // `@datalayer/jupyter-react` and `@jupyterlab/apputils` are heavy (they pull
7
8
  // JupyterLab / CodeMirror / lumino) and are only needed when running INSIDE
8
9
  // JupyterLab. They are lazily loaded here so the standalone bundle (landing app)
@@ -76,11 +77,25 @@ function createContent(message, closeHandler, actions) {
76
77
  return (_jsx(React.Suspense, { fallback: null, children: _jsx(JupyterReactTheme, { children: _jsx(ToastButton, { action: action, closeToast: closeHandler }) }) }, 'button-' + idx));
77
78
  })] }))] }));
78
79
  }
80
+ /**
81
+ * Toasts in JupyterLab, as they always were: its notifications, or
82
+ * react-toastify until they are loaded. Everywhere else (the SaaS, the
83
+ * desktop…) they go to `ToastPanel`, primer-addons' sliding panel, which the
84
+ * application mounts once (2 November 2026).
85
+ */
79
86
  export const useToast = () => {
80
87
  // This CANNOT use Jupyter React hooks as it may be used outside Jupyter React context.
81
88
  const insideJupyterLab = isInsideJupyterLab();
82
89
  const enqueueToast = (message, options = { variant: 'info' }) => {
83
90
  const { actions, autoClose } = options;
91
+ if (!insideJupyterLab) {
92
+ return toastQueue.push({
93
+ variant: options.variant,
94
+ message,
95
+ actions,
96
+ autoClose: autoClose ?? defaultAutoClose(options.variant),
97
+ });
98
+ }
84
99
  const theme = resolveToastTheme();
85
100
  switch (options.variant) {
86
101
  case 'info': {
@@ -138,6 +153,27 @@ export const useToast = () => {
138
153
  }
139
154
  };
140
155
  const trackAsyncTask = (promise, options) => {
156
+ if (!insideJupyterLab) {
157
+ // One toast for the task: pending, then what it came to.
158
+ const message = (value, result) => typeof value === 'function'
159
+ ? String(value(result))
160
+ : String(value);
161
+ const id = toastQueue.push({
162
+ variant: 'info',
163
+ message: message(options.pending.message, undefined),
164
+ autoClose: false,
165
+ });
166
+ promise.then(result => toastQueue.update(id, {
167
+ variant: 'success',
168
+ message: message(options.success.message, result),
169
+ autoClose: defaultAutoClose('success'),
170
+ }), reason => toastQueue.update(id, {
171
+ variant: 'error',
172
+ message: message(options.error.message, reason),
173
+ autoClose: defaultAutoClose('error'),
174
+ }));
175
+ return id;
176
+ }
141
177
  const theme = resolveToastTheme();
142
178
  return insideJupyterLab && NotificationApi
143
179
  ? NotificationApi.promise(promise, options)
@@ -169,7 +205,10 @@ export const useToast = () => {
169
205
  * @param id Toast id
170
206
  */
171
207
  dismiss: (id) => {
172
- if (insideJupyterLab && NotificationApi) {
208
+ if (!insideJupyterLab) {
209
+ toastQueue.dismiss(id === undefined ? undefined : String(id));
210
+ }
211
+ else if (insideJupyterLab && NotificationApi) {
173
212
  NotificationApi.dismiss(id);
174
213
  }
175
214
  else {
@@ -0,0 +1,95 @@
1
+ /**
2
+ * What one agent may execute and reach, as the Jupyter MCP Server decides it.
3
+ *
4
+ * Every answer here comes from the steps a call goes through — the token's
5
+ * scopes, the policy layers, then Spacer or Contents for the object — so a
6
+ * tool or a notebook listed as refused is one the agent would be refused.
7
+ *
8
+ * @module models/McpAgentAccess
9
+ */
10
+ /** The agent an answer is about. `grant` is null for the token that asked. */
11
+ export interface McpAgentNamed {
12
+ grant: string | null;
13
+ clientId: string | null;
14
+ clientName: string | null;
15
+ scopes: string[];
16
+ orgUid: string | null;
17
+ teamUid: string | null;
18
+ }
19
+ export type McpToolsetHealthState = 'ready' | 'degraded' | 'unknown';
20
+ export interface McpToolsetHealth {
21
+ /** `unknown` is a toolset nothing here measures, never one reported well. */
22
+ state: McpToolsetHealthState;
23
+ detail: string;
24
+ }
25
+ /**
26
+ * How a client comes to hold a toolset: the plain endpoint serves `base`, a
27
+ * URL names an `optional` one, a `source` is enabled in a session, and an
28
+ * `application` one is reached through an application's connection.
29
+ */
30
+ export type McpToolsetSelected = 'base' | 'optional' | 'source' | 'application';
31
+ /** One tool, decided for this agent. */
32
+ export interface McpAgentTool {
33
+ name: string;
34
+ /** The scopes it requires, space-separated. */
35
+ scope: string;
36
+ allowed: boolean;
37
+ /** Why not: `scope`, a policy rule, `connection` or `item`. */
38
+ rule: string | null;
39
+ /** Which layer decided. */
40
+ decidedBy: string | null;
41
+ detail: string | null;
42
+ /** The layer that makes a person approve a call first, when one does. */
43
+ approval: string | null;
44
+ }
45
+ /** A source's own rule for its calls, as Contents keeps it. */
46
+ export type McpSourceApprovalPolicy = 'explicit' | 'auto-allowlisted' | 'never';
47
+ export interface McpAgentToolset {
48
+ kind: 'toolset' | 'source';
49
+ /** The source, when it is one. */
50
+ uid?: string;
51
+ name: string;
52
+ title: string;
53
+ description: string | null;
54
+ selected: McpToolsetSelected;
55
+ health: McpToolsetHealth;
56
+ /** A source's; a toolset's tools each say their own `approval`. */
57
+ approvalPolicy: McpSourceApprovalPolicy | null;
58
+ tools: McpAgentTool[];
59
+ }
60
+ export interface McpAgentToolsets {
61
+ agent: McpAgentNamed;
62
+ toolsets: McpAgentToolset[];
63
+ /** Why the sources are missing, when Contents could not be read. */
64
+ sourcesError: string | null;
65
+ }
66
+ /** One notebook or source, and how far this agent reaches it. */
67
+ export interface McpReachRow {
68
+ kind: 'notebook' | 'source';
69
+ uid: string;
70
+ name: string;
71
+ /** A notebook's Space, a source's kind. */
72
+ where: string | null;
73
+ read: boolean;
74
+ write: boolean;
75
+ execute: boolean;
76
+ /** Why it stops short of `execute`, in sentences. */
77
+ refused: string[];
78
+ /** Whether a source is the caller's own. */
79
+ yours?: boolean;
80
+ /** Set when the check could not run: an answer about the check, not the agent. */
81
+ unchecked: string | null;
82
+ }
83
+ export interface McpAgentReach {
84
+ agent: McpAgentNamed;
85
+ notebooks: McpReachRow[];
86
+ sources: McpReachRow[];
87
+ /** How many there are of each, of which `limit` were asked about. */
88
+ notebooksListed: number;
89
+ sourcesListed: number;
90
+ limit: number;
91
+ /** Contents has more than were read. */
92
+ sourcesTruncated: boolean;
93
+ notebooksError: string | null;
94
+ sourcesError: string | null;
95
+ }
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) 2023-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export {};
@@ -8,6 +8,15 @@ interface IResources {
8
8
  memory: string;
9
9
  'nvidia.com/gpu'?: string;
10
10
  }
11
+ /**
12
+ * What ran and hosted the agent a usage record is for (`runtime_engine`):
13
+ * a Datalayer runtime, kagent, or AWS AgentCore in an organization's AWS
14
+ * account. IAM records it from the launch; a record from before, or of a
15
+ * call with no agent runtime behind it, has none.
16
+ */
17
+ export type UsageRuntimeEngine = 'datalayer' | 'kagent' | 'agentcore';
18
+ /** The engine a raw record says, or `undefined` when it says none (or one not known). */
19
+ export declare const usageRuntimeEngineOf: (value: unknown) => UsageRuntimeEngine | undefined;
11
20
  export interface IRawUsage {
12
21
  account_uid: string;
13
22
  resource_uid: string;
@@ -26,6 +35,7 @@ export interface IRawUsage {
26
35
  usage_period_label?: string;
27
36
  usage_period_start?: string;
28
37
  usage_period_end?: string;
38
+ runtime_engine?: UsageRuntimeEngine | null;
29
39
  metadata: Map<string, string>;
30
40
  }
31
41
  export declare const asUsage: (u: any) => {
@@ -46,6 +56,7 @@ export declare const asUsage: (u: any) => {
46
56
  usagePeriodLabel: any;
47
57
  usagePeriodStart: Date | undefined;
48
58
  usagePeriodEnd: Date | undefined;
59
+ runtimeEngine: UsageRuntimeEngine | undefined;
49
60
  metadata: Map<string, string>;
50
61
  };
51
62
  export interface IUsage {
@@ -66,6 +77,8 @@ export interface IUsage {
66
77
  usagePeriodLabel?: string;
67
78
  usagePeriodStart?: Date;
68
79
  usagePeriodEnd?: Date;
80
+ /** What ran and hosted its agent; absent on a record that says none. */
81
+ runtimeEngine?: UsageRuntimeEngine;
69
82
  metadata: Map<string, string>;
70
83
  }
71
84
  export {};
@@ -2,6 +2,10 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ /** The engine a raw record says, or `undefined` when it says none (or one not known). */
6
+ export const usageRuntimeEngineOf = (value) => value === 'datalayer' || value === 'kagent' || value === 'agentcore'
7
+ ? value
8
+ : undefined;
5
9
  export const asUsage = (u) => {
6
10
  const usagePeriodKey = u.usage_period_key;
7
11
  const usagePeriodLabel = u.usage_period_label;
@@ -25,6 +29,7 @@ export const asUsage = (u) => {
25
29
  usagePeriodLabel,
26
30
  usagePeriodStart: usagePeriodStart ? new Date(usagePeriodStart) : undefined,
27
31
  usagePeriodEnd: usagePeriodEnd ? new Date(usagePeriodEnd) : undefined,
32
+ runtimeEngine: usageRuntimeEngineOf(u.runtime_engine),
28
33
  metadata: new Map(Object.entries(u.metadata ?? {})),
29
34
  };
30
35
  };
@@ -51,6 +51,7 @@ export * from './Invite';
51
51
  export * from './LandingRoles';
52
52
  export * from './McpAuditEvent';
53
53
  export * from './McpBinding';
54
+ export * from './McpAgentAccess';
54
55
  export * from './McpPolicy';
55
56
  export * from './McpTask';
56
57
  export * from './Member';
@@ -55,6 +55,7 @@ export * from './Invite.js';
55
55
  export * from './LandingRoles.js';
56
56
  export * from './McpAuditEvent.js';
57
57
  export * from './McpBinding.js';
58
+ export * from './McpAgentAccess.js';
58
59
  export * from './McpPolicy.js';
59
60
  export * from './McpTask.js';
60
61
  export * from './Member.js';
@@ -1,5 +1,53 @@
1
1
  import type { OtelSpan, OtelLog, OtelMetric, OtelQueryResult } from '../types';
2
2
  import type { OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable } from '../hooks';
3
+ export interface FetchTokenUsageOptions {
4
+ /** The window, in hours back from now (24 by default). */
5
+ hours?: number;
6
+ /** Only these services (runtimes); every one by default. */
7
+ serviceNames?: string[];
8
+ accountUid?: string;
9
+ }
10
+ /** What `GET /api/otel/v1/metrics/tokens` answers. */
11
+ export interface OtelTokenUsage {
12
+ total: number;
13
+ prompt: number;
14
+ completion: number;
15
+ by_service: Array<{
16
+ service_name: string;
17
+ total: number;
18
+ prompt: number;
19
+ completion: number;
20
+ }>;
21
+ window?: {
22
+ start: number;
23
+ end: number;
24
+ hours: number;
25
+ };
26
+ /**
27
+ * When telemetry last reached the store, whatever the account: `at` in
28
+ * unix nanoseconds, from rows ingested since the service started
29
+ * (`ingest`) or from its newest stored file (`files`); `null` with the
30
+ * `reason` when nothing was ever stored or the time could not be read.
31
+ * Absent from a service that does not say it.
32
+ */
33
+ last_received?: OtelLastReceived;
34
+ /**
35
+ * The same for the asked account, known only from what arrived since the
36
+ * service started (`since`, unix nanoseconds): `at` is `null`, with a
37
+ * `reason`, until rows of the account arrive.
38
+ */
39
+ account_last_received?: OtelAccountLastReceived;
40
+ }
41
+ export interface OtelLastReceived {
42
+ at: number | null;
43
+ source: 'ingest' | 'files' | null;
44
+ reason: string | null;
45
+ }
46
+ export interface OtelAccountLastReceived {
47
+ at: number | null;
48
+ since: number;
49
+ reason: string | null;
50
+ }
3
51
  export interface OtelClientOptions {
4
52
  /**
5
53
  * Base URL of the OTEL service.
@@ -65,6 +113,14 @@ export declare class OtelClient {
65
113
  data: OtelMetric[];
66
114
  count: number;
67
115
  }>;
116
+ /**
117
+ * The tokens agents counted over the last `hours` (24 by default): total,
118
+ * prompt and completion, overall and per service, in one request — the
119
+ * service reads each counter's increase over the window in one scan. The
120
+ * answer also says when telemetry last reached the store (`last_received`),
121
+ * so a total of 0 can be told from a store that stopped receiving.
122
+ */
123
+ fetchTokenUsage(options?: FetchTokenUsageOptions): Promise<OtelTokenUsage>;
68
124
  /** Sum all values for one metric name, with optional service filter. */
69
125
  fetchMetricTotal(metricName: string, options?: FetchMetricTotalOptions): Promise<number>;
70
126
  /** List known service names observed in traces. */
@@ -177,6 +177,31 @@ export class OtelClient {
177
177
  : [];
178
178
  return { data: normalized, count: normalized.length };
179
179
  }
180
+ /**
181
+ * The tokens agents counted over the last `hours` (24 by default): total,
182
+ * prompt and completion, overall and per service, in one request — the
183
+ * service reads each counter's increase over the window in one scan. The
184
+ * answer also says when telemetry last reached the store (`last_received`),
185
+ * so a total of 0 can be told from a store that stopped receiving.
186
+ */
187
+ async fetchTokenUsage(options = {}) {
188
+ const params = new URLSearchParams({ hours: String(options.hours ?? 24) });
189
+ for (const name of options.serviceNames ?? []) {
190
+ params.append('service_name', name);
191
+ }
192
+ if (options.accountUid)
193
+ params.set('account_uid', options.accountUid);
194
+ const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/tokens?${params}`, this.token, this.userUid);
195
+ return {
196
+ total: Number(resp.total ?? 0),
197
+ prompt: Number(resp.prompt ?? 0),
198
+ completion: Number(resp.completion ?? 0),
199
+ by_service: Array.isArray(resp.by_service) ? resp.by_service : [],
200
+ window: resp.window,
201
+ last_received: resp.last_received,
202
+ account_last_received: resp.account_last_received,
203
+ };
204
+ }
180
205
  /** Sum all values for one metric name, with optional service filter. */
181
206
  async fetchMetricTotal(metricName, options = {}) {
182
207
  const { serviceName, limit = 500, fallbackWithoutService = true, accountUid, } = options;
@@ -1,2 +1,2 @@
1
1
  export { OtelClient, createOtelClient } from './OtelClient';
2
- export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './OtelClient';
2
+ export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, FetchTokenUsageOptions, OtelTokenUsage, OtelLastReceived, OtelAccountLastReceived, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './OtelClient';
@@ -20,7 +20,7 @@ export type { ResolvedOtelAuth } from './auth';
20
20
  export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks';
21
21
  export type { OtelWsMessage, OtelWsCallbacks, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './hooks';
22
22
  export { OtelClient, createOtelClient } from './client';
23
- export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, } from './client';
23
+ export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, FetchTokenUsageOptions, OtelTokenUsage, OtelLastReceived, OtelAccountLastReceived, } from './client';
24
24
  export { OtelLive } from './views';
25
25
  export { OtelTracesList } from './views';
26
26
  export { OtelSpanDetail } from './views';