@dxv-systems/helpdesk 0.0.0-stage → 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.
Files changed (46) hide show
  1. package/README.md +272 -2
  2. package/dist/bounce.d.ts +13 -0
  3. package/dist/bounce.js +79 -0
  4. package/dist/controls.d.ts +13 -0
  5. package/dist/controls.js +82 -0
  6. package/dist/draft-body.d.ts +4 -0
  7. package/dist/draft-body.js +38 -0
  8. package/dist/drafting.d.ts +18 -0
  9. package/dist/drafting.js +243 -0
  10. package/dist/errors.d.ts +60 -0
  11. package/dist/errors.js +133 -0
  12. package/dist/inbox/handlers.d.ts +36 -0
  13. package/dist/inbox/handlers.js +523 -0
  14. package/dist/inbox/messages.d.ts +34 -0
  15. package/dist/inbox/messages.js +66 -0
  16. package/dist/inbox/plain-text.d.ts +1 -0
  17. package/dist/inbox/plain-text.js +58 -0
  18. package/dist/inbox/types.d.ts +116 -0
  19. package/dist/inbox/types.js +2 -0
  20. package/dist/index.d.ts +12 -0
  21. package/dist/index.js +66 -0
  22. package/dist/ingest.d.ts +25 -0
  23. package/dist/ingest.js +335 -0
  24. package/dist/internal.d.ts +8 -0
  25. package/dist/internal.js +44 -0
  26. package/dist/noise.d.ts +17 -0
  27. package/dist/noise.js +77 -0
  28. package/dist/react.d.ts +97 -0
  29. package/dist/react.js +576 -0
  30. package/dist/reply-attachments.d.ts +28 -0
  31. package/dist/reply-attachments.js +119 -0
  32. package/dist/sending.d.ts +34 -0
  33. package/dist/sending.js +268 -0
  34. package/dist/server.d.ts +6 -0
  35. package/dist/server.js +10 -0
  36. package/dist/states.d.ts +13 -0
  37. package/dist/states.js +62 -0
  38. package/dist/subject.d.ts +2 -0
  39. package/dist/subject.js +30 -0
  40. package/dist/testing/index.d.ts +97 -0
  41. package/dist/testing/index.js +472 -0
  42. package/dist/threading.d.ts +18 -0
  43. package/dist/threading.js +66 -0
  44. package/dist/types.d.ts +339 -0
  45. package/dist/types.js +2 -0
  46. package/package.json +71 -4
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.currentReplyRun = currentReplyRun;
4
+ exports.ensureDraft = ensureDraft;
5
+ exports.startDraftFor = startDraftFor;
6
+ exports.completeDraft = completeDraft;
7
+ exports.regenerateDraft = regenerateDraft;
8
+ exports.editDraft = editDraft;
9
+ exports.sweepStuckDrafts = sweepStuckDrafts;
10
+ exports.unignoreThread = unignoreThread;
11
+ const controls_1 = require("./controls");
12
+ const errors_1 = require("./errors");
13
+ const internal_1 = require("./internal");
14
+ const states_1 = require("./states");
15
+ const DEFAULT_MIN_CONFIDENCE = 0.7;
16
+ const DEFAULT_RESUME_DRAFT_MS = 2 * 60 * 1000;
17
+ const DEFAULT_DRAFT_TIMEOUT_MS = 15 * 60 * 1000;
18
+ const inFlight = (run) => run.status === "requested" || run.status === "started";
19
+ function currentReplyRun(deps, thread, run) {
20
+ if (!run || run.status === "failed")
21
+ return null;
22
+ if (!deps.drafter && run.inboundMessageId !== thread.latestInboundId)
23
+ return null;
24
+ return run;
25
+ }
26
+ function reviewReasonFor(deps, thread, run) {
27
+ if (run.status === "failed")
28
+ return "drafter_failed";
29
+ if (thread.latestInboundId !== run.inboundMessageId)
30
+ return "stale_draft";
31
+ if ((run.confidence ?? 0) < (deps.config.minConfidence ?? DEFAULT_MIN_CONFIDENCE)) {
32
+ return "low_confidence";
33
+ }
34
+ return "policy";
35
+ }
36
+ async function moveToReview(deps, thread, run) {
37
+ const events = thread.state === "new" ? ["draft", "needs_review"] : ["needs_review"];
38
+ const reason = reviewReasonFor(deps, thread, run);
39
+ const updated = await (0, internal_1.applyEvents)(deps, thread, events, { reviewReason: reason });
40
+ await (0, internal_1.notify)(deps, { type: "review_needed", threadId: thread.id, mailbox: thread.mailbox, subject: thread.subject, reason });
41
+ return updated;
42
+ }
43
+ async function failRun(deps, run, error) {
44
+ const failed = await deps.store.updateDraftRun(run.idempotencyKey, { status: "failed", error });
45
+ const thread = await (0, internal_1.loadThread)(deps, run.threadId);
46
+ if (thread.state === "new" || thread.state === "drafted")
47
+ await moveToReview(deps, thread, failed);
48
+ }
49
+ async function startRun(deps, run, message) {
50
+ if (!deps.drafter)
51
+ return failRun(deps, run, "no drafter configured");
52
+ try {
53
+ const { runId } = await deps.drafter.start({
54
+ idempotencyKey: run.idempotencyKey,
55
+ mailbox: run.mailbox,
56
+ threadId: run.threadId,
57
+ messageId: run.inboundMessageId,
58
+ conversationId: message.conversationId,
59
+ subject: message.subject,
60
+ from: message.fromAddress,
61
+ });
62
+ await deps.store.updateDraftRun(run.idempotencyKey, { status: "started", externalRunId: runId });
63
+ }
64
+ catch (err) {
65
+ await failRun(deps, run, (0, internal_1.errorText)(err));
66
+ }
67
+ }
68
+ function newRun(deps, thread, messageId, generation) {
69
+ return {
70
+ mailbox: thread.mailbox,
71
+ threadId: thread.id,
72
+ inboundMessageId: messageId,
73
+ generation,
74
+ idempotencyKey: `draft:${thread.mailbox}:${messageId}:${generation}`,
75
+ status: "requested",
76
+ externalRunId: null,
77
+ requestedAt: (0, internal_1.nowIso)(deps),
78
+ error: null,
79
+ text: null,
80
+ confidence: null,
81
+ reason: null,
82
+ category: null,
83
+ placedDraftId: null,
84
+ placedBodyHtml: null,
85
+ commentText: null,
86
+ };
87
+ }
88
+ async function ensureDraft(deps, threadId, message) {
89
+ if ((await (0, controls_1.loadControls)(deps)).paused)
90
+ return "paused";
91
+ return startDraftFor(deps, threadId, message);
92
+ }
93
+ async function startDraftFor(deps, threadId, message) {
94
+ const thread = await (0, internal_1.loadThread)(deps, threadId);
95
+ (0, controls_1.assertInScope)(deps.config, thread.mailbox);
96
+ if (thread.state === "drafted") {
97
+ const latest = await deps.store.getLatestDraftRunForThread(thread.id);
98
+ if (latest && !inFlight(latest))
99
+ await moveToReview(deps, thread, latest);
100
+ return "exists";
101
+ }
102
+ if (thread.state !== "new" || thread.latestInboundId !== message.id)
103
+ return "not_needed";
104
+ if (!deps.drafter) {
105
+ await (0, internal_1.applyEvents)(deps, thread, ["draft", "needs_review"], { reviewReason: "no_draft" });
106
+ await (0, internal_1.notify)(deps, { type: "review_needed", threadId: thread.id, mailbox: thread.mailbox, subject: thread.subject, reason: "no_draft" });
107
+ return "no_drafter";
108
+ }
109
+ const existing = await deps.store.getLatestDraftRun(thread.mailbox, message.id);
110
+ if (existing) {
111
+ const age = deps.clock.now().getTime() - Date.parse(existing.requestedAt);
112
+ const resumeAfter = deps.config.resumeDraftAfterMs ?? DEFAULT_RESUME_DRAFT_MS;
113
+ if (existing.status === "requested" && age >= resumeAfter) {
114
+ await startRun(deps, existing, message);
115
+ return "started";
116
+ }
117
+ return "exists";
118
+ }
119
+ const active = await deps.store.getLatestDraftRunForThread(thread.id);
120
+ if (active && inFlight(active))
121
+ return "busy";
122
+ const { created, run } = await deps.store.createDraftRunIfAbsent(newRun(deps, thread, message.id, 1));
123
+ if (!created)
124
+ return "exists";
125
+ await startRun(deps, run, message);
126
+ return "started";
127
+ }
128
+ async function completeDraft(deps, input) {
129
+ if ((await (0, controls_1.loadControls)(deps)).paused)
130
+ return (0, controls_1.paused)();
131
+ (0, controls_1.assertInScope)(deps.config, input.mailbox);
132
+ const run = await deps.store.getLatestDraftRun(input.mailbox, input.inboundMessageId);
133
+ if (!run)
134
+ throw new errors_1.DraftNotReadyError(input.inboundMessageId, "no draft run recorded");
135
+ if (run.idempotencyKey !== input.idempotencyKey)
136
+ return run;
137
+ if (!inFlight(run))
138
+ return run;
139
+ const { result } = input;
140
+ if (!result.ok) {
141
+ await failRun(deps, run, result.error);
142
+ return deps.store.updateDraftRun(run.idempotencyKey, {});
143
+ }
144
+ const current = await deps.store.updateDraftRun(run.idempotencyKey, {
145
+ status: "succeeded",
146
+ text: result.text,
147
+ commentText: result.text,
148
+ confidence: result.confidence,
149
+ reason: result.reason,
150
+ category: result.category ?? null,
151
+ });
152
+ const thread = await (0, internal_1.loadThread)(deps, run.threadId);
153
+ if (thread.state === "new" || thread.state === "drafted")
154
+ await moveToReview(deps, thread, current);
155
+ return current;
156
+ }
157
+ async function regenerateDraft(deps, threadId) {
158
+ if ((await (0, controls_1.loadControls)(deps)).paused)
159
+ return (0, controls_1.paused)();
160
+ const thread = await (0, internal_1.loadThread)(deps, threadId);
161
+ (0, controls_1.assertInScope)(deps.config, thread.mailbox);
162
+ if (!deps.drafter)
163
+ throw new errors_1.DraftNotReadyError(threadId, "no drafter configured");
164
+ if (!thread.latestInboundId)
165
+ throw new errors_1.DraftNotReadyError(threadId, "no inbound message");
166
+ const message = await deps.store.getMessage(thread.mailbox, thread.latestInboundId);
167
+ if (!message)
168
+ throw new errors_1.DraftNotReadyError(threadId, "inbound message not stored");
169
+ const latest = await deps.store.getLatestDraftRun(thread.mailbox, message.id);
170
+ await (0, internal_1.applyEvents)(deps, thread, ["redraft"], { reviewReason: null });
171
+ const { created, run } = await deps.store.createDraftRunIfAbsent(newRun(deps, thread, message.id, (latest?.generation ?? 0) + 1));
172
+ if (created)
173
+ await startRun(deps, run, message);
174
+ return run;
175
+ }
176
+ async function writeStaffReply(deps, thread, text) {
177
+ const inboundId = thread.latestInboundId;
178
+ if (!inboundId)
179
+ throw new errors_1.DraftNotReadyError(thread.id, "no inbound message");
180
+ (0, states_1.transition)(thread.state, "redraft");
181
+ const latest = await deps.store.getLatestDraftRun(thread.mailbox, inboundId);
182
+ const { run } = await deps.store.createDraftRunIfAbsent({
183
+ ...newRun(deps, thread, inboundId, (latest?.generation ?? 0) + 1),
184
+ status: "succeeded",
185
+ reason: "staff",
186
+ commentText: text,
187
+ });
188
+ if (run.status !== "succeeded")
189
+ throw new errors_1.DraftNotReadyError(thread.id, "a draft is being written");
190
+ const current = await deps.store.updateDraftRun(run.idempotencyKey, { commentText: text });
191
+ await (0, internal_1.applyEvents)(deps, thread, ["redraft", "needs_review"], { reviewReason: "staff_edited" });
192
+ return current;
193
+ }
194
+ async function editDraft(deps, input) {
195
+ if ((await (0, controls_1.loadControls)(deps)).paused)
196
+ return (0, controls_1.paused)();
197
+ const thread = await (0, internal_1.loadThread)(deps, input.threadId);
198
+ (0, controls_1.assertInScope)(deps.config, thread.mailbox);
199
+ if (input.text.trim() === "")
200
+ throw new errors_1.EmptyReplyError(thread.id);
201
+ const run = currentReplyRun(deps, thread, await deps.store.getLatestDraftRunForThread(thread.id));
202
+ if (!run)
203
+ return writeStaffReply(deps, thread, input.text);
204
+ if (run.status !== "succeeded")
205
+ throw new errors_1.DraftNotReadyError(thread.id, "no draft text to edit");
206
+ (0, states_1.transition)(thread.state, "redraft");
207
+ const updated = await deps.store.updateDraftRun(run.idempotencyKey, { commentText: input.text });
208
+ await (0, internal_1.applyEvents)(deps, thread, ["redraft", "needs_review"], { reviewReason: "staff_edited" });
209
+ return updated;
210
+ }
211
+ async function sweepStuckDrafts(deps, mailbox) {
212
+ if ((await (0, controls_1.loadControls)(deps)).paused)
213
+ return (0, controls_1.paused)();
214
+ (0, controls_1.assertInScope)(deps.config, mailbox);
215
+ const timeout = deps.config.draftTimeoutMs ?? DEFAULT_DRAFT_TIMEOUT_MS;
216
+ const cutoff = new Date(deps.clock.now().getTime() - timeout).toISOString();
217
+ let failed = 0;
218
+ for (const listed of await deps.store.listInFlightDraftRuns(mailbox, cutoff)) {
219
+ const run = await deps.store.getLatestDraftRun(mailbox, listed.inboundMessageId);
220
+ if (!run || run.idempotencyKey !== listed.idempotencyKey || !inFlight(run))
221
+ continue;
222
+ await failRun(deps, run, "no report from the drafter in time");
223
+ failed++;
224
+ }
225
+ return failed;
226
+ }
227
+ async function unignoreThread(deps, threadId) {
228
+ if ((await (0, controls_1.loadControls)(deps)).paused)
229
+ return (0, controls_1.paused)();
230
+ const thread = await (0, internal_1.loadThread)(deps, threadId);
231
+ (0, controls_1.assertInScope)(deps.config, thread.mailbox);
232
+ (0, states_1.transition)(thread.state, "unignore");
233
+ const message = await deps.store.getLatestMessageForThread(thread.id);
234
+ if (!message)
235
+ throw new errors_1.DraftNotReadyError(thread.id, "thread has no message");
236
+ const updated = await (0, internal_1.applyEvents)(deps, thread, ["unignore"], {
237
+ latestInboundId: message.id,
238
+ reviewReason: null,
239
+ dismissedBy: null,
240
+ });
241
+ await startDraftFor(deps, thread.id, message);
242
+ return updated;
243
+ }
@@ -0,0 +1,60 @@
1
+ import type { TicketState } from "./types";
2
+ export declare class HelpdeskError extends Error {
3
+ readonly code: string;
4
+ constructor(code: string, message: string);
5
+ }
6
+ export declare class InvalidTransitionError extends HelpdeskError {
7
+ readonly from: TicketState;
8
+ readonly event: string;
9
+ constructor(from: TicketState, event: string);
10
+ }
11
+ export declare class StaleThreadError extends HelpdeskError {
12
+ constructor(threadId: string);
13
+ }
14
+ export declare class MailboxNotConnectedError extends HelpdeskError {
15
+ constructor(mailbox: string);
16
+ }
17
+ export declare class InvalidDeltaPageError extends HelpdeskError {
18
+ constructor();
19
+ }
20
+ export declare class DeltaLinkExpiredError extends HelpdeskError {
21
+ constructor(message?: string);
22
+ }
23
+ export declare class DraftNotReadyError extends HelpdeskError {
24
+ constructor(threadId: string, why: string);
25
+ }
26
+ export declare class EmptyReplyError extends HelpdeskError {
27
+ constructor(threadId: string);
28
+ }
29
+ export declare class StaleDraftError extends HelpdeskError {
30
+ constructor(threadId: string);
31
+ }
32
+ export declare class MailboxNotInScopeError extends HelpdeskError {
33
+ readonly mailbox: string;
34
+ constructor(mailbox: string);
35
+ }
36
+ export declare class MailboxWriteBlockedError extends HelpdeskError {
37
+ readonly mailbox: string;
38
+ readonly mode: string;
39
+ readonly operation: string;
40
+ constructor(mailbox: string, mode: string, operation: string);
41
+ }
42
+ export declare class SendPendingError extends HelpdeskError {
43
+ readonly sendStatus: "sending" | "unknown";
44
+ constructor(threadId: string, sendStatus: "sending" | "unknown");
45
+ }
46
+ export declare class AttachmentsUnavailableError extends HelpdeskError {
47
+ constructor();
48
+ }
49
+ export declare class AttachmentLimitError extends HelpdeskError {
50
+ readonly limit: "count" | "size";
51
+ constructor(limit: "count" | "size");
52
+ }
53
+ export declare class ReplyAttachmentNotFoundError extends HelpdeskError {
54
+ constructor(id: string);
55
+ }
56
+ export declare class ThreadNotFoundError extends HelpdeskError {
57
+ constructor(threadId: string);
58
+ }
59
+ export declare function isDeltaLinkExpired(err: unknown): boolean;
60
+ export declare function isTransientMailError(err: unknown): boolean;
package/dist/errors.js ADDED
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ThreadNotFoundError = exports.ReplyAttachmentNotFoundError = exports.AttachmentLimitError = exports.AttachmentsUnavailableError = exports.SendPendingError = exports.MailboxWriteBlockedError = exports.MailboxNotInScopeError = exports.StaleDraftError = exports.EmptyReplyError = exports.DraftNotReadyError = exports.DeltaLinkExpiredError = exports.InvalidDeltaPageError = exports.MailboxNotConnectedError = exports.StaleThreadError = exports.InvalidTransitionError = exports.HelpdeskError = void 0;
4
+ exports.isDeltaLinkExpired = isDeltaLinkExpired;
5
+ exports.isTransientMailError = isTransientMailError;
6
+ class HelpdeskError extends Error {
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = new.target.name;
10
+ this.code = code;
11
+ }
12
+ }
13
+ exports.HelpdeskError = HelpdeskError;
14
+ class InvalidTransitionError extends HelpdeskError {
15
+ constructor(from, event) {
16
+ super("invalid_transition", `Cannot apply "${event}" to a ticket in state "${from}"`);
17
+ this.from = from;
18
+ this.event = event;
19
+ }
20
+ }
21
+ exports.InvalidTransitionError = InvalidTransitionError;
22
+ class StaleThreadError extends HelpdeskError {
23
+ constructor(threadId) {
24
+ super("stale_thread", `Thread ${threadId} changed while it was being updated`);
25
+ }
26
+ }
27
+ exports.StaleThreadError = StaleThreadError;
28
+ class MailboxNotConnectedError extends HelpdeskError {
29
+ constructor(mailbox) {
30
+ super("mailbox_not_connected", `Mailbox ${mailbox} has no sync state; connect it first`);
31
+ }
32
+ }
33
+ exports.MailboxNotConnectedError = MailboxNotConnectedError;
34
+ class InvalidDeltaPageError extends HelpdeskError {
35
+ constructor() {
36
+ super("invalid_delta_page", "Delta page has neither nextLink nor deltaLink");
37
+ }
38
+ }
39
+ exports.InvalidDeltaPageError = InvalidDeltaPageError;
40
+ class DeltaLinkExpiredError extends HelpdeskError {
41
+ constructor(message = "Delta link expired") {
42
+ super("delta_link_expired", message);
43
+ }
44
+ }
45
+ exports.DeltaLinkExpiredError = DeltaLinkExpiredError;
46
+ class DraftNotReadyError extends HelpdeskError {
47
+ constructor(threadId, why) {
48
+ super("draft_not_ready", `Thread ${threadId} has no sendable draft: ${why}`);
49
+ }
50
+ }
51
+ exports.DraftNotReadyError = DraftNotReadyError;
52
+ class EmptyReplyError extends HelpdeskError {
53
+ constructor(threadId) {
54
+ super("empty_reply", `Thread ${threadId} has an empty reply text; refusing to send`);
55
+ }
56
+ }
57
+ exports.EmptyReplyError = EmptyReplyError;
58
+ class StaleDraftError extends HelpdeskError {
59
+ constructor(threadId) {
60
+ super("stale_draft", `Thread ${threadId} received newer mail after this draft; pass acceptStale to send anyway`);
61
+ }
62
+ }
63
+ exports.StaleDraftError = StaleDraftError;
64
+ class MailboxNotInScopeError extends HelpdeskError {
65
+ constructor(mailbox) {
66
+ super("mailbox_not_in_scope", `Mailbox ${mailbox} is not one of this helpdesk's mailboxes`);
67
+ this.mailbox = mailbox;
68
+ }
69
+ }
70
+ exports.MailboxNotInScopeError = MailboxNotInScopeError;
71
+ class MailboxWriteBlockedError extends HelpdeskError {
72
+ constructor(mailbox, mode, operation) {
73
+ super("mailbox_write_blocked", `${operation} on ${mailbox} is not allowed in write mode ${mode}`);
74
+ this.mailbox = mailbox;
75
+ this.mode = mode;
76
+ this.operation = operation;
77
+ }
78
+ }
79
+ exports.MailboxWriteBlockedError = MailboxWriteBlockedError;
80
+ class SendPendingError extends HelpdeskError {
81
+ constructor(threadId, sendStatus) {
82
+ super(sendStatus === "sending" ? "send_in_progress" : "send_outcome_unknown", `Thread ${threadId} has a send that is ${sendStatus === "sending" ? "under way" : "of unknown outcome"}`);
83
+ this.sendStatus = sendStatus;
84
+ }
85
+ }
86
+ exports.SendPendingError = SendPendingError;
87
+ class AttachmentsUnavailableError extends HelpdeskError {
88
+ constructor() {
89
+ super("attachments_unavailable", "This app cannot add files to replies");
90
+ }
91
+ }
92
+ exports.AttachmentsUnavailableError = AttachmentsUnavailableError;
93
+ class AttachmentLimitError extends HelpdeskError {
94
+ constructor(limit) {
95
+ super("attachment_limit", limit === "count" ? "Too many files on this reply" : "The files on this reply are too large together");
96
+ this.limit = limit;
97
+ }
98
+ }
99
+ exports.AttachmentLimitError = AttachmentLimitError;
100
+ class ReplyAttachmentNotFoundError extends HelpdeskError {
101
+ constructor(id) {
102
+ super("reply_attachment_not_found", `Reply attachment ${id} does not exist or was already sent`);
103
+ }
104
+ }
105
+ exports.ReplyAttachmentNotFoundError = ReplyAttachmentNotFoundError;
106
+ class ThreadNotFoundError extends HelpdeskError {
107
+ constructor(threadId) {
108
+ super("thread_not_found", `Thread ${threadId} does not exist`);
109
+ }
110
+ }
111
+ exports.ThreadNotFoundError = ThreadNotFoundError;
112
+ const EXPIRED_NAMES = new Set(["DeltaLinkExpiredError", "GraphDeltaExpiredError"]);
113
+ const EXPIRED_CODES = new Set(["delta_link_expired", "syncStateNotFound", "resyncRequired"]);
114
+ function isDeltaLinkExpired(err) {
115
+ if (err instanceof DeltaLinkExpiredError)
116
+ return true;
117
+ const e = err;
118
+ if (!e || typeof e !== "object")
119
+ return false;
120
+ return ((typeof e.name === "string" && EXPIRED_NAMES.has(e.name)) ||
121
+ (typeof e.code === "string" && EXPIRED_CODES.has(e.code)) ||
122
+ e.status === 410);
123
+ }
124
+ function isTransientMailError(err) {
125
+ const e = err;
126
+ if (!e || typeof e !== "object")
127
+ return false;
128
+ if (e.name === "TimeoutError" || e.name === "AbortError")
129
+ return true;
130
+ if (typeof e.status !== "number")
131
+ return false;
132
+ return e.status === 0 || e.status === 408 || e.status === 429 || e.status >= 500;
133
+ }
@@ -0,0 +1,36 @@
1
+ import type { HelpdeskDeps, StoredMessage, Thread, TicketState } from "../types";
2
+ import type { HelpdeskStaff, InboxPerson } from "./types";
3
+ export declare const MAX_ATTACHMENT_DOWNLOAD_BYTES: number;
4
+ export declare function contentDisposition(name: string): string;
5
+ export interface InboxThreadQuery {
6
+ states: TicketState[] | null;
7
+ mailbox: string | null;
8
+ assigneeId?: string | null;
9
+ cursor: string | null;
10
+ limit: number;
11
+ }
12
+ export interface HelpdeskInboxQueries {
13
+ listThreads(query: InboxThreadQuery): Promise<{
14
+ rows: Array<{
15
+ thread: Thread;
16
+ assignee: InboxPerson | null;
17
+ }>;
18
+ nextCursor: string | null;
19
+ }>;
20
+ listMessages(threadId: string): Promise<StoredMessage[]>;
21
+ getAssignee(threadId: string): Promise<InboxPerson | null>;
22
+ setAssignee(threadId: string, assignee: InboxPerson | null): Promise<void>;
23
+ }
24
+ export interface HelpdeskHandlersConfig {
25
+ deps: HelpdeskDeps;
26
+ inbox: HelpdeskInboxQueries;
27
+ getStaff(request: Request): Promise<HelpdeskStaff | null>;
28
+ isProduction: boolean;
29
+ basePath?: string;
30
+ appOrigin?: string;
31
+ onError?(error: unknown): void;
32
+ }
33
+ export interface HelpdeskHandlers {
34
+ handle(request: Request): Promise<Response>;
35
+ }
36
+ export declare function createHelpdeskHandlers(config: HelpdeskHandlersConfig): HelpdeskHandlers;