@todoist/automations-sdk 0.0.0-stage → 1.0.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.
@@ -0,0 +1,710 @@
1
+ import { a as chatStreamRequestErrorResponseSchema, l as getChatBillingRefusal, p as isShownChatAcknowledgement, t as connectionAccountLabel } from "./connection-selection-BjNp9A-d.js";
2
+ import { n as AutomationsRequestError, t as AutomationsNetworkError } from "./request-error-DoLXUa2p.js";
3
+ import { z } from "zod";
4
+ import { DefaultChatTransport, readUIMessageStream } from "ai";
5
+ //#region ../core/dist/esm/schemas/automation-preview-artifact.schema.js
6
+ const automationPreviewSources = ["synthetic", "run"];
7
+ const automationPreviewKinds = [
8
+ "content",
9
+ "action",
10
+ "none",
11
+ "error"
12
+ ];
13
+ const automationPreviewGenerationKinds = [
14
+ "content",
15
+ "action",
16
+ "none"
17
+ ];
18
+ const automationPreviewSourceSchema = z.enum(automationPreviewSources);
19
+ const automationPreviewKindSchema = z.enum(automationPreviewKinds);
20
+ const automationPreviewGenerationKindSchema = z.enum(automationPreviewGenerationKinds);
21
+ const automationPreviewArtifactSchema = z.object({
22
+ source: automationPreviewSourceSchema,
23
+ kind: automationPreviewKindSchema,
24
+ manifestHash: z.string().regex(/^[a-f0-9]{64}$/, "manifestHash must be a sha256 hex digest"),
25
+ title: z.string().trim().min(1),
26
+ summary: z.string().trim().min(1),
27
+ markdown: z.string().trim().min(1).nullable()
28
+ }).superRefine((artifact, ctx) => {
29
+ if (artifact.kind === "content" && artifact.markdown == null) ctx.addIssue({
30
+ code: z.ZodIssueCode.custom,
31
+ message: "content artifacts must include markdown",
32
+ path: ["markdown"]
33
+ });
34
+ if (artifact.kind !== "content" && artifact.markdown != null) ctx.addIssue({
35
+ code: z.ZodIssueCode.custom,
36
+ message: "only content artifacts may include markdown",
37
+ path: ["markdown"]
38
+ });
39
+ });
40
+ z.object({
41
+ kind: automationPreviewGenerationKindSchema,
42
+ title: z.string().trim().min(1),
43
+ summary: z.string().trim().min(1),
44
+ markdown: z.string().trim().min(1).nullish()
45
+ });
46
+ //#endregion
47
+ //#region ../core/dist/esm/chat-tools/complete-automation-draft.js
48
+ const CompleteAutomationDraftActionSchema = z.enum(["create", "update"]);
49
+ const CompleteAutomationDraftInputSchema = z.object({
50
+ draftId: z.string().min(1).describe("The draft ID to generate an automation preview for."),
51
+ language: z.string().trim().min(1).optional().describe("Optional human language name for the preview. When relevant, pass the language used in the current user conversation.")
52
+ });
53
+ const CompleteAutomationDraftOutputSchema = z.union([z.object({
54
+ success: z.literal(true),
55
+ message: z.string().min(1),
56
+ instructions: z.string().min(1),
57
+ draftId: z.string().min(1),
58
+ action: CompleteAutomationDraftActionSchema,
59
+ artifact: automationPreviewArtifactSchema
60
+ }), z.object({
61
+ success: z.literal(false),
62
+ message: z.string().min(1),
63
+ instructions: z.string().min(1),
64
+ draftId: z.string().min(1)
65
+ })]);
66
+ z.object({
67
+ type: z.literal("tool-complete-automation-draft"),
68
+ toolCallId: z.string().min(1),
69
+ state: z.literal("input-available"),
70
+ input: CompleteAutomationDraftInputSchema
71
+ });
72
+ z.object({
73
+ type: z.literal("tool-complete-automation-draft"),
74
+ toolCallId: z.string().min(1),
75
+ state: z.literal("output-available"),
76
+ input: CompleteAutomationDraftInputSchema,
77
+ output: CompleteAutomationDraftOutputSchema
78
+ });
79
+ //#endregion
80
+ //#region ../core/dist/esm/chat-tools/publish-draft.js
81
+ const PublishDraftInputSchema = z.object({
82
+ draftId: z.string().min(1).describe("The draft ID to preview and publish."),
83
+ language: z.string().trim().min(1).optional().describe("Optional human language name for the preview. When relevant, pass the language used in the current user conversation.")
84
+ });
85
+ const PublishDraftOutputSchema = z.union([z.object({
86
+ success: z.literal(true),
87
+ message: z.string().min(1),
88
+ instructions: z.string().min(1),
89
+ draftId: z.string().min(1),
90
+ action: CompleteAutomationDraftActionSchema,
91
+ automationId: z.string().min(1),
92
+ previewArtifact: automationPreviewArtifactSchema.optional()
93
+ }), z.object({
94
+ success: z.literal(false),
95
+ message: z.string().min(1),
96
+ instructions: z.string().min(1),
97
+ draftId: z.string().min(1),
98
+ action: CompleteAutomationDraftActionSchema.optional(),
99
+ previewArtifact: automationPreviewArtifactSchema.optional()
100
+ })]);
101
+ z.object({
102
+ type: z.literal("tool-publish-draft"),
103
+ toolCallId: z.string().min(1),
104
+ state: z.literal("input-available"),
105
+ input: PublishDraftInputSchema
106
+ });
107
+ const toolPublishDraftResultSchema = z.object({
108
+ type: z.literal("tool-publish-draft"),
109
+ toolCallId: z.string().min(1),
110
+ state: z.literal("output-available"),
111
+ input: PublishDraftInputSchema,
112
+ output: PublishDraftOutputSchema
113
+ });
114
+ function isPublishDraftCandidate(part) {
115
+ return typeof part === "object" && part !== null && "type" in part && part.type === "tool-publish-draft";
116
+ }
117
+ function isPublishDraftResult(part) {
118
+ if (!isPublishDraftCandidate(part)) return false;
119
+ return toolPublishDraftResultSchema.safeParse(part).success;
120
+ }
121
+ //#endregion
122
+ //#region ../core/dist/esm/chat-tools/prompt-enable-push.js
123
+ const PromptEnablePushInputSchema = z.object({ draftId: z.string().min(1).optional().describe("Optional draft ID currently being edited, if available") });
124
+ const PromptEnablePushOutputSchema = z.object({
125
+ enabled: z.boolean().describe("Whether the user finished enabling push notifications and has at least one active subscription."),
126
+ deviceCount: z.number().int().min(0).describe("Number of active push devices the user has registered."),
127
+ message: z.string().min(1).describe("Human-readable status describing the outcome of the prompt.")
128
+ });
129
+ const toolPromptEnablePushSchema = z.object({
130
+ type: z.literal("tool-prompt-enable-push-notifications"),
131
+ toolCallId: z.string().min(1),
132
+ state: z.literal("input-available"),
133
+ input: PromptEnablePushInputSchema
134
+ });
135
+ z.object({
136
+ type: z.literal("tool-prompt-enable-push-notifications"),
137
+ toolCallId: z.string().min(1),
138
+ state: z.literal("output-available"),
139
+ input: PromptEnablePushInputSchema,
140
+ output: PromptEnablePushOutputSchema
141
+ });
142
+ function isPromptEnablePush(part) {
143
+ return toolPromptEnablePushSchema.safeParse(part).success;
144
+ }
145
+ //#endregion
146
+ //#region ../core/dist/esm/chat-tools/select-connections.js
147
+ const SelectConnectionTypeSchema = z.enum(["mcp", "trigger"]);
148
+ const ConnectionItemSchema = z.object({
149
+ connectorId: z.string().min(1).describe("The connector ID to select a connection for"),
150
+ serviceName: z.string().min(1).describe("The human-readable name of the service"),
151
+ connectionType: SelectConnectionTypeSchema.describe("Type of connection needed: \"mcp\" for actions, \"trigger\" for events")
152
+ });
153
+ const SelectConnectionsInputSchema = z.object({
154
+ draftId: z.string().min(1).describe("The draft ID these connections belong to"),
155
+ connections: z.array(ConnectionItemSchema).min(1).describe("List of connections needed for this automation")
156
+ });
157
+ const ConnectionResultItemSchema = z.union([z.object({
158
+ type: z.literal("success"),
159
+ connectionId: z.string().min(1),
160
+ connectorId: z.string().min(1),
161
+ connectionType: SelectConnectionTypeSchema
162
+ }), z.object({
163
+ type: z.literal("error"),
164
+ message: z.string().min(1),
165
+ connectorId: z.string().min(1),
166
+ connectionType: SelectConnectionTypeSchema
167
+ })]);
168
+ const SelectConnectionsOutputSchema = z.array(ConnectionResultItemSchema);
169
+ const toolSelectConnectionsSchema = z.object({
170
+ type: z.literal("tool-select-connections"),
171
+ toolCallId: z.string().min(1),
172
+ state: z.literal("input-available"),
173
+ input: SelectConnectionsInputSchema
174
+ });
175
+ z.object({
176
+ type: z.literal("tool-select-connections"),
177
+ toolCallId: z.string().min(1),
178
+ state: z.literal("output-available"),
179
+ input: SelectConnectionsInputSchema,
180
+ output: SelectConnectionsOutputSchema
181
+ });
182
+ function isSelectConnections(part) {
183
+ return toolSelectConnectionsSchema.safeParse(part).success;
184
+ }
185
+ //#endregion
186
+ //#region src/chat-turn/connection-requirements.ts
187
+ const CONNECTION_LOOKUP_CONCURRENCY = 4;
188
+ function deduplicateConnectionRequirements(requirements) {
189
+ const seen = /* @__PURE__ */ new Set();
190
+ return requirements.filter((requirement) => {
191
+ const key = JSON.stringify([requirement.connectorId, requirement.connectionType]);
192
+ if (seen.has(key)) return false;
193
+ seen.add(key);
194
+ return true;
195
+ });
196
+ }
197
+ async function loadConnectionRequirements(connections, requirements, options) {
198
+ return mapWithConcurrency(deduplicateConnectionRequirements(requirements), CONNECTION_LOOKUP_CONCURRENCY, async (requirement) => {
199
+ const response = await connections.list({
200
+ connectorId: requirement.connectorId,
201
+ capability: requirement.connectionType,
202
+ status: "authorized"
203
+ }, options);
204
+ return {
205
+ ...requirement,
206
+ availableConnections: response.connections.map((connection) => ({
207
+ id: connection.id,
208
+ label: connectionAccountLabel({
209
+ label: connection.accountDisplayLabel ?? connection.name ?? connection.id,
210
+ mcpServerUrl: connection.mcpServerUrl
211
+ })
212
+ }))
213
+ };
214
+ });
215
+ }
216
+ async function mapWithConcurrency(items, concurrency, map) {
217
+ const results = new Array(items.length);
218
+ let next = 0;
219
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
220
+ while (next < items.length) {
221
+ const index = next;
222
+ next += 1;
223
+ results[index] = await map(items[index]);
224
+ }
225
+ });
226
+ await Promise.all(workers);
227
+ return results;
228
+ }
229
+ //#endregion
230
+ //#region src/chat-turn/connection-setup.ts
231
+ async function startConnectionSetup(connections, request, options) {
232
+ const body = {
233
+ ...request.automationIds?.length ? { automationIds: [...request.automationIds] } : {},
234
+ ...request.tenant !== void 0 ? { tenant: request.tenant } : {},
235
+ ...request.serverUrl !== void 0 ? { serverUrl: request.serverUrl } : {}
236
+ };
237
+ const created = await connections.create({
238
+ connectorId: request.connectorId,
239
+ capability: request.capability,
240
+ ...Object.keys(body).length > 0 ? { body } : {}
241
+ }, options);
242
+ return {
243
+ connectionId: created.connectionId,
244
+ setup: created.setup,
245
+ resumed: false
246
+ };
247
+ }
248
+ const INPUT_REQUIRED = "input_required";
249
+ function getConnectionInputRequiredMessage(error) {
250
+ if (!(error instanceof AutomationsRequestError) || error.httpStatusCode !== 400) return;
251
+ const data = error.responseData;
252
+ if (data?.code !== INPUT_REQUIRED) return;
253
+ return typeof data.message === "string" ? data.message : error.message;
254
+ }
255
+ function resumeConnectionSetup(connection) {
256
+ return {
257
+ connectionId: connection.id,
258
+ setup: connection.setup,
259
+ resumed: true
260
+ };
261
+ }
262
+ function summarizeConnectionSetup(setup) {
263
+ if (setup?.kind === "oauth") return {
264
+ setupKind: "oauth",
265
+ authorizationUrl: setup.authorizationUrl
266
+ };
267
+ if (setup?.kind === "managed") return {
268
+ setupKind: "managed",
269
+ instructions: setup.instructions,
270
+ code: setup.code,
271
+ deepLinkUrl: setup.deepLinkUrl,
272
+ expiresAt: setup.expiresAt
273
+ };
274
+ return { setupKind: "none" };
275
+ }
276
+ function getConnectionSetupUrl(setup) {
277
+ if (setup?.kind === "oauth") return setup.authorizationUrl;
278
+ if (setup?.kind === "managed") return setup.deepLinkUrl;
279
+ return null;
280
+ }
281
+ //#endregion
282
+ //#region src/chat-turn/messages.ts
283
+ const persistedToolPartSchema = z.object({
284
+ type: z.string().trim().min(6).refine((type) => type.startsWith("tool-")),
285
+ toolCallId: z.string().trim().min(1),
286
+ state: z.enum([
287
+ "input-streaming",
288
+ "input-available",
289
+ "output-available",
290
+ "output-error"
291
+ ])
292
+ }).passthrough();
293
+ function createUserMessage(text) {
294
+ return {
295
+ id: globalThis.crypto.randomUUID(),
296
+ role: "user",
297
+ parts: [{
298
+ type: "text",
299
+ text
300
+ }]
301
+ };
302
+ }
303
+ function toUiMessage(message) {
304
+ if (message.role !== "user" && message.role !== "assistant" && message.role !== "system") throw new Error(`Unsupported persisted chat role: ${message.role}`);
305
+ if (!Array.isArray(message.parts)) throw new Error(`Persisted chat message ${message.messageId} has invalid parts.`);
306
+ return {
307
+ id: message.messageId,
308
+ role: message.role,
309
+ parts: normalizePersistedMessageParts(message.parts)
310
+ };
311
+ }
312
+ function normalizePersistedMessageParts(parts) {
313
+ return parts.filter((part) => isTextPart(part) || isStepStartPart(part) || isReasoningPart(part) || persistedToolPartSchema.safeParse(part).success || isShownChatAcknowledgement(part));
314
+ }
315
+ function isTextPart(part) {
316
+ if (typeof part !== "object" || part === null) return false;
317
+ const candidate = part;
318
+ return candidate.type === "text" && typeof candidate.text === "string";
319
+ }
320
+ function isStepStartPart(part) {
321
+ if (typeof part !== "object" || part === null) return false;
322
+ return part.type === "step-start";
323
+ }
324
+ function isReasoningPart(part) {
325
+ if (typeof part !== "object" || part === null) return false;
326
+ const candidate = part;
327
+ return candidate.type === "reasoning" && typeof candidate.text === "string" && (candidate.state === void 0 || candidate.state === "streaming" || candidate.state === "done");
328
+ }
329
+ function getVisibleText(message) {
330
+ return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
331
+ }
332
+ function getPendingClientAction(message) {
333
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
334
+ const part = message.parts[index];
335
+ if (isSelectConnections(part)) return {
336
+ kind: "select_connections",
337
+ message,
338
+ toolCallId: part.toolCallId,
339
+ draftId: part.input.draftId,
340
+ connections: part.input.connections
341
+ };
342
+ if (isPromptEnablePush(part)) return {
343
+ kind: "enable_push",
344
+ message,
345
+ toolCallId: part.toolCallId,
346
+ draftId: part.input.draftId
347
+ };
348
+ }
349
+ return null;
350
+ }
351
+ function getPendingActionFromMessages(messages) {
352
+ const latestMessage = messages.at(-1);
353
+ if (!latestMessage || latestMessage.role !== "assistant") return null;
354
+ return getPendingClientAction(toUiMessage(latestMessage));
355
+ }
356
+ function getPublishedAutomationId(message) {
357
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
358
+ const part = message.parts[index];
359
+ if (isPublishDraftResult(part) && part.output.success) return part.output.automationId;
360
+ }
361
+ }
362
+ function createEnablePushContinuation(action, output) {
363
+ return {
364
+ id: action.message.id,
365
+ role: "assistant",
366
+ parts: [{
367
+ type: "tool-prompt-enable-push-notifications",
368
+ toolCallId: action.toolCallId,
369
+ state: "output-available",
370
+ output
371
+ }]
372
+ };
373
+ }
374
+ function createSelectConnectionsContinuation(action, output) {
375
+ return {
376
+ id: action.message.id,
377
+ role: "assistant",
378
+ parts: [{
379
+ type: "tool-select-connections",
380
+ toolCallId: action.toolCallId,
381
+ state: "output-available",
382
+ output
383
+ }]
384
+ };
385
+ }
386
+ //#endregion
387
+ //#region src/chat-turn/types.ts
388
+ var ChatTurnInputError = class extends Error {
389
+ code;
390
+ constructor(code, message) {
391
+ super(message);
392
+ this.name = "ChatTurnInputError";
393
+ this.code = code;
394
+ }
395
+ };
396
+ //#endregion
397
+ //#region src/chat-turn/run-conversation-turn.ts
398
+ async function runConversationTurn(options) {
399
+ const { api, transport, conversationId, signal } = options;
400
+ const workspaceId = options.workspaceId ?? null;
401
+ const requestOptions = { signal };
402
+ const pending = await readPendingAction(options);
403
+ let nextMessage;
404
+ if (pending?.kind === "enable_push") {
405
+ if ("message" in options.input || options.input.selections.length > 0) throw new ChatTurnInputError("awaiting_push", "The conversation is waiting for the push notification prompt to be answered.");
406
+ nextMessage = await answerEnablePush(options, pending);
407
+ } else if ("message" in options.input) {
408
+ if (pending?.kind === "select_connections") throw new ChatTurnInputError("awaiting_selection", "The conversation is waiting for connection selection.");
409
+ nextMessage = createUserMessage(options.input.message);
410
+ } else {
411
+ const selections = options.input.selections;
412
+ if (pending?.kind !== "select_connections") throw new ChatTurnInputError(selections.length > 0 ? "not_awaiting_selection" : "input_required", selections.length > 0 ? "The conversation is not waiting for connection selection." : "A message is required to continue.");
413
+ const resolution = await resolveSelectConnections(options, pending, selections);
414
+ if (resolution.kind === "required") return actionRequired(conversationId, getVisibleText(pending.message), resolution.pendingAction);
415
+ nextMessage = resolution.continuation;
416
+ }
417
+ while (true) {
418
+ const response = await transport.streamTurn({
419
+ conversationId,
420
+ workspaceId,
421
+ message: nextMessage,
422
+ signal,
423
+ onText: options.onText,
424
+ onToolActivity: options.onToolActivity
425
+ });
426
+ const assistantText = getVisibleText(response);
427
+ const automationId = getPublishedAutomationId(response);
428
+ if (automationId) {
429
+ options.onPublished?.(automationId);
430
+ return {
431
+ conversationId,
432
+ outcome: "published",
433
+ assistantText,
434
+ automationId,
435
+ ...await archiveConversation(api, conversationId, requestOptions)
436
+ };
437
+ }
438
+ const responseAction = getPendingClientAction(response);
439
+ if (responseAction?.kind === "select_connections") {
440
+ const resolution = await resolveSelectConnections(options, responseAction, []);
441
+ if (resolution.kind === "resolved") {
442
+ nextMessage = resolution.continuation;
443
+ continue;
444
+ }
445
+ return actionRequired(conversationId, assistantText, resolution.pendingAction);
446
+ }
447
+ if (responseAction?.kind === "enable_push") return actionRequired(conversationId, assistantText, {
448
+ kind: "enable_push",
449
+ toolCallId: responseAction.toolCallId,
450
+ draftId: responseAction.draftId
451
+ });
452
+ return {
453
+ conversationId,
454
+ outcome: "awaiting_input",
455
+ assistantText
456
+ };
457
+ }
458
+ }
459
+ async function answerEnablePush(options, action) {
460
+ const { subscriptions } = await options.api.notifications.push.list({ signal: options.signal });
461
+ const deviceCount = subscriptions.length;
462
+ return createEnablePushContinuation(action, {
463
+ enabled: deviceCount > 0,
464
+ deviceCount,
465
+ message: deviceCount > 0 ? `Push notifications are enabled on ${deviceCount} device${deviceCount === 1 ? "" : "s"}.` : "The user has not enabled push notifications."
466
+ });
467
+ }
468
+ async function readPendingAction(options) {
469
+ if (options.pendingAction !== void 0) return options.pendingAction;
470
+ return getPendingActionFromMessages(await options.api.chat.getMessages(options.conversationId, { signal: options.signal }));
471
+ }
472
+ function actionRequired(conversationId, assistantText, pendingAction) {
473
+ return {
474
+ conversationId,
475
+ outcome: "action_required",
476
+ assistantText,
477
+ pendingAction
478
+ };
479
+ }
480
+ async function archiveConversation(api, conversationId, requestOptions) {
481
+ try {
482
+ if (!(await api.chat.updateConversation(conversationId, { isArchived: true }, requestOptions)).isArchived) throw new Error("The Automations API returned an active conversation.");
483
+ return { conversationArchived: true };
484
+ } catch (error) {
485
+ return {
486
+ conversationArchived: false,
487
+ conversationArchiveError: error instanceof Error ? error.message : String(error)
488
+ };
489
+ }
490
+ }
491
+ async function resolveSelectConnections(options, action, selections) {
492
+ const { api, signal } = options;
493
+ const requestOptions = { signal };
494
+ const requirements = await loadConnectionRequirements(api.connections, action.connections, requestOptions);
495
+ assertSelectionsUnambiguous(requirements, selections);
496
+ const resolved = [];
497
+ for (const requirement of requirements) {
498
+ let connectionId = selections.find((selection) => selection.connectorId === requirement.connectorId && (!selection.connectionType || selection.connectionType === requirement.connectionType))?.connectionId;
499
+ let setup;
500
+ if (connectionId) {
501
+ if (!requirement.availableConnections.some((connection) => connection.id === connectionId)) throw new ChatTurnInputError("unknown_connection", `Connection ${connectionId} is not an authorized ${requirement.serviceName} connection.`);
502
+ } else if (requirement.availableConnections.length === 1) connectionId = requirement.availableConnections[0]?.id;
503
+ else if (requirement.availableConnections.length > 1) connectionId = await options.chooseConnection?.(requirement);
504
+ else if (options.authorizeConnection) connectionId = await options.authorizeConnection(requirement);
505
+ else setup = await startConnectionSetup(api.connections, {
506
+ connectorId: requirement.connectorId,
507
+ capability: requirement.connectionType
508
+ }, requestOptions).catch((error) => {
509
+ throw toConnectionInputRequiredError(error, requirement.serviceName) ?? error;
510
+ });
511
+ if (!connectionId) return {
512
+ kind: "required",
513
+ pendingAction: {
514
+ kind: "select_connections",
515
+ toolCallId: action.toolCallId,
516
+ draftId: action.draftId,
517
+ requirements: requirements.map((item) => item === requirement && setup ? {
518
+ ...item,
519
+ setup
520
+ } : item)
521
+ }
522
+ };
523
+ resolved.push({
524
+ requirement,
525
+ connectionId
526
+ });
527
+ }
528
+ const output = [];
529
+ for (const item of resolved) {
530
+ const body = {
531
+ connectorId: item.requirement.connectorId,
532
+ connectionType: item.requirement.connectionType,
533
+ connectionId: item.connectionId
534
+ };
535
+ const workspaceId = options.workspaceId ?? null;
536
+ if (workspaceId === null) await api.automationDraftConnections.upsert(action.draftId, body, requestOptions);
537
+ else await api.workspaceAutomationDraftConnections.upsert(workspaceId, action.draftId, body, requestOptions);
538
+ output.push({
539
+ type: "success",
540
+ ...body
541
+ });
542
+ }
543
+ return {
544
+ kind: "resolved",
545
+ continuation: createSelectConnectionsContinuation(action, output)
546
+ };
547
+ }
548
+ function assertSelectionsUnambiguous(requirements, selections) {
549
+ for (const selection of selections) {
550
+ if (selection.connectionType) continue;
551
+ if (new Set(requirements.filter((requirement) => requirement.connectorId === selection.connectorId).map((requirement) => requirement.connectionType)).size > 1) throw new ChatTurnInputError("ambiguous_selection", `Connection selection for ${selection.connectorId} is ambiguous. Specify ${selection.connectorId}:mcp or ${selection.connectorId}:trigger.`);
552
+ }
553
+ }
554
+ function toConnectionInputRequiredError(error, serviceName) {
555
+ const message = getConnectionInputRequiredMessage(error);
556
+ if (message === void 0) return;
557
+ return new ChatTurnInputError("connection_input_required", `${serviceName} needs to be connected before the agent can continue. ${message}`);
558
+ }
559
+ //#endregion
560
+ //#region src/chat-turn/errors.ts
561
+ var ChatTurnError = class extends Error {
562
+ conversationId;
563
+ requestId;
564
+ stage;
565
+ response;
566
+ constructor({ conversationId, requestId, stage, cause }) {
567
+ const response = stage === "request" ? parseChatStreamRequestError(cause) : void 0;
568
+ super(response?.message ?? (cause instanceof Error ? cause.message : String(cause)), { cause });
569
+ this.name = "ChatTurnError";
570
+ this.conversationId = conversationId;
571
+ this.requestId = requestId;
572
+ this.stage = stage;
573
+ this.response = response;
574
+ }
575
+ get httpStatus() {
576
+ return this.response?.statusCode;
577
+ }
578
+ };
579
+ function parseChatStreamRequestError(error) {
580
+ if (!(error instanceof Error) || !error.message.trim().startsWith("{")) return void 0;
581
+ let body;
582
+ try {
583
+ body = JSON.parse(error.message);
584
+ } catch {
585
+ return;
586
+ }
587
+ const parsed = chatStreamRequestErrorResponseSchema.safeParse(body);
588
+ return parsed.success ? parsed.data : void 0;
589
+ }
590
+ function getChatTurnBillingRefusal(error) {
591
+ return getChatBillingRefusal(error instanceof ChatTurnError ? error.cause : error);
592
+ }
593
+ function readFailedRequest(error) {
594
+ if (error instanceof ChatTurnError) return {
595
+ requestId: error.requestId,
596
+ ...error.httpStatus !== void 0 ? { httpStatus: error.httpStatus } : {}
597
+ };
598
+ if (error instanceof AutomationsRequestError) return {
599
+ ...error.requestId ? { requestId: error.requestId } : {},
600
+ httpStatus: error.httpStatusCode
601
+ };
602
+ return {};
603
+ }
604
+ function classifyChatTurnFailure(error) {
605
+ const { httpStatus } = readFailedRequest(error);
606
+ if (httpStatus !== void 0) return httpStatus < 500 ? "refused" : "broken";
607
+ return error instanceof ChatTurnError || error instanceof AutomationsNetworkError ? "broken" : "other";
608
+ }
609
+ //#endregion
610
+ //#region src/chat-turn/transport.ts
611
+ var ChatTurnTransport = class {
612
+ transport;
613
+ constructor({ token, baseUrl, headers, fetch }) {
614
+ const apiRoot = `${baseUrl.replace(/\/+$/, "")}/v1`;
615
+ this.transport = new DefaultChatTransport({
616
+ api: `${apiRoot}/chat`,
617
+ headers: {
618
+ Authorization: `Bearer ${token}`,
619
+ ...headers
620
+ },
621
+ credentials: "include",
622
+ fetch,
623
+ prepareSendMessagesRequest({ messages, body, api }) {
624
+ const { workspaceId, ...rest } = body ?? {};
625
+ return {
626
+ api: workspaceId ? `${apiRoot}/workspaces/${encodeURIComponent(workspaceId)}/chat` : api,
627
+ body: {
628
+ ...rest,
629
+ message: messages.at(-1)
630
+ }
631
+ };
632
+ }
633
+ });
634
+ }
635
+ async streamTurn({ conversationId, workspaceId = null, message, signal, onText, onToolActivity }) {
636
+ const requestId = globalThis.crypto.randomUUID();
637
+ const fail = (stage, cause) => signal?.aborted ? cause : new ChatTurnError({
638
+ conversationId,
639
+ requestId,
640
+ stage,
641
+ cause
642
+ });
643
+ let stream;
644
+ try {
645
+ stream = await this.transport.sendMessages({
646
+ trigger: "submit-message",
647
+ chatId: conversationId,
648
+ messageId: void 0,
649
+ messages: [message],
650
+ abortSignal: signal,
651
+ headers: { "X-Request-Id": requestId },
652
+ body: {
653
+ conversationId,
654
+ workspaceId
655
+ }
656
+ });
657
+ } catch (error) {
658
+ throw fail("request", error);
659
+ }
660
+ let finalMessage;
661
+ let emittedText = "";
662
+ const emittedToolStates = /* @__PURE__ */ new Set();
663
+ let callbackError;
664
+ const report = (callback) => {
665
+ try {
666
+ callback();
667
+ } catch (error) {
668
+ callbackError = { error };
669
+ throw error;
670
+ }
671
+ };
672
+ try {
673
+ for await (const currentMessage of readUIMessageStream({
674
+ stream,
675
+ terminateOnError: true
676
+ })) {
677
+ finalMessage = currentMessage;
678
+ const visibleText = getVisibleText(currentMessage);
679
+ if (visibleText.startsWith(emittedText)) {
680
+ const delta = visibleText.slice(emittedText.length);
681
+ if (delta) report(() => onText?.(delta));
682
+ emittedText = visibleText;
683
+ }
684
+ for (const part of currentMessage.parts) {
685
+ const activity = toToolActivity(part);
686
+ if (!activity) continue;
687
+ const key = `${activity.toolCallId}:${activity.state}`;
688
+ if (emittedToolStates.has(key)) continue;
689
+ emittedToolStates.add(key);
690
+ report(() => onToolActivity?.(activity));
691
+ }
692
+ }
693
+ } catch (error) {
694
+ if (callbackError) throw callbackError.error;
695
+ throw fail("stream", error);
696
+ }
697
+ if (!finalMessage) throw new Error("The Automations chat returned an empty response.");
698
+ return finalMessage;
699
+ }
700
+ };
701
+ function toToolActivity(part) {
702
+ if (!part.type.startsWith("tool-") || !("toolCallId" in part) || !("state" in part)) return null;
703
+ return {
704
+ tool: part.type.slice(5),
705
+ toolCallId: String(part.toolCallId),
706
+ state: String(part.state)
707
+ };
708
+ }
709
+ //#endregion
710
+ export { ChatTurnError, ChatTurnInputError, ChatTurnTransport, classifyChatTurnFailure, createEnablePushContinuation, createSelectConnectionsContinuation, createUserMessage, deduplicateConnectionRequirements, getChatTurnBillingRefusal, getConnectionInputRequiredMessage, getConnectionSetupUrl, getPendingActionFromMessages, getPendingClientAction, getPublishedAutomationId, getVisibleText, isReasoningPart, isStepStartPart, isTextPart, loadConnectionRequirements, normalizePersistedMessageParts, parseChatStreamRequestError, readFailedRequest, resumeConnectionSetup, runConversationTurn, startConnectionSetup, summarizeConnectionSetup, toUiMessage };