@datalayer/core 1.1.66 → 1.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -18
- package/lib/api/constants.d.ts +1 -1
- package/lib/api/constants.js +1 -1
- package/lib/api/contents/attachments.js +3 -7
- package/lib/api/contents/datasets.d.ts +48 -0
- package/lib/api/contents/datasets.js +44 -7
- package/lib/api/contents/runtimeMounts.js +1 -1
- package/lib/api/contents/sandboxUid.d.ts +13 -45
- package/lib/api/contents/sandboxUid.js +14 -65
- package/lib/api/evals/client.d.ts +402 -0
- package/lib/api/evals/client.js +359 -0
- package/lib/api/evals/index.d.ts +10 -0
- package/lib/api/evals/index.js +18 -0
- package/lib/api/evals/request.d.ts +43 -0
- package/lib/api/evals/request.js +67 -0
- package/lib/api/evals/status.d.ts +27 -0
- package/lib/api/evals/status.js +122 -0
- package/lib/api/evals/types.d.ts +1335 -0
- package/lib/api/evals/types.js +5 -0
- package/lib/api/iam/connectedAgents.d.ts +29 -0
- package/lib/api/iam/connectedAgents.js +35 -0
- package/lib/api/iam/identityProviders.d.ts +76 -0
- package/lib/api/iam/identityProviders.js +187 -0
- package/lib/api/iam/index.d.ts +1 -0
- package/lib/api/iam/index.js +1 -0
- package/lib/api/iam/mcpPolicy.d.ts +102 -1
- package/lib/api/iam/mcpPolicy.js +38 -2
- package/lib/api/iam/profile.d.ts +17 -1
- package/lib/api/iam/profile.js +27 -0
- package/lib/api/iam/scimTokens.d.ts +79 -0
- package/lib/api/iam/scimTokens.js +141 -0
- package/lib/api/iam/secrets.js +9 -21
- package/lib/api/iam/trials.d.ts +38 -0
- package/lib/api/iam/trials.js +66 -0
- package/lib/api/index.d.ts +4 -0
- package/lib/api/index.js +4 -0
- package/lib/api/mcp/observability.d.ts +25 -9
- package/lib/api/mcp/observability.js +63 -50
- package/lib/api/orchestration/client.d.ts +53 -0
- package/lib/api/orchestration/client.js +105 -0
- package/lib/api/orchestration/events.d.ts +88 -0
- package/lib/api/orchestration/events.js +245 -0
- package/lib/api/orchestration/generated.d.ts +693 -0
- package/lib/api/orchestration/generated.js +687 -0
- package/lib/api/orchestration/index.d.ts +30 -0
- package/lib/api/orchestration/index.js +34 -0
- package/lib/api/orchestration/lifecycle.d.ts +39 -0
- package/lib/api/orchestration/lifecycle.js +57 -0
- package/lib/api/orchestration/measures.d.ts +55 -0
- package/lib/api/orchestration/measures.js +99 -0
- package/lib/api/otel/dashboards.d.ts +56 -0
- package/lib/api/otel/dashboards.js +50 -0
- package/lib/api/otel/index.d.ts +1 -0
- package/lib/api/otel/index.js +1 -0
- package/lib/api/otel/metrics.d.ts +16 -0
- package/lib/api/otel/metrics.js +15 -0
- package/lib/api/scheduler/client.d.ts +32 -0
- package/lib/api/scheduler/client.js +42 -0
- package/lib/api/scheduler/index.d.ts +8 -0
- package/lib/api/scheduler/index.js +16 -0
- package/lib/api/scheduler/request.d.ts +28 -0
- package/lib/api/scheduler/request.js +50 -0
- package/lib/api/scheduler/types.d.ts +88 -0
- package/lib/api/scheduler/types.js +5 -0
- package/lib/api/spacer/comments.d.ts +97 -0
- package/lib/api/spacer/comments.js +45 -0
- package/lib/api/spacer/index.d.ts +9 -0
- package/lib/api/spacer/index.js +17 -0
- package/lib/api/spacer/notebooks.d.ts +33 -0
- package/lib/api/spacer/notebooks.js +39 -0
- package/lib/api/spacer/request.d.ts +25 -0
- package/lib/api/spacer/request.js +46 -0
- package/lib/api/spacer/spaces.d.ts +68 -0
- package/lib/api/spacer/spaces.js +45 -0
- package/lib/api/utils/validation.d.ts +4 -4
- package/lib/api/utils/validation.js +10 -7
- package/lib/client/constants.d.ts +1 -0
- package/lib/client/constants.js +1 -0
- package/lib/components/animation/AnimatedText.d.ts +1 -44
- package/lib/components/animation/AnimatedText.js +6 -122
- package/lib/components/anonymous/AnonymousKeyExpired.d.ts +56 -0
- package/lib/components/anonymous/AnonymousKeyExpired.js +96 -0
- package/lib/components/anonymous/AnonymousKeyTimer.d.ts +63 -0
- package/lib/components/anonymous/AnonymousKeyTimer.js +140 -0
- package/lib/components/anonymous/index.d.ts +7 -0
- package/lib/components/anonymous/index.js +15 -0
- package/lib/components/avatars/UserAvatar.d.ts +7 -1
- package/lib/components/avatars/UserAvatar.js +15 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +6 -0
- package/lib/components/billing/BillingEntitySelect.js +49 -10
- package/lib/components/billing/eligibility.d.ts +22 -0
- package/lib/components/billing/eligibility.js +27 -0
- package/lib/components/billing/index.d.ts +1 -0
- package/lib/components/billing/index.js +1 -0
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +5 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +3 -1
- package/lib/components/display/DatalayerBox.d.ts +2 -3
- package/lib/components/display/DatalayerBox.js +1 -1
- package/lib/components/display/NavLink.js +3 -2
- package/lib/components/labels/StatusLabels.d.ts +2 -10
- package/lib/components/labels/StatusLabels.js +2 -26
- package/lib/components/principal/PrincipalAppearance.d.ts +21 -42
- package/lib/components/principal/PrincipalAppearance.js +232 -174
- package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
- package/lib/components/principal/PrincipalAvatar.js +2 -2
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +8 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
- package/lib/components/screencapture/Screencapture.js +46 -3
- package/lib/components/sharing/ShareAccessComponent.d.ts +45 -4
- package/lib/components/sharing/ShareAccessComponent.js +110 -78
- package/lib/components/sharing/SharingEditor.d.ts +5 -1
- package/lib/components/sharing/SharingEditor.js +9 -4
- package/lib/components/sharing/index.d.ts +1 -0
- package/lib/components/sharing/index.js +1 -0
- package/lib/components/sharing/sandboxSharing.d.ts +26 -0
- package/lib/components/sharing/sandboxSharing.js +41 -0
- package/lib/components/spaces/SpaceDetailsOverlay.d.ts +39 -0
- package/lib/components/spaces/SpaceDetailsOverlay.js +80 -0
- package/lib/components/spaces/SpaceDisplay.d.ts +38 -0
- package/lib/components/spaces/SpaceDisplay.js +92 -0
- package/lib/components/spaces/index.d.ts +4 -0
- package/lib/components/spaces/index.js +7 -0
- package/lib/components/spaces/spaceDisplayModel.d.ts +93 -0
- package/lib/components/spaces/spaceDisplayModel.js +170 -0
- package/lib/hooks/cacheConverters.d.ts +0 -7
- package/lib/hooks/cacheConverters.js +0 -25
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useBillingEntityStore.js +3 -0
- package/lib/hooks/useCache.d.ts +168 -25
- package/lib/hooks/useCache.js +1424 -307
- package/lib/hooks/useContents.d.ts +22 -1
- package/lib/hooks/useContents.js +90 -21
- package/lib/hooks/useMcp.d.ts +87 -1
- package/lib/hooks/useMcp.js +247 -12
- package/lib/hooks/useNavigate.d.ts +41 -3
- package/lib/hooks/useNavigate.js +106 -52
- package/lib/hooks/usePrincipalCacheStore.js +3 -0
- package/lib/hooks/usePrincipalStore.js +3 -0
- package/lib/hooks/useSpaceCacheStore.d.ts +50 -0
- package/lib/hooks/useSpaceCacheStore.js +85 -0
- package/lib/models/CreditsDTO.d.ts +6 -5
- package/lib/models/CreditsDTO.js +7 -5
- package/lib/models/McpBinding.d.ts +6 -0
- package/lib/models/Organization.d.ts +69 -0
- package/lib/models/Organization.js +96 -0
- package/lib/models/Secret.d.ts +1 -2
- package/lib/models/Secret.js +2 -18
- package/lib/models/StartedBy.d.ts +34 -0
- package/lib/models/StartedBy.js +36 -0
- package/lib/models/Team.d.ts +2 -0
- package/lib/models/Team.js +2 -0
- package/lib/models/User.d.ts +4 -0
- package/lib/models/User.js +3 -0
- package/lib/models/UserSettings.d.ts +13 -0
- package/lib/models/UserSettings.js +9 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +158 -30
- package/lib/navigation/components.js +2 -3
- package/lib/routes/publicPaths.js +4 -0
- package/lib/state/index.d.ts +1 -0
- package/lib/state/index.js +1 -0
- package/lib/state/sessionEnd.d.ts +55 -0
- package/lib/state/sessionEnd.js +162 -0
- package/lib/state/substates/CoreState.js +3 -3
- package/lib/state/substates/IAMState.js +7 -0
- package/lib/state/substates/LayoutState.js +12 -0
- package/lib/state/substates/ProfileState.d.ts +1 -0
- package/lib/state/substates/ProfileState.js +1 -0
- package/lib/utils/Lazy.d.ts +20 -0
- package/lib/utils/Lazy.js +32 -6
- package/lib/utils/Screencapture.js +3 -0
- package/lib/utils/WithSuspense.d.ts +11 -1
- package/lib/utils/WithSuspense.js +11 -1
- package/lib/views/mcp/AdmittedClients.d.ts +44 -0
- package/lib/views/mcp/AdmittedClients.js +59 -0
- package/lib/views/mcp/ApprovalQueue.d.ts +66 -0
- package/lib/views/mcp/ApprovalQueue.js +95 -0
- package/lib/views/mcp/ConnectedAgents.js +38 -3
- package/lib/views/mcp/EnterpriseConsole.d.ts +1 -1
- package/lib/views/mcp/EnterpriseConsole.js +23 -1
- package/lib/views/mcp/IdentityProviders.d.ts +33 -0
- package/lib/views/mcp/IdentityProviders.js +299 -0
- package/lib/views/mcp/McpDashboard.js +90 -7
- package/lib/views/mcp/McpObservability.d.ts +30 -1
- package/lib/views/mcp/McpObservability.js +105 -19
- package/lib/views/mcp/NotebookRuns.d.ts +39 -0
- package/lib/views/mcp/NotebookRuns.js +44 -0
- package/lib/views/mcp/OrganizationPolicy.js +2 -1
- package/lib/views/mcp/PersonalPolicy.js +2 -1
- package/lib/views/mcp/PolicyForm.d.ts +17 -1
- package/lib/views/mcp/PolicyForm.js +25 -3
- package/lib/views/mcp/PolicyHistory.d.ts +13 -0
- package/lib/views/mcp/PolicyHistory.js +14 -1
- package/lib/views/mcp/RunDetail.d.ts +87 -0
- package/lib/views/mcp/RunDetail.js +186 -0
- package/lib/views/mcp/ScimProvisioning.d.ts +54 -0
- package/lib/views/mcp/ScimProvisioning.js +173 -0
- package/lib/views/mcp/TeamPolicies.js +66 -5
- package/lib/views/mcp/ToolAccess.d.ts +60 -0
- package/lib/views/mcp/ToolAccess.js +101 -0
- package/lib/views/mcp/TraceTimeline.d.ts +81 -0
- package/lib/views/mcp/TraceTimeline.js +132 -0
- package/lib/views/mcp/index.d.ts +5 -0
- package/lib/views/mcp/index.js +5 -0
- package/lib/views/otel/simpleAuthStore.js +3 -0
- package/lib/views/secrets/SecretEdit.js +3 -2
- package/lib/views/secrets/SecretNew.js +5 -1
- package/package.json +269 -264
- package/scripts/generate-mcp-types.py +1 -1
- package/scripts/generate-orchestration-types.py +920 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** A SCIM credential as the console may see it — never the secret or its hash. */
|
|
2
|
+
export interface ScimToken {
|
|
3
|
+
uid: string;
|
|
4
|
+
orgUid: string;
|
|
5
|
+
/** What an administrator called it, to tell two directories apart. */
|
|
6
|
+
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* Revoked rather than deleted, so the rows a provisioning run wrote can
|
|
9
|
+
* still name the token that wrote them.
|
|
10
|
+
*/
|
|
11
|
+
revoked: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* When the secret was last replaced. A token that has never been rotated
|
|
14
|
+
* carries its creation moment here.
|
|
15
|
+
*/
|
|
16
|
+
rotatedAt?: string;
|
|
17
|
+
/**
|
|
18
|
+
* When it last authenticated a SCIM call, or empty for never. Written best
|
|
19
|
+
* effort — a provisioning call is never failed for the bookkeeping.
|
|
20
|
+
*/
|
|
21
|
+
lastUsedAt?: string;
|
|
22
|
+
createdBy?: string;
|
|
23
|
+
createdAt?: string;
|
|
24
|
+
updatedAt?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A freshly minted credential: the record, and the secret **this once**.
|
|
28
|
+
*
|
|
29
|
+
* The two are separate fields rather than one merged object on purpose. The
|
|
30
|
+
* secret does not belong in anything the console stores, renders twice, or
|
|
31
|
+
* puts in a list, and a shape that carried it alongside every other field
|
|
32
|
+
* invites exactly that.
|
|
33
|
+
*/
|
|
34
|
+
export interface MintedScimToken {
|
|
35
|
+
token: ScimToken;
|
|
36
|
+
/** Shown once. Not retrievable, by construction — only a hash is stored. */
|
|
37
|
+
secret: string;
|
|
38
|
+
}
|
|
39
|
+
export declare const listScimTokens: (token: string, orgUid: string, baseUrl?: string) => Promise<ScimToken[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Mint one. The secret is in this answer and nowhere else, ever again.
|
|
42
|
+
*
|
|
43
|
+
* The caller must put it in front of somebody before the next render. There
|
|
44
|
+
* is no second chance and no call that would provide one.
|
|
45
|
+
*/
|
|
46
|
+
export declare const createScimToken: (token: string, orgUid: string, name: string, baseUrl?: string) => Promise<MintedScimToken>;
|
|
47
|
+
/**
|
|
48
|
+
* A new secret for the same record; the old one stops at once.
|
|
49
|
+
*
|
|
50
|
+
* Rotation rather than delete-and-recreate because the record's uid is what
|
|
51
|
+
* the provisioning rows name. Replacing it would leave them pointing at
|
|
52
|
+
* nothing.
|
|
53
|
+
*/
|
|
54
|
+
export declare const rotateScimToken: (token: string, orgUid: string, uid: string, baseUrl?: string) => Promise<MintedScimToken>;
|
|
55
|
+
/** Stop a token, keeping the rows that name it readable. */
|
|
56
|
+
export declare const revokeScimToken: (token: string, orgUid: string, uid: string, baseUrl?: string) => Promise<ScimToken>;
|
|
57
|
+
/**
|
|
58
|
+
* The base URL a directory is configured with.
|
|
59
|
+
*
|
|
60
|
+
* Built here rather than typed into the page, because it is half of what an
|
|
61
|
+
* administrator copies into Okta or Entra and a typo in it looks like an
|
|
62
|
+
* outage on the directory's side. The directory appends `/Users` itself;
|
|
63
|
+
* RFC 7644 fixes everything after this.
|
|
64
|
+
*/
|
|
65
|
+
export declare const scimBaseUrl: (baseUrl?: string) => string;
|
|
66
|
+
/**
|
|
67
|
+
* Whether this organization can actually be provisioned into right now.
|
|
68
|
+
*
|
|
69
|
+
* A list with tokens in it is not the same as a working integration, and the
|
|
70
|
+
* distinction is the one an administrator most needs: every token revoked
|
|
71
|
+
* reads, in a table, almost exactly like a healthy setup. `neverUsed` is the
|
|
72
|
+
* other quiet state — configured, live, and never once called, which is what
|
|
73
|
+
* a directory that was set up and never switched on looks like.
|
|
74
|
+
*/
|
|
75
|
+
export declare const scimStatus: (tokens: ScimToken[]) => {
|
|
76
|
+
state: "none" | "revoked" | "never-used" | "active";
|
|
77
|
+
live: ScimToken[];
|
|
78
|
+
lastUsedAt?: string;
|
|
79
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
7
|
+
* Distributed under the terms of the Modified BSD License.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The credentials an organization's directory provisions with, over SCIM.
|
|
11
|
+
*
|
|
12
|
+
* The identity provider registry beside this one made a directory able to
|
|
13
|
+
* *sign somebody in*. SCIM is the half that does not wait for them to turn
|
|
14
|
+
* up: the directory says who exists when they join and, far more
|
|
15
|
+
* importantly, who has left.
|
|
16
|
+
*
|
|
17
|
+
* **The token is the tenant.** Every SCIM call resolves its organization
|
|
18
|
+
* from the bearer credential before it looks at anything else, so these
|
|
19
|
+
* records are not a convenience — each one *is* an organization's provisioning
|
|
20
|
+
* authority, and there is nothing else identifying the caller.
|
|
21
|
+
*
|
|
22
|
+
* **The secret is answered once and never again.** `createScimToken` and
|
|
23
|
+
* `rotateScimToken` return it; nothing reads it back, because only a SHA-256
|
|
24
|
+
* of it is stored. A console that offered to show it later would be
|
|
25
|
+
* promising something the server cannot do — so this client has no call that
|
|
26
|
+
* could, and the UI has to say "copy it now" and mean it.
|
|
27
|
+
*
|
|
28
|
+
* `lastUsedAt` is the column worth reading. A directory configured once and
|
|
29
|
+
* forgotten looks exactly like a live one in every other field, and the
|
|
30
|
+
* difference matters most for deprovisioning: the integration that has not
|
|
31
|
+
* called in a month is not removing anybody.
|
|
32
|
+
*
|
|
33
|
+
* @module api/iam/scimTokens
|
|
34
|
+
*/
|
|
35
|
+
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
36
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
37
|
+
const fromWire = (wire) => ({
|
|
38
|
+
uid: wire.uid ?? '',
|
|
39
|
+
orgUid: wire.org_uid ?? '',
|
|
40
|
+
name: wire.name ?? '',
|
|
41
|
+
// A record with no `revoked` field is a live one: absence is not
|
|
42
|
+
// revocation, and reading it as one would show every token as stopped.
|
|
43
|
+
revoked: wire.revoked === true,
|
|
44
|
+
rotatedAt: wire.rotated_at || undefined,
|
|
45
|
+
lastUsedAt: wire.last_used_at || undefined,
|
|
46
|
+
createdBy: wire.created_by || undefined,
|
|
47
|
+
createdAt: wire.created_at || undefined,
|
|
48
|
+
updatedAt: wire.updated_at || undefined,
|
|
49
|
+
});
|
|
50
|
+
const tokensUrl = (baseUrl, orgUid, suffix = '') => `${baseUrl}${API_BASE_PATHS.IAM}/organizations/${encodeURIComponent(orgUid)}` +
|
|
51
|
+
`/scim-tokens${suffix}`;
|
|
52
|
+
export const listScimTokens = async (token, orgUid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
53
|
+
const response = await requestDatalayerAPI({
|
|
54
|
+
url: tokensUrl(baseUrl, orgUid),
|
|
55
|
+
method: 'GET',
|
|
56
|
+
token,
|
|
57
|
+
});
|
|
58
|
+
return (response.tokens ?? []).map(fromWire);
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Mint one. The secret is in this answer and nowhere else, ever again.
|
|
62
|
+
*
|
|
63
|
+
* The caller must put it in front of somebody before the next render. There
|
|
64
|
+
* is no second chance and no call that would provide one.
|
|
65
|
+
*/
|
|
66
|
+
export const createScimToken = async (token, orgUid, name, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
67
|
+
const response = await requestDatalayerAPI({
|
|
68
|
+
url: tokensUrl(baseUrl, orgUid),
|
|
69
|
+
method: 'POST',
|
|
70
|
+
token,
|
|
71
|
+
body: { name },
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
token: fromWire(response.token ?? {}),
|
|
75
|
+
secret: response.token?.token ?? '',
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* A new secret for the same record; the old one stops at once.
|
|
80
|
+
*
|
|
81
|
+
* Rotation rather than delete-and-recreate because the record's uid is what
|
|
82
|
+
* the provisioning rows name. Replacing it would leave them pointing at
|
|
83
|
+
* nothing.
|
|
84
|
+
*/
|
|
85
|
+
export const rotateScimToken = async (token, orgUid, uid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
86
|
+
const response = await requestDatalayerAPI({
|
|
87
|
+
url: tokensUrl(baseUrl, orgUid, `/${encodeURIComponent(uid)}/rotate`),
|
|
88
|
+
method: 'POST',
|
|
89
|
+
token,
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
token: fromWire(response.token ?? {}),
|
|
93
|
+
secret: response.token?.token ?? '',
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/** Stop a token, keeping the rows that name it readable. */
|
|
97
|
+
export const revokeScimToken = async (token, orgUid, uid, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
98
|
+
const response = await requestDatalayerAPI({
|
|
99
|
+
url: tokensUrl(baseUrl, orgUid, `/${encodeURIComponent(uid)}`),
|
|
100
|
+
method: 'DELETE',
|
|
101
|
+
token,
|
|
102
|
+
});
|
|
103
|
+
return fromWire(response.token ?? {});
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* The base URL a directory is configured with.
|
|
107
|
+
*
|
|
108
|
+
* Built here rather than typed into the page, because it is half of what an
|
|
109
|
+
* administrator copies into Okta or Entra and a typo in it looks like an
|
|
110
|
+
* outage on the directory's side. The directory appends `/Users` itself;
|
|
111
|
+
* RFC 7644 fixes everything after this.
|
|
112
|
+
*/
|
|
113
|
+
export const scimBaseUrl = (baseUrl = DEFAULT_SERVICE_URLS.IAM) => `${baseUrl}/api/iam/scim/v2`;
|
|
114
|
+
/**
|
|
115
|
+
* Whether this organization can actually be provisioned into right now.
|
|
116
|
+
*
|
|
117
|
+
* A list with tokens in it is not the same as a working integration, and the
|
|
118
|
+
* distinction is the one an administrator most needs: every token revoked
|
|
119
|
+
* reads, in a table, almost exactly like a healthy setup. `neverUsed` is the
|
|
120
|
+
* other quiet state — configured, live, and never once called, which is what
|
|
121
|
+
* a directory that was set up and never switched on looks like.
|
|
122
|
+
*/
|
|
123
|
+
export const scimStatus = (tokens) => {
|
|
124
|
+
const live = tokens.filter(entry => !entry.revoked);
|
|
125
|
+
if (tokens.length === 0) {
|
|
126
|
+
return { state: 'none', live };
|
|
127
|
+
}
|
|
128
|
+
if (live.length === 0) {
|
|
129
|
+
return { state: 'revoked', live };
|
|
130
|
+
}
|
|
131
|
+
const used = live
|
|
132
|
+
.map(entry => entry.lastUsedAt)
|
|
133
|
+
.filter((value) => Boolean(value))
|
|
134
|
+
.sort();
|
|
135
|
+
const lastUsedAt = used.length > 0 ? used[used.length - 1] : undefined;
|
|
136
|
+
return {
|
|
137
|
+
state: lastUsedAt ? 'active' : 'never-used',
|
|
138
|
+
live,
|
|
139
|
+
lastUsedAt,
|
|
140
|
+
};
|
|
141
|
+
};
|
package/lib/api/iam/secrets.js
CHANGED
|
@@ -9,21 +9,6 @@
|
|
|
9
9
|
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
10
10
|
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
11
11
|
import { validateToken, validateRequiredString } from '../utils/validation.js';
|
|
12
|
-
/**
|
|
13
|
-
* Helper function to Base64 encode a value (cross-platform).
|
|
14
|
-
* @param value - Plain text value to encode
|
|
15
|
-
* @returns Base64 encoded string
|
|
16
|
-
*/
|
|
17
|
-
function encodeValue(value) {
|
|
18
|
-
if (typeof Buffer !== 'undefined') {
|
|
19
|
-
// Node.js environment
|
|
20
|
-
return Buffer.from(value).toString('base64');
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
// Browser environment
|
|
24
|
-
return btoa(value);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
12
|
/**
|
|
28
13
|
* Create a new secret.
|
|
29
14
|
* Creates a new encrypted secret for the authenticated user.
|
|
@@ -52,7 +37,10 @@ export const createSecret = async (token, data, baseUrl = DEFAULT_SERVICE_URLS.I
|
|
|
52
37
|
variant: data.variant || 'generic',
|
|
53
38
|
name: data.name,
|
|
54
39
|
description: data.description || '',
|
|
55
|
-
|
|
40
|
+
// As it is. A secret has one representation end to end (PLAN_ENVS.md
|
|
41
|
+
// E3-05): this used to base64-encode, and nothing on the read side undid
|
|
42
|
+
// it, so a build secret arrived at its build encoded and opened nothing.
|
|
43
|
+
value: data.value,
|
|
56
44
|
};
|
|
57
45
|
try {
|
|
58
46
|
return await requestDatalayerAPI({
|
|
@@ -64,7 +52,7 @@ export const createSecret = async (token, data, baseUrl = DEFAULT_SERVICE_URLS.I
|
|
|
64
52
|
}
|
|
65
53
|
catch (error) {
|
|
66
54
|
if (error.response?.status === 409) {
|
|
67
|
-
throw new Error(`Secret with name '${data.name}' already exists. Please use a different name
|
|
55
|
+
throw new Error(`Secret with name '${data.name}' already exists. Please use a different name.`, { cause: error });
|
|
68
56
|
}
|
|
69
57
|
throw error;
|
|
70
58
|
}
|
|
@@ -118,7 +106,7 @@ export const getSecret = async (token, secretId, baseUrl = DEFAULT_SERVICE_URLS.
|
|
|
118
106
|
}
|
|
119
107
|
catch (error) {
|
|
120
108
|
if (error.response?.status === 404) {
|
|
121
|
-
throw new Error(`Secret '${secretId}' not found
|
|
109
|
+
throw new Error(`Secret '${secretId}' not found.`, { cause: error });
|
|
122
110
|
}
|
|
123
111
|
throw error;
|
|
124
112
|
}
|
|
@@ -147,7 +135,7 @@ export const updateSecret = async (token, secretId, data, baseUrl = DEFAULT_SERV
|
|
|
147
135
|
// Base64 encode value if provided
|
|
148
136
|
const requestBody = { ...data };
|
|
149
137
|
if (data.value) {
|
|
150
|
-
requestBody.value =
|
|
138
|
+
requestBody.value = data.value;
|
|
151
139
|
}
|
|
152
140
|
try {
|
|
153
141
|
return await requestDatalayerAPI({
|
|
@@ -159,7 +147,7 @@ export const updateSecret = async (token, secretId, data, baseUrl = DEFAULT_SERV
|
|
|
159
147
|
}
|
|
160
148
|
catch (error) {
|
|
161
149
|
if (error.response?.status === 404) {
|
|
162
|
-
throw new Error(`Secret '${secretId}' not found
|
|
150
|
+
throw new Error(`Secret '${secretId}' not found.`, { cause: error });
|
|
163
151
|
}
|
|
164
152
|
throw error;
|
|
165
153
|
}
|
|
@@ -189,7 +177,7 @@ export const deleteSecret = async (token, secretId, baseUrl = DEFAULT_SERVICE_UR
|
|
|
189
177
|
}
|
|
190
178
|
catch (error) {
|
|
191
179
|
if (error.response?.status === 404) {
|
|
192
|
-
throw new Error(`Secret '${secretId}' not found
|
|
180
|
+
throw new Error(`Secret '${secretId}' not found.`, { cause: error });
|
|
193
181
|
}
|
|
194
182
|
throw error;
|
|
195
183
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Where a claim carries the trial's own key. */
|
|
2
|
+
export declare const TRIAL_TOKEN_HEADER = "X-Datalayer-Trial-Token";
|
|
3
|
+
/** What a visitor may try here, if anything. */
|
|
4
|
+
export interface TrialOffer {
|
|
5
|
+
offered: boolean;
|
|
6
|
+
/** The one benchmark a trial may run. */
|
|
7
|
+
evalsetId: string;
|
|
8
|
+
/** The credits it may spend. */
|
|
9
|
+
credits: number;
|
|
10
|
+
/** How long it lives, in seconds. */
|
|
11
|
+
ttlSeconds: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Trial {
|
|
14
|
+
id: string;
|
|
15
|
+
/** The uid the trial's work is owned by, which is the trial's own. */
|
|
16
|
+
ownerUid: string;
|
|
17
|
+
state: 'open' | 'claimed' | 'expired';
|
|
18
|
+
evalsetId: string;
|
|
19
|
+
creditsLimit: number | null;
|
|
20
|
+
creditsSpent: number;
|
|
21
|
+
expiresAt: string | null;
|
|
22
|
+
claimedByUid: string;
|
|
23
|
+
claimedAt: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** A trial and the key that acts as it. The key is answered once. */
|
|
26
|
+
export interface IssuedTrial {
|
|
27
|
+
trial: Trial;
|
|
28
|
+
token: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether this deployment lets a visitor try a benchmark, and which one.
|
|
32
|
+
* Unauthenticated: it is what a public page asks before it offers the run.
|
|
33
|
+
*/
|
|
34
|
+
export declare const getTrialOffer: (baseUrl?: string) => Promise<TrialOffer>;
|
|
35
|
+
/** Take a trial. Unauthenticated, rate limited, and refused where none are offered. */
|
|
36
|
+
export declare const createTrial: (baseUrl?: string) => Promise<IssuedTrial>;
|
|
37
|
+
/** The trial a key acts as: what it has left, and how long it has. */
|
|
38
|
+
export declare const getCurrentTrial: (trialToken: string, baseUrl?: string) => Promise<Trial>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
7
|
+
* Distributed under the terms of the Modified BSD License.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The anonymous trial: running the public benchmark before signing up
|
|
11
|
+
* (BENCHMARK.md, B2-14).
|
|
12
|
+
*
|
|
13
|
+
* A visitor with no account asks for a trial and gets a principal of its
|
|
14
|
+
* own with one benchmark it may run, a few credits and a short expiry. The
|
|
15
|
+
* work they make is owned by that principal, so when they sign up it is
|
|
16
|
+
* given to them rather than lost — `claimTrial` in the evals client moves
|
|
17
|
+
* it, and the trial's own key has to be presented alongside their new one.
|
|
18
|
+
*
|
|
19
|
+
* A deployment that has not named a public benchmark offers no trials, and
|
|
20
|
+
* `getTrialOffer` says so: a page asks before it shows the button.
|
|
21
|
+
*
|
|
22
|
+
* @module api/iam/trials
|
|
23
|
+
*/
|
|
24
|
+
import { requestDatalayerAPI } from '../DatalayerApi.js';
|
|
25
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
26
|
+
/** Where a claim carries the trial's own key. */
|
|
27
|
+
export const TRIAL_TOKEN_HEADER = 'X-Datalayer-Trial-Token';
|
|
28
|
+
const trialsUrl = (baseUrl, suffix = '') => `${baseUrl}${API_BASE_PATHS.IAM}/trials${suffix}`;
|
|
29
|
+
const asTrial = (wire) => ({
|
|
30
|
+
id: wire.id,
|
|
31
|
+
ownerUid: wire.owner_uid ?? wire.id,
|
|
32
|
+
state: wire.state ?? 'open',
|
|
33
|
+
evalsetId: wire.evalset_id ?? '',
|
|
34
|
+
creditsLimit: wire.credits_limit ?? null,
|
|
35
|
+
creditsSpent: wire.credits_spent ?? 0,
|
|
36
|
+
expiresAt: wire.expires_at ?? null,
|
|
37
|
+
claimedByUid: wire.claimed_by_uid ?? '',
|
|
38
|
+
claimedAt: wire.claimed_at ?? null,
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Whether this deployment lets a visitor try a benchmark, and which one.
|
|
42
|
+
* Unauthenticated: it is what a public page asks before it offers the run.
|
|
43
|
+
*/
|
|
44
|
+
export const getTrialOffer = async (baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
45
|
+
const response = await requestDatalayerAPI({ url: trialsUrl(baseUrl, '/offer'), method: 'GET' });
|
|
46
|
+
return {
|
|
47
|
+
offered: !!response.offered,
|
|
48
|
+
evalsetId: response.evalset_id ?? '',
|
|
49
|
+
credits: response.credits ?? 0,
|
|
50
|
+
ttlSeconds: response.ttl_seconds ?? 0,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** Take a trial. Unauthenticated, rate limited, and refused where none are offered. */
|
|
54
|
+
export const createTrial = async (baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
55
|
+
const response = await requestDatalayerAPI({ url: trialsUrl(baseUrl), method: 'POST' });
|
|
56
|
+
return { trial: asTrial(response.trial), token: response.token };
|
|
57
|
+
};
|
|
58
|
+
/** The trial a key acts as: what it has left, and how long it has. */
|
|
59
|
+
export const getCurrentTrial = async (trialToken, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
|
|
60
|
+
const response = await requestDatalayerAPI({
|
|
61
|
+
url: trialsUrl(baseUrl, '/me'),
|
|
62
|
+
method: 'GET',
|
|
63
|
+
token: trialToken,
|
|
64
|
+
});
|
|
65
|
+
return asTrial(response.trial);
|
|
66
|
+
};
|
package/lib/api/index.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export type { IRequestDatalayerAPIOptions } from './DatalayerApi';
|
|
|
11
11
|
export type { IDatalayerAPIResponse } from './DatalayerApi';
|
|
12
12
|
export * as iam from './iam';
|
|
13
13
|
export * as contents from './contents';
|
|
14
|
+
export * as evals from './evals';
|
|
14
15
|
export * as mcp from './mcp';
|
|
16
|
+
export * as orchestration from './orchestration';
|
|
15
17
|
export * as otel from './otel';
|
|
18
|
+
export * as scheduler from './scheduler';
|
|
19
|
+
export * as spacer from './spacer';
|
|
16
20
|
export * from '../api/DatalayerApi';
|
package/lib/api/index.js
CHANGED
|
@@ -15,6 +15,10 @@ export { requestDatalayerAPI, requestDatalayerAPIWithResponse, RunResponseError,
|
|
|
15
15
|
// Domain-organized API exports
|
|
16
16
|
export * as iam from './iam/index.js';
|
|
17
17
|
export * as contents from './contents/index.js';
|
|
18
|
+
export * as evals from './evals/index.js';
|
|
18
19
|
export * as mcp from './mcp/index.js';
|
|
20
|
+
export * as orchestration from './orchestration/index.js';
|
|
19
21
|
export * as otel from './otel/index.js';
|
|
22
|
+
export * as scheduler from './scheduler/index.js';
|
|
23
|
+
export * as spacer from './spacer/index.js';
|
|
20
24
|
export * from '../api/DatalayerApi.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type DashboardData } from '../otel/dashboards';
|
|
2
|
+
import type { OtelLog, OtelSpan } from '../otel/types';
|
|
2
3
|
import type { McpTask } from '../../models/McpTask';
|
|
3
4
|
/**
|
|
4
5
|
* The `service.name` the gateway exports under, and the one its workers use.
|
|
@@ -40,11 +41,25 @@ export declare const MCP_SLI_METRICS: {
|
|
|
40
41
|
readonly taskSuccess: "mcp.tasks";
|
|
41
42
|
readonly sandboxLaunch: "sandbox.launch_seconds";
|
|
42
43
|
};
|
|
44
|
+
/** The OTEL service's built-in dashboard the four SLIs are read from. */
|
|
45
|
+
export declare const MCP_SERVICE_LEVELS_DASHBOARD = "mcp-service-levels";
|
|
46
|
+
/**
|
|
47
|
+
* Its panels, by the ids the OTEL service gives them. A contract test there
|
|
48
|
+
* holds them, so a panel renamed on the service fails a test rather than
|
|
49
|
+
* reading here as nothing measured.
|
|
50
|
+
*/
|
|
51
|
+
export declare const MCP_SERVICE_LEVEL_PANELS: {
|
|
52
|
+
readonly callsByOutcome: "mcp-calls-by-outcome";
|
|
53
|
+
readonly callDurationP95: "mcp-call-duration-p95";
|
|
54
|
+
readonly tasksByStatus: "mcp-tasks-by-status";
|
|
55
|
+
readonly sandboxLaunchP95: "sandbox-launch_seconds-by-provider-p95";
|
|
56
|
+
readonly sandboxLaunches: "sandbox-launch_seconds-by-provider-count";
|
|
57
|
+
};
|
|
43
58
|
export interface McpMetricsFilters {
|
|
44
59
|
/** The `client_id` of one agent: read from spans, not from metrics. */
|
|
45
60
|
agent?: string;
|
|
46
61
|
org?: string;
|
|
47
|
-
/** ISO 8601;
|
|
62
|
+
/** ISO 8601; the reading starts there. */
|
|
48
63
|
since?: string;
|
|
49
64
|
limit?: number;
|
|
50
65
|
}
|
|
@@ -66,8 +81,8 @@ export interface McpSliSummary {
|
|
|
66
81
|
}
|
|
67
82
|
export interface McpMetricsSnapshot {
|
|
68
83
|
filters: McpMetricsFilters;
|
|
69
|
-
/** The catalog points
|
|
70
|
-
|
|
84
|
+
/** The catalog metrics the OTEL service holds points of, in catalog order. */
|
|
85
|
+
reporting: McpMetricName[];
|
|
71
86
|
/** The `mcp.request` spans a per-agent or per-organization reading rests on. */
|
|
72
87
|
spans: OtelSpan[];
|
|
73
88
|
slis: McpSliSummary;
|
|
@@ -76,13 +91,14 @@ export interface McpMetricsSnapshot {
|
|
|
76
91
|
/** The nearest-rank percentile of a sample; `null` for an empty one. */
|
|
77
92
|
export declare const percentile: (values: number[], fraction: number) => number | null;
|
|
78
93
|
/**
|
|
79
|
-
* The SLIs
|
|
94
|
+
* The SLIs from the service levels dashboard — the platform-wide reading.
|
|
80
95
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
96
|
+
* Its panels arrive summarised: calls and tasks as how much each counter grew
|
|
97
|
+
* over the window, the two p95s from their histograms' buckets. The call
|
|
98
|
+
* duration is recorded in seconds and shown in milliseconds. A panel with
|
|
99
|
+
* nothing in it reads as not measured, never as a zero.
|
|
84
100
|
*/
|
|
85
|
-
export declare const
|
|
101
|
+
export declare const summarizeServiceLevels: (data: DashboardData) => McpSliSummary;
|
|
86
102
|
/**
|
|
87
103
|
* The SLIs over `mcp.request` spans — the per-agent or per-organization
|
|
88
104
|
* reading, since metrics carry neither label.
|
|
@@ -13,17 +13,21 @@
|
|
|
13
13
|
* Audit answers *who did what, and was it allowed*; this answers *how did
|
|
14
14
|
* it run*. A task carries its `trace_id`, so "show me this run" is the
|
|
15
15
|
* task's spans; its logs are the log records of that trace; and the four
|
|
16
|
-
* SLIs are read from the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* SLIs are read from the OTEL service's service levels dashboard, which
|
|
17
|
+
* computes them from the metrics as they were exported — a counter's
|
|
18
|
+
* increase, a percentile from a histogram's buckets. Nothing here adds
|
|
19
|
+
* metric points up: the gateway reports running totals, and adding those
|
|
20
|
+
* counts every earlier report again. Metrics carry no user, agent or
|
|
21
|
+
* organization label by design, so a per-agent reading is computed from the
|
|
22
|
+
* `mcp.request` spans, which do carry `client.id` and `org.uid`.
|
|
20
23
|
*
|
|
21
24
|
* @module api/mcp/observability
|
|
22
25
|
*/
|
|
23
26
|
import { DEFAULT_SERVICE_URLS } from '../constants.js';
|
|
24
27
|
import { getTrace, listTraces } from '../otel/traces.js';
|
|
25
|
-
import {
|
|
28
|
+
import { listMetricNames } from '../otel/metrics.js';
|
|
26
29
|
import { queryLogs } from '../otel/logs.js';
|
|
30
|
+
import { getDashboardData } from '../otel/dashboards.js';
|
|
27
31
|
/**
|
|
28
32
|
* The `service.name` the gateway exports under, and the one its workers use.
|
|
29
33
|
*
|
|
@@ -94,6 +98,21 @@ export const MCP_SLI_METRICS = {
|
|
|
94
98
|
taskSuccess: 'mcp.tasks',
|
|
95
99
|
sandboxLaunch: 'sandbox.launch_seconds',
|
|
96
100
|
};
|
|
101
|
+
/** The OTEL service's built-in dashboard the four SLIs are read from. */
|
|
102
|
+
export const MCP_SERVICE_LEVELS_DASHBOARD = 'mcp-service-levels';
|
|
103
|
+
/**
|
|
104
|
+
* Its panels, by the ids the OTEL service gives them. A contract test there
|
|
105
|
+
* holds them, so a panel renamed on the service fails a test rather than
|
|
106
|
+
* reading here as nothing measured.
|
|
107
|
+
*/
|
|
108
|
+
export const MCP_SERVICE_LEVEL_PANELS = {
|
|
109
|
+
callsByOutcome: 'mcp-calls-by-outcome',
|
|
110
|
+
callDurationP95: 'mcp-call-duration-p95',
|
|
111
|
+
tasksByStatus: 'mcp-tasks-by-status',
|
|
112
|
+
sandboxLaunchP95: 'sandbox-launch_seconds-by-provider-p95',
|
|
113
|
+
sandboxLaunches: 'sandbox-launch_seconds-by-provider-count',
|
|
114
|
+
};
|
|
115
|
+
const TERMINAL_STATUSES = ['completed', 'failed', 'cancelled'];
|
|
97
116
|
/** The nearest-rank percentile of a sample; `null` for an empty one. */
|
|
98
117
|
export const percentile = (values, fraction) => {
|
|
99
118
|
if (values.length === 0) {
|
|
@@ -112,45 +131,39 @@ const isUnavailable = (span) => {
|
|
|
112
131
|
return status.startsWith('5') || rpc === '-32001';
|
|
113
132
|
};
|
|
114
133
|
/**
|
|
115
|
-
* The SLIs
|
|
134
|
+
* The SLIs from the service levels dashboard — the platform-wide reading.
|
|
116
135
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
136
|
+
* Its panels arrive summarised: calls and tasks as how much each counter grew
|
|
137
|
+
* over the window, the two p95s from their histograms' buckets. The call
|
|
138
|
+
* duration is recorded in seconds and shown in milliseconds. A panel with
|
|
139
|
+
* nothing in it reads as not measured, never as a zero.
|
|
120
140
|
*/
|
|
121
|
-
export const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
for (const point of launches) {
|
|
138
|
-
const provider = attribute(point, 'provider') || 'unknown';
|
|
139
|
-
byProvider.set(provider, [...(byProvider.get(provider) ?? []), point.value]);
|
|
140
|
-
}
|
|
141
|
-
const p95SandboxLaunchSeconds = {};
|
|
142
|
-
for (const [provider, values] of byProvider) {
|
|
143
|
-
const p95 = percentile(values, 0.95);
|
|
144
|
-
if (p95 !== null) {
|
|
145
|
-
p95SandboxLaunchSeconds[provider] = p95;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
141
|
+
export const summarizeServiceLevels = (data) => {
|
|
142
|
+
const panels = new Map(data.panels.map(panel => [panel.id, panel]));
|
|
143
|
+
const byLabel = (id) => Object.fromEntries((panels.get(id)?.series ?? [])
|
|
144
|
+
.filter(entry => entry.summary !== null)
|
|
145
|
+
.map(entry => [entry.label, entry.summary]));
|
|
146
|
+
const total = (values) => Object.values(values).reduce((sum, value) => sum + value, 0);
|
|
147
|
+
const calls = byLabel(MCP_SERVICE_LEVEL_PANELS.callsByOutcome);
|
|
148
|
+
const totalCalls = total(calls);
|
|
149
|
+
const failedCalls = (calls.error ?? 0) + (calls.unavailable ?? 0);
|
|
150
|
+
const tasks = byLabel(MCP_SERVICE_LEVEL_PANELS.tasksByStatus);
|
|
151
|
+
const terminal = TERMINAL_STATUSES.reduce((sum, status) => sum + (tasks[status] ?? 0), 0);
|
|
152
|
+
const p95Seconds = byLabel(MCP_SERVICE_LEVEL_PANELS.callDurationP95)[''];
|
|
153
|
+
const p95SandboxLaunchSeconds = Object.fromEntries(Object.entries(byLabel(MCP_SERVICE_LEVEL_PANELS.sandboxLaunchP95)).map(([provider, seconds]) => [
|
|
154
|
+
provider || 'unknown',
|
|
155
|
+
seconds,
|
|
156
|
+
]));
|
|
148
157
|
return {
|
|
149
158
|
availability: totalCalls > 0 ? (totalCalls - failedCalls) / totalCalls : null,
|
|
150
|
-
p95CallDurationMs:
|
|
151
|
-
taskSuccessRate:
|
|
159
|
+
p95CallDurationMs: p95Seconds === undefined ? null : p95Seconds * 1000,
|
|
160
|
+
taskSuccessRate: terminal > 0 ? (tasks.completed ?? 0) / terminal : null,
|
|
152
161
|
p95SandboxLaunchSeconds,
|
|
153
|
-
samples: {
|
|
162
|
+
samples: {
|
|
163
|
+
calls: totalCalls,
|
|
164
|
+
tasks: terminal,
|
|
165
|
+
launches: total(byLabel(MCP_SERVICE_LEVEL_PANELS.sandboxLaunches)),
|
|
166
|
+
},
|
|
154
167
|
};
|
|
155
168
|
};
|
|
156
169
|
/**
|
|
@@ -183,21 +196,21 @@ export const summarizeRequestSpans = (spans, filters = {}) => {
|
|
|
183
196
|
/** The four SLIs and the catalog, for everyone or for one agent or organization. */
|
|
184
197
|
export const fetchMcpMetrics = async (token, filters = {}, otelUrl = DEFAULT_SERVICE_URLS.OTEL) => {
|
|
185
198
|
const limit = filters.limit ?? 500;
|
|
186
|
-
const names = Object.values(MCP_SLI_METRICS);
|
|
187
|
-
const pages = await Promise.all(names.map(name => queryMetrics(token, { metricName: name, serviceName: serviceNameFor(name), limit }, otelUrl)));
|
|
188
|
-
const metrics = {};
|
|
189
|
-
names.forEach((name, index) => {
|
|
190
|
-
metrics[name] = pages[index].data ?? [];
|
|
191
|
-
});
|
|
192
199
|
const scoped = Boolean(filters.agent || filters.org);
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
const [levels, names, traces] = await Promise.all([
|
|
201
|
+
scoped
|
|
202
|
+
? undefined
|
|
203
|
+
: getDashboardData(token, MCP_SERVICE_LEVELS_DASHBOARD, { since: filters.since ? new Date(filters.since) : undefined }, otelUrl),
|
|
204
|
+
listMetricNames(token, otelUrl),
|
|
205
|
+
scoped ? listTraces(token, { serviceName: MCP_GATEWAY_SERVICE_NAME, limit }, otelUrl) : undefined,
|
|
206
|
+
]);
|
|
207
|
+
const reported = new Set((names.data ?? []).map(row => row.metric_name));
|
|
208
|
+
const spans = traces?.data ?? [];
|
|
196
209
|
return {
|
|
197
210
|
filters,
|
|
198
|
-
|
|
211
|
+
reporting: MCP_METRIC_CATALOG.filter(name => reported.has(name)),
|
|
199
212
|
spans,
|
|
200
|
-
slis:
|
|
213
|
+
slis: levels ? summarizeServiceLevels(levels) : summarizeRequestSpans(spans, filters),
|
|
201
214
|
readAt: new Date().toISOString(),
|
|
202
215
|
};
|
|
203
216
|
};
|