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