@pmate/chat-sdk 0.4.0 → 0.5.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/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/runtime/activity.d.ts +14 -4
- package/lib/runtime/activity.js +18 -13
- package/lib/runtime/chatClient.d.ts +3 -2
- package/lib/runtime/chatClient.js +7 -4
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ChatRuntimeMessage } from "./protocol.js";
|
|
2
|
-
export type ChatActivityState = "
|
|
2
|
+
export type ChatActivityState = "working" | "typing" | "idle";
|
|
3
3
|
export type ChatActivityBody = {
|
|
4
4
|
kind: "activity";
|
|
5
5
|
appId: string;
|
|
@@ -7,6 +7,9 @@ export type ChatActivityBody = {
|
|
|
7
7
|
activityId: string;
|
|
8
8
|
seq: number;
|
|
9
9
|
state: ChatActivityState;
|
|
10
|
+
/** Open operation identifier, e.g. thinking, search, read. Unknown phases remain displayable. */
|
|
11
|
+
phase?: string;
|
|
12
|
+
replyTo?: string;
|
|
10
13
|
ttlMs: number;
|
|
11
14
|
/** Short public operation label, never private model reasoning. */
|
|
12
15
|
detail?: string;
|
|
@@ -22,6 +25,7 @@ export type ActivityTarget = {
|
|
|
22
25
|
to: string;
|
|
23
26
|
threadKind?: "dm" | "group";
|
|
24
27
|
recipients?: string[];
|
|
28
|
+
replyTo?: string;
|
|
25
29
|
};
|
|
26
30
|
export declare const ACTIVITY_TTL_MS = 15000;
|
|
27
31
|
export declare function createActivityMessage(input: ActivityTarget & {
|
|
@@ -30,6 +34,7 @@ export declare function createActivityMessage(input: ActivityTarget & {
|
|
|
30
34
|
activityId: string;
|
|
31
35
|
seq: number;
|
|
32
36
|
state: ChatActivityState;
|
|
37
|
+
phase?: string;
|
|
33
38
|
detail?: string;
|
|
34
39
|
}): ChatRuntimeMessage;
|
|
35
40
|
export declare function parseChatActivity(message: ChatRuntimeMessage, appId: string, profileId: string, now?: number): ChatActivity | null;
|
|
@@ -45,23 +50,28 @@ export declare class ChatActivityStore {
|
|
|
45
50
|
clear(): void;
|
|
46
51
|
private emit;
|
|
47
52
|
}
|
|
53
|
+
export type ChatActivityUpdate = {
|
|
54
|
+
state: Exclude<ChatActivityState, "idle">;
|
|
55
|
+
phase?: string;
|
|
56
|
+
detail?: string;
|
|
57
|
+
};
|
|
48
58
|
/** Coalesced, serialized, best-effort progress; transport failures never reject the chat task. */
|
|
49
59
|
export declare class ChatActivityPublisher {
|
|
50
60
|
private readonly send;
|
|
51
61
|
private readonly onError;
|
|
52
62
|
private seq;
|
|
53
63
|
private stopped;
|
|
54
|
-
private
|
|
55
|
-
private detail;
|
|
64
|
+
private current;
|
|
56
65
|
private pending;
|
|
57
66
|
private sending;
|
|
58
67
|
private timer;
|
|
59
68
|
constructor(send: (value: {
|
|
60
69
|
state: ChatActivityState;
|
|
70
|
+
phase?: string;
|
|
61
71
|
detail?: string;
|
|
62
72
|
seq: number;
|
|
63
73
|
}) => Promise<unknown>, onError?: (error: unknown) => void);
|
|
64
|
-
update(
|
|
74
|
+
update(value: ChatActivityUpdate): void;
|
|
65
75
|
stop(): Promise<void>;
|
|
66
76
|
private enqueue;
|
|
67
77
|
private flush;
|
package/lib/runtime/activity.js
CHANGED
|
@@ -3,6 +3,8 @@ import { dmThreadId, groupThreadId } from "./thread.js";
|
|
|
3
3
|
export const ACTIVITY_TTL_MS = 15_000;
|
|
4
4
|
export function createActivityMessage(input) {
|
|
5
5
|
const body = { kind: "activity", appId: input.appId, threadKind: input.threadKind ?? "dm", activityId: input.activityId, seq: input.seq, state: input.state, ttlMs: ACTIVITY_TTL_MS,
|
|
6
|
+
...(input.phase ? { phase: input.phase } : {}),
|
|
7
|
+
...(input.replyTo ? { replyTo: input.replyTo } : {}),
|
|
6
8
|
...(input.detail ? { detail: Array.from(input.detail).slice(0, 120).join("") } : {}) };
|
|
7
9
|
const message = createRuntimeMsg(input.from, input.to, ChatMsgOp.CHAT, body);
|
|
8
10
|
if (body.threadKind === "group")
|
|
@@ -16,7 +18,9 @@ export function parseChatActivity(message, appId, profileId, now = Date.now()) {
|
|
|
16
18
|
|| (body.threadKind === "dm" ? message.to !== profileId : !message.recipients?.includes(profileId))
|
|
17
19
|
|| typeof body.activityId !== "string" || !body.activityId || body.activityId.length > 128
|
|
18
20
|
|| !Number.isSafeInteger(body.seq) || body.seq < 0
|
|
19
|
-
|| !["
|
|
21
|
+
|| !["working", "typing", "idle"].includes(body.state ?? "")
|
|
22
|
+
|| (body.phase !== undefined && (typeof body.phase !== "string" || !/^[a-z][a-z0-9._-]{0,63}$/.test(body.phase)))
|
|
23
|
+
|| (body.replyTo !== undefined && (typeof body.replyTo !== "string" || !body.replyTo || body.replyTo.length > 128))
|
|
20
24
|
|| !Number.isFinite(body.ttlMs) || body.ttlMs < 1000 || body.ttlMs > ACTIVITY_TTL_MS
|
|
21
25
|
|| !Number.isFinite(message.t) || message.t > now + ACTIVITY_TTL_MS || message.t + body.ttlMs <= now
|
|
22
26
|
|| (body.detail !== undefined && (typeof body.detail !== "string" || Array.from(body.detail).length > 120)))
|
|
@@ -52,7 +56,7 @@ export class ChatActivityStore {
|
|
|
52
56
|
return;
|
|
53
57
|
let changed = false;
|
|
54
58
|
for (const [key, record] of this.records) {
|
|
55
|
-
if (record.from === message.from && record.to === message.to && record.threadKind === body.threadKind && record.updatedAt <= message.t && record.state !== "idle") {
|
|
59
|
+
if (record.from === message.from && record.to === message.to && record.threadKind === body.threadKind && (record.replyTo && body.content?.replyTo ? record.replyTo === body.content.replyTo : record.updatedAt <= message.t) && record.state !== "idle") {
|
|
56
60
|
this.records.set(key, { ...record, state: "idle" });
|
|
57
61
|
changed = true;
|
|
58
62
|
}
|
|
@@ -76,33 +80,31 @@ export class ChatActivityPublisher {
|
|
|
76
80
|
onError;
|
|
77
81
|
seq = 0;
|
|
78
82
|
stopped = false;
|
|
79
|
-
|
|
80
|
-
detail;
|
|
83
|
+
current = { state: "working" };
|
|
81
84
|
pending;
|
|
82
85
|
sending;
|
|
83
86
|
timer;
|
|
84
87
|
constructor(send, onError = () => { }) {
|
|
85
88
|
this.send = send;
|
|
86
89
|
this.onError = onError;
|
|
87
|
-
this.timer = setInterval(() => this.enqueue(this.
|
|
90
|
+
this.timer = setInterval(() => this.enqueue(this.current), 5000);
|
|
88
91
|
}
|
|
89
|
-
update(
|
|
92
|
+
update(value) {
|
|
90
93
|
if (this.stopped)
|
|
91
94
|
return;
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.enqueue(state, detail);
|
|
95
|
+
this.current = value;
|
|
96
|
+
this.enqueue(value);
|
|
95
97
|
}
|
|
96
98
|
async stop() {
|
|
97
99
|
if (!this.stopped) {
|
|
98
100
|
this.stopped = true;
|
|
99
101
|
clearInterval(this.timer);
|
|
100
|
-
this.enqueue("idle");
|
|
102
|
+
this.enqueue({ state: "idle" });
|
|
101
103
|
}
|
|
102
104
|
await this.sending;
|
|
103
105
|
}
|
|
104
|
-
enqueue(
|
|
105
|
-
this.pending =
|
|
106
|
+
enqueue(value) {
|
|
107
|
+
this.pending = value;
|
|
106
108
|
if (!this.sending)
|
|
107
109
|
this.sending = this.flush().finally(() => { this.sending = undefined; });
|
|
108
110
|
}
|
|
@@ -114,7 +116,10 @@ export class ChatActivityPublisher {
|
|
|
114
116
|
await this.send({ ...current, seq: this.seq++ });
|
|
115
117
|
}
|
|
116
118
|
catch (error) {
|
|
117
|
-
|
|
119
|
+
try {
|
|
120
|
+
this.onError(error);
|
|
121
|
+
}
|
|
122
|
+
catch { /* Error reporting must not break progress cleanup. */ }
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatActivityPublisher, type ActivityTarget, type ChatActivity, type ChatActivityState } from "./activity.js";
|
|
1
|
+
import { ChatActivityPublisher, type ActivityTarget, type ChatActivity, type ChatActivityState, type ChatActivityUpdate } from "./activity.js";
|
|
2
2
|
import type { ChatMention } from "../mentions/model";
|
|
3
3
|
import { type BotLoginSession } from "./botAuth.js";
|
|
4
4
|
import { type PmateBotKeyFile } from "./botKey.js";
|
|
@@ -34,9 +34,10 @@ export declare class ChatClient {
|
|
|
34
34
|
activityId: string;
|
|
35
35
|
seq: number;
|
|
36
36
|
state: ChatActivityState;
|
|
37
|
+
phase?: string;
|
|
37
38
|
detail?: string;
|
|
38
39
|
}): Promise<ChatRuntimeMessage>;
|
|
39
|
-
startActivity(target: ActivityTarget,
|
|
40
|
+
startActivity(target: ActivityTarget, initial?: ChatActivityUpdate): ChatActivityPublisher;
|
|
40
41
|
requestFriend(input: {
|
|
41
42
|
to: string;
|
|
42
43
|
message?: string;
|
|
@@ -42,8 +42,11 @@ export class ChatClient {
|
|
|
42
42
|
this.client.on(ChatRealtimeEvents.Message, (message) => {
|
|
43
43
|
this.handleIncoming(message);
|
|
44
44
|
});
|
|
45
|
-
this.client.on(ChatRealtimeEvents.Connected, (connected) => { if (!connected)
|
|
46
|
-
this.
|
|
45
|
+
this.client.on(ChatRealtimeEvents.Connected, (connected) => { if (!connected) {
|
|
46
|
+
for (const publisher of this.publishers)
|
|
47
|
+
void publisher.stop();
|
|
48
|
+
this.activities.clear();
|
|
49
|
+
} });
|
|
47
50
|
await this.waitUntilConnected();
|
|
48
51
|
await this.sendAuth();
|
|
49
52
|
return this;
|
|
@@ -71,13 +74,13 @@ export class ChatClient {
|
|
|
71
74
|
await this.sendAndWaitForAck(message);
|
|
72
75
|
return message;
|
|
73
76
|
}
|
|
74
|
-
startActivity(target,
|
|
77
|
+
startActivity(target, initial = { state: "typing" }) {
|
|
75
78
|
const activityId = createRuntimeMsg(this.profileId(), target.to, ChatMsgOp.CHAT, null).hash;
|
|
76
79
|
const publisher = new ChatActivityPublisher(value => this.sendActivity({ ...target, activityId, ...value }), error => this.options.onActivityError?.(error));
|
|
77
80
|
this.publishers.add(publisher);
|
|
78
81
|
const stop = publisher.stop.bind(publisher);
|
|
79
82
|
publisher.stop = async () => { await stop(); this.publishers.delete(publisher); };
|
|
80
|
-
publisher.update(
|
|
83
|
+
publisher.update(initial);
|
|
81
84
|
return publisher;
|
|
82
85
|
}
|
|
83
86
|
async requestFriend(input) {
|