@neta-art/cohub 5.2.0 → 5.3.1
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/chunks/http.d.ts +45 -2
- package/dist/chunks/http.js +240 -13
- package/dist/chunks/transport.js +38 -5
- package/dist/chunks/websocket.d.ts +94 -4
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +100 -3
- package/dist/index.js +400 -40
- package/dist/protocol/dist/provenance.js +1 -0
- package/docs/work-runtime-guide.md +32 -0
- package/package.json +1 -1
package/dist/chunks/http.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as CreateSpacePromptInput, $
|
|
1
|
+
import { $ as CreateSpacePromptInput, $n as SpaceFsTreeResponse, Ai as WorkContentKind, Ar as SpaceUsageResponse, At as Permission, Ba as ContentBlock, Bi as UiCommandError, Br as ChannelHealth, Bt as ReferenceAggregateGroupBy, Ca as MessageRecord, Ci as BoardAwarenessUpdate, Cn as SpaceCommerceBenefit, Ct as LabelResourceType, Da as SessionTurnRecord, Dn as SpaceCommerceOrder, Dt as ModelCatalogEntry, Er as SpaceTurnAuthorFilter, Et as MeResponse, Fn as SpaceCreateResponse, Fr as UserRulesResponse, Ft as PublicUserPageResponse, Gt as ReferenceQueryableType, Hi as UiCommandStatus, Hn as SpaceFsCreateUploadResponse, Ht as ReferenceDirection, Ia as SpaceCompletionResult, In as SpaceDefaultResponse, J as ClaimReferralResponse, Jn as SpaceFsMoveInput, Jt as ReferralDashboard, K as CheckpointDiffSummary, La as SpaceCompletionStreamEvent, Ln as SpaceEnvInput, Mn as SpaceConfigInput, Mr as TaskRunDetailResponse, Nn as SpaceConfigResponse, Nr as TaskRunRecord, Nt as PromptTemplateCatalogResponse, Oa as SpaceTurnsResponse, On as SpaceCommerceProduct, Ot as PatchResourceLabelsInput, Pa as CreateSpaceCompletionInput, Pn as SpaceConfigUpdateResponse, Pr as UserProfile, Pt as PublicReferral, Qi as BoardCapabilities, Qn as SpaceFsReadFilesResponse, R as Channel, Ra as Usage, Ri as UiCommand, Rn as SpaceFsCompleteUploadInput, Rr as UserSessionsResponse, Sa as SpacePublicEndpoints, Sn as SpaceCheckpointDetailResponse, Tr as SpaceSessionsResponse, Ut as ReferenceKind, V as CheckpointDiffFileResponse, Vi as UiCommandRecord, Vn as SpaceFsCreateUploadInput, Vt as ReferenceAggregateResponse, Wa as RequestSource, Wt as ReferenceQueryResponse, X as CreateInvitationResponse, Y as CreateInvitationInput, Yn as SpaceFsPreparingFile, Z as CreateSpaceInput, Zi as BoardBootstrap, _a as BoardTransaction, _r as SpacePresenceSnapshot, a as WebsocketClientOptions, an as SessionRecord, at as CursorPageInfo, br as SpaceRecord, ci as BoardTransactionAppliedEvent$1, cn as SessionTurnSignedUrlsResponse, d as BatchUserProfilesResponse, di as RealtimePatchOperation, dn as SessionTurnsPaginatedResponse, dt as GlobalSearchType, ea as BoardCreateInput, et as CreateSpacePromptResponse, fr as SpaceMember, ft as InvitationDetail, gn as SpaceAccessPolicy, gr as SpacePendingDiffSummary, hr as SpacePendingDiffFileResponse, ia as BoardInspectInput, ii as GenerationModelDeclaration, in as SessionMessagesResponse, it as CronJobUpdatePatch, kn as SpaceCommerceProductBenefitBinding, kt as PatchResourceLabelsResponse, l as AcceptInvitationResponse, ln as SessionTurnStreamSnapshotResponse, lr as SpaceInvitationListResponse, lt as GlobalSearchResponse, mr as SpaceModListItem, nn as SessionMessageResponse, oi as BoardAwarenessUpdatedEvent$1, on as SessionTurnIndexResponse, or as SpaceFsUploadResponse, pa as BoardPlaybackSnapshot, pn as SkillCatalogResponse, q as CheckpointRecord, qn as SpaceFsFileResponse, r as WebsocketClient, ri as GenerationContentBlock, rn as SessionMessagesPaginatedResponse, rt as CronJobRecord, s as WebsocketEventPayload, si as BoardPlaybackChangedEvent$1, sn as SessionTurnResponse, sr as SpaceFsWriteFileInput, tt as CreateSpaceSessionInput, ua as BoardPlaybackCommand, un as SessionTurnWindowResponse, va as BoardValidationResult, vi as SessionTurnPatchEvent, vt as LabelAssignmentRecord, wa as SessionForkRecord, wi as WorkArtifactDescriptor, wn as SpaceCommerceBuyerProfile, xr as SpaceRole, xt as LabelListItem, yr as SpacePublicProfile, yt as LabelItemsResponse, za as BillingPayload, zn as SpaceFsCompleteUploadResponse, zr as ChannelConfig } from "./websocket.js";
|
|
2
2
|
import { n as CohubEnvironment } from "./environment.js";
|
|
3
3
|
import { a as VoiceInputCreateOptions } from "./voice-input.js";
|
|
4
4
|
//#region ../protocol/dist/model/status.d.ts
|
|
@@ -351,11 +351,19 @@ type AccessTokenRequestOptions = {
|
|
|
351
351
|
rejectedToken?: string | null;
|
|
352
352
|
};
|
|
353
353
|
type AuthSessionVersion = string | number | null;
|
|
354
|
+
type HttpTraceContext = {
|
|
355
|
+
requestId: string | null;
|
|
356
|
+
traceId: string | null;
|
|
357
|
+
spanId: string | null;
|
|
358
|
+
traceparent: string | null;
|
|
359
|
+
};
|
|
354
360
|
type UnauthorizedContext = {
|
|
355
361
|
/** Compare a known candidate without exposing the rejected bearer token. */
|
|
356
362
|
matchesRejectedToken(candidate: string | null | undefined): boolean;
|
|
357
363
|
/** Auth state observed before the rejected request resolved its token. */
|
|
358
364
|
authSessionVersion?: AuthSessionVersion;
|
|
365
|
+
/** Safe identifiers returned by the final rejected API response. */
|
|
366
|
+
traceContext?: HttpTraceContext;
|
|
359
367
|
};
|
|
360
368
|
type CohubClientOptions = {
|
|
361
369
|
env?: CohubEnvironment;
|
|
@@ -389,9 +397,12 @@ declare class HttpError extends Error {
|
|
|
389
397
|
readonly unauthorizedHandled: boolean;
|
|
390
398
|
/** Auth state observed before the rejected request resolved its token. */
|
|
391
399
|
readonly authSessionVersion?: AuthSessionVersion;
|
|
400
|
+
/** Safe identifiers returned by the rejected API response. */
|
|
401
|
+
readonly traceContext?: HttpTraceContext;
|
|
392
402
|
constructor(message: string, status: number, body: unknown, options?: {
|
|
393
403
|
unauthorizedHandled?: boolean;
|
|
394
404
|
authSessionVersion?: AuthSessionVersion;
|
|
405
|
+
traceContext?: HttpTraceContext;
|
|
395
406
|
});
|
|
396
407
|
}
|
|
397
408
|
/**
|
|
@@ -1616,6 +1627,37 @@ declare class TasksApi {
|
|
|
1616
1627
|
}>;
|
|
1617
1628
|
}
|
|
1618
1629
|
//#endregion
|
|
1630
|
+
//#region src/apis/ui-commands.d.ts
|
|
1631
|
+
type CreateUiCommandInput = {
|
|
1632
|
+
command: UiCommand;
|
|
1633
|
+
commandId?: string;
|
|
1634
|
+
targetClientId?: string;
|
|
1635
|
+
};
|
|
1636
|
+
type WaitForUiCommandOptions = {
|
|
1637
|
+
timeoutMs?: number;
|
|
1638
|
+
pollIntervalMs?: number;
|
|
1639
|
+
signal?: AbortSignal;
|
|
1640
|
+
};
|
|
1641
|
+
declare class UiCommandsApi {
|
|
1642
|
+
private readonly transport;
|
|
1643
|
+
constructor(transport: HttpTransport);
|
|
1644
|
+
create(input: CreateUiCommandInput): Promise<{
|
|
1645
|
+
command: UiCommandRecord;
|
|
1646
|
+
}>;
|
|
1647
|
+
get(commandId: string): Promise<{
|
|
1648
|
+
command: UiCommandRecord;
|
|
1649
|
+
}>;
|
|
1650
|
+
reportResult(commandId: string, input: {
|
|
1651
|
+
status: UiCommandStatus;
|
|
1652
|
+
result?: unknown;
|
|
1653
|
+
error?: UiCommandError | null;
|
|
1654
|
+
}): Promise<{
|
|
1655
|
+
command: UiCommandRecord;
|
|
1656
|
+
}>;
|
|
1657
|
+
run(input: CreateUiCommandInput, options?: WaitForUiCommandOptions): Promise<UiCommandRecord>;
|
|
1658
|
+
wait(commandId: string, options?: WaitForUiCommandOptions): Promise<UiCommandRecord>;
|
|
1659
|
+
}
|
|
1660
|
+
//#endregion
|
|
1619
1661
|
//#region src/apis/user.d.ts
|
|
1620
1662
|
declare class UserApi {
|
|
1621
1663
|
private readonly transport;
|
|
@@ -1991,10 +2033,11 @@ declare class CohubHttpClient {
|
|
|
1991
2033
|
readonly referrals: ReferralsApi;
|
|
1992
2034
|
readonly works: WorksApi;
|
|
1993
2035
|
readonly workCommerce: WorkCommerceApi;
|
|
2036
|
+
readonly ui: UiCommandsApi;
|
|
1994
2037
|
private readonly transport;
|
|
1995
2038
|
constructor(options?: CohubClientOptions);
|
|
1996
2039
|
space(spaceId: string): SpaceClient;
|
|
1997
2040
|
}
|
|
1998
2041
|
declare const createHttpClient: (options?: CohubClientOptions) => CohubHttpClient;
|
|
1999
2042
|
//#endregion
|
|
2000
|
-
export {
|
|
2043
|
+
export { SpaceEventName as $, HttpTransport as $t, WorkViewSource as A, ReferenceResourceSelector as At, TasksApi as B, UploadChatAttachmentInput as Bt, WorkRecord as C, ListGenerationModelsResponse as Cn, SessionPatchApplyInput as Ct, WorkTargetType as D, SessionPatchStatus as Dt, WorkStatus as E, ModelStatusResponse as En, SessionPatchState as Et, UsersApi as F, PublicAssetMimeType as Ft, BoardSubscriptionHandlers as G, ModelsApi as Gt, BoardClient as H, UploadPublicAssetInput as Ht, UserApi as I, PublicAssetPurpose as It, BoardTransactionInput as J, ChannelsApi as Jt, BoardTransactionAppliedEvent as K, GenerationsApi as Kt, CreateUiCommandInput as L, PublicAssetUploadProgress as Lt, WorkVisibility as M, SearchApi as Mt, WorksApi as N, CreatePublicAssetUploadInput as Nt, WorkUpdateInput as O, createSessionPatchReducer as Ot, ReferralsApi as P, CreatePublicAssetUploadResponse as Pt, SpaceClient as Q, HttpTraceContext as Qt, UiCommandsApi as R, PublicAssetUploadProtocol as Rt, WorkPublicSpaceRecord as S, GenerationUsageBilling as Sn, parseAssistantMessageCommit as St, WorkSessionResponse as T, ModelStatusEntry as Tn, SessionPatchReducer as Tt, BoardEventName as U, SkillsApi as Ut, BoardAwarenessUpdatedEvent as V, UploadChatImageAttachmentInput as Vt, BoardPlaybackChangedEvent as W, PromptsApi as Wt, SessionSubscriptionHandlers as X, Fetch as Xt, SessionEventName as Y, CohubClientOptions as Yt, SpaceChannelBindingRecord as Z, HttpError as Zt, WorkExtractedPageMeta as _, resolveWorkTransport as _n, GenerationStreamSubscribeOptions as _t, WorkCommerceCreditConsumeResponse as a, ParentBridgeTransport as an, PublicInviteApi as at, WorkPresentationMeta as b, CreateGenerationTaskResponse as bn, SessionGenerationStreamClient as bt, WorkCommerceEntitlementsResponse as c, WorkRuntimeApi as cn, AssistantMessageCommit as ct, WorkCommercePurchaseResponse as d, WorkRuntimeContext as dn, GenerationStreamEvent as dt, RawHttpResponse as en, SpaceTurnListOptions as et, WorkAuthorizeResponse as f, WorkRuntimeModeConfig as fn, GenerationStreamFinalizedEvent as ft, WorkDetailResponse as g, createWorkRuntime as gn, GenerationStreamStateEvent as gt, WorkCreateInput as h, createSlugWorkIdResolver as hn, GenerationStreamOutOfSyncEvent as ht, WorkCommerceCheckoutStatus as i, sanitizeAccessToken as in, BuildSpacePathInput as it, WorkViewStatsResponse as j, ReferencesApi as jt, WorkVersionRecord as k, SessionAccessApi as kt, WorkCommerceOrder as l, WorkRuntimeCheckoutState as ln, GenerationStreamCommitEvent as lt, WorkContentDownload as m, WorkRuntimeTransport as mn, GenerationStreamLifecycleEvent as mt, createHttpClient as n, joinApiUrl as nn, WebSocketConnectionState as nt, WorkCommerceCreditConsumeStatus as o, PopupBrokerTransport as on, buildSpaceInvitePath as ot, WorkContent as p, WorkRuntimeRequestOptions as pn, GenerationStreamIntermediateMessage as pt, BoardTransactionError as q, CronJobsApi as qt, WorkCommerceApi as r, matchesUnauthorizedErrorToken as rn, BuildSpaceInvitePathInput as rt, WorkCommerceEntitlement as s, WorkIdResolver as sn, buildSpacePath as st, CohubHttpClient as t, UnauthorizedContext as tn, SpacesApi as tt, WorkCommerceProductResolveResponse as u, WorkRuntimeCheckoutStatus as un, GenerationStreamErrorEvent as ut, WorkGetResponse as v, SpaceStartupResponse as vn, GenerationStreamSubscriptionHandlers as vt, WorkResolveResponse as w, PublicGenerationDeclaration as wn, SessionPatchApplyResult as wt, WorkPublicOwnerRecord as x, GenerationTaskResult as xn, createSessionGenerationStreamClient as xt, WorkMeta as y, CreateGenerationTaskRequest as yn, GenerationStreamTurnUpdatedEvent as yt, WaitForUiCommandOptions as z, PublicAssetsApi as zt };
|
package/dist/chunks/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as getRealtimeSpaceRoom, T as getRealtimeBoardRoom, n as HttpTransport, t as HttpError } from "./transport.js";
|
|
2
2
|
import { a as resolveApiBaseUrl } from "./environment.js";
|
|
3
3
|
//#region src/apis/channels.ts
|
|
4
4
|
var ChannelsApi = class {
|
|
@@ -82,7 +82,7 @@ var CronJobsApi = class {
|
|
|
82
82
|
//#region src/apis/generations.ts
|
|
83
83
|
const DEFAULT_INTERVAL_MS = 1500;
|
|
84
84
|
const DEFAULT_TIMEOUT_MS = 1800 * 1e3;
|
|
85
|
-
function sleep(ms, signal) {
|
|
85
|
+
function sleep$1(ms, signal) {
|
|
86
86
|
if (signal?.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Generation wait aborted"));
|
|
87
87
|
return new Promise((resolve, reject) => {
|
|
88
88
|
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
@@ -147,7 +147,7 @@ var GenerationsApi = class {
|
|
|
147
147
|
}
|
|
148
148
|
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
149
149
|
if (remainingMs <= 0) throw new Error(`Generation task timed out after ${timeoutMs}ms`);
|
|
150
|
-
await sleep(Math.min(intervalMs, remainingMs), options.signal);
|
|
150
|
+
await sleep$1(Math.min(intervalMs, remainingMs), options.signal);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
async createAndWait(request, options) {
|
|
@@ -847,8 +847,8 @@ const createSessionPatchReducer = () => new SessionPatchReducer();
|
|
|
847
847
|
//#region src/session-generation-stream.ts
|
|
848
848
|
const SNAPSHOT_RECOVERY_TIMEOUT_MS = 2500;
|
|
849
849
|
const SNAPSHOT_RECOVERY_MAX_BUFFERED_EVENTS = 256;
|
|
850
|
-
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
851
|
-
const isContentBlockArray = (value) => Array.isArray(value) && value.every((item) => isRecord(item) && typeof item.type === "string");
|
|
850
|
+
const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
851
|
+
const isContentBlockArray = (value) => Array.isArray(value) && value.every((item) => isRecord$1(item) && typeof item.type === "string");
|
|
852
852
|
const getMessageKind = (message) => {
|
|
853
853
|
const kind = message.meta?.messageKind;
|
|
854
854
|
return typeof kind === "string" ? kind : null;
|
|
@@ -888,7 +888,7 @@ function parseAssistantMessageCommit(message) {
|
|
|
888
888
|
}
|
|
889
889
|
function messageRecordToIntermediate(message) {
|
|
890
890
|
if (!isContentBlockArray(message.content) || message.content.length === 0) return null;
|
|
891
|
-
const meta = isRecord(message.meta) ? message.meta : {};
|
|
891
|
+
const meta = isRecord$1(message.meta) ? message.meta : {};
|
|
892
892
|
return {
|
|
893
893
|
id: message.id,
|
|
894
894
|
sessionId: message.sessionId,
|
|
@@ -1006,9 +1006,9 @@ function mergeMessagesSharingToolUseId(messages) {
|
|
|
1006
1006
|
return merged;
|
|
1007
1007
|
}
|
|
1008
1008
|
function getCompactionPlacement(message) {
|
|
1009
|
-
const meta = isRecord(message.meta) ? message.meta : null;
|
|
1010
|
-
const compaction = isRecord(meta?.compaction) ? meta.compaction : null;
|
|
1011
|
-
const placement = isRecord(compaction?.placement) ? compaction.placement : null;
|
|
1009
|
+
const meta = isRecord$1(message.meta) ? message.meta : null;
|
|
1010
|
+
const compaction = isRecord$1(meta?.compaction) ? meta.compaction : null;
|
|
1011
|
+
const placement = isRecord$1(compaction?.placement) ? compaction.placement : null;
|
|
1012
1012
|
return meta?.messageKind === "compacted" ? {
|
|
1013
1013
|
beforeMessageId: typeof placement?.beforeMessageId === "string" ? placement.beforeMessageId : null,
|
|
1014
1014
|
sequence: typeof message.sequence === "number" ? message.sequence : null
|
|
@@ -1282,7 +1282,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
1282
1282
|
}
|
|
1283
1283
|
handlePersisted(event, handlers) {
|
|
1284
1284
|
const message = event.payload.message;
|
|
1285
|
-
if (!isRecord(message)) return;
|
|
1285
|
+
if (!isRecord$1(message)) return;
|
|
1286
1286
|
const commit = parseAssistantMessageCommit(message);
|
|
1287
1287
|
if (commit.kind === "intermediate") {
|
|
1288
1288
|
const intermediate = messageRecordToIntermediate(commit.message);
|
|
@@ -1317,7 +1317,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
1317
1317
|
}
|
|
1318
1318
|
handleFinalized(event, handlers) {
|
|
1319
1319
|
const turn = event.payload.turn;
|
|
1320
|
-
if (!isRecord(turn)) return;
|
|
1320
|
+
if (!isRecord$1(turn)) return;
|
|
1321
1321
|
const typedTurn = turn;
|
|
1322
1322
|
if (typedTurn.status === "interrupted") this.reducer.interrupt({
|
|
1323
1323
|
spaceId: this.spaceId,
|
|
@@ -1349,7 +1349,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
1349
1349
|
return;
|
|
1350
1350
|
case "session.turn.updated": {
|
|
1351
1351
|
const turn = event.payload.turn;
|
|
1352
|
-
if (!isRecord(turn)) return;
|
|
1352
|
+
if (!isRecord$1(turn)) return;
|
|
1353
1353
|
this.emit(handlers, {
|
|
1354
1354
|
type: "turn_updated",
|
|
1355
1355
|
turn,
|
|
@@ -2863,6 +2863,231 @@ var TasksApi = class {
|
|
|
2863
2863
|
}
|
|
2864
2864
|
};
|
|
2865
2865
|
//#endregion
|
|
2866
|
+
//#region ../protocol/dist/ui-command.js
|
|
2867
|
+
/**
|
|
2868
|
+
* Lets an agent drive the Cohub frontend that originated the work. Routing comes
|
|
2869
|
+
* from request provenance, never a caller-supplied target, so a command only
|
|
2870
|
+
* reaches the actor's own instances.
|
|
2871
|
+
*/
|
|
2872
|
+
const UI_COMMAND_VERSION = 1;
|
|
2873
|
+
/** Persisted and broadcast, so every field is capped; MAX_BYTES bounds the whole. */
|
|
2874
|
+
const UI_COMMAND_PAYLOAD_MAX_BYTES = 32 * 1024;
|
|
2875
|
+
const UI_COMMAND_MAX_BYTES = 40 * 1024;
|
|
2876
|
+
const UI_COMMAND_LAUNCH_MAX_LENGTH = 2048;
|
|
2877
|
+
const UI_COMMAND_DEFAULT_TIMEOUT_MS = 600 * 1e3;
|
|
2878
|
+
const UI_COMMAND_MAX_TIMEOUT_MS = 720 * 60 * 1e3;
|
|
2879
|
+
const UI_COMMAND_SETTLEMENT_GRACE_SECONDS = 600;
|
|
2880
|
+
/** Keeps pending commands reportable for the full wait window plus settlement grace. */
|
|
2881
|
+
const UI_COMMAND_PENDING_TTL_SECONDS = 43800;
|
|
2882
|
+
const UI_COMMAND_TERMINAL_TTL_SECONDS = 1800;
|
|
2883
|
+
const UI_COMMAND_TERMINAL_STATUSES = [
|
|
2884
|
+
"applied",
|
|
2885
|
+
"no_active_client",
|
|
2886
|
+
"ui_host_unavailable",
|
|
2887
|
+
"rejected",
|
|
2888
|
+
"unsupported",
|
|
2889
|
+
"timeout"
|
|
2890
|
+
];
|
|
2891
|
+
const isTerminalUiCommandStatus = (status) => UI_COMMAND_TERMINAL_STATUSES.includes(status);
|
|
2892
|
+
const METHOD_RE = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
2893
|
+
const isUiSurfaceMethod = (value) => typeof value === "string" && METHOD_RE.test(value);
|
|
2894
|
+
const WORK_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2895
|
+
const UI_COMMAND_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
2896
|
+
const parseUiCommandId = (value) => {
|
|
2897
|
+
if (typeof value !== "string") return null;
|
|
2898
|
+
const trimmed = value.trim();
|
|
2899
|
+
return UI_COMMAND_ID_RE.test(trimmed) ? trimmed : null;
|
|
2900
|
+
};
|
|
2901
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2902
|
+
const asTrimmed = (value) => {
|
|
2903
|
+
if (typeof value !== "string") return null;
|
|
2904
|
+
const trimmed = value.trim();
|
|
2905
|
+
return trimmed ? trimmed : null;
|
|
2906
|
+
};
|
|
2907
|
+
const parseLaunch = (value) => {
|
|
2908
|
+
if (!isRecord(value)) return void 0;
|
|
2909
|
+
const search = asTrimmed(value.search);
|
|
2910
|
+
const hash = asTrimmed(value.hash);
|
|
2911
|
+
if (!search && !hash) return void 0;
|
|
2912
|
+
return {
|
|
2913
|
+
...search ? { search: search.startsWith("?") ? search : `?${search}` } : {},
|
|
2914
|
+
...hash ? { hash: hash.startsWith("#") ? hash : `#${hash}` } : {}
|
|
2915
|
+
};
|
|
2916
|
+
};
|
|
2917
|
+
const measureUiCommandPayload = (value) => {
|
|
2918
|
+
if (value === void 0) return 0;
|
|
2919
|
+
try {
|
|
2920
|
+
return new TextEncoder().encode(JSON.stringify(value) ?? "").length;
|
|
2921
|
+
} catch {
|
|
2922
|
+
return null;
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
const parseUiCommand = (input) => {
|
|
2926
|
+
if (!isRecord(input)) return {
|
|
2927
|
+
command: null,
|
|
2928
|
+
error: "command must be an object"
|
|
2929
|
+
};
|
|
2930
|
+
if (input.type !== "preview.show") return {
|
|
2931
|
+
command: null,
|
|
2932
|
+
error: "command.type must be one of: preview.show"
|
|
2933
|
+
};
|
|
2934
|
+
const preview = input.preview;
|
|
2935
|
+
if (!isRecord(preview)) return {
|
|
2936
|
+
command: null,
|
|
2937
|
+
error: "command.preview is required"
|
|
2938
|
+
};
|
|
2939
|
+
if (preview.kind !== "work") return {
|
|
2940
|
+
command: null,
|
|
2941
|
+
error: "command.preview.kind must be one of: work"
|
|
2942
|
+
};
|
|
2943
|
+
const workId = asTrimmed(preview.workId);
|
|
2944
|
+
if (!workId) return {
|
|
2945
|
+
command: null,
|
|
2946
|
+
error: "command.preview.workId is required"
|
|
2947
|
+
};
|
|
2948
|
+
if (!WORK_ID_RE.test(workId)) return {
|
|
2949
|
+
command: null,
|
|
2950
|
+
error: "command.preview.workId must be a Work id"
|
|
2951
|
+
};
|
|
2952
|
+
const label = asTrimmed(preview.label);
|
|
2953
|
+
if (label && label.length > 200) return {
|
|
2954
|
+
command: null,
|
|
2955
|
+
error: `command.preview.label exceeds 200 characters`
|
|
2956
|
+
};
|
|
2957
|
+
const launch = parseLaunch(preview.launch);
|
|
2958
|
+
if (launch) {
|
|
2959
|
+
for (const [field, value] of [["search", launch.search], ["hash", launch.hash]]) if (value && value.length > 2048) return {
|
|
2960
|
+
command: null,
|
|
2961
|
+
error: `command.preview.launch.${field} exceeds ${UI_COMMAND_LAUNCH_MAX_LENGTH} characters`
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
let request;
|
|
2965
|
+
if (input.request !== void 0 && input.request !== null) {
|
|
2966
|
+
if (!isRecord(input.request)) return {
|
|
2967
|
+
command: null,
|
|
2968
|
+
error: "command.request must be an object"
|
|
2969
|
+
};
|
|
2970
|
+
const method = asTrimmed(input.request.method);
|
|
2971
|
+
if (!method) return {
|
|
2972
|
+
command: null,
|
|
2973
|
+
error: "command.request.method is required"
|
|
2974
|
+
};
|
|
2975
|
+
if (!isUiSurfaceMethod(method)) return {
|
|
2976
|
+
command: null,
|
|
2977
|
+
error: "command.request.method has an unsupported format"
|
|
2978
|
+
};
|
|
2979
|
+
const size = measureUiCommandPayload(input.request.input);
|
|
2980
|
+
if (size === null) return {
|
|
2981
|
+
command: null,
|
|
2982
|
+
error: "command.request.input must be JSON-serializable"
|
|
2983
|
+
};
|
|
2984
|
+
if (size > 32768) return {
|
|
2985
|
+
command: null,
|
|
2986
|
+
error: `command.request.input exceeds ${UI_COMMAND_PAYLOAD_MAX_BYTES} bytes`
|
|
2987
|
+
};
|
|
2988
|
+
request = {
|
|
2989
|
+
method,
|
|
2990
|
+
...input.request.input === void 0 ? {} : { input: input.request.input }
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
const command = {
|
|
2994
|
+
type: "preview.show",
|
|
2995
|
+
preview: {
|
|
2996
|
+
kind: "work",
|
|
2997
|
+
workId,
|
|
2998
|
+
...label ? { label } : {},
|
|
2999
|
+
...launch ? { launch } : {}
|
|
3000
|
+
},
|
|
3001
|
+
...request ? { request } : {}
|
|
3002
|
+
};
|
|
3003
|
+
const totalSize = measureUiCommandPayload(command);
|
|
3004
|
+
if (totalSize === null) return {
|
|
3005
|
+
command: null,
|
|
3006
|
+
error: "command must be JSON-serializable"
|
|
3007
|
+
};
|
|
3008
|
+
if (totalSize > 40960) return {
|
|
3009
|
+
command: null,
|
|
3010
|
+
error: `command exceeds ${UI_COMMAND_MAX_BYTES} bytes`
|
|
3011
|
+
};
|
|
3012
|
+
return {
|
|
3013
|
+
command,
|
|
3014
|
+
error: null
|
|
3015
|
+
};
|
|
3016
|
+
};
|
|
3017
|
+
//#endregion
|
|
3018
|
+
//#region src/apis/ui-commands.ts
|
|
3019
|
+
const DEFAULT_POLL_INTERVAL_MS = 300;
|
|
3020
|
+
const resolveTimeoutMs = (timeoutMs) => {
|
|
3021
|
+
const value = timeoutMs ?? 6e5;
|
|
3022
|
+
if (!Number.isFinite(value) || value <= 0 || value > 432e5) throw new RangeError(`timeoutMs must be between 1 and ${UI_COMMAND_MAX_TIMEOUT_MS} milliseconds`);
|
|
3023
|
+
return value;
|
|
3024
|
+
};
|
|
3025
|
+
const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
3026
|
+
if (signal?.aborted) {
|
|
3027
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
const timer = setTimeout(() => {
|
|
3031
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3032
|
+
resolve();
|
|
3033
|
+
}, ms);
|
|
3034
|
+
const onAbort = () => {
|
|
3035
|
+
clearTimeout(timer);
|
|
3036
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
3037
|
+
};
|
|
3038
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3039
|
+
});
|
|
3040
|
+
var UiCommandsApi = class {
|
|
3041
|
+
transport;
|
|
3042
|
+
constructor(transport) {
|
|
3043
|
+
this.transport = transport;
|
|
3044
|
+
}
|
|
3045
|
+
create(input) {
|
|
3046
|
+
return this.transport.request("/api/ui/commands", {
|
|
3047
|
+
method: "POST",
|
|
3048
|
+
headers: { "Content-Type": "application/json" },
|
|
3049
|
+
body: JSON.stringify(input)
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
get(commandId) {
|
|
3053
|
+
return this.transport.request(`/api/ui/commands/${encodeURIComponent(commandId)}`);
|
|
3054
|
+
}
|
|
3055
|
+
reportResult(commandId, input) {
|
|
3056
|
+
return this.transport.request(`/api/ui/commands/${encodeURIComponent(commandId)}/result`, {
|
|
3057
|
+
method: "POST",
|
|
3058
|
+
headers: { "Content-Type": "application/json" },
|
|
3059
|
+
body: JSON.stringify(input)
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
async run(input, options = {}) {
|
|
3063
|
+
const { command } = await this.create(input);
|
|
3064
|
+
if (isTerminalUiCommandStatus(command.status)) return command;
|
|
3065
|
+
return this.wait(command.commandId, options);
|
|
3066
|
+
}
|
|
3067
|
+
async wait(commandId, options = {}) {
|
|
3068
|
+
const timeoutMs = resolveTimeoutMs(options.timeoutMs);
|
|
3069
|
+
const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
3070
|
+
const deadline = Date.now() + timeoutMs;
|
|
3071
|
+
let latest = (await this.get(commandId)).command;
|
|
3072
|
+
while (!isTerminalUiCommandStatus(latest.status)) {
|
|
3073
|
+
const remaining = deadline - Date.now();
|
|
3074
|
+
if (remaining <= 0) break;
|
|
3075
|
+
await sleep(Math.min(pollIntervalMs, remaining), options.signal);
|
|
3076
|
+
latest = (await this.get(commandId)).command;
|
|
3077
|
+
}
|
|
3078
|
+
if (isTerminalUiCommandStatus(latest.status)) return latest;
|
|
3079
|
+
return {
|
|
3080
|
+
...latest,
|
|
3081
|
+
status: "timeout",
|
|
3082
|
+
error: {
|
|
3083
|
+
code: "timeout",
|
|
3084
|
+
message: "No Cohub frontend reported a result before the timeout."
|
|
3085
|
+
},
|
|
3086
|
+
settledAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
//#endregion
|
|
2866
3091
|
//#region src/apis/user.ts
|
|
2867
3092
|
var UserApi = class {
|
|
2868
3093
|
transport;
|
|
@@ -3104,6 +3329,7 @@ var CohubHttpClient = class {
|
|
|
3104
3329
|
referrals;
|
|
3105
3330
|
works;
|
|
3106
3331
|
workCommerce;
|
|
3332
|
+
ui;
|
|
3107
3333
|
transport;
|
|
3108
3334
|
constructor(options = {}) {
|
|
3109
3335
|
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
@@ -3126,6 +3352,7 @@ var CohubHttpClient = class {
|
|
|
3126
3352
|
this.referrals = new ReferralsApi(this.transport);
|
|
3127
3353
|
this.works = new WorksApi(this.transport);
|
|
3128
3354
|
this.workCommerce = new WorkCommerceApi(this.transport);
|
|
3355
|
+
this.ui = new UiCommandsApi(this.transport);
|
|
3129
3356
|
}
|
|
3130
3357
|
space(spaceId) {
|
|
3131
3358
|
return new SpaceClient(spaceId, this.transport, null);
|
|
@@ -3133,4 +3360,4 @@ var CohubHttpClient = class {
|
|
|
3133
3360
|
};
|
|
3134
3361
|
const createHttpClient = (options) => new CohubHttpClient(options);
|
|
3135
3362
|
//#endregion
|
|
3136
|
-
export {
|
|
3363
|
+
export { parseAssistantMessageCommit as A, ModelsApi as B, SpaceClient as C, buildSpacePath as D, buildSpaceInvitePath as E, ReferencesApi as F, CronJobsApi as H, SearchApi as I, PublicAssetsApi as L, createSessionPatchReducer as M, ensureRealtimeConnected as N, SessionGenerationStreamClient as O, SessionAccessApi as P, SkillsApi as R, BoardTransactionError as S, PublicInviteApi as T, ChannelsApi as U, GenerationsApi as V, isUiSurfaceMethod as _, ReferralsApi as a, TasksApi as b, UiCommandsApi as c, UI_COMMAND_PAYLOAD_MAX_BYTES as d, UI_COMMAND_PENDING_TTL_SECONDS as f, isTerminalUiCommandStatus as g, UI_COMMAND_VERSION as h, WorksApi as i, SessionPatchReducer as j, createSessionGenerationStreamClient as k, UI_COMMAND_DEFAULT_TIMEOUT_MS as l, UI_COMMAND_TERMINAL_TTL_SECONDS as m, createHttpClient as n, UsersApi as o, UI_COMMAND_SETTLEMENT_GRACE_SECONDS as p, WorkCommerceApi as r, UserApi as s, CohubHttpClient as t, UI_COMMAND_MAX_TIMEOUT_MS as u, parseUiCommand as v, SpacesApi as w, BoardClient as x, parseUiCommandId as y, PromptsApi as z };
|
package/dist/chunks/transport.js
CHANGED
|
@@ -49,11 +49,15 @@ const COHUB_SOURCE_HEADER = {
|
|
|
49
49
|
session: "X-Cohub-Source-Session",
|
|
50
50
|
turn: "X-Cohub-Source-Turn",
|
|
51
51
|
toolCall: "X-Cohub-Source-Tool-Call",
|
|
52
|
+
client: "X-Cohub-Source-Client",
|
|
52
53
|
via: "X-Cohub-Source-Via"
|
|
53
54
|
};
|
|
54
55
|
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
55
56
|
const isRequestSourceUuid = (value) => typeof value === "string" && UUID_RE.test(value);
|
|
56
57
|
const REQUEST_SOURCE_VIA_MAX_LENGTH = 64;
|
|
58
|
+
/** Opaque, url-safe client instance id. */
|
|
59
|
+
const CLIENT_ID_RE = /^[A-Za-z0-9_-]{8,64}$/;
|
|
60
|
+
const isRequestSourceClientId = (value) => typeof value === "string" && CLIENT_ID_RE.test(value);
|
|
57
61
|
/** Remove C0 controls and DEL without a control-char regex (biome-friendly). */
|
|
58
62
|
const stripControlChars = (value) => {
|
|
59
63
|
let out = "";
|
|
@@ -77,13 +81,17 @@ const asVia = (value) => {
|
|
|
77
81
|
if (!cleaned) return void 0;
|
|
78
82
|
return cleaned.slice(0, 64);
|
|
79
83
|
};
|
|
84
|
+
const asClientId = (value) => {
|
|
85
|
+
const cleaned = asNonEmpty(value);
|
|
86
|
+
return cleaned && isRequestSourceClientId(cleaned) ? cleaned : void 0;
|
|
87
|
+
};
|
|
80
88
|
const isRequestSourceEmpty = (source) => {
|
|
81
89
|
if (!source) return true;
|
|
82
|
-
return !source.spaceId && !source.sessionId && !source.turnId && !source.toolCallId && !source.via;
|
|
90
|
+
return !source.spaceId && !source.sessionId && !source.turnId && !source.toolCallId && !source.clientId && !source.via;
|
|
83
91
|
};
|
|
84
92
|
const hasRequestSourceIdentity = (source) => {
|
|
85
93
|
if (!source) return false;
|
|
86
|
-
return Boolean(source.spaceId || source.sessionId || source.turnId || source.toolCallId);
|
|
94
|
+
return Boolean(source.spaceId || source.sessionId || source.turnId || source.toolCallId || source.clientId);
|
|
87
95
|
};
|
|
88
96
|
/** Drop invalid UUIDs; via-only is valid. */
|
|
89
97
|
const normalizeRequestSource = (input) => {
|
|
@@ -93,12 +101,14 @@ const normalizeRequestSource = (input) => {
|
|
|
93
101
|
const sessionId = asUuid(record.sessionId);
|
|
94
102
|
const turnId = asUuid(record.turnId);
|
|
95
103
|
const toolCallId = asUuid(record.toolCallId);
|
|
104
|
+
const clientId = asClientId(record.clientId);
|
|
96
105
|
const via = asVia(record.via);
|
|
97
106
|
const source = {
|
|
98
107
|
...spaceId ? { spaceId } : {},
|
|
99
108
|
...sessionId ? { sessionId } : {},
|
|
100
109
|
...turnId ? { turnId } : {},
|
|
101
110
|
...toolCallId ? { toolCallId } : {},
|
|
111
|
+
...clientId ? { clientId } : {},
|
|
102
112
|
...via ? { via } : {}
|
|
103
113
|
};
|
|
104
114
|
return isRequestSourceEmpty(source) ? null : source;
|
|
@@ -108,6 +118,7 @@ const parseRequestSourceFromHeaders = (getHeader) => normalizeRequestSource({
|
|
|
108
118
|
sessionId: getHeader(COHUB_SOURCE_HEADER.session),
|
|
109
119
|
turnId: getHeader(COHUB_SOURCE_HEADER.turn),
|
|
110
120
|
toolCallId: getHeader(COHUB_SOURCE_HEADER.toolCall),
|
|
121
|
+
clientId: getHeader(COHUB_SOURCE_HEADER.client),
|
|
111
122
|
via: getHeader(COHUB_SOURCE_HEADER.via)
|
|
112
123
|
});
|
|
113
124
|
const requestSourceToHeaders = (source) => {
|
|
@@ -118,6 +129,7 @@ const requestSourceToHeaders = (source) => {
|
|
|
118
129
|
if (normalized.sessionId) headers[COHUB_SOURCE_HEADER.session] = normalized.sessionId;
|
|
119
130
|
if (normalized.turnId) headers[COHUB_SOURCE_HEADER.turn] = normalized.turnId;
|
|
120
131
|
if (normalized.toolCallId) headers[COHUB_SOURCE_HEADER.toolCall] = normalized.toolCallId;
|
|
132
|
+
if (normalized.clientId) headers[COHUB_SOURCE_HEADER.client] = normalized.clientId;
|
|
121
133
|
if (normalized.via) headers[COHUB_SOURCE_HEADER.via] = normalized.via;
|
|
122
134
|
return headers;
|
|
123
135
|
};
|
|
@@ -127,6 +139,7 @@ const readRequestSourceFromEnv = (env = {}, defaults) => normalizeRequestSource(
|
|
|
127
139
|
sessionId: env.COHUB_SESSION_ID,
|
|
128
140
|
turnId: env.COHUB_TURN_ID,
|
|
129
141
|
toolCallId: env.COHUB_TOOL_CALL_ID,
|
|
142
|
+
clientId: env.COHUB_SOURCE_CLIENT_ID,
|
|
130
143
|
via: env.COHUB_SOURCE_VIA ?? defaults?.via
|
|
131
144
|
});
|
|
132
145
|
const resolveRequestSourceChannel = (source, fallback = "public_api") => {
|
|
@@ -171,6 +184,19 @@ function errorCodeFromBody(body) {
|
|
|
171
184
|
if (typeof errorBody.code === "string" && errorBody.code.trim()) return errorBody.code;
|
|
172
185
|
return null;
|
|
173
186
|
}
|
|
187
|
+
function cleanTraceHeader(value) {
|
|
188
|
+
if (!value) return null;
|
|
189
|
+
const cleaned = value.replace(/[\r\n\t\0]/g, "").trim();
|
|
190
|
+
return cleaned ? cleaned.slice(0, 256) : null;
|
|
191
|
+
}
|
|
192
|
+
function traceContextFromResponse(response) {
|
|
193
|
+
return {
|
|
194
|
+
requestId: cleanTraceHeader(response.headers.get("x-request-id")),
|
|
195
|
+
traceId: cleanTraceHeader(response.headers.get("x-trace-id")),
|
|
196
|
+
spanId: cleanTraceHeader(response.headers.get("x-span-id")),
|
|
197
|
+
traceparent: cleanTraceHeader(response.headers.get("traceparent"))
|
|
198
|
+
};
|
|
199
|
+
}
|
|
174
200
|
/**
|
|
175
201
|
* Access tokens must be a single HTTP header token.
|
|
176
202
|
* Newlines / control chars (from corrupted storage or clipboard paste) make
|
|
@@ -204,6 +230,8 @@ var HttpError = class extends Error {
|
|
|
204
230
|
unauthorizedHandled;
|
|
205
231
|
/** Auth state observed before the rejected request resolved its token. */
|
|
206
232
|
authSessionVersion;
|
|
233
|
+
/** Safe identifiers returned by the rejected API response. */
|
|
234
|
+
traceContext;
|
|
207
235
|
constructor(message, status, body, options) {
|
|
208
236
|
super(message);
|
|
209
237
|
this.name = "HttpError";
|
|
@@ -212,6 +240,7 @@ var HttpError = class extends Error {
|
|
|
212
240
|
this.code = errorCodeFromBody(body);
|
|
213
241
|
this.unauthorizedHandled = Boolean(options?.unauthorizedHandled);
|
|
214
242
|
this.authSessionVersion = options?.authSessionVersion;
|
|
243
|
+
this.traceContext = options?.traceContext;
|
|
215
244
|
}
|
|
216
245
|
};
|
|
217
246
|
const rejectedTokenByError = /* @__PURE__ */ new WeakMap();
|
|
@@ -346,20 +375,24 @@ var HttpTransport = class {
|
|
|
346
375
|
}
|
|
347
376
|
return retryResponse;
|
|
348
377
|
}
|
|
378
|
+
response = retryResponse;
|
|
349
379
|
}
|
|
350
380
|
}
|
|
351
381
|
if (response.status === 401) {
|
|
382
|
+
const traceContext = traceContextFromResponse(response);
|
|
352
383
|
let unauthorizedHandled = false;
|
|
353
384
|
if (!skipUnauthorizedHandler && this.onUnauthorized) {
|
|
354
385
|
await this.onUnauthorized({
|
|
355
386
|
matchesRejectedToken: (candidate) => sanitizeAccessToken(candidate) === rejectedToken,
|
|
356
|
-
authSessionVersion: rejectedAuthSessionVersion
|
|
387
|
+
authSessionVersion: rejectedAuthSessionVersion,
|
|
388
|
+
traceContext
|
|
357
389
|
});
|
|
358
390
|
unauthorizedHandled = true;
|
|
359
391
|
}
|
|
360
392
|
const error = new HttpError("unauthorized", 401, null, {
|
|
361
393
|
unauthorizedHandled,
|
|
362
|
-
authSessionVersion: rejectedAuthSessionVersion
|
|
394
|
+
authSessionVersion: rejectedAuthSessionVersion,
|
|
395
|
+
traceContext
|
|
363
396
|
});
|
|
364
397
|
rejectedTokenByError.set(error, rejectedToken);
|
|
365
398
|
throw error;
|
|
@@ -390,4 +423,4 @@ var HttpTransport = class {
|
|
|
390
423
|
}
|
|
391
424
|
};
|
|
392
425
|
//#endregion
|
|
393
|
-
export {
|
|
426
|
+
export { WS_REALTIME_ROOM_CAPABILITY as C, getSessionTurnPatchStreamKey as D, getRealtimeSpaceRoom as E, normalizeRealtimeRooms as O, WS_COMPACT_STREAM_CAPABILITY as S, getRealtimeBoardRoom as T, requestSourceToHeaders as _, sanitizeAccessToken as a, REALTIME_ROOM_MAX_PAYLOAD_BYTES as b, REQUEST_SOURCE_VIA_MAX_LENGTH as c, isRequestSourceEmpty as d, isRequestSourceUuid as f, readRequestSourceFromEnv as g, parseRequestSourceFromHeaders as h, matchesUnauthorizedErrorToken as i, hasRequestSourceIdentity as l, normalizeRequestSource as m, HttpTransport as n, COHUB_SOURCE_HEADER as o, mergeRequestSourceIntoMeta as p, joinApiUrl as r, COHUB_SOURCE_HEADER_NAMES as s, HttpError as t, isRequestSourceClientId as u, resolveRequestSourceChannel as v, WS_ROOM_SUBSCRIPTION_CAPABILITY as w, WS_BOARD_AWARENESS_CAPABILITY as x, REALTIME_ROOM_EVENT_NAME_PATTERN as y };
|