@appthen/sdk-core 0.0.1 → 0.0.3
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/index.d.ts +96 -1
- package/dist/index.js +74 -15
- package/dist-cjs/index.d.ts +96 -1
- package/dist-cjs/index.js +76 -15
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -298,7 +298,26 @@ export interface QueryConversationsInput {
|
|
|
298
298
|
status?: string;
|
|
299
299
|
createdBySubjectId?: string;
|
|
300
300
|
subjectId?: string;
|
|
301
|
+
/** 分页(P1):不传即全量(保持历史行为)。 */
|
|
302
|
+
limit?: number;
|
|
303
|
+
offset?: number;
|
|
304
|
+
/** keyset 游标(上一页最后一条的 encode 结果,见 encodeConversationListCursor)。 */
|
|
305
|
+
cursor?: string;
|
|
306
|
+
/** 服务端搜索:标题 / conversationId 纯文本匹配(不区分大小写)。 */
|
|
307
|
+
search?: string;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 会话列表游标(keyset 分页,P1):与服务端 conversation-list-cursor.util 同构。
|
|
311
|
+
* 载荷 = base64url(JSON { u: updatedAt 毫秒, c: conversationId })。
|
|
312
|
+
* 客户端从「当前列表最后一条」构造;服务端按 (updatedAt desc, conversationId desc) 续页。
|
|
313
|
+
*/
|
|
314
|
+
export interface ConversationListCursorPayload {
|
|
315
|
+
updatedAt: number;
|
|
316
|
+
conversationId: string;
|
|
301
317
|
}
|
|
318
|
+
export declare function encodeConversationListCursor(payload: ConversationListCursorPayload): string;
|
|
319
|
+
/** 从会话记录构造游标(读取 updatedAt;缺失/非法返回 null)。 */
|
|
320
|
+
export declare function buildConversationListCursorFromRecord(record: Record<string, unknown> | null | undefined): string | null;
|
|
302
321
|
export interface CreateInternalConversationInput extends CreateConversationInput {
|
|
303
322
|
tenantId: string;
|
|
304
323
|
}
|
|
@@ -332,6 +351,29 @@ export interface ChatHubSubjectClassPreset {
|
|
|
332
351
|
requiredExternalIdentity?: Record<string, unknown>;
|
|
333
352
|
tags?: string[];
|
|
334
353
|
notes?: string;
|
|
354
|
+
/**
|
|
355
|
+
* 类级 agent 运行配置(与服务端 SubjectClass.agentConfig 同形)。
|
|
356
|
+
* 人主体类缺省;agent 类用它声明能力/运行档偏好(provider/bindingDefaults 等)。
|
|
357
|
+
*/
|
|
358
|
+
agentConfig?: {
|
|
359
|
+
capabilityId?: string;
|
|
360
|
+
capabilityIds?: string[];
|
|
361
|
+
provider?: string;
|
|
362
|
+
roleDefinition?: string;
|
|
363
|
+
toolPreset?: string;
|
|
364
|
+
permissionStrategy?: 'manual' | 'auto-allow' | 'auto-deny';
|
|
365
|
+
prompt?: Record<string, unknown>;
|
|
366
|
+
triggerModes?: string[];
|
|
367
|
+
bindingDefaults?: {
|
|
368
|
+
bindingId?: string;
|
|
369
|
+
projectId?: string;
|
|
370
|
+
workspaceId?: string;
|
|
371
|
+
modeId?: string;
|
|
372
|
+
model?: string;
|
|
373
|
+
thinkingOptionId?: string;
|
|
374
|
+
};
|
|
375
|
+
[key: string]: unknown;
|
|
376
|
+
};
|
|
335
377
|
}
|
|
336
378
|
export interface ChatHubSubjectPreset {
|
|
337
379
|
subjectId: string;
|
|
@@ -537,7 +579,14 @@ export declare class ChatHubAppRuntime {
|
|
|
537
579
|
updateConversationRead(input: ConversationReadStateInput): Promise<void>;
|
|
538
580
|
ensureAuthenticated(): Promise<void>;
|
|
539
581
|
bootstrapConversation(): Promise<ChatHubBootstrapResult>;
|
|
540
|
-
listConversations(subjectId?: string
|
|
582
|
+
listConversations(subjectId?: string, options?: {
|
|
583
|
+
/** 分页(P1):不传 limit 即全量(历史行为)。 */
|
|
584
|
+
limit?: number;
|
|
585
|
+
/** keyset 游标(上一页最后一条的 encode 结果)。 */
|
|
586
|
+
cursor?: string;
|
|
587
|
+
/** 服务端搜索:标题 / conversationId 纯文本匹配。 */
|
|
588
|
+
search?: string;
|
|
589
|
+
}): Promise<ConversationRecord[]>;
|
|
541
590
|
listMessages(input: string | ListChatHubMessagesOptions): Promise<ConversationMessageRecord[]>;
|
|
542
591
|
listMessagesPage(input: string | ListChatHubMessagesOptions): Promise<ListChatHubMessagesPageResult>;
|
|
543
592
|
ensureParticipant(input: CreateParticipantInput): Promise<Record<string, unknown>>;
|
|
@@ -577,6 +626,11 @@ export interface PlatformExternalIdentityInput {
|
|
|
577
626
|
displayLabel?: string;
|
|
578
627
|
metadata?: Record<string, unknown>;
|
|
579
628
|
}
|
|
629
|
+
export interface PlatformLoginIdentifierInput {
|
|
630
|
+
/** 登录渠道。v1 仅支持 `phone`(邮箱/工号等预留,新增渠道不改契约形状)。 */
|
|
631
|
+
provider: 'phone' | string;
|
|
632
|
+
value: string;
|
|
633
|
+
}
|
|
580
634
|
export interface PlatformSyncSubjectInput {
|
|
581
635
|
classId: string;
|
|
582
636
|
displayName: string;
|
|
@@ -588,6 +642,32 @@ export interface PlatformSyncSubjectInput {
|
|
|
588
642
|
tags?: string[];
|
|
589
643
|
/** 命中已存在主体时是否更新其可写字段,默认 false(纯幂等)。 */
|
|
590
644
|
updateOnMatch?: boolean;
|
|
645
|
+
/**
|
|
646
|
+
* 登录标识(v1 = 手机号)。**带上才可登录**——平台据此把"这个成员"与
|
|
647
|
+
* "用该标识登录的人"对上;不带不算错,但该主体只作为通讯录/圈人对象存在,
|
|
648
|
+
* 登录时列不出来。
|
|
649
|
+
*
|
|
650
|
+
* 语义要点:
|
|
651
|
+
* - 与 `updateOnMatch` **正交**:传入即补绑(命中已存在主体时同样生效),
|
|
652
|
+
* 所以"先同步→后拿到手机号→再推一次"即可让身份变得可用;
|
|
653
|
+
* - **不传 ≠ 解绑**(缺席不代表移除);
|
|
654
|
+
* - 同一标识可同时挂在多个主体上(例如第三方系统里同一手机号对应
|
|
655
|
+
* 「用户前台」「员工后台」两个账号空间)——平台按声明如实挂载,不自行归并;
|
|
656
|
+
* - 系统内**首次**挂载该标识的主体为「主身份」,用于登录时的默认选择。
|
|
657
|
+
*/
|
|
658
|
+
loginIdentifiers?: PlatformLoginIdentifierInput[];
|
|
659
|
+
/**
|
|
660
|
+
* 身份标签:**由第三方声明的短标签**(如「用户前台」/「员工后台」),
|
|
661
|
+
* 用于在工作台的「切换身份」里区分同一个人的多个身份。
|
|
662
|
+
*
|
|
663
|
+
* 语义要点:
|
|
664
|
+
* - 平台**只提供字段,不发明语义**:值原样展示,不翻译、不判定、不造词;
|
|
665
|
+
* 不传时界面回退到角色(member / admin / owner)。
|
|
666
|
+
* - 属**内容字段**(与 `displayName` / `tags` 同规则):建时落库;命中已存在
|
|
667
|
+
* 主体时**仅在 `updateOnMatch: true` 下覆盖**,不传不会被清空。
|
|
668
|
+
* - 建议 ≤ 4 个字(行内展示位有限);接口上限 64 字符,超长返回 400。
|
|
669
|
+
*/
|
|
670
|
+
identityLabel?: string;
|
|
591
671
|
/** 幂等键:{source, externalKey}。重复推送只产生一个 Subject,可安全重试。 */
|
|
592
672
|
externalIdentity: PlatformExternalIdentityInput;
|
|
593
673
|
}
|
|
@@ -619,6 +699,21 @@ export declare class PlatformApi extends BaseApi {
|
|
|
619
699
|
* 收件人二选一:标签圈人(selector)或单点声明(receive,如微信客服消息迁移)。
|
|
620
700
|
*/
|
|
621
701
|
notify(targetTenantId: string, request: PlatformNotifyRequest, notify: PlatformNotifyInput): Promise<PlatformNotifyResult>;
|
|
702
|
+
/**
|
|
703
|
+
* 重发认领凭证(平台级):租户 7 天内未认领时,服务商重新签发 claimToken。
|
|
704
|
+
* **重发即作废**——epoch 递增,旧 token 立即失效;仅 pending 租户可重发。
|
|
705
|
+
*/
|
|
706
|
+
reissueClaimToken(tenantId: string): Promise<PlatformReissueClaimTokenResult>;
|
|
707
|
+
}
|
|
708
|
+
/** 重发认领凭证的返回(claimToken 有效期 7 天;重发后旧 token 立即失效)。 */
|
|
709
|
+
export interface PlatformReissueClaimTokenResult {
|
|
710
|
+
tenantId: string;
|
|
711
|
+
status: string;
|
|
712
|
+
claimToken: string;
|
|
713
|
+
expiresIn: string;
|
|
714
|
+
/** 签发轮次:0 = 首次开通签发,>=1 = 第 N 次重发。 */
|
|
715
|
+
claimEpoch: number;
|
|
716
|
+
reissued: boolean;
|
|
622
717
|
}
|
|
623
718
|
/** 声明式主体引用:调用方只需本地维护 id/昵称/头像,服务端 lazily create-or-get。 */
|
|
624
719
|
export interface PlatformLazySubject {
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,27 @@
|
|
|
10
10
|
* - 内部 JWT 保护的查询接口(messages / conversations)
|
|
11
11
|
*/
|
|
12
12
|
export const SDK_VERSION = '0.1.0';
|
|
13
|
+
export function encodeConversationListCursor(payload) {
|
|
14
|
+
const json = JSON.stringify({ u: payload.updatedAt, c: payload.conversationId });
|
|
15
|
+
// 同时兼容浏览器(btoa,需先转 latin1)与 Node(Buffer):base64url 无 padding。
|
|
16
|
+
const base64 = typeof Buffer !== 'undefined'
|
|
17
|
+
? Buffer.from(json, 'utf8').toString('base64')
|
|
18
|
+
: btoa(unescape(encodeURIComponent(json)));
|
|
19
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
20
|
+
}
|
|
21
|
+
/** 从会话记录构造游标(读取 updatedAt;缺失/非法返回 null)。 */
|
|
22
|
+
export function buildConversationListCursorFromRecord(record) {
|
|
23
|
+
const conversationId = typeof record?.conversationId === 'string' ? record.conversationId : '';
|
|
24
|
+
const rawUpdatedAt = record?.updatedAt;
|
|
25
|
+
const updatedAt = typeof rawUpdatedAt === 'number'
|
|
26
|
+
? rawUpdatedAt
|
|
27
|
+
: typeof rawUpdatedAt === 'string'
|
|
28
|
+
? Date.parse(rawUpdatedAt)
|
|
29
|
+
: NaN;
|
|
30
|
+
if (!conversationId || !Number.isFinite(updatedAt) || updatedAt <= 0)
|
|
31
|
+
return null;
|
|
32
|
+
return encodeConversationListCursor({ updatedAt, conversationId });
|
|
33
|
+
}
|
|
13
34
|
export function resolveChatHubMessagePolicy(value) {
|
|
14
35
|
const policy = value && typeof value === 'object' ? value : {};
|
|
15
36
|
return {
|
|
@@ -22,6 +43,12 @@ export function resolveChatHubMessagePolicy(value) {
|
|
|
22
43
|
allowRevoke: policy.allowRevoke === true,
|
|
23
44
|
};
|
|
24
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* 断线补齐时一次 refresh 最多向后续拉的页数(防单次同步打爆服务端)。
|
|
48
|
+
* 每页按 watch 的 limit(上限 100),20 页 = 最多 2000 条,
|
|
49
|
+
* 覆盖任何合理的断线积压;真超过这个量级应走历史分页而不是实时补齐。
|
|
50
|
+
*/
|
|
51
|
+
const MAX_CATCHUP_PAGES = 20;
|
|
25
52
|
function withBearerToken(headers, token) {
|
|
26
53
|
if (!token) {
|
|
27
54
|
return headers || {};
|
|
@@ -384,13 +411,16 @@ export class ChatHubAppRuntime {
|
|
|
384
411
|
conversation,
|
|
385
412
|
};
|
|
386
413
|
}
|
|
387
|
-
async listConversations(subjectId) {
|
|
414
|
+
async listConversations(subjectId, options) {
|
|
388
415
|
await this.ensureAuthenticated();
|
|
389
416
|
const resolvedSubjectId = subjectId || this.config.subjects?.[0]?.subjectId || undefined;
|
|
390
417
|
const conversations = await this.client.conversations.list({
|
|
391
418
|
tenantId: this.tenantId,
|
|
392
419
|
typeId: this.config.conversationType?.typeId,
|
|
393
420
|
subjectId: resolvedSubjectId,
|
|
421
|
+
...(options?.limit ? { limit: options.limit } : {}),
|
|
422
|
+
...(options?.cursor ? { cursor: options.cursor } : {}),
|
|
423
|
+
...(options?.search ? { search: options.search } : {}),
|
|
394
424
|
});
|
|
395
425
|
// Participants are intentionally NOT fetched here: doing so would fan out
|
|
396
426
|
// one `/conversation-runtime/participants` HTTP request per conversation
|
|
@@ -506,22 +536,41 @@ export class ChatHubAppRuntime {
|
|
|
506
536
|
if (refreshPromise) {
|
|
507
537
|
return refreshPromise;
|
|
508
538
|
}
|
|
509
|
-
const afterSequence = lastSequence;
|
|
510
539
|
refreshPromise = (async () => {
|
|
511
540
|
try {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
541
|
+
let cursor = lastSequence;
|
|
542
|
+
for (let page = 0; page < MAX_CATCHUP_PAGES; page += 1) {
|
|
543
|
+
const pageAfterSequence = cursor;
|
|
544
|
+
const messages = await this.listMessages({
|
|
545
|
+
conversationId: options.conversationId,
|
|
546
|
+
limit,
|
|
547
|
+
afterSequence: pageAfterSequence,
|
|
548
|
+
});
|
|
549
|
+
const maxSequence = messages.reduce((current, message) => {
|
|
550
|
+
const sequence = typeof message.sequence === 'number' && Number.isFinite(message.sequence)
|
|
551
|
+
? Math.floor(message.sequence)
|
|
552
|
+
: current;
|
|
553
|
+
return Math.max(current, sequence);
|
|
554
|
+
}, cursor);
|
|
555
|
+
const advanced = maxSequence > cursor;
|
|
556
|
+
if (advanced) {
|
|
557
|
+
cursor = maxSequence;
|
|
558
|
+
}
|
|
559
|
+
lastSequence = Math.max(lastSequence, maxSequence);
|
|
560
|
+
await options.onMessages({
|
|
561
|
+
reason,
|
|
562
|
+
messages,
|
|
563
|
+
afterSequence: pageAfterSequence,
|
|
564
|
+
});
|
|
565
|
+
// 收敛条件:不满页说明已追平;本页没有推进说明游标打转,必须停下
|
|
566
|
+
// (否则服务端若回传不含 sequence 的消息会死循环)。
|
|
567
|
+
if (messages.length < limit || !advanced) {
|
|
568
|
+
if (page > 0) {
|
|
569
|
+
console.info('[chat-hub-sdk] watch caught up', options.conversationId, 'pages=', page + 1, 'lastSequence=', lastSequence);
|
|
570
|
+
}
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
525
574
|
}
|
|
526
575
|
catch (error) {
|
|
527
576
|
options.onError?.(error);
|
|
@@ -799,4 +848,14 @@ export class PlatformApi extends BaseApi {
|
|
|
799
848
|
body: { ...notify, ...request, targetTenantId },
|
|
800
849
|
});
|
|
801
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* 重发认领凭证(平台级):租户 7 天内未认领时,服务商重新签发 claimToken。
|
|
853
|
+
* **重发即作废**——epoch 递增,旧 token 立即失效;仅 pending 租户可重发。
|
|
854
|
+
*/
|
|
855
|
+
async reissueClaimToken(tenantId) {
|
|
856
|
+
return this.request({
|
|
857
|
+
method: 'POST',
|
|
858
|
+
path: `/open-platform/platform/tenants/${encodeURIComponent(tenantId)}/claim-token`,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
802
861
|
}
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -298,7 +298,26 @@ export interface QueryConversationsInput {
|
|
|
298
298
|
status?: string;
|
|
299
299
|
createdBySubjectId?: string;
|
|
300
300
|
subjectId?: string;
|
|
301
|
+
/** 分页(P1):不传即全量(保持历史行为)。 */
|
|
302
|
+
limit?: number;
|
|
303
|
+
offset?: number;
|
|
304
|
+
/** keyset 游标(上一页最后一条的 encode 结果,见 encodeConversationListCursor)。 */
|
|
305
|
+
cursor?: string;
|
|
306
|
+
/** 服务端搜索:标题 / conversationId 纯文本匹配(不区分大小写)。 */
|
|
307
|
+
search?: string;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 会话列表游标(keyset 分页,P1):与服务端 conversation-list-cursor.util 同构。
|
|
311
|
+
* 载荷 = base64url(JSON { u: updatedAt 毫秒, c: conversationId })。
|
|
312
|
+
* 客户端从「当前列表最后一条」构造;服务端按 (updatedAt desc, conversationId desc) 续页。
|
|
313
|
+
*/
|
|
314
|
+
export interface ConversationListCursorPayload {
|
|
315
|
+
updatedAt: number;
|
|
316
|
+
conversationId: string;
|
|
301
317
|
}
|
|
318
|
+
export declare function encodeConversationListCursor(payload: ConversationListCursorPayload): string;
|
|
319
|
+
/** 从会话记录构造游标(读取 updatedAt;缺失/非法返回 null)。 */
|
|
320
|
+
export declare function buildConversationListCursorFromRecord(record: Record<string, unknown> | null | undefined): string | null;
|
|
302
321
|
export interface CreateInternalConversationInput extends CreateConversationInput {
|
|
303
322
|
tenantId: string;
|
|
304
323
|
}
|
|
@@ -332,6 +351,29 @@ export interface ChatHubSubjectClassPreset {
|
|
|
332
351
|
requiredExternalIdentity?: Record<string, unknown>;
|
|
333
352
|
tags?: string[];
|
|
334
353
|
notes?: string;
|
|
354
|
+
/**
|
|
355
|
+
* 类级 agent 运行配置(与服务端 SubjectClass.agentConfig 同形)。
|
|
356
|
+
* 人主体类缺省;agent 类用它声明能力/运行档偏好(provider/bindingDefaults 等)。
|
|
357
|
+
*/
|
|
358
|
+
agentConfig?: {
|
|
359
|
+
capabilityId?: string;
|
|
360
|
+
capabilityIds?: string[];
|
|
361
|
+
provider?: string;
|
|
362
|
+
roleDefinition?: string;
|
|
363
|
+
toolPreset?: string;
|
|
364
|
+
permissionStrategy?: 'manual' | 'auto-allow' | 'auto-deny';
|
|
365
|
+
prompt?: Record<string, unknown>;
|
|
366
|
+
triggerModes?: string[];
|
|
367
|
+
bindingDefaults?: {
|
|
368
|
+
bindingId?: string;
|
|
369
|
+
projectId?: string;
|
|
370
|
+
workspaceId?: string;
|
|
371
|
+
modeId?: string;
|
|
372
|
+
model?: string;
|
|
373
|
+
thinkingOptionId?: string;
|
|
374
|
+
};
|
|
375
|
+
[key: string]: unknown;
|
|
376
|
+
};
|
|
335
377
|
}
|
|
336
378
|
export interface ChatHubSubjectPreset {
|
|
337
379
|
subjectId: string;
|
|
@@ -537,7 +579,14 @@ export declare class ChatHubAppRuntime {
|
|
|
537
579
|
updateConversationRead(input: ConversationReadStateInput): Promise<void>;
|
|
538
580
|
ensureAuthenticated(): Promise<void>;
|
|
539
581
|
bootstrapConversation(): Promise<ChatHubBootstrapResult>;
|
|
540
|
-
listConversations(subjectId?: string
|
|
582
|
+
listConversations(subjectId?: string, options?: {
|
|
583
|
+
/** 分页(P1):不传 limit 即全量(历史行为)。 */
|
|
584
|
+
limit?: number;
|
|
585
|
+
/** keyset 游标(上一页最后一条的 encode 结果)。 */
|
|
586
|
+
cursor?: string;
|
|
587
|
+
/** 服务端搜索:标题 / conversationId 纯文本匹配。 */
|
|
588
|
+
search?: string;
|
|
589
|
+
}): Promise<ConversationRecord[]>;
|
|
541
590
|
listMessages(input: string | ListChatHubMessagesOptions): Promise<ConversationMessageRecord[]>;
|
|
542
591
|
listMessagesPage(input: string | ListChatHubMessagesOptions): Promise<ListChatHubMessagesPageResult>;
|
|
543
592
|
ensureParticipant(input: CreateParticipantInput): Promise<Record<string, unknown>>;
|
|
@@ -577,6 +626,11 @@ export interface PlatformExternalIdentityInput {
|
|
|
577
626
|
displayLabel?: string;
|
|
578
627
|
metadata?: Record<string, unknown>;
|
|
579
628
|
}
|
|
629
|
+
export interface PlatformLoginIdentifierInput {
|
|
630
|
+
/** 登录渠道。v1 仅支持 `phone`(邮箱/工号等预留,新增渠道不改契约形状)。 */
|
|
631
|
+
provider: 'phone' | string;
|
|
632
|
+
value: string;
|
|
633
|
+
}
|
|
580
634
|
export interface PlatformSyncSubjectInput {
|
|
581
635
|
classId: string;
|
|
582
636
|
displayName: string;
|
|
@@ -588,6 +642,32 @@ export interface PlatformSyncSubjectInput {
|
|
|
588
642
|
tags?: string[];
|
|
589
643
|
/** 命中已存在主体时是否更新其可写字段,默认 false(纯幂等)。 */
|
|
590
644
|
updateOnMatch?: boolean;
|
|
645
|
+
/**
|
|
646
|
+
* 登录标识(v1 = 手机号)。**带上才可登录**——平台据此把"这个成员"与
|
|
647
|
+
* "用该标识登录的人"对上;不带不算错,但该主体只作为通讯录/圈人对象存在,
|
|
648
|
+
* 登录时列不出来。
|
|
649
|
+
*
|
|
650
|
+
* 语义要点:
|
|
651
|
+
* - 与 `updateOnMatch` **正交**:传入即补绑(命中已存在主体时同样生效),
|
|
652
|
+
* 所以"先同步→后拿到手机号→再推一次"即可让身份变得可用;
|
|
653
|
+
* - **不传 ≠ 解绑**(缺席不代表移除);
|
|
654
|
+
* - 同一标识可同时挂在多个主体上(例如第三方系统里同一手机号对应
|
|
655
|
+
* 「用户前台」「员工后台」两个账号空间)——平台按声明如实挂载,不自行归并;
|
|
656
|
+
* - 系统内**首次**挂载该标识的主体为「主身份」,用于登录时的默认选择。
|
|
657
|
+
*/
|
|
658
|
+
loginIdentifiers?: PlatformLoginIdentifierInput[];
|
|
659
|
+
/**
|
|
660
|
+
* 身份标签:**由第三方声明的短标签**(如「用户前台」/「员工后台」),
|
|
661
|
+
* 用于在工作台的「切换身份」里区分同一个人的多个身份。
|
|
662
|
+
*
|
|
663
|
+
* 语义要点:
|
|
664
|
+
* - 平台**只提供字段,不发明语义**:值原样展示,不翻译、不判定、不造词;
|
|
665
|
+
* 不传时界面回退到角色(member / admin / owner)。
|
|
666
|
+
* - 属**内容字段**(与 `displayName` / `tags` 同规则):建时落库;命中已存在
|
|
667
|
+
* 主体时**仅在 `updateOnMatch: true` 下覆盖**,不传不会被清空。
|
|
668
|
+
* - 建议 ≤ 4 个字(行内展示位有限);接口上限 64 字符,超长返回 400。
|
|
669
|
+
*/
|
|
670
|
+
identityLabel?: string;
|
|
591
671
|
/** 幂等键:{source, externalKey}。重复推送只产生一个 Subject,可安全重试。 */
|
|
592
672
|
externalIdentity: PlatformExternalIdentityInput;
|
|
593
673
|
}
|
|
@@ -619,6 +699,21 @@ export declare class PlatformApi extends BaseApi {
|
|
|
619
699
|
* 收件人二选一:标签圈人(selector)或单点声明(receive,如微信客服消息迁移)。
|
|
620
700
|
*/
|
|
621
701
|
notify(targetTenantId: string, request: PlatformNotifyRequest, notify: PlatformNotifyInput): Promise<PlatformNotifyResult>;
|
|
702
|
+
/**
|
|
703
|
+
* 重发认领凭证(平台级):租户 7 天内未认领时,服务商重新签发 claimToken。
|
|
704
|
+
* **重发即作废**——epoch 递增,旧 token 立即失效;仅 pending 租户可重发。
|
|
705
|
+
*/
|
|
706
|
+
reissueClaimToken(tenantId: string): Promise<PlatformReissueClaimTokenResult>;
|
|
707
|
+
}
|
|
708
|
+
/** 重发认领凭证的返回(claimToken 有效期 7 天;重发后旧 token 立即失效)。 */
|
|
709
|
+
export interface PlatformReissueClaimTokenResult {
|
|
710
|
+
tenantId: string;
|
|
711
|
+
status: string;
|
|
712
|
+
claimToken: string;
|
|
713
|
+
expiresIn: string;
|
|
714
|
+
/** 签发轮次:0 = 首次开通签发,>=1 = 第 N 次重发。 */
|
|
715
|
+
claimEpoch: number;
|
|
716
|
+
reissued: boolean;
|
|
622
717
|
}
|
|
623
718
|
/** 声明式主体引用:调用方只需本地维护 id/昵称/头像,服务端 lazily create-or-get。 */
|
|
624
719
|
export interface PlatformLazySubject {
|
package/dist-cjs/index.js
CHANGED
|
@@ -12,9 +12,32 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.PlatformApi = exports.ChatHubAppRuntime = exports.ChatHubClient = exports.MessagesApi = exports.ConversationsApi = exports.ConversationTypesApi = exports.SubjectDirectoryApi = exports.OpenPlatformApi = exports.SDK_VERSION = void 0;
|
|
15
|
+
exports.encodeConversationListCursor = encodeConversationListCursor;
|
|
16
|
+
exports.buildConversationListCursorFromRecord = buildConversationListCursorFromRecord;
|
|
15
17
|
exports.resolveChatHubMessagePolicy = resolveChatHubMessagePolicy;
|
|
16
18
|
exports.createChatHubAppRuntime = createChatHubAppRuntime;
|
|
17
19
|
exports.SDK_VERSION = '0.1.0';
|
|
20
|
+
function encodeConversationListCursor(payload) {
|
|
21
|
+
const json = JSON.stringify({ u: payload.updatedAt, c: payload.conversationId });
|
|
22
|
+
// 同时兼容浏览器(btoa,需先转 latin1)与 Node(Buffer):base64url 无 padding。
|
|
23
|
+
const base64 = typeof Buffer !== 'undefined'
|
|
24
|
+
? Buffer.from(json, 'utf8').toString('base64')
|
|
25
|
+
: btoa(unescape(encodeURIComponent(json)));
|
|
26
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
27
|
+
}
|
|
28
|
+
/** 从会话记录构造游标(读取 updatedAt;缺失/非法返回 null)。 */
|
|
29
|
+
function buildConversationListCursorFromRecord(record) {
|
|
30
|
+
const conversationId = typeof record?.conversationId === 'string' ? record.conversationId : '';
|
|
31
|
+
const rawUpdatedAt = record?.updatedAt;
|
|
32
|
+
const updatedAt = typeof rawUpdatedAt === 'number'
|
|
33
|
+
? rawUpdatedAt
|
|
34
|
+
: typeof rawUpdatedAt === 'string'
|
|
35
|
+
? Date.parse(rawUpdatedAt)
|
|
36
|
+
: NaN;
|
|
37
|
+
if (!conversationId || !Number.isFinite(updatedAt) || updatedAt <= 0)
|
|
38
|
+
return null;
|
|
39
|
+
return encodeConversationListCursor({ updatedAt, conversationId });
|
|
40
|
+
}
|
|
18
41
|
function resolveChatHubMessagePolicy(value) {
|
|
19
42
|
const policy = value && typeof value === 'object' ? value : {};
|
|
20
43
|
return {
|
|
@@ -27,6 +50,12 @@ function resolveChatHubMessagePolicy(value) {
|
|
|
27
50
|
allowRevoke: policy.allowRevoke === true,
|
|
28
51
|
};
|
|
29
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* 断线补齐时一次 refresh 最多向后续拉的页数(防单次同步打爆服务端)。
|
|
55
|
+
* 每页按 watch 的 limit(上限 100),20 页 = 最多 2000 条,
|
|
56
|
+
* 覆盖任何合理的断线积压;真超过这个量级应走历史分页而不是实时补齐。
|
|
57
|
+
*/
|
|
58
|
+
const MAX_CATCHUP_PAGES = 20;
|
|
30
59
|
function withBearerToken(headers, token) {
|
|
31
60
|
if (!token) {
|
|
32
61
|
return headers || {};
|
|
@@ -395,13 +424,16 @@ class ChatHubAppRuntime {
|
|
|
395
424
|
conversation,
|
|
396
425
|
};
|
|
397
426
|
}
|
|
398
|
-
async listConversations(subjectId) {
|
|
427
|
+
async listConversations(subjectId, options) {
|
|
399
428
|
await this.ensureAuthenticated();
|
|
400
429
|
const resolvedSubjectId = subjectId || this.config.subjects?.[0]?.subjectId || undefined;
|
|
401
430
|
const conversations = await this.client.conversations.list({
|
|
402
431
|
tenantId: this.tenantId,
|
|
403
432
|
typeId: this.config.conversationType?.typeId,
|
|
404
433
|
subjectId: resolvedSubjectId,
|
|
434
|
+
...(options?.limit ? { limit: options.limit } : {}),
|
|
435
|
+
...(options?.cursor ? { cursor: options.cursor } : {}),
|
|
436
|
+
...(options?.search ? { search: options.search } : {}),
|
|
405
437
|
});
|
|
406
438
|
// Participants are intentionally NOT fetched here: doing so would fan out
|
|
407
439
|
// one `/conversation-runtime/participants` HTTP request per conversation
|
|
@@ -517,22 +549,41 @@ class ChatHubAppRuntime {
|
|
|
517
549
|
if (refreshPromise) {
|
|
518
550
|
return refreshPromise;
|
|
519
551
|
}
|
|
520
|
-
const afterSequence = lastSequence;
|
|
521
552
|
refreshPromise = (async () => {
|
|
522
553
|
try {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
554
|
+
let cursor = lastSequence;
|
|
555
|
+
for (let page = 0; page < MAX_CATCHUP_PAGES; page += 1) {
|
|
556
|
+
const pageAfterSequence = cursor;
|
|
557
|
+
const messages = await this.listMessages({
|
|
558
|
+
conversationId: options.conversationId,
|
|
559
|
+
limit,
|
|
560
|
+
afterSequence: pageAfterSequence,
|
|
561
|
+
});
|
|
562
|
+
const maxSequence = messages.reduce((current, message) => {
|
|
563
|
+
const sequence = typeof message.sequence === 'number' && Number.isFinite(message.sequence)
|
|
564
|
+
? Math.floor(message.sequence)
|
|
565
|
+
: current;
|
|
566
|
+
return Math.max(current, sequence);
|
|
567
|
+
}, cursor);
|
|
568
|
+
const advanced = maxSequence > cursor;
|
|
569
|
+
if (advanced) {
|
|
570
|
+
cursor = maxSequence;
|
|
571
|
+
}
|
|
572
|
+
lastSequence = Math.max(lastSequence, maxSequence);
|
|
573
|
+
await options.onMessages({
|
|
574
|
+
reason,
|
|
575
|
+
messages,
|
|
576
|
+
afterSequence: pageAfterSequence,
|
|
577
|
+
});
|
|
578
|
+
// 收敛条件:不满页说明已追平;本页没有推进说明游标打转,必须停下
|
|
579
|
+
// (否则服务端若回传不含 sequence 的消息会死循环)。
|
|
580
|
+
if (messages.length < limit || !advanced) {
|
|
581
|
+
if (page > 0) {
|
|
582
|
+
console.info('[chat-hub-sdk] watch caught up', options.conversationId, 'pages=', page + 1, 'lastSequence=', lastSequence);
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
536
587
|
}
|
|
537
588
|
catch (error) {
|
|
538
589
|
options.onError?.(error);
|
|
@@ -811,5 +862,15 @@ class PlatformApi extends BaseApi {
|
|
|
811
862
|
body: { ...notify, ...request, targetTenantId },
|
|
812
863
|
});
|
|
813
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* 重发认领凭证(平台级):租户 7 天内未认领时,服务商重新签发 claimToken。
|
|
867
|
+
* **重发即作废**——epoch 递增,旧 token 立即失效;仅 pending 租户可重发。
|
|
868
|
+
*/
|
|
869
|
+
async reissueClaimToken(tenantId) {
|
|
870
|
+
return this.request({
|
|
871
|
+
method: 'POST',
|
|
872
|
+
path: `/open-platform/platform/tenants/${encodeURIComponent(tenantId)}/claim-token`,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
814
875
|
}
|
|
815
876
|
exports.PlatformApi = PlatformApi;
|