@neta-art/cohub 8.12.0 → 8.12.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.
@@ -521,7 +521,7 @@ type QQChannelConfig = BaseChannelConfig & {
521
521
  type ChannelConfig = DiscordChannelConfig | FeishuChannelConfig | WeChatChannelConfig | QQChannelConfig | (BaseChannelConfig & Record<string, unknown>);
522
522
  /** Runtime connection health for a bound gateway channel (space_channel id). */
523
523
  type ChannelRuntimeState = "unbound" | "connecting" | "ready" | "degraded" | "error" | "stopped";
524
- type ChannelHealthReasonCode = "invalid_credentials" | "auth_failed" | "network" | "permission" | "provider_error" | "unknown";
524
+ type ChannelHealthReasonCode = "invalid_credentials" | "auth_failed" | "network" | "permission" | "provider_error" | "rate_limit" | "unknown";
525
525
  type ChannelHealth = {
526
526
  state: ChannelRuntimeState;
527
527
  reasonCode?: ChannelHealthReasonCode | null;
@@ -536,11 +536,31 @@ type ChannelHealth = {
536
536
  meta?: Record<string, string> | null;
537
537
  };
538
538
  //#endregion
539
- //#region ../../node_modules/.pnpm/@neta-art+generation@0.1.25/node_modules/@neta-art/generation/dist/builtins-D0tS2UtT.d.ts
539
+ //#region ../../node_modules/.pnpm/@neta-art+generation@0.1.26/node_modules/@neta-art/generation/dist/builtins-jIE_k69Z.d.ts
540
540
  //#region src/types.d.ts
541
541
  declare const MODEL_SCHEMA: "neta.generation.model.v1";
542
542
  declare const GENERATION_MODEL_CATEGORIES: readonly ["image", "video", "audio"];
543
543
  type GenerationModelCategory = (typeof GENERATION_MODEL_CATEGORIES)[number];
544
+ declare const GENERATION_PRICING_UNITS: readonly ["image", "second", "request", "1m_tokens"];
545
+ type GenerationPricingUnit = (typeof GENERATION_PRICING_UNITS)[number];
546
+ /**
547
+ * Display-only unit price for a model catalog. `unit` names what one charge
548
+ * covers; `amount` is the exact unit price, or a `min`/`max` pair when the
549
+ * price depends on request parameters (resolution, duration, quality).
550
+ */
551
+ type GenerationModelPricing = {
552
+ unit: GenerationPricingUnit;
553
+ /** Short qualifier shown with the price, e.g. "std-pro". */
554
+ note?: string;
555
+ } & ({
556
+ amount: number;
557
+ min?: never;
558
+ max?: never;
559
+ } | {
560
+ amount?: never;
561
+ min: number;
562
+ max: number;
563
+ });
544
564
  type GenerationSource = {
545
565
  type: "url";
546
566
  url: string;
@@ -643,6 +663,8 @@ type GenerationModelDeclaration = {
643
663
  title?: string;
644
664
  /** Product catalog class. */
645
665
  category?: GenerationModelCategory;
666
+ /** Display-only unit price in USD, maintained per deployment. */
667
+ pricing?: GenerationModelPricing;
646
668
  description?: string;
647
669
  /** Hide from default discovery while keeping exact-ID lookup and runtime use available. */
648
670
  hidden?: boolean;
@@ -50,26 +50,6 @@ type ModelStatusResponse = {
50
50
  models: Record<string, ModelStatusEntry>;
51
51
  };
52
52
  //#endregion
53
- //#region ../protocol/dist/generation/pricing.d.ts
54
- declare const GENERATION_PRICING_UNITS: readonly ["image", "second", "request", "1m_tokens"];
55
- type GenerationPricingUnit = (typeof GENERATION_PRICING_UNITS)[number];
56
- /**
57
- * Display price for a generation model, maintained in the platform config space
58
- * and synced from the upstream gateway catalog.
59
- *
60
- * `unit` names what one charge covers. `amount` is the exact unit price; use
61
- * `min`/`max` instead when the price depends on request parameters (resolution,
62
- * duration, quality) and a single number would mislead.
63
- */
64
- type GenerationModelPricing = {
65
- unit: GenerationPricingUnit;
66
- amount?: number;
67
- min?: number;
68
- max?: number;
69
- /** Short qualifier shown with the price, e.g. "std–pro". */
70
- note?: string;
71
- };
72
- //#endregion
73
53
  //#region ../protocol/dist/generation/index.d.ts
74
54
  declare const GENERATION_TASK_TYPE: "generation";
75
55
  type CreateGenerationTaskRequest = {
@@ -117,9 +97,7 @@ type GenerationTaskResult = {
117
97
  billing?: GenerationUsageBilling | null;
118
98
  meta?: Record<string, unknown>;
119
99
  };
120
- type PublicGenerationDeclaration = Omit<GenerationModelDeclaration, "adapter"> & {
121
- pricing?: GenerationModelPricing;
122
- };
100
+ type PublicGenerationDeclaration = Omit<GenerationModelDeclaration, "adapter">;
123
101
  type ListGenerationModelsResponse = {
124
102
  models: PublicGenerationDeclaration[];
125
103
  };
@@ -1195,6 +1173,7 @@ type SessionSubscriptionHandlers = {
1195
1173
  patchState?: (result: SessionPatchApplyResult) => void;
1196
1174
  turnUpdated?: (event: WebsocketEventPayload) => void;
1197
1175
  turnFinalized?: (event: WebsocketEventPayload) => void;
1176
+ /** A failed turn, or a rejected subscription (`system.subscribe.error`). */
1198
1177
  error?: (event: WebsocketEventPayload) => void;
1199
1178
  persisted?: (event: WebsocketEventPayload) => void;
1200
1179
  event?: (event: WebsocketEventPayload) => void;
@@ -2972,6 +2972,15 @@ var SessionTurnsClient = class {
2972
2972
  });
2973
2973
  }
2974
2974
  };
2975
+ /**
2976
+ * Routes a rejected subscription for `room` (for example a missing
2977
+ * `space.view` permission) to `onRejected`. Without this an unauthorized
2978
+ * subscriber would simply never receive events, with no way to tell.
2979
+ */
2980
+ const onRoomRejected = (websocketClient, room, onRejected) => websocketClient.on("event", (event) => {
2981
+ if (event.type !== "system.subscribe.error") return;
2982
+ if (event.payload.rejected?.some((entry) => entry.room === room)) onRejected(event);
2983
+ });
2975
2984
  var SessionRealtimeClient = class {
2976
2985
  websocketClient;
2977
2986
  spaceId;
@@ -2985,7 +2994,12 @@ var SessionRealtimeClient = class {
2985
2994
  subscribe(handlers) {
2986
2995
  if (!this.websocketClient) throw new Error("realtime transport is not configured for this client");
2987
2996
  ensureRealtimeConnected(this.websocketClient);
2988
- const releaseRoom = this.websocketClient.retainRooms([getRealtimeSpaceRoom(this.spaceId)]);
2997
+ const room = getRealtimeSpaceRoom(this.spaceId);
2998
+ const releaseRoom = this.websocketClient.retainRooms([room]);
2999
+ const offRejected = onRoomRejected(this.websocketClient, room, (event) => {
3000
+ handlers.event?.(event);
3001
+ handlers.error?.(event);
3002
+ });
2989
3003
  const unsubscribe = this.websocketClient.on("event", (event) => {
2990
3004
  if (event.spaceId !== this.spaceId || event.sessionId !== this.sessionId) return;
2991
3005
  handlers.event?.(event);
@@ -3036,6 +3050,7 @@ var SessionRealtimeClient = class {
3036
3050
  });
3037
3051
  return () => {
3038
3052
  unsubscribe();
3053
+ offRejected();
3039
3054
  releaseRoom();
3040
3055
  };
3041
3056
  }
@@ -3181,13 +3196,16 @@ var SpaceEventsApi = class {
3181
3196
  subscribe(handler) {
3182
3197
  if (!this.websocketClient) throw new Error("realtime transport is not configured for this client");
3183
3198
  ensureRealtimeConnected(this.websocketClient);
3184
- const releaseRoom = this.websocketClient.retainRooms([getRealtimeSpaceRoom(this.spaceId)]);
3199
+ const room = getRealtimeSpaceRoom(this.spaceId);
3200
+ const releaseRoom = this.websocketClient.retainRooms([room]);
3201
+ const offRejected = onRoomRejected(this.websocketClient, room, handler);
3185
3202
  const offEvent = this.websocketClient.on("event", (event) => {
3186
3203
  if (event.spaceId !== this.spaceId) return;
3187
3204
  handler(event);
3188
3205
  });
3189
3206
  return () => {
3190
3207
  offEvent();
3208
+ offRejected();
3191
3209
  releaseRoom();
3192
3210
  };
3193
3211
  }
package/dist/index.d.ts CHANGED
@@ -529,6 +529,8 @@ type AppAuthorizeSpaceOption = {
529
529
  */
530
530
  type AppAuthorizeRequest = {
531
531
  requestId: string;
532
+ /** Identical requests made while the dialog is open, answered with it. */
533
+ joinedRequestIds?: string[];
532
534
  scopes: Permission[];
533
535
  reason?: string;
534
536
  spaceId?: string;
package/dist/index.js CHANGED
@@ -3239,6 +3239,20 @@ const isDefinitiveAuthorizationFailure = (error) => error instanceof AppAuthoriz
3239
3239
  403,
3240
3240
  404
3241
3241
  ].includes(error.status);
3242
+ /**
3243
+ * Whether a new authorize request asks for exactly the consent a pending
3244
+ * dialog already shows: same scopes, same target and same mode. Such a request
3245
+ * joins the dialog instead of replacing it, so an App that asks twice (for
3246
+ * example on every context update) gets one dialog and one shared answer.
3247
+ */
3248
+ function isSameConsent(pending, next) {
3249
+ if (next.createSpace || pending.createSpace) return false;
3250
+ if (Boolean(pending.selectSpace) !== next.selectSpace) return false;
3251
+ if (pending.spaceId !== next.spaceId) return false;
3252
+ const a = normalizePermissionScopes(pending.scopes);
3253
+ const b = normalizePermissionScopes(next.scopes);
3254
+ return a.length === b.length && a.every((scope) => b.includes(scope));
3255
+ }
3242
3256
  function sanitizeReason(value) {
3243
3257
  if (typeof value !== "string") return void 0;
3244
3258
  const trimmed = value.trim();
@@ -3792,7 +3806,18 @@ function createAppBridgeCore(config) {
3792
3806
  }, true);
3793
3807
  return;
3794
3808
  }
3795
- if (state.pendingAuth) dismissPendingAuth();
3809
+ if (state.pendingAuth) {
3810
+ if (isSameConsent(state.pendingAuth, {
3811
+ scopes,
3812
+ spaceId,
3813
+ selectSpace,
3814
+ createSpace
3815
+ })) {
3816
+ state.pendingAuth.joinedRequestIds = [...state.pendingAuth.joinedRequestIds ?? [], data.requestId];
3817
+ return;
3818
+ }
3819
+ dismissPendingAuth();
3820
+ }
3796
3821
  if (createSpace) {
3797
3822
  mintedSpace = null;
3798
3823
  state.pendingAuth = {
@@ -3862,12 +3887,16 @@ function createAppBridgeCore(config) {
3862
3887
  }, true);
3863
3888
  }
3864
3889
  }
3890
+ /** Answers the dialog's request and every request that joined it. */
3891
+ function replyPendingAuth(pending, payload) {
3892
+ for (const requestId of [pending.requestId, ...pending.joinedRequestIds ?? []]) replyForRequest(requestId, payload, true);
3893
+ }
3865
3894
  function dismissPendingAuth() {
3866
3895
  if (!state.pendingAuth) return;
3867
- replyForRequest(state.pendingAuth.requestId, {
3896
+ replyPendingAuth(state.pendingAuth, {
3868
3897
  type: "cohub.app.authorize.result",
3869
3898
  token: null
3870
- }, true);
3899
+ });
3871
3900
  state.pendingAuth = null;
3872
3901
  state.authOpen = false;
3873
3902
  state.authError = null;
@@ -3931,7 +3960,7 @@ function createAppBridgeCore(config) {
3931
3960
  if (pending.createSpace) {
3932
3961
  mintedSpace ??= await createViewerSpace(pending.createSpace);
3933
3962
  if (!mintedSpace.provisioned) {
3934
- replyForRequest(pending.requestId, authorizeResult(null, mintedSpace.id, mintedSpace.name ?? pending.createSpace.name), true);
3963
+ replyPendingAuth(pending, authorizeResult(null, mintedSpace.id, mintedSpace.name ?? pending.createSpace.name));
3935
3964
  state.authOpen = false;
3936
3965
  state.pendingAuth = null;
3937
3966
  mintedSpace = null;
@@ -3943,7 +3972,7 @@ function createAppBridgeCore(config) {
3943
3972
  const result = await authorize(pending.scopes, requestedSpaceId);
3944
3973
  setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
3945
3974
  if (pending.selectSpace || pending.createSpace) writeLastPickedSpace(result.spaceId);
3946
- replyForRequest(pending.requestId, authorizeResult(result.token, result.spaceId, spaceName), true);
3975
+ replyPendingAuth(pending, authorizeResult(result.token, result.spaceId, spaceName));
3947
3976
  state.authOpen = false;
3948
3977
  state.pendingAuth = null;
3949
3978
  mintedSpace = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "8.12.0",
3
+ "version": "8.12.1",
4
4
  "description": "Cohub SDK for spaces, sessions, boards, and realtime agent collaboration.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,