chatwoot-router 0.1.0 → 0.2.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.
@@ -1,11 +1,12 @@
1
1
  import { DurableObject } from "cloudflare:workers";
2
2
  import type { Env } from "./env.ts";
3
+ import type { Transition } from "./webhook.ts";
3
4
  export declare const ROUTER_NAME = "global";
4
- export declare const ROUTE_BUDGET = 15;
5
+ export declare const ROUTE_BUDGET = 45;
5
6
  export declare class Router extends DurableObject<Env> {
6
7
  private readonly store;
7
8
  constructor(context: DurableObjectState, env: Env);
8
- enqueueConversation(accountId: number, conversationId: number): Promise<void>;
9
+ enqueueConversation(accountId: number, conversationId: number, transition?: Transition): Promise<void>;
9
10
  requestSweep(): Promise<void>;
10
11
  alarm(): Promise<void>;
11
12
  private sweep;
@@ -4,7 +4,6 @@ import type { Settings } from "./config.ts";
4
4
  export declare const UNCLEAR = "unclear";
5
5
  export interface RoutingStore {
6
6
  get(key: string): string | undefined;
7
- list(start: string, end: string): string[];
8
7
  set(key: string, value: string): void;
9
8
  }
10
9
  export interface RoutingContext {
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+ import { type ChatwootClient } from "../../../shared/chatwoot/api.ts";
3
+ import type { RoutingStore } from "./routing.ts";
4
+ 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
+ export declare function expectActivity(store: RoutingStore, accountId: number, conversationId: number, transition: Transition): void;
22
+ /** Preserve an observed pending departure across retries, without using metadata timestamps. */
23
+ export declare function observeStatus(store: RoutingStore, accountId: number, conversationId: number, status: string): void;
24
+ export declare function requestHandoff(store: RoutingStore, accountId: number, conversationId: number): void;
25
+ /** Counts processing failures for this turn, independently of an old queued job's backoff. */
26
+ export declare function recordFailure(store: RoutingStore, accountId: number, conversationId: number): number;
27
+ export declare function clearFailures(store: RoutingStore, accountId: number, conversationId: number): void;
28
+ export declare class ActivityPendingError extends Error {
29
+ constructor();
30
+ }
31
+ export declare function readTurn(chatwoot: ChatwootClient, store: RoutingStore, accountId: number, conversationId: number): Promise<{
32
+ messages: {
33
+ id: number;
34
+ content?: string | null | undefined;
35
+ message_type: number;
36
+ content_type?: string | null | undefined;
37
+ status?: string | null | undefined;
38
+ created_at?: number | null | undefined;
39
+ private?: boolean | null | undefined;
40
+ content_attributes?: {
41
+ email?: {
42
+ subject?: string | null | undefined;
43
+ auto_reply?: boolean | null | undefined;
44
+ text_content?: {
45
+ quoted?: string | null | undefined;
46
+ } | null | undefined;
47
+ html_content?: {
48
+ quoted?: string | null | undefined;
49
+ } | null | undefined;
50
+ } | null | undefined;
51
+ deleted?: boolean | null | undefined;
52
+ external_error?: string | null | undefined;
53
+ activity?: {
54
+ type?: string | null | undefined;
55
+ status?: string | null | undefined;
56
+ } | null | undefined;
57
+ submitted_values?: unknown;
58
+ submitted_email?: unknown;
59
+ items?: unknown;
60
+ } | null | undefined;
61
+ sender?: {
62
+ id?: number | null | undefined;
63
+ name?: string | null | undefined;
64
+ email?: string | null | undefined;
65
+ type?: string | null | undefined;
66
+ thumbnail?: string | null | undefined;
67
+ avatar_url?: string | null | undefined;
68
+ } | null | undefined;
69
+ attachments?: {
70
+ file_type?: string | null | undefined;
71
+ data_url?: string | null | undefined;
72
+ fallback_title?: string | null | undefined;
73
+ coordinates_lat?: number | null | undefined;
74
+ coordinates_long?: number | null | undefined;
75
+ meta?: {
76
+ firstName?: string | null | undefined;
77
+ lastName?: string | null | undefined;
78
+ first_name?: string | null | undefined;
79
+ last_name?: string | null | undefined;
80
+ } | null | undefined;
81
+ }[] | null | undefined;
82
+ }[];
83
+ boundary: number;
84
+ handoff: boolean;
85
+ }>;
86
+ export {};
@@ -1,4 +1,9 @@
1
+ export interface Transition {
2
+ status: string;
3
+ at?: number;
4
+ }
1
5
  export declare function eventTarget(payload: unknown): {
2
6
  accountId: number;
3
7
  conversationId: number;
8
+ transition?: Transition;
4
9
  } | undefined;
@@ -12,7 +12,11 @@ export declare class ChatwootError extends Error {
12
12
  * A conversation as returned by GET conversations/{id} and in the conversation list. The spec's
13
13
  * `status` enum omits `snoozed`, which the API does return, so the relay treats it as a string.
14
14
  */
15
- export type ChatwootConversation = components["schemas"]["conversation_show"];
15
+ export type ChatwootConversation = components["schemas"]["conversation_show"] & {
16
+ meta?: {
17
+ assignee_type?: string | null;
18
+ };
19
+ };
16
20
  declare const messageSchema: z.ZodObject<{
17
21
  id: z.ZodNumber;
18
22
  content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -34,6 +38,10 @@ declare const messageSchema: z.ZodObject<{
34
38
  }, z.core.$strip>>>;
35
39
  deleted: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
36
40
  external_error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ activity: z.ZodOptional<z.ZodNullable<z.ZodObject<{
42
+ type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
43
+ status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
44
+ }, z.core.$strip>>>;
37
45
  submitted_values: z.ZodOptional<z.ZodUnknown>;
38
46
  submitted_email: z.ZodOptional<z.ZodUnknown>;
39
47
  items: z.ZodOptional<z.ZodUnknown>;
@@ -65,6 +73,8 @@ export type ChatwootMessage = z.infer<typeof messageSchema>;
65
73
  export declare const CONVERSATIONS_PER_PAGE = 25;
66
74
  /** Chatwoot returns up to 100 messages per `after` page. */
67
75
  export declare const MESSAGE_PAGE_SIZE = 100;
76
+ /** MessageFinder returns 20 for the latest page and for `before`. */
77
+ export declare const MESSAGE_HISTORY_PAGE_SIZE = 20;
68
78
  /** The body of POST conversations/{id}/toggle_status. */
69
79
  export type StatusChange = operations["toggle-status-of-a-conversation"]["requestBody"]["content"]["application/json"];
70
80
  /** A priority for POST conversations/{id}/toggle_priority; null clears it. */
@@ -97,13 +107,17 @@ export declare function chatwootClient(baseUrl: string, token: string, fetch: Fe
97
107
  * (MessageFinder#messages_between at v4.18.0) or exclusively (as the spec describes it).
98
108
  */
99
109
  getMessage(accountId: number, conversationId: number, messageId: number): Promise<ChatwootMessage | undefined>;
110
+ listInboxes(accountId: number): Promise<number[]>;
111
+ inboxBot(accountId: number, inboxId: number): Promise<{
112
+ id: number;
113
+ } | undefined>;
100
114
  inboxName(accountId: number, inboxId: number): Promise<string | undefined>;
101
115
  /**
102
116
  * One page (CONVERSATIONS_PER_PAGE) of conversations of any assignee and the given status
103
117
  * (default: any), most recent activity first (Conversations::SortService's default,
104
118
  * `last_activity_at_desc`, at v4.18.0).
105
119
  */
106
- listConversations(accountId: number, page: number, status?: "all" | "open" | "snoozed"): Promise<ChatwootConversation[]>;
120
+ listConversations(accountId: number, page: number, status?: "all" | "open" | "snoozed" | "pending"): Promise<ChatwootConversation[]>;
107
121
  /** The content of the account's canned response with this short code, if it exists. */
108
122
  cannedResponse(accountId: number, shortCode: string): Promise<string | undefined>;
109
123
  getProfile(): Promise<{
@@ -160,7 +174,7 @@ export declare function chatwootClient(baseUrl: string, token: string, fetch: Fe
160
174
  setStatus(accountId: number, conversationId: number, change: StatusChange): Promise<void>;
161
175
  setPriority(accountId: number, conversationId: number, priority: ConversationPriority | null): Promise<void>;
162
176
  setContactBlocked(accountId: number, contactId: number, blocked: boolean): Promise<void>;
163
- assign(accountId: number, conversationId: number, assigneeId: number): Promise<void>;
177
+ assign(accountId: number, conversationId: number, assigneeId: number, assigneeType?: "User" | "AgentBot"): Promise<void>;
164
178
  /**
165
179
  * Removes the conversation's assignee the way Chatwoot's dashboard does (ConversationAction.vue
166
180
  * posts `assignee_id: null`, which AssignmentsController#create applies, at v4.18.0). The
@@ -180,6 +194,43 @@ export declare function chatwootClient(baseUrl: string, token: string, fetch: Fe
180
194
  */
181
195
  createMessage(accountId: number, conversationId: number, message: NewMessage): Promise<void>;
182
196
  };
197
+ /** A bot (or another explicit non-user assignee) must never be matched to a numeric user id. */
198
+ export declare function personAssignee(conversation: ChatwootConversation): {
199
+ id?: number;
200
+ access_token?: string;
201
+ account_id?: number;
202
+ available_name?: string;
203
+ avatar_url?: string;
204
+ confirmed?: boolean;
205
+ display_name?: string | null;
206
+ message_signature?: string | null;
207
+ email?: string;
208
+ hmac_identifier?: string;
209
+ inviter_id?: number | null;
210
+ name?: string;
211
+ provider?: string;
212
+ pubsub_token?: string;
213
+ role?: "agent" | "administrator";
214
+ ui_settings?: Record<string, unknown>;
215
+ uid?: string;
216
+ type?: string | null;
217
+ custom_attributes?: Record<string, unknown>;
218
+ accounts?: {
219
+ id?: number;
220
+ name?: string;
221
+ status?: string;
222
+ active_at?: string | null;
223
+ role?: "administrator" | "agent";
224
+ permissions?: string[];
225
+ availability?: string;
226
+ availability_status?: string;
227
+ auto_offline?: boolean;
228
+ custom_role_id?: number | null;
229
+ custom_role?: Record<string, unknown> | null;
230
+ }[];
231
+ } | undefined;
232
+ /** An actual public answer; Chatwoot templates include greetings and out-of-office notices. */
233
+ export declare function isAnsweringReply(message: ChatwootMessage): boolean;
183
234
  export declare function toRelayConversation(conversationId: number, conversation: ChatwootConversation): RelayConversation;
184
235
  export declare function toRelayMessage(message: ChatwootMessage, context: {
185
236
  account: {
@@ -15,7 +15,6 @@ export declare class QueueStore {
15
15
  get(key: string): string | undefined;
16
16
  set(key: string, value: string, ttlMs?: number): void;
17
17
  delete(key: string): void;
18
- list(start: string, end: string): string[];
19
18
  /**
20
19
  * Adds a job, or marks an existing one as needing another run (its version changes). A job
21
20
  * that is backing off after failures keeps its schedule.
@@ -33,5 +32,9 @@ export declare class QueueStore {
33
32
  * budget, or Discord rate limits it.
34
33
  */
35
34
  deferJob(job: Job, delayMs?: number): void;
35
+ /** One short re-read for a racing status webhook, then wait for an event or a sweep. */
36
+ holdJob(job: Job): void;
37
+ /** Suspended conversation jobs need reconciliation even outside the ordinary activity window. */
38
+ wakeHeldJobs(): void;
36
39
  prune(): void;
37
40
  }
@@ -49,7 +49,9 @@ export interface RelayConversation {
49
49
  blocked?: boolean;
50
50
  avatarUrl?: string | null;
51
51
  };
52
+ /** Person only; agent bots are represented as unassigned. */
52
53
  assignee?: RelayAssignee | null;
54
+ assigneeType?: string | null;
53
55
  customAttributes: Record<string, unknown>;
54
56
  }
55
57
  export interface RelayMessage {
@@ -64,6 +66,8 @@ export interface RelayMessage {
64
66
  emailSubject?: string | null;
65
67
  /** An automatic email reply (out of office, for example): relayed without notifications. */
66
68
  autoReply?: boolean;
69
+ /** A qualifying public reply follows this message in Chatwoot. */
70
+ answered?: boolean;
67
71
  attachments: RelayAttachment[];
68
72
  /** A bot's options to pick, cards, or articles, with a link each when they have one. */
69
73
  items?: RelayItem[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatwoot-router",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Route Chatwoot conversations with TypeSafe Jev on Cloudflare Workers.",
5
5
  "keywords": [
6
6
  "chatwoot",
@@ -1,14 +0,0 @@
1
- import { z } from "zod";
2
- import type { RoutingContext, RoutingStore } from "./routing.ts";
3
- declare const effectSchema: z.ZodObject<{
4
- inputId: z.ZodNumber;
5
- kind: z.ZodNullable<z.ZodString>;
6
- handled: z.ZodNumber;
7
- }, z.core.$strip>;
8
- export declare function actOnce(ctx: RoutingContext, key: string, effect: z.infer<typeof effectSchema>, record: "before" | "after", act: () => Promise<unknown>): Promise<void>;
9
- export declare function readEffects(store: RoutingStore, accountId: number, conversationId: number): {
10
- inputId: number;
11
- kind: string | null;
12
- handled: number;
13
- }[];
14
- export {};
@@ -1,6 +0,0 @@
1
- export declare const ROUTING_ATTRIBUTES: {
2
- readonly seen: "routing_seen";
3
- readonly handled: "routing_handled";
4
- readonly kind: "routing_kind";
5
- };
6
- export declare function messageWatermark(value: unknown): number;
@@ -1,11 +0,0 @@
1
- import { z } from "zod";
2
- import type { reconcileSchema } from "./config.ts";
3
- import type { QueueStore } from "./store.ts";
4
- declare const passSchema: z.ZodObject<{
5
- cutoff: z.ZodNumber;
6
- page: z.ZodNumber;
7
- startedAt: z.ZodNumber;
8
- }, z.core.$strip>;
9
- export declare function readSweepPass(store: QueueStore, accountId: number, reconcile: z.infer<typeof reconcileSchema>): z.infer<typeof passSchema>;
10
- export declare function saveSweepPass(store: QueueStore, accountId: number, pass: z.infer<typeof passSchema>, nextPage?: number): void;
11
- export {};