@contractspec/integration.providers-impls 3.7.5 → 3.7.7

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