@contractspec/integration.providers-impls 3.7.6 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +80 -241
  2. package/dist/impls/composio-fallback-resolver.d.ts +4 -4
  3. package/dist/impls/composio-fallback-resolver.js +73 -73
  4. package/dist/impls/composio-mcp.d.ts +1 -1
  5. package/dist/impls/composio-proxies.d.ts +5 -5
  6. package/dist/impls/composio-sdk.d.ts +1 -1
  7. package/dist/impls/elevenlabs-voice.d.ts +1 -1
  8. package/dist/impls/fal-voice.d.ts +1 -1
  9. package/dist/impls/gcs-storage.d.ts +1 -1
  10. package/dist/impls/gmail-inbound.d.ts +1 -1
  11. package/dist/impls/gradium-voice.d.ts +2 -2
  12. package/dist/impls/health/base-health-provider.d.ts +1 -1
  13. package/dist/impls/health/official-health-providers.d.ts +1 -1
  14. package/dist/impls/health/providers.d.ts +1 -1
  15. package/dist/impls/health-provider-factory.d.ts +1 -1
  16. package/dist/impls/index.d.ts +31 -30
  17. package/dist/impls/index.js +2189 -2138
  18. package/dist/impls/messaging-telegram.d.ts +13 -0
  19. package/dist/impls/messaging-telegram.js +49 -0
  20. package/dist/impls/mistral-conversational.d.ts +1 -1
  21. package/dist/impls/mistral-conversational.js +159 -159
  22. package/dist/impls/posthog-reader.d.ts +1 -1
  23. package/dist/impls/provider-factory.d.ts +11 -11
  24. package/dist/impls/provider-factory.js +2116 -2066
  25. package/dist/index.d.ts +12 -12
  26. package/dist/index.js +2197 -2146
  27. package/dist/node/impls/composio-fallback-resolver.js +73 -73
  28. package/dist/node/impls/index.js +2189 -2138
  29. package/dist/node/impls/messaging-telegram.js +49 -0
  30. package/dist/node/impls/mistral-conversational.js +159 -159
  31. package/dist/node/impls/provider-factory.js +2116 -2066
  32. package/dist/node/index.js +2197 -2146
  33. package/dist/node/secrets/provider.js +2 -2
  34. package/dist/secrets/provider.d.ts +2 -2
  35. package/dist/secrets/provider.js +2 -2
  36. package/package.json +25 -13
@@ -2816,476 +2816,563 @@ function asString(value) {
2816
2816
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
2817
2817
  }
2818
2818
 
2819
- // src/impls/mistral-llm.ts
2820
- import { Mistral } from "@mistralai/mistralai";
2819
+ // src/impls/jira.ts
2820
+ import { Buffer as Buffer3 } from "buffer";
2821
2821
 
2822
- class MistralLLMProvider {
2823
- client;
2824
- defaultModel;
2822
+ class JiraProjectManagementProvider {
2823
+ siteUrl;
2824
+ authHeader;
2825
+ defaults;
2826
+ fetchFn;
2825
2827
  constructor(options) {
2826
- if (!options.apiKey) {
2827
- throw new Error("MistralLLMProvider requires an apiKey");
2828
+ this.siteUrl = normalizeSiteUrl(options.siteUrl);
2829
+ this.authHeader = buildAuthHeader(options.email, options.apiToken);
2830
+ this.defaults = {
2831
+ projectKey: options.projectKey,
2832
+ issueType: options.issueType,
2833
+ defaultLabels: options.defaultLabels,
2834
+ issueTypeMap: options.issueTypeMap
2835
+ };
2836
+ this.fetchFn = options.fetch ?? fetch;
2837
+ }
2838
+ async createWorkItem(input) {
2839
+ const projectKey = input.projectId ?? this.defaults.projectKey;
2840
+ if (!projectKey) {
2841
+ throw new Error("Jira projectKey is required to create work items.");
2828
2842
  }
2829
- this.client = options.client ?? new Mistral({
2830
- apiKey: options.apiKey,
2831
- serverURL: options.serverURL,
2832
- userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
2843
+ const issueType = resolveIssueType(input.type, this.defaults);
2844
+ const description = buildJiraDescription(input.description);
2845
+ const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
2846
+ const priority = mapPriority(input.priority);
2847
+ const payload = {
2848
+ fields: {
2849
+ project: { key: projectKey },
2850
+ summary: input.title,
2851
+ description,
2852
+ issuetype: { name: issueType },
2853
+ labels,
2854
+ priority: priority ? { name: priority } : undefined,
2855
+ assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
2856
+ duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
2857
+ }
2858
+ };
2859
+ const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
2860
+ method: "POST",
2861
+ headers: {
2862
+ Authorization: this.authHeader,
2863
+ "Content-Type": "application/json",
2864
+ Accept: "application/json"
2865
+ },
2866
+ body: JSON.stringify(payload)
2833
2867
  });
2834
- this.defaultModel = options.defaultModel ?? "mistral-large-latest";
2868
+ if (!response.ok) {
2869
+ const body = await response.text();
2870
+ throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
2871
+ }
2872
+ const data = await response.json();
2873
+ return {
2874
+ id: data.id ?? data.key ?? "",
2875
+ title: input.title,
2876
+ url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
2877
+ status: input.status,
2878
+ priority: input.priority,
2879
+ tags: input.tags,
2880
+ projectId: projectKey,
2881
+ externalId: input.externalId,
2882
+ metadata: input.metadata
2883
+ };
2835
2884
  }
2836
- async chat(messages, options = {}) {
2837
- const request = this.buildChatRequest(messages, options);
2838
- const response = await this.client.chat.complete(request);
2839
- return this.buildLLMResponse(response);
2885
+ async createWorkItems(items) {
2886
+ const created = [];
2887
+ for (const item of items) {
2888
+ created.push(await this.createWorkItem(item));
2889
+ }
2890
+ return created;
2840
2891
  }
2841
- async* stream(messages, options = {}) {
2842
- const request = this.buildChatRequest(messages, options);
2843
- request.stream = true;
2844
- const stream = await this.client.chat.stream(request);
2845
- const aggregatedParts = [];
2846
- const aggregatedToolCalls = [];
2847
- let usage;
2848
- let finishReason;
2849
- for await (const event of stream) {
2850
- for (const choice of event.data.choices) {
2851
- const delta = choice.delta;
2852
- if (typeof delta.content === "string") {
2853
- if (delta.content.length > 0) {
2854
- aggregatedParts.push({ type: "text", text: delta.content });
2855
- yield {
2856
- type: "message_delta",
2857
- delta: { type: "text", text: delta.content },
2858
- index: choice.index
2859
- };
2860
- }
2861
- } else if (Array.isArray(delta.content)) {
2862
- for (const chunk of delta.content) {
2863
- if (chunk.type === "text" && "text" in chunk) {
2864
- aggregatedParts.push({ type: "text", text: chunk.text });
2865
- yield {
2866
- type: "message_delta",
2867
- delta: { type: "text", text: chunk.text },
2868
- index: choice.index
2869
- };
2870
- }
2871
- }
2872
- }
2873
- if (delta.toolCalls) {
2874
- let localIndex = 0;
2875
- for (const call of delta.toolCalls) {
2876
- const toolCall = this.fromMistralToolCall(call, localIndex);
2877
- aggregatedToolCalls.push(toolCall);
2878
- yield {
2879
- type: "tool_call",
2880
- call: toolCall,
2881
- index: choice.index
2882
- };
2883
- localIndex += 1;
2884
- }
2885
- }
2886
- if (choice.finishReason && choice.finishReason !== "null") {
2887
- finishReason = choice.finishReason;
2888
- }
2889
- }
2890
- if (event.data.usage) {
2891
- const usageEntry = this.fromUsage(event.data.usage);
2892
- if (usageEntry) {
2893
- usage = usageEntry;
2894
- yield { type: "usage", usage: usageEntry };
2895
- }
2896
- }
2892
+ }
2893
+ function normalizeSiteUrl(siteUrl) {
2894
+ return siteUrl.replace(/\/$/, "");
2895
+ }
2896
+ function buildAuthHeader(email, apiToken) {
2897
+ const token = Buffer3.from(`${email}:${apiToken}`).toString("base64");
2898
+ return `Basic ${token}`;
2899
+ }
2900
+ function resolveIssueType(type, defaults) {
2901
+ if (type && defaults.issueTypeMap?.[type]) {
2902
+ return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
2903
+ }
2904
+ return defaults.issueType ?? "Task";
2905
+ }
2906
+ function mapPriority(priority) {
2907
+ switch (priority) {
2908
+ case "urgent":
2909
+ return "Highest";
2910
+ case "high":
2911
+ return "High";
2912
+ case "medium":
2913
+ return "Medium";
2914
+ case "low":
2915
+ return "Low";
2916
+ case "none":
2917
+ default:
2918
+ return;
2919
+ }
2920
+ }
2921
+ function mergeLabels(defaults, tags) {
2922
+ const merged = new Set;
2923
+ (defaults ?? []).forEach((label) => merged.add(label));
2924
+ (tags ?? []).forEach((tag) => merged.add(tag));
2925
+ const result = [...merged];
2926
+ return result.length > 0 ? result : undefined;
2927
+ }
2928
+ function buildJiraDescription(description) {
2929
+ if (!description)
2930
+ return;
2931
+ const lines = description.split(/\r?\n/).filter((line) => line.trim());
2932
+ const content = lines.map((line) => ({
2933
+ type: "paragraph",
2934
+ content: [{ type: "text", text: line }]
2935
+ }));
2936
+ if (content.length === 0)
2937
+ return;
2938
+ return { type: "doc", version: 1, content };
2939
+ }
2940
+
2941
+ // src/impls/linear.ts
2942
+ import { LinearClient } from "@linear/sdk";
2943
+ var PRIORITY_MAP = {
2944
+ urgent: 1,
2945
+ high: 2,
2946
+ medium: 3,
2947
+ low: 4,
2948
+ none: 0
2949
+ };
2950
+
2951
+ class LinearProjectManagementProvider {
2952
+ client;
2953
+ defaults;
2954
+ constructor(options) {
2955
+ this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
2956
+ this.defaults = {
2957
+ teamId: options.teamId,
2958
+ projectId: options.projectId,
2959
+ assigneeId: options.assigneeId,
2960
+ stateId: options.stateId,
2961
+ labelIds: options.labelIds,
2962
+ tagLabelMap: options.tagLabelMap
2963
+ };
2964
+ }
2965
+ async createWorkItem(input) {
2966
+ const teamId = this.defaults.teamId;
2967
+ if (!teamId) {
2968
+ throw new Error("Linear teamId is required to create work items.");
2897
2969
  }
2898
- const message = {
2899
- role: "assistant",
2900
- content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
2970
+ const payload = await this.client.createIssue({
2971
+ teamId,
2972
+ title: input.title,
2973
+ description: input.description,
2974
+ priority: mapPriority2(input.priority),
2975
+ estimate: input.estimate,
2976
+ assigneeId: input.assigneeId ?? this.defaults.assigneeId,
2977
+ projectId: input.projectId ?? this.defaults.projectId,
2978
+ stateId: this.defaults.stateId,
2979
+ labelIds: resolveLabelIds(this.defaults, input.tags)
2980
+ });
2981
+ const issue = await payload.issue;
2982
+ const state = issue ? await issue.state : undefined;
2983
+ return {
2984
+ id: issue?.id ?? "",
2985
+ title: issue?.title ?? input.title,
2986
+ url: issue?.url ?? undefined,
2987
+ status: state?.name ?? undefined,
2988
+ priority: input.priority,
2989
+ tags: input.tags,
2990
+ projectId: input.projectId ?? this.defaults.projectId,
2991
+ externalId: input.externalId,
2992
+ metadata: input.metadata
2901
2993
  };
2902
- if (aggregatedToolCalls.length > 0) {
2903
- message.content = [
2904
- ...aggregatedToolCalls,
2905
- ...aggregatedParts.length ? aggregatedParts : []
2906
- ];
2994
+ }
2995
+ async createWorkItems(items) {
2996
+ const created = [];
2997
+ for (const item of items) {
2998
+ created.push(await this.createWorkItem(item));
2907
2999
  }
2908
- yield {
2909
- type: "end",
2910
- response: {
2911
- message,
2912
- usage,
2913
- finishReason: mapFinishReason(finishReason)
3000
+ return created;
3001
+ }
3002
+ }
3003
+ function mapPriority2(priority) {
3004
+ if (!priority)
3005
+ return;
3006
+ return PRIORITY_MAP[priority] ?? undefined;
3007
+ }
3008
+ function resolveLabelIds(defaults, tags) {
3009
+ const labelIds = new Set;
3010
+ (defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
3011
+ if (tags && defaults.tagLabelMap) {
3012
+ tags.forEach((tag) => {
3013
+ const mapped = defaults.tagLabelMap?.[tag];
3014
+ if (mapped)
3015
+ labelIds.add(mapped);
3016
+ });
3017
+ }
3018
+ const merged = [...labelIds];
3019
+ return merged.length > 0 ? merged : undefined;
3020
+ }
3021
+
3022
+ // src/impls/messaging-github.ts
3023
+ class GithubMessagingProvider {
3024
+ token;
3025
+ defaultOwner;
3026
+ defaultRepo;
3027
+ apiBaseUrl;
3028
+ constructor(options) {
3029
+ this.token = options.token;
3030
+ this.defaultOwner = options.defaultOwner;
3031
+ this.defaultRepo = options.defaultRepo;
3032
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
3033
+ }
3034
+ async sendMessage(input) {
3035
+ const target = this.resolveTarget(input);
3036
+ const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
3037
+ method: "POST",
3038
+ headers: {
3039
+ authorization: `Bearer ${this.token}`,
3040
+ accept: "application/vnd.github+json",
3041
+ "content-type": "application/json"
3042
+ },
3043
+ body: JSON.stringify({ body: input.text })
3044
+ });
3045
+ const body = await response.json();
3046
+ if (!response.ok || !body.id) {
3047
+ throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
3048
+ }
3049
+ return {
3050
+ id: String(body.id),
3051
+ providerMessageId: body.node_id,
3052
+ status: "sent",
3053
+ sentAt: new Date,
3054
+ metadata: {
3055
+ url: body.html_url ?? "",
3056
+ owner: target.owner,
3057
+ repo: target.repo,
3058
+ issueNumber: String(target.issueNumber)
2914
3059
  }
2915
3060
  };
2916
3061
  }
2917
- async countTokens(_messages) {
2918
- throw new Error("Mistral API does not currently support token counting");
2919
- }
2920
- buildChatRequest(messages, options) {
2921
- const model = options.model ?? this.defaultModel;
2922
- const mappedMessages = messages.map((message) => this.toMistralMessage(message));
2923
- const request = {
2924
- model,
2925
- messages: mappedMessages
2926
- };
2927
- if (options.temperature != null) {
2928
- request.temperature = options.temperature;
2929
- }
2930
- if (options.topP != null) {
2931
- request.topP = options.topP;
2932
- }
2933
- if (options.maxOutputTokens != null) {
2934
- request.maxTokens = options.maxOutputTokens;
2935
- }
2936
- if (options.stopSequences?.length) {
2937
- request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
2938
- }
2939
- if (options.tools?.length) {
2940
- request.tools = options.tools.map((tool) => ({
2941
- type: "function",
2942
- function: {
2943
- name: tool.name,
2944
- description: tool.description,
2945
- parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
2946
- }
2947
- }));
2948
- }
2949
- if (options.responseFormat === "json") {
2950
- request.responseFormat = { type: "json_object" };
3062
+ async updateMessage(messageId, input) {
3063
+ const owner = input.metadata?.owner ?? this.defaultOwner;
3064
+ const repo = input.metadata?.repo ?? this.defaultRepo;
3065
+ if (!owner || !repo) {
3066
+ throw new Error("GitHub updateMessage requires owner and repo metadata.");
2951
3067
  }
2952
- return request;
2953
- }
2954
- buildLLMResponse(response) {
2955
- const firstChoice = response.choices[0];
2956
- if (!firstChoice) {
2957
- return {
2958
- message: {
2959
- role: "assistant",
2960
- content: [{ type: "text", text: "" }]
2961
- },
2962
- usage: this.fromUsage(response.usage),
2963
- raw: response
2964
- };
3068
+ const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
3069
+ method: "PATCH",
3070
+ headers: {
3071
+ authorization: `Bearer ${this.token}`,
3072
+ accept: "application/vnd.github+json",
3073
+ "content-type": "application/json"
3074
+ },
3075
+ body: JSON.stringify({ body: input.text })
3076
+ });
3077
+ const body = await response.json();
3078
+ if (!response.ok || !body.id) {
3079
+ throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
2965
3080
  }
2966
- const message = this.fromAssistantMessage(firstChoice.message);
2967
3081
  return {
2968
- message,
2969
- usage: this.fromUsage(response.usage),
2970
- finishReason: mapFinishReason(firstChoice.finishReason),
2971
- raw: response
3082
+ id: String(body.id),
3083
+ providerMessageId: body.node_id,
3084
+ status: "sent",
3085
+ sentAt: new Date,
3086
+ metadata: {
3087
+ url: body.html_url ?? "",
3088
+ owner,
3089
+ repo
3090
+ }
2972
3091
  };
2973
3092
  }
2974
- fromUsage(usage) {
2975
- if (!usage)
2976
- return;
3093
+ resolveTarget(input) {
3094
+ const parsedRecipient = parseRecipient(input.recipientId);
3095
+ const owner = parsedRecipient?.owner ?? this.defaultOwner;
3096
+ const repo = parsedRecipient?.repo ?? this.defaultRepo;
3097
+ const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
3098
+ if (!owner || !repo || issueNumber == null) {
3099
+ throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
3100
+ }
2977
3101
  return {
2978
- promptTokens: usage.promptTokens ?? 0,
2979
- completionTokens: usage.completionTokens ?? 0,
2980
- totalTokens: usage.totalTokens ?? 0
3102
+ owner,
3103
+ repo,
3104
+ issueNumber
2981
3105
  };
2982
3106
  }
2983
- fromAssistantMessage(message) {
2984
- const parts = [];
2985
- if (typeof message.content === "string") {
2986
- parts.push({ type: "text", text: message.content });
2987
- } else if (Array.isArray(message.content)) {
2988
- message.content.forEach((chunk) => {
2989
- if (chunk.type === "text" && "text" in chunk) {
2990
- parts.push({ type: "text", text: chunk.text });
2991
- }
2992
- });
2993
- }
2994
- const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
2995
- if (toolCalls.length > 0) {
2996
- parts.splice(0, 0, ...toolCalls);
2997
- }
2998
- if (parts.length === 0) {
2999
- parts.push({ type: "text", text: "" });
3107
+ }
3108
+ function parseRecipient(value) {
3109
+ if (!value)
3110
+ return null;
3111
+ const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
3112
+ if (!match)
3113
+ return null;
3114
+ const owner = match[1];
3115
+ const repo = match[2];
3116
+ const issueNumber = Number(match[3]);
3117
+ if (!owner || !repo || !Number.isInteger(issueNumber)) {
3118
+ return null;
3119
+ }
3120
+ return { owner, repo, issueNumber };
3121
+ }
3122
+ function parseIssueNumber(value) {
3123
+ if (!value)
3124
+ return null;
3125
+ const numeric = Number(value);
3126
+ return Number.isInteger(numeric) ? numeric : null;
3127
+ }
3128
+
3129
+ // src/impls/messaging-slack.ts
3130
+ class SlackMessagingProvider {
3131
+ botToken;
3132
+ defaultChannelId;
3133
+ apiBaseUrl;
3134
+ constructor(options) {
3135
+ this.botToken = options.botToken;
3136
+ this.defaultChannelId = options.defaultChannelId;
3137
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
3138
+ }
3139
+ async sendMessage(input) {
3140
+ const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
3141
+ if (!channel) {
3142
+ throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
3000
3143
  }
3001
- return {
3002
- role: "assistant",
3003
- content: parts
3144
+ const payload = {
3145
+ channel,
3146
+ text: input.text,
3147
+ mrkdwn: input.markdown ?? true,
3148
+ thread_ts: input.threadId
3004
3149
  };
3005
- }
3006
- fromMistralToolCall(call, index) {
3007
- const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
3150
+ const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
3151
+ method: "POST",
3152
+ headers: {
3153
+ authorization: `Bearer ${this.botToken}`,
3154
+ "content-type": "application/json"
3155
+ },
3156
+ body: JSON.stringify(payload)
3157
+ });
3158
+ const body = await response.json();
3159
+ if (!response.ok || !body.ok || !body.ts) {
3160
+ throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
3161
+ }
3008
3162
  return {
3009
- type: "tool-call",
3010
- id: call.id ?? `tool-call-${index}`,
3011
- name: call.function.name,
3012
- arguments: args
3163
+ id: `slack:${body.channel ?? channel}:${body.ts}`,
3164
+ providerMessageId: body.ts,
3165
+ status: "sent",
3166
+ sentAt: new Date,
3167
+ metadata: {
3168
+ channelId: body.channel ?? channel
3169
+ }
3013
3170
  };
3014
3171
  }
3015
- toMistralMessage(message) {
3016
- const textContent = this.extractText(message.content);
3017
- const toolCalls = this.extractToolCalls(message);
3018
- switch (message.role) {
3019
- case "system":
3020
- return {
3021
- role: "system",
3022
- content: textContent ?? ""
3023
- };
3024
- case "user":
3025
- return {
3026
- role: "user",
3027
- content: textContent ?? ""
3028
- };
3029
- case "assistant":
3030
- return {
3031
- role: "assistant",
3032
- content: toolCalls.length > 0 ? null : textContent ?? "",
3033
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined
3034
- };
3035
- case "tool":
3036
- return {
3037
- role: "tool",
3038
- content: textContent ?? "",
3039
- toolCallId: message.toolCallId ?? toolCalls[0]?.id
3040
- };
3041
- default:
3042
- return {
3043
- role: "user",
3044
- content: textContent ?? ""
3045
- };
3172
+ async updateMessage(messageId, input) {
3173
+ const channel = input.channelId ?? this.defaultChannelId;
3174
+ if (!channel) {
3175
+ throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
3046
3176
  }
3047
- }
3048
- extractText(parts) {
3049
- const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
3050
- if (textParts.length === 0)
3051
- return null;
3052
- return textParts.join("");
3053
- }
3054
- extractToolCalls(message) {
3055
- const toolCallParts = message.content.filter((part) => part.type === "tool-call");
3056
- return toolCallParts.map((call, index) => ({
3057
- id: call.id ?? `call_${index}`,
3058
- type: "function",
3059
- index,
3060
- function: {
3061
- name: call.name,
3062
- arguments: call.arguments
3177
+ const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
3178
+ method: "POST",
3179
+ headers: {
3180
+ authorization: `Bearer ${this.botToken}`,
3181
+ "content-type": "application/json"
3182
+ },
3183
+ body: JSON.stringify({
3184
+ channel,
3185
+ ts: messageId,
3186
+ text: input.text,
3187
+ mrkdwn: input.markdown ?? true
3188
+ })
3189
+ });
3190
+ const body = await response.json();
3191
+ if (!response.ok || !body.ok || !body.ts) {
3192
+ throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
3193
+ }
3194
+ return {
3195
+ id: `slack:${body.channel ?? channel}:${body.ts}`,
3196
+ providerMessageId: body.ts,
3197
+ status: "sent",
3198
+ sentAt: new Date,
3199
+ metadata: {
3200
+ channelId: body.channel ?? channel
3063
3201
  }
3064
- }));
3065
- }
3066
- }
3067
- function mapFinishReason(reason) {
3068
- if (!reason)
3069
- return;
3070
- const normalized = reason.toLowerCase();
3071
- switch (normalized) {
3072
- case "stop":
3073
- return "stop";
3074
- case "length":
3075
- return "length";
3076
- case "tool_call":
3077
- case "tool_calls":
3078
- return "tool_call";
3079
- case "content_filter":
3080
- return "content_filter";
3081
- default:
3082
- return;
3202
+ };
3083
3203
  }
3084
3204
  }
3085
3205
 
3086
- // src/impls/mistral-embedding.ts
3087
- import { Mistral as Mistral2 } from "@mistralai/mistralai";
3088
-
3089
- class MistralEmbeddingProvider {
3090
- client;
3091
- defaultModel;
3206
+ // src/impls/messaging-telegram.ts
3207
+ class TelegramMessagingProvider {
3208
+ botToken;
3209
+ defaultChatId;
3210
+ apiBaseUrl;
3092
3211
  constructor(options) {
3093
- if (!options.apiKey) {
3094
- throw new Error("MistralEmbeddingProvider requires an apiKey");
3095
- }
3096
- this.client = options.client ?? new Mistral2({
3097
- apiKey: options.apiKey,
3098
- serverURL: options.serverURL
3099
- });
3100
- this.defaultModel = options.defaultModel ?? "mistral-embed";
3212
+ this.botToken = options.botToken;
3213
+ this.defaultChatId = options.defaultChatId;
3214
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://api.telegram.org";
3101
3215
  }
3102
- async embedDocuments(documents, options) {
3103
- if (documents.length === 0)
3104
- return [];
3105
- const model = options?.model ?? this.defaultModel;
3106
- const response = await this.client.embeddings.create({
3107
- model,
3108
- inputs: documents.map((doc) => doc.text)
3216
+ async sendMessage(input) {
3217
+ const chatId = input.channelId ?? input.recipientId ?? this.defaultChatId ?? undefined;
3218
+ if (!chatId) {
3219
+ throw new Error("Telegram sendMessage requires channelId, recipientId, or defaultChatId.");
3220
+ }
3221
+ const messageThreadId = input.threadId && input.threadId !== chatId ? Number.parseInt(input.threadId, 10) : undefined;
3222
+ const response = await fetch(`${this.apiBaseUrl}/bot${this.botToken}/sendMessage`, {
3223
+ method: "POST",
3224
+ headers: {
3225
+ "content-type": "application/json"
3226
+ },
3227
+ body: JSON.stringify({
3228
+ chat_id: chatId,
3229
+ text: input.text,
3230
+ message_thread_id: Number.isFinite(messageThreadId) ? messageThreadId : undefined
3231
+ })
3109
3232
  });
3110
- return response.data.map((item, index) => ({
3111
- id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
3112
- vector: item.embedding ?? [],
3113
- dimensions: item.embedding?.length ?? 0,
3114
- model: response.model,
3115
- metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
3116
- }));
3117
- }
3118
- async embedQuery(query, options) {
3119
- const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
3120
- if (!result) {
3121
- throw new Error("Failed to compute embedding for query");
3233
+ const body = await response.json();
3234
+ const providerMessageId = body.result?.message_id;
3235
+ if (!response.ok || !body.ok || providerMessageId == null) {
3236
+ throw new Error(`Telegram sendMessage failed: ${body.description ?? `HTTP_${response.status}`}`);
3122
3237
  }
3123
- return result;
3238
+ return {
3239
+ id: `telegram:${chatId}:${providerMessageId}`,
3240
+ providerMessageId: String(providerMessageId),
3241
+ status: "sent",
3242
+ sentAt: new Date,
3243
+ metadata: {
3244
+ chatId: String(body.result?.chat?.id ?? chatId)
3245
+ }
3246
+ };
3124
3247
  }
3125
3248
  }
3126
3249
 
3127
- // src/impls/mistral-stt.ts
3128
- var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
3129
- var DEFAULT_MODEL = "voxtral-mini-latest";
3130
- var AUDIO_MIME_BY_FORMAT = {
3131
- mp3: "audio/mpeg",
3132
- wav: "audio/wav",
3133
- ogg: "audio/ogg",
3134
- pcm: "audio/pcm",
3135
- opus: "audio/opus"
3136
- };
3137
-
3138
- class MistralSttProvider {
3139
- apiKey;
3140
- defaultModel;
3141
- defaultLanguage;
3142
- baseUrl;
3143
- fetchImpl;
3250
+ // src/impls/messaging-whatsapp-meta.ts
3251
+ class MetaWhatsappMessagingProvider {
3252
+ accessToken;
3253
+ phoneNumberId;
3254
+ apiVersion;
3144
3255
  constructor(options) {
3145
- if (!options.apiKey) {
3146
- throw new Error("MistralSttProvider requires an apiKey");
3147
- }
3148
- this.apiKey = options.apiKey;
3149
- this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
3150
- this.defaultLanguage = options.defaultLanguage;
3151
- this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
3152
- this.fetchImpl = options.fetchImpl ?? fetch;
3256
+ this.accessToken = options.accessToken;
3257
+ this.phoneNumberId = options.phoneNumberId;
3258
+ this.apiVersion = options.apiVersion ?? "v22.0";
3153
3259
  }
3154
- async transcribe(input) {
3155
- const formData = new FormData;
3156
- const model = input.model ?? this.defaultModel;
3157
- const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
3158
- const fileName = `audio.${input.audio.format}`;
3159
- const audioBytes = new Uint8Array(input.audio.data);
3160
- const blob = new Blob([audioBytes], { type: mimeType });
3161
- formData.append("file", blob, fileName);
3162
- formData.append("model", model);
3163
- formData.append("response_format", "verbose_json");
3164
- const language = input.language ?? this.defaultLanguage;
3165
- if (language) {
3166
- formData.append("language", language);
3260
+ async sendMessage(input) {
3261
+ const to = input.recipientId;
3262
+ if (!to) {
3263
+ throw new Error("Meta WhatsApp sendMessage requires recipientId.");
3167
3264
  }
3168
- const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
3265
+ const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
3169
3266
  method: "POST",
3170
3267
  headers: {
3171
- Authorization: `Bearer ${this.apiKey}`
3268
+ authorization: `Bearer ${this.accessToken}`,
3269
+ "content-type": "application/json"
3172
3270
  },
3173
- body: formData
3271
+ body: JSON.stringify({
3272
+ messaging_product: "whatsapp",
3273
+ to,
3274
+ type: "text",
3275
+ text: {
3276
+ body: input.text,
3277
+ preview_url: false
3278
+ }
3279
+ })
3174
3280
  });
3175
- if (!response.ok) {
3176
- const body = await response.text();
3177
- throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
3281
+ const body = await response.json();
3282
+ const messageId = body.messages?.[0]?.id;
3283
+ if (!response.ok || !messageId) {
3284
+ const errorCode = body.error?.code != null ? String(body.error.code) : "";
3285
+ throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
3178
3286
  }
3179
- const payload = await response.json();
3180
- return toTranscriptionResult(payload, input);
3181
- }
3182
- }
3183
- function toTranscriptionResult(payload, input) {
3184
- const record = asRecord2(payload);
3185
- const text = readString3(record, "text") ?? "";
3186
- const language = readString3(record, "language") ?? input.language ?? "unknown";
3187
- const segments = parseSegments(record);
3188
- if (segments.length === 0 && text.length > 0) {
3189
- segments.push({
3190
- text,
3191
- startMs: 0,
3192
- endMs: input.audio.durationMs ?? 0
3193
- });
3287
+ return {
3288
+ id: messageId,
3289
+ providerMessageId: messageId,
3290
+ status: "sent",
3291
+ sentAt: new Date,
3292
+ metadata: {
3293
+ phoneNumberId: this.phoneNumberId
3294
+ }
3295
+ };
3194
3296
  }
3195
- const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
3196
- const topLevelWords = parseWordTimings(record.words);
3197
- const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
3198
- const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
3199
- const speakers = dedupeSpeakers(segments);
3200
- return {
3201
- text,
3202
- segments,
3203
- language,
3204
- durationMs,
3205
- speakers: speakers.length > 0 ? speakers : undefined,
3206
- wordTimings
3207
- };
3208
3297
  }
3209
- function parseSegments(record) {
3210
- if (!Array.isArray(record.segments)) {
3211
- return [];
3298
+
3299
+ // src/impls/messaging-whatsapp-twilio.ts
3300
+ import { Buffer as Buffer4 } from "buffer";
3301
+
3302
+ class TwilioWhatsappMessagingProvider {
3303
+ accountSid;
3304
+ authToken;
3305
+ fromNumber;
3306
+ constructor(options) {
3307
+ this.accountSid = options.accountSid;
3308
+ this.authToken = options.authToken;
3309
+ this.fromNumber = options.fromNumber;
3212
3310
  }
3213
- const parsed = [];
3214
- for (const entry of record.segments) {
3215
- const segmentRecord = asRecord2(entry);
3216
- const text = readString3(segmentRecord, "text");
3217
- if (!text) {
3218
- continue;
3311
+ async sendMessage(input) {
3312
+ const to = normalizeWhatsappAddress(input.recipientId);
3313
+ const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
3314
+ if (!to) {
3315
+ throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
3219
3316
  }
3220
- const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
3221
- const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
3222
- parsed.push({
3223
- text,
3224
- startMs: secondsToMs(startSeconds),
3225
- endMs: secondsToMs(endSeconds),
3226
- speakerId: readString3(segmentRecord, "speaker") ?? undefined,
3227
- confidence: readNumber2(segmentRecord, "confidence"),
3228
- wordTimings: parseWordTimings(segmentRecord.words)
3229
- });
3230
- }
3231
- return parsed;
3232
- }
3233
- function parseWordTimings(value) {
3234
- if (!Array.isArray(value)) {
3235
- return [];
3236
- }
3237
- const words = [];
3238
- for (const entry of value) {
3239
- const wordRecord = asRecord2(entry);
3240
- const word = readString3(wordRecord, "word");
3241
- const startSeconds = readNumber2(wordRecord, "start");
3242
- const endSeconds = readNumber2(wordRecord, "end");
3243
- if (!word || startSeconds == null || endSeconds == null) {
3244
- continue;
3317
+ if (!from) {
3318
+ throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
3245
3319
  }
3246
- words.push({
3247
- word,
3248
- startMs: secondsToMs(startSeconds),
3249
- endMs: secondsToMs(endSeconds),
3250
- confidence: readNumber2(wordRecord, "confidence")
3320
+ const params = new URLSearchParams;
3321
+ params.set("To", to);
3322
+ params.set("From", from);
3323
+ params.set("Body", input.text);
3324
+ const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
3325
+ const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
3326
+ method: "POST",
3327
+ headers: {
3328
+ authorization: `Basic ${auth}`,
3329
+ "content-type": "application/x-www-form-urlencoded"
3330
+ },
3331
+ body: params.toString()
3251
3332
  });
3252
- }
3253
- return words;
3254
- }
3255
- function dedupeSpeakers(segments) {
3256
- const seen = new Set;
3257
- const speakers = [];
3258
- for (const segment of segments) {
3259
- if (!segment.speakerId || seen.has(segment.speakerId)) {
3260
- continue;
3333
+ const body = await response.json();
3334
+ if (!response.ok || !body.sid) {
3335
+ throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
3261
3336
  }
3262
- seen.add(segment.speakerId);
3263
- speakers.push({
3264
- id: segment.speakerId,
3265
- name: segment.speakerName
3266
- });
3337
+ return {
3338
+ id: body.sid,
3339
+ providerMessageId: body.sid,
3340
+ status: mapTwilioStatus(body.status),
3341
+ sentAt: new Date,
3342
+ errorCode: body.error_code != null ? String(body.error_code) : undefined,
3343
+ errorMessage: body.error_message ?? undefined,
3344
+ metadata: {
3345
+ from,
3346
+ to
3347
+ }
3348
+ };
3267
3349
  }
3268
- return speakers;
3269
- }
3270
- function normalizeBaseUrl(url) {
3271
- return url.endsWith("/") ? url.slice(0, -1) : url;
3272
3350
  }
3273
- function asRecord2(value) {
3274
- if (value && typeof value === "object") {
3351
+ function normalizeWhatsappAddress(value) {
3352
+ if (!value)
3353
+ return null;
3354
+ if (value.startsWith("whatsapp:"))
3275
3355
  return value;
3276
- }
3277
- return {};
3278
- }
3279
- function readString3(record, key) {
3280
- const value = record[key];
3281
- return typeof value === "string" ? value : undefined;
3282
- }
3283
- function readNumber2(record, key) {
3284
- const value = record[key];
3285
- return typeof value === "number" ? value : undefined;
3356
+ return `whatsapp:${value}`;
3286
3357
  }
3287
- function secondsToMs(value) {
3288
- return Math.round(value * 1000);
3358
+ function mapTwilioStatus(status) {
3359
+ switch (status) {
3360
+ case "queued":
3361
+ case "accepted":
3362
+ case "scheduled":
3363
+ return "queued";
3364
+ case "sending":
3365
+ return "sending";
3366
+ case "delivered":
3367
+ return "delivered";
3368
+ case "failed":
3369
+ case "undelivered":
3370
+ case "canceled":
3371
+ return "failed";
3372
+ case "sent":
3373
+ default:
3374
+ return "sent";
3375
+ }
3289
3376
  }
3290
3377
 
3291
3378
  // src/impls/mistral-conversational.session.ts
@@ -3405,832 +3492,781 @@ class MistralConversationSession {
3405
3492
  });
3406
3493
  this.history.push({ role: "user", content: text });
3407
3494
  const assistantStart = Date.now();
3408
- const assistantText = await this.complete(this.history, {
3409
- ...this.sessionConfig,
3410
- llmModel: this.sessionConfig.llmModel ?? this.defaultModel
3411
- });
3412
- if (this.closed) {
3413
- return;
3414
- }
3415
- const normalizedAssistantText = assistantText.trim();
3416
- const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
3417
- this.queue.push({
3418
- type: "agent_speech_started",
3419
- text: finalAssistantText
3420
- });
3421
- this.queue.push({
3422
- type: "transcript",
3423
- role: "agent",
3424
- text: finalAssistantText,
3425
- timestamp: assistantStart
3426
- });
3427
- this.queue.push({ type: "agent_speech_ended" });
3428
- this.turns.push({
3429
- role: "assistant",
3430
- text: finalAssistantText,
3431
- startMs: assistantStart,
3432
- endMs: Date.now()
3433
- });
3434
- this.history.push({ role: "assistant", content: finalAssistantText });
3435
- }
3436
- emitError(error) {
3437
- if (this.closed) {
3438
- return;
3439
- }
3440
- this.queue.push({ type: "error", error: toError(error) });
3441
- }
3442
- }
3443
- function toError(error) {
3444
- if (error instanceof Error) {
3445
- return error;
3446
- }
3447
- return new Error(String(error));
3448
- }
3449
-
3450
- // src/impls/mistral-conversational.ts
3451
- var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
3452
- var DEFAULT_MODEL2 = "mistral-small-latest";
3453
- var DEFAULT_VOICE = "default";
3454
-
3455
- class MistralConversationalProvider {
3456
- apiKey;
3457
- defaultModel;
3458
- defaultVoiceId;
3459
- baseUrl;
3460
- fetchImpl;
3461
- sttProvider;
3462
- constructor(options) {
3463
- if (!options.apiKey) {
3464
- throw new Error("MistralConversationalProvider requires an apiKey");
3465
- }
3466
- this.apiKey = options.apiKey;
3467
- this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
3468
- this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
3469
- this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
3470
- this.fetchImpl = options.fetchImpl ?? fetch;
3471
- this.sttProvider = options.sttProvider ?? new MistralSttProvider({
3472
- apiKey: options.apiKey,
3473
- defaultModel: options.sttOptions?.defaultModel,
3474
- defaultLanguage: options.sttOptions?.defaultLanguage,
3475
- serverURL: options.sttOptions?.serverURL ?? options.serverURL,
3476
- fetchImpl: this.fetchImpl
3477
- });
3478
- }
3479
- async startSession(config) {
3480
- return new MistralConversationSession({
3481
- sessionConfig: {
3482
- ...config,
3483
- voiceId: config.voiceId || this.defaultVoiceId
3484
- },
3485
- defaultModel: this.defaultModel,
3486
- complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
3487
- sttProvider: this.sttProvider
3488
- });
3489
- }
3490
- async listVoices() {
3491
- return [
3492
- {
3493
- id: this.defaultVoiceId,
3494
- name: "Mistral Default Voice",
3495
- description: "Default conversational voice profile.",
3496
- capabilities: ["conversational"]
3497
- }
3498
- ];
3499
- }
3500
- async completeConversation(history, sessionConfig) {
3501
- const model = sessionConfig.llmModel ?? this.defaultModel;
3502
- const messages = [];
3503
- if (sessionConfig.systemPrompt) {
3504
- messages.push({ role: "system", content: sessionConfig.systemPrompt });
3505
- }
3506
- for (const item of history) {
3507
- messages.push({ role: item.role, content: item.content });
3508
- }
3509
- const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
3510
- method: "POST",
3511
- headers: {
3512
- Authorization: `Bearer ${this.apiKey}`,
3513
- "Content-Type": "application/json"
3514
- },
3515
- body: JSON.stringify({
3516
- model,
3517
- messages
3518
- })
3519
- });
3520
- if (!response.ok) {
3521
- const body = await response.text();
3522
- throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
3523
- }
3524
- const payload = await response.json();
3525
- return readAssistantText(payload);
3526
- }
3527
- }
3528
- function normalizeBaseUrl2(url) {
3529
- return url.endsWith("/") ? url.slice(0, -1) : url;
3530
- }
3531
- function readAssistantText(payload) {
3532
- const record = asRecord3(payload);
3533
- const choices = Array.isArray(record.choices) ? record.choices : [];
3534
- const firstChoice = asRecord3(choices[0]);
3535
- const message = asRecord3(firstChoice.message);
3536
- if (typeof message.content === "string") {
3537
- return message.content;
3538
- }
3539
- if (Array.isArray(message.content)) {
3540
- const textParts = message.content.map((part) => {
3541
- const entry = asRecord3(part);
3542
- const text = entry.text;
3543
- return typeof text === "string" ? text : "";
3544
- }).filter((text) => text.length > 0);
3545
- return textParts.join("");
3546
- }
3547
- return "";
3548
- }
3549
- function asRecord3(value) {
3550
- if (value && typeof value === "object") {
3551
- return value;
3552
- }
3553
- return {};
3554
- }
3555
-
3556
- // src/impls/qdrant-vector.ts
3557
- import { QdrantClient } from "@qdrant/js-client-rest";
3558
-
3559
- class QdrantVectorProvider {
3560
- client;
3561
- createCollectionIfMissing;
3562
- distance;
3563
- constructor(options) {
3564
- this.client = options.client ?? new QdrantClient({
3565
- url: options.url,
3566
- apiKey: options.apiKey,
3567
- ...options.clientParams
3568
- });
3569
- this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
3570
- this.distance = options.distance ?? "Cosine";
3571
- }
3572
- async upsert(request) {
3573
- if (request.documents.length === 0)
3574
- return;
3575
- const firstDocument = request.documents[0];
3576
- if (!firstDocument)
3577
- return;
3578
- const vectorSize = firstDocument.vector.length;
3579
- if (this.createCollectionIfMissing) {
3580
- await this.ensureCollection(request.collection, vectorSize);
3581
- }
3582
- const points = request.documents.map((document) => ({
3583
- id: document.id,
3584
- vector: document.vector,
3585
- payload: {
3586
- ...document.payload,
3587
- ...document.namespace ? { namespace: document.namespace } : {},
3588
- ...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
3589
- }
3590
- }));
3591
- await this.client.upsert(request.collection, {
3592
- wait: true,
3593
- points
3495
+ const assistantText = await this.complete(this.history, {
3496
+ ...this.sessionConfig,
3497
+ llmModel: this.sessionConfig.llmModel ?? this.defaultModel
3594
3498
  });
3595
- }
3596
- async search(query) {
3597
- const results = await this.client.search(query.collection, {
3598
- vector: query.vector,
3599
- limit: query.topK,
3600
- filter: query.filter,
3601
- score_threshold: query.scoreThreshold,
3602
- with_payload: true,
3603
- with_vector: false
3499
+ if (this.closed) {
3500
+ return;
3501
+ }
3502
+ const normalizedAssistantText = assistantText.trim();
3503
+ const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
3504
+ this.queue.push({
3505
+ type: "agent_speech_started",
3506
+ text: finalAssistantText
3604
3507
  });
3605
- return results.map((item) => ({
3606
- id: String(item.id),
3607
- score: item.score,
3608
- payload: item.payload ?? undefined,
3609
- namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
3610
- }));
3611
- }
3612
- async delete(request) {
3613
- await this.client.delete(request.collection, {
3614
- wait: true,
3615
- points: request.ids
3508
+ this.queue.push({
3509
+ type: "transcript",
3510
+ role: "agent",
3511
+ text: finalAssistantText,
3512
+ timestamp: assistantStart
3513
+ });
3514
+ this.queue.push({ type: "agent_speech_ended" });
3515
+ this.turns.push({
3516
+ role: "assistant",
3517
+ text: finalAssistantText,
3518
+ startMs: assistantStart,
3519
+ endMs: Date.now()
3616
3520
  });
3521
+ this.history.push({ role: "assistant", content: finalAssistantText });
3617
3522
  }
3618
- async ensureCollection(collectionName, vectorSize) {
3619
- try {
3620
- await this.client.getCollection(collectionName);
3621
- } catch (_error) {
3622
- await this.client.createCollection(collectionName, {
3623
- vectors: {
3624
- size: vectorSize,
3625
- distance: this.distance
3626
- }
3627
- });
3523
+ emitError(error) {
3524
+ if (this.closed) {
3525
+ return;
3628
3526
  }
3527
+ this.queue.push({ type: "error", error: toError(error) });
3528
+ }
3529
+ }
3530
+ function toError(error) {
3531
+ if (error instanceof Error) {
3532
+ return error;
3629
3533
  }
3534
+ return new Error(String(error));
3630
3535
  }
3631
3536
 
3632
- // src/impls/supabase-psql.ts
3633
- import { Buffer as Buffer3 } from "buffer";
3634
- import { sql as drizzleSql } from "drizzle-orm";
3635
- import { drizzle } from "drizzle-orm/postgres-js";
3636
- import postgres from "postgres";
3537
+ // src/impls/mistral-stt.ts
3538
+ var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
3539
+ var DEFAULT_MODEL = "voxtral-mini-latest";
3540
+ var AUDIO_MIME_BY_FORMAT = {
3541
+ mp3: "audio/mpeg",
3542
+ wav: "audio/wav",
3543
+ ogg: "audio/ogg",
3544
+ pcm: "audio/pcm",
3545
+ opus: "audio/opus"
3546
+ };
3637
3547
 
3638
- class SupabasePostgresProvider {
3639
- client;
3640
- db;
3641
- ownsClient;
3642
- createDrizzle;
3643
- constructor(options = {}) {
3644
- this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
3645
- if (options.db) {
3646
- if (!options.client) {
3647
- throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
3648
- }
3649
- this.client = options.client;
3650
- this.db = options.db;
3651
- this.ownsClient = false;
3652
- return;
3653
- }
3654
- if (options.client) {
3655
- this.client = options.client;
3656
- this.ownsClient = false;
3657
- } else {
3658
- if (!options.connectionString) {
3659
- throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
3660
- }
3661
- this.client = postgres(options.connectionString, {
3662
- max: options.maxConnections,
3663
- prepare: false,
3664
- ssl: resolveSslMode(options.sslMode)
3665
- });
3666
- this.ownsClient = true;
3548
+ class MistralSttProvider {
3549
+ apiKey;
3550
+ defaultModel;
3551
+ defaultLanguage;
3552
+ baseUrl;
3553
+ fetchImpl;
3554
+ constructor(options) {
3555
+ if (!options.apiKey) {
3556
+ throw new Error("MistralSttProvider requires an apiKey");
3667
3557
  }
3668
- this.db = this.createDrizzle(this.client);
3669
- }
3670
- async query(statement, params = []) {
3671
- const query = buildParameterizedSql(statement, params);
3672
- const result = await this.db.execute(query);
3673
- const rows = asRows(result);
3674
- return {
3675
- rows,
3676
- rowCount: rows.length
3677
- };
3678
- }
3679
- async execute(statement, params = []) {
3680
- const query = buildParameterizedSql(statement, params);
3681
- await this.db.execute(query);
3558
+ this.apiKey = options.apiKey;
3559
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
3560
+ this.defaultLanguage = options.defaultLanguage;
3561
+ this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
3562
+ this.fetchImpl = options.fetchImpl ?? fetch;
3682
3563
  }
3683
- async transaction(run) {
3684
- const transactionResult = this.client.begin(async (transactionClient) => {
3685
- const transactionalProvider = new SupabasePostgresProvider({
3686
- client: transactionClient,
3687
- db: this.createDrizzle(transactionClient),
3688
- createDrizzle: this.createDrizzle
3689
- });
3690
- return run(transactionalProvider);
3564
+ async transcribe(input) {
3565
+ const formData = new FormData;
3566
+ const model = input.model ?? this.defaultModel;
3567
+ const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
3568
+ const fileName = `audio.${input.audio.format}`;
3569
+ const audioBytes = new Uint8Array(input.audio.data);
3570
+ const blob = new Blob([audioBytes], { type: mimeType });
3571
+ formData.append("file", blob, fileName);
3572
+ formData.append("model", model);
3573
+ formData.append("response_format", "verbose_json");
3574
+ const language = input.language ?? this.defaultLanguage;
3575
+ if (language) {
3576
+ formData.append("language", language);
3577
+ }
3578
+ const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
3579
+ method: "POST",
3580
+ headers: {
3581
+ Authorization: `Bearer ${this.apiKey}`
3582
+ },
3583
+ body: formData
3691
3584
  });
3692
- return transactionResult;
3693
- }
3694
- async close() {
3695
- if (this.ownsClient) {
3696
- await this.client.end({ timeout: 5 });
3585
+ if (!response.ok) {
3586
+ const body = await response.text();
3587
+ throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
3697
3588
  }
3589
+ const payload = await response.json();
3590
+ return toTranscriptionResult(payload, input);
3698
3591
  }
3699
3592
  }
3700
- function buildParameterizedSql(statement, params) {
3701
- const segments = [];
3702
- const pattern = /\$(\d+)/g;
3703
- let cursor = 0;
3704
- for (const match of statement.matchAll(pattern)) {
3705
- const token = match[0];
3706
- const indexPart = match[1];
3707
- const start = match.index;
3708
- if (indexPart == null || start == null)
3709
- continue;
3710
- const parameterIndex = Number(indexPart) - 1;
3711
- if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
3712
- throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
3713
- }
3714
- const staticSegment = statement.slice(cursor, start);
3715
- if (staticSegment.length > 0) {
3716
- segments.push(drizzleSql.raw(staticSegment));
3717
- }
3718
- const parameterValue = params[parameterIndex];
3719
- if (parameterValue === undefined) {
3720
- throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
3721
- }
3722
- const normalizedValue = normalizeParam(parameterValue);
3723
- segments.push(drizzleSql`${normalizedValue}`);
3724
- cursor = start + token.length;
3725
- }
3726
- const tailSegment = statement.slice(cursor);
3727
- if (tailSegment.length > 0) {
3728
- segments.push(drizzleSql.raw(tailSegment));
3729
- }
3730
- if (segments.length === 0) {
3731
- return drizzleSql.raw("");
3593
+ function toTranscriptionResult(payload, input) {
3594
+ const record = asRecord2(payload);
3595
+ const text = readString3(record, "text") ?? "";
3596
+ const language = readString3(record, "language") ?? input.language ?? "unknown";
3597
+ const segments = parseSegments(record);
3598
+ if (segments.length === 0 && text.length > 0) {
3599
+ segments.push({
3600
+ text,
3601
+ startMs: 0,
3602
+ endMs: input.audio.durationMs ?? 0
3603
+ });
3732
3604
  }
3733
- return drizzleSql.join(segments);
3605
+ const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
3606
+ const topLevelWords = parseWordTimings(record.words);
3607
+ const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
3608
+ const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
3609
+ const speakers = dedupeSpeakers(segments);
3610
+ return {
3611
+ text,
3612
+ segments,
3613
+ language,
3614
+ durationMs,
3615
+ speakers: speakers.length > 0 ? speakers : undefined,
3616
+ wordTimings
3617
+ };
3734
3618
  }
3735
- function normalizeParam(value) {
3736
- if (typeof value === "bigint") {
3737
- return value.toString();
3738
- }
3739
- if (value instanceof Uint8Array) {
3740
- return Buffer3.from(value);
3619
+ function parseSegments(record) {
3620
+ if (!Array.isArray(record.segments)) {
3621
+ return [];
3741
3622
  }
3742
- if (isPlainObject(value)) {
3743
- return JSON.stringify(value);
3623
+ const parsed = [];
3624
+ for (const entry of record.segments) {
3625
+ const segmentRecord = asRecord2(entry);
3626
+ const text = readString3(segmentRecord, "text");
3627
+ if (!text) {
3628
+ continue;
3629
+ }
3630
+ const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
3631
+ const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
3632
+ parsed.push({
3633
+ text,
3634
+ startMs: secondsToMs(startSeconds),
3635
+ endMs: secondsToMs(endSeconds),
3636
+ speakerId: readString3(segmentRecord, "speaker") ?? undefined,
3637
+ confidence: readNumber2(segmentRecord, "confidence"),
3638
+ wordTimings: parseWordTimings(segmentRecord.words)
3639
+ });
3744
3640
  }
3745
- return value;
3641
+ return parsed;
3746
3642
  }
3747
- function asRows(result) {
3748
- if (!Array.isArray(result)) {
3643
+ function parseWordTimings(value) {
3644
+ if (!Array.isArray(value)) {
3749
3645
  return [];
3750
3646
  }
3751
- return result;
3752
- }
3753
- function isPlainObject(value) {
3754
- if (value == null || typeof value !== "object") {
3755
- return false;
3756
- }
3757
- if (Array.isArray(value)) {
3758
- return false;
3759
- }
3760
- if (value instanceof Date) {
3761
- return false;
3647
+ const words = [];
3648
+ for (const entry of value) {
3649
+ const wordRecord = asRecord2(entry);
3650
+ const word = readString3(wordRecord, "word");
3651
+ const startSeconds = readNumber2(wordRecord, "start");
3652
+ const endSeconds = readNumber2(wordRecord, "end");
3653
+ if (!word || startSeconds == null || endSeconds == null) {
3654
+ continue;
3655
+ }
3656
+ words.push({
3657
+ word,
3658
+ startMs: secondsToMs(startSeconds),
3659
+ endMs: secondsToMs(endSeconds),
3660
+ confidence: readNumber2(wordRecord, "confidence")
3661
+ });
3762
3662
  }
3763
- if (value instanceof Uint8Array) {
3764
- return false;
3663
+ return words;
3664
+ }
3665
+ function dedupeSpeakers(segments) {
3666
+ const seen = new Set;
3667
+ const speakers = [];
3668
+ for (const segment of segments) {
3669
+ if (!segment.speakerId || seen.has(segment.speakerId)) {
3670
+ continue;
3671
+ }
3672
+ seen.add(segment.speakerId);
3673
+ speakers.push({
3674
+ id: segment.speakerId,
3675
+ name: segment.speakerName
3676
+ });
3765
3677
  }
3766
- return true;
3678
+ return speakers;
3767
3679
  }
3768
- function resolveSslMode(mode) {
3769
- switch (mode) {
3770
- case "allow":
3771
- return false;
3772
- case "prefer":
3773
- return "prefer";
3774
- case "require":
3775
- default:
3776
- return "require";
3680
+ function normalizeBaseUrl(url) {
3681
+ return url.endsWith("/") ? url.slice(0, -1) : url;
3682
+ }
3683
+ function asRecord2(value) {
3684
+ if (value && typeof value === "object") {
3685
+ return value;
3777
3686
  }
3687
+ return {};
3688
+ }
3689
+ function readString3(record, key) {
3690
+ const value = record[key];
3691
+ return typeof value === "string" ? value : undefined;
3692
+ }
3693
+ function readNumber2(record, key) {
3694
+ const value = record[key];
3695
+ return typeof value === "number" ? value : undefined;
3696
+ }
3697
+ function secondsToMs(value) {
3698
+ return Math.round(value * 1000);
3778
3699
  }
3779
3700
 
3780
- // src/impls/supabase-vector.ts
3781
- class SupabaseVectorProvider {
3782
- database;
3783
- createTableIfMissing;
3784
- distanceMetric;
3785
- quotedSchema;
3786
- qualifiedTable;
3787
- collectionIndex;
3788
- namespaceIndex;
3789
- ensureTablePromise;
3701
+ // src/impls/mistral-conversational.ts
3702
+ var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
3703
+ var DEFAULT_MODEL2 = "mistral-small-latest";
3704
+ var DEFAULT_VOICE = "default";
3705
+
3706
+ class MistralConversationalProvider {
3707
+ apiKey;
3708
+ defaultModel;
3709
+ defaultVoiceId;
3710
+ baseUrl;
3711
+ fetchImpl;
3712
+ sttProvider;
3790
3713
  constructor(options) {
3791
- this.database = options.database ?? new SupabasePostgresProvider({
3792
- connectionString: options.connectionString,
3793
- maxConnections: options.maxConnections,
3794
- sslMode: options.sslMode
3795
- });
3796
- this.createTableIfMissing = options.createTableIfMissing ?? true;
3797
- this.distanceMetric = options.distanceMetric ?? "cosine";
3798
- const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
3799
- const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
3800
- this.quotedSchema = quoteIdentifier(schema);
3801
- this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
3802
- this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
3803
- this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
3804
- }
3805
- async upsert(request) {
3806
- if (request.documents.length === 0) {
3807
- return;
3808
- }
3809
- if (this.createTableIfMissing) {
3810
- await this.ensureTable();
3811
- }
3812
- for (const document of request.documents) {
3813
- await this.database.execute(`INSERT INTO ${this.qualifiedTable}
3814
- (collection, id, embedding, payload, namespace, expires_at, updated_at)
3815
- VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
3816
- ON CONFLICT (collection, id)
3817
- DO UPDATE SET
3818
- embedding = EXCLUDED.embedding,
3819
- payload = EXCLUDED.payload,
3820
- namespace = EXCLUDED.namespace,
3821
- expires_at = EXCLUDED.expires_at,
3822
- updated_at = now();`, [
3823
- request.collection,
3824
- document.id,
3825
- toVectorLiteral(document.vector),
3826
- document.payload ? JSON.stringify(document.payload) : null,
3827
- document.namespace ?? null,
3828
- document.expiresAt ?? null
3829
- ]);
3714
+ if (!options.apiKey) {
3715
+ throw new Error("MistralConversationalProvider requires an apiKey");
3830
3716
  }
3717
+ this.apiKey = options.apiKey;
3718
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
3719
+ this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
3720
+ this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
3721
+ this.fetchImpl = options.fetchImpl ?? fetch;
3722
+ this.sttProvider = options.sttProvider ?? new MistralSttProvider({
3723
+ apiKey: options.apiKey,
3724
+ defaultModel: options.sttOptions?.defaultModel,
3725
+ defaultLanguage: options.sttOptions?.defaultLanguage,
3726
+ serverURL: options.sttOptions?.serverURL ?? options.serverURL,
3727
+ fetchImpl: this.fetchImpl
3728
+ });
3831
3729
  }
3832
- async search(query) {
3833
- const operator = this.distanceOperator;
3834
- const results = await this.database.query(`SELECT
3835
- id,
3836
- payload,
3837
- namespace,
3838
- (embedding ${operator} $3::vector) AS distance
3839
- FROM ${this.qualifiedTable}
3840
- WHERE collection = $1
3841
- AND ($2::text IS NULL OR namespace = $2)
3842
- AND (expires_at IS NULL OR expires_at > now())
3843
- AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
3844
- ORDER BY embedding ${operator} $3::vector
3845
- LIMIT $5;`, [
3846
- query.collection,
3847
- query.namespace ?? null,
3848
- toVectorLiteral(query.vector),
3849
- query.filter ? JSON.stringify(query.filter) : null,
3850
- query.topK
3851
- ]);
3852
- const mapped = results.rows.map((row) => {
3853
- const distance = Number(row.distance);
3854
- return {
3855
- id: row.id,
3856
- score: distanceToScore(distance, this.distanceMetric),
3857
- payload: isRecord(row.payload) ? row.payload : undefined,
3858
- namespace: row.namespace ?? undefined
3859
- };
3730
+ async startSession(config) {
3731
+ return new MistralConversationSession({
3732
+ sessionConfig: {
3733
+ ...config,
3734
+ voiceId: config.voiceId || this.defaultVoiceId
3735
+ },
3736
+ defaultModel: this.defaultModel,
3737
+ complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
3738
+ sttProvider: this.sttProvider
3860
3739
  });
3861
- const scoreThreshold = query.scoreThreshold;
3862
- if (scoreThreshold == null) {
3863
- return mapped;
3864
- }
3865
- return mapped.filter((result) => result.score >= scoreThreshold);
3866
3740
  }
3867
- async delete(request) {
3868
- if (request.ids.length === 0) {
3869
- return;
3870
- }
3871
- const params = [
3872
- request.collection,
3873
- request.ids,
3874
- request.namespace ?? null
3741
+ async listVoices() {
3742
+ return [
3743
+ {
3744
+ id: this.defaultVoiceId,
3745
+ name: "Mistral Default Voice",
3746
+ description: "Default conversational voice profile.",
3747
+ capabilities: ["conversational"]
3748
+ }
3875
3749
  ];
3876
- await this.database.execute(`DELETE FROM ${this.qualifiedTable}
3877
- WHERE collection = $1
3878
- AND id = ANY($2::text[])
3879
- AND ($3::text IS NULL OR namespace = $3);`, params);
3880
3750
  }
3881
- async ensureTable() {
3882
- if (!this.ensureTablePromise) {
3883
- this.ensureTablePromise = this.createTable();
3751
+ async completeConversation(history, sessionConfig) {
3752
+ const model = sessionConfig.llmModel ?? this.defaultModel;
3753
+ const messages = [];
3754
+ if (sessionConfig.systemPrompt) {
3755
+ messages.push({ role: "system", content: sessionConfig.systemPrompt });
3884
3756
  }
3885
- await this.ensureTablePromise;
3886
- }
3887
- async createTable() {
3888
- await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
3889
- await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
3890
- await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
3891
- collection text NOT NULL,
3892
- id text NOT NULL,
3893
- embedding vector NOT NULL,
3894
- payload jsonb,
3895
- namespace text,
3896
- expires_at timestamptz,
3897
- created_at timestamptz NOT NULL DEFAULT now(),
3898
- updated_at timestamptz NOT NULL DEFAULT now(),
3899
- PRIMARY KEY (collection, id)
3900
- );`);
3901
- await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
3902
- ON ${this.qualifiedTable} (collection);`);
3903
- await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
3904
- ON ${this.qualifiedTable} (namespace);`);
3905
- }
3906
- get distanceOperator() {
3907
- switch (this.distanceMetric) {
3908
- case "l2":
3909
- return "<->";
3910
- case "inner_product":
3911
- return "<#>";
3912
- case "cosine":
3913
- default:
3914
- return "<=>";
3757
+ for (const item of history) {
3758
+ messages.push({ role: item.role, content: item.content });
3915
3759
  }
3760
+ const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
3761
+ method: "POST",
3762
+ headers: {
3763
+ Authorization: `Bearer ${this.apiKey}`,
3764
+ "Content-Type": "application/json"
3765
+ },
3766
+ body: JSON.stringify({
3767
+ model,
3768
+ messages
3769
+ })
3770
+ });
3771
+ if (!response.ok) {
3772
+ const body = await response.text();
3773
+ throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
3774
+ }
3775
+ const payload = await response.json();
3776
+ return readAssistantText(payload);
3916
3777
  }
3917
3778
  }
3918
- function sanitizeIdentifier(value, label) {
3919
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
3920
- throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
3921
- }
3922
- return value;
3923
- }
3924
- function quoteIdentifier(value) {
3925
- return `"${value.replaceAll('"', '""')}"`;
3779
+ function normalizeBaseUrl2(url) {
3780
+ return url.endsWith("/") ? url.slice(0, -1) : url;
3926
3781
  }
3927
- function toVectorLiteral(vector) {
3928
- if (vector.length === 0) {
3929
- throw new Error("Supabase vectors must contain at least one dimension.");
3782
+ function readAssistantText(payload) {
3783
+ const record = asRecord3(payload);
3784
+ const choices = Array.isArray(record.choices) ? record.choices : [];
3785
+ const firstChoice = asRecord3(choices[0]);
3786
+ const message = asRecord3(firstChoice.message);
3787
+ if (typeof message.content === "string") {
3788
+ return message.content;
3930
3789
  }
3931
- for (const value of vector) {
3932
- if (!Number.isFinite(value)) {
3933
- throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
3934
- }
3790
+ if (Array.isArray(message.content)) {
3791
+ const textParts = message.content.map((part) => {
3792
+ const entry = asRecord3(part);
3793
+ const text = entry.text;
3794
+ return typeof text === "string" ? text : "";
3795
+ }).filter((text) => text.length > 0);
3796
+ return textParts.join("");
3935
3797
  }
3936
- return `[${vector.join(",")}]`;
3937
- }
3938
- function isRecord(value) {
3939
- return typeof value === "object" && value !== null && !Array.isArray(value);
3798
+ return "";
3940
3799
  }
3941
- function distanceToScore(distance, metric) {
3942
- switch (metric) {
3943
- case "inner_product":
3944
- return -distance;
3945
- case "l2":
3946
- return 1 / (1 + distance);
3947
- case "cosine":
3948
- default:
3949
- return 1 - distance;
3800
+ function asRecord3(value) {
3801
+ if (value && typeof value === "object") {
3802
+ return value;
3950
3803
  }
3804
+ return {};
3951
3805
  }
3952
3806
 
3953
- // src/impls/stripe-payments.ts
3954
- import Stripe from "stripe";
3955
- var API_VERSION = "2026-02-25.clover";
3807
+ // src/impls/mistral-embedding.ts
3808
+ import { Mistral } from "@mistralai/mistralai";
3956
3809
 
3957
- class StripePaymentsProvider {
3958
- stripe;
3810
+ class MistralEmbeddingProvider {
3811
+ client;
3812
+ defaultModel;
3959
3813
  constructor(options) {
3960
- this.stripe = options.stripe ?? new Stripe(options.apiKey, {
3961
- apiVersion: API_VERSION
3814
+ if (!options.apiKey) {
3815
+ throw new Error("MistralEmbeddingProvider requires an apiKey");
3816
+ }
3817
+ this.client = options.client ?? new Mistral({
3818
+ apiKey: options.apiKey,
3819
+ serverURL: options.serverURL
3962
3820
  });
3821
+ this.defaultModel = options.defaultModel ?? "mistral-embed";
3963
3822
  }
3964
- async createCustomer(input) {
3965
- const customer = await this.stripe.customers.create({
3966
- email: input.email,
3967
- name: input.name,
3968
- description: input.description,
3969
- metadata: input.metadata
3823
+ async embedDocuments(documents, options) {
3824
+ if (documents.length === 0)
3825
+ return [];
3826
+ const model = options?.model ?? this.defaultModel;
3827
+ const response = await this.client.embeddings.create({
3828
+ model,
3829
+ inputs: documents.map((doc) => doc.text)
3970
3830
  });
3971
- return this.toCustomer(customer);
3831
+ return response.data.map((item, index) => ({
3832
+ id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
3833
+ vector: item.embedding ?? [],
3834
+ dimensions: item.embedding?.length ?? 0,
3835
+ model: response.model,
3836
+ metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
3837
+ }));
3972
3838
  }
3973
- async getCustomer(customerId) {
3974
- const customer = await this.stripe.customers.retrieve(customerId);
3975
- if (customer.deleted)
3976
- return null;
3977
- return this.toCustomer(customer);
3839
+ async embedQuery(query, options) {
3840
+ const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
3841
+ if (!result) {
3842
+ throw new Error("Failed to compute embedding for query");
3843
+ }
3844
+ return result;
3978
3845
  }
3979
- async createPaymentIntent(input) {
3980
- const intent = await this.stripe.paymentIntents.create({
3981
- amount: input.amount.amount,
3982
- currency: input.amount.currency,
3983
- customer: input.customerId,
3984
- description: input.description,
3985
- capture_method: input.captureMethod ?? "automatic",
3986
- confirmation_method: input.confirmationMethod ?? "automatic",
3987
- automatic_payment_methods: { enabled: true },
3988
- metadata: input.metadata,
3989
- return_url: input.returnUrl,
3990
- statement_descriptor: input.statementDescriptor
3846
+ }
3847
+
3848
+ // src/impls/mistral-llm.ts
3849
+ import { Mistral as Mistral2 } from "@mistralai/mistralai";
3850
+
3851
+ class MistralLLMProvider {
3852
+ client;
3853
+ defaultModel;
3854
+ constructor(options) {
3855
+ if (!options.apiKey) {
3856
+ throw new Error("MistralLLMProvider requires an apiKey");
3857
+ }
3858
+ this.client = options.client ?? new Mistral2({
3859
+ apiKey: options.apiKey,
3860
+ serverURL: options.serverURL,
3861
+ userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
3991
3862
  });
3992
- return this.toPaymentIntent(intent);
3993
- }
3994
- async capturePayment(paymentIntentId, input) {
3995
- const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
3996
- return this.toPaymentIntent(intent);
3863
+ this.defaultModel = options.defaultModel ?? "mistral-large-latest";
3997
3864
  }
3998
- async cancelPaymentIntent(paymentIntentId) {
3999
- const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
4000
- return this.toPaymentIntent(intent);
3865
+ async chat(messages, options = {}) {
3866
+ const request = this.buildChatRequest(messages, options);
3867
+ const response = await this.client.chat.complete(request);
3868
+ return this.buildLLMResponse(response);
4001
3869
  }
4002
- async refundPayment(input) {
4003
- const refund = await this.stripe.refunds.create({
4004
- payment_intent: input.paymentIntentId,
4005
- amount: input.amount?.amount,
4006
- reason: mapRefundReason(input.reason),
4007
- metadata: input.metadata
4008
- });
4009
- const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
4010
- return {
4011
- id: refund.id,
4012
- paymentIntentId,
4013
- amount: {
4014
- amount: refund.amount ?? 0,
4015
- currency: refund.currency?.toUpperCase() ?? "USD"
4016
- },
4017
- status: mapRefundStatus(refund.status),
4018
- reason: refund.reason ?? undefined,
4019
- metadata: this.toMetadata(refund.metadata),
4020
- createdAt: refund.created ? new Date(refund.created * 1000) : undefined
3870
+ async* stream(messages, options = {}) {
3871
+ const request = this.buildChatRequest(messages, options);
3872
+ request.stream = true;
3873
+ const stream = await this.client.chat.stream(request);
3874
+ const aggregatedParts = [];
3875
+ const aggregatedToolCalls = [];
3876
+ let usage;
3877
+ let finishReason;
3878
+ for await (const event of stream) {
3879
+ for (const choice of event.data.choices) {
3880
+ const delta = choice.delta;
3881
+ if (typeof delta.content === "string") {
3882
+ if (delta.content.length > 0) {
3883
+ aggregatedParts.push({ type: "text", text: delta.content });
3884
+ yield {
3885
+ type: "message_delta",
3886
+ delta: { type: "text", text: delta.content },
3887
+ index: choice.index
3888
+ };
3889
+ }
3890
+ } else if (Array.isArray(delta.content)) {
3891
+ for (const chunk of delta.content) {
3892
+ if (chunk.type === "text" && "text" in chunk) {
3893
+ aggregatedParts.push({ type: "text", text: chunk.text });
3894
+ yield {
3895
+ type: "message_delta",
3896
+ delta: { type: "text", text: chunk.text },
3897
+ index: choice.index
3898
+ };
3899
+ }
3900
+ }
3901
+ }
3902
+ if (delta.toolCalls) {
3903
+ let localIndex = 0;
3904
+ for (const call of delta.toolCalls) {
3905
+ const toolCall = this.fromMistralToolCall(call, localIndex);
3906
+ aggregatedToolCalls.push(toolCall);
3907
+ yield {
3908
+ type: "tool_call",
3909
+ call: toolCall,
3910
+ index: choice.index
3911
+ };
3912
+ localIndex += 1;
3913
+ }
3914
+ }
3915
+ if (choice.finishReason && choice.finishReason !== "null") {
3916
+ finishReason = choice.finishReason;
3917
+ }
3918
+ }
3919
+ if (event.data.usage) {
3920
+ const usageEntry = this.fromUsage(event.data.usage);
3921
+ if (usageEntry) {
3922
+ usage = usageEntry;
3923
+ yield { type: "usage", usage: usageEntry };
3924
+ }
3925
+ }
3926
+ }
3927
+ const message = {
3928
+ role: "assistant",
3929
+ content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
3930
+ };
3931
+ if (aggregatedToolCalls.length > 0) {
3932
+ message.content = [
3933
+ ...aggregatedToolCalls,
3934
+ ...aggregatedParts.length ? aggregatedParts : []
3935
+ ];
3936
+ }
3937
+ yield {
3938
+ type: "end",
3939
+ response: {
3940
+ message,
3941
+ usage,
3942
+ finishReason: mapFinishReason(finishReason)
3943
+ }
4021
3944
  };
4022
3945
  }
4023
- async listInvoices(query) {
4024
- const requestedStatus = query?.status?.[0];
4025
- const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
4026
- const response = await this.stripe.invoices.list({
4027
- customer: query?.customerId,
4028
- status: stripeStatus,
4029
- limit: query?.limit,
4030
- starting_after: query?.startingAfter
4031
- });
4032
- return response.data.map((invoice) => this.toInvoice(invoice));
4033
- }
4034
- async listTransactions(query) {
4035
- const response = await this.stripe.charges.list({
4036
- customer: query?.customerId,
4037
- payment_intent: query?.paymentIntentId,
4038
- limit: query?.limit,
4039
- starting_after: query?.startingAfter
4040
- });
4041
- return response.data.map((charge) => ({
4042
- id: charge.id,
4043
- paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
4044
- amount: {
4045
- amount: charge.amount,
4046
- currency: charge.currency?.toUpperCase() ?? "USD"
4047
- },
4048
- type: "capture",
4049
- status: mapChargeStatus(charge.status),
4050
- description: charge.description ?? undefined,
4051
- createdAt: new Date(charge.created * 1000),
4052
- metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
4053
- balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
4054
- })
4055
- }));
3946
+ async countTokens(_messages) {
3947
+ throw new Error("Mistral API does not currently support token counting");
4056
3948
  }
4057
- toCustomer(customer) {
4058
- const metadata = this.toMetadata(customer.metadata);
4059
- const updatedAtValue = metadata?.updatedAt;
4060
- return {
4061
- id: customer.id,
4062
- email: customer.email ?? undefined,
4063
- name: customer.name ?? undefined,
4064
- metadata,
4065
- createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
4066
- updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
3949
+ buildChatRequest(messages, options) {
3950
+ const model = options.model ?? this.defaultModel;
3951
+ const mappedMessages = messages.map((message) => this.toMistralMessage(message));
3952
+ const request = {
3953
+ model,
3954
+ messages: mappedMessages
4067
3955
  };
3956
+ if (options.temperature != null) {
3957
+ request.temperature = options.temperature;
3958
+ }
3959
+ if (options.topP != null) {
3960
+ request.topP = options.topP;
3961
+ }
3962
+ if (options.maxOutputTokens != null) {
3963
+ request.maxTokens = options.maxOutputTokens;
3964
+ }
3965
+ if (options.stopSequences?.length) {
3966
+ request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
3967
+ }
3968
+ if (options.tools?.length) {
3969
+ request.tools = options.tools.map((tool) => ({
3970
+ type: "function",
3971
+ function: {
3972
+ name: tool.name,
3973
+ description: tool.description,
3974
+ parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
3975
+ }
3976
+ }));
3977
+ }
3978
+ if (options.responseFormat === "json") {
3979
+ request.responseFormat = { type: "json_object" };
3980
+ }
3981
+ return request;
4068
3982
  }
4069
- toPaymentIntent(intent) {
4070
- const metadata = this.toMetadata(intent.metadata);
3983
+ buildLLMResponse(response) {
3984
+ const firstChoice = response.choices[0];
3985
+ if (!firstChoice) {
3986
+ return {
3987
+ message: {
3988
+ role: "assistant",
3989
+ content: [{ type: "text", text: "" }]
3990
+ },
3991
+ usage: this.fromUsage(response.usage),
3992
+ raw: response
3993
+ };
3994
+ }
3995
+ const message = this.fromAssistantMessage(firstChoice.message);
4071
3996
  return {
4072
- id: intent.id,
4073
- amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
4074
- status: mapPaymentIntentStatus(intent.status),
4075
- customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
4076
- description: intent.description ?? undefined,
4077
- clientSecret: intent.client_secret ?? undefined,
4078
- metadata,
4079
- createdAt: new Date(intent.created * 1000),
4080
- updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
3997
+ message,
3998
+ usage: this.fromUsage(response.usage),
3999
+ finishReason: mapFinishReason(firstChoice.finishReason),
4000
+ raw: response
4081
4001
  };
4082
4002
  }
4083
- toInvoice(invoice) {
4084
- const metadata = this.toMetadata(invoice.metadata);
4003
+ fromUsage(usage) {
4004
+ if (!usage)
4005
+ return;
4085
4006
  return {
4086
- id: invoice.id,
4087
- number: invoice.number ?? undefined,
4088
- status: invoice.status ?? "draft",
4089
- amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
4090
- amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
4091
- customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
4092
- dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
4093
- hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
4094
- metadata,
4095
- createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
4096
- updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
4007
+ promptTokens: usage.promptTokens ?? 0,
4008
+ completionTokens: usage.completionTokens ?? 0,
4009
+ totalTokens: usage.totalTokens ?? 0
4097
4010
  };
4098
4011
  }
4099
- toMoney(amount, currency) {
4012
+ fromAssistantMessage(message) {
4013
+ const parts = [];
4014
+ if (typeof message.content === "string") {
4015
+ parts.push({ type: "text", text: message.content });
4016
+ } else if (Array.isArray(message.content)) {
4017
+ message.content.forEach((chunk) => {
4018
+ if (chunk.type === "text" && "text" in chunk) {
4019
+ parts.push({ type: "text", text: chunk.text });
4020
+ }
4021
+ });
4022
+ }
4023
+ const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
4024
+ if (toolCalls.length > 0) {
4025
+ parts.splice(0, 0, ...toolCalls);
4026
+ }
4027
+ if (parts.length === 0) {
4028
+ parts.push({ type: "text", text: "" });
4029
+ }
4100
4030
  return {
4101
- amount,
4102
- currency: currency?.toUpperCase() ?? "USD"
4031
+ role: "assistant",
4032
+ content: parts
4103
4033
  };
4104
4034
  }
4105
- toMetadata(metadata) {
4106
- if (!metadata)
4107
- return;
4108
- const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
4109
- if (entries.length === 0)
4110
- return;
4111
- return Object.fromEntries(entries);
4112
- }
4113
- mergeMetadata(base, extras) {
4114
- const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
4115
- if (!base && filteredExtras.length === 0) {
4116
- return;
4117
- }
4035
+ fromMistralToolCall(call, index) {
4036
+ const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
4118
4037
  return {
4119
- ...base ?? {},
4120
- ...Object.fromEntries(filteredExtras)
4038
+ type: "tool-call",
4039
+ id: call.id ?? `tool-call-${index}`,
4040
+ name: call.function.name,
4041
+ arguments: args
4121
4042
  };
4122
4043
  }
4123
- }
4124
- function mapRefundReason(reason) {
4125
- if (!reason)
4126
- return;
4127
- const allowed = [
4128
- "duplicate",
4129
- "fraudulent",
4130
- "requested_by_customer"
4131
- ];
4132
- return allowed.includes(reason) ? reason : undefined;
4133
- }
4134
- function mapPaymentIntentStatus(status) {
4135
- switch (status) {
4136
- case "requires_payment_method":
4137
- return "requires_payment_method";
4138
- case "requires_confirmation":
4139
- return "requires_confirmation";
4140
- case "requires_action":
4141
- case "requires_capture":
4142
- return "requires_action";
4143
- case "processing":
4144
- return "processing";
4145
- case "succeeded":
4146
- return "succeeded";
4147
- case "canceled":
4148
- return "canceled";
4149
- default:
4150
- return "requires_payment_method";
4044
+ toMistralMessage(message) {
4045
+ const textContent = this.extractText(message.content);
4046
+ const toolCalls = this.extractToolCalls(message);
4047
+ switch (message.role) {
4048
+ case "system":
4049
+ return {
4050
+ role: "system",
4051
+ content: textContent ?? ""
4052
+ };
4053
+ case "user":
4054
+ return {
4055
+ role: "user",
4056
+ content: textContent ?? ""
4057
+ };
4058
+ case "assistant":
4059
+ return {
4060
+ role: "assistant",
4061
+ content: toolCalls.length > 0 ? null : textContent ?? "",
4062
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
4063
+ };
4064
+ case "tool":
4065
+ return {
4066
+ role: "tool",
4067
+ content: textContent ?? "",
4068
+ toolCallId: message.toolCallId ?? toolCalls[0]?.id
4069
+ };
4070
+ default:
4071
+ return {
4072
+ role: "user",
4073
+ content: textContent ?? ""
4074
+ };
4075
+ }
4151
4076
  }
4152
- }
4153
- function mapRefundStatus(status) {
4154
- switch (status) {
4155
- case "pending":
4156
- case "succeeded":
4157
- case "failed":
4158
- case "canceled":
4159
- return status;
4160
- default:
4161
- return "pending";
4077
+ extractText(parts) {
4078
+ const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
4079
+ if (textParts.length === 0)
4080
+ return null;
4081
+ return textParts.join("");
4162
4082
  }
4163
- }
4164
- function mapChargeStatus(status) {
4165
- switch (status) {
4166
- case "pending":
4167
- case "processing":
4168
- return "pending";
4169
- case "succeeded":
4170
- return "succeeded";
4171
- case "failed":
4172
- case "canceled":
4173
- return "failed";
4083
+ extractToolCalls(message) {
4084
+ const toolCallParts = message.content.filter((part) => part.type === "tool-call");
4085
+ return toolCallParts.map((call, index) => ({
4086
+ id: call.id ?? `call_${index}`,
4087
+ type: "function",
4088
+ index,
4089
+ function: {
4090
+ name: call.name,
4091
+ arguments: call.arguments
4092
+ }
4093
+ }));
4094
+ }
4095
+ }
4096
+ function mapFinishReason(reason) {
4097
+ if (!reason)
4098
+ return;
4099
+ const normalized = reason.toLowerCase();
4100
+ switch (normalized) {
4101
+ case "stop":
4102
+ return "stop";
4103
+ case "length":
4104
+ return "length";
4105
+ case "tool_call":
4106
+ case "tool_calls":
4107
+ return "tool_call";
4108
+ case "content_filter":
4109
+ return "content_filter";
4174
4110
  default:
4175
- return "pending";
4111
+ return;
4176
4112
  }
4177
4113
  }
4178
4114
 
4179
- // src/impls/postmark-email.ts
4180
- import { ServerClient } from "postmark";
4115
+ // src/impls/notion.ts
4116
+ import { Client } from "@notionhq/client";
4181
4117
 
4182
- class PostmarkEmailProvider {
4118
+ class NotionProjectManagementProvider {
4183
4119
  client;
4184
- defaultFromEmail;
4185
- messageStream;
4120
+ defaults;
4186
4121
  constructor(options) {
4187
- this.client = options.client ?? new ServerClient(options.serverToken, {
4188
- useHttps: true
4122
+ this.client = options.client ?? new Client({ auth: options.apiKey });
4123
+ this.defaults = {
4124
+ databaseId: options.databaseId,
4125
+ summaryParentPageId: options.summaryParentPageId,
4126
+ titleProperty: options.titleProperty,
4127
+ statusProperty: options.statusProperty,
4128
+ priorityProperty: options.priorityProperty,
4129
+ tagsProperty: options.tagsProperty,
4130
+ dueDateProperty: options.dueDateProperty,
4131
+ descriptionProperty: options.descriptionProperty
4132
+ };
4133
+ }
4134
+ async createWorkItem(input) {
4135
+ if (input.type === "summary" && this.defaults.summaryParentPageId) {
4136
+ return this.createSummaryPage(input);
4137
+ }
4138
+ const databaseId = this.defaults.databaseId;
4139
+ if (!databaseId) {
4140
+ throw new Error("Notion databaseId is required to create work items.");
4141
+ }
4142
+ const titleProperty = this.defaults.titleProperty ?? "Name";
4143
+ const properties = {
4144
+ [titleProperty]: buildTitleProperty(input.title)
4145
+ };
4146
+ applySelect(properties, this.defaults.statusProperty, input.status);
4147
+ applySelect(properties, this.defaults.priorityProperty, input.priority);
4148
+ applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
4149
+ applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
4150
+ applyRichText(properties, this.defaults.descriptionProperty, input.description);
4151
+ const page = await this.client.pages.create({
4152
+ parent: { type: "database_id", database_id: databaseId },
4153
+ properties
4189
4154
  });
4190
- this.defaultFromEmail = options.defaultFromEmail;
4191
- this.messageStream = options.messageStream;
4155
+ return {
4156
+ id: page.id,
4157
+ title: input.title,
4158
+ url: "url" in page ? page.url : undefined,
4159
+ status: input.status,
4160
+ priority: input.priority,
4161
+ tags: input.tags,
4162
+ projectId: databaseId,
4163
+ externalId: input.externalId,
4164
+ metadata: input.metadata
4165
+ };
4192
4166
  }
4193
- async sendEmail(message) {
4194
- const request = {
4195
- From: formatAddress(message.from) ?? this.defaultFromEmail,
4196
- To: message.to.map((addr) => formatAddress(addr)).join(", "),
4197
- Cc: message.cc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
4198
- Bcc: message.bcc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
4199
- ReplyTo: message.replyTo ? formatAddress(message.replyTo) : undefined,
4200
- Subject: message.subject,
4201
- TextBody: message.textBody,
4202
- HtmlBody: message.htmlBody,
4203
- Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
4204
- Name: name,
4205
- Value: value
4206
- })) : undefined,
4207
- MessageStream: this.messageStream,
4208
- Attachments: buildAttachments(message)
4167
+ async createWorkItems(items) {
4168
+ const created = [];
4169
+ for (const item of items) {
4170
+ created.push(await this.createWorkItem(item));
4171
+ }
4172
+ return created;
4173
+ }
4174
+ async createSummaryPage(input) {
4175
+ const parentId = this.defaults.summaryParentPageId;
4176
+ if (!parentId) {
4177
+ throw new Error("Notion summaryParentPageId is required for summaries.");
4178
+ }
4179
+ const summaryProperties = {
4180
+ title: buildTitleProperty(input.title)
4209
4181
  };
4210
- const response = await this.client.sendEmail(request);
4182
+ const page = await this.client.pages.create({
4183
+ parent: { type: "page_id", page_id: parentId },
4184
+ properties: summaryProperties
4185
+ });
4186
+ if (input.description) {
4187
+ const children = buildParagraphBlocks(input.description);
4188
+ if (children.length > 0) {
4189
+ await this.client.blocks.children.append({
4190
+ block_id: page.id,
4191
+ children
4192
+ });
4193
+ }
4194
+ }
4211
4195
  return {
4212
- id: response.MessageID,
4213
- providerMessageId: response.MessageID,
4214
- queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
4196
+ id: page.id,
4197
+ title: input.title,
4198
+ url: "url" in page ? page.url : undefined,
4199
+ status: input.status,
4200
+ priority: input.priority,
4201
+ tags: input.tags,
4202
+ projectId: parentId,
4203
+ externalId: input.externalId,
4204
+ metadata: input.metadata
4215
4205
  };
4216
4206
  }
4217
4207
  }
4218
- function formatAddress(address) {
4219
- if (address.name) {
4220
- return `"${address.name}" <${address.email}>`;
4221
- }
4222
- return address.email;
4208
+ function buildTitleProperty(title) {
4209
+ return {
4210
+ title: [
4211
+ {
4212
+ type: "text",
4213
+ text: { content: title }
4214
+ }
4215
+ ]
4216
+ };
4223
4217
  }
4224
- function buildAttachments(message) {
4225
- if (!message.attachments?.length)
4218
+ function buildRichText(text) {
4219
+ return {
4220
+ rich_text: [
4221
+ {
4222
+ type: "text",
4223
+ text: { content: text }
4224
+ }
4225
+ ]
4226
+ };
4227
+ }
4228
+ function applySelect(properties, property, value) {
4229
+ if (!property || !value)
4226
4230
  return;
4227
- return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
4228
- Name: attachment.filename,
4229
- Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
4230
- ContentType: attachment.contentType,
4231
- ContentID: null,
4232
- ContentLength: attachment.sizeBytes,
4233
- Disposition: "attachment"
4231
+ const next = {
4232
+ select: { name: value }
4233
+ };
4234
+ properties[property] = next;
4235
+ }
4236
+ function applyMultiSelect(properties, property, values) {
4237
+ if (!property || !values || values.length === 0)
4238
+ return;
4239
+ const next = {
4240
+ multi_select: values.map((value) => ({ name: value }))
4241
+ };
4242
+ properties[property] = next;
4243
+ }
4244
+ function applyDate(properties, property, value) {
4245
+ if (!property || !value)
4246
+ return;
4247
+ const next = {
4248
+ date: { start: value.toISOString() }
4249
+ };
4250
+ properties[property] = next;
4251
+ }
4252
+ function applyRichText(properties, property, value) {
4253
+ if (!property || !value)
4254
+ return;
4255
+ properties[property] = buildRichText(value);
4256
+ }
4257
+ function buildParagraphBlocks(text) {
4258
+ const lines = text.split(/\r?\n/).filter((line) => line.trim());
4259
+ return lines.map((line) => ({
4260
+ object: "block",
4261
+ type: "paragraph",
4262
+ paragraph: {
4263
+ rich_text: [
4264
+ {
4265
+ type: "text",
4266
+ text: { content: line }
4267
+ }
4268
+ ]
4269
+ }
4234
4270
  }));
4235
4271
  }
4236
4272
 
@@ -4251,682 +4287,365 @@ class PosthogAnalyticsReader {
4251
4287
  query: {
4252
4288
  kind: "HogQLQuery",
4253
4289
  query: input.query,
4254
- values: input.values
4255
- }
4256
- }
4257
- });
4258
- return response.data;
4259
- }
4260
- async getEvents(input) {
4261
- const projectId = resolveProjectId(input.projectId, this.projectId);
4262
- const response = await this.client.request({
4263
- method: "GET",
4264
- path: `/api/projects/${projectId}/events/`,
4265
- query: {
4266
- event: input.event ?? resolveSingleEvent(input.events),
4267
- events: resolveEventList(input.events),
4268
- distinct_id: input.distinctId,
4269
- order_by: input.orderBy,
4270
- limit: input.limit,
4271
- offset: input.offset,
4272
- properties: input.properties ? JSON.stringify(input.properties) : undefined,
4273
- ...buildEventDateQuery(input.dateRange)
4274
- }
4275
- });
4276
- return response.data;
4277
- }
4278
- async getPersons(input) {
4279
- const projectId = resolveProjectId(input.projectId, this.projectId);
4280
- const response = await this.client.request({
4281
- method: "GET",
4282
- path: `/api/projects/${projectId}/persons/`,
4283
- query: {
4284
- cohort_id: input.cohortId,
4285
- search: input.search,
4286
- limit: input.limit,
4287
- offset: input.offset,
4288
- properties: input.properties ? JSON.stringify(input.properties) : undefined
4289
- }
4290
- });
4291
- return response.data;
4292
- }
4293
- async getInsights(input) {
4294
- const projectId = resolveProjectId(input.projectId, this.projectId);
4295
- const response = await this.client.request({
4296
- method: "GET",
4297
- path: `/api/projects/${projectId}/insights/`,
4298
- query: {
4299
- insight: input.insightType,
4300
- limit: input.limit,
4301
- offset: input.offset
4290
+ values: input.values
4291
+ }
4302
4292
  }
4303
4293
  });
4304
4294
  return response.data;
4305
4295
  }
4306
- async getInsightResult(input) {
4307
- const projectId = resolveProjectId(input.projectId, this.projectId);
4308
- const response = await this.client.request({
4309
- method: "GET",
4310
- path: `/api/projects/${projectId}/insights/${input.insightId}/`
4311
- });
4312
- return response.data;
4313
- }
4314
- async getCohorts(input) {
4296
+ async getEvents(input) {
4315
4297
  const projectId = resolveProjectId(input.projectId, this.projectId);
4316
4298
  const response = await this.client.request({
4317
4299
  method: "GET",
4318
- path: `/api/projects/${projectId}/cohorts/`,
4300
+ path: `/api/projects/${projectId}/events/`,
4319
4301
  query: {
4302
+ event: input.event ?? resolveSingleEvent(input.events),
4303
+ events: resolveEventList(input.events),
4304
+ distinct_id: input.distinctId,
4305
+ order_by: input.orderBy,
4320
4306
  limit: input.limit,
4321
- offset: input.offset
4307
+ offset: input.offset,
4308
+ properties: input.properties ? JSON.stringify(input.properties) : undefined,
4309
+ ...buildEventDateQuery(input.dateRange)
4322
4310
  }
4323
4311
  });
4324
4312
  return response.data;
4325
4313
  }
4326
- async getFeatureFlags(input) {
4314
+ async getPersons(input) {
4327
4315
  const projectId = resolveProjectId(input.projectId, this.projectId);
4328
4316
  const response = await this.client.request({
4329
4317
  method: "GET",
4330
- path: `/api/projects/${projectId}/feature_flags/`,
4318
+ path: `/api/projects/${projectId}/persons/`,
4331
4319
  query: {
4332
- active: input.active,
4320
+ cohort_id: input.cohortId,
4321
+ search: input.search,
4333
4322
  limit: input.limit,
4334
- offset: input.offset
4323
+ offset: input.offset,
4324
+ properties: input.properties ? JSON.stringify(input.properties) : undefined
4335
4325
  }
4336
4326
  });
4337
4327
  return response.data;
4338
4328
  }
4339
- async getAnnotations(input) {
4329
+ async getInsights(input) {
4340
4330
  const projectId = resolveProjectId(input.projectId, this.projectId);
4341
4331
  const response = await this.client.request({
4342
4332
  method: "GET",
4343
- path: `/api/projects/${projectId}/annotations/`,
4333
+ path: `/api/projects/${projectId}/insights/`,
4344
4334
  query: {
4335
+ insight: input.insightType,
4345
4336
  limit: input.limit,
4346
- offset: input.offset,
4347
- ...buildAnnotationDateQuery(input.dateRange)
4348
- }
4349
- });
4350
- return response.data;
4351
- }
4352
- }
4353
- function resolveProjectId(inputProjectId, defaultProjectId) {
4354
- const projectId = inputProjectId ?? defaultProjectId;
4355
- if (!projectId) {
4356
- throw new Error("PostHog projectId is required for API reads.");
4357
- }
4358
- return projectId;
4359
- }
4360
- function resolveSingleEvent(events) {
4361
- if (!events || events.length !== 1)
4362
- return;
4363
- return events[0];
4364
- }
4365
- function resolveEventList(events) {
4366
- if (!events || events.length <= 1)
4367
- return;
4368
- return events.join(",");
4369
- }
4370
- function buildEventDateQuery(range) {
4371
- const after = formatDate(range?.from);
4372
- const before = formatDate(range?.to);
4373
- return {
4374
- after,
4375
- before,
4376
- timezone: range?.timezone
4377
- };
4378
- }
4379
- function buildAnnotationDateQuery(range) {
4380
- const dateFrom = formatDate(range?.from);
4381
- const dateTo = formatDate(range?.to);
4382
- return {
4383
- date_from: dateFrom,
4384
- date_to: dateTo,
4385
- timezone: range?.timezone
4386
- };
4387
- }
4388
- function formatDate(value) {
4389
- if (!value)
4390
- return;
4391
- return typeof value === "string" ? value : value.toISOString();
4392
- }
4393
-
4394
- // src/impls/posthog-utils.ts
4395
- function normalizeHost(host) {
4396
- return host.replace(/\/$/, "");
4397
- }
4398
- function buildUrl(host, path, query) {
4399
- if (/^https?:\/\//.test(path)) {
4400
- return appendQuery(path, query);
4401
- }
4402
- const normalizedPath = path.replace(/^\/+/, "");
4403
- return appendQuery(`${host}/${normalizedPath}`, query);
4404
- }
4405
- function appendQuery(url, query) {
4406
- if (!query)
4407
- return url;
4408
- const params = new URLSearchParams;
4409
- Object.entries(query).forEach(([key, value]) => {
4410
- if (value === undefined)
4411
- return;
4412
- params.set(key, String(value));
4413
- });
4414
- const suffix = params.toString();
4415
- return suffix ? `${url}?${suffix}` : url;
4416
- }
4417
- function parseJson(value) {
4418
- if (!value)
4419
- return {};
4420
- try {
4421
- return JSON.parse(value);
4422
- } catch {
4423
- return value;
4424
- }
4425
- }
4426
-
4427
- // src/impls/posthog.ts
4428
- import { PostHog } from "posthog-node";
4429
- var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
4430
-
4431
- class PosthogAnalyticsProvider {
4432
- host;
4433
- projectId;
4434
- projectApiKey;
4435
- personalApiKey;
4436
- mcpUrl;
4437
- fetchFn;
4438
- client;
4439
- reader;
4440
- constructor(options) {
4441
- this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
4442
- this.projectId = options.projectId;
4443
- this.projectApiKey = options.projectApiKey;
4444
- this.personalApiKey = options.personalApiKey;
4445
- this.mcpUrl = options.mcpUrl;
4446
- this.fetchFn = options.fetch ?? fetch;
4447
- this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
4448
- host: this.host,
4449
- requestTimeout: options.requestTimeoutMs ?? 1e4
4450
- }) : undefined);
4451
- this.reader = new PosthogAnalyticsReader({
4452
- projectId: this.projectId,
4453
- client: { request: this.request.bind(this) }
4454
- });
4455
- }
4456
- async capture(event) {
4457
- if (!this.client) {
4458
- throw new Error("PostHog projectApiKey is required for capture.");
4459
- }
4460
- await this.client.capture({
4461
- distinctId: event.distinctId,
4462
- event: event.event,
4463
- properties: event.properties,
4464
- timestamp: event.timestamp,
4465
- groups: event.groups
4466
- });
4467
- }
4468
- async identify(input) {
4469
- if (!this.client) {
4470
- throw new Error("PostHog projectApiKey is required for identify.");
4471
- }
4472
- await this.client.identify({
4473
- distinctId: input.distinctId,
4474
- properties: {
4475
- ...input.properties ? { $set: input.properties } : {},
4476
- ...input.setOnce ? { $set_once: input.setOnce } : {}
4337
+ offset: input.offset
4477
4338
  }
4478
4339
  });
4340
+ return response.data;
4479
4341
  }
4480
- async queryHogQL(input) {
4481
- return this.reader.queryHogQL(input);
4482
- }
4483
- async getEvents(input) {
4484
- return this.reader.getEvents(input);
4485
- }
4486
- async getPersons(input) {
4487
- return this.reader.getPersons(input);
4488
- }
4489
- async getInsights(input) {
4490
- return this.reader.getInsights(input);
4491
- }
4492
- async getInsightResult(input) {
4493
- return this.reader.getInsightResult(input);
4494
- }
4495
- async getCohorts(input) {
4496
- return this.reader.getCohorts(input);
4497
- }
4498
- async getFeatureFlags(input) {
4499
- return this.reader.getFeatureFlags(input);
4500
- }
4501
- async getAnnotations(input) {
4502
- return this.reader.getAnnotations(input);
4503
- }
4504
- async request(request) {
4505
- if (!this.personalApiKey) {
4506
- throw new Error("PostHog personalApiKey is required for API requests.");
4507
- }
4508
- const url = buildUrl(this.host, request.path, request.query);
4509
- const response = await this.fetchFn(url, {
4510
- method: request.method,
4511
- headers: {
4512
- Authorization: `Bearer ${this.personalApiKey}`,
4513
- "Content-Type": "application/json",
4514
- ...request.headers ?? {}
4515
- },
4516
- body: request.body ? JSON.stringify(request.body) : undefined
4517
- });
4518
- const text = await response.text();
4519
- const data = parseJson(text);
4520
- return {
4521
- status: response.status,
4522
- data,
4523
- headers: Object.fromEntries(response.headers.entries())
4524
- };
4525
- }
4526
- async callMcpTool(call) {
4527
- if (!this.mcpUrl) {
4528
- throw new Error("PostHog MCP URL is not configured.");
4529
- }
4530
- const response = await this.fetchFn(this.mcpUrl, {
4531
- method: "POST",
4532
- headers: {
4533
- "Content-Type": "application/json"
4534
- },
4535
- body: JSON.stringify({
4536
- jsonrpc: "2.0",
4537
- id: 1,
4538
- method: "tools/call",
4539
- params: {
4540
- name: call.name,
4541
- arguments: call.arguments ?? {}
4542
- }
4543
- })
4544
- });
4545
- if (!response.ok) {
4546
- const body = await response.text();
4547
- throw new Error(`PostHog MCP error (${response.status}): ${body}`);
4548
- }
4549
- const result = await response.json();
4550
- if (result.error) {
4551
- throw new Error(result.error.message ?? "PostHog MCP error");
4552
- }
4553
- return result.result ?? null;
4554
- }
4555
- }
4556
-
4557
- // src/impls/twilio-sms.ts
4558
- import Twilio from "twilio";
4559
-
4560
- class TwilioSmsProvider {
4561
- client;
4562
- fromNumber;
4563
- constructor(options) {
4564
- this.client = options.client ?? Twilio(options.accountSid, options.authToken);
4565
- this.fromNumber = options.fromNumber;
4566
- }
4567
- async sendSms(input) {
4568
- const message = await this.client.messages.create({
4569
- to: input.to,
4570
- from: input.from ?? this.fromNumber,
4571
- body: input.body
4342
+ async getInsightResult(input) {
4343
+ const projectId = resolveProjectId(input.projectId, this.projectId);
4344
+ const response = await this.client.request({
4345
+ method: "GET",
4346
+ path: `/api/projects/${projectId}/insights/${input.insightId}/`
4572
4347
  });
4573
- return {
4574
- id: message.sid,
4575
- to: message.to ?? input.to,
4576
- from: message.from ?? input.from ?? this.fromNumber ?? "",
4577
- body: message.body ?? input.body,
4578
- status: mapStatus(message.status),
4579
- sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
4580
- deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
4581
- price: message.price ? Number(message.price) : undefined,
4582
- priceCurrency: message.priceUnit ?? undefined,
4583
- errorCode: message.errorCode ? String(message.errorCode) : undefined,
4584
- errorMessage: message.errorMessage ?? undefined
4585
- };
4348
+ return response.data;
4586
4349
  }
4587
- async getDeliveryStatus(messageId) {
4588
- const message = await this.client.messages(messageId).fetch();
4589
- return {
4590
- status: mapStatus(message.status),
4591
- errorCode: message.errorCode ? String(message.errorCode) : undefined,
4592
- errorMessage: message.errorMessage ?? undefined,
4593
- updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
4594
- };
4350
+ async getCohorts(input) {
4351
+ const projectId = resolveProjectId(input.projectId, this.projectId);
4352
+ const response = await this.client.request({
4353
+ method: "GET",
4354
+ path: `/api/projects/${projectId}/cohorts/`,
4355
+ query: {
4356
+ limit: input.limit,
4357
+ offset: input.offset
4358
+ }
4359
+ });
4360
+ return response.data;
4361
+ }
4362
+ async getFeatureFlags(input) {
4363
+ const projectId = resolveProjectId(input.projectId, this.projectId);
4364
+ const response = await this.client.request({
4365
+ method: "GET",
4366
+ path: `/api/projects/${projectId}/feature_flags/`,
4367
+ query: {
4368
+ active: input.active,
4369
+ limit: input.limit,
4370
+ offset: input.offset
4371
+ }
4372
+ });
4373
+ return response.data;
4374
+ }
4375
+ async getAnnotations(input) {
4376
+ const projectId = resolveProjectId(input.projectId, this.projectId);
4377
+ const response = await this.client.request({
4378
+ method: "GET",
4379
+ path: `/api/projects/${projectId}/annotations/`,
4380
+ query: {
4381
+ limit: input.limit,
4382
+ offset: input.offset,
4383
+ ...buildAnnotationDateQuery(input.dateRange)
4384
+ }
4385
+ });
4386
+ return response.data;
4595
4387
  }
4596
4388
  }
4597
- function mapStatus(status) {
4598
- switch (status) {
4599
- case "queued":
4600
- case "accepted":
4601
- case "scheduled":
4602
- return "queued";
4603
- case "sending":
4604
- case "processing":
4605
- return "sending";
4606
- case "sent":
4607
- return "sent";
4608
- case "delivered":
4609
- return "delivered";
4610
- case "undelivered":
4611
- return "undelivered";
4612
- case "failed":
4613
- case "canceled":
4614
- return "failed";
4615
- default:
4616
- return "queued";
4389
+ function resolveProjectId(inputProjectId, defaultProjectId) {
4390
+ const projectId = inputProjectId ?? defaultProjectId;
4391
+ if (!projectId) {
4392
+ throw new Error("PostHog projectId is required for API reads.");
4617
4393
  }
4394
+ return projectId;
4395
+ }
4396
+ function resolveSingleEvent(events) {
4397
+ if (!events || events.length !== 1)
4398
+ return;
4399
+ return events[0];
4400
+ }
4401
+ function resolveEventList(events) {
4402
+ if (!events || events.length <= 1)
4403
+ return;
4404
+ return events.join(",");
4405
+ }
4406
+ function buildEventDateQuery(range) {
4407
+ const after = formatDate(range?.from);
4408
+ const before = formatDate(range?.to);
4409
+ return {
4410
+ after,
4411
+ before,
4412
+ timezone: range?.timezone
4413
+ };
4414
+ }
4415
+ function buildAnnotationDateQuery(range) {
4416
+ const dateFrom = formatDate(range?.from);
4417
+ const dateTo = formatDate(range?.to);
4418
+ return {
4419
+ date_from: dateFrom,
4420
+ date_to: dateTo,
4421
+ timezone: range?.timezone
4422
+ };
4423
+ }
4424
+ function formatDate(value) {
4425
+ if (!value)
4426
+ return;
4427
+ return typeof value === "string" ? value : value.toISOString();
4618
4428
  }
4619
4429
 
4620
- // src/impls/messaging-slack.ts
4621
- class SlackMessagingProvider {
4622
- botToken;
4623
- defaultChannelId;
4624
- apiBaseUrl;
4625
- constructor(options) {
4626
- this.botToken = options.botToken;
4627
- this.defaultChannelId = options.defaultChannelId;
4628
- this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
4629
- }
4630
- async sendMessage(input) {
4631
- const channel = input.channelId ?? input.recipientId ?? this.defaultChannelId;
4632
- if (!channel) {
4633
- throw new Error("Slack sendMessage requires channelId, recipientId, or defaultChannelId.");
4634
- }
4635
- const payload = {
4636
- channel,
4637
- text: input.text,
4638
- mrkdwn: input.markdown ?? true,
4639
- thread_ts: input.threadId
4640
- };
4641
- const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
4642
- method: "POST",
4643
- headers: {
4644
- authorization: `Bearer ${this.botToken}`,
4645
- "content-type": "application/json"
4646
- },
4647
- body: JSON.stringify(payload)
4648
- });
4649
- const body = await response.json();
4650
- if (!response.ok || !body.ok || !body.ts) {
4651
- throw new Error(`Slack sendMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
4652
- }
4653
- return {
4654
- id: `slack:${body.channel ?? channel}:${body.ts}`,
4655
- providerMessageId: body.ts,
4656
- status: "sent",
4657
- sentAt: new Date,
4658
- metadata: {
4659
- channelId: body.channel ?? channel
4660
- }
4661
- };
4430
+ // src/impls/posthog-utils.ts
4431
+ function normalizeHost(host) {
4432
+ return host.replace(/\/$/, "");
4433
+ }
4434
+ function buildUrl(host, path, query) {
4435
+ if (/^https?:\/\//.test(path)) {
4436
+ return appendQuery(path, query);
4662
4437
  }
4663
- async updateMessage(messageId, input) {
4664
- const channel = input.channelId ?? this.defaultChannelId;
4665
- if (!channel) {
4666
- throw new Error("Slack updateMessage requires channelId or defaultChannelId.");
4667
- }
4668
- const response = await fetch(`${this.apiBaseUrl}/chat.update`, {
4669
- method: "POST",
4670
- headers: {
4671
- authorization: `Bearer ${this.botToken}`,
4672
- "content-type": "application/json"
4673
- },
4674
- body: JSON.stringify({
4675
- channel,
4676
- ts: messageId,
4677
- text: input.text,
4678
- mrkdwn: input.markdown ?? true
4679
- })
4680
- });
4681
- const body = await response.json();
4682
- if (!response.ok || !body.ok || !body.ts) {
4683
- throw new Error(`Slack updateMessage failed: ${body.error ?? `HTTP_${response.status}`}`);
4684
- }
4685
- return {
4686
- id: `slack:${body.channel ?? channel}:${body.ts}`,
4687
- providerMessageId: body.ts,
4688
- status: "sent",
4689
- sentAt: new Date,
4690
- metadata: {
4691
- channelId: body.channel ?? channel
4692
- }
4693
- };
4438
+ const normalizedPath = path.replace(/^\/+/, "");
4439
+ return appendQuery(`${host}/${normalizedPath}`, query);
4440
+ }
4441
+ function appendQuery(url, query) {
4442
+ if (!query)
4443
+ return url;
4444
+ const params = new URLSearchParams;
4445
+ Object.entries(query).forEach(([key, value]) => {
4446
+ if (value === undefined)
4447
+ return;
4448
+ params.set(key, String(value));
4449
+ });
4450
+ const suffix = params.toString();
4451
+ return suffix ? `${url}?${suffix}` : url;
4452
+ }
4453
+ function parseJson(value) {
4454
+ if (!value)
4455
+ return {};
4456
+ try {
4457
+ return JSON.parse(value);
4458
+ } catch {
4459
+ return value;
4694
4460
  }
4695
4461
  }
4696
4462
 
4697
- // src/impls/messaging-github.ts
4698
- class GithubMessagingProvider {
4699
- token;
4700
- defaultOwner;
4701
- defaultRepo;
4702
- apiBaseUrl;
4463
+ // src/impls/posthog.ts
4464
+ import { PostHog } from "posthog-node";
4465
+ var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
4466
+
4467
+ class PosthogAnalyticsProvider {
4468
+ host;
4469
+ projectId;
4470
+ projectApiKey;
4471
+ personalApiKey;
4472
+ mcpUrl;
4473
+ fetchFn;
4474
+ client;
4475
+ reader;
4703
4476
  constructor(options) {
4704
- this.token = options.token;
4705
- this.defaultOwner = options.defaultOwner;
4706
- this.defaultRepo = options.defaultRepo;
4707
- this.apiBaseUrl = options.apiBaseUrl ?? "https://api.github.com";
4477
+ this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
4478
+ this.projectId = options.projectId;
4479
+ this.projectApiKey = options.projectApiKey;
4480
+ this.personalApiKey = options.personalApiKey;
4481
+ this.mcpUrl = options.mcpUrl;
4482
+ this.fetchFn = options.fetch ?? fetch;
4483
+ this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
4484
+ host: this.host,
4485
+ requestTimeout: options.requestTimeoutMs ?? 1e4
4486
+ }) : undefined);
4487
+ this.reader = new PosthogAnalyticsReader({
4488
+ projectId: this.projectId,
4489
+ client: { request: this.request.bind(this) }
4490
+ });
4708
4491
  }
4709
- async sendMessage(input) {
4710
- const target = this.resolveTarget(input);
4711
- const response = await fetch(`${this.apiBaseUrl}/repos/${target.owner}/${target.repo}/issues/${target.issueNumber}/comments`, {
4712
- method: "POST",
4713
- headers: {
4714
- authorization: `Bearer ${this.token}`,
4715
- accept: "application/vnd.github+json",
4716
- "content-type": "application/json"
4717
- },
4718
- body: JSON.stringify({ body: input.text })
4492
+ async capture(event) {
4493
+ if (!this.client) {
4494
+ throw new Error("PostHog projectApiKey is required for capture.");
4495
+ }
4496
+ await this.client.capture({
4497
+ distinctId: event.distinctId,
4498
+ event: event.event,
4499
+ properties: event.properties,
4500
+ timestamp: event.timestamp,
4501
+ groups: event.groups
4719
4502
  });
4720
- const body = await response.json();
4721
- if (!response.ok || !body.id) {
4722
- throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
4503
+ }
4504
+ async identify(input) {
4505
+ if (!this.client) {
4506
+ throw new Error("PostHog projectApiKey is required for identify.");
4723
4507
  }
4724
- return {
4725
- id: String(body.id),
4726
- providerMessageId: body.node_id,
4727
- status: "sent",
4728
- sentAt: new Date,
4729
- metadata: {
4730
- url: body.html_url ?? "",
4731
- owner: target.owner,
4732
- repo: target.repo,
4733
- issueNumber: String(target.issueNumber)
4508
+ await this.client.identify({
4509
+ distinctId: input.distinctId,
4510
+ properties: {
4511
+ ...input.properties ? { $set: input.properties } : {},
4512
+ ...input.setOnce ? { $set_once: input.setOnce } : {}
4734
4513
  }
4735
- };
4514
+ });
4736
4515
  }
4737
- async updateMessage(messageId, input) {
4738
- const owner = input.metadata?.owner ?? this.defaultOwner;
4739
- const repo = input.metadata?.repo ?? this.defaultRepo;
4740
- if (!owner || !repo) {
4741
- throw new Error("GitHub updateMessage requires owner and repo metadata.");
4516
+ async queryHogQL(input) {
4517
+ return this.reader.queryHogQL(input);
4518
+ }
4519
+ async getEvents(input) {
4520
+ return this.reader.getEvents(input);
4521
+ }
4522
+ async getPersons(input) {
4523
+ return this.reader.getPersons(input);
4524
+ }
4525
+ async getInsights(input) {
4526
+ return this.reader.getInsights(input);
4527
+ }
4528
+ async getInsightResult(input) {
4529
+ return this.reader.getInsightResult(input);
4530
+ }
4531
+ async getCohorts(input) {
4532
+ return this.reader.getCohorts(input);
4533
+ }
4534
+ async getFeatureFlags(input) {
4535
+ return this.reader.getFeatureFlags(input);
4536
+ }
4537
+ async getAnnotations(input) {
4538
+ return this.reader.getAnnotations(input);
4539
+ }
4540
+ async request(request) {
4541
+ if (!this.personalApiKey) {
4542
+ throw new Error("PostHog personalApiKey is required for API requests.");
4742
4543
  }
4743
- const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
4744
- method: "PATCH",
4544
+ const url = buildUrl(this.host, request.path, request.query);
4545
+ const response = await this.fetchFn(url, {
4546
+ method: request.method,
4745
4547
  headers: {
4746
- authorization: `Bearer ${this.token}`,
4747
- accept: "application/vnd.github+json",
4748
- "content-type": "application/json"
4548
+ Authorization: `Bearer ${this.personalApiKey}`,
4549
+ "Content-Type": "application/json",
4550
+ ...request.headers ?? {}
4749
4551
  },
4750
- body: JSON.stringify({ body: input.text })
4552
+ body: request.body ? JSON.stringify(request.body) : undefined
4751
4553
  });
4752
- const body = await response.json();
4753
- if (!response.ok || !body.id) {
4754
- throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
4755
- }
4756
- return {
4757
- id: String(body.id),
4758
- providerMessageId: body.node_id,
4759
- status: "sent",
4760
- sentAt: new Date,
4761
- metadata: {
4762
- url: body.html_url ?? "",
4763
- owner,
4764
- repo
4765
- }
4766
- };
4767
- }
4768
- resolveTarget(input) {
4769
- const parsedRecipient = parseRecipient(input.recipientId);
4770
- const owner = parsedRecipient?.owner ?? this.defaultOwner;
4771
- const repo = parsedRecipient?.repo ?? this.defaultRepo;
4772
- const issueNumber = parsedRecipient?.issueNumber ?? parseIssueNumber(input.threadId);
4773
- if (!owner || !repo || issueNumber == null) {
4774
- throw new Error("GitHub sendMessage requires owner/repo and issueNumber (use recipientId like owner/repo#123 or provide defaults + threadId).");
4775
- }
4554
+ const text = await response.text();
4555
+ const data = parseJson(text);
4776
4556
  return {
4777
- owner,
4778
- repo,
4779
- issueNumber
4557
+ status: response.status,
4558
+ data,
4559
+ headers: Object.fromEntries(response.headers.entries())
4780
4560
  };
4781
4561
  }
4782
- }
4783
- function parseRecipient(value) {
4784
- if (!value)
4785
- return null;
4786
- const match = value.trim().match(/^([^/]+)\/([^#]+)#(\d+)$/);
4787
- if (!match)
4788
- return null;
4789
- const owner = match[1];
4790
- const repo = match[2];
4791
- const issueNumber = Number(match[3]);
4792
- if (!owner || !repo || !Number.isInteger(issueNumber)) {
4793
- return null;
4794
- }
4795
- return { owner, repo, issueNumber };
4796
- }
4797
- function parseIssueNumber(value) {
4798
- if (!value)
4799
- return null;
4800
- const numeric = Number(value);
4801
- return Number.isInteger(numeric) ? numeric : null;
4802
- }
4803
-
4804
- // src/impls/messaging-whatsapp-meta.ts
4805
- class MetaWhatsappMessagingProvider {
4806
- accessToken;
4807
- phoneNumberId;
4808
- apiVersion;
4809
- constructor(options) {
4810
- this.accessToken = options.accessToken;
4811
- this.phoneNumberId = options.phoneNumberId;
4812
- this.apiVersion = options.apiVersion ?? "v22.0";
4813
- }
4814
- async sendMessage(input) {
4815
- const to = input.recipientId;
4816
- if (!to) {
4817
- throw new Error("Meta WhatsApp sendMessage requires recipientId.");
4562
+ async callMcpTool(call) {
4563
+ if (!this.mcpUrl) {
4564
+ throw new Error("PostHog MCP URL is not configured.");
4818
4565
  }
4819
- const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
4566
+ const response = await this.fetchFn(this.mcpUrl, {
4820
4567
  method: "POST",
4821
4568
  headers: {
4822
- authorization: `Bearer ${this.accessToken}`,
4823
- "content-type": "application/json"
4569
+ "Content-Type": "application/json"
4824
4570
  },
4825
4571
  body: JSON.stringify({
4826
- messaging_product: "whatsapp",
4827
- to,
4828
- type: "text",
4829
- text: {
4830
- body: input.text,
4831
- preview_url: false
4572
+ jsonrpc: "2.0",
4573
+ id: 1,
4574
+ method: "tools/call",
4575
+ params: {
4576
+ name: call.name,
4577
+ arguments: call.arguments ?? {}
4832
4578
  }
4833
4579
  })
4834
4580
  });
4835
- const body = await response.json();
4836
- const messageId = body.messages?.[0]?.id;
4837
- if (!response.ok || !messageId) {
4838
- const errorCode = body.error?.code != null ? String(body.error.code) : "";
4839
- throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
4581
+ if (!response.ok) {
4582
+ const body = await response.text();
4583
+ throw new Error(`PostHog MCP error (${response.status}): ${body}`);
4840
4584
  }
4841
- return {
4842
- id: messageId,
4843
- providerMessageId: messageId,
4844
- status: "sent",
4845
- sentAt: new Date,
4846
- metadata: {
4847
- phoneNumberId: this.phoneNumberId
4848
- }
4849
- };
4585
+ const result = await response.json();
4586
+ if (result.error) {
4587
+ throw new Error(result.error.message ?? "PostHog MCP error");
4588
+ }
4589
+ return result.result ?? null;
4850
4590
  }
4851
4591
  }
4852
4592
 
4853
- // src/impls/messaging-whatsapp-twilio.ts
4854
- import { Buffer as Buffer4 } from "buffer";
4593
+ // src/impls/postmark-email.ts
4594
+ import { ServerClient } from "postmark";
4855
4595
 
4856
- class TwilioWhatsappMessagingProvider {
4857
- accountSid;
4858
- authToken;
4859
- fromNumber;
4596
+ class PostmarkEmailProvider {
4597
+ client;
4598
+ defaultFromEmail;
4599
+ messageStream;
4860
4600
  constructor(options) {
4861
- this.accountSid = options.accountSid;
4862
- this.authToken = options.authToken;
4863
- this.fromNumber = options.fromNumber;
4864
- }
4865
- async sendMessage(input) {
4866
- const to = normalizeWhatsappAddress(input.recipientId);
4867
- const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
4868
- if (!to) {
4869
- throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
4870
- }
4871
- if (!from) {
4872
- throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
4873
- }
4874
- const params = new URLSearchParams;
4875
- params.set("To", to);
4876
- params.set("From", from);
4877
- params.set("Body", input.text);
4878
- const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
4879
- const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
4880
- method: "POST",
4881
- headers: {
4882
- authorization: `Basic ${auth}`,
4883
- "content-type": "application/x-www-form-urlencoded"
4884
- },
4885
- body: params.toString()
4601
+ this.client = options.client ?? new ServerClient(options.serverToken, {
4602
+ useHttps: true
4886
4603
  });
4887
- const body = await response.json();
4888
- if (!response.ok || !body.sid) {
4889
- throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
4890
- }
4604
+ this.defaultFromEmail = options.defaultFromEmail;
4605
+ this.messageStream = options.messageStream;
4606
+ }
4607
+ async sendEmail(message) {
4608
+ const request = {
4609
+ From: formatAddress(message.from) ?? this.defaultFromEmail,
4610
+ To: message.to.map((addr) => formatAddress(addr)).join(", "),
4611
+ Cc: message.cc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
4612
+ Bcc: message.bcc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
4613
+ ReplyTo: message.replyTo ? formatAddress(message.replyTo) : undefined,
4614
+ Subject: message.subject,
4615
+ TextBody: message.textBody,
4616
+ HtmlBody: message.htmlBody,
4617
+ Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
4618
+ Name: name,
4619
+ Value: value
4620
+ })) : undefined,
4621
+ MessageStream: this.messageStream,
4622
+ Attachments: buildAttachments(message)
4623
+ };
4624
+ const response = await this.client.sendEmail(request);
4891
4625
  return {
4892
- id: body.sid,
4893
- providerMessageId: body.sid,
4894
- status: mapTwilioStatus(body.status),
4895
- sentAt: new Date,
4896
- errorCode: body.error_code != null ? String(body.error_code) : undefined,
4897
- errorMessage: body.error_message ?? undefined,
4898
- metadata: {
4899
- from,
4900
- to
4901
- }
4626
+ id: response.MessageID,
4627
+ providerMessageId: response.MessageID,
4628
+ queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
4902
4629
  };
4903
4630
  }
4904
4631
  }
4905
- function normalizeWhatsappAddress(value) {
4906
- if (!value)
4907
- return null;
4908
- if (value.startsWith("whatsapp:"))
4909
- return value;
4910
- return `whatsapp:${value}`;
4911
- }
4912
- function mapTwilioStatus(status) {
4913
- switch (status) {
4914
- case "queued":
4915
- case "accepted":
4916
- case "scheduled":
4917
- return "queued";
4918
- case "sending":
4919
- return "sending";
4920
- case "delivered":
4921
- return "delivered";
4922
- case "failed":
4923
- case "undelivered":
4924
- case "canceled":
4925
- return "failed";
4926
- case "sent":
4927
- default:
4928
- return "sent";
4632
+ function formatAddress(address) {
4633
+ if (address.name) {
4634
+ return `"${address.name}" <${address.email}>`;
4929
4635
  }
4636
+ return address.email;
4637
+ }
4638
+ function buildAttachments(message) {
4639
+ if (!message.attachments?.length)
4640
+ return;
4641
+ return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
4642
+ Name: attachment.filename,
4643
+ Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
4644
+ ContentType: attachment.contentType,
4645
+ ContentID: null,
4646
+ ContentLength: attachment.sizeBytes,
4647
+ Disposition: "attachment"
4648
+ }));
4930
4649
  }
4931
4650
 
4932
4651
  // src/impls/powens-client.ts
@@ -5351,367 +5070,629 @@ class PowensOpenBankingProvider {
5351
5070
  this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
5352
5071
  throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
5353
5072
  }
5354
- }
5355
-
5356
- // src/impls/linear.ts
5357
- import { LinearClient } from "@linear/sdk";
5358
- var PRIORITY_MAP = {
5359
- urgent: 1,
5360
- high: 2,
5361
- medium: 3,
5362
- low: 4,
5363
- none: 0
5364
- };
5365
-
5366
- class LinearProjectManagementProvider {
5367
- client;
5368
- defaults;
5369
- constructor(options) {
5370
- this.client = options.client ?? new LinearClient({ apiKey: options.apiKey });
5371
- this.defaults = {
5372
- teamId: options.teamId,
5373
- projectId: options.projectId,
5374
- assigneeId: options.assigneeId,
5375
- stateId: options.stateId,
5376
- labelIds: options.labelIds,
5377
- tagLabelMap: options.tagLabelMap
5073
+ }
5074
+
5075
+ // src/impls/qdrant-vector.ts
5076
+ import { QdrantClient } from "@qdrant/js-client-rest";
5077
+
5078
+ class QdrantVectorProvider {
5079
+ client;
5080
+ createCollectionIfMissing;
5081
+ distance;
5082
+ constructor(options) {
5083
+ this.client = options.client ?? new QdrantClient({
5084
+ url: options.url,
5085
+ apiKey: options.apiKey,
5086
+ ...options.clientParams
5087
+ });
5088
+ this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
5089
+ this.distance = options.distance ?? "Cosine";
5090
+ }
5091
+ async upsert(request) {
5092
+ if (request.documents.length === 0)
5093
+ return;
5094
+ const firstDocument = request.documents[0];
5095
+ if (!firstDocument)
5096
+ return;
5097
+ const vectorSize = firstDocument.vector.length;
5098
+ if (this.createCollectionIfMissing) {
5099
+ await this.ensureCollection(request.collection, vectorSize);
5100
+ }
5101
+ const points = request.documents.map((document) => ({
5102
+ id: document.id,
5103
+ vector: document.vector,
5104
+ payload: {
5105
+ ...document.payload,
5106
+ ...document.namespace ? { namespace: document.namespace } : {},
5107
+ ...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
5108
+ }
5109
+ }));
5110
+ await this.client.upsert(request.collection, {
5111
+ wait: true,
5112
+ points
5113
+ });
5114
+ }
5115
+ async search(query) {
5116
+ const results = await this.client.search(query.collection, {
5117
+ vector: query.vector,
5118
+ limit: query.topK,
5119
+ filter: query.filter,
5120
+ score_threshold: query.scoreThreshold,
5121
+ with_payload: true,
5122
+ with_vector: false
5123
+ });
5124
+ return results.map((item) => ({
5125
+ id: String(item.id),
5126
+ score: item.score,
5127
+ payload: item.payload ?? undefined,
5128
+ namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
5129
+ }));
5130
+ }
5131
+ async delete(request) {
5132
+ await this.client.delete(request.collection, {
5133
+ wait: true,
5134
+ points: request.ids
5135
+ });
5136
+ }
5137
+ async ensureCollection(collectionName, vectorSize) {
5138
+ try {
5139
+ await this.client.getCollection(collectionName);
5140
+ } catch (_error) {
5141
+ await this.client.createCollection(collectionName, {
5142
+ vectors: {
5143
+ size: vectorSize,
5144
+ distance: this.distance
5145
+ }
5146
+ });
5147
+ }
5148
+ }
5149
+ }
5150
+
5151
+ // src/impls/stripe-payments.ts
5152
+ import Stripe from "stripe";
5153
+ var API_VERSION = "2026-02-25.clover";
5154
+
5155
+ class StripePaymentsProvider {
5156
+ stripe;
5157
+ constructor(options) {
5158
+ this.stripe = options.stripe ?? new Stripe(options.apiKey, {
5159
+ apiVersion: API_VERSION
5160
+ });
5161
+ }
5162
+ async createCustomer(input) {
5163
+ const customer = await this.stripe.customers.create({
5164
+ email: input.email,
5165
+ name: input.name,
5166
+ description: input.description,
5167
+ metadata: input.metadata
5168
+ });
5169
+ return this.toCustomer(customer);
5170
+ }
5171
+ async getCustomer(customerId) {
5172
+ const customer = await this.stripe.customers.retrieve(customerId);
5173
+ if (customer.deleted)
5174
+ return null;
5175
+ return this.toCustomer(customer);
5176
+ }
5177
+ async createPaymentIntent(input) {
5178
+ const intent = await this.stripe.paymentIntents.create({
5179
+ amount: input.amount.amount,
5180
+ currency: input.amount.currency,
5181
+ customer: input.customerId,
5182
+ description: input.description,
5183
+ capture_method: input.captureMethod ?? "automatic",
5184
+ confirmation_method: input.confirmationMethod ?? "automatic",
5185
+ automatic_payment_methods: { enabled: true },
5186
+ metadata: input.metadata,
5187
+ return_url: input.returnUrl,
5188
+ statement_descriptor: input.statementDescriptor
5189
+ });
5190
+ return this.toPaymentIntent(intent);
5191
+ }
5192
+ async capturePayment(paymentIntentId, input) {
5193
+ const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
5194
+ return this.toPaymentIntent(intent);
5195
+ }
5196
+ async cancelPaymentIntent(paymentIntentId) {
5197
+ const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
5198
+ return this.toPaymentIntent(intent);
5199
+ }
5200
+ async refundPayment(input) {
5201
+ const refund = await this.stripe.refunds.create({
5202
+ payment_intent: input.paymentIntentId,
5203
+ amount: input.amount?.amount,
5204
+ reason: mapRefundReason(input.reason),
5205
+ metadata: input.metadata
5206
+ });
5207
+ const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
5208
+ return {
5209
+ id: refund.id,
5210
+ paymentIntentId,
5211
+ amount: {
5212
+ amount: refund.amount ?? 0,
5213
+ currency: refund.currency?.toUpperCase() ?? "USD"
5214
+ },
5215
+ status: mapRefundStatus(refund.status),
5216
+ reason: refund.reason ?? undefined,
5217
+ metadata: this.toMetadata(refund.metadata),
5218
+ createdAt: refund.created ? new Date(refund.created * 1000) : undefined
5219
+ };
5220
+ }
5221
+ async listInvoices(query) {
5222
+ const requestedStatus = query?.status?.[0];
5223
+ const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
5224
+ const response = await this.stripe.invoices.list({
5225
+ customer: query?.customerId,
5226
+ status: stripeStatus,
5227
+ limit: query?.limit,
5228
+ starting_after: query?.startingAfter
5229
+ });
5230
+ return response.data.map((invoice) => this.toInvoice(invoice));
5231
+ }
5232
+ async listTransactions(query) {
5233
+ const response = await this.stripe.charges.list({
5234
+ customer: query?.customerId,
5235
+ payment_intent: query?.paymentIntentId,
5236
+ limit: query?.limit,
5237
+ starting_after: query?.startingAfter
5238
+ });
5239
+ return response.data.map((charge) => ({
5240
+ id: charge.id,
5241
+ paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
5242
+ amount: {
5243
+ amount: charge.amount,
5244
+ currency: charge.currency?.toUpperCase() ?? "USD"
5245
+ },
5246
+ type: "capture",
5247
+ status: mapChargeStatus(charge.status),
5248
+ description: charge.description ?? undefined,
5249
+ createdAt: new Date(charge.created * 1000),
5250
+ metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
5251
+ balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
5252
+ })
5253
+ }));
5254
+ }
5255
+ toCustomer(customer) {
5256
+ const metadata = this.toMetadata(customer.metadata);
5257
+ const updatedAtValue = metadata?.updatedAt;
5258
+ return {
5259
+ id: customer.id,
5260
+ email: customer.email ?? undefined,
5261
+ name: customer.name ?? undefined,
5262
+ metadata,
5263
+ createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
5264
+ updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
5265
+ };
5266
+ }
5267
+ toPaymentIntent(intent) {
5268
+ const metadata = this.toMetadata(intent.metadata);
5269
+ return {
5270
+ id: intent.id,
5271
+ amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
5272
+ status: mapPaymentIntentStatus(intent.status),
5273
+ customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
5274
+ description: intent.description ?? undefined,
5275
+ clientSecret: intent.client_secret ?? undefined,
5276
+ metadata,
5277
+ createdAt: new Date(intent.created * 1000),
5278
+ updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
5378
5279
  };
5379
5280
  }
5380
- async createWorkItem(input) {
5381
- const teamId = this.defaults.teamId;
5382
- if (!teamId) {
5383
- throw new Error("Linear teamId is required to create work items.");
5384
- }
5385
- const payload = await this.client.createIssue({
5386
- teamId,
5387
- title: input.title,
5388
- description: input.description,
5389
- priority: mapPriority(input.priority),
5390
- estimate: input.estimate,
5391
- assigneeId: input.assigneeId ?? this.defaults.assigneeId,
5392
- projectId: input.projectId ?? this.defaults.projectId,
5393
- stateId: this.defaults.stateId,
5394
- labelIds: resolveLabelIds(this.defaults, input.tags)
5395
- });
5396
- const issue = await payload.issue;
5397
- const state = issue ? await issue.state : undefined;
5281
+ toInvoice(invoice) {
5282
+ const metadata = this.toMetadata(invoice.metadata);
5398
5283
  return {
5399
- id: issue?.id ?? "",
5400
- title: issue?.title ?? input.title,
5401
- url: issue?.url ?? undefined,
5402
- status: state?.name ?? undefined,
5403
- priority: input.priority,
5404
- tags: input.tags,
5405
- projectId: input.projectId ?? this.defaults.projectId,
5406
- externalId: input.externalId,
5407
- metadata: input.metadata
5284
+ id: invoice.id,
5285
+ number: invoice.number ?? undefined,
5286
+ status: invoice.status ?? "draft",
5287
+ amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
5288
+ amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
5289
+ customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
5290
+ dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
5291
+ hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
5292
+ metadata,
5293
+ createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
5294
+ updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
5408
5295
  };
5409
5296
  }
5410
- async createWorkItems(items) {
5411
- const created = [];
5412
- for (const item of items) {
5413
- created.push(await this.createWorkItem(item));
5297
+ toMoney(amount, currency) {
5298
+ return {
5299
+ amount,
5300
+ currency: currency?.toUpperCase() ?? "USD"
5301
+ };
5302
+ }
5303
+ toMetadata(metadata) {
5304
+ if (!metadata)
5305
+ return;
5306
+ const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
5307
+ if (entries.length === 0)
5308
+ return;
5309
+ return Object.fromEntries(entries);
5310
+ }
5311
+ mergeMetadata(base, extras) {
5312
+ const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
5313
+ if (!base && filteredExtras.length === 0) {
5314
+ return;
5414
5315
  }
5415
- return created;
5316
+ return {
5317
+ ...base ?? {},
5318
+ ...Object.fromEntries(filteredExtras)
5319
+ };
5416
5320
  }
5417
5321
  }
5418
- function mapPriority(priority) {
5419
- if (!priority)
5322
+ function mapRefundReason(reason) {
5323
+ if (!reason)
5420
5324
  return;
5421
- return PRIORITY_MAP[priority] ?? undefined;
5325
+ const allowed = [
5326
+ "duplicate",
5327
+ "fraudulent",
5328
+ "requested_by_customer"
5329
+ ];
5330
+ return allowed.includes(reason) ? reason : undefined;
5422
5331
  }
5423
- function resolveLabelIds(defaults, tags) {
5424
- const labelIds = new Set;
5425
- (defaults.labelIds ?? []).forEach((id) => labelIds.add(id));
5426
- if (tags && defaults.tagLabelMap) {
5427
- tags.forEach((tag) => {
5428
- const mapped = defaults.tagLabelMap?.[tag];
5429
- if (mapped)
5430
- labelIds.add(mapped);
5431
- });
5332
+ function mapPaymentIntentStatus(status) {
5333
+ switch (status) {
5334
+ case "requires_payment_method":
5335
+ return "requires_payment_method";
5336
+ case "requires_confirmation":
5337
+ return "requires_confirmation";
5338
+ case "requires_action":
5339
+ case "requires_capture":
5340
+ return "requires_action";
5341
+ case "processing":
5342
+ return "processing";
5343
+ case "succeeded":
5344
+ return "succeeded";
5345
+ case "canceled":
5346
+ return "canceled";
5347
+ default:
5348
+ return "requires_payment_method";
5349
+ }
5350
+ }
5351
+ function mapRefundStatus(status) {
5352
+ switch (status) {
5353
+ case "pending":
5354
+ case "succeeded":
5355
+ case "failed":
5356
+ case "canceled":
5357
+ return status;
5358
+ default:
5359
+ return "pending";
5360
+ }
5361
+ }
5362
+ function mapChargeStatus(status) {
5363
+ switch (status) {
5364
+ case "pending":
5365
+ case "processing":
5366
+ return "pending";
5367
+ case "succeeded":
5368
+ return "succeeded";
5369
+ case "failed":
5370
+ case "canceled":
5371
+ return "failed";
5372
+ default:
5373
+ return "pending";
5432
5374
  }
5433
- const merged = [...labelIds];
5434
- return merged.length > 0 ? merged : undefined;
5435
5375
  }
5436
5376
 
5437
- // src/impls/jira.ts
5377
+ // src/impls/supabase-psql.ts
5438
5378
  import { Buffer as Buffer5 } from "buffer";
5379
+ import { sql as drizzleSql } from "drizzle-orm";
5380
+ import { drizzle } from "drizzle-orm/postgres-js";
5381
+ import postgres from "postgres";
5439
5382
 
5440
- class JiraProjectManagementProvider {
5441
- siteUrl;
5442
- authHeader;
5443
- defaults;
5444
- fetchFn;
5445
- constructor(options) {
5446
- this.siteUrl = normalizeSiteUrl(options.siteUrl);
5447
- this.authHeader = buildAuthHeader(options.email, options.apiToken);
5448
- this.defaults = {
5449
- projectKey: options.projectKey,
5450
- issueType: options.issueType,
5451
- defaultLabels: options.defaultLabels,
5452
- issueTypeMap: options.issueTypeMap
5453
- };
5454
- this.fetchFn = options.fetch ?? fetch;
5455
- }
5456
- async createWorkItem(input) {
5457
- const projectKey = input.projectId ?? this.defaults.projectKey;
5458
- if (!projectKey) {
5459
- throw new Error("Jira projectKey is required to create work items.");
5383
+ class SupabasePostgresProvider {
5384
+ client;
5385
+ db;
5386
+ ownsClient;
5387
+ createDrizzle;
5388
+ constructor(options = {}) {
5389
+ this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
5390
+ if (options.db) {
5391
+ if (!options.client) {
5392
+ throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
5393
+ }
5394
+ this.client = options.client;
5395
+ this.db = options.db;
5396
+ this.ownsClient = false;
5397
+ return;
5460
5398
  }
5461
- const issueType = resolveIssueType(input.type, this.defaults);
5462
- const description = buildJiraDescription(input.description);
5463
- const labels = mergeLabels(this.defaults.defaultLabels, input.tags);
5464
- const priority = mapPriority2(input.priority);
5465
- const payload = {
5466
- fields: {
5467
- project: { key: projectKey },
5468
- summary: input.title,
5469
- description,
5470
- issuetype: { name: issueType },
5471
- labels,
5472
- priority: priority ? { name: priority } : undefined,
5473
- assignee: input.assigneeId ? { accountId: input.assigneeId } : undefined,
5474
- duedate: input.dueDate ? input.dueDate.toISOString().slice(0, 10) : undefined
5399
+ if (options.client) {
5400
+ this.client = options.client;
5401
+ this.ownsClient = false;
5402
+ } else {
5403
+ if (!options.connectionString) {
5404
+ throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
5475
5405
  }
5476
- };
5477
- const response = await this.fetchFn(`${this.siteUrl}/rest/api/3/issue`, {
5478
- method: "POST",
5479
- headers: {
5480
- Authorization: this.authHeader,
5481
- "Content-Type": "application/json",
5482
- Accept: "application/json"
5483
- },
5484
- body: JSON.stringify(payload)
5485
- });
5486
- if (!response.ok) {
5487
- const body = await response.text();
5488
- throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
5406
+ this.client = postgres(options.connectionString, {
5407
+ max: options.maxConnections,
5408
+ prepare: false,
5409
+ ssl: resolveSslMode(options.sslMode)
5410
+ });
5411
+ this.ownsClient = true;
5489
5412
  }
5490
- const data = await response.json();
5413
+ this.db = this.createDrizzle(this.client);
5414
+ }
5415
+ async query(statement, params = []) {
5416
+ const query = buildParameterizedSql(statement, params);
5417
+ const result = await this.db.execute(query);
5418
+ const rows = asRows(result);
5491
5419
  return {
5492
- id: data.id ?? data.key ?? "",
5493
- title: input.title,
5494
- url: data.key ? `${this.siteUrl}/browse/${data.key}` : undefined,
5495
- status: input.status,
5496
- priority: input.priority,
5497
- tags: input.tags,
5498
- projectId: projectKey,
5499
- externalId: input.externalId,
5500
- metadata: input.metadata
5420
+ rows,
5421
+ rowCount: rows.length
5501
5422
  };
5502
5423
  }
5503
- async createWorkItems(items) {
5504
- const created = [];
5505
- for (const item of items) {
5506
- created.push(await this.createWorkItem(item));
5424
+ async execute(statement, params = []) {
5425
+ const query = buildParameterizedSql(statement, params);
5426
+ await this.db.execute(query);
5427
+ }
5428
+ async transaction(run) {
5429
+ const transactionResult = this.client.begin(async (transactionClient) => {
5430
+ const transactionalProvider = new SupabasePostgresProvider({
5431
+ client: transactionClient,
5432
+ db: this.createDrizzle(transactionClient),
5433
+ createDrizzle: this.createDrizzle
5434
+ });
5435
+ return run(transactionalProvider);
5436
+ });
5437
+ return transactionResult;
5438
+ }
5439
+ async close() {
5440
+ if (this.ownsClient) {
5441
+ await this.client.end({ timeout: 5 });
5507
5442
  }
5508
- return created;
5509
5443
  }
5510
5444
  }
5511
- function normalizeSiteUrl(siteUrl) {
5512
- return siteUrl.replace(/\/$/, "");
5513
- }
5514
- function buildAuthHeader(email, apiToken) {
5515
- const token = Buffer5.from(`${email}:${apiToken}`).toString("base64");
5516
- return `Basic ${token}`;
5445
+ function buildParameterizedSql(statement, params) {
5446
+ const segments = [];
5447
+ const pattern = /\$(\d+)/g;
5448
+ let cursor = 0;
5449
+ for (const match of statement.matchAll(pattern)) {
5450
+ const token = match[0];
5451
+ const indexPart = match[1];
5452
+ const start = match.index;
5453
+ if (indexPart == null || start == null)
5454
+ continue;
5455
+ const parameterIndex = Number(indexPart) - 1;
5456
+ if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
5457
+ throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
5458
+ }
5459
+ const staticSegment = statement.slice(cursor, start);
5460
+ if (staticSegment.length > 0) {
5461
+ segments.push(drizzleSql.raw(staticSegment));
5462
+ }
5463
+ const parameterValue = params[parameterIndex];
5464
+ if (parameterValue === undefined) {
5465
+ throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
5466
+ }
5467
+ const normalizedValue = normalizeParam(parameterValue);
5468
+ segments.push(drizzleSql`${normalizedValue}`);
5469
+ cursor = start + token.length;
5470
+ }
5471
+ const tailSegment = statement.slice(cursor);
5472
+ if (tailSegment.length > 0) {
5473
+ segments.push(drizzleSql.raw(tailSegment));
5474
+ }
5475
+ if (segments.length === 0) {
5476
+ return drizzleSql.raw("");
5477
+ }
5478
+ return drizzleSql.join(segments);
5517
5479
  }
5518
- function resolveIssueType(type, defaults) {
5519
- if (type && defaults.issueTypeMap?.[type]) {
5520
- return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
5480
+ function normalizeParam(value) {
5481
+ if (typeof value === "bigint") {
5482
+ return value.toString();
5521
5483
  }
5522
- return defaults.issueType ?? "Task";
5484
+ if (value instanceof Uint8Array) {
5485
+ return Buffer5.from(value);
5486
+ }
5487
+ if (isPlainObject(value)) {
5488
+ return JSON.stringify(value);
5489
+ }
5490
+ return value;
5523
5491
  }
5524
- function mapPriority2(priority) {
5525
- switch (priority) {
5526
- case "urgent":
5527
- return "Highest";
5528
- case "high":
5529
- return "High";
5530
- case "medium":
5531
- return "Medium";
5532
- case "low":
5533
- return "Low";
5534
- case "none":
5535
- default:
5536
- return;
5492
+ function asRows(result) {
5493
+ if (!Array.isArray(result)) {
5494
+ return [];
5537
5495
  }
5496
+ return result;
5538
5497
  }
5539
- function mergeLabels(defaults, tags) {
5540
- const merged = new Set;
5541
- (defaults ?? []).forEach((label) => merged.add(label));
5542
- (tags ?? []).forEach((tag) => merged.add(tag));
5543
- const result = [...merged];
5544
- return result.length > 0 ? result : undefined;
5498
+ function isPlainObject(value) {
5499
+ if (value == null || typeof value !== "object") {
5500
+ return false;
5501
+ }
5502
+ if (Array.isArray(value)) {
5503
+ return false;
5504
+ }
5505
+ if (value instanceof Date) {
5506
+ return false;
5507
+ }
5508
+ if (value instanceof Uint8Array) {
5509
+ return false;
5510
+ }
5511
+ return true;
5545
5512
  }
5546
- function buildJiraDescription(description) {
5547
- if (!description)
5548
- return;
5549
- const lines = description.split(/\r?\n/).filter((line) => line.trim());
5550
- const content = lines.map((line) => ({
5551
- type: "paragraph",
5552
- content: [{ type: "text", text: line }]
5553
- }));
5554
- if (content.length === 0)
5555
- return;
5556
- return { type: "doc", version: 1, content };
5513
+ function resolveSslMode(mode) {
5514
+ switch (mode) {
5515
+ case "allow":
5516
+ return false;
5517
+ case "prefer":
5518
+ return "prefer";
5519
+ case "require":
5520
+ default:
5521
+ return "require";
5522
+ }
5557
5523
  }
5558
5524
 
5559
- // src/impls/notion.ts
5560
- import { Client } from "@notionhq/client";
5561
-
5562
- class NotionProjectManagementProvider {
5563
- client;
5564
- defaults;
5525
+ // src/impls/supabase-vector.ts
5526
+ class SupabaseVectorProvider {
5527
+ database;
5528
+ createTableIfMissing;
5529
+ distanceMetric;
5530
+ quotedSchema;
5531
+ qualifiedTable;
5532
+ collectionIndex;
5533
+ namespaceIndex;
5534
+ ensureTablePromise;
5565
5535
  constructor(options) {
5566
- this.client = options.client ?? new Client({ auth: options.apiKey });
5567
- this.defaults = {
5568
- databaseId: options.databaseId,
5569
- summaryParentPageId: options.summaryParentPageId,
5570
- titleProperty: options.titleProperty,
5571
- statusProperty: options.statusProperty,
5572
- priorityProperty: options.priorityProperty,
5573
- tagsProperty: options.tagsProperty,
5574
- dueDateProperty: options.dueDateProperty,
5575
- descriptionProperty: options.descriptionProperty
5576
- };
5536
+ this.database = options.database ?? new SupabasePostgresProvider({
5537
+ connectionString: options.connectionString,
5538
+ maxConnections: options.maxConnections,
5539
+ sslMode: options.sslMode
5540
+ });
5541
+ this.createTableIfMissing = options.createTableIfMissing ?? true;
5542
+ this.distanceMetric = options.distanceMetric ?? "cosine";
5543
+ const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
5544
+ const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
5545
+ this.quotedSchema = quoteIdentifier(schema);
5546
+ this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
5547
+ this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
5548
+ this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
5577
5549
  }
5578
- async createWorkItem(input) {
5579
- if (input.type === "summary" && this.defaults.summaryParentPageId) {
5580
- return this.createSummaryPage(input);
5550
+ async upsert(request) {
5551
+ if (request.documents.length === 0) {
5552
+ return;
5581
5553
  }
5582
- const databaseId = this.defaults.databaseId;
5583
- if (!databaseId) {
5584
- throw new Error("Notion databaseId is required to create work items.");
5554
+ if (this.createTableIfMissing) {
5555
+ await this.ensureTable();
5585
5556
  }
5586
- const titleProperty = this.defaults.titleProperty ?? "Name";
5587
- const properties = {
5588
- [titleProperty]: buildTitleProperty(input.title)
5589
- };
5590
- applySelect(properties, this.defaults.statusProperty, input.status);
5591
- applySelect(properties, this.defaults.priorityProperty, input.priority);
5592
- applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
5593
- applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
5594
- applyRichText(properties, this.defaults.descriptionProperty, input.description);
5595
- const page = await this.client.pages.create({
5596
- parent: { type: "database_id", database_id: databaseId },
5597
- properties
5557
+ for (const document of request.documents) {
5558
+ await this.database.execute(`INSERT INTO ${this.qualifiedTable}
5559
+ (collection, id, embedding, payload, namespace, expires_at, updated_at)
5560
+ VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
5561
+ ON CONFLICT (collection, id)
5562
+ DO UPDATE SET
5563
+ embedding = EXCLUDED.embedding,
5564
+ payload = EXCLUDED.payload,
5565
+ namespace = EXCLUDED.namespace,
5566
+ expires_at = EXCLUDED.expires_at,
5567
+ updated_at = now();`, [
5568
+ request.collection,
5569
+ document.id,
5570
+ toVectorLiteral(document.vector),
5571
+ document.payload ? JSON.stringify(document.payload) : null,
5572
+ document.namespace ?? null,
5573
+ document.expiresAt ?? null
5574
+ ]);
5575
+ }
5576
+ }
5577
+ async search(query) {
5578
+ const operator = this.distanceOperator;
5579
+ const results = await this.database.query(`SELECT
5580
+ id,
5581
+ payload,
5582
+ namespace,
5583
+ (embedding ${operator} $3::vector) AS distance
5584
+ FROM ${this.qualifiedTable}
5585
+ WHERE collection = $1
5586
+ AND ($2::text IS NULL OR namespace = $2)
5587
+ AND (expires_at IS NULL OR expires_at > now())
5588
+ AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
5589
+ ORDER BY embedding ${operator} $3::vector
5590
+ LIMIT $5;`, [
5591
+ query.collection,
5592
+ query.namespace ?? null,
5593
+ toVectorLiteral(query.vector),
5594
+ query.filter ? JSON.stringify(query.filter) : null,
5595
+ query.topK
5596
+ ]);
5597
+ const mapped = results.rows.map((row) => {
5598
+ const distance = Number(row.distance);
5599
+ return {
5600
+ id: row.id,
5601
+ score: distanceToScore(distance, this.distanceMetric),
5602
+ payload: isRecord(row.payload) ? row.payload : undefined,
5603
+ namespace: row.namespace ?? undefined
5604
+ };
5598
5605
  });
5599
- return {
5600
- id: page.id,
5601
- title: input.title,
5602
- url: "url" in page ? page.url : undefined,
5603
- status: input.status,
5604
- priority: input.priority,
5605
- tags: input.tags,
5606
- projectId: databaseId,
5607
- externalId: input.externalId,
5608
- metadata: input.metadata
5609
- };
5606
+ const scoreThreshold = query.scoreThreshold;
5607
+ if (scoreThreshold == null) {
5608
+ return mapped;
5609
+ }
5610
+ return mapped.filter((result) => result.score >= scoreThreshold);
5610
5611
  }
5611
- async createWorkItems(items) {
5612
- const created = [];
5613
- for (const item of items) {
5614
- created.push(await this.createWorkItem(item));
5612
+ async delete(request) {
5613
+ if (request.ids.length === 0) {
5614
+ return;
5615
5615
  }
5616
- return created;
5616
+ const params = [
5617
+ request.collection,
5618
+ request.ids,
5619
+ request.namespace ?? null
5620
+ ];
5621
+ await this.database.execute(`DELETE FROM ${this.qualifiedTable}
5622
+ WHERE collection = $1
5623
+ AND id = ANY($2::text[])
5624
+ AND ($3::text IS NULL OR namespace = $3);`, params);
5617
5625
  }
5618
- async createSummaryPage(input) {
5619
- const parentId = this.defaults.summaryParentPageId;
5620
- if (!parentId) {
5621
- throw new Error("Notion summaryParentPageId is required for summaries.");
5626
+ async ensureTable() {
5627
+ if (!this.ensureTablePromise) {
5628
+ this.ensureTablePromise = this.createTable();
5622
5629
  }
5623
- const summaryProperties = {
5624
- title: buildTitleProperty(input.title)
5625
- };
5626
- const page = await this.client.pages.create({
5627
- parent: { type: "page_id", page_id: parentId },
5628
- properties: summaryProperties
5629
- });
5630
- if (input.description) {
5631
- const children = buildParagraphBlocks(input.description);
5632
- if (children.length > 0) {
5633
- await this.client.blocks.children.append({
5634
- block_id: page.id,
5635
- children
5636
- });
5637
- }
5630
+ await this.ensureTablePromise;
5631
+ }
5632
+ async createTable() {
5633
+ await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
5634
+ await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
5635
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
5636
+ collection text NOT NULL,
5637
+ id text NOT NULL,
5638
+ embedding vector NOT NULL,
5639
+ payload jsonb,
5640
+ namespace text,
5641
+ expires_at timestamptz,
5642
+ created_at timestamptz NOT NULL DEFAULT now(),
5643
+ updated_at timestamptz NOT NULL DEFAULT now(),
5644
+ PRIMARY KEY (collection, id)
5645
+ );`);
5646
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
5647
+ ON ${this.qualifiedTable} (collection);`);
5648
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
5649
+ ON ${this.qualifiedTable} (namespace);`);
5650
+ }
5651
+ get distanceOperator() {
5652
+ switch (this.distanceMetric) {
5653
+ case "l2":
5654
+ return "<->";
5655
+ case "inner_product":
5656
+ return "<#>";
5657
+ case "cosine":
5658
+ default:
5659
+ return "<=>";
5638
5660
  }
5639
- return {
5640
- id: page.id,
5641
- title: input.title,
5642
- url: "url" in page ? page.url : undefined,
5643
- status: input.status,
5644
- priority: input.priority,
5645
- tags: input.tags,
5646
- projectId: parentId,
5647
- externalId: input.externalId,
5648
- metadata: input.metadata
5649
- };
5650
5661
  }
5651
5662
  }
5652
- function buildTitleProperty(title) {
5653
- return {
5654
- title: [
5655
- {
5656
- type: "text",
5657
- text: { content: title }
5658
- }
5659
- ]
5660
- };
5661
- }
5662
- function buildRichText(text) {
5663
- return {
5664
- rich_text: [
5665
- {
5666
- type: "text",
5667
- text: { content: text }
5668
- }
5669
- ]
5670
- };
5671
- }
5672
- function applySelect(properties, property, value) {
5673
- if (!property || !value)
5674
- return;
5675
- const next = {
5676
- select: { name: value }
5677
- };
5678
- properties[property] = next;
5663
+ function sanitizeIdentifier(value, label) {
5664
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
5665
+ throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
5666
+ }
5667
+ return value;
5679
5668
  }
5680
- function applyMultiSelect(properties, property, values) {
5681
- if (!property || !values || values.length === 0)
5682
- return;
5683
- const next = {
5684
- multi_select: values.map((value) => ({ name: value }))
5685
- };
5686
- properties[property] = next;
5669
+ function quoteIdentifier(value) {
5670
+ return `"${value.replaceAll('"', '""')}"`;
5687
5671
  }
5688
- function applyDate(properties, property, value) {
5689
- if (!property || !value)
5690
- return;
5691
- const next = {
5692
- date: { start: value.toISOString() }
5693
- };
5694
- properties[property] = next;
5672
+ function toVectorLiteral(vector) {
5673
+ if (vector.length === 0) {
5674
+ throw new Error("Supabase vectors must contain at least one dimension.");
5675
+ }
5676
+ for (const value of vector) {
5677
+ if (!Number.isFinite(value)) {
5678
+ throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
5679
+ }
5680
+ }
5681
+ return `[${vector.join(",")}]`;
5695
5682
  }
5696
- function applyRichText(properties, property, value) {
5697
- if (!property || !value)
5698
- return;
5699
- properties[property] = buildRichText(value);
5683
+ function isRecord(value) {
5684
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5700
5685
  }
5701
- function buildParagraphBlocks(text) {
5702
- const lines = text.split(/\r?\n/).filter((line) => line.trim());
5703
- return lines.map((line) => ({
5704
- object: "block",
5705
- type: "paragraph",
5706
- paragraph: {
5707
- rich_text: [
5708
- {
5709
- type: "text",
5710
- text: { content: line }
5711
- }
5712
- ]
5713
- }
5714
- }));
5686
+ function distanceToScore(distance, metric) {
5687
+ switch (metric) {
5688
+ case "inner_product":
5689
+ return -distance;
5690
+ case "l2":
5691
+ return 1 / (1 + distance);
5692
+ case "cosine":
5693
+ default:
5694
+ return 1 - distance;
5695
+ }
5715
5696
  }
5716
5697
 
5717
5698
  // src/impls/tldv-meeting-recorder.ts
@@ -5857,11 +5838,74 @@ async function safeReadError4(response) {
5857
5838
  }
5858
5839
  }
5859
5840
 
5841
+ // src/impls/twilio-sms.ts
5842
+ import Twilio from "twilio";
5843
+
5844
+ class TwilioSmsProvider {
5845
+ client;
5846
+ fromNumber;
5847
+ constructor(options) {
5848
+ this.client = options.client ?? Twilio(options.accountSid, options.authToken);
5849
+ this.fromNumber = options.fromNumber;
5850
+ }
5851
+ async sendSms(input) {
5852
+ const message = await this.client.messages.create({
5853
+ to: input.to,
5854
+ from: input.from ?? this.fromNumber,
5855
+ body: input.body
5856
+ });
5857
+ return {
5858
+ id: message.sid,
5859
+ to: message.to ?? input.to,
5860
+ from: message.from ?? input.from ?? this.fromNumber ?? "",
5861
+ body: message.body ?? input.body,
5862
+ status: mapStatus(message.status),
5863
+ sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
5864
+ deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
5865
+ price: message.price ? Number(message.price) : undefined,
5866
+ priceCurrency: message.priceUnit ?? undefined,
5867
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
5868
+ errorMessage: message.errorMessage ?? undefined
5869
+ };
5870
+ }
5871
+ async getDeliveryStatus(messageId) {
5872
+ const message = await this.client.messages(messageId).fetch();
5873
+ return {
5874
+ status: mapStatus(message.status),
5875
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
5876
+ errorMessage: message.errorMessage ?? undefined,
5877
+ updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
5878
+ };
5879
+ }
5880
+ }
5881
+ function mapStatus(status) {
5882
+ switch (status) {
5883
+ case "queued":
5884
+ case "accepted":
5885
+ case "scheduled":
5886
+ return "queued";
5887
+ case "sending":
5888
+ case "processing":
5889
+ return "sending";
5890
+ case "sent":
5891
+ return "sent";
5892
+ case "delivered":
5893
+ return "delivered";
5894
+ case "undelivered":
5895
+ return "undelivered";
5896
+ case "failed":
5897
+ case "canceled":
5898
+ return "failed";
5899
+ default:
5900
+ return "queued";
5901
+ }
5902
+ }
5903
+
5860
5904
  // src/impls/provider-factory.ts
5861
5905
  import { Buffer as Buffer6 } from "buffer";
5862
- import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
5863
- import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
5864
5906
  import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
5907
+ import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
5908
+ import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
5865
5909
  var SECRET_CACHE = new Map;
5866
5910
 
5867
5911
  class IntegrationProviderFactory {
@@ -5945,6 +5989,12 @@ class IntegrationProviderFactory {
5945
5989
  defaultRepo: config?.defaultRepo,
5946
5990
  apiBaseUrl: config?.apiBaseUrl
5947
5991
  });
5992
+ case "messaging.telegram":
5993
+ return new TelegramMessagingProvider({
5994
+ botToken: requireSecret(secrets, "botToken", "Telegram bot token is required"),
5995
+ defaultChatId: config?.defaultChatId,
5996
+ apiBaseUrl: config?.apiBaseUrl
5997
+ });
5948
5998
  case "messaging.whatsapp.meta":
5949
5999
  return new MetaWhatsappMessagingProvider({
5950
6000
  accessToken: requireSecret(secrets, "accessToken", "Meta WhatsApp access token is required"),