@opengeni/api-router 0.30.3 → 2.2.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth.d.ts +6 -0
- package/dist/{chunk-WCXHI3FF.js → chunk-3TP54PPX.js} +26692 -15389
- package/dist/chunk-3TP54PPX.js.map +1 -0
- package/dist/codemode.d.ts +8 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-authority-owner.d.ts +59 -0
- package/dist/connection-ownership.d.ts +47 -0
- package/dist/controller-data-plane.d.ts +16 -0
- package/dist/editable-artifact-websocket.d.ts +5 -15
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/http/api-error.d.ts +10 -0
- package/dist/http/cors.d.ts +5 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/sse.d.ts +2 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +326 -82
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +17 -0
- package/dist/integrations/fiken.d.ts +7 -2
- package/dist/integrations/google-drive.d.ts +18 -0
- package/dist/integrations/oauth-client.d.ts +22 -3
- package/dist/integrations/oauth-profiles.d.ts +195 -0
- package/dist/integrations/personal-github-repositories.d.ts +45 -0
- package/dist/integrations/personal-github.d.ts +40 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +8 -0
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +157 -9
- package/dist/integrations/slack-interactions.d.ts +39 -5
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/social-oauth.d.ts +5 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/documents.d.ts +1 -0
- package/dist/mcp/remember.d.ts +28 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-view.d.ts +4 -4
- package/dist/mcp/server.d.ts +15 -2
- package/dist/mcp/session-view.d.ts +4 -0
- package/dist/mcp/session-wait.d.ts +137 -0
- package/dist/routes/automations.d.ts +4 -0
- package/dist/routes/billing.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +4 -1
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/connection-authorities.d.ts +3 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/sessions.d.ts +1 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspaces.d.ts +19 -0
- package/dist/sandbox/channel-a.d.ts +11 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +2 -0
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +10 -20
- package/dist/sandbox/viewer.d.ts +24 -1
- package/dist/sandbox-file-artifacts.d.ts +11 -0
- package/dist/scheduled-task-deletion.d.ts +14 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/package.json +20 -18
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +161 -23
- package/src/auth/managed-auth.ts +25 -3
- package/src/codemode.ts +53 -23
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-authority-owner.ts +61 -0
- package/src/connection-ownership.ts +180 -0
- package/src/controller-data-plane.ts +47 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +28 -3
- package/src/editable-artifact-production.ts +13 -7
- package/src/editable-artifact-websocket.ts +11 -18
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/http/api-error.ts +28 -0
- package/src/http/auth.ts +4 -0
- package/src/http/cors.ts +35 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/sse.ts +112 -66
- package/src/index.ts +46 -6
- package/src/integrations/atlassian.ts +146 -35
- package/src/integrations/fiken.ts +102 -22
- package/src/integrations/google-drive.ts +301 -92
- package/src/integrations/oauth-client.ts +272 -143
- package/src/integrations/oauth-profiles.ts +477 -0
- package/src/integrations/personal-github-repositories.ts +445 -0
- package/src/integrations/personal-github.ts +705 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +121 -5
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +342 -57
- package/src/integrations/slack-interactions.ts +1898 -259
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/social-oauth.ts +25 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/mcp/company-brain-governed-writes.ts +262 -0
- package/src/mcp/company-profile-agent-admin.ts +205 -0
- package/src/mcp/documents.ts +37 -6
- package/src/mcp/files.ts +16 -1
- package/src/mcp/remember.ts +181 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-view.ts +1 -0
- package/src/mcp/server.ts +719 -369
- package/src/mcp/session-view.ts +54 -0
- package/src/mcp/session-wait.ts +554 -0
- package/src/model-catalog.ts +20 -12
- package/src/routes/api-integrations.ts +4 -0
- package/src/routes/api-keys.ts +1 -0
- package/src/routes/automations.ts +534 -0
- package/src/routes/billing.ts +57 -1
- package/src/routes/browser-sessions.ts +329 -79
- package/src/routes/channels.ts +17 -1
- package/src/routes/codex.ts +50 -0
- package/src/routes/company-brain.ts +367 -0
- package/src/routes/company-profile.ts +1 -1
- package/src/routes/computer-sessions.ts +320 -93
- package/src/routes/connection-authorities.ts +139 -0
- package/src/routes/connections.ts +144 -34
- package/src/routes/documents.ts +353 -4
- package/src/routes/editable-artifacts.ts +11 -3
- package/src/routes/enrollments.ts +116 -26
- package/src/routes/environments.ts +127 -55
- package/src/routes/files.ts +72 -11
- package/src/routes/install.ts +82 -28
- package/src/routes/integration-facets.ts +29 -0
- package/src/routes/interaction-resources.ts +20 -2
- package/src/routes/machines.ts +275 -13
- package/src/routes/organization-memberships.ts +717 -28
- package/src/routes/packs.ts +4 -3
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +319 -0
- package/src/routes/pr-review.ts +531 -0
- package/src/routes/rigs.ts +104 -39
- package/src/routes/scheduled-tasks.ts +19 -21
- package/src/routes/sessions.ts +775 -38
- package/src/routes/social.ts +14 -2
- package/src/routes/supergrok.ts +23 -4
- package/src/routes/transcription-recordings.ts +8 -2
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/workspace-artifacts.ts +4 -1
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-learning.ts +228 -0
- package/src/routes/workspaces.ts +59 -6
- package/src/sandbox/auth-callout.ts +38 -12
- package/src/sandbox/channel-a.ts +242 -19
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +15 -3
- package/src/sandbox/machines.ts +186 -64
- package/src/sandbox/metrics-ingestion.ts +220 -58
- package/src/sandbox/viewer.ts +243 -19
- package/src/sandbox-file-artifacts.ts +329 -0
- package/src/scheduled-task-deletion.ts +127 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/dist/chunk-WCXHI3FF.js.map +0 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type { Session } from "@opengeni/contracts";
|
|
2
|
+
import type { SlackHomeBlock } from "./slack-bot";
|
|
3
|
+
|
|
4
|
+
const ATTENTION_LIMIT = 5;
|
|
5
|
+
const ACTIVE_LIMIT = 8;
|
|
6
|
+
const RECENT_LIMIT = 5;
|
|
7
|
+
|
|
8
|
+
const ACTIVE_STATUSES = new Set<Session["status"]>([
|
|
9
|
+
"queued",
|
|
10
|
+
"running",
|
|
11
|
+
"recovering",
|
|
12
|
+
"waiting_capacity",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export type SlackAppHomeOpenedEvent = {
|
|
16
|
+
eventId: string;
|
|
17
|
+
slackTeamId: string;
|
|
18
|
+
slackUserId: string;
|
|
19
|
+
viewHash: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function slackAppHomeOpenedEvent(payload: unknown): SlackAppHomeOpenedEvent | null {
|
|
23
|
+
const envelope = record(payload);
|
|
24
|
+
const event = record(envelope?.event);
|
|
25
|
+
if (envelope?.type !== "event_callback" || event?.type !== "app_home_opened") return null;
|
|
26
|
+
if (event.tab !== undefined && event.tab !== "home") return null;
|
|
27
|
+
const eventId = boundedId(envelope.event_id, 256);
|
|
28
|
+
const slackTeamId = boundedId(envelope.team_id, 64);
|
|
29
|
+
const slackUserId = boundedId(event.user, 64);
|
|
30
|
+
const viewHash = boundedOptionalId(record(event.view)?.hash, 256);
|
|
31
|
+
if (!eventId || !slackTeamId || !slackUserId) return null;
|
|
32
|
+
return { eventId, slackTeamId, slackUserId, viewHash };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** URL-only App Home buttons are acknowledged but never become Slack commands. */
|
|
36
|
+
export function isSlackAppHomeLinkAction(payload: unknown): boolean {
|
|
37
|
+
const envelope = record(payload);
|
|
38
|
+
if (envelope?.type !== "block_actions" || !Array.isArray(envelope.actions)) return false;
|
|
39
|
+
if (envelope.actions.length !== 1) return false;
|
|
40
|
+
const action = record(envelope.actions[0]);
|
|
41
|
+
const actionId = boundedId(action?.action_id, 255);
|
|
42
|
+
if (!actionId) return false;
|
|
43
|
+
return (
|
|
44
|
+
actionId === "opengeni.home.open_all" ||
|
|
45
|
+
actionId === "opengeni.home.connect" ||
|
|
46
|
+
/^opengeni\.home\.open\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(
|
|
47
|
+
actionId,
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildSlackAppHomeBlocks(input: {
|
|
53
|
+
sessions: readonly Session[];
|
|
54
|
+
workspaceUrl: string | null;
|
|
55
|
+
sessionUrl: (sessionId: string) => string | null;
|
|
56
|
+
nowMs?: number;
|
|
57
|
+
}): SlackHomeBlock[] {
|
|
58
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
59
|
+
const sessions = dedupeSessions(input.sessions);
|
|
60
|
+
const attention = sessions.filter(sessionNeedsAttention).slice(0, ATTENTION_LIMIT);
|
|
61
|
+
const attentionIds = new Set(attention.map((session) => session.id));
|
|
62
|
+
const active = sessions
|
|
63
|
+
.filter((session) => !attentionIds.has(session.id) && sessionIsActive(session))
|
|
64
|
+
.slice(0, ACTIVE_LIMIT);
|
|
65
|
+
const activeIds = new Set(active.map((session) => session.id));
|
|
66
|
+
const recent = sessions
|
|
67
|
+
.filter(
|
|
68
|
+
(session) =>
|
|
69
|
+
!attentionIds.has(session.id) &&
|
|
70
|
+
!activeIds.has(session.id) &&
|
|
71
|
+
(session.status === "idle" || session.status === "cancelled"),
|
|
72
|
+
)
|
|
73
|
+
.slice(0, RECENT_LIMIT);
|
|
74
|
+
|
|
75
|
+
const blocks: SlackHomeBlock[] = [
|
|
76
|
+
{
|
|
77
|
+
type: "header",
|
|
78
|
+
block_id: "opengeni_home_header",
|
|
79
|
+
text: { type: "plain_text", text: "Your OpenGeni tasks", emoji: true },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "context",
|
|
83
|
+
block_id: "opengeni_home_context",
|
|
84
|
+
elements: [
|
|
85
|
+
{
|
|
86
|
+
type: "mrkdwn",
|
|
87
|
+
text: "Private to you · refreshed from your current OpenGeni access",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
appendSessionGroup(blocks, "Needs your input", attention, input.sessionUrl, nowMs);
|
|
94
|
+
appendSessionGroup(blocks, "Active", active, input.sessionUrl, nowMs);
|
|
95
|
+
appendSessionGroup(blocks, "Recent", recent, input.sessionUrl, nowMs);
|
|
96
|
+
|
|
97
|
+
if (attention.length + active.length + recent.length === 0) {
|
|
98
|
+
blocks.push(
|
|
99
|
+
{ type: "divider" },
|
|
100
|
+
{
|
|
101
|
+
type: "section",
|
|
102
|
+
block_id: "opengeni_home_empty",
|
|
103
|
+
text: {
|
|
104
|
+
type: "mrkdwn",
|
|
105
|
+
text: "*You're all caught up.*\nNo active or recent tasks are visible to this account.",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (input.workspaceUrl) {
|
|
111
|
+
blocks.push({
|
|
112
|
+
type: "actions",
|
|
113
|
+
block_id: "opengeni_home_actions",
|
|
114
|
+
elements: [
|
|
115
|
+
{
|
|
116
|
+
type: "button",
|
|
117
|
+
action_id: "opengeni.home.open_all",
|
|
118
|
+
text: { type: "plain_text", text: "Open OpenGeni", emoji: true },
|
|
119
|
+
url: input.workspaceUrl,
|
|
120
|
+
style: "primary",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return blocks;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function buildSlackAppHomeAccessBlocks(input: {
|
|
129
|
+
title: string;
|
|
130
|
+
message: string;
|
|
131
|
+
actionLabel?: string;
|
|
132
|
+
actionUrl?: string | null;
|
|
133
|
+
}): SlackHomeBlock[] {
|
|
134
|
+
const blocks: SlackHomeBlock[] = [
|
|
135
|
+
{
|
|
136
|
+
type: "header",
|
|
137
|
+
block_id: "opengeni_home_header",
|
|
138
|
+
text: { type: "plain_text", text: "OpenGeni", emoji: true },
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: "section",
|
|
142
|
+
block_id: "opengeni_home_access",
|
|
143
|
+
text: {
|
|
144
|
+
type: "mrkdwn",
|
|
145
|
+
text: `*${escapeSlackMrkdwn(input.title)}*\n${escapeSlackMrkdwn(input.message)}`,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
if (input.actionUrl) {
|
|
150
|
+
blocks.push({
|
|
151
|
+
type: "actions",
|
|
152
|
+
block_id: "opengeni_home_access_actions",
|
|
153
|
+
elements: [
|
|
154
|
+
{
|
|
155
|
+
type: "button",
|
|
156
|
+
action_id: "opengeni.home.connect",
|
|
157
|
+
text: {
|
|
158
|
+
type: "plain_text",
|
|
159
|
+
text: input.actionLabel?.slice(0, 75) || "Open OpenGeni",
|
|
160
|
+
emoji: true,
|
|
161
|
+
},
|
|
162
|
+
url: input.actionUrl,
|
|
163
|
+
style: "primary",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return blocks;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function appendSessionGroup(
|
|
172
|
+
blocks: SlackHomeBlock[],
|
|
173
|
+
heading: string,
|
|
174
|
+
sessions: readonly Session[],
|
|
175
|
+
sessionUrl: (sessionId: string) => string | null,
|
|
176
|
+
nowMs: number,
|
|
177
|
+
): void {
|
|
178
|
+
if (sessions.length === 0) return;
|
|
179
|
+
blocks.push(
|
|
180
|
+
{ type: "divider" },
|
|
181
|
+
{
|
|
182
|
+
type: "section",
|
|
183
|
+
block_id: `opengeni_home_group_${heading.toLowerCase().replaceAll(" ", "_")}`,
|
|
184
|
+
text: { type: "mrkdwn", text: `*${heading}*` },
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
for (const session of sessions) {
|
|
188
|
+
const url = sessionUrl(session.id);
|
|
189
|
+
const title = (session.title || session.initialMessage || "Untitled task").slice(0, 180);
|
|
190
|
+
blocks.push({
|
|
191
|
+
type: "section",
|
|
192
|
+
block_id: `opengeni_home_session_${session.id}`,
|
|
193
|
+
text: {
|
|
194
|
+
type: "plain_text",
|
|
195
|
+
text: `${title}\n${statusLabel(session)} · ${relativeUpdatedAt(session.updatedAt, nowMs)}`,
|
|
196
|
+
emoji: true,
|
|
197
|
+
},
|
|
198
|
+
...(url
|
|
199
|
+
? {
|
|
200
|
+
accessory: {
|
|
201
|
+
type: "button",
|
|
202
|
+
action_id: `opengeni.home.open.${session.id}`,
|
|
203
|
+
text: { type: "plain_text", text: "Open", emoji: true },
|
|
204
|
+
url,
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
: {}),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function dedupeSessions(sessions: readonly Session[]): Session[] {
|
|
213
|
+
const seen = new Set<string>();
|
|
214
|
+
return [...sessions]
|
|
215
|
+
.sort(
|
|
216
|
+
(left, right) =>
|
|
217
|
+
right.updatedAt.localeCompare(left.updatedAt) || left.id.localeCompare(right.id),
|
|
218
|
+
)
|
|
219
|
+
.filter((session) => {
|
|
220
|
+
if (seen.has(session.id)) return false;
|
|
221
|
+
seen.add(session.id);
|
|
222
|
+
return true;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function sessionNeedsAttention(session: Session): boolean {
|
|
227
|
+
return (
|
|
228
|
+
session.status === "requires_action" ||
|
|
229
|
+
session.status === "failed" ||
|
|
230
|
+
(session.treeStats?.attentionDescendants ?? 0) > 0 ||
|
|
231
|
+
(session.treeStats?.failedDescendants ?? 0) > 0
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function sessionIsActive(session: Session): boolean {
|
|
236
|
+
return (
|
|
237
|
+
ACTIVE_STATUSES.has(session.status) ||
|
|
238
|
+
(session.treeStats?.runningDescendants ?? 0) > 0 ||
|
|
239
|
+
(session.treeStats?.queuedDescendants ?? 0) > 0
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function statusLabel(session: Session): string {
|
|
244
|
+
if ((session.treeStats?.attentionDescendants ?? 0) > 0) return "A child task needs input";
|
|
245
|
+
if ((session.treeStats?.failedDescendants ?? 0) > 0) return "A child task needs review";
|
|
246
|
+
if ((session.treeStats?.runningDescendants ?? 0) > 0) return "Child task running";
|
|
247
|
+
if ((session.treeStats?.queuedDescendants ?? 0) > 0) return "Child task queued";
|
|
248
|
+
switch (session.status) {
|
|
249
|
+
case "queued":
|
|
250
|
+
return "Queued";
|
|
251
|
+
case "running":
|
|
252
|
+
return "Running";
|
|
253
|
+
case "requires_action":
|
|
254
|
+
return "Approval or answer needed";
|
|
255
|
+
case "recovering":
|
|
256
|
+
return "Recovering";
|
|
257
|
+
case "waiting_capacity":
|
|
258
|
+
return "Waiting for capacity";
|
|
259
|
+
case "idle":
|
|
260
|
+
return "Completed";
|
|
261
|
+
case "failed":
|
|
262
|
+
return "Needs review";
|
|
263
|
+
case "cancelled":
|
|
264
|
+
return "Cancelled";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function relativeUpdatedAt(value: string, nowMs: number): string {
|
|
269
|
+
const timestamp = Date.parse(value);
|
|
270
|
+
if (!Number.isFinite(timestamp)) return "recently updated";
|
|
271
|
+
const elapsedMinutes = Math.max(0, Math.floor((nowMs - timestamp) / 60_000));
|
|
272
|
+
if (elapsedMinutes < 1) return "updated now";
|
|
273
|
+
if (elapsedMinutes < 60) return `updated ${elapsedMinutes}m ago`;
|
|
274
|
+
const elapsedHours = Math.floor(elapsedMinutes / 60);
|
|
275
|
+
if (elapsedHours < 24) return `updated ${elapsedHours}h ago`;
|
|
276
|
+
return `updated ${Math.floor(elapsedHours / 24)}d ago`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function escapeSlackMrkdwn(value: string): string {
|
|
280
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function boundedId(value: unknown, maxBytes: number): string | null {
|
|
284
|
+
return typeof value === "string" &&
|
|
285
|
+
value.length > 0 &&
|
|
286
|
+
Buffer.byteLength(value, "utf8") <= maxBytes
|
|
287
|
+
? value
|
|
288
|
+
: null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function boundedOptionalId(value: unknown, maxBytes: number): string | null {
|
|
292
|
+
if (value === undefined || value === null) return null;
|
|
293
|
+
return boundedId(value, maxBytes);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function record(value: unknown): Record<string, unknown> | null {
|
|
297
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
298
|
+
? (value as Record<string, unknown>)
|
|
299
|
+
: null;
|
|
300
|
+
}
|