chatwoot-router 0.2.0 → 0.3.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/CHANGELOG.md +46 -1
- package/README.md +81 -31
- package/lib/packages/chatwoot-router/src/coordinator.d.ts +10 -0
- package/lib/packages/chatwoot-router/src/env.d.ts +2 -0
- package/lib/packages/chatwoot-router/src/index.d.ts +1 -0
- package/lib/packages/chatwoot-router/src/index.js +438 -183
- package/lib/packages/chatwoot-router/src/reply.d.ts +5 -0
- package/lib/packages/chatwoot-router/src/router.d.ts +1 -5
- package/lib/packages/chatwoot-router/src/routing.d.ts +0 -3
- package/lib/packages/chatwoot-router/src/turn.d.ts +53 -21
- package/lib/shared/alarm.d.ts +2 -0
- package/lib/shared/budget.d.ts +15 -2
- package/lib/shared/chatwoot/api.d.ts +7 -5
- package/lib/shared/concurrent.d.ts +4 -0
- package/lib/shared/deadline.d.ts +6 -0
- package/lib/shared/rate-limit.d.ts +5 -0
- package/lib/shared/store.d.ts +7 -3
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ChatwootClient, type ChatwootMessage } from "../../../shared/chatwoot/api.ts";
|
|
2
|
+
/** A Chatwoot creation receipt, not a channel delivery receipt. */
|
|
3
|
+
export declare function confirmedReply(message: ChatwootMessage | undefined, botId: number | undefined): boolean;
|
|
4
|
+
/** Read past turn boundaries, including replies made by the former relay's brand bot. */
|
|
5
|
+
export declare function replyHistory(chatwoot: ChatwootClient, accountId: number, conversationId: number, botId: number | undefined, freshPages?: readonly ChatwootMessage[][]): Promise<"found" | "complete-none" | "unknown">;
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { DurableObject } from "cloudflare:workers";
|
|
2
2
|
import type { Env } from "./env.ts";
|
|
3
3
|
import type { Transition } from "./webhook.ts";
|
|
4
|
-
export declare
|
|
5
|
-
export declare const ROUTE_BUDGET = 45;
|
|
4
|
+
export declare function conversationName(accountId: number, conversationId: number): string;
|
|
6
5
|
export declare class Router extends DurableObject<Env> {
|
|
7
6
|
private readonly store;
|
|
8
7
|
constructor(context: DurableObjectState, env: Env);
|
|
9
8
|
enqueueConversation(accountId: number, conversationId: number, transition?: Transition): Promise<void>;
|
|
10
|
-
requestSweep(): Promise<void>;
|
|
11
9
|
alarm(): Promise<void>;
|
|
12
|
-
private sweep;
|
|
13
10
|
private enqueue;
|
|
14
|
-
private schedule;
|
|
15
11
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { type ChatwootClient, type Fetch } from "../../../shared/chatwoot/api.ts";
|
|
2
2
|
import type { Settings } from "./config.ts";
|
|
3
|
-
/** Jev's answer when no owner fits; also the reserved route name. */
|
|
4
|
-
export declare const UNCLEAR = "unclear";
|
|
5
3
|
export interface RoutingStore {
|
|
6
4
|
get(key: string): string | undefined;
|
|
7
5
|
set(key: string, value: string): void;
|
|
@@ -15,4 +13,3 @@ export interface RoutingContext {
|
|
|
15
13
|
}
|
|
16
14
|
export declare function routesAccount(settings: Settings, accountId: number): boolean;
|
|
17
15
|
export declare function routeConversation(ctx: RoutingContext, accountId: number, conversationId: number): Promise<"defer" | undefined>;
|
|
18
|
-
export declare function sanitize(text: string, identities: Array<string | null | undefined>): string;
|
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { type ChatwootClient } from "../../../shared/chatwoot/api.ts";
|
|
3
2
|
import type { RoutingStore } from "./routing.ts";
|
|
4
3
|
import type { Transition } from "./webhook.ts";
|
|
5
|
-
declare const guardSchema: z.ZodObject<{
|
|
6
|
-
boundary: z.ZodOptional<z.ZodNumber>;
|
|
7
|
-
claimed: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
-
pending: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
-
transitionAt: z.ZodOptional<z.ZodNumber>;
|
|
10
|
-
expected: z.ZodOptional<z.ZodObject<{
|
|
11
|
-
status: z.ZodString;
|
|
12
|
-
at: z.ZodOptional<z.ZodNumber>;
|
|
13
|
-
after: z.ZodOptional<z.ZodNumber>;
|
|
14
|
-
}, z.core.$strip>>;
|
|
15
|
-
handoff: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
-
failures: z.ZodDefault<z.ZodNumber>;
|
|
17
|
-
}, z.core.$strip>;
|
|
18
|
-
type Guard = z.infer<typeof guardSchema>;
|
|
19
|
-
export declare function readGuard(store: RoutingStore, accountId: number, conversationId: number): Guard;
|
|
20
|
-
export declare function saveGuard(store: RoutingStore, accountId: number, conversationId: number, guard: Guard): void;
|
|
21
4
|
export declare function expectActivity(store: RoutingStore, accountId: number, conversationId: number, transition: Transition): void;
|
|
22
5
|
/** Preserve an observed pending departure across retries, without using metadata timestamps. */
|
|
23
6
|
export declare function observeStatus(store: RoutingStore, accountId: number, conversationId: number, status: string): void;
|
|
@@ -25,12 +8,62 @@ export declare function requestHandoff(store: RoutingStore, accountId: number, c
|
|
|
25
8
|
/** Counts processing failures for this turn, independently of an old queued job's backoff. */
|
|
26
9
|
export declare function recordFailure(store: RoutingStore, accountId: number, conversationId: number): number;
|
|
27
10
|
export declare function clearFailures(store: RoutingStore, accountId: number, conversationId: number): void;
|
|
28
|
-
export declare class ActivityPendingError extends Error {
|
|
29
|
-
constructor();
|
|
30
|
-
}
|
|
31
11
|
export declare function readTurn(chatwoot: ChatwootClient, store: RoutingStore, accountId: number, conversationId: number): Promise<{
|
|
12
|
+
pages: {
|
|
13
|
+
id: number;
|
|
14
|
+
conversation_id?: number | undefined;
|
|
15
|
+
content?: string | null | undefined;
|
|
16
|
+
message_type: number;
|
|
17
|
+
content_type?: string | null | undefined;
|
|
18
|
+
status?: string | null | undefined;
|
|
19
|
+
created_at?: number | null | undefined;
|
|
20
|
+
private?: boolean | null | undefined;
|
|
21
|
+
content_attributes?: {
|
|
22
|
+
email?: {
|
|
23
|
+
subject?: string | null | undefined;
|
|
24
|
+
auto_reply?: boolean | null | undefined;
|
|
25
|
+
text_content?: {
|
|
26
|
+
quoted?: string | null | undefined;
|
|
27
|
+
} | null | undefined;
|
|
28
|
+
html_content?: {
|
|
29
|
+
quoted?: string | null | undefined;
|
|
30
|
+
} | null | undefined;
|
|
31
|
+
} | null | undefined;
|
|
32
|
+
deleted?: boolean | null | undefined;
|
|
33
|
+
external_error?: string | null | undefined;
|
|
34
|
+
activity?: {
|
|
35
|
+
type?: string | null | undefined;
|
|
36
|
+
status?: string | null | undefined;
|
|
37
|
+
} | null | undefined;
|
|
38
|
+
submitted_values?: unknown;
|
|
39
|
+
submitted_email?: unknown;
|
|
40
|
+
items?: unknown;
|
|
41
|
+
} | null | undefined;
|
|
42
|
+
sender?: {
|
|
43
|
+
id?: number | null | undefined;
|
|
44
|
+
name?: string | null | undefined;
|
|
45
|
+
email?: string | null | undefined;
|
|
46
|
+
type?: string | null | undefined;
|
|
47
|
+
thumbnail?: string | null | undefined;
|
|
48
|
+
avatar_url?: string | null | undefined;
|
|
49
|
+
} | null | undefined;
|
|
50
|
+
attachments?: {
|
|
51
|
+
file_type?: string | null | undefined;
|
|
52
|
+
data_url?: string | null | undefined;
|
|
53
|
+
fallback_title?: string | null | undefined;
|
|
54
|
+
coordinates_lat?: number | null | undefined;
|
|
55
|
+
coordinates_long?: number | null | undefined;
|
|
56
|
+
meta?: {
|
|
57
|
+
firstName?: string | null | undefined;
|
|
58
|
+
lastName?: string | null | undefined;
|
|
59
|
+
first_name?: string | null | undefined;
|
|
60
|
+
last_name?: string | null | undefined;
|
|
61
|
+
} | null | undefined;
|
|
62
|
+
}[] | null | undefined;
|
|
63
|
+
}[][];
|
|
32
64
|
messages: {
|
|
33
65
|
id: number;
|
|
66
|
+
conversation_id?: number | undefined;
|
|
34
67
|
content?: string | null | undefined;
|
|
35
68
|
message_type: number;
|
|
36
69
|
content_type?: string | null | undefined;
|
|
@@ -83,4 +116,3 @@ export declare function readTurn(chatwoot: ChatwootClient, store: RoutingStore,
|
|
|
83
116
|
boundary: number;
|
|
84
117
|
handoff: boolean;
|
|
85
118
|
}>;
|
|
86
|
-
export {};
|
package/lib/shared/budget.d.ts
CHANGED
|
@@ -2,12 +2,25 @@ import type { Fetch } from "./chatwoot/api.ts";
|
|
|
2
2
|
export declare class BudgetExhaustedError extends Error {
|
|
3
3
|
constructor();
|
|
4
4
|
}
|
|
5
|
+
export { JobDeadlineError } from "./deadline.ts";
|
|
6
|
+
export declare const METADATA_TIMEOUT_MS = 1500;
|
|
7
|
+
export declare const TRANSFER_TIMEOUT_MS = 8000;
|
|
8
|
+
export declare const JOB_SLICE_MS = 10000;
|
|
5
9
|
export declare class Budget {
|
|
6
10
|
readonly limit: number;
|
|
7
11
|
private readonly fetchImpl;
|
|
8
|
-
private readonly timeoutMs;
|
|
9
12
|
private used;
|
|
10
|
-
|
|
13
|
+
private slice;
|
|
14
|
+
private sliceEnds;
|
|
15
|
+
constructor(limit: number, fetchImpl?: Fetch);
|
|
11
16
|
get remaining(): number;
|
|
17
|
+
startSlice(ms?: number): void;
|
|
18
|
+
requireTime(ms: number): void;
|
|
19
|
+
checkpoint(): void;
|
|
20
|
+
consume(): void;
|
|
21
|
+
require(requests: number): void;
|
|
22
|
+
controlSignal(ms: number): AbortSignal;
|
|
23
|
+
fetchWith(timeoutMs: number): Fetch;
|
|
12
24
|
readonly fetch: Fetch;
|
|
25
|
+
private request;
|
|
13
26
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { RateLimitStore } from "../rate-limit.ts";
|
|
2
3
|
import type { RelayConversation, RelayMessage } from "../types.ts";
|
|
3
4
|
import type { components, operations } from "./schema.ts";
|
|
4
5
|
export type Fetch = (input: Request) => Promise<Response>;
|
|
@@ -6,7 +7,8 @@ export type Fetch = (input: Request) => Promise<Response>;
|
|
|
6
7
|
export declare class ChatwootError extends Error {
|
|
7
8
|
readonly status: number;
|
|
8
9
|
readonly conversationMissing: boolean;
|
|
9
|
-
|
|
10
|
+
readonly retryAfterMs: number | undefined;
|
|
11
|
+
constructor(status: number, operation: string, conversationMissing?: boolean, retryAfterMs?: number);
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* A conversation as returned by GET conversations/{id} and in the conversation list. The spec's
|
|
@@ -19,6 +21,7 @@ export type ChatwootConversation = components["schemas"]["conversation_show"] &
|
|
|
19
21
|
};
|
|
20
22
|
declare const messageSchema: z.ZodObject<{
|
|
21
23
|
id: z.ZodNumber;
|
|
24
|
+
conversation_id: z.ZodOptional<z.ZodNumber>;
|
|
22
25
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
23
26
|
message_type: z.ZodNumber;
|
|
24
27
|
content_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -96,7 +99,7 @@ export type ChatwootClient = ReturnType<typeof chatwootClient>;
|
|
|
96
99
|
type MessageQuery = NonNullable<operations["list-all-messages"]["parameters"]["query"]> & {
|
|
97
100
|
filter_internal_messages?: true;
|
|
98
101
|
};
|
|
99
|
-
export declare function chatwootClient(baseUrl: string, token: string, fetch: Fetch): {
|
|
102
|
+
export declare function chatwootClient(baseUrl: string, token: string, fetch: Fetch, limits?: RateLimitStore): {
|
|
100
103
|
/** The conversation, or undefined when it does not exist (it was deleted). */
|
|
101
104
|
getConversation(accountId: number, conversationId: number): Promise<ChatwootConversation | undefined>;
|
|
102
105
|
/** Messages with an id above `after`, oldest first; without `after`, the latest page. */
|
|
@@ -107,7 +110,6 @@ export declare function chatwootClient(baseUrl: string, token: string, fetch: Fe
|
|
|
107
110
|
* (MessageFinder#messages_between at v4.18.0) or exclusively (as the spec describes it).
|
|
108
111
|
*/
|
|
109
112
|
getMessage(accountId: number, conversationId: number, messageId: number): Promise<ChatwootMessage | undefined>;
|
|
110
|
-
listInboxes(accountId: number): Promise<number[]>;
|
|
111
113
|
inboxBot(accountId: number, inboxId: number): Promise<{
|
|
112
114
|
id: number;
|
|
113
115
|
} | undefined>;
|
|
@@ -186,13 +188,13 @@ export declare function chatwootClient(baseUrl: string, token: string, fetch: Fe
|
|
|
186
188
|
conversationLabels(accountId: number, conversationId: number): Promise<string[]>;
|
|
187
189
|
/** Replaces the conversation's labels (the spec's "Add Labels" overwrites the list). */
|
|
188
190
|
setLabels(accountId: number, conversationId: number, labels: string[]): Promise<void>;
|
|
189
|
-
setCustomAttributes
|
|
191
|
+
setCustomAttributes(accountId: number, conversationId: number, attributes: Record<string, unknown>): Promise<void>;
|
|
190
192
|
/**
|
|
191
193
|
* Sends an outgoing message (or private note). With files it is sent as the spec's
|
|
192
194
|
* multipart/form-data body; the generated type renders its `attachments[]` binaries as
|
|
193
195
|
* strings, so the serializer appends the files itself.
|
|
194
196
|
*/
|
|
195
|
-
createMessage(accountId: number, conversationId: number, message: NewMessage): Promise<
|
|
197
|
+
createMessage(accountId: number, conversationId: number, message: NewMessage): Promise<ChatwootMessage | undefined>;
|
|
196
198
|
};
|
|
197
199
|
/** A bot (or another explicit non-user assignee) must never be matched to a numeric user id. */
|
|
198
200
|
export declare function personAssignee(conversation: ChatwootConversation): {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Bounds non-fetch dependencies too, including an RPC whose enqueue outcome can be unknown. */
|
|
2
|
+
export declare function within<T>(pending: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
3
|
+
export declare class JobDeadlineError extends Error {
|
|
4
|
+
readonly requestStarted: boolean;
|
|
5
|
+
constructor(requestStarted?: boolean);
|
|
6
|
+
}
|
package/lib/shared/store.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
export declare function retryDelay(attempts: number): number;
|
|
2
|
-
export
|
|
2
|
+
export type Job = {
|
|
3
3
|
key: string;
|
|
4
4
|
payload: string;
|
|
5
5
|
version: number;
|
|
6
6
|
attempts: number;
|
|
7
7
|
/** When the job was first queued (ms since the epoch). */
|
|
8
8
|
createdAt: number;
|
|
9
|
-
}
|
|
9
|
+
};
|
|
10
10
|
export declare class QueueStore {
|
|
11
11
|
protected readonly sql: SqlStorage;
|
|
12
12
|
protected readonly now: () => number;
|
|
13
|
-
|
|
13
|
+
private readonly atomic;
|
|
14
|
+
constructor(sql: SqlStorage, now?: () => number, atomic?: <T>(write: () => T) => T);
|
|
15
|
+
transaction<T>(write: () => T): T;
|
|
14
16
|
migrate(): void;
|
|
15
17
|
get(key: string): string | undefined;
|
|
16
18
|
set(key: string, value: string, ttlMs?: number): void;
|
|
@@ -21,6 +23,8 @@ export declare class QueueStore {
|
|
|
21
23
|
*/
|
|
22
24
|
enqueue(key: string, priority: number, payload: string, notBefore?: number): void;
|
|
23
25
|
nextDueJob(): Job | undefined;
|
|
26
|
+
/** A bounded due batch; independent scan and delivery work can select their own keys. */
|
|
27
|
+
nextDueJobs(limit: number, prefix?: string): Job[];
|
|
24
28
|
/** Earliest due time. */
|
|
25
29
|
nextWakeup(): number | undefined;
|
|
26
30
|
/** Removes a finished job unless it was enqueued again while running. */
|