@contractspec/integration.providers-impls 3.7.6 → 3.7.7

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 (33) hide show
  1. package/README.md +80 -241
  2. package/dist/impls/composio-fallback-resolver.d.ts +4 -4
  3. package/dist/impls/composio-fallback-resolver.js +73 -73
  4. package/dist/impls/composio-mcp.d.ts +1 -1
  5. package/dist/impls/composio-proxies.d.ts +5 -5
  6. package/dist/impls/composio-sdk.d.ts +1 -1
  7. package/dist/impls/elevenlabs-voice.d.ts +1 -1
  8. package/dist/impls/fal-voice.d.ts +1 -1
  9. package/dist/impls/gcs-storage.d.ts +1 -1
  10. package/dist/impls/gmail-inbound.d.ts +1 -1
  11. package/dist/impls/gradium-voice.d.ts +2 -2
  12. package/dist/impls/health/base-health-provider.d.ts +1 -1
  13. package/dist/impls/health/official-health-providers.d.ts +1 -1
  14. package/dist/impls/health/providers.d.ts +1 -1
  15. package/dist/impls/health-provider-factory.d.ts +1 -1
  16. package/dist/impls/index.d.ts +30 -30
  17. package/dist/impls/index.js +2150 -2150
  18. package/dist/impls/mistral-conversational.d.ts +1 -1
  19. package/dist/impls/mistral-conversational.js +159 -159
  20. package/dist/impls/posthog-reader.d.ts +1 -1
  21. package/dist/impls/provider-factory.d.ts +11 -11
  22. package/dist/impls/provider-factory.js +2073 -2073
  23. package/dist/index.d.ts +12 -12
  24. package/dist/index.js +2057 -2057
  25. package/dist/node/impls/composio-fallback-resolver.js +73 -73
  26. package/dist/node/impls/index.js +2150 -2150
  27. package/dist/node/impls/mistral-conversational.js +159 -159
  28. package/dist/node/impls/provider-factory.js +2073 -2073
  29. package/dist/node/index.js +2057 -2057
  30. package/dist/node/secrets/provider.js +2 -2
  31. package/dist/secrets/provider.d.ts +2 -2
  32. package/dist/secrets/provider.js +2 -2
  33. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -222,79 +222,6 @@ class ComposioMcpProvider {
222
222
  }
223
223
  }
224
224
 
225
- // src/impls/composio-sdk.ts
226
- class ComposioSdkProvider {
227
- config;
228
- client;
229
- constructor(config) {
230
- this.config = config;
231
- }
232
- async executeTool(toolName, args) {
233
- const client = await this.getClient();
234
- const userId = args._userId ?? "default";
235
- try {
236
- const entity = await client.getEntity(userId);
237
- const result = await entity.execute(toolName, args);
238
- return { success: true, data: result };
239
- } catch (error) {
240
- return {
241
- success: false,
242
- error: error instanceof Error ? error.message : String(error)
243
- };
244
- }
245
- }
246
- async searchTools(query) {
247
- const client = await this.getClient();
248
- try {
249
- const tools = await client.actions.list({ query, limit: 20 });
250
- return tools.map((t) => ({
251
- name: t.name,
252
- description: t.description ?? "",
253
- toolkit: t.appName ?? "",
254
- parameters: t.parameters ?? {}
255
- }));
256
- } catch {
257
- return [];
258
- }
259
- }
260
- async getConnectedAccounts(userId) {
261
- const client = await this.getClient();
262
- try {
263
- const entity = await client.getEntity(userId);
264
- const connections = await entity.getConnections();
265
- return connections.map((c) => ({
266
- id: c.id,
267
- appName: c.appName,
268
- status: c.status
269
- }));
270
- } catch {
271
- return [];
272
- }
273
- }
274
- async getMcpConfig(userId) {
275
- const client = await this.getClient();
276
- try {
277
- const entity = await client.getEntity(userId);
278
- return {
279
- url: entity.getMcpUrl(),
280
- headers: entity.getMcpHeaders()
281
- };
282
- } catch {
283
- return;
284
- }
285
- }
286
- async getClient() {
287
- if (this.client)
288
- return this.client;
289
- const { Composio } = await import("@composio/core");
290
- this.client = new Composio({
291
- apiKey: this.config.apiKey,
292
- ...this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}
293
- });
294
- return this.client;
295
- }
296
- }
297
-
298
225
  // src/impls/composio-proxies.ts
299
226
  function composioToolName(toolkit, action) {
300
227
  return `${toolkit.toUpperCase()}_${action.toUpperCase()}`;
@@ -596,6 +523,79 @@ class ComposioGenericProxy {
596
523
  }
597
524
  }
598
525
 
526
+ // src/impls/composio-sdk.ts
527
+ class ComposioSdkProvider {
528
+ config;
529
+ client;
530
+ constructor(config) {
531
+ this.config = config;
532
+ }
533
+ async executeTool(toolName, args) {
534
+ const client = await this.getClient();
535
+ const userId = args._userId ?? "default";
536
+ try {
537
+ const entity = await client.getEntity(userId);
538
+ const result = await entity.execute(toolName, args);
539
+ return { success: true, data: result };
540
+ } catch (error) {
541
+ return {
542
+ success: false,
543
+ error: error instanceof Error ? error.message : String(error)
544
+ };
545
+ }
546
+ }
547
+ async searchTools(query) {
548
+ const client = await this.getClient();
549
+ try {
550
+ const tools = await client.actions.list({ query, limit: 20 });
551
+ return tools.map((t) => ({
552
+ name: t.name,
553
+ description: t.description ?? "",
554
+ toolkit: t.appName ?? "",
555
+ parameters: t.parameters ?? {}
556
+ }));
557
+ } catch {
558
+ return [];
559
+ }
560
+ }
561
+ async getConnectedAccounts(userId) {
562
+ const client = await this.getClient();
563
+ try {
564
+ const entity = await client.getEntity(userId);
565
+ const connections = await entity.getConnections();
566
+ return connections.map((c) => ({
567
+ id: c.id,
568
+ appName: c.appName,
569
+ status: c.status
570
+ }));
571
+ } catch {
572
+ return [];
573
+ }
574
+ }
575
+ async getMcpConfig(userId) {
576
+ const client = await this.getClient();
577
+ try {
578
+ const entity = await client.getEntity(userId);
579
+ return {
580
+ url: entity.getMcpUrl(),
581
+ headers: entity.getMcpHeaders()
582
+ };
583
+ } catch {
584
+ return;
585
+ }
586
+ }
587
+ async getClient() {
588
+ if (this.client)
589
+ return this.client;
590
+ const { Composio } = await import("@composio/core");
591
+ this.client = new Composio({
592
+ apiKey: this.config.apiKey,
593
+ ...this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}
594
+ });
595
+ return this.client;
596
+ }
597
+ }
598
+
599
599
  // src/impls/composio-fallback-resolver.ts
600
600
  class ComposioFallbackResolver {
601
601
  mcpProvider;
@@ -3960,476 +3960,519 @@ function asString(value) {
3960
3960
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
3961
3961
  }
3962
3962
 
3963
- // src/impls/mistral-llm.ts
3964
- import { Mistral } from "@mistralai/mistralai";
3963
+ // src/impls/jira.ts
3964
+ import { Buffer as Buffer3 } from "buffer";
3965
3965
 
3966
- class MistralLLMProvider {
3967
- client;
3968
- defaultModel;
3966
+ class JiraProjectManagementProvider {
3967
+ siteUrl;
3968
+ authHeader;
3969
+ defaults;
3970
+ fetchFn;
3969
3971
  constructor(options) {
3970
- if (!options.apiKey) {
3971
- throw new Error("MistralLLMProvider requires an apiKey");
3972
+ this.siteUrl = normalizeSiteUrl(options.siteUrl);
3973
+ this.authHeader = buildAuthHeader(options.email, options.apiToken);
3974
+ this.defaults = {
3975
+ projectKey: options.projectKey,
3976
+ issueType: options.issueType,
3977
+ defaultLabels: options.defaultLabels,
3978
+ issueTypeMap: options.issueTypeMap
3979
+ };
3980
+ this.fetchFn = options.fetch ?? fetch;
3981
+ }
3982
+ async createWorkItem(input) {
3983
+ const projectKey = input.projectId ?? this.defaults.projectKey;
3984
+ if (!projectKey) {
3985
+ throw new Error("Jira projectKey is required to create work items.");
3972
3986
  }
3973
- this.client = options.client ?? new Mistral({
3974
- apiKey: options.apiKey,
3975
- serverURL: options.serverURL,
3976
- userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
3987
+ const issueType = resolveIssueType(input.type, this.defaults);
3988
+ const description = buildJiraDescription(input.description);
3989
+ const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
3990
+ const priority = mapPriority(input.priority);
3991
+ const payload = {
3992
+ fields: {
3993
+ project: { key: projectKey },
3994
+ summary: input.title,
3995
+ description,
3996
+ issuetype: { name: issueType },
3997
+ labels,
3998
+ priority: priority ? { name: priority } : undefined,
3999
+ assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
4000
+ duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
4001
+ }
4002
+ };
4003
+ const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
4004
+ method: "POST",
4005
+ headers: {
4006
+ Authorization: this.authHeader,
4007
+ "Content-Type": "application/json",
4008
+ Accept: "application/json"
4009
+ },
4010
+ body: JSON.stringify(payload)
3977
4011
  });
3978
- this.defaultModel = options.defaultModel ?? "mistral-large-latest";
4012
+ if (!response.ok) {
4013
+ const body = await response.text();
4014
+ throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
4015
+ }
4016
+ const data = await response.json();
4017
+ return {
4018
+ id: data.id ?? data.key ?? "",
4019
+ title: input.title,
4020
+ url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
4021
+ status: input.status,
4022
+ priority: input.priority,
4023
+ tags: input.tags,
4024
+ projectId: projectKey,
4025
+ externalId: input.externalId,
4026
+ metadata: input.metadata
4027
+ };
3979
4028
  }
3980
- async chat(messages, options = {}) {
3981
- const request = this.buildChatRequest(messages, options);
3982
- const response = await this.client.chat.complete(request);
3983
- return this.buildLLMResponse(response);
4029
+ async createWorkItems(items) {
4030
+ const created = [];
4031
+ for (const item of items) {
4032
+ created.push(await this.createWorkItem(item));
4033
+ }
4034
+ return created;
3984
4035
  }
3985
- async* stream(messages, options = {}) {
3986
- const request = this.buildChatRequest(messages, options);
3987
- request.stream = true;
3988
- const stream = await this.client.chat.stream(request);
3989
- const aggregatedParts = [];
3990
- const aggregatedToolCalls = [];
3991
- let usage;
3992
- let finishReason;
3993
- for await (const event of stream) {
3994
- for (const choice of event.data.choices) {
3995
- const delta = choice.delta;
3996
- if (typeof delta.content === "string") {
3997
- if (delta.content.length > 0) {
3998
- aggregatedParts.push({ type: "text", text: delta.content });
3999
- yield {
4000
- type: "message_delta",
4001
- delta: { type: "text", text: delta.content },
4002
- index: choice.index
4003
- };
4004
- }
4005
- } else if (Array.isArray(delta.content)) {
4006
- for (const chunk of delta.content) {
4007
- if (chunk.type === "text" && "text" in chunk) {
4008
- aggregatedParts.push({ type: "text", text: chunk.text });
4009
- yield {
4010
- type: "message_delta",
4011
- delta: { type: "text", text: chunk.text },
4012
- index: choice.index
4013
- };
4014
- }
4015
- }
4016
- }
4017
- if (delta.toolCalls) {
4018
- let localIndex = 0;
4019
- for (const call of delta.toolCalls) {
4020
- const toolCall = this.fromMistralToolCall(call, localIndex);
4021
- aggregatedToolCalls.push(toolCall);
4022
- yield {
4023
- type: "tool_call",
4024
- call: toolCall,
4025
- index: choice.index
4026
- };
4027
- localIndex += 1;
4028
- }
4029
- }
4030
- if (choice.finishReason && choice.finishReason !== "null") {
4031
- finishReason = choice.finishReason;
4032
- }
4033
- }
4034
- if (event.data.usage) {
4035
- const usageEntry = this.fromUsage(event.data.usage);
4036
- if (usageEntry) {
4037
- usage = usageEntry;
4038
- yield { type: "usage", usage: usageEntry };
4039
- }
4040
- }
4041
- }
4042
- const message = {
4043
- role: "assistant",
4044
- content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
4045
- };
4046
- if (aggregatedToolCalls.length > 0) {
4047
- message.content = [
4048
- ...aggregatedToolCalls,
4049
- ...aggregatedParts.length ? aggregatedParts : []
4050
- ];
4051
- }
4052
- yield {
4053
- type: "end",
4054
- response: {
4055
- message,
4056
- usage,
4057
- finishReason: mapFinishReason(finishReason)
4058
- }
4059
- };
4060
- }
4061
- async countTokens(_messages) {
4062
- throw new Error("Mistral API does not currently support token counting");
4063
- }
4064
- buildChatRequest(messages, options) {
4065
- const model = options.model ?? this.defaultModel;
4066
- const mappedMessages = messages.map((message) => this.toMistralMessage(message));
4067
- const request = {
4068
- model,
4069
- messages: mappedMessages
4070
- };
4071
- if (options.temperature != null) {
4072
- request.temperature = options.temperature;
4073
- }
4074
- if (options.topP != null) {
4075
- request.topP = options.topP;
4076
- }
4077
- if (options.maxOutputTokens != null) {
4078
- request.maxTokens = options.maxOutputTokens;
4079
- }
4080
- if (options.stopSequences?.length) {
4081
- request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
4082
- }
4083
- if (options.tools?.length) {
4084
- request.tools = options.tools.map((tool) => ({
4085
- type: "function",
4086
- function: {
4087
- name: tool.name,
4088
- description: tool.description,
4089
- parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
4090
- }
4091
- }));
4092
- }
4093
- if (options.responseFormat === "json") {
4094
- request.responseFormat = { type: "json_object" };
4095
- }
4096
- return request;
4097
- }
4098
- buildLLMResponse(response) {
4099
- const firstChoice = response.choices[0];
4100
- if (!firstChoice) {
4101
- return {
4102
- message: {
4103
- role: "assistant",
4104
- content: [{ type: "text", text: "" }]
4105
- },
4106
- usage: this.fromUsage(response.usage),
4107
- raw: response
4108
- };
4109
- }
4110
- const message = this.fromAssistantMessage(firstChoice.message);
4111
- return {
4112
- message,
4113
- usage: this.fromUsage(response.usage),
4114
- finishReason: mapFinishReason(firstChoice.finishReason),
4115
- raw: response
4116
- };
4036
+ }
4037
+ function normalizeSiteUrl(siteUrl) {
4038
+ return siteUrl.replace(/\/$/, "");
4039
+ }
4040
+ function buildAuthHeader(email, apiToken) {
4041
+ const token = Buffer3.from(`${email}:${apiToken}`).toString("base64");
4042
+ return `Basic ${token}`;
4043
+ }
4044
+ function resolveIssueType(type, defaults) {
4045
+ if (type && defaults.issueTypeMap?.[type]) {
4046
+ return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
4117
4047
  }
4118
- fromUsage(usage) {
4119
- if (!usage)
4048
+ return defaults.issueType ?? "Task";
4049
+ }
4050
+ function mapPriority(priority) {
4051
+ switch (priority) {
4052
+ case "urgent":
4053
+ return "Highest";
4054
+ case "high":
4055
+ return "High";
4056
+ case "medium":
4057
+ return "Medium";
4058
+ case "low":
4059
+ return "Low";
4060
+ case "none":
4061
+ default:
4120
4062
  return;
4121
- return {
4122
- promptTokens: usage.promptTokens ?? 0,
4123
- completionTokens: usage.completionTokens ?? 0,
4124
- totalTokens: usage.totalTokens ?? 0
4125
- };
4126
- }
4127
- fromAssistantMessage(message) {
4128
- const parts = [];
4129
- if (typeof message.content === "string") {
4130
- parts.push({ type: "text", text: message.content });
4131
- } else if (Array.isArray(message.content)) {
4132
- message.content.forEach((chunk) => {
4133
- if (chunk.type === "text" && "text" in chunk) {
4134
- parts.push({ type: "text", text: chunk.text });
4135
- }
4136
- });
4137
- }
4138
- const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
4139
- if (toolCalls.length > 0) {
4140
- parts.splice(0, 0, ...toolCalls);
4141
- }
4142
- if (parts.length === 0) {
4143
- parts.push({ type: "text", text: "" });
4144
- }
4145
- return {
4146
- role: "assistant",
4147
- content: parts
4148
- };
4149
4063
  }
4150
- fromMistralToolCall(call, index) {
4151
- const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
4152
- return {
4153
- type: "tool-call",
4154
- id: call.id ?? `tool-call-${index}`,
4155
- name: call.function.name,
4156
- arguments: args
4064
+ }
4065
+ function mergeLabels(defaults, tags) {
4066
+ const merged = new Set;
4067
+ (defaults ?? []).forEach((label) => merged.add(label));
4068
+ (tags ?? []).forEach((tag) => merged.add(tag));
4069
+ const result = [...merged];
4070
+ return result.length > 0 ? result : undefined;
4071
+ }
4072
+ function buildJiraDescription(description) {
4073
+ if (!description)
4074
+ return;
4075
+ const lines = description.split(/\r?\n/).filter((line) => line.trim());
4076
+ const content = lines.map((line) => ({
4077
+ type: "paragraph",
4078
+ content: [{ type: "text", text: line }]
4079
+ }));
4080
+ if (content.length === 0)
4081
+ return;
4082
+ return { type: "doc", version: 1, content };
4083
+ }
4084
+
4085
+ // src/impls/linear.ts
4086
+ import { LinearClient } from "@linear/sdk";
4087
+ var PRIORITY_MAP = {
4088
+ urgent: 1,
4089
+ high: 2,
4090
+ medium: 3,
4091
+ low: 4,
4092
+ none: 0
4093
+ };
4094
+
4095
+ class LinearProjectManagementProvider {
4096
+ client;
4097
+ defaults;
4098
+ constructor(options) {
4099
+ this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
4100
+ this.defaults = {
4101
+ teamId: options.teamId,
4102
+ projectId: options.projectId,
4103
+ assigneeId: options.assigneeId,
4104
+ stateId: options.stateId,
4105
+ labelIds: options.labelIds,
4106
+ tagLabelMap: options.tagLabelMap
4157
4107
  };
4158
4108
  }
4159
- toMistralMessage(message) {
4160
- const textContent = this.extractText(message.content);
4161
- const toolCalls = this.extractToolCalls(message);
4162
- switch (message.role) {
4163
- case "system":
4164
- return {
4165
- role: "system",
4166
- content: textContent ?? ""
4167
- };
4168
- case "user":
4169
- return {
4170
- role: "user",
4171
- content: textContent ?? ""
4172
- };
4173
- case "assistant":
4174
- return {
4175
- role: "assistant",
4176
- content: toolCalls.length > 0 ? null : textContent ?? "",
4177
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined
4178
- };
4179
- case "tool":
4180
- return {
4181
- role: "tool",
4182
- content: textContent ?? "",
4183
- toolCallId: message.toolCallId ?? toolCalls[0]?.id
4184
- };
4185
- default:
4186
- return {
4187
- role: "user",
4188
- content: textContent ?? ""
4189
- };
4109
+ async createWorkItem(input) {
4110
+ const teamId = this.defaults.teamId;
4111
+ if (!teamId) {
4112
+ throw new Error("Linear teamId is required to create work items.");
4190
4113
  }
4114
+ const payload = await this.client.createIssue({
4115
+ teamId,
4116
+ title: input.title,
4117
+ description: input.description,
4118
+ priority: mapPriority2(input.priority),
4119
+ estimate: input.estimate,
4120
+ assigneeId: input.assigneeId ?? this.defaults.assigneeId,
4121
+ projectId: input.projectId ?? this.defaults.projectId,
4122
+ stateId: this.defaults.stateId,
4123
+ labelIds: resolveLabelIds(this.defaults, input.tags)
4124
+ });
4125
+ const issue = await payload.issue;
4126
+ const state = issue ? await issue.state : undefined;
4127
+ return {
4128
+ id: issue?.id ?? "",
4129
+ title: issue?.title ?? input.title,
4130
+ url: issue?.url ?? undefined,
4131
+ status: state?.name ?? undefined,
4132
+ priority: input.priority,
4133
+ tags: input.tags,
4134
+ projectId: input.projectId ?? this.defaults.projectId,
4135
+ externalId: input.externalId,
4136
+ metadata: input.metadata
4137
+ };
4191
4138
  }
4192
- extractText(parts) {
4193
- const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
4194
- if (textParts.length === 0)
4195
- return null;
4196
- return textParts.join("");
4197
- }
4198
- extractToolCalls(message) {
4199
- const toolCallParts = message.content.filter((part) => part.type === "tool-call");
4200
- return toolCallParts.map((call, index) => ({
4201
- id: call.id ?? `call_${index}`,
4202
- type: "function",
4203
- index,
4204
- function: {
4205
- name: call.name,
4206
- arguments: call.arguments
4207
- }
4208
- }));
4139
+ async createWorkItems(items) {
4140
+ const created = [];
4141
+ for (const item of items) {
4142
+ created.push(await this.createWorkItem(item));
4143
+ }
4144
+ return created;
4209
4145
  }
4210
4146
  }
4211
- function mapFinishReason(reason) {
4212
- if (!reason)
4147
+ function mapPriority2(priority) {
4148
+ if (!priority)
4213
4149
  return;
4214
- const normalized = reason.toLowerCase();
4215
- switch (normalized) {
4216
- case "stop":
4217
- return "stop";
4218
- case "length":
4219
- return "length";
4220
- case "tool_call":
4221
- case "tool_calls":
4222
- return "tool_call";
4223
- case "content_filter":
4224
- return "content_filter";
4225
- default:
4226
- return;
4150
+ return PRIORITY_MAP[priority] ?? undefined;
4151
+ }
4152
+ function resolveLabelIds(defaults, tags) {
4153
+ const labelIds = new Set;
4154
+ (defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
4155
+ if (tags && defaults.tagLabelMap) {
4156
+ tags.forEach((tag) => {
4157
+ const mapped = defaults.tagLabelMap?.[tag];
4158
+ if (mapped)
4159
+ labelIds.add(mapped);
4160
+ });
4227
4161
  }
4162
+ const merged = [...labelIds];
4163
+ return merged.length > 0 ? merged : undefined;
4228
4164
  }
4229
4165
 
4230
- // src/impls/mistral-embedding.ts
4231
- import { Mistral as Mistral2 } from "@mistralai/mistralai";
4232
-
4233
- class MistralEmbeddingProvider {
4234
- client;
4235
- defaultModel;
4166
+ // src/impls/messaging-github.ts
4167
+ class GithubMessagingProvider {
4168
+ token;
4169
+ defaultOwner;
4170
+ defaultRepo;
4171
+ apiBaseUrl;
4236
4172
  constructor(options) {
4237
- if (!options.apiKey) {
4238
- throw new Error("MistralEmbeddingProvider requires an apiKey");
4239
- }
4240
- this.client = options.client ?? new Mistral2({
4241
- apiKey: options.apiKey,
4242
- serverURL: options.serverURL
4173
+ this.token = options.token;
4174
+ this.defaultOwner = options.defaultOwner;
4175
+ this.defaultRepo = options.defaultRepo;
4176
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
4177
+ }
4178
+ async sendMessage(input) {
4179
+ const target = this.resolveTarget(input);
4180
+ const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
4181
+ method: "POST",
4182
+ headers: {
4183
+ authorization: `Bearer ${this.token}`,
4184
+ accept: "application/vnd.github+json",
4185
+ "content-type": "application/json"
4186
+ },
4187
+ body: JSON.stringify({ body: input.text })
4243
4188
  });
4244
- this.defaultModel = options.defaultModel ?? "mistral-embed";
4189
+ const body = await response.json();
4190
+ if (!response.ok || !body.id) {
4191
+ throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
4192
+ }
4193
+ return {
4194
+ id: String(body.id),
4195
+ providerMessageId: body.node_id,
4196
+ status: "sent",
4197
+ sentAt: new Date,
4198
+ metadata: {
4199
+ url: body.html_url ?? "",
4200
+ owner: target.owner,
4201
+ repo: target.repo,
4202
+ issueNumber: String(target.issueNumber)
4203
+ }
4204
+ };
4245
4205
  }
4246
- async embedDocuments(documents, options) {
4247
- if (documents.length === 0)
4248
- return [];
4249
- const model = options?.model ?? this.defaultModel;
4250
- const response = await this.client.embeddings.create({
4251
- model,
4252
- inputs: documents.map((doc) => doc.text)
4206
+ async updateMessage(messageId, input) {
4207
+ const owner = input.metadata?.owner ?? this.defaultOwner;
4208
+ const repo = input.metadata?.repo ?? this.defaultRepo;
4209
+ if (!owner || !repo) {
4210
+ throw new Error("GitHub updateMessage requires owner and repo metadata.");
4211
+ }
4212
+ const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
4213
+ method: "PATCH",
4214
+ headers: {
4215
+ authorization: `Bearer ${this.token}`,
4216
+ accept: "application/vnd.github+json",
4217
+ "content-type": "application/json"
4218
+ },
4219
+ body: JSON.stringify({ body: input.text })
4253
4220
  });
4254
- return response.data.map((item, index) => ({
4255
- id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
4256
- vector: item.embedding ?? [],
4257
- dimensions: item.embedding?.length ?? 0,
4258
- model: response.model,
4259
- metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
4260
- }));
4221
+ const body = await response.json();
4222
+ if (!response.ok || !body.id) {
4223
+ throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
4224
+ }
4225
+ return {
4226
+ id: String(body.id),
4227
+ providerMessageId: body.node_id,
4228
+ status: "sent",
4229
+ sentAt: new Date,
4230
+ metadata: {
4231
+ url: body.html_url ?? "",
4232
+ owner,
4233
+ repo
4234
+ }
4235
+ };
4261
4236
  }
4262
- async embedQuery(query, options) {
4263
- const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
4264
- if (!result) {
4265
- throw new Error("Failed to compute embedding for query");
4237
+ resolveTarget(input) {
4238
+ const parsedRecipient = parseRecipient(input.recipientId);
4239
+ const owner = parsedRecipient?.owner ?? this.defaultOwner;
4240
+ const repo = parsedRecipient?.repo ?? this.defaultRepo;
4241
+ const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
4242
+ if (!owner || !repo || issueNumber == null) {
4243
+ throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
4266
4244
  }
4267
- return result;
4245
+ return {
4246
+ owner,
4247
+ repo,
4248
+ issueNumber
4249
+ };
4268
4250
  }
4269
4251
  }
4252
+ function parseRecipient(value) {
4253
+ if (!value)
4254
+ return null;
4255
+ const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
4256
+ if (!match)
4257
+ return null;
4258
+ const owner = match[1];
4259
+ const repo = match[2];
4260
+ const issueNumber = Number(match[3]);
4261
+ if (!owner || !repo || !Number.isInteger(issueNumber)) {
4262
+ return null;
4263
+ }
4264
+ return { owner, repo, issueNumber };
4265
+ }
4266
+ function parseIssueNumber(value) {
4267
+ if (!value)
4268
+ return null;
4269
+ const numeric = Number(value);
4270
+ return Number.isInteger(numeric) ? numeric : null;
4271
+ }
4270
4272
 
4271
- // src/impls/mistral-stt.ts
4272
- var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
4273
- var DEFAULT_MODEL = "voxtral-mini-latest";
4274
- var AUDIO_MIME_BY_FORMAT = {
4275
- mp3: "audio/mpeg",
4276
- wav: "audio/wav",
4277
- ogg: "audio/ogg",
4278
- pcm: "audio/pcm",
4279
- opus: "audio/opus"
4280
- };
4281
-
4282
- class MistralSttProvider {
4283
- apiKey;
4284
- defaultModel;
4285
- defaultLanguage;
4286
- baseUrl;
4287
- fetchImpl;
4273
+ // src/impls/messaging-slack.ts
4274
+ class SlackMessagingProvider {
4275
+ botToken;
4276
+ defaultChannelId;
4277
+ apiBaseUrl;
4288
4278
  constructor(options) {
4289
- if (!options.apiKey) {
4290
- throw new Error("MistralSttProvider requires an apiKey");
4291
- }
4292
- this.apiKey = options.apiKey;
4293
- this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
4294
- this.defaultLanguage = options.defaultLanguage;
4295
- this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
4296
- this.fetchImpl = options.fetchImpl ?? fetch;
4279
+ this.botToken = options.botToken;
4280
+ this.defaultChannelId = options.defaultChannelId;
4281
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
4297
4282
  }
4298
- async transcribe(input) {
4299
- const formData = new FormData;
4300
- const model = input.model ?? this.defaultModel;
4301
- const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
4302
- const fileName = `audio.${input.audio.format}`;
4303
- const audioBytes = new Uint8Array(input.audio.data);
4304
- const blob = new Blob([audioBytes], { type: mimeType });
4305
- formData.append("file", blob, fileName);
4306
- formData.append("model", model);
4307
- formData.append("response_format", "verbose_json");
4308
- const language = input.language ?? this.defaultLanguage;
4309
- if (language) {
4310
- formData.append("language", language);
4283
+ async sendMessage(input) {
4284
+ const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
4285
+ if (!channel) {
4286
+ throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
4287
+ }
4288
+ const payload = {
4289
+ channel,
4290
+ text: input.text,
4291
+ mrkdwn: input.markdown ?? true,
4292
+ thread_ts: input.threadId
4293
+ };
4294
+ const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
4295
+ method: "POST",
4296
+ headers: {
4297
+ authorization: `Bearer ${this.botToken}`,
4298
+ "content-type": "application/json"
4299
+ },
4300
+ body: JSON.stringify(payload)
4301
+ });
4302
+ const body = await response.json();
4303
+ if (!response.ok || !body.ok || !body.ts) {
4304
+ throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
4305
+ }
4306
+ return {
4307
+ id: `slack:${body.channel ?? channel}:${body.ts}`,
4308
+ providerMessageId: body.ts,
4309
+ status: "sent",
4310
+ sentAt: new Date,
4311
+ metadata: {
4312
+ channelId: body.channel ?? channel
4313
+ }
4314
+ };
4315
+ }
4316
+ async updateMessage(messageId, input) {
4317
+ const channel = input.channelId ?? this.defaultChannelId;
4318
+ if (!channel) {
4319
+ throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
4311
4320
  }
4312
- const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
4321
+ const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
4313
4322
  method: "POST",
4314
4323
  headers: {
4315
- Authorization: `Bearer ${this.apiKey}`
4324
+ authorization: `Bearer ${this.botToken}`,
4325
+ "content-type": "application/json"
4316
4326
  },
4317
- body: formData
4327
+ body: JSON.stringify({
4328
+ channel,
4329
+ ts: messageId,
4330
+ text: input.text,
4331
+ mrkdwn: input.markdown ?? true
4332
+ })
4318
4333
  });
4319
- if (!response.ok) {
4320
- const body = await response.text();
4321
- throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
4334
+ const body = await response.json();
4335
+ if (!response.ok || !body.ok || !body.ts) {
4336
+ throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
4322
4337
  }
4323
- const payload = await response.json();
4324
- return toTranscriptionResult(payload, input);
4325
- }
4326
- }
4327
- function toTranscriptionResult(payload, input) {
4328
- const record = asRecord2(payload);
4329
- const text = readString3(record, "text") ?? "";
4330
- const language = readString3(record, "language") ?? input.language ?? "unknown";
4331
- const segments = parseSegments(record);
4332
- if (segments.length === 0 && text.length > 0) {
4333
- segments.push({
4334
- text,
4335
- startMs: 0,
4336
- endMs: input.audio.durationMs ?? 0
4337
- });
4338
+ return {
4339
+ id: `slack:${body.channel ?? channel}:${body.ts}`,
4340
+ providerMessageId: body.ts,
4341
+ status: "sent",
4342
+ sentAt: new Date,
4343
+ metadata: {
4344
+ channelId: body.channel ?? channel
4345
+ }
4346
+ };
4338
4347
  }
4339
- const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
4340
- const topLevelWords = parseWordTimings(record.words);
4341
- const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
4342
- const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
4343
- const speakers = dedupeSpeakers(segments);
4344
- return {
4345
- text,
4346
- segments,
4347
- language,
4348
- durationMs,
4349
- speakers: speakers.length > 0 ? speakers : undefined,
4350
- wordTimings
4351
- };
4352
4348
  }
4353
- function parseSegments(record) {
4354
- if (!Array.isArray(record.segments)) {
4355
- return [];
4349
+
4350
+ // src/impls/messaging-whatsapp-meta.ts
4351
+ class MetaWhatsappMessagingProvider {
4352
+ accessToken;
4353
+ phoneNumberId;
4354
+ apiVersion;
4355
+ constructor(options) {
4356
+ this.accessToken = options.accessToken;
4357
+ this.phoneNumberId = options.phoneNumberId;
4358
+ this.apiVersion = options.apiVersion ?? "v22.0";
4356
4359
  }
4357
- const parsed = [];
4358
- for (const entry of record.segments) {
4359
- const segmentRecord = asRecord2(entry);
4360
- const text = readString3(segmentRecord, "text");
4361
- if (!text) {
4362
- continue;
4360
+ async sendMessage(input) {
4361
+ const to = input.recipientId;
4362
+ if (!to) {
4363
+ throw new Error("Meta WhatsApp sendMessage requires recipientId.");
4363
4364
  }
4364
- const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
4365
- const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
4366
- parsed.push({
4367
- text,
4368
- startMs: secondsToMs(startSeconds),
4369
- endMs: secondsToMs(endSeconds),
4370
- speakerId: readString3(segmentRecord, "speaker") ?? undefined,
4371
- confidence: readNumber2(segmentRecord, "confidence"),
4372
- wordTimings: parseWordTimings(segmentRecord.words)
4365
+ const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
4366
+ method: "POST",
4367
+ headers: {
4368
+ authorization: `Bearer ${this.accessToken}`,
4369
+ "content-type": "application/json"
4370
+ },
4371
+ body: JSON.stringify({
4372
+ messaging_product: "whatsapp",
4373
+ to,
4374
+ type: "text",
4375
+ text: {
4376
+ body: input.text,
4377
+ preview_url: false
4378
+ }
4379
+ })
4373
4380
  });
4381
+ const body = await response.json();
4382
+ const messageId = body.messages?.[0]?.id;
4383
+ if (!response.ok || !messageId) {
4384
+ const errorCode = body.error?.code != null ? String(body.error.code) : "";
4385
+ throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
4386
+ }
4387
+ return {
4388
+ id: messageId,
4389
+ providerMessageId: messageId,
4390
+ status: "sent",
4391
+ sentAt: new Date,
4392
+ metadata: {
4393
+ phoneNumberId: this.phoneNumberId
4394
+ }
4395
+ };
4374
4396
  }
4375
- return parsed;
4376
4397
  }
4377
- function parseWordTimings(value) {
4378
- if (!Array.isArray(value)) {
4379
- return [];
4398
+
4399
+ // src/impls/messaging-whatsapp-twilio.ts
4400
+ import { Buffer as Buffer4 } from "buffer";
4401
+
4402
+ class TwilioWhatsappMessagingProvider {
4403
+ accountSid;
4404
+ authToken;
4405
+ fromNumber;
4406
+ constructor(options) {
4407
+ this.accountSid = options.accountSid;
4408
+ this.authToken = options.authToken;
4409
+ this.fromNumber = options.fromNumber;
4380
4410
  }
4381
- const words = [];
4382
- for (const entry of value) {
4383
- const wordRecord = asRecord2(entry);
4384
- const word = readString3(wordRecord, "word");
4385
- const startSeconds = readNumber2(wordRecord, "start");
4386
- const endSeconds = readNumber2(wordRecord, "end");
4387
- if (!word || startSeconds == null || endSeconds == null) {
4388
- continue;
4411
+ async sendMessage(input) {
4412
+ const to = normalizeWhatsappAddress(input.recipientId);
4413
+ const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
4414
+ if (!to) {
4415
+ throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
4389
4416
  }
4390
- words.push({
4391
- word,
4392
- startMs: secondsToMs(startSeconds),
4393
- endMs: secondsToMs(endSeconds),
4394
- confidence: readNumber2(wordRecord, "confidence")
4395
- });
4396
- }
4397
- return words;
4398
- }
4399
- function dedupeSpeakers(segments) {
4400
- const seen = new Set;
4401
- const speakers = [];
4402
- for (const segment of segments) {
4403
- if (!segment.speakerId || seen.has(segment.speakerId)) {
4404
- continue;
4417
+ if (!from) {
4418
+ throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
4405
4419
  }
4406
- seen.add(segment.speakerId);
4407
- speakers.push({
4408
- id: segment.speakerId,
4409
- name: segment.speakerName
4420
+ const params = new URLSearchParams;
4421
+ params.set("To", to);
4422
+ params.set("From", from);
4423
+ params.set("Body", input.text);
4424
+ const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
4425
+ const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
4426
+ method: "POST",
4427
+ headers: {
4428
+ authorization: `Basic ${auth}`,
4429
+ "content-type": "application/x-www-form-urlencoded"
4430
+ },
4431
+ body: params.toString()
4410
4432
  });
4433
+ const body = await response.json();
4434
+ if (!response.ok || !body.sid) {
4435
+ throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
4436
+ }
4437
+ return {
4438
+ id: body.sid,
4439
+ providerMessageId: body.sid,
4440
+ status: mapTwilioStatus(body.status),
4441
+ sentAt: new Date,
4442
+ errorCode: body.error_code != null ? String(body.error_code) : undefined,
4443
+ errorMessage: body.error_message ?? undefined,
4444
+ metadata: {
4445
+ from,
4446
+ to
4447
+ }
4448
+ };
4411
4449
  }
4412
- return speakers;
4413
- }
4414
- function normalizeBaseUrl(url) {
4415
- return url.endsWith("/") ? url.slice(0, -1) : url;
4416
4450
  }
4417
- function asRecord2(value) {
4418
- if (value && typeof value === "object") {
4451
+ function normalizeWhatsappAddress(value) {
4452
+ if (!value)
4453
+ return null;
4454
+ if (value.startsWith("whatsapp:"))
4419
4455
  return value;
4420
- }
4421
- return {};
4422
- }
4423
- function readString3(record, key) {
4424
- const value = record[key];
4425
- return typeof value === "string" ? value : undefined;
4426
- }
4427
- function readNumber2(record, key) {
4428
- const value = record[key];
4429
- return typeof value === "number" ? value : undefined;
4456
+ return `whatsapp:${value}`;
4430
4457
  }
4431
- function secondsToMs(value) {
4432
- return Math.round(value * 1000);
4458
+ function mapTwilioStatus(status) {
4459
+ switch (status) {
4460
+ case "queued":
4461
+ case "accepted":
4462
+ case "scheduled":
4463
+ return "queued";
4464
+ case "sending":
4465
+ return "sending";
4466
+ case "delivered":
4467
+ return "delivered";
4468
+ case "failed":
4469
+ case "undelivered":
4470
+ case "canceled":
4471
+ return "failed";
4472
+ case "sent":
4473
+ default:
4474
+ return "sent";
4475
+ }
4433
4476
  }
4434
4477
 
4435
4478
  // src/impls/mistral-conversational.session.ts
@@ -4591,790 +4634,739 @@ function toError(error) {
4591
4634
  return new Error(String(error));
4592
4635
  }
4593
4636
 
4594
- // src/impls/mistral-conversational.ts
4595
- var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
4596
- var DEFAULT_MODEL2 = "mistral-small-latest";
4597
- var DEFAULT_VOICE = "default";
4637
+ // src/impls/mistral-stt.ts
4638
+ var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
4639
+ var DEFAULT_MODEL = "voxtral-mini-latest";
4640
+ var AUDIO_MIME_BY_FORMAT = {
4641
+ mp3: "audio/mpeg",
4642
+ wav: "audio/wav",
4643
+ ogg: "audio/ogg",
4644
+ pcm: "audio/pcm",
4645
+ opus: "audio/opus"
4646
+ };
4598
4647
 
4599
- class MistralConversationalProvider {
4648
+ class MistralSttProvider {
4600
4649
  apiKey;
4601
4650
  defaultModel;
4602
- defaultVoiceId;
4651
+ defaultLanguage;
4603
4652
  baseUrl;
4604
4653
  fetchImpl;
4605
- sttProvider;
4606
4654
  constructor(options) {
4607
4655
  if (!options.apiKey) {
4608
- throw new Error("MistralConversationalProvider requires an apiKey");
4656
+ throw new Error("MistralSttProvider requires an apiKey");
4609
4657
  }
4610
4658
  this.apiKey = options.apiKey;
4611
- this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
4612
- this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
4613
- this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
4659
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
4660
+ this.defaultLanguage = options.defaultLanguage;
4661
+ this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
4614
4662
  this.fetchImpl = options.fetchImpl ?? fetch;
4615
- this.sttProvider = options.sttProvider ?? new MistralSttProvider({
4616
- apiKey: options.apiKey,
4617
- defaultModel: options.sttOptions?.defaultModel,
4618
- defaultLanguage: options.sttOptions?.defaultLanguage,
4619
- serverURL: options.sttOptions?.serverURL ?? options.serverURL,
4620
- fetchImpl: this.fetchImpl
4621
- });
4622
- }
4623
- async startSession(config) {
4624
- return new MistralConversationSession({
4625
- sessionConfig: {
4626
- ...config,
4627
- voiceId: config.voiceId || this.defaultVoiceId
4628
- },
4629
- defaultModel: this.defaultModel,
4630
- complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
4631
- sttProvider: this.sttProvider
4632
- });
4633
- }
4634
- async listVoices() {
4635
- return [
4636
- {
4637
- id: this.defaultVoiceId,
4638
- name: "Mistral Default Voice",
4639
- description: "Default conversational voice profile.",
4640
- capabilities: ["conversational"]
4641
- }
4642
- ];
4643
4663
  }
4644
- async completeConversation(history, sessionConfig) {
4645
- const model = sessionConfig.llmModel ?? this.defaultModel;
4646
- const messages = [];
4647
- if (sessionConfig.systemPrompt) {
4648
- messages.push({ role: "system", content: sessionConfig.systemPrompt });
4649
- }
4650
- for (const item of history) {
4651
- messages.push({ role: item.role, content: item.content });
4664
+ async transcribe(input) {
4665
+ const formData = new FormData;
4666
+ const model = input.model ?? this.defaultModel;
4667
+ const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
4668
+ const fileName = `audio.${input.audio.format}`;
4669
+ const audioBytes = new Uint8Array(input.audio.data);
4670
+ const blob = new Blob([audioBytes], { type: mimeType });
4671
+ formData.append("file", blob, fileName);
4672
+ formData.append("model", model);
4673
+ formData.append("response_format", "verbose_json");
4674
+ const language = input.language ?? this.defaultLanguage;
4675
+ if (language) {
4676
+ formData.append("language", language);
4652
4677
  }
4653
- const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
4678
+ const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
4654
4679
  method: "POST",
4655
4680
  headers: {
4656
- Authorization: `Bearer ${this.apiKey}`,
4657
- "Content-Type": "application/json"
4681
+ Authorization: `Bearer ${this.apiKey}`
4658
4682
  },
4659
- body: JSON.stringify({
4660
- model,
4661
- messages
4662
- })
4683
+ body: formData
4663
4684
  });
4664
4685
  if (!response.ok) {
4665
4686
  const body = await response.text();
4666
- throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
4687
+ throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
4667
4688
  }
4668
4689
  const payload = await response.json();
4669
- return readAssistantText(payload);
4670
- }
4671
- }
4672
- function normalizeBaseUrl2(url) {
4673
- return url.endsWith("/") ? url.slice(0, -1) : url;
4674
- }
4675
- function readAssistantText(payload) {
4676
- const record = asRecord3(payload);
4677
- const choices = Array.isArray(record.choices) ? record.choices : [];
4678
- const firstChoice = asRecord3(choices[0]);
4679
- const message = asRecord3(firstChoice.message);
4680
- if (typeof message.content === "string") {
4681
- return message.content;
4682
- }
4683
- if (Array.isArray(message.content)) {
4684
- const textParts = message.content.map((part) => {
4685
- const entry = asRecord3(part);
4686
- const text = entry.text;
4687
- return typeof text === "string" ? text : "";
4688
- }).filter((text) => text.length > 0);
4689
- return textParts.join("");
4690
- }
4691
- return "";
4692
- }
4693
- function asRecord3(value) {
4694
- if (value && typeof value === "object") {
4695
- return value;
4690
+ return toTranscriptionResult(payload, input);
4696
4691
  }
4697
- return {};
4698
4692
  }
4699
-
4700
- // src/impls/qdrant-vector.ts
4701
- import { QdrantClient } from "@qdrant/js-client-rest";
4702
-
4703
- class QdrantVectorProvider {
4704
- client;
4705
- createCollectionIfMissing;
4706
- distance;
4707
- constructor(options) {
4708
- this.client = options.client ?? new QdrantClient({
4709
- url: options.url,
4710
- apiKey: options.apiKey,
4711
- ...options.clientParams
4712
- });
4713
- this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
4714
- this.distance = options.distance ?? "Cosine";
4715
- }
4716
- async upsert(request) {
4717
- if (request.documents.length === 0)
4718
- return;
4719
- const firstDocument = request.documents[0];
4720
- if (!firstDocument)
4721
- return;
4722
- const vectorSize = firstDocument.vector.length;
4723
- if (this.createCollectionIfMissing) {
4724
- await this.ensureCollection(request.collection, vectorSize);
4725
- }
4726
- const points = request.documents.map((document) => ({
4727
- id: document.id,
4728
- vector: document.vector,
4729
- payload: {
4730
- ...document.payload,
4731
- ...document.namespace ? { namespace: document.namespace } : {},
4732
- ...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
4733
- }
4734
- }));
4735
- await this.client.upsert(request.collection, {
4736
- wait: true,
4737
- points
4738
- });
4739
- }
4740
- async search(query) {
4741
- const results = await this.client.search(query.collection, {
4742
- vector: query.vector,
4743
- limit: query.topK,
4744
- filter: query.filter,
4745
- score_threshold: query.scoreThreshold,
4746
- with_payload: true,
4747
- with_vector: false
4748
- });
4749
- return results.map((item) => ({
4750
- id: String(item.id),
4751
- score: item.score,
4752
- payload: item.payload ?? undefined,
4753
- namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
4754
- }));
4755
- }
4756
- async delete(request) {
4757
- await this.client.delete(request.collection, {
4758
- wait: true,
4759
- points: request.ids
4693
+ function toTranscriptionResult(payload, input) {
4694
+ const record = asRecord2(payload);
4695
+ const text = readString3(record, "text") ?? "";
4696
+ const language = readString3(record, "language") ?? input.language ?? "unknown";
4697
+ const segments = parseSegments(record);
4698
+ if (segments.length === 0 && text.length > 0) {
4699
+ segments.push({
4700
+ text,
4701
+ startMs: 0,
4702
+ endMs: input.audio.durationMs ?? 0
4760
4703
  });
4761
4704
  }
4762
- async ensureCollection(collectionName, vectorSize) {
4763
- try {
4764
- await this.client.getCollection(collectionName);
4765
- } catch (_error) {
4766
- await this.client.createCollection(collectionName, {
4767
- vectors: {
4768
- size: vectorSize,
4769
- distance: this.distance
4770
- }
4771
- });
4772
- }
4773
- }
4705
+ const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
4706
+ const topLevelWords = parseWordTimings(record.words);
4707
+ const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
4708
+ const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
4709
+ const speakers = dedupeSpeakers(segments);
4710
+ return {
4711
+ text,
4712
+ segments,
4713
+ language,
4714
+ durationMs,
4715
+ speakers: speakers.length > 0 ? speakers : undefined,
4716
+ wordTimings
4717
+ };
4774
4718
  }
4775
-
4776
- // src/impls/supabase-psql.ts
4777
- import { Buffer as Buffer3 } from "buffer";
4778
- import { sql as drizzleSql } from "drizzle-orm";
4779
- import { drizzle } from "drizzle-orm/postgres-js";
4780
- import postgres from "postgres";
4781
-
4782
- class SupabasePostgresProvider {
4783
- client;
4784
- db;
4785
- ownsClient;
4786
- createDrizzle;
4787
- constructor(options = {}) {
4788
- this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
4789
- if (options.db) {
4790
- if (!options.client) {
4791
- throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
4792
- }
4793
- this.client = options.client;
4794
- this.db = options.db;
4795
- this.ownsClient = false;
4796
- return;
4797
- }
4798
- if (options.client) {
4799
- this.client = options.client;
4800
- this.ownsClient = false;
4801
- } else {
4802
- if (!options.connectionString) {
4803
- throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
4804
- }
4805
- this.client = postgres(options.connectionString, {
4806
- max: options.maxConnections,
4807
- prepare: false,
4808
- ssl: resolveSslMode(options.sslMode)
4809
- });
4810
- this.ownsClient = true;
4811
- }
4812
- this.db = this.createDrizzle(this.client);
4813
- }
4814
- async query(statement, params = []) {
4815
- const query = buildParameterizedSql(statement, params);
4816
- const result = await this.db.execute(query);
4817
- const rows = asRows(result);
4818
- return {
4819
- rows,
4820
- rowCount: rows.length
4821
- };
4822
- }
4823
- async execute(statement, params = []) {
4824
- const query = buildParameterizedSql(statement, params);
4825
- await this.db.execute(query);
4719
+ function parseSegments(record) {
4720
+ if (!Array.isArray(record.segments)) {
4721
+ return [];
4826
4722
  }
4827
- async transaction(run) {
4828
- const transactionResult = this.client.begin(async (transactionClient) => {
4829
- const transactionalProvider = new SupabasePostgresProvider({
4830
- client: transactionClient,
4831
- db: this.createDrizzle(transactionClient),
4832
- createDrizzle: this.createDrizzle
4833
- });
4834
- return run(transactionalProvider);
4723
+ const parsed = [];
4724
+ for (const entry of record.segments) {
4725
+ const segmentRecord = asRecord2(entry);
4726
+ const text = readString3(segmentRecord, "text");
4727
+ if (!text) {
4728
+ continue;
4729
+ }
4730
+ const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
4731
+ const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
4732
+ parsed.push({
4733
+ text,
4734
+ startMs: secondsToMs(startSeconds),
4735
+ endMs: secondsToMs(endSeconds),
4736
+ speakerId: readString3(segmentRecord, "speaker") ?? undefined,
4737
+ confidence: readNumber2(segmentRecord, "confidence"),
4738
+ wordTimings: parseWordTimings(segmentRecord.words)
4835
4739
  });
4836
- return transactionResult;
4837
4740
  }
4838
- async close() {
4839
- if (this.ownsClient) {
4840
- await this.client.end({ timeout: 5 });
4741
+ return parsed;
4742
+ }
4743
+ function parseWordTimings(value) {
4744
+ if (!Array.isArray(value)) {
4745
+ return [];
4746
+ }
4747
+ const words = [];
4748
+ for (const entry of value) {
4749
+ const wordRecord = asRecord2(entry);
4750
+ const word = readString3(wordRecord, "word");
4751
+ const startSeconds = readNumber2(wordRecord, "start");
4752
+ const endSeconds = readNumber2(wordRecord, "end");
4753
+ if (!word || startSeconds == null || endSeconds == null) {
4754
+ continue;
4841
4755
  }
4756
+ words.push({
4757
+ word,
4758
+ startMs: secondsToMs(startSeconds),
4759
+ endMs: secondsToMs(endSeconds),
4760
+ confidence: readNumber2(wordRecord, "confidence")
4761
+ });
4842
4762
  }
4763
+ return words;
4843
4764
  }
4844
- function buildParameterizedSql(statement, params) {
4845
- const segments = [];
4846
- const pattern = /\$(\d+)/g;
4847
- let cursor = 0;
4848
- for (const match of statement.matchAll(pattern)) {
4849
- const token = match[0];
4850
- const indexPart = match[1];
4851
- const start = match.index;
4852
- if (indexPart == null || start == null)
4765
+ function dedupeSpeakers(segments) {
4766
+ const seen = new Set;
4767
+ const speakers = [];
4768
+ for (const segment of segments) {
4769
+ if (!segment.speakerId || seen.has(segment.speakerId)) {
4853
4770
  continue;
4854
- const parameterIndex = Number(indexPart) - 1;
4855
- if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
4856
- throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
4857
- }
4858
- const staticSegment = statement.slice(cursor, start);
4859
- if (staticSegment.length > 0) {
4860
- segments.push(drizzleSql.raw(staticSegment));
4861
- }
4862
- const parameterValue = params[parameterIndex];
4863
- if (parameterValue === undefined) {
4864
- throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
4865
4771
  }
4866
- const normalizedValue = normalizeParam(parameterValue);
4867
- segments.push(drizzleSql`${normalizedValue}`);
4868
- cursor = start + token.length;
4869
- }
4870
- const tailSegment = statement.slice(cursor);
4871
- if (tailSegment.length > 0) {
4872
- segments.push(drizzleSql.raw(tailSegment));
4873
- }
4874
- if (segments.length === 0) {
4875
- return drizzleSql.raw("");
4772
+ seen.add(segment.speakerId);
4773
+ speakers.push({
4774
+ id: segment.speakerId,
4775
+ name: segment.speakerName
4776
+ });
4876
4777
  }
4877
- return drizzleSql.join(segments);
4778
+ return speakers;
4878
4779
  }
4879
- function normalizeParam(value) {
4880
- if (typeof value === "bigint") {
4881
- return value.toString();
4882
- }
4883
- if (value instanceof Uint8Array) {
4884
- return Buffer3.from(value);
4885
- }
4886
- if (isPlainObject(value)) {
4887
- return JSON.stringify(value);
4888
- }
4889
- return value;
4780
+ function normalizeBaseUrl(url) {
4781
+ return url.endsWith("/") ? url.slice(0, -1) : url;
4890
4782
  }
4891
- function asRows(result) {
4892
- if (!Array.isArray(result)) {
4893
- return [];
4783
+ function asRecord2(value) {
4784
+ if (value && typeof value === "object") {
4785
+ return value;
4894
4786
  }
4895
- return result;
4787
+ return {};
4896
4788
  }
4897
- function isPlainObject(value) {
4898
- if (value == null || typeof value !== "object") {
4899
- return false;
4900
- }
4901
- if (Array.isArray(value)) {
4902
- return false;
4903
- }
4904
- if (value instanceof Date) {
4905
- return false;
4906
- }
4907
- if (value instanceof Uint8Array) {
4908
- return false;
4909
- }
4910
- return true;
4789
+ function readString3(record, key) {
4790
+ const value = record[key];
4791
+ return typeof value === "string" ? value : undefined;
4911
4792
  }
4912
- function resolveSslMode(mode) {
4913
- switch (mode) {
4914
- case "allow":
4915
- return false;
4916
- case "prefer":
4917
- return "prefer";
4918
- case "require":
4919
- default:
4920
- return "require";
4921
- }
4793
+ function readNumber2(record, key) {
4794
+ const value = record[key];
4795
+ return typeof value === "number" ? value : undefined;
4796
+ }
4797
+ function secondsToMs(value) {
4798
+ return Math.round(value * 1000);
4922
4799
  }
4923
4800
 
4924
- // src/impls/supabase-vector.ts
4925
- class SupabaseVectorProvider {
4926
- database;
4927
- createTableIfMissing;
4928
- distanceMetric;
4929
- quotedSchema;
4930
- qualifiedTable;
4931
- collectionIndex;
4932
- namespaceIndex;
4933
- ensureTablePromise;
4801
+ // src/impls/mistral-conversational.ts
4802
+ var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
4803
+ var DEFAULT_MODEL2 = "mistral-small-latest";
4804
+ var DEFAULT_VOICE = "default";
4805
+
4806
+ class MistralConversationalProvider {
4807
+ apiKey;
4808
+ defaultModel;
4809
+ defaultVoiceId;
4810
+ baseUrl;
4811
+ fetchImpl;
4812
+ sttProvider;
4934
4813
  constructor(options) {
4935
- this.database = options.database ?? new SupabasePostgresProvider({
4936
- connectionString: options.connectionString,
4937
- maxConnections: options.maxConnections,
4938
- sslMode: options.sslMode
4939
- });
4940
- this.createTableIfMissing = options.createTableIfMissing ?? true;
4941
- this.distanceMetric = options.distanceMetric ?? "cosine";
4942
- const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
4943
- const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
4944
- this.quotedSchema = quoteIdentifier(schema);
4945
- this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
4946
- this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
4947
- this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
4948
- }
4949
- async upsert(request) {
4950
- if (request.documents.length === 0) {
4951
- return;
4952
- }
4953
- if (this.createTableIfMissing) {
4954
- await this.ensureTable();
4955
- }
4956
- for (const document of request.documents) {
4957
- await this.database.execute(`INSERT INTO ${this.qualifiedTable}
4958
- (collection, id, embedding, payload, namespace, expires_at, updated_at)
4959
- VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
4960
- ON CONFLICT (collection, id)
4961
- DO UPDATE SET
4962
- embedding = EXCLUDED.embedding,
4963
- payload = EXCLUDED.payload,
4964
- namespace = EXCLUDED.namespace,
4965
- expires_at = EXCLUDED.expires_at,
4966
- updated_at = now();`, [
4967
- request.collection,
4968
- document.id,
4969
- toVectorLiteral(document.vector),
4970
- document.payload ? JSON.stringify(document.payload) : null,
4971
- document.namespace ?? null,
4972
- document.expiresAt ?? null
4973
- ]);
4814
+ if (!options.apiKey) {
4815
+ throw new Error("MistralConversationalProvider requires an apiKey");
4974
4816
  }
4817
+ this.apiKey = options.apiKey;
4818
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
4819
+ this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
4820
+ this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
4821
+ this.fetchImpl = options.fetchImpl ?? fetch;
4822
+ this.sttProvider = options.sttProvider ?? new MistralSttProvider({
4823
+ apiKey: options.apiKey,
4824
+ defaultModel: options.sttOptions?.defaultModel,
4825
+ defaultLanguage: options.sttOptions?.defaultLanguage,
4826
+ serverURL: options.sttOptions?.serverURL ?? options.serverURL,
4827
+ fetchImpl: this.fetchImpl
4828
+ });
4975
4829
  }
4976
- async search(query) {
4977
- const operator = this.distanceOperator;
4978
- const results = await this.database.query(`SELECT
4979
- id,
4980
- payload,
4981
- namespace,
4982
- (embedding ${operator} $3::vector) AS distance
4983
- FROM ${this.qualifiedTable}
4984
- WHERE collection = $1
4985
- AND ($2::text IS NULL OR namespace = $2)
4986
- AND (expires_at IS NULL OR expires_at > now())
4987
- AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
4988
- ORDER BY embedding ${operator} $3::vector
4989
- LIMIT $5;`, [
4990
- query.collection,
4991
- query.namespace ?? null,
4992
- toVectorLiteral(query.vector),
4993
- query.filter ? JSON.stringify(query.filter) : null,
4994
- query.topK
4995
- ]);
4996
- const mapped = results.rows.map((row) => {
4997
- const distance = Number(row.distance);
4998
- return {
4999
- id: row.id,
5000
- score: distanceToScore(distance, this.distanceMetric),
5001
- payload: isRecord(row.payload) ? row.payload : undefined,
5002
- namespace: row.namespace ?? undefined
5003
- };
4830
+ async startSession(config) {
4831
+ return new MistralConversationSession({
4832
+ sessionConfig: {
4833
+ ...config,
4834
+ voiceId: config.voiceId || this.defaultVoiceId
4835
+ },
4836
+ defaultModel: this.defaultModel,
4837
+ complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
4838
+ sttProvider: this.sttProvider
5004
4839
  });
5005
- const scoreThreshold = query.scoreThreshold;
5006
- if (scoreThreshold == null) {
5007
- return mapped;
5008
- }
5009
- return mapped.filter((result) => result.score >= scoreThreshold);
5010
4840
  }
5011
- async delete(request) {
5012
- if (request.ids.length === 0) {
5013
- return;
5014
- }
5015
- const params = [
5016
- request.collection,
5017
- request.ids,
5018
- request.namespace ?? null
4841
+ async listVoices() {
4842
+ return [
4843
+ {
4844
+ id: this.defaultVoiceId,
4845
+ name: "Mistral Default Voice",
4846
+ description: "Default conversational voice profile.",
4847
+ capabilities: ["conversational"]
4848
+ }
5019
4849
  ];
5020
- await this.database.execute(`DELETE FROM ${this.qualifiedTable}
5021
- WHERE collection = $1
5022
- AND id = ANY($2::text[])
5023
- AND ($3::text IS NULL OR namespace = $3);`, params);
5024
4850
  }
5025
- async ensureTable() {
5026
- if (!this.ensureTablePromise) {
5027
- this.ensureTablePromise = this.createTable();
4851
+ async completeConversation(history, sessionConfig) {
4852
+ const model = sessionConfig.llmModel ?? this.defaultModel;
4853
+ const messages = [];
4854
+ if (sessionConfig.systemPrompt) {
4855
+ messages.push({ role: "system", content: sessionConfig.systemPrompt });
5028
4856
  }
5029
- await this.ensureTablePromise;
5030
- }
5031
- async createTable() {
5032
- await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
5033
- await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
5034
- await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
5035
- collection text NOT NULL,
5036
- id text NOT NULL,
5037
- embedding vector NOT NULL,
5038
- payload jsonb,
5039
- namespace text,
5040
- expires_at timestamptz,
5041
- created_at timestamptz NOT NULL DEFAULT now(),
5042
- updated_at timestamptz NOT NULL DEFAULT now(),
5043
- PRIMARY KEY (collection, id)
5044
- );`);
5045
- await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
5046
- ON ${this.qualifiedTable} (collection);`);
5047
- await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
5048
- ON ${this.qualifiedTable} (namespace);`);
5049
- }
5050
- get distanceOperator() {
5051
- switch (this.distanceMetric) {
5052
- case "l2":
5053
- return "<->";
5054
- case "inner_product":
5055
- return "<#>";
5056
- case "cosine":
5057
- default:
5058
- return "<=>";
4857
+ for (const item of history) {
4858
+ messages.push({ role: item.role, content: item.content });
5059
4859
  }
4860
+ const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
4861
+ method: "POST",
4862
+ headers: {
4863
+ Authorization: `Bearer ${this.apiKey}`,
4864
+ "Content-Type": "application/json"
4865
+ },
4866
+ body: JSON.stringify({
4867
+ model,
4868
+ messages
4869
+ })
4870
+ });
4871
+ if (!response.ok) {
4872
+ const body = await response.text();
4873
+ throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
4874
+ }
4875
+ const payload = await response.json();
4876
+ return readAssistantText(payload);
5060
4877
  }
5061
4878
  }
5062
- function sanitizeIdentifier(value, label) {
5063
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
5064
- throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
5065
- }
5066
- return value;
5067
- }
5068
- function quoteIdentifier(value) {
5069
- return `"${value.replaceAll('"', '""')}"`;
4879
+ function normalizeBaseUrl2(url) {
4880
+ return url.endsWith("/") ? url.slice(0, -1) : url;
5070
4881
  }
5071
- function toVectorLiteral(vector) {
5072
- if (vector.length === 0) {
5073
- throw new Error("Supabase vectors must contain at least one dimension.");
4882
+ function readAssistantText(payload) {
4883
+ const record = asRecord3(payload);
4884
+ const choices = Array.isArray(record.choices) ? record.choices : [];
4885
+ const firstChoice = asRecord3(choices[0]);
4886
+ const message = asRecord3(firstChoice.message);
4887
+ if (typeof message.content === "string") {
4888
+ return message.content;
5074
4889
  }
5075
- for (const value of vector) {
5076
- if (!Number.isFinite(value)) {
5077
- throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
5078
- }
4890
+ if (Array.isArray(message.content)) {
4891
+ const textParts = message.content.map((part) => {
4892
+ const entry = asRecord3(part);
4893
+ const text = entry.text;
4894
+ return typeof text === "string" ? text : "";
4895
+ }).filter((text) => text.length > 0);
4896
+ return textParts.join("");
5079
4897
  }
5080
- return `[${vector.join(",")}]`;
5081
- }
5082
- function isRecord(value) {
5083
- return typeof value === "object" && value !== null && !Array.isArray(value);
4898
+ return "";
5084
4899
  }
5085
- function distanceToScore(distance, metric) {
5086
- switch (metric) {
5087
- case "inner_product":
5088
- return -distance;
5089
- case "l2":
5090
- return 1 / (1 + distance);
5091
- case "cosine":
5092
- default:
5093
- return 1 - distance;
4900
+ function asRecord3(value) {
4901
+ if (value && typeof value === "object") {
4902
+ return value;
5094
4903
  }
4904
+ return {};
5095
4905
  }
5096
4906
 
5097
- // src/impls/stripe-payments.ts
5098
- import Stripe from "stripe";
5099
- var API_VERSION = "2026-02-25.clover";
4907
+ // src/impls/mistral-embedding.ts
4908
+ import { Mistral } from "@mistralai/mistralai";
5100
4909
 
5101
- class StripePaymentsProvider {
5102
- stripe;
4910
+ class MistralEmbeddingProvider {
4911
+ client;
4912
+ defaultModel;
5103
4913
  constructor(options) {
5104
- this.stripe = options.stripe ?? new Stripe(options.apiKey, {
5105
- apiVersion: API_VERSION
4914
+ if (!options.apiKey) {
4915
+ throw new Error("MistralEmbeddingProvider requires an apiKey");
4916
+ }
4917
+ this.client = options.client ?? new Mistral({
4918
+ apiKey: options.apiKey,
4919
+ serverURL: options.serverURL
5106
4920
  });
4921
+ this.defaultModel = options.defaultModel ?? "mistral-embed";
5107
4922
  }
5108
- async createCustomer(input) {
5109
- const customer = await this.stripe.customers.create({
5110
- email: input.email,
5111
- name: input.name,
5112
- description: input.description,
5113
- metadata: input.metadata
4923
+ async embedDocuments(documents, options) {
4924
+ if (documents.length === 0)
4925
+ return [];
4926
+ const model = options?.model ?? this.defaultModel;
4927
+ const response = await this.client.embeddings.create({
4928
+ model,
4929
+ inputs: documents.map((doc) => doc.text)
5114
4930
  });
5115
- return this.toCustomer(customer);
4931
+ return response.data.map((item, index) => ({
4932
+ id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
4933
+ vector: item.embedding ?? [],
4934
+ dimensions: item.embedding?.length ?? 0,
4935
+ model: response.model,
4936
+ metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
4937
+ }));
5116
4938
  }
5117
- async getCustomer(customerId) {
5118
- const customer = await this.stripe.customers.retrieve(customerId);
5119
- if (customer.deleted)
5120
- return null;
5121
- return this.toCustomer(customer);
4939
+ async embedQuery(query, options) {
4940
+ const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
4941
+ if (!result) {
4942
+ throw new Error("Failed to compute embedding for query");
4943
+ }
4944
+ return result;
5122
4945
  }
5123
- async createPaymentIntent(input) {
5124
- const intent = await this.stripe.paymentIntents.create({
5125
- amount: input.amount.amount,
5126
- currency: input.amount.currency,
5127
- customer: input.customerId,
5128
- description: input.description,
5129
- capture_method: input.captureMethod ?? "automatic",
5130
- confirmation_method: input.confirmationMethod ?? "automatic",
5131
- automatic_payment_methods: { enabled: true },
5132
- metadata: input.metadata,
5133
- return_url: input.returnUrl,
5134
- statement_descriptor: input.statementDescriptor
4946
+ }
4947
+
4948
+ // src/impls/mistral-llm.ts
4949
+ import { Mistral as Mistral2 } from "@mistralai/mistralai";
4950
+
4951
+ class MistralLLMProvider {
4952
+ client;
4953
+ defaultModel;
4954
+ constructor(options) {
4955
+ if (!options.apiKey) {
4956
+ throw new Error("MistralLLMProvider requires an apiKey");
4957
+ }
4958
+ this.client = options.client ?? new Mistral2({
4959
+ apiKey: options.apiKey,
4960
+ serverURL: options.serverURL,
4961
+ userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
5135
4962
  });
5136
- return this.toPaymentIntent(intent);
5137
- }
5138
- async capturePayment(paymentIntentId, input) {
5139
- const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
5140
- return this.toPaymentIntent(intent);
4963
+ this.defaultModel = options.defaultModel ?? "mistral-large-latest";
5141
4964
  }
5142
- async cancelPaymentIntent(paymentIntentId) {
5143
- const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
5144
- return this.toPaymentIntent(intent);
4965
+ async chat(messages, options = {}) {
4966
+ const request = this.buildChatRequest(messages, options);
4967
+ const response = await this.client.chat.complete(request);
4968
+ return this.buildLLMResponse(response);
5145
4969
  }
5146
- async refundPayment(input) {
5147
- const refund = await this.stripe.refunds.create({
5148
- payment_intent: input.paymentIntentId,
5149
- amount: input.amount?.amount,
5150
- reason: mapRefundReason(input.reason),
5151
- metadata: input.metadata
5152
- });
5153
- const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
5154
- return {
5155
- id: refund.id,
5156
- paymentIntentId,
5157
- amount: {
5158
- amount: refund.amount ?? 0,
5159
- currency: refund.currency?.toUpperCase() ?? "USD"
5160
- },
5161
- status: mapRefundStatus(refund.status),
5162
- reason: refund.reason ?? undefined,
5163
- metadata: this.toMetadata(refund.metadata),
5164
- createdAt: refund.created ? new Date(refund.created * 1000) : undefined
4970
+ async* stream(messages, options = {}) {
4971
+ const request = this.buildChatRequest(messages, options);
4972
+ request.stream = true;
4973
+ const stream = await this.client.chat.stream(request);
4974
+ const aggregatedParts = [];
4975
+ const aggregatedToolCalls = [];
4976
+ let usage;
4977
+ let finishReason;
4978
+ for await (const event of stream) {
4979
+ for (const choice of event.data.choices) {
4980
+ const delta = choice.delta;
4981
+ if (typeof delta.content === "string") {
4982
+ if (delta.content.length > 0) {
4983
+ aggregatedParts.push({ type: "text", text: delta.content });
4984
+ yield {
4985
+ type: "message_delta",
4986
+ delta: { type: "text", text: delta.content },
4987
+ index: choice.index
4988
+ };
4989
+ }
4990
+ } else if (Array.isArray(delta.content)) {
4991
+ for (const chunk of delta.content) {
4992
+ if (chunk.type === "text" && "text" in chunk) {
4993
+ aggregatedParts.push({ type: "text", text: chunk.text });
4994
+ yield {
4995
+ type: "message_delta",
4996
+ delta: { type: "text", text: chunk.text },
4997
+ index: choice.index
4998
+ };
4999
+ }
5000
+ }
5001
+ }
5002
+ if (delta.toolCalls) {
5003
+ let localIndex = 0;
5004
+ for (const call of delta.toolCalls) {
5005
+ const toolCall = this.fromMistralToolCall(call, localIndex);
5006
+ aggregatedToolCalls.push(toolCall);
5007
+ yield {
5008
+ type: "tool_call",
5009
+ call: toolCall,
5010
+ index: choice.index
5011
+ };
5012
+ localIndex += 1;
5013
+ }
5014
+ }
5015
+ if (choice.finishReason && choice.finishReason !== "null") {
5016
+ finishReason = choice.finishReason;
5017
+ }
5018
+ }
5019
+ if (event.data.usage) {
5020
+ const usageEntry = this.fromUsage(event.data.usage);
5021
+ if (usageEntry) {
5022
+ usage = usageEntry;
5023
+ yield { type: "usage", usage: usageEntry };
5024
+ }
5025
+ }
5026
+ }
5027
+ const message = {
5028
+ role: "assistant",
5029
+ content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
5030
+ };
5031
+ if (aggregatedToolCalls.length > 0) {
5032
+ message.content = [
5033
+ ...aggregatedToolCalls,
5034
+ ...aggregatedParts.length ? aggregatedParts : []
5035
+ ];
5036
+ }
5037
+ yield {
5038
+ type: "end",
5039
+ response: {
5040
+ message,
5041
+ usage,
5042
+ finishReason: mapFinishReason(finishReason)
5043
+ }
5165
5044
  };
5166
5045
  }
5167
- async listInvoices(query) {
5168
- const requestedStatus = query?.status?.[0];
5169
- const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
5170
- const response = await this.stripe.invoices.list({
5171
- customer: query?.customerId,
5172
- status: stripeStatus,
5173
- limit: query?.limit,
5174
- starting_after: query?.startingAfter
5175
- });
5176
- return response.data.map((invoice) => this.toInvoice(invoice));
5046
+ async countTokens(_messages) {
5047
+ throw new Error("Mistral API does not currently support token counting");
5177
5048
  }
5178
- async listTransactions(query) {
5179
- const response = await this.stripe.charges.list({
5180
- customer: query?.customerId,
5181
- payment_intent: query?.paymentIntentId,
5182
- limit: query?.limit,
5183
- starting_after: query?.startingAfter
5184
- });
5185
- return response.data.map((charge) => ({
5186
- id: charge.id,
5187
- paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
5188
- amount: {
5189
- amount: charge.amount,
5190
- currency: charge.currency?.toUpperCase() ?? "USD"
5191
- },
5192
- type: "capture",
5193
- status: mapChargeStatus(charge.status),
5194
- description: charge.description ?? undefined,
5195
- createdAt: new Date(charge.created * 1000),
5196
- metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
5197
- balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
5198
- })
5199
- }));
5049
+ buildChatRequest(messages, options) {
5050
+ const model = options.model ?? this.defaultModel;
5051
+ const mappedMessages = messages.map((message) => this.toMistralMessage(message));
5052
+ const request = {
5053
+ model,
5054
+ messages: mappedMessages
5055
+ };
5056
+ if (options.temperature != null) {
5057
+ request.temperature = options.temperature;
5058
+ }
5059
+ if (options.topP != null) {
5060
+ request.topP = options.topP;
5061
+ }
5062
+ if (options.maxOutputTokens != null) {
5063
+ request.maxTokens = options.maxOutputTokens;
5064
+ }
5065
+ if (options.stopSequences?.length) {
5066
+ request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
5067
+ }
5068
+ if (options.tools?.length) {
5069
+ request.tools = options.tools.map((tool) => ({
5070
+ type: "function",
5071
+ function: {
5072
+ name: tool.name,
5073
+ description: tool.description,
5074
+ parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
5075
+ }
5076
+ }));
5077
+ }
5078
+ if (options.responseFormat === "json") {
5079
+ request.responseFormat = { type: "json_object" };
5080
+ }
5081
+ return request;
5200
5082
  }
5201
- toCustomer(customer) {
5202
- const metadata = this.toMetadata(customer.metadata);
5203
- const updatedAtValue = metadata?.updatedAt;
5083
+ buildLLMResponse(response) {
5084
+ const firstChoice = response.choices[0];
5085
+ if (!firstChoice) {
5086
+ return {
5087
+ message: {
5088
+ role: "assistant",
5089
+ content: [{ type: "text", text: "" }]
5090
+ },
5091
+ usage: this.fromUsage(response.usage),
5092
+ raw: response
5093
+ };
5094
+ }
5095
+ const message = this.fromAssistantMessage(firstChoice.message);
5204
5096
  return {
5205
- id: customer.id,
5206
- email: customer.email ?? undefined,
5207
- name: customer.name ?? undefined,
5208
- metadata,
5209
- createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
5210
- updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
5097
+ message,
5098
+ usage: this.fromUsage(response.usage),
5099
+ finishReason: mapFinishReason(firstChoice.finishReason),
5100
+ raw: response
5211
5101
  };
5212
5102
  }
5213
- toPaymentIntent(intent) {
5214
- const metadata = this.toMetadata(intent.metadata);
5103
+ fromUsage(usage) {
5104
+ if (!usage)
5105
+ return;
5215
5106
  return {
5216
- id: intent.id,
5217
- amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
5218
- status: mapPaymentIntentStatus(intent.status),
5219
- customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
5220
- description: intent.description ?? undefined,
5221
- clientSecret: intent.client_secret ?? undefined,
5222
- metadata,
5223
- createdAt: new Date(intent.created * 1000),
5224
- updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
5107
+ promptTokens: usage.promptTokens ?? 0,
5108
+ completionTokens: usage.completionTokens ?? 0,
5109
+ totalTokens: usage.totalTokens ?? 0
5225
5110
  };
5226
5111
  }
5227
- toInvoice(invoice) {
5228
- const metadata = this.toMetadata(invoice.metadata);
5112
+ fromAssistantMessage(message) {
5113
+ const parts = [];
5114
+ if (typeof message.content === "string") {
5115
+ parts.push({ type: "text", text: message.content });
5116
+ } else if (Array.isArray(message.content)) {
5117
+ message.content.forEach((chunk) => {
5118
+ if (chunk.type === "text" && "text" in chunk) {
5119
+ parts.push({ type: "text", text: chunk.text });
5120
+ }
5121
+ });
5122
+ }
5123
+ const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
5124
+ if (toolCalls.length > 0) {
5125
+ parts.splice(0, 0, ...toolCalls);
5126
+ }
5127
+ if (parts.length === 0) {
5128
+ parts.push({ type: "text", text: "" });
5129
+ }
5229
5130
  return {
5230
- id: invoice.id,
5231
- number: invoice.number ?? undefined,
5232
- status: invoice.status ?? "draft",
5233
- amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
5234
- amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
5235
- customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
5236
- dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
5237
- hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
5238
- metadata,
5239
- createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
5240
- updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
5131
+ role: "assistant",
5132
+ content: parts
5241
5133
  };
5242
5134
  }
5243
- toMoney(amount, currency) {
5135
+ fromMistralToolCall(call, index) {
5136
+ const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
5244
5137
  return {
5245
- amount,
5246
- currency: currency?.toUpperCase() ?? "USD"
5138
+ type: "tool-call",
5139
+ id: call.id ?? `tool-call-${index}`,
5140
+ name: call.function.name,
5141
+ arguments: args
5247
5142
  };
5248
5143
  }
5249
- toMetadata(metadata) {
5250
- if (!metadata)
5251
- return;
5252
- const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
5253
- if (entries.length === 0)
5254
- return;
5255
- return Object.fromEntries(entries);
5256
- }
5257
- mergeMetadata(base, extras) {
5258
- const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
5259
- if (!base && filteredExtras.length === 0) {
5260
- return;
5144
+ toMistralMessage(message) {
5145
+ const textContent = this.extractText(message.content);
5146
+ const toolCalls = this.extractToolCalls(message);
5147
+ switch (message.role) {
5148
+ case "system":
5149
+ return {
5150
+ role: "system",
5151
+ content: textContent ?? ""
5152
+ };
5153
+ case "user":
5154
+ return {
5155
+ role: "user",
5156
+ content: textContent ?? ""
5157
+ };
5158
+ case "assistant":
5159
+ return {
5160
+ role: "assistant",
5161
+ content: toolCalls.length > 0 ? null : textContent ?? "",
5162
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
5163
+ };
5164
+ case "tool":
5165
+ return {
5166
+ role: "tool",
5167
+ content: textContent ?? "",
5168
+ toolCallId: message.toolCallId ?? toolCalls[0]?.id
5169
+ };
5170
+ default:
5171
+ return {
5172
+ role: "user",
5173
+ content: textContent ?? ""
5174
+ };
5261
5175
  }
5262
- return {
5263
- ...base ?? {},
5264
- ...Object.fromEntries(filteredExtras)
5265
- };
5266
5176
  }
5267
- }
5268
- function mapRefundReason(reason) {
5269
- if (!reason)
5270
- return;
5271
- const allowed = [
5272
- "duplicate",
5273
- "fraudulent",
5274
- "requested_by_customer"
5275
- ];
5276
- return allowed.includes(reason) ? reason : undefined;
5277
- }
5278
- function mapPaymentIntentStatus(status) {
5279
- switch (status) {
5280
- case "requires_payment_method":
5281
- return "requires_payment_method";
5282
- case "requires_confirmation":
5283
- return "requires_confirmation";
5284
- case "requires_action":
5285
- case "requires_capture":
5286
- return "requires_action";
5287
- case "processing":
5288
- return "processing";
5289
- case "succeeded":
5290
- return "succeeded";
5291
- case "canceled":
5292
- return "canceled";
5293
- default:
5294
- return "requires_payment_method";
5177
+ extractText(parts) {
5178
+ const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
5179
+ if (textParts.length === 0)
5180
+ return null;
5181
+ return textParts.join("");
5295
5182
  }
5296
- }
5297
- function mapRefundStatus(status) {
5298
- switch (status) {
5299
- case "pending":
5300
- case "succeeded":
5301
- case "failed":
5302
- case "canceled":
5303
- return status;
5304
- default:
5305
- return "pending";
5183
+ extractToolCalls(message) {
5184
+ const toolCallParts = message.content.filter((part) => part.type === "tool-call");
5185
+ return toolCallParts.map((call, index) => ({
5186
+ id: call.id ?? `call_${index}`,
5187
+ type: "function",
5188
+ index,
5189
+ function: {
5190
+ name: call.name,
5191
+ arguments: call.arguments
5192
+ }
5193
+ }));
5306
5194
  }
5307
5195
  }
5308
- function mapChargeStatus(status) {
5309
- switch (status) {
5310
- case "pending":
5311
- case "processing":
5312
- return "pending";
5313
- case "succeeded":
5314
- return "succeeded";
5315
- case "failed":
5316
- case "canceled":
5317
- return "failed";
5196
+ function mapFinishReason(reason) {
5197
+ if (!reason)
5198
+ return;
5199
+ const normalized = reason.toLowerCase();
5200
+ switch (normalized) {
5201
+ case "stop":
5202
+ return "stop";
5203
+ case "length":
5204
+ return "length";
5205
+ case "tool_call":
5206
+ case "tool_calls":
5207
+ return "tool_call";
5208
+ case "content_filter":
5209
+ return "content_filter";
5318
5210
  default:
5319
- return "pending";
5211
+ return;
5320
5212
  }
5321
5213
  }
5322
5214
 
5323
- // src/impls/postmark-email.ts
5324
- import { ServerClient } from "postmark";
5215
+ // src/impls/notion.ts
5216
+ import { Client } from "@notionhq/client";
5325
5217
 
5326
- class PostmarkEmailProvider {
5218
+ class NotionProjectManagementProvider {
5327
5219
  client;
5328
- defaultFromEmail;
5329
- messageStream;
5220
+ defaults;
5330
5221
  constructor(options) {
5331
- this.client = options.client ?? new ServerClient(options.serverToken, {
5332
- useHttps: true
5222
+ this.client = options.client ?? new Client({ auth: options.apiKey });
5223
+ this.defaults = {
5224
+ databaseId: options.databaseId,
5225
+ summaryParentPageId: options.summaryParentPageId,
5226
+ titleProperty: options.titleProperty,
5227
+ statusProperty: options.statusProperty,
5228
+ priorityProperty: options.priorityProperty,
5229
+ tagsProperty: options.tagsProperty,
5230
+ dueDateProperty: options.dueDateProperty,
5231
+ descriptionProperty: options.descriptionProperty
5232
+ };
5233
+ }
5234
+ async createWorkItem(input) {
5235
+ if (input.type === "summary" && this.defaults.summaryParentPageId) {
5236
+ return this.createSummaryPage(input);
5237
+ }
5238
+ const databaseId = this.defaults.databaseId;
5239
+ if (!databaseId) {
5240
+ throw new Error("Notion databaseId is required to create work items.");
5241
+ }
5242
+ const titleProperty = this.defaults.titleProperty ?? "Name";
5243
+ const properties = {
5244
+ [titleProperty]: buildTitleProperty(input.title)
5245
+ };
5246
+ applySelect(properties, this.defaults.statusProperty, input.status);
5247
+ applySelect(properties, this.defaults.priorityProperty, input.priority);
5248
+ applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
5249
+ applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
5250
+ applyRichText(properties, this.defaults.descriptionProperty, input.description);
5251
+ const page = await this.client.pages.create({
5252
+ parent: { type: "database_id", database_id: databaseId },
5253
+ properties
5333
5254
  });
5334
- this.defaultFromEmail = options.defaultFromEmail;
5335
- this.messageStream = options.messageStream;
5255
+ return {
5256
+ id: page.id,
5257
+ title: input.title,
5258
+ url: "url" in page ? page.url : undefined,
5259
+ status: input.status,
5260
+ priority: input.priority,
5261
+ tags: input.tags,
5262
+ projectId: databaseId,
5263
+ externalId: input.externalId,
5264
+ metadata: input.metadata
5265
+ };
5336
5266
  }
5337
- async sendEmail(message) {
5338
- const request = {
5339
- From: formatAddress2(message.from) ?? this.defaultFromEmail,
5340
- To: message.to.map((addr) => formatAddress2(addr)).join(", "),
5341
- Cc: message.cc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
5342
- Bcc: message.bcc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
5343
- ReplyTo: message.replyTo ? formatAddress2(message.replyTo) : undefined,
5344
- Subject: message.subject,
5345
- TextBody: message.textBody,
5346
- HtmlBody: message.htmlBody,
5347
- Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
5348
- Name: name,
5349
- Value: value
5350
- })) : undefined,
5351
- MessageStream: this.messageStream,
5352
- Attachments: buildAttachments(message)
5267
+ async createWorkItems(items) {
5268
+ const created = [];
5269
+ for (const item of items) {
5270
+ created.push(await this.createWorkItem(item));
5271
+ }
5272
+ return created;
5273
+ }
5274
+ async createSummaryPage(input) {
5275
+ const parentId = this.defaults.summaryParentPageId;
5276
+ if (!parentId) {
5277
+ throw new Error("Notion summaryParentPageId is required for summaries.");
5278
+ }
5279
+ const summaryProperties = {
5280
+ title: buildTitleProperty(input.title)
5353
5281
  };
5354
- const response = await this.client.sendEmail(request);
5282
+ const page = await this.client.pages.create({
5283
+ parent: { type: "page_id", page_id: parentId },
5284
+ properties: summaryProperties
5285
+ });
5286
+ if (input.description) {
5287
+ const children = buildParagraphBlocks(input.description);
5288
+ if (children.length > 0) {
5289
+ await this.client.blocks.children.append({
5290
+ block_id: page.id,
5291
+ children
5292
+ });
5293
+ }
5294
+ }
5355
5295
  return {
5356
- id: response.MessageID,
5357
- providerMessageId: response.MessageID,
5358
- queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
5296
+ id: page.id,
5297
+ title: input.title,
5298
+ url: "url" in page ? page.url : undefined,
5299
+ status: input.status,
5300
+ priority: input.priority,
5301
+ tags: input.tags,
5302
+ projectId: parentId,
5303
+ externalId: input.externalId,
5304
+ metadata: input.metadata
5359
5305
  };
5360
5306
  }
5361
5307
  }
5362
- function formatAddress2(address) {
5363
- if (address.name) {
5364
- return `"${address.name}" <${address.email}>`;
5365
- }
5366
- return address.email;
5308
+ function buildTitleProperty(title) {
5309
+ return {
5310
+ title: [
5311
+ {
5312
+ type: "text",
5313
+ text: { content: title }
5314
+ }
5315
+ ]
5316
+ };
5367
5317
  }
5368
- function buildAttachments(message) {
5369
- if (!message.attachments?.length)
5318
+ function buildRichText(text) {
5319
+ return {
5320
+ rich_text: [
5321
+ {
5322
+ type: "text",
5323
+ text: { content: text }
5324
+ }
5325
+ ]
5326
+ };
5327
+ }
5328
+ function applySelect(properties, property, value) {
5329
+ if (!property || !value)
5370
5330
  return;
5371
- return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
5372
- Name: attachment.filename,
5373
- Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
5374
- ContentType: attachment.contentType,
5375
- ContentID: null,
5376
- ContentLength: attachment.sizeBytes,
5377
- Disposition: "attachment"
5331
+ const next = {
5332
+ select: { name: value }
5333
+ };
5334
+ properties[property] = next;
5335
+ }
5336
+ function applyMultiSelect(properties, property, values) {
5337
+ if (!property || !values || values.length === 0)
5338
+ return;
5339
+ const next = {
5340
+ multi_select: values.map((value) => ({ name: value }))
5341
+ };
5342
+ properties[property] = next;
5343
+ }
5344
+ function applyDate(properties, property, value) {
5345
+ if (!property || !value)
5346
+ return;
5347
+ const next = {
5348
+ date: { start: value.toISOString() }
5349
+ };
5350
+ properties[property] = next;
5351
+ }
5352
+ function applyRichText(properties, property, value) {
5353
+ if (!property || !value)
5354
+ return;
5355
+ properties[property] = buildRichText(value);
5356
+ }
5357
+ function buildParagraphBlocks(text) {
5358
+ const lines = text.split(/\r?\n/).filter((line) => line.trim());
5359
+ return lines.map((line) => ({
5360
+ object: "block",
5361
+ type: "paragraph",
5362
+ paragraph: {
5363
+ rich_text: [
5364
+ {
5365
+ type: "text",
5366
+ text: { content: line }
5367
+ }
5368
+ ]
5369
+ }
5378
5370
  }));
5379
5371
  }
5380
5372
 
@@ -5465,612 +5457,295 @@ class PosthogAnalyticsReader {
5465
5457
  offset: input.offset
5466
5458
  }
5467
5459
  });
5468
- return response.data;
5469
- }
5470
- async getFeatureFlags(input) {
5471
- const projectId = resolveProjectId(input.projectId, this.projectId);
5472
- const response = await this.client.request({
5473
- method: "GET",
5474
- path: `/api/projects/${projectId}/feature_flags/`,
5475
- query: {
5476
- active: input.active,
5477
- limit: input.limit,
5478
- offset: input.offset
5479
- }
5480
- });
5481
- return response.data;
5482
- }
5483
- async getAnnotations(input) {
5484
- const projectId = resolveProjectId(input.projectId, this.projectId);
5485
- const response = await this.client.request({
5486
- method: "GET",
5487
- path: `/api/projects/${projectId}/annotations/`,
5488
- query: {
5489
- limit: input.limit,
5490
- offset: input.offset,
5491
- ...buildAnnotationDateQuery(input.dateRange)
5492
- }
5493
- });
5494
- return response.data;
5495
- }
5496
- }
5497
- function resolveProjectId(inputProjectId, defaultProjectId) {
5498
- const projectId = inputProjectId ?? defaultProjectId;
5499
- if (!projectId) {
5500
- throw new Error("PostHog projectId is required for API reads.");
5501
- }
5502
- return projectId;
5503
- }
5504
- function resolveSingleEvent(events) {
5505
- if (!events || events.length !== 1)
5506
- return;
5507
- return events[0];
5508
- }
5509
- function resolveEventList(events) {
5510
- if (!events || events.length <= 1)
5511
- return;
5512
- return events.join(",");
5513
- }
5514
- function buildEventDateQuery(range) {
5515
- const after = formatDate(range?.from);
5516
- const before = formatDate(range?.to);
5517
- return {
5518
- after,
5519
- before,
5520
- timezone: range?.timezone
5521
- };
5522
- }
5523
- function buildAnnotationDateQuery(range) {
5524
- const dateFrom = formatDate(range?.from);
5525
- const dateTo = formatDate(range?.to);
5526
- return {
5527
- date_from: dateFrom,
5528
- date_to: dateTo,
5529
- timezone: range?.timezone
5530
- };
5531
- }
5532
- function formatDate(value) {
5533
- if (!value)
5534
- return;
5535
- return typeof value === "string" ? value : value.toISOString();
5536
- }
5537
-
5538
- // src/impls/posthog-utils.ts
5539
- function normalizeHost(host) {
5540
- return host.replace(/\/$/, "");
5541
- }
5542
- function buildUrl(host, path, query) {
5543
- if (/^https?:\/\//.test(path)) {
5544
- return appendQuery(path, query);
5545
- }
5546
- const normalizedPath = path.replace(/^\/+/, "");
5547
- return appendQuery(`${host}/${normalizedPath}`, query);
5548
- }
5549
- function appendQuery(url, query) {
5550
- if (!query)
5551
- return url;
5552
- const params = new URLSearchParams;
5553
- Object.entries(query).forEach(([key, value]) => {
5554
- if (value === undefined)
5555
- return;
5556
- params.set(key, String(value));
5557
- });
5558
- const suffix = params.toString();
5559
- return suffix ? `${url}?${suffix}` : url;
5560
- }
5561
- function parseJson(value) {
5562
- if (!value)
5563
- return {};
5564
- try {
5565
- return JSON.parse(value);
5566
- } catch {
5567
- return value;
5568
- }
5569
- }
5570
-
5571
- // src/impls/posthog.ts
5572
- import { PostHog } from "posthog-node";
5573
- var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
5574
-
5575
- class PosthogAnalyticsProvider {
5576
- host;
5577
- projectId;
5578
- projectApiKey;
5579
- personalApiKey;
5580
- mcpUrl;
5581
- fetchFn;
5582
- client;
5583
- reader;
5584
- constructor(options) {
5585
- this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
5586
- this.projectId = options.projectId;
5587
- this.projectApiKey = options.projectApiKey;
5588
- this.personalApiKey = options.personalApiKey;
5589
- this.mcpUrl = options.mcpUrl;
5590
- this.fetchFn = options.fetch ?? fetch;
5591
- this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
5592
- host: this.host,
5593
- requestTimeout: options.requestTimeoutMs ?? 1e4
5594
- }) : undefined);
5595
- this.reader = new PosthogAnalyticsReader({
5596
- projectId: this.projectId,
5597
- client: { request: this.request.bind(this) }
5598
- });
5599
- }
5600
- async capture(event) {
5601
- if (!this.client) {
5602
- throw new Error("PostHog projectApiKey is required for capture.");
5603
- }
5604
- await this.client.capture({
5605
- distinctId: event.distinctId,
5606
- event: event.event,
5607
- properties: event.properties,
5608
- timestamp: event.timestamp,
5609
- groups: event.groups
5610
- });
5611
- }
5612
- async identify(input) {
5613
- if (!this.client) {
5614
- throw new Error("PostHog projectApiKey is required for identify.");
5615
- }
5616
- await this.client.identify({
5617
- distinctId: input.distinctId,
5618
- properties: {
5619
- ...input.properties ? { $set: input.properties } : {},
5620
- ...input.setOnce ? { $set_once: input.setOnce } : {}
5621
- }
5622
- });
5623
- }
5624
- async queryHogQL(input) {
5625
- return this.reader.queryHogQL(input);
5626
- }
5627
- async getEvents(input) {
5628
- return this.reader.getEvents(input);
5629
- }
5630
- async getPersons(input) {
5631
- return this.reader.getPersons(input);
5632
- }
5633
- async getInsights(input) {
5634
- return this.reader.getInsights(input);
5635
- }
5636
- async getInsightResult(input) {
5637
- return this.reader.getInsightResult(input);
5638
- }
5639
- async getCohorts(input) {
5640
- return this.reader.getCohorts(input);
5641
- }
5642
- async getFeatureFlags(input) {
5643
- return this.reader.getFeatureFlags(input);
5644
- }
5645
- async getAnnotations(input) {
5646
- return this.reader.getAnnotations(input);
5647
- }
5648
- async request(request) {
5649
- if (!this.personalApiKey) {
5650
- throw new Error("PostHog personalApiKey is required for API requests.");
5651
- }
5652
- const url = buildUrl(this.host, request.path, request.query);
5653
- const response = await this.fetchFn(url, {
5654
- method: request.method,
5655
- headers: {
5656
- Authorization: `Bearer ${this.personalApiKey}`,
5657
- "Content-Type": "application/json",
5658
- ...request.headers ?? {}
5659
- },
5660
- body: request.body ? JSON.stringify(request.body) : undefined
5661
- });
5662
- const text = await response.text();
5663
- const data = parseJson(text);
5664
- return {
5665
- status: response.status,
5666
- data,
5667
- headers: Object.fromEntries(response.headers.entries())
5668
- };
5669
- }
5670
- async callMcpTool(call) {
5671
- if (!this.mcpUrl) {
5672
- throw new Error("PostHog MCP URL is not configured.");
5673
- }
5674
- const response = await this.fetchFn(this.mcpUrl, {
5675
- method: "POST",
5676
- headers: {
5677
- "Content-Type": "application/json"
5678
- },
5679
- body: JSON.stringify({
5680
- jsonrpc: "2.0",
5681
- id: 1,
5682
- method: "tools/call",
5683
- params: {
5684
- name: call.name,
5685
- arguments: call.arguments ?? {}
5686
- }
5687
- })
5688
- });
5689
- if (!response.ok) {
5690
- const body = await response.text();
5691
- throw new Error(`PostHog MCP error (${response.status}): ${body}`);
5692
- }
5693
- const result = await response.json();
5694
- if (result.error) {
5695
- throw new Error(result.error.message ?? "PostHog MCP error");
5696
- }
5697
- return result.result ?? null;
5698
- }
5699
- }
5700
-
5701
- // src/impls/twilio-sms.ts
5702
- import Twilio from "twilio";
5703
-
5704
- class TwilioSmsProvider {
5705
- client;
5706
- fromNumber;
5707
- constructor(options) {
5708
- this.client = options.client ?? Twilio(options.accountSid, options.authToken);
5709
- this.fromNumber = options.fromNumber;
5460
+ return response.data;
5710
5461
  }
5711
- async sendSms(input) {
5712
- const message = await this.client.messages.create({
5713
- to: input.to,
5714
- from: input.from ?? this.fromNumber,
5715
- body: input.body
5462
+ async getFeatureFlags(input) {
5463
+ const projectId = resolveProjectId(input.projectId, this.projectId);
5464
+ const response = await this.client.request({
5465
+ method: "GET",
5466
+ path: `/api/projects/${projectId}/feature_flags/`,
5467
+ query: {
5468
+ active: input.active,
5469
+ limit: input.limit,
5470
+ offset: input.offset
5471
+ }
5716
5472
  });
5717
- return {
5718
- id: message.sid,
5719
- to: message.to ?? input.to,
5720
- from: message.from ?? input.from ?? this.fromNumber ?? "",
5721
- body: message.body ?? input.body,
5722
- status: mapStatus(message.status),
5723
- sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
5724
- deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
5725
- price: message.price ? Number(message.price) : undefined,
5726
- priceCurrency: message.priceUnit ?? undefined,
5727
- errorCode: message.errorCode ? String(message.errorCode) : undefined,
5728
- errorMessage: message.errorMessage ?? undefined
5729
- };
5473
+ return response.data;
5730
5474
  }
5731
- async getDeliveryStatus(messageId) {
5732
- const message = await this.client.messages(messageId).fetch();
5733
- return {
5734
- status: mapStatus(message.status),
5735
- errorCode: message.errorCode ? String(message.errorCode) : undefined,
5736
- errorMessage: message.errorMessage ?? undefined,
5737
- updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
5738
- };
5475
+ async getAnnotations(input) {
5476
+ const projectId = resolveProjectId(input.projectId, this.projectId);
5477
+ const response = await this.client.request({
5478
+ method: "GET",
5479
+ path: `/api/projects/${projectId}/annotations/`,
5480
+ query: {
5481
+ limit: input.limit,
5482
+ offset: input.offset,
5483
+ ...buildAnnotationDateQuery(input.dateRange)
5484
+ }
5485
+ });
5486
+ return response.data;
5739
5487
  }
5740
5488
  }
5741
- function mapStatus(status) {
5742
- switch (status) {
5743
- case "queued":
5744
- case "accepted":
5745
- case "scheduled":
5746
- return "queued";
5747
- case "sending":
5748
- case "processing":
5749
- return "sending";
5750
- case "sent":
5751
- return "sent";
5752
- case "delivered":
5753
- return "delivered";
5754
- case "undelivered":
5755
- return "undelivered";
5756
- case "failed":
5757
- case "canceled":
5758
- return "failed";
5759
- default:
5760
- return "queued";
5489
+ function resolveProjectId(inputProjectId, defaultProjectId) {
5490
+ const projectId = inputProjectId ?? defaultProjectId;
5491
+ if (!projectId) {
5492
+ throw new Error("PostHog projectId is required for API reads.");
5761
5493
  }
5494
+ return projectId;
5495
+ }
5496
+ function resolveSingleEvent(events) {
5497
+ if (!events || events.length !== 1)
5498
+ return;
5499
+ return events[0];
5500
+ }
5501
+ function resolveEventList(events) {
5502
+ if (!events || events.length <= 1)
5503
+ return;
5504
+ return events.join(",");
5505
+ }
5506
+ function buildEventDateQuery(range) {
5507
+ const after = formatDate(range?.from);
5508
+ const before = formatDate(range?.to);
5509
+ return {
5510
+ after,
5511
+ before,
5512
+ timezone: range?.timezone
5513
+ };
5514
+ }
5515
+ function buildAnnotationDateQuery(range) {
5516
+ const dateFrom = formatDate(range?.from);
5517
+ const dateTo = formatDate(range?.to);
5518
+ return {
5519
+ date_from: dateFrom,
5520
+ date_to: dateTo,
5521
+ timezone: range?.timezone
5522
+ };
5523
+ }
5524
+ function formatDate(value) {
5525
+ if (!value)
5526
+ return;
5527
+ return typeof value === "string" ? value : value.toISOString();
5762
5528
  }
5763
5529
 
5764
- // src/impls/messaging-slack.ts
5765
- class SlackMessagingProvider {
5766
- botToken;
5767
- defaultChannelId;
5768
- apiBaseUrl;
5769
- constructor(options) {
5770
- this.botToken = options.botToken;
5771
- this.defaultChannelId = options.defaultChannelId;
5772
- this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
5773
- }
5774
- async sendMessage(input) {
5775
- const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
5776
- if (!channel) {
5777
- throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
5778
- }
5779
- const payload = {
5780
- channel,
5781
- text: input.text,
5782
- mrkdwn: input.markdown ?? true,
5783
- thread_ts: input.threadId
5784
- };
5785
- const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
5786
- method: "POST",
5787
- headers: {
5788
- authorization: `Bearer ${this.botToken}`,
5789
- "content-type": "application/json"
5790
- },
5791
- body: JSON.stringify(payload)
5792
- });
5793
- const body = await response.json();
5794
- if (!response.ok || !body.ok || !body.ts) {
5795
- throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
5796
- }
5797
- return {
5798
- id: `slack:${body.channel ?? channel}:${body.ts}`,
5799
- providerMessageId: body.ts,
5800
- status: "sent",
5801
- sentAt: new Date,
5802
- metadata: {
5803
- channelId: body.channel ?? channel
5804
- }
5805
- };
5530
+ // src/impls/posthog-utils.ts
5531
+ function normalizeHost(host) {
5532
+ return host.replace(/\/$/, "");
5533
+ }
5534
+ function buildUrl(host, path, query) {
5535
+ if (/^https?:\/\//.test(path)) {
5536
+ return appendQuery(path, query);
5806
5537
  }
5807
- async updateMessage(messageId, input) {
5808
- const channel = input.channelId ?? this.defaultChannelId;
5809
- if (!channel) {
5810
- throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
5811
- }
5812
- const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
5813
- method: "POST",
5814
- headers: {
5815
- authorization: `Bearer ${this.botToken}`,
5816
- "content-type": "application/json"
5817
- },
5818
- body: JSON.stringify({
5819
- channel,
5820
- ts: messageId,
5821
- text: input.text,
5822
- mrkdwn: input.markdown ?? true
5823
- })
5824
- });
5825
- const body = await response.json();
5826
- if (!response.ok || !body.ok || !body.ts) {
5827
- throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
5828
- }
5829
- return {
5830
- id: `slack:${body.channel ?? channel}:${body.ts}`,
5831
- providerMessageId: body.ts,
5832
- status: "sent",
5833
- sentAt: new Date,
5834
- metadata: {
5835
- channelId: body.channel ?? channel
5836
- }
5837
- };
5538
+ const normalizedPath = path.replace(/^\/+/, "");
5539
+ return appendQuery(`${host}/${normalizedPath}`, query);
5540
+ }
5541
+ function appendQuery(url, query) {
5542
+ if (!query)
5543
+ return url;
5544
+ const params = new URLSearchParams;
5545
+ Object.entries(query).forEach(([key, value]) => {
5546
+ if (value === undefined)
5547
+ return;
5548
+ params.set(key, String(value));
5549
+ });
5550
+ const suffix = params.toString();
5551
+ return suffix ? `${url}?${suffix}` : url;
5552
+ }
5553
+ function parseJson(value) {
5554
+ if (!value)
5555
+ return {};
5556
+ try {
5557
+ return JSON.parse(value);
5558
+ } catch {
5559
+ return value;
5838
5560
  }
5839
5561
  }
5840
5562
 
5841
- // src/impls/messaging-github.ts
5842
- class GithubMessagingProvider {
5843
- token;
5844
- defaultOwner;
5845
- defaultRepo;
5846
- apiBaseUrl;
5563
+ // src/impls/posthog.ts
5564
+ import { PostHog } from "posthog-node";
5565
+ var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
5566
+
5567
+ class PosthogAnalyticsProvider {
5568
+ host;
5569
+ projectId;
5570
+ projectApiKey;
5571
+ personalApiKey;
5572
+ mcpUrl;
5573
+ fetchFn;
5574
+ client;
5575
+ reader;
5847
5576
  constructor(options) {
5848
- this.token = options.token;
5849
- this.defaultOwner = options.defaultOwner;
5850
- this.defaultRepo = options.defaultRepo;
5851
- this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
5852
- }
5853
- async sendMessage(input) {
5854
- const target = this.resolveTarget(input);
5855
- const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
5856
- method: "POST",
5857
- headers: {
5858
- authorization: `Bearer ${this.token}`,
5859
- accept: "application/vnd.github+json",
5860
- "content-type": "application/json"
5861
- },
5862
- body: JSON.stringify({ body: input.text })
5577
+ this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
5578
+ this.projectId = options.projectId;
5579
+ this.projectApiKey = options.projectApiKey;
5580
+ this.personalApiKey = options.personalApiKey;
5581
+ this.mcpUrl = options.mcpUrl;
5582
+ this.fetchFn = options.fetch ?? fetch;
5583
+ this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
5584
+ host: this.host,
5585
+ requestTimeout: options.requestTimeoutMs ?? 1e4
5586
+ }) : undefined);
5587
+ this.reader = new PosthogAnalyticsReader({
5588
+ projectId: this.projectId,
5589
+ client: { request: this.request.bind(this) }
5863
5590
  });
5864
- const body = await response.json();
5865
- if (!response.ok || !body.id) {
5866
- throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
5867
- }
5868
- return {
5869
- id: String(body.id),
5870
- providerMessageId: body.node_id,
5871
- status: "sent",
5872
- sentAt: new Date,
5873
- metadata: {
5874
- url: body.html_url ?? "",
5875
- owner: target.owner,
5876
- repo: target.repo,
5877
- issueNumber: String(target.issueNumber)
5878
- }
5879
- };
5880
5591
  }
5881
- async updateMessage(messageId, input) {
5882
- const owner = input.metadata?.owner ?? this.defaultOwner;
5883
- const repo = input.metadata?.repo ?? this.defaultRepo;
5884
- if (!owner || !repo) {
5885
- throw new Error("GitHub updateMessage requires owner and repo metadata.");
5592
+ async capture(event) {
5593
+ if (!this.client) {
5594
+ throw new Error("PostHog projectApiKey is required for capture.");
5886
5595
  }
5887
- const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
5888
- method: "PATCH",
5889
- headers: {
5890
- authorization: `Bearer ${this.token}`,
5891
- accept: "application/vnd.github+json",
5892
- "content-type": "application/json"
5893
- },
5894
- body: JSON.stringify({ body: input.text })
5596
+ await this.client.capture({
5597
+ distinctId: event.distinctId,
5598
+ event: event.event,
5599
+ properties: event.properties,
5600
+ timestamp: event.timestamp,
5601
+ groups: event.groups
5895
5602
  });
5896
- const body = await response.json();
5897
- if (!response.ok || !body.id) {
5898
- throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
5603
+ }
5604
+ async identify(input) {
5605
+ if (!this.client) {
5606
+ throw new Error("PostHog projectApiKey is required for identify.");
5899
5607
  }
5900
- return {
5901
- id: String(body.id),
5902
- providerMessageId: body.node_id,
5903
- status: "sent",
5904
- sentAt: new Date,
5905
- metadata: {
5906
- url: body.html_url ?? "",
5907
- owner,
5908
- repo
5608
+ await this.client.identify({
5609
+ distinctId: input.distinctId,
5610
+ properties: {
5611
+ ...input.properties ? { $set: input.properties } : {},
5612
+ ...input.setOnce ? { $set_once: input.setOnce } : {}
5909
5613
  }
5910
- };
5614
+ });
5911
5615
  }
5912
- resolveTarget(input) {
5913
- const parsedRecipient = parseRecipient(input.recipientId);
5914
- const owner = parsedRecipient?.owner ?? this.defaultOwner;
5915
- const repo = parsedRecipient?.repo ?? this.defaultRepo;
5916
- const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
5917
- if (!owner || !repo || issueNumber == null) {
5918
- throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
5616
+ async queryHogQL(input) {
5617
+ return this.reader.queryHogQL(input);
5618
+ }
5619
+ async getEvents(input) {
5620
+ return this.reader.getEvents(input);
5621
+ }
5622
+ async getPersons(input) {
5623
+ return this.reader.getPersons(input);
5624
+ }
5625
+ async getInsights(input) {
5626
+ return this.reader.getInsights(input);
5627
+ }
5628
+ async getInsightResult(input) {
5629
+ return this.reader.getInsightResult(input);
5630
+ }
5631
+ async getCohorts(input) {
5632
+ return this.reader.getCohorts(input);
5633
+ }
5634
+ async getFeatureFlags(input) {
5635
+ return this.reader.getFeatureFlags(input);
5636
+ }
5637
+ async getAnnotations(input) {
5638
+ return this.reader.getAnnotations(input);
5639
+ }
5640
+ async request(request) {
5641
+ if (!this.personalApiKey) {
5642
+ throw new Error("PostHog personalApiKey is required for API requests.");
5919
5643
  }
5644
+ const url = buildUrl(this.host, request.path, request.query);
5645
+ const response = await this.fetchFn(url, {
5646
+ method: request.method,
5647
+ headers: {
5648
+ Authorization: `Bearer ${this.personalApiKey}`,
5649
+ "Content-Type": "application/json",
5650
+ ...request.headers ?? {}
5651
+ },
5652
+ body: request.body ? JSON.stringify(request.body) : undefined
5653
+ });
5654
+ const text = await response.text();
5655
+ const data = parseJson(text);
5920
5656
  return {
5921
- owner,
5922
- repo,
5923
- issueNumber
5657
+ status: response.status,
5658
+ data,
5659
+ headers: Object.fromEntries(response.headers.entries())
5924
5660
  };
5925
5661
  }
5926
- }
5927
- function parseRecipient(value) {
5928
- if (!value)
5929
- return null;
5930
- const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
5931
- if (!match)
5932
- return null;
5933
- const owner = match[1];
5934
- const repo = match[2];
5935
- const issueNumber = Number(match[3]);
5936
- if (!owner || !repo || !Number.isInteger(issueNumber)) {
5937
- return null;
5938
- }
5939
- return { owner, repo, issueNumber };
5940
- }
5941
- function parseIssueNumber(value) {
5942
- if (!value)
5943
- return null;
5944
- const numeric = Number(value);
5945
- return Number.isInteger(numeric) ? numeric : null;
5946
- }
5947
-
5948
- // src/impls/messaging-whatsapp-meta.ts
5949
- class MetaWhatsappMessagingProvider {
5950
- accessToken;
5951
- phoneNumberId;
5952
- apiVersion;
5953
- constructor(options) {
5954
- this.accessToken = options.accessToken;
5955
- this.phoneNumberId = options.phoneNumberId;
5956
- this.apiVersion = options.apiVersion ?? "v22.0";
5957
- }
5958
- async sendMessage(input) {
5959
- const to = input.recipientId;
5960
- if (!to) {
5961
- throw new Error("Meta WhatsApp sendMessage requires recipientId.");
5662
+ async callMcpTool(call) {
5663
+ if (!this.mcpUrl) {
5664
+ throw new Error("PostHog MCP URL is not configured.");
5962
5665
  }
5963
- const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
5666
+ const response = await this.fetchFn(this.mcpUrl, {
5964
5667
  method: "POST",
5965
5668
  headers: {
5966
- authorization: `Bearer ${this.accessToken}`,
5967
- "content-type": "application/json"
5669
+ "Content-Type": "application/json"
5968
5670
  },
5969
5671
  body: JSON.stringify({
5970
- messaging_product: "whatsapp",
5971
- to,
5972
- type: "text",
5973
- text: {
5974
- body: input.text,
5975
- preview_url: false
5672
+ jsonrpc: "2.0",
5673
+ id: 1,
5674
+ method: "tools/call",
5675
+ params: {
5676
+ name: call.name,
5677
+ arguments: call.arguments ?? {}
5976
5678
  }
5977
5679
  })
5978
5680
  });
5979
- const body = await response.json();
5980
- const messageId = body.messages?.[0]?.id;
5981
- if (!response.ok || !messageId) {
5982
- const errorCode = body.error?.code != null ? String(body.error.code) : "";
5983
- throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
5681
+ if (!response.ok) {
5682
+ const body = await response.text();
5683
+ throw new Error(`PostHog MCP error (${response.status}): ${body}`);
5984
5684
  }
5985
- return {
5986
- id: messageId,
5987
- providerMessageId: messageId,
5988
- status: "sent",
5989
- sentAt: new Date,
5990
- metadata: {
5991
- phoneNumberId: this.phoneNumberId
5992
- }
5993
- };
5685
+ const result = await response.json();
5686
+ if (result.error) {
5687
+ throw new Error(result.error.message ?? "PostHog MCP error");
5688
+ }
5689
+ return result.result ?? null;
5994
5690
  }
5995
5691
  }
5996
5692
 
5997
- // src/impls/messaging-whatsapp-twilio.ts
5998
- import { Buffer as Buffer4 } from "buffer";
5693
+ // src/impls/postmark-email.ts
5694
+ import { ServerClient } from "postmark";
5999
5695
 
6000
- class TwilioWhatsappMessagingProvider {
6001
- accountSid;
6002
- authToken;
6003
- fromNumber;
5696
+ class PostmarkEmailProvider {
5697
+ client;
5698
+ defaultFromEmail;
5699
+ messageStream;
6004
5700
  constructor(options) {
6005
- this.accountSid = options.accountSid;
6006
- this.authToken = options.authToken;
6007
- this.fromNumber = options.fromNumber;
6008
- }
6009
- async sendMessage(input) {
6010
- const to = normalizeWhatsappAddress(input.recipientId);
6011
- const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
6012
- if (!to) {
6013
- throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
6014
- }
6015
- if (!from) {
6016
- throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
6017
- }
6018
- const params = new URLSearchParams;
6019
- params.set("To", to);
6020
- params.set("From", from);
6021
- params.set("Body", input.text);
6022
- const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
6023
- const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
6024
- method: "POST",
6025
- headers: {
6026
- authorization: `Basic ${auth}`,
6027
- "content-type": "application/x-www-form-urlencoded"
6028
- },
6029
- body: params.toString()
5701
+ this.client = options.client ?? new ServerClient(options.serverToken, {
5702
+ useHttps: true
6030
5703
  });
6031
- const body = await response.json();
6032
- if (!response.ok || !body.sid) {
6033
- throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
6034
- }
5704
+ this.defaultFromEmail = options.defaultFromEmail;
5705
+ this.messageStream = options.messageStream;
5706
+ }
5707
+ async sendEmail(message) {
5708
+ const request = {
5709
+ From: formatAddress2(message.from) ?? this.defaultFromEmail,
5710
+ To: message.to.map((addr) => formatAddress2(addr)).join(", "),
5711
+ Cc: message.cc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
5712
+ Bcc: message.bcc?.map((addr) => formatAddress2(addr)).join(", ") || undefined,
5713
+ ReplyTo: message.replyTo ? formatAddress2(message.replyTo) : undefined,
5714
+ Subject: message.subject,
5715
+ TextBody: message.textBody,
5716
+ HtmlBody: message.htmlBody,
5717
+ Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
5718
+ Name: name,
5719
+ Value: value
5720
+ })) : undefined,
5721
+ MessageStream: this.messageStream,
5722
+ Attachments: buildAttachments(message)
5723
+ };
5724
+ const response = await this.client.sendEmail(request);
6035
5725
  return {
6036
- id: body.sid,
6037
- providerMessageId: body.sid,
6038
- status: mapTwilioStatus(body.status),
6039
- sentAt: new Date,
6040
- errorCode: body.error_code != null ? String(body.error_code) : undefined,
6041
- errorMessage: body.error_message ?? undefined,
6042
- metadata: {
6043
- from,
6044
- to
6045
- }
5726
+ id: response.MessageID,
5727
+ providerMessageId: response.MessageID,
5728
+ queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
6046
5729
  };
6047
5730
  }
6048
5731
  }
6049
- function normalizeWhatsappAddress(value) {
6050
- if (!value)
6051
- return null;
6052
- if (value.startsWith("whatsapp:"))
6053
- return value;
6054
- return `whatsapp:${value}`;
6055
- }
6056
- function mapTwilioStatus(status) {
6057
- switch (status) {
6058
- case "queued":
6059
- case "accepted":
6060
- case "scheduled":
6061
- return "queued";
6062
- case "sending":
6063
- return "sending";
6064
- case "delivered":
6065
- return "delivered";
6066
- case "failed":
6067
- case "undelivered":
6068
- case "canceled":
6069
- return "failed";
6070
- case "sent":
6071
- default:
6072
- return "sent";
5732
+ function formatAddress2(address) {
5733
+ if (address.name) {
5734
+ return `"${address.name}" <${address.email}>`;
6073
5735
  }
5736
+ return address.email;
5737
+ }
5738
+ function buildAttachments(message) {
5739
+ if (!message.attachments?.length)
5740
+ return;
5741
+ return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
5742
+ Name: attachment.filename,
5743
+ Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
5744
+ ContentType: attachment.contentType,
5745
+ ContentID: null,
5746
+ ContentLength: attachment.sizeBytes,
5747
+ Disposition: "attachment"
5748
+ }));
6074
5749
  }
6075
5750
 
6076
5751
  // src/impls/powens-client.ts
@@ -6495,367 +6170,629 @@ class PowensOpenBankingProvider {
6495
6170
  this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
6496
6171
  throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
6497
6172
  }
6498
- }
6499
-
6500
- // src/impls/linear.ts
6501
- import { LinearClient } from "@linear/sdk";
6502
- var PRIORITY_MAP = {
6503
- urgent: 1,
6504
- high: 2,
6505
- medium: 3,
6506
- low: 4,
6507
- none: 0
6508
- };
6509
-
6510
- class LinearProjectManagementProvider {
6511
- client;
6512
- defaults;
6513
- constructor(options) {
6514
- this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
6515
- this.defaults = {
6516
- teamId: options.teamId,
6517
- projectId: options.projectId,
6518
- assigneeId: options.assigneeId,
6519
- stateId: options.stateId,
6520
- labelIds: options.labelIds,
6521
- tagLabelMap: options.tagLabelMap
6173
+ }
6174
+
6175
+ // src/impls/qdrant-vector.ts
6176
+ import { QdrantClient } from "@qdrant/js-client-rest";
6177
+
6178
+ class QdrantVectorProvider {
6179
+ client;
6180
+ createCollectionIfMissing;
6181
+ distance;
6182
+ constructor(options) {
6183
+ this.client = options.client ?? new QdrantClient({
6184
+ url: options.url,
6185
+ apiKey: options.apiKey,
6186
+ ...options.clientParams
6187
+ });
6188
+ this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
6189
+ this.distance = options.distance ?? "Cosine";
6190
+ }
6191
+ async upsert(request) {
6192
+ if (request.documents.length === 0)
6193
+ return;
6194
+ const firstDocument = request.documents[0];
6195
+ if (!firstDocument)
6196
+ return;
6197
+ const vectorSize = firstDocument.vector.length;
6198
+ if (this.createCollectionIfMissing) {
6199
+ await this.ensureCollection(request.collection, vectorSize);
6200
+ }
6201
+ const points = request.documents.map((document) => ({
6202
+ id: document.id,
6203
+ vector: document.vector,
6204
+ payload: {
6205
+ ...document.payload,
6206
+ ...document.namespace ? { namespace: document.namespace } : {},
6207
+ ...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
6208
+ }
6209
+ }));
6210
+ await this.client.upsert(request.collection, {
6211
+ wait: true,
6212
+ points
6213
+ });
6214
+ }
6215
+ async search(query) {
6216
+ const results = await this.client.search(query.collection, {
6217
+ vector: query.vector,
6218
+ limit: query.topK,
6219
+ filter: query.filter,
6220
+ score_threshold: query.scoreThreshold,
6221
+ with_payload: true,
6222
+ with_vector: false
6223
+ });
6224
+ return results.map((item) => ({
6225
+ id: String(item.id),
6226
+ score: item.score,
6227
+ payload: item.payload ?? undefined,
6228
+ namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
6229
+ }));
6230
+ }
6231
+ async delete(request) {
6232
+ await this.client.delete(request.collection, {
6233
+ wait: true,
6234
+ points: request.ids
6235
+ });
6236
+ }
6237
+ async ensureCollection(collectionName, vectorSize) {
6238
+ try {
6239
+ await this.client.getCollection(collectionName);
6240
+ } catch (_error) {
6241
+ await this.client.createCollection(collectionName, {
6242
+ vectors: {
6243
+ size: vectorSize,
6244
+ distance: this.distance
6245
+ }
6246
+ });
6247
+ }
6248
+ }
6249
+ }
6250
+
6251
+ // src/impls/stripe-payments.ts
6252
+ import Stripe from "stripe";
6253
+ var API_VERSION = "2026-02-25.clover";
6254
+
6255
+ class StripePaymentsProvider {
6256
+ stripe;
6257
+ constructor(options) {
6258
+ this.stripe = options.stripe ?? new Stripe(options.apiKey, {
6259
+ apiVersion: API_VERSION
6260
+ });
6261
+ }
6262
+ async createCustomer(input) {
6263
+ const customer = await this.stripe.customers.create({
6264
+ email: input.email,
6265
+ name: input.name,
6266
+ description: input.description,
6267
+ metadata: input.metadata
6268
+ });
6269
+ return this.toCustomer(customer);
6270
+ }
6271
+ async getCustomer(customerId) {
6272
+ const customer = await this.stripe.customers.retrieve(customerId);
6273
+ if (customer.deleted)
6274
+ return null;
6275
+ return this.toCustomer(customer);
6276
+ }
6277
+ async createPaymentIntent(input) {
6278
+ const intent = await this.stripe.paymentIntents.create({
6279
+ amount: input.amount.amount,
6280
+ currency: input.amount.currency,
6281
+ customer: input.customerId,
6282
+ description: input.description,
6283
+ capture_method: input.captureMethod ?? "automatic",
6284
+ confirmation_method: input.confirmationMethod ?? "automatic",
6285
+ automatic_payment_methods: { enabled: true },
6286
+ metadata: input.metadata,
6287
+ return_url: input.returnUrl,
6288
+ statement_descriptor: input.statementDescriptor
6289
+ });
6290
+ return this.toPaymentIntent(intent);
6291
+ }
6292
+ async capturePayment(paymentIntentId, input) {
6293
+ const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
6294
+ return this.toPaymentIntent(intent);
6295
+ }
6296
+ async cancelPaymentIntent(paymentIntentId) {
6297
+ const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
6298
+ return this.toPaymentIntent(intent);
6299
+ }
6300
+ async refundPayment(input) {
6301
+ const refund = await this.stripe.refunds.create({
6302
+ payment_intent: input.paymentIntentId,
6303
+ amount: input.amount?.amount,
6304
+ reason: mapRefundReason(input.reason),
6305
+ metadata: input.metadata
6306
+ });
6307
+ const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
6308
+ return {
6309
+ id: refund.id,
6310
+ paymentIntentId,
6311
+ amount: {
6312
+ amount: refund.amount ?? 0,
6313
+ currency: refund.currency?.toUpperCase() ?? "USD"
6314
+ },
6315
+ status: mapRefundStatus(refund.status),
6316
+ reason: refund.reason ?? undefined,
6317
+ metadata: this.toMetadata(refund.metadata),
6318
+ createdAt: refund.created ? new Date(refund.created * 1000) : undefined
6319
+ };
6320
+ }
6321
+ async listInvoices(query) {
6322
+ const requestedStatus = query?.status?.[0];
6323
+ const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
6324
+ const response = await this.stripe.invoices.list({
6325
+ customer: query?.customerId,
6326
+ status: stripeStatus,
6327
+ limit: query?.limit,
6328
+ starting_after: query?.startingAfter
6329
+ });
6330
+ return response.data.map((invoice) => this.toInvoice(invoice));
6331
+ }
6332
+ async listTransactions(query) {
6333
+ const response = await this.stripe.charges.list({
6334
+ customer: query?.customerId,
6335
+ payment_intent: query?.paymentIntentId,
6336
+ limit: query?.limit,
6337
+ starting_after: query?.startingAfter
6338
+ });
6339
+ return response.data.map((charge) => ({
6340
+ id: charge.id,
6341
+ paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
6342
+ amount: {
6343
+ amount: charge.amount,
6344
+ currency: charge.currency?.toUpperCase() ?? "USD"
6345
+ },
6346
+ type: "capture",
6347
+ status: mapChargeStatus(charge.status),
6348
+ description: charge.description ?? undefined,
6349
+ createdAt: new Date(charge.created * 1000),
6350
+ metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
6351
+ balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
6352
+ })
6353
+ }));
6354
+ }
6355
+ toCustomer(customer) {
6356
+ const metadata = this.toMetadata(customer.metadata);
6357
+ const updatedAtValue = metadata?.updatedAt;
6358
+ return {
6359
+ id: customer.id,
6360
+ email: customer.email ?? undefined,
6361
+ name: customer.name ?? undefined,
6362
+ metadata,
6363
+ createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
6364
+ updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
6365
+ };
6366
+ }
6367
+ toPaymentIntent(intent) {
6368
+ const metadata = this.toMetadata(intent.metadata);
6369
+ return {
6370
+ id: intent.id,
6371
+ amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
6372
+ status: mapPaymentIntentStatus(intent.status),
6373
+ customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
6374
+ description: intent.description ?? undefined,
6375
+ clientSecret: intent.client_secret ?? undefined,
6376
+ metadata,
6377
+ createdAt: new Date(intent.created * 1000),
6378
+ updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
6379
+ };
6380
+ }
6381
+ toInvoice(invoice) {
6382
+ const metadata = this.toMetadata(invoice.metadata);
6383
+ return {
6384
+ id: invoice.id,
6385
+ number: invoice.number ?? undefined,
6386
+ status: invoice.status ?? "draft",
6387
+ amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
6388
+ amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
6389
+ customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
6390
+ dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
6391
+ hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
6392
+ metadata,
6393
+ createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
6394
+ updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
6522
6395
  };
6523
6396
  }
6524
- async createWorkItem(input) {
6525
- const teamId = this.defaults.teamId;
6526
- if (!teamId) {
6527
- throw new Error("Linear teamId is required to create work items.");
6528
- }
6529
- const payload = await this.client.createIssue({
6530
- teamId,
6531
- title: input.title,
6532
- description: input.description,
6533
- priority: mapPriority(input.priority),
6534
- estimate: input.estimate,
6535
- assigneeId: input.assigneeId ?? this.defaults.assigneeId,
6536
- projectId: input.projectId ?? this.defaults.projectId,
6537
- stateId: this.defaults.stateId,
6538
- labelIds: resolveLabelIds(this.defaults, input.tags)
6539
- });
6540
- const issue = await payload.issue;
6541
- const state = issue ? await issue.state : undefined;
6397
+ toMoney(amount, currency) {
6542
6398
  return {
6543
- id: issue?.id ?? "",
6544
- title: issue?.title ?? input.title,
6545
- url: issue?.url ?? undefined,
6546
- status: state?.name ?? undefined,
6547
- priority: input.priority,
6548
- tags: input.tags,
6549
- projectId: input.projectId ?? this.defaults.projectId,
6550
- externalId: input.externalId,
6551
- metadata: input.metadata
6399
+ amount,
6400
+ currency: currency?.toUpperCase() ?? "USD"
6552
6401
  };
6553
6402
  }
6554
- async createWorkItems(items) {
6555
- const created = [];
6556
- for (const item of items) {
6557
- created.push(await this.createWorkItem(item));
6403
+ toMetadata(metadata) {
6404
+ if (!metadata)
6405
+ return;
6406
+ const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
6407
+ if (entries.length === 0)
6408
+ return;
6409
+ return Object.fromEntries(entries);
6410
+ }
6411
+ mergeMetadata(base, extras) {
6412
+ const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
6413
+ if (!base && filteredExtras.length === 0) {
6414
+ return;
6558
6415
  }
6559
- return created;
6416
+ return {
6417
+ ...base ?? {},
6418
+ ...Object.fromEntries(filteredExtras)
6419
+ };
6560
6420
  }
6561
6421
  }
6562
- function mapPriority(priority) {
6563
- if (!priority)
6422
+ function mapRefundReason(reason) {
6423
+ if (!reason)
6564
6424
  return;
6565
- return PRIORITY_MAP[priority] ?? undefined;
6425
+ const allowed = [
6426
+ "duplicate",
6427
+ "fraudulent",
6428
+ "requested_by_customer"
6429
+ ];
6430
+ return allowed.includes(reason) ? reason : undefined;
6566
6431
  }
6567
- function resolveLabelIds(defaults, tags) {
6568
- const labelIds = new Set;
6569
- (defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
6570
- if (tags && defaults.tagLabelMap) {
6571
- tags.forEach((tag) => {
6572
- const mapped = defaults.tagLabelMap?.[tag];
6573
- if (mapped)
6574
- labelIds.add(mapped);
6575
- });
6432
+ function mapPaymentIntentStatus(status) {
6433
+ switch (status) {
6434
+ case "requires_payment_method":
6435
+ return "requires_payment_method";
6436
+ case "requires_confirmation":
6437
+ return "requires_confirmation";
6438
+ case "requires_action":
6439
+ case "requires_capture":
6440
+ return "requires_action";
6441
+ case "processing":
6442
+ return "processing";
6443
+ case "succeeded":
6444
+ return "succeeded";
6445
+ case "canceled":
6446
+ return "canceled";
6447
+ default:
6448
+ return "requires_payment_method";
6449
+ }
6450
+ }
6451
+ function mapRefundStatus(status) {
6452
+ switch (status) {
6453
+ case "pending":
6454
+ case "succeeded":
6455
+ case "failed":
6456
+ case "canceled":
6457
+ return status;
6458
+ default:
6459
+ return "pending";
6460
+ }
6461
+ }
6462
+ function mapChargeStatus(status) {
6463
+ switch (status) {
6464
+ case "pending":
6465
+ case "processing":
6466
+ return "pending";
6467
+ case "succeeded":
6468
+ return "succeeded";
6469
+ case "failed":
6470
+ case "canceled":
6471
+ return "failed";
6472
+ default:
6473
+ return "pending";
6576
6474
  }
6577
- const merged = [...labelIds];
6578
- return merged.length > 0 ? merged : undefined;
6579
6475
  }
6580
6476
 
6581
- // src/impls/jira.ts
6477
+ // src/impls/supabase-psql.ts
6582
6478
  import { Buffer as Buffer5 } from "buffer";
6479
+ import { sql as drizzleSql } from "drizzle-orm";
6480
+ import { drizzle } from "drizzle-orm/postgres-js";
6481
+ import postgres from "postgres";
6583
6482
 
6584
- class JiraProjectManagementProvider {
6585
- siteUrl;
6586
- authHeader;
6587
- defaults;
6588
- fetchFn;
6589
- constructor(options) {
6590
- this.siteUrl = normalizeSiteUrl(options.siteUrl);
6591
- this.authHeader = buildAuthHeader(options.email, options.apiToken);
6592
- this.defaults = {
6593
- projectKey: options.projectKey,
6594
- issueType: options.issueType,
6595
- defaultLabels: options.defaultLabels,
6596
- issueTypeMap: options.issueTypeMap
6597
- };
6598
- this.fetchFn = options.fetch ?? fetch;
6599
- }
6600
- async createWorkItem(input) {
6601
- const projectKey = input.projectId ?? this.defaults.projectKey;
6602
- if (!projectKey) {
6603
- throw new Error("Jira projectKey is required to create work items.");
6483
+ class SupabasePostgresProvider {
6484
+ client;
6485
+ db;
6486
+ ownsClient;
6487
+ createDrizzle;
6488
+ constructor(options = {}) {
6489
+ this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
6490
+ if (options.db) {
6491
+ if (!options.client) {
6492
+ throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
6493
+ }
6494
+ this.client = options.client;
6495
+ this.db = options.db;
6496
+ this.ownsClient = false;
6497
+ return;
6604
6498
  }
6605
- const issueType = resolveIssueType(input.type, this.defaults);
6606
- const description = buildJiraDescription(input.description);
6607
- const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
6608
- const priority = mapPriority2(input.priority);
6609
- const payload = {
6610
- fields: {
6611
- project: { key: projectKey },
6612
- summary: input.title,
6613
- description,
6614
- issuetype: { name: issueType },
6615
- labels,
6616
- priority: priority ? { name: priority } : undefined,
6617
- assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
6618
- duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
6499
+ if (options.client) {
6500
+ this.client = options.client;
6501
+ this.ownsClient = false;
6502
+ } else {
6503
+ if (!options.connectionString) {
6504
+ throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
6619
6505
  }
6506
+ this.client = postgres(options.connectionString, {
6507
+ max: options.maxConnections,
6508
+ prepare: false,
6509
+ ssl: resolveSslMode(options.sslMode)
6510
+ });
6511
+ this.ownsClient = true;
6512
+ }
6513
+ this.db = this.createDrizzle(this.client);
6514
+ }
6515
+ async query(statement, params = []) {
6516
+ const query = buildParameterizedSql(statement, params);
6517
+ const result = await this.db.execute(query);
6518
+ const rows = asRows(result);
6519
+ return {
6520
+ rows,
6521
+ rowCount: rows.length
6620
6522
  };
6621
- const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
6622
- method: "POST",
6623
- headers: {
6624
- Authorization: this.authHeader,
6625
- "Content-Type": "application/json",
6626
- Accept: "application/json"
6627
- },
6628
- body: JSON.stringify(payload)
6523
+ }
6524
+ async execute(statement, params = []) {
6525
+ const query = buildParameterizedSql(statement, params);
6526
+ await this.db.execute(query);
6527
+ }
6528
+ async transaction(run) {
6529
+ const transactionResult = this.client.begin(async (transactionClient) => {
6530
+ const transactionalProvider = new SupabasePostgresProvider({
6531
+ client: transactionClient,
6532
+ db: this.createDrizzle(transactionClient),
6533
+ createDrizzle: this.createDrizzle
6534
+ });
6535
+ return run(transactionalProvider);
6629
6536
  });
6630
- if (!response.ok) {
6631
- const body = await response.text();
6632
- throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
6537
+ return transactionResult;
6538
+ }
6539
+ async close() {
6540
+ if (this.ownsClient) {
6541
+ await this.client.end({ timeout: 5 });
6633
6542
  }
6634
- const data = await response.json();
6635
- return {
6636
- id: data.id ?? data.key ?? "",
6637
- title: input.title,
6638
- url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
6639
- status: input.status,
6640
- priority: input.priority,
6641
- tags: input.tags,
6642
- projectId: projectKey,
6643
- externalId: input.externalId,
6644
- metadata: input.metadata
6645
- };
6646
6543
  }
6647
- async createWorkItems(items) {
6648
- const created = [];
6649
- for (const item of items) {
6650
- created.push(await this.createWorkItem(item));
6544
+ }
6545
+ function buildParameterizedSql(statement, params) {
6546
+ const segments = [];
6547
+ const pattern = /\$(\d+)/g;
6548
+ let cursor = 0;
6549
+ for (const match of statement.matchAll(pattern)) {
6550
+ const token = match[0];
6551
+ const indexPart = match[1];
6552
+ const start = match.index;
6553
+ if (indexPart == null || start == null)
6554
+ continue;
6555
+ const parameterIndex = Number(indexPart) - 1;
6556
+ if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
6557
+ throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
6651
6558
  }
6652
- return created;
6559
+ const staticSegment = statement.slice(cursor, start);
6560
+ if (staticSegment.length > 0) {
6561
+ segments.push(drizzleSql.raw(staticSegment));
6562
+ }
6563
+ const parameterValue = params[parameterIndex];
6564
+ if (parameterValue === undefined) {
6565
+ throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
6566
+ }
6567
+ const normalizedValue = normalizeParam(parameterValue);
6568
+ segments.push(drizzleSql`${normalizedValue}`);
6569
+ cursor = start + token.length;
6570
+ }
6571
+ const tailSegment = statement.slice(cursor);
6572
+ if (tailSegment.length > 0) {
6573
+ segments.push(drizzleSql.raw(tailSegment));
6574
+ }
6575
+ if (segments.length === 0) {
6576
+ return drizzleSql.raw("");
6653
6577
  }
6578
+ return drizzleSql.join(segments);
6654
6579
  }
6655
- function normalizeSiteUrl(siteUrl) {
6656
- return siteUrl.replace(/\/$/, "");
6580
+ function normalizeParam(value) {
6581
+ if (typeof value === "bigint") {
6582
+ return value.toString();
6583
+ }
6584
+ if (value instanceof Uint8Array) {
6585
+ return Buffer5.from(value);
6586
+ }
6587
+ if (isPlainObject(value)) {
6588
+ return JSON.stringify(value);
6589
+ }
6590
+ return value;
6657
6591
  }
6658
- function buildAuthHeader(email, apiToken) {
6659
- const token = Buffer5.from(`${email}:${apiToken}`).toString("base64");
6660
- return `Basic ${token}`;
6592
+ function asRows(result) {
6593
+ if (!Array.isArray(result)) {
6594
+ return [];
6595
+ }
6596
+ return result;
6661
6597
  }
6662
- function resolveIssueType(type, defaults) {
6663
- if (type && defaults.issueTypeMap?.[type]) {
6664
- return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
6598
+ function isPlainObject(value) {
6599
+ if (value == null || typeof value !== "object") {
6600
+ return false;
6665
6601
  }
6666
- return defaults.issueType ?? "Task";
6602
+ if (Array.isArray(value)) {
6603
+ return false;
6604
+ }
6605
+ if (value instanceof Date) {
6606
+ return false;
6607
+ }
6608
+ if (value instanceof Uint8Array) {
6609
+ return false;
6610
+ }
6611
+ return true;
6667
6612
  }
6668
- function mapPriority2(priority) {
6669
- switch (priority) {
6670
- case "urgent":
6671
- return "Highest";
6672
- case "high":
6673
- return "High";
6674
- case "medium":
6675
- return "Medium";
6676
- case "low":
6677
- return "Low";
6678
- case "none":
6613
+ function resolveSslMode(mode) {
6614
+ switch (mode) {
6615
+ case "allow":
6616
+ return false;
6617
+ case "prefer":
6618
+ return "prefer";
6619
+ case "require":
6679
6620
  default:
6680
- return;
6621
+ return "require";
6681
6622
  }
6682
6623
  }
6683
- function mergeLabels(defaults, tags) {
6684
- const merged = new Set;
6685
- (defaults ?? []).forEach((label) => merged.add(label));
6686
- (tags ?? []).forEach((tag) => merged.add(tag));
6687
- const result = [...merged];
6688
- return result.length > 0 ? result : undefined;
6689
- }
6690
- function buildJiraDescription(description) {
6691
- if (!description)
6692
- return;
6693
- const lines = description.split(/\r?\n/).filter((line) => line.trim());
6694
- const content = lines.map((line) => ({
6695
- type: "paragraph",
6696
- content: [{ type: "text", text: line }]
6697
- }));
6698
- if (content.length === 0)
6699
- return;
6700
- return { type: "doc", version: 1, content };
6701
- }
6702
-
6703
- // src/impls/notion.ts
6704
- import { Client } from "@notionhq/client";
6705
6624
 
6706
- class NotionProjectManagementProvider {
6707
- client;
6708
- defaults;
6625
+ // src/impls/supabase-vector.ts
6626
+ class SupabaseVectorProvider {
6627
+ database;
6628
+ createTableIfMissing;
6629
+ distanceMetric;
6630
+ quotedSchema;
6631
+ qualifiedTable;
6632
+ collectionIndex;
6633
+ namespaceIndex;
6634
+ ensureTablePromise;
6709
6635
  constructor(options) {
6710
- this.client = options.client ?? new Client({ auth: options.apiKey });
6711
- this.defaults = {
6712
- databaseId: options.databaseId,
6713
- summaryParentPageId: options.summaryParentPageId,
6714
- titleProperty: options.titleProperty,
6715
- statusProperty: options.statusProperty,
6716
- priorityProperty: options.priorityProperty,
6717
- tagsProperty: options.tagsProperty,
6718
- dueDateProperty: options.dueDateProperty,
6719
- descriptionProperty: options.descriptionProperty
6720
- };
6636
+ this.database = options.database ?? new SupabasePostgresProvider({
6637
+ connectionString: options.connectionString,
6638
+ maxConnections: options.maxConnections,
6639
+ sslMode: options.sslMode
6640
+ });
6641
+ this.createTableIfMissing = options.createTableIfMissing ?? true;
6642
+ this.distanceMetric = options.distanceMetric ?? "cosine";
6643
+ const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
6644
+ const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
6645
+ this.quotedSchema = quoteIdentifier(schema);
6646
+ this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
6647
+ this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
6648
+ this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
6721
6649
  }
6722
- async createWorkItem(input) {
6723
- if (input.type === "summary" && this.defaults.summaryParentPageId) {
6724
- return this.createSummaryPage(input);
6650
+ async upsert(request) {
6651
+ if (request.documents.length === 0) {
6652
+ return;
6725
6653
  }
6726
- const databaseId = this.defaults.databaseId;
6727
- if (!databaseId) {
6728
- throw new Error("Notion databaseId is required to create work items.");
6654
+ if (this.createTableIfMissing) {
6655
+ await this.ensureTable();
6729
6656
  }
6730
- const titleProperty = this.defaults.titleProperty ?? "Name";
6731
- const properties = {
6732
- [titleProperty]: buildTitleProperty(input.title)
6733
- };
6734
- applySelect(properties, this.defaults.statusProperty, input.status);
6735
- applySelect(properties, this.defaults.priorityProperty, input.priority);
6736
- applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
6737
- applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
6738
- applyRichText(properties, this.defaults.descriptionProperty, input.description);
6739
- const page = await this.client.pages.create({
6740
- parent: { type: "database_id", database_id: databaseId },
6741
- properties
6657
+ for (const document of request.documents) {
6658
+ await this.database.execute(`INSERT INTO ${this.qualifiedTable}
6659
+ (collection, id, embedding, payload, namespace, expires_at, updated_at)
6660
+ VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
6661
+ ON CONFLICT (collection, id)
6662
+ DO UPDATE SET
6663
+ embedding = EXCLUDED.embedding,
6664
+ payload = EXCLUDED.payload,
6665
+ namespace = EXCLUDED.namespace,
6666
+ expires_at = EXCLUDED.expires_at,
6667
+ updated_at = now();`, [
6668
+ request.collection,
6669
+ document.id,
6670
+ toVectorLiteral(document.vector),
6671
+ document.payload ? JSON.stringify(document.payload) : null,
6672
+ document.namespace ?? null,
6673
+ document.expiresAt ?? null
6674
+ ]);
6675
+ }
6676
+ }
6677
+ async search(query) {
6678
+ const operator = this.distanceOperator;
6679
+ const results = await this.database.query(`SELECT
6680
+ id,
6681
+ payload,
6682
+ namespace,
6683
+ (embedding ${operator} $3::vector) AS distance
6684
+ FROM ${this.qualifiedTable}
6685
+ WHERE collection = $1
6686
+ AND ($2::text IS NULL OR namespace = $2)
6687
+ AND (expires_at IS NULL OR expires_at > now())
6688
+ AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
6689
+ ORDER BY embedding ${operator} $3::vector
6690
+ LIMIT $5;`, [
6691
+ query.collection,
6692
+ query.namespace ?? null,
6693
+ toVectorLiteral(query.vector),
6694
+ query.filter ? JSON.stringify(query.filter) : null,
6695
+ query.topK
6696
+ ]);
6697
+ const mapped = results.rows.map((row) => {
6698
+ const distance = Number(row.distance);
6699
+ return {
6700
+ id: row.id,
6701
+ score: distanceToScore(distance, this.distanceMetric),
6702
+ payload: isRecord(row.payload) ? row.payload : undefined,
6703
+ namespace: row.namespace ?? undefined
6704
+ };
6742
6705
  });
6743
- return {
6744
- id: page.id,
6745
- title: input.title,
6746
- url: "url" in page ? page.url : undefined,
6747
- status: input.status,
6748
- priority: input.priority,
6749
- tags: input.tags,
6750
- projectId: databaseId,
6751
- externalId: input.externalId,
6752
- metadata: input.metadata
6753
- };
6706
+ const scoreThreshold = query.scoreThreshold;
6707
+ if (scoreThreshold == null) {
6708
+ return mapped;
6709
+ }
6710
+ return mapped.filter((result) => result.score >= scoreThreshold);
6754
6711
  }
6755
- async createWorkItems(items) {
6756
- const created = [];
6757
- for (const item of items) {
6758
- created.push(await this.createWorkItem(item));
6712
+ async delete(request) {
6713
+ if (request.ids.length === 0) {
6714
+ return;
6759
6715
  }
6760
- return created;
6716
+ const params = [
6717
+ request.collection,
6718
+ request.ids,
6719
+ request.namespace ?? null
6720
+ ];
6721
+ await this.database.execute(`DELETE FROM ${this.qualifiedTable}
6722
+ WHERE collection = $1
6723
+ AND id = ANY($2::text[])
6724
+ AND ($3::text IS NULL OR namespace = $3);`, params);
6761
6725
  }
6762
- async createSummaryPage(input) {
6763
- const parentId = this.defaults.summaryParentPageId;
6764
- if (!parentId) {
6765
- throw new Error("Notion summaryParentPageId is required for summaries.");
6726
+ async ensureTable() {
6727
+ if (!this.ensureTablePromise) {
6728
+ this.ensureTablePromise = this.createTable();
6766
6729
  }
6767
- const summaryProperties = {
6768
- title: buildTitleProperty(input.title)
6769
- };
6770
- const page = await this.client.pages.create({
6771
- parent: { type: "page_id", page_id: parentId },
6772
- properties: summaryProperties
6773
- });
6774
- if (input.description) {
6775
- const children = buildParagraphBlocks(input.description);
6776
- if (children.length > 0) {
6777
- await this.client.blocks.children.append({
6778
- block_id: page.id,
6779
- children
6780
- });
6781
- }
6730
+ await this.ensureTablePromise;
6731
+ }
6732
+ async createTable() {
6733
+ await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
6734
+ await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
6735
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
6736
+ collection text NOT NULL,
6737
+ id text NOT NULL,
6738
+ embedding vector NOT NULL,
6739
+ payload jsonb,
6740
+ namespace text,
6741
+ expires_at timestamptz,
6742
+ created_at timestamptz NOT NULL DEFAULT now(),
6743
+ updated_at timestamptz NOT NULL DEFAULT now(),
6744
+ PRIMARY KEY (collection, id)
6745
+ );`);
6746
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
6747
+ ON ${this.qualifiedTable} (collection);`);
6748
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
6749
+ ON ${this.qualifiedTable} (namespace);`);
6750
+ }
6751
+ get distanceOperator() {
6752
+ switch (this.distanceMetric) {
6753
+ case "l2":
6754
+ return "<->";
6755
+ case "inner_product":
6756
+ return "<#>";
6757
+ case "cosine":
6758
+ default:
6759
+ return "<=>";
6782
6760
  }
6783
- return {
6784
- id: page.id,
6785
- title: input.title,
6786
- url: "url" in page ? page.url : undefined,
6787
- status: input.status,
6788
- priority: input.priority,
6789
- tags: input.tags,
6790
- projectId: parentId,
6791
- externalId: input.externalId,
6792
- metadata: input.metadata
6793
- };
6794
6761
  }
6795
6762
  }
6796
- function buildTitleProperty(title) {
6797
- return {
6798
- title: [
6799
- {
6800
- type: "text",
6801
- text: { content: title }
6802
- }
6803
- ]
6804
- };
6805
- }
6806
- function buildRichText(text) {
6807
- return {
6808
- rich_text: [
6809
- {
6810
- type: "text",
6811
- text: { content: text }
6812
- }
6813
- ]
6814
- };
6815
- }
6816
- function applySelect(properties, property, value) {
6817
- if (!property || !value)
6818
- return;
6819
- const next = {
6820
- select: { name: value }
6821
- };
6822
- properties[property] = next;
6763
+ function sanitizeIdentifier(value, label) {
6764
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
6765
+ throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
6766
+ }
6767
+ return value;
6823
6768
  }
6824
- function applyMultiSelect(properties, property, values) {
6825
- if (!property || !values || values.length === 0)
6826
- return;
6827
- const next = {
6828
- multi_select: values.map((value) => ({ name: value }))
6829
- };
6830
- properties[property] = next;
6769
+ function quoteIdentifier(value) {
6770
+ return `"${value.replaceAll('"', '""')}"`;
6831
6771
  }
6832
- function applyDate(properties, property, value) {
6833
- if (!property || !value)
6834
- return;
6835
- const next = {
6836
- date: { start: value.toISOString() }
6837
- };
6838
- properties[property] = next;
6772
+ function toVectorLiteral(vector) {
6773
+ if (vector.length === 0) {
6774
+ throw new Error("Supabase vectors must contain at least one dimension.");
6775
+ }
6776
+ for (const value of vector) {
6777
+ if (!Number.isFinite(value)) {
6778
+ throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
6779
+ }
6780
+ }
6781
+ return `[${vector.join(",")}]`;
6839
6782
  }
6840
- function applyRichText(properties, property, value) {
6841
- if (!property || !value)
6842
- return;
6843
- properties[property] = buildRichText(value);
6783
+ function isRecord(value) {
6784
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6844
6785
  }
6845
- function buildParagraphBlocks(text) {
6846
- const lines = text.split(/\r?\n/).filter((line) => line.trim());
6847
- return lines.map((line) => ({
6848
- object: "block",
6849
- type: "paragraph",
6850
- paragraph: {
6851
- rich_text: [
6852
- {
6853
- type: "text",
6854
- text: { content: line }
6855
- }
6856
- ]
6857
- }
6858
- }));
6786
+ function distanceToScore(distance, metric) {
6787
+ switch (metric) {
6788
+ case "inner_product":
6789
+ return -distance;
6790
+ case "l2":
6791
+ return 1 / (1 + distance);
6792
+ case "cosine":
6793
+ default:
6794
+ return 1 - distance;
6795
+ }
6859
6796
  }
6860
6797
 
6861
6798
  // src/impls/tldv-meeting-recorder.ts
@@ -7001,11 +6938,74 @@ async function safeReadError4(response) {
7001
6938
  }
7002
6939
  }
7003
6940
 
6941
+ // src/impls/twilio-sms.ts
6942
+ import Twilio from "twilio";
6943
+
6944
+ class TwilioSmsProvider {
6945
+ client;
6946
+ fromNumber;
6947
+ constructor(options) {
6948
+ this.client = options.client ?? Twilio(options.accountSid, options.authToken);
6949
+ this.fromNumber = options.fromNumber;
6950
+ }
6951
+ async sendSms(input) {
6952
+ const message = await this.client.messages.create({
6953
+ to: input.to,
6954
+ from: input.from ?? this.fromNumber,
6955
+ body: input.body
6956
+ });
6957
+ return {
6958
+ id: message.sid,
6959
+ to: message.to ?? input.to,
6960
+ from: message.from ?? input.from ?? this.fromNumber ?? "",
6961
+ body: message.body ?? input.body,
6962
+ status: mapStatus(message.status),
6963
+ sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
6964
+ deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
6965
+ price: message.price ? Number(message.price) : undefined,
6966
+ priceCurrency: message.priceUnit ?? undefined,
6967
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
6968
+ errorMessage: message.errorMessage ?? undefined
6969
+ };
6970
+ }
6971
+ async getDeliveryStatus(messageId) {
6972
+ const message = await this.client.messages(messageId).fetch();
6973
+ return {
6974
+ status: mapStatus(message.status),
6975
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
6976
+ errorMessage: message.errorMessage ?? undefined,
6977
+ updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
6978
+ };
6979
+ }
6980
+ }
6981
+ function mapStatus(status) {
6982
+ switch (status) {
6983
+ case "queued":
6984
+ case "accepted":
6985
+ case "scheduled":
6986
+ return "queued";
6987
+ case "sending":
6988
+ case "processing":
6989
+ return "sending";
6990
+ case "sent":
6991
+ return "sent";
6992
+ case "delivered":
6993
+ return "delivered";
6994
+ case "undelivered":
6995
+ return "undelivered";
6996
+ case "failed":
6997
+ case "canceled":
6998
+ return "failed";
6999
+ default:
7000
+ return "queued";
7001
+ }
7002
+ }
7003
+
7004
7004
  // src/impls/provider-factory.ts
7005
7005
  import { Buffer as Buffer6 } from "buffer";
7006
- import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
7007
- import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
7008
7006
  import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
7007
+ import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
7008
+ import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
7009
7009
  var SECRET_CACHE = new Map;
7010
7010
 
7011
7011
  class IntegrationProviderFactory {
@@ -7458,34 +7458,34 @@ function requireConfig(context, key, message) {
7458
7458
  }
7459
7459
  return value;
7460
7460
  }
7461
- // src/openbanking.ts
7461
+ // src/llm.ts
7462
7462
  export * from "@contractspec/lib.contracts-integrations";
7463
7463
 
7464
- // src/llm.ts
7464
+ // src/meeting-recorder.ts
7465
7465
  export * from "@contractspec/lib.contracts-integrations";
7466
7466
 
7467
- // src/vector-store.ts
7467
+ // src/messaging.ts
7468
7468
  export * from "@contractspec/lib.contracts-integrations";
7469
7469
 
7470
- // src/storage.ts
7470
+ // src/openbanking.ts
7471
7471
  export * from "@contractspec/lib.contracts-integrations";
7472
7472
 
7473
- // src/sms.ts
7473
+ // src/payments.ts
7474
7474
  export * from "@contractspec/lib.contracts-integrations";
7475
7475
 
7476
- // src/messaging.ts
7476
+ // src/project-management.ts
7477
7477
  export * from "@contractspec/lib.contracts-integrations";
7478
7478
 
7479
- // src/payments.ts
7479
+ // src/sms.ts
7480
7480
  export * from "@contractspec/lib.contracts-integrations";
7481
7481
 
7482
- // src/voice.ts
7482
+ // src/storage.ts
7483
7483
  export * from "@contractspec/lib.contracts-integrations";
7484
7484
 
7485
- // src/project-management.ts
7485
+ // src/vector-store.ts
7486
7486
  export * from "@contractspec/lib.contracts-integrations";
7487
7487
 
7488
- // src/meeting-recorder.ts
7488
+ // src/voice.ts
7489
7489
  export * from "@contractspec/lib.contracts-integrations";
7490
7490
  export {
7491
7491
  resolveToolkit,