@contractspec/integration.providers-impls 3.7.6 → 3.8.2

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