@contractspec/integration.providers-impls 3.7.6 → 3.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +80 -241
  2. package/dist/impls/composio-fallback-resolver.d.ts +4 -4
  3. package/dist/impls/composio-fallback-resolver.js +73 -73
  4. package/dist/impls/composio-mcp.d.ts +1 -1
  5. package/dist/impls/composio-proxies.d.ts +5 -5
  6. package/dist/impls/composio-sdk.d.ts +1 -1
  7. package/dist/impls/elevenlabs-voice.d.ts +1 -1
  8. package/dist/impls/fal-voice.d.ts +1 -1
  9. package/dist/impls/gcs-storage.d.ts +1 -1
  10. package/dist/impls/gmail-inbound.d.ts +1 -1
  11. package/dist/impls/gradium-voice.d.ts +2 -2
  12. package/dist/impls/health/base-health-provider.d.ts +1 -1
  13. package/dist/impls/health/official-health-providers.d.ts +1 -1
  14. package/dist/impls/health/providers.d.ts +1 -1
  15. package/dist/impls/health-provider-factory.d.ts +1 -1
  16. package/dist/impls/index.d.ts +30 -30
  17. package/dist/impls/index.js +2150 -2150
  18. package/dist/impls/mistral-conversational.d.ts +1 -1
  19. package/dist/impls/mistral-conversational.js +159 -159
  20. package/dist/impls/posthog-reader.d.ts +1 -1
  21. package/dist/impls/provider-factory.d.ts +11 -11
  22. package/dist/impls/provider-factory.js +2073 -2073
  23. package/dist/index.d.ts +12 -12
  24. package/dist/index.js +2057 -2057
  25. package/dist/node/impls/composio-fallback-resolver.js +73 -73
  26. package/dist/node/impls/index.js +2150 -2150
  27. package/dist/node/impls/mistral-conversational.js +159 -159
  28. package/dist/node/impls/provider-factory.js +2073 -2073
  29. package/dist/node/index.js +2057 -2057
  30. package/dist/node/secrets/provider.js +2 -2
  31. package/dist/secrets/provider.d.ts +2 -2
  32. package/dist/secrets/provider.js +2 -2
  33. package/package.json +11 -11
@@ -2816,476 +2816,519 @@ 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
- }
2829
- this.client = options.client ?? new Mistral({
2830
- apiKey: options.apiKey,
2831
- serverURL: options.serverURL,
2832
- userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
2833
- });
2834
- this.defaultModel = options.defaultModel ?? "mistral-large-latest";
2835
- }
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);
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;
2840
2837
  }
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
- }
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.");
2897
2842
  }
2898
- const message = {
2899
- role: "assistant",
2900
- content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
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
+ }
2901
2858
  };
2902
- if (aggregatedToolCalls.length > 0) {
2903
- message.content = [
2904
- ...aggregatedToolCalls,
2905
- ...aggregatedParts.length ? aggregatedParts : []
2906
- ];
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)
2867
+ });
2868
+ if (!response.ok) {
2869
+ const body = await response.text();
2870
+ throw new Error(`Jira API error (${response.status}): ${body || response.statusText}`);
2907
2871
  }
2908
- yield {
2909
- type: "end",
2910
- response: {
2911
- message,
2912
- usage,
2913
- finishReason: mapFinishReason(finishReason)
2914
- }
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
2915
2883
  };
2916
2884
  }
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" };
2885
+ async createWorkItems(items) {
2886
+ const created = [];
2887
+ for (const item of items) {
2888
+ created.push(await this.createWorkItem(item));
2951
2889
  }
2952
- return request;
2890
+ return created;
2953
2891
  }
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
- };
2965
- }
2966
- const message = this.fromAssistantMessage(firstChoice.message);
2967
- return {
2968
- message,
2969
- usage: this.fromUsage(response.usage),
2970
- finishReason: mapFinishReason(firstChoice.finishReason),
2971
- raw: response
2972
- };
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";
2973
2903
  }
2974
- fromUsage(usage) {
2975
- if (!usage)
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:
2976
2918
  return;
2977
- return {
2978
- promptTokens: usage.promptTokens ?? 0,
2979
- completionTokens: usage.completionTokens ?? 0,
2980
- totalTokens: usage.totalTokens ?? 0
2981
- };
2982
2919
  }
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: "" });
3000
- }
3001
- return {
3002
- role: "assistant",
3003
- content: parts
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
3004
2963
  };
3005
2964
  }
3006
- fromMistralToolCall(call, index) {
3007
- const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
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.");
2969
+ }
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;
3008
2983
  return {
3009
- type: "tool-call",
3010
- id: call.id ?? `tool-call-${index}`,
3011
- name: call.function.name,
3012
- arguments: args
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
3013
2993
  };
3014
2994
  }
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
- };
2995
+ async createWorkItems(items) {
2996
+ const created = [];
2997
+ for (const item of items) {
2998
+ created.push(await this.createWorkItem(item));
3046
2999
  }
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
3063
- }
3064
- }));
3000
+ return created;
3065
3001
  }
3066
3002
  }
3067
- function mapFinishReason(reason) {
3068
- if (!reason)
3003
+ function mapPriority2(priority) {
3004
+ if (!priority)
3069
3005
  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;
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
+ });
3083
3017
  }
3018
+ const merged = [...labelIds];
3019
+ return merged.length > 0 ? merged : undefined;
3084
3020
  }
3085
3021
 
3086
- // src/impls/mistral-embedding.ts
3087
- import { Mistral as Mistral2 } from "@mistralai/mistralai";
3088
-
3089
- class MistralEmbeddingProvider {
3090
- client;
3091
- defaultModel;
3022
+ // src/impls/messaging-github.ts
3023
+ class GithubMessagingProvider {
3024
+ token;
3025
+ defaultOwner;
3026
+ defaultRepo;
3027
+ apiBaseUrl;
3092
3028
  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
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 })
3099
3044
  });
3100
- this.defaultModel = options.defaultModel ?? "mistral-embed";
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)
3059
+ }
3060
+ };
3101
3061
  }
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)
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.");
3067
+ }
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 })
3109
3076
  });
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
- }));
3077
+ const body = await response.json();
3078
+ if (!response.ok || !body.id) {
3079
+ throw new Error(`GitHub updateMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
3080
+ }
3081
+ return {
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
+ }
3091
+ };
3117
3092
  }
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");
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).");
3122
3100
  }
3123
- return result;
3101
+ return {
3102
+ owner,
3103
+ repo,
3104
+ issueNumber
3105
+ };
3124
3106
  }
3125
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
+ }
3126
3128
 
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;
3129
+ // src/impls/messaging-slack.ts
3130
+ class SlackMessagingProvider {
3131
+ botToken;
3132
+ defaultChannelId;
3133
+ apiBaseUrl;
3144
3134
  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;
3135
+ this.botToken = options.botToken;
3136
+ this.defaultChannelId = options.defaultChannelId;
3137
+ this.apiBaseUrl = options.apiBaseUrl ?? "https://slack.com/api";
3153
3138
  }
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);
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.");
3167
3143
  }
3168
- const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
3144
+ const payload = {
3145
+ channel,
3146
+ text: input.text,
3147
+ mrkdwn: input.markdown ?? true,
3148
+ thread_ts: input.threadId
3149
+ };
3150
+ const response = await fetch(`${this.apiBaseUrl}/chat.postMessage`, {
3169
3151
  method: "POST",
3170
3152
  headers: {
3171
- Authorization: `Bearer ${this.apiKey}`
3153
+ authorization: `Bearer ${this.botToken}`,
3154
+ "content-type": "application/json"
3172
3155
  },
3173
- body: formData
3156
+ body: JSON.stringify(payload)
3174
3157
  });
3175
- if (!response.ok) {
3176
- const body = await response.text();
3177
- throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
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}`}`);
3178
3161
  }
3179
- const payload = await response.json();
3180
- return toTranscriptionResult(payload, input);
3162
+ return {
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
+ }
3170
+ };
3181
3171
  }
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
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.");
3176
+ }
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
+ })
3193
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
3201
+ }
3202
+ };
3194
3203
  }
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
3204
  }
3209
- function parseSegments(record) {
3210
- if (!Array.isArray(record.segments)) {
3211
- return [];
3205
+
3206
+ // src/impls/messaging-whatsapp-meta.ts
3207
+ class MetaWhatsappMessagingProvider {
3208
+ accessToken;
3209
+ phoneNumberId;
3210
+ apiVersion;
3211
+ constructor(options) {
3212
+ this.accessToken = options.accessToken;
3213
+ this.phoneNumberId = options.phoneNumberId;
3214
+ this.apiVersion = options.apiVersion ?? "v22.0";
3212
3215
  }
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;
3216
+ async sendMessage(input) {
3217
+ const to = input.recipientId;
3218
+ if (!to) {
3219
+ throw new Error("Meta WhatsApp sendMessage requires recipientId.");
3219
3220
  }
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)
3221
+ const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
3222
+ method: "POST",
3223
+ headers: {
3224
+ authorization: `Bearer ${this.accessToken}`,
3225
+ "content-type": "application/json"
3226
+ },
3227
+ body: JSON.stringify({
3228
+ messaging_product: "whatsapp",
3229
+ to,
3230
+ type: "text",
3231
+ text: {
3232
+ body: input.text,
3233
+ preview_url: false
3234
+ }
3235
+ })
3229
3236
  });
3237
+ const body = await response.json();
3238
+ const messageId = body.messages?.[0]?.id;
3239
+ if (!response.ok || !messageId) {
3240
+ const errorCode = body.error?.code != null ? String(body.error.code) : "";
3241
+ throw new Error(`Meta WhatsApp sendMessage failed: ${body.error?.message ?? `HTTP_${response.status}`}${errorCode ? ` (${errorCode})` : ""}`);
3242
+ }
3243
+ return {
3244
+ id: messageId,
3245
+ providerMessageId: messageId,
3246
+ status: "sent",
3247
+ sentAt: new Date,
3248
+ metadata: {
3249
+ phoneNumberId: this.phoneNumberId
3250
+ }
3251
+ };
3230
3252
  }
3231
- return parsed;
3232
3253
  }
3233
- function parseWordTimings(value) {
3234
- if (!Array.isArray(value)) {
3235
- return [];
3254
+
3255
+ // src/impls/messaging-whatsapp-twilio.ts
3256
+ import { Buffer as Buffer4 } from "buffer";
3257
+
3258
+ class TwilioWhatsappMessagingProvider {
3259
+ accountSid;
3260
+ authToken;
3261
+ fromNumber;
3262
+ constructor(options) {
3263
+ this.accountSid = options.accountSid;
3264
+ this.authToken = options.authToken;
3265
+ this.fromNumber = options.fromNumber;
3236
3266
  }
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;
3267
+ async sendMessage(input) {
3268
+ const to = normalizeWhatsappAddress(input.recipientId);
3269
+ const from = normalizeWhatsappAddress(input.channelId ?? this.fromNumber);
3270
+ if (!to) {
3271
+ throw new Error("Twilio WhatsApp sendMessage requires recipientId.");
3245
3272
  }
3246
- words.push({
3247
- word,
3248
- startMs: secondsToMs(startSeconds),
3249
- endMs: secondsToMs(endSeconds),
3250
- confidence: readNumber2(wordRecord, "confidence")
3251
- });
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;
3273
+ if (!from) {
3274
+ throw new Error("Twilio WhatsApp sendMessage requires channelId or configured fromNumber.");
3261
3275
  }
3262
- seen.add(segment.speakerId);
3263
- speakers.push({
3264
- id: segment.speakerId,
3265
- name: segment.speakerName
3276
+ const params = new URLSearchParams;
3277
+ params.set("To", to);
3278
+ params.set("From", from);
3279
+ params.set("Body", input.text);
3280
+ const auth = Buffer4.from(`${this.accountSid}:${this.authToken}`).toString("base64");
3281
+ const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`, {
3282
+ method: "POST",
3283
+ headers: {
3284
+ authorization: `Basic ${auth}`,
3285
+ "content-type": "application/x-www-form-urlencoded"
3286
+ },
3287
+ body: params.toString()
3266
3288
  });
3289
+ const body = await response.json();
3290
+ if (!response.ok || !body.sid) {
3291
+ throw new Error(`Twilio WhatsApp sendMessage failed: ${body.error_message ?? `HTTP_${response.status}`}`);
3292
+ }
3293
+ return {
3294
+ id: body.sid,
3295
+ providerMessageId: body.sid,
3296
+ status: mapTwilioStatus(body.status),
3297
+ sentAt: new Date,
3298
+ errorCode: body.error_code != null ? String(body.error_code) : undefined,
3299
+ errorMessage: body.error_message ?? undefined,
3300
+ metadata: {
3301
+ from,
3302
+ to
3303
+ }
3304
+ };
3267
3305
  }
3268
- return speakers;
3269
3306
  }
3270
- function normalizeBaseUrl(url) {
3271
- return url.endsWith("/") ? url.slice(0, -1) : url;
3272
- }
3273
- function asRecord2(value) {
3274
- if (value && typeof value === "object") {
3307
+ function normalizeWhatsappAddress(value) {
3308
+ if (!value)
3309
+ return null;
3310
+ if (value.startsWith("whatsapp:"))
3275
3311
  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;
3312
+ return `whatsapp:${value}`;
3286
3313
  }
3287
- function secondsToMs(value) {
3288
- return Math.round(value * 1000);
3314
+ function mapTwilioStatus(status) {
3315
+ switch (status) {
3316
+ case "queued":
3317
+ case "accepted":
3318
+ case "scheduled":
3319
+ return "queued";
3320
+ case "sending":
3321
+ return "sending";
3322
+ case "delivered":
3323
+ return "delivered";
3324
+ case "failed":
3325
+ case "undelivered":
3326
+ case "canceled":
3327
+ return "failed";
3328
+ case "sent":
3329
+ default:
3330
+ return "sent";
3331
+ }
3289
3332
  }
3290
3333
 
3291
3334
  // src/impls/mistral-conversational.session.ts
@@ -3361,876 +3404,825 @@ class MistralConversationSession {
3361
3404
  return this.closedSummary;
3362
3405
  }
3363
3406
  this.closed = true;
3364
- await this.pending;
3365
- const durationMs = Date.now() - this.startedAt;
3366
- const summary = {
3367
- sessionId: this.sessionId,
3368
- durationMs,
3369
- turns: this.turns.map((turn) => ({
3370
- role: turn.role === "assistant" ? "agent" : turn.role,
3371
- text: turn.text,
3372
- startMs: turn.startMs,
3373
- endMs: turn.endMs
3374
- })),
3375
- transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
3376
- `)
3377
- };
3378
- this.closedSummary = summary;
3379
- this.queue.push({
3380
- type: "session_ended",
3381
- reason: "closed_by_client",
3382
- durationMs
3383
- });
3384
- this.queue.close();
3385
- return summary;
3386
- }
3387
- async handleUserText(text) {
3388
- if (this.closed) {
3389
- return;
3390
- }
3391
- const userStart = Date.now();
3392
- this.queue.push({ type: "user_speech_started" });
3393
- this.queue.push({ type: "user_speech_ended", transcript: text });
3394
- this.queue.push({
3395
- type: "transcript",
3396
- role: "user",
3397
- text,
3398
- timestamp: userStart
3399
- });
3400
- this.turns.push({
3401
- role: "user",
3402
- text,
3403
- startMs: userStart,
3404
- endMs: Date.now()
3405
- });
3406
- this.history.push({ role: "user", content: text });
3407
- 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
3407
+ await this.pending;
3408
+ const durationMs = Date.now() - this.startedAt;
3409
+ const summary = {
3410
+ sessionId: this.sessionId,
3411
+ durationMs,
3412
+ turns: this.turns.map((turn) => ({
3413
+ role: turn.role === "assistant" ? "agent" : turn.role,
3414
+ text: turn.text,
3415
+ startMs: turn.startMs,
3416
+ endMs: turn.endMs
3417
+ })),
3418
+ transcript: this.turns.map((turn) => `${turn.role}: ${turn.text}`).join(`
3419
+ `)
3420
+ };
3421
+ this.closedSummary = summary;
3422
+ this.queue.push({
3423
+ type: "session_ended",
3424
+ reason: "closed_by_client",
3425
+ durationMs
3568
3426
  });
3569
- this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
3570
- this.distance = options.distance ?? "Cosine";
3427
+ this.queue.close();
3428
+ return summary;
3571
3429
  }
3572
- async upsert(request) {
3573
- if (request.documents.length === 0)
3430
+ async handleUserText(text) {
3431
+ if (this.closed) {
3574
3432
  return;
3575
- const firstDocument = request.documents[0];
3576
- if (!firstDocument)
3433
+ }
3434
+ const userStart = Date.now();
3435
+ this.queue.push({ type: "user_speech_started" });
3436
+ this.queue.push({ type: "user_speech_ended", transcript: text });
3437
+ this.queue.push({
3438
+ type: "transcript",
3439
+ role: "user",
3440
+ text,
3441
+ timestamp: userStart
3442
+ });
3443
+ this.turns.push({
3444
+ role: "user",
3445
+ text,
3446
+ startMs: userStart,
3447
+ endMs: Date.now()
3448
+ });
3449
+ this.history.push({ role: "user", content: text });
3450
+ const assistantStart = Date.now();
3451
+ const assistantText = await this.complete(this.history, {
3452
+ ...this.sessionConfig,
3453
+ llmModel: this.sessionConfig.llmModel ?? this.defaultModel
3454
+ });
3455
+ if (this.closed) {
3577
3456
  return;
3578
- const vectorSize = firstDocument.vector.length;
3579
- if (this.createCollectionIfMissing) {
3580
- await this.ensureCollection(request.collection, vectorSize);
3581
3457
  }
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
3458
+ const normalizedAssistantText = assistantText.trim();
3459
+ const finalAssistantText = normalizedAssistantText.length > 0 ? normalizedAssistantText : "I was unable to produce a response.";
3460
+ this.queue.push({
3461
+ type: "agent_speech_started",
3462
+ text: finalAssistantText
3594
3463
  });
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
3464
+ this.queue.push({
3465
+ type: "transcript",
3466
+ role: "agent",
3467
+ text: finalAssistantText,
3468
+ timestamp: assistantStart
3604
3469
  });
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
3470
+ this.queue.push({ type: "agent_speech_ended" });
3471
+ this.turns.push({
3472
+ role: "assistant",
3473
+ text: finalAssistantText,
3474
+ startMs: assistantStart,
3475
+ endMs: Date.now()
3616
3476
  });
3477
+ this.history.push({ role: "assistant", content: finalAssistantText });
3617
3478
  }
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
- });
3479
+ emitError(error) {
3480
+ if (this.closed) {
3481
+ return;
3628
3482
  }
3483
+ this.queue.push({ type: "error", error: toError(error) });
3484
+ }
3485
+ }
3486
+ function toError(error) {
3487
+ if (error instanceof Error) {
3488
+ return error;
3629
3489
  }
3490
+ return new Error(String(error));
3630
3491
  }
3631
3492
 
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";
3493
+ // src/impls/mistral-stt.ts
3494
+ var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
3495
+ var DEFAULT_MODEL = "voxtral-mini-latest";
3496
+ var AUDIO_MIME_BY_FORMAT = {
3497
+ mp3: "audio/mpeg",
3498
+ wav: "audio/wav",
3499
+ ogg: "audio/ogg",
3500
+ pcm: "audio/pcm",
3501
+ opus: "audio/opus"
3502
+ };
3637
3503
 
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;
3504
+ class MistralSttProvider {
3505
+ apiKey;
3506
+ defaultModel;
3507
+ defaultLanguage;
3508
+ baseUrl;
3509
+ fetchImpl;
3510
+ constructor(options) {
3511
+ if (!options.apiKey) {
3512
+ throw new Error("MistralSttProvider requires an apiKey");
3667
3513
  }
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
- };
3514
+ this.apiKey = options.apiKey;
3515
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
3516
+ this.defaultLanguage = options.defaultLanguage;
3517
+ this.baseUrl = normalizeBaseUrl(options.serverURL ?? DEFAULT_BASE_URL4);
3518
+ this.fetchImpl = options.fetchImpl ?? fetch;
3678
3519
  }
3679
- async execute(statement, params = []) {
3680
- const query = buildParameterizedSql(statement, params);
3681
- await this.db.execute(query);
3520
+ async transcribe(input) {
3521
+ const formData = new FormData;
3522
+ const model = input.model ?? this.defaultModel;
3523
+ const mimeType = AUDIO_MIME_BY_FORMAT[input.audio.format] ?? "audio/wav";
3524
+ const fileName = `audio.${input.audio.format}`;
3525
+ const audioBytes = new Uint8Array(input.audio.data);
3526
+ const blob = new Blob([audioBytes], { type: mimeType });
3527
+ formData.append("file", blob, fileName);
3528
+ formData.append("model", model);
3529
+ formData.append("response_format", "verbose_json");
3530
+ const language = input.language ?? this.defaultLanguage;
3531
+ if (language) {
3532
+ formData.append("language", language);
3533
+ }
3534
+ const response = await this.fetchImpl(`${this.baseUrl}/audio/transcriptions`, {
3535
+ method: "POST",
3536
+ headers: {
3537
+ Authorization: `Bearer ${this.apiKey}`
3538
+ },
3539
+ body: formData
3540
+ });
3541
+ if (!response.ok) {
3542
+ const body = await response.text();
3543
+ throw new Error(`Mistral transcription request failed (${response.status}): ${body}`);
3544
+ }
3545
+ const payload = await response.json();
3546
+ return toTranscriptionResult(payload, input);
3682
3547
  }
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);
3548
+ }
3549
+ function toTranscriptionResult(payload, input) {
3550
+ const record = asRecord2(payload);
3551
+ const text = readString3(record, "text") ?? "";
3552
+ const language = readString3(record, "language") ?? input.language ?? "unknown";
3553
+ const segments = parseSegments(record);
3554
+ if (segments.length === 0 && text.length > 0) {
3555
+ segments.push({
3556
+ text,
3557
+ startMs: 0,
3558
+ endMs: input.audio.durationMs ?? 0
3691
3559
  });
3692
- return transactionResult;
3693
3560
  }
3694
- async close() {
3695
- if (this.ownsClient) {
3696
- await this.client.end({ timeout: 5 });
3561
+ const durationMs = input.audio.durationMs ?? segments.reduce((max, segment) => Math.max(max, segment.endMs), 0);
3562
+ const topLevelWords = parseWordTimings(record.words);
3563
+ const flattenedWords = segments.flatMap((segment) => segment.wordTimings ?? []);
3564
+ const wordTimings = topLevelWords.length > 0 ? topLevelWords : flattenedWords.length > 0 ? flattenedWords : undefined;
3565
+ const speakers = dedupeSpeakers(segments);
3566
+ return {
3567
+ text,
3568
+ segments,
3569
+ language,
3570
+ durationMs,
3571
+ speakers: speakers.length > 0 ? speakers : undefined,
3572
+ wordTimings
3573
+ };
3574
+ }
3575
+ function parseSegments(record) {
3576
+ if (!Array.isArray(record.segments)) {
3577
+ return [];
3578
+ }
3579
+ const parsed = [];
3580
+ for (const entry of record.segments) {
3581
+ const segmentRecord = asRecord2(entry);
3582
+ const text = readString3(segmentRecord, "text");
3583
+ if (!text) {
3584
+ continue;
3697
3585
  }
3586
+ const startSeconds = readNumber2(segmentRecord, "start") ?? 0;
3587
+ const endSeconds = readNumber2(segmentRecord, "end") ?? startSeconds;
3588
+ parsed.push({
3589
+ text,
3590
+ startMs: secondsToMs(startSeconds),
3591
+ endMs: secondsToMs(endSeconds),
3592
+ speakerId: readString3(segmentRecord, "speaker") ?? undefined,
3593
+ confidence: readNumber2(segmentRecord, "confidence"),
3594
+ wordTimings: parseWordTimings(segmentRecord.words)
3595
+ });
3698
3596
  }
3597
+ return parsed;
3699
3598
  }
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)
3599
+ function parseWordTimings(value) {
3600
+ if (!Array.isArray(value)) {
3601
+ return [];
3602
+ }
3603
+ const words = [];
3604
+ for (const entry of value) {
3605
+ const wordRecord = asRecord2(entry);
3606
+ const word = readString3(wordRecord, "word");
3607
+ const startSeconds = readNumber2(wordRecord, "start");
3608
+ const endSeconds = readNumber2(wordRecord, "end");
3609
+ if (!word || startSeconds == null || endSeconds == null) {
3709
3610
  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
3611
  }
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("");
3612
+ words.push({
3613
+ word,
3614
+ startMs: secondsToMs(startSeconds),
3615
+ endMs: secondsToMs(endSeconds),
3616
+ confidence: readNumber2(wordRecord, "confidence")
3617
+ });
3732
3618
  }
3733
- return drizzleSql.join(segments);
3619
+ return words;
3734
3620
  }
3735
- function normalizeParam(value) {
3736
- if (typeof value === "bigint") {
3737
- return value.toString();
3738
- }
3739
- if (value instanceof Uint8Array) {
3740
- return Buffer3.from(value);
3741
- }
3742
- if (isPlainObject(value)) {
3743
- return JSON.stringify(value);
3621
+ function dedupeSpeakers(segments) {
3622
+ const seen = new Set;
3623
+ const speakers = [];
3624
+ for (const segment of segments) {
3625
+ if (!segment.speakerId || seen.has(segment.speakerId)) {
3626
+ continue;
3627
+ }
3628
+ seen.add(segment.speakerId);
3629
+ speakers.push({
3630
+ id: segment.speakerId,
3631
+ name: segment.speakerName
3632
+ });
3744
3633
  }
3745
- return value;
3634
+ return speakers;
3746
3635
  }
3747
- function asRows(result) {
3748
- if (!Array.isArray(result)) {
3749
- return [];
3750
- }
3751
- return result;
3636
+ function normalizeBaseUrl(url) {
3637
+ return url.endsWith("/") ? url.slice(0, -1) : url;
3752
3638
  }
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;
3762
- }
3763
- if (value instanceof Uint8Array) {
3764
- return false;
3639
+ function asRecord2(value) {
3640
+ if (value && typeof value === "object") {
3641
+ return value;
3765
3642
  }
3766
- return true;
3643
+ return {};
3767
3644
  }
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";
3777
- }
3645
+ function readString3(record, key) {
3646
+ const value = record[key];
3647
+ return typeof value === "string" ? value : undefined;
3648
+ }
3649
+ function readNumber2(record, key) {
3650
+ const value = record[key];
3651
+ return typeof value === "number" ? value : undefined;
3652
+ }
3653
+ function secondsToMs(value) {
3654
+ return Math.round(value * 1000);
3778
3655
  }
3779
3656
 
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;
3657
+ // src/impls/mistral-conversational.ts
3658
+ var DEFAULT_BASE_URL5 = "https://api.mistral.ai/v1";
3659
+ var DEFAULT_MODEL2 = "mistral-small-latest";
3660
+ var DEFAULT_VOICE = "default";
3661
+
3662
+ class MistralConversationalProvider {
3663
+ apiKey;
3664
+ defaultModel;
3665
+ defaultVoiceId;
3666
+ baseUrl;
3667
+ fetchImpl;
3668
+ sttProvider;
3790
3669
  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
- ]);
3670
+ if (!options.apiKey) {
3671
+ throw new Error("MistralConversationalProvider requires an apiKey");
3830
3672
  }
3673
+ this.apiKey = options.apiKey;
3674
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL2;
3675
+ this.defaultVoiceId = options.defaultVoiceId ?? DEFAULT_VOICE;
3676
+ this.baseUrl = normalizeBaseUrl2(options.serverURL ?? DEFAULT_BASE_URL5);
3677
+ this.fetchImpl = options.fetchImpl ?? fetch;
3678
+ this.sttProvider = options.sttProvider ?? new MistralSttProvider({
3679
+ apiKey: options.apiKey,
3680
+ defaultModel: options.sttOptions?.defaultModel,
3681
+ defaultLanguage: options.sttOptions?.defaultLanguage,
3682
+ serverURL: options.sttOptions?.serverURL ?? options.serverURL,
3683
+ fetchImpl: this.fetchImpl
3684
+ });
3831
3685
  }
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
- };
3686
+ async startSession(config) {
3687
+ return new MistralConversationSession({
3688
+ sessionConfig: {
3689
+ ...config,
3690
+ voiceId: config.voiceId || this.defaultVoiceId
3691
+ },
3692
+ defaultModel: this.defaultModel,
3693
+ complete: (history, sessionConfig) => this.completeConversation(history, sessionConfig),
3694
+ sttProvider: this.sttProvider
3860
3695
  });
3861
- const scoreThreshold = query.scoreThreshold;
3862
- if (scoreThreshold == null) {
3863
- return mapped;
3864
- }
3865
- return mapped.filter((result) => result.score >= scoreThreshold);
3866
3696
  }
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
3697
+ async listVoices() {
3698
+ return [
3699
+ {
3700
+ id: this.defaultVoiceId,
3701
+ name: "Mistral Default Voice",
3702
+ description: "Default conversational voice profile.",
3703
+ capabilities: ["conversational"]
3704
+ }
3875
3705
  ];
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
3706
  }
3881
- async ensureTable() {
3882
- if (!this.ensureTablePromise) {
3883
- this.ensureTablePromise = this.createTable();
3707
+ async completeConversation(history, sessionConfig) {
3708
+ const model = sessionConfig.llmModel ?? this.defaultModel;
3709
+ const messages = [];
3710
+ if (sessionConfig.systemPrompt) {
3711
+ messages.push({ role: "system", content: sessionConfig.systemPrompt });
3884
3712
  }
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 "<=>";
3713
+ for (const item of history) {
3714
+ messages.push({ role: item.role, content: item.content });
3915
3715
  }
3716
+ const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
3717
+ method: "POST",
3718
+ headers: {
3719
+ Authorization: `Bearer ${this.apiKey}`,
3720
+ "Content-Type": "application/json"
3721
+ },
3722
+ body: JSON.stringify({
3723
+ model,
3724
+ messages
3725
+ })
3726
+ });
3727
+ if (!response.ok) {
3728
+ const body = await response.text();
3729
+ throw new Error(`Mistral conversational request failed (${response.status}): ${body}`);
3730
+ }
3731
+ const payload = await response.json();
3732
+ return readAssistantText(payload);
3916
3733
  }
3917
3734
  }
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('"', '""')}"`;
3735
+ function normalizeBaseUrl2(url) {
3736
+ return url.endsWith("/") ? url.slice(0, -1) : url;
3926
3737
  }
3927
- function toVectorLiteral(vector) {
3928
- if (vector.length === 0) {
3929
- throw new Error("Supabase vectors must contain at least one dimension.");
3738
+ function readAssistantText(payload) {
3739
+ const record = asRecord3(payload);
3740
+ const choices = Array.isArray(record.choices) ? record.choices : [];
3741
+ const firstChoice = asRecord3(choices[0]);
3742
+ const message = asRecord3(firstChoice.message);
3743
+ if (typeof message.content === "string") {
3744
+ return message.content;
3930
3745
  }
3931
- for (const value of vector) {
3932
- if (!Number.isFinite(value)) {
3933
- throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
3934
- }
3746
+ if (Array.isArray(message.content)) {
3747
+ const textParts = message.content.map((part) => {
3748
+ const entry = asRecord3(part);
3749
+ const text = entry.text;
3750
+ return typeof text === "string" ? text : "";
3751
+ }).filter((text) => text.length > 0);
3752
+ return textParts.join("");
3935
3753
  }
3936
- return `[${vector.join(",")}]`;
3937
- }
3938
- function isRecord(value) {
3939
- return typeof value === "object" && value !== null && !Array.isArray(value);
3754
+ return "";
3940
3755
  }
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;
3756
+ function asRecord3(value) {
3757
+ if (value && typeof value === "object") {
3758
+ return value;
3950
3759
  }
3760
+ return {};
3951
3761
  }
3952
3762
 
3953
- // src/impls/stripe-payments.ts
3954
- import Stripe from "stripe";
3955
- var API_VERSION = "2026-02-25.clover";
3763
+ // src/impls/mistral-embedding.ts
3764
+ import { Mistral } from "@mistralai/mistralai";
3956
3765
 
3957
- class StripePaymentsProvider {
3958
- stripe;
3766
+ class MistralEmbeddingProvider {
3767
+ client;
3768
+ defaultModel;
3959
3769
  constructor(options) {
3960
- this.stripe = options.stripe ?? new Stripe(options.apiKey, {
3961
- apiVersion: API_VERSION
3770
+ if (!options.apiKey) {
3771
+ throw new Error("MistralEmbeddingProvider requires an apiKey");
3772
+ }
3773
+ this.client = options.client ?? new Mistral({
3774
+ apiKey: options.apiKey,
3775
+ serverURL: options.serverURL
3962
3776
  });
3777
+ this.defaultModel = options.defaultModel ?? "mistral-embed";
3963
3778
  }
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
3779
+ async embedDocuments(documents, options) {
3780
+ if (documents.length === 0)
3781
+ return [];
3782
+ const model = options?.model ?? this.defaultModel;
3783
+ const response = await this.client.embeddings.create({
3784
+ model,
3785
+ inputs: documents.map((doc) => doc.text)
3970
3786
  });
3971
- return this.toCustomer(customer);
3787
+ return response.data.map((item, index) => ({
3788
+ id: documents[index]?.id ?? (item.index != null ? `embedding-${item.index}` : `embedding-${index}`),
3789
+ vector: item.embedding ?? [],
3790
+ dimensions: item.embedding?.length ?? 0,
3791
+ model: response.model,
3792
+ metadata: documents[index]?.metadata ? Object.fromEntries(Object.entries(documents[index]?.metadata ?? {}).map(([key, value]) => [key, String(value)])) : undefined
3793
+ }));
3972
3794
  }
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);
3795
+ async embedQuery(query, options) {
3796
+ const [result] = await this.embedDocuments([{ id: "query", text: query }], options);
3797
+ if (!result) {
3798
+ throw new Error("Failed to compute embedding for query");
3799
+ }
3800
+ return result;
3978
3801
  }
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
3802
+ }
3803
+
3804
+ // src/impls/mistral-llm.ts
3805
+ import { Mistral as Mistral2 } from "@mistralai/mistralai";
3806
+
3807
+ class MistralLLMProvider {
3808
+ client;
3809
+ defaultModel;
3810
+ constructor(options) {
3811
+ if (!options.apiKey) {
3812
+ throw new Error("MistralLLMProvider requires an apiKey");
3813
+ }
3814
+ this.client = options.client ?? new Mistral2({
3815
+ apiKey: options.apiKey,
3816
+ serverURL: options.serverURL,
3817
+ userAgent: options.userAgentSuffix ? `${options.userAgentSuffix}` : undefined
3991
3818
  });
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);
3819
+ this.defaultModel = options.defaultModel ?? "mistral-large-latest";
3997
3820
  }
3998
- async cancelPaymentIntent(paymentIntentId) {
3999
- const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
4000
- return this.toPaymentIntent(intent);
3821
+ async chat(messages, options = {}) {
3822
+ const request = this.buildChatRequest(messages, options);
3823
+ const response = await this.client.chat.complete(request);
3824
+ return this.buildLLMResponse(response);
4001
3825
  }
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
3826
+ async* stream(messages, options = {}) {
3827
+ const request = this.buildChatRequest(messages, options);
3828
+ request.stream = true;
3829
+ const stream = await this.client.chat.stream(request);
3830
+ const aggregatedParts = [];
3831
+ const aggregatedToolCalls = [];
3832
+ let usage;
3833
+ let finishReason;
3834
+ for await (const event of stream) {
3835
+ for (const choice of event.data.choices) {
3836
+ const delta = choice.delta;
3837
+ if (typeof delta.content === "string") {
3838
+ if (delta.content.length > 0) {
3839
+ aggregatedParts.push({ type: "text", text: delta.content });
3840
+ yield {
3841
+ type: "message_delta",
3842
+ delta: { type: "text", text: delta.content },
3843
+ index: choice.index
3844
+ };
3845
+ }
3846
+ } else if (Array.isArray(delta.content)) {
3847
+ for (const chunk of delta.content) {
3848
+ if (chunk.type === "text" && "text" in chunk) {
3849
+ aggregatedParts.push({ type: "text", text: chunk.text });
3850
+ yield {
3851
+ type: "message_delta",
3852
+ delta: { type: "text", text: chunk.text },
3853
+ index: choice.index
3854
+ };
3855
+ }
3856
+ }
3857
+ }
3858
+ if (delta.toolCalls) {
3859
+ let localIndex = 0;
3860
+ for (const call of delta.toolCalls) {
3861
+ const toolCall = this.fromMistralToolCall(call, localIndex);
3862
+ aggregatedToolCalls.push(toolCall);
3863
+ yield {
3864
+ type: "tool_call",
3865
+ call: toolCall,
3866
+ index: choice.index
3867
+ };
3868
+ localIndex += 1;
3869
+ }
3870
+ }
3871
+ if (choice.finishReason && choice.finishReason !== "null") {
3872
+ finishReason = choice.finishReason;
3873
+ }
3874
+ }
3875
+ if (event.data.usage) {
3876
+ const usageEntry = this.fromUsage(event.data.usage);
3877
+ if (usageEntry) {
3878
+ usage = usageEntry;
3879
+ yield { type: "usage", usage: usageEntry };
3880
+ }
3881
+ }
3882
+ }
3883
+ const message = {
3884
+ role: "assistant",
3885
+ content: aggregatedParts.length ? aggregatedParts : [{ type: "text", text: "" }]
3886
+ };
3887
+ if (aggregatedToolCalls.length > 0) {
3888
+ message.content = [
3889
+ ...aggregatedToolCalls,
3890
+ ...aggregatedParts.length ? aggregatedParts : []
3891
+ ];
3892
+ }
3893
+ yield {
3894
+ type: "end",
3895
+ response: {
3896
+ message,
3897
+ usage,
3898
+ finishReason: mapFinishReason(finishReason)
3899
+ }
4021
3900
  };
4022
3901
  }
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));
3902
+ async countTokens(_messages) {
3903
+ throw new Error("Mistral API does not currently support token counting");
4033
3904
  }
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
- }));
3905
+ buildChatRequest(messages, options) {
3906
+ const model = options.model ?? this.defaultModel;
3907
+ const mappedMessages = messages.map((message) => this.toMistralMessage(message));
3908
+ const request = {
3909
+ model,
3910
+ messages: mappedMessages
3911
+ };
3912
+ if (options.temperature != null) {
3913
+ request.temperature = options.temperature;
3914
+ }
3915
+ if (options.topP != null) {
3916
+ request.topP = options.topP;
3917
+ }
3918
+ if (options.maxOutputTokens != null) {
3919
+ request.maxTokens = options.maxOutputTokens;
3920
+ }
3921
+ if (options.stopSequences?.length) {
3922
+ request.stop = options.stopSequences.length === 1 ? options.stopSequences[0] : options.stopSequences;
3923
+ }
3924
+ if (options.tools?.length) {
3925
+ request.tools = options.tools.map((tool) => ({
3926
+ type: "function",
3927
+ function: {
3928
+ name: tool.name,
3929
+ description: tool.description,
3930
+ parameters: typeof tool.inputSchema === "object" && tool.inputSchema !== null ? tool.inputSchema : {}
3931
+ }
3932
+ }));
3933
+ }
3934
+ if (options.responseFormat === "json") {
3935
+ request.responseFormat = { type: "json_object" };
3936
+ }
3937
+ return request;
4056
3938
  }
4057
- toCustomer(customer) {
4058
- const metadata = this.toMetadata(customer.metadata);
4059
- const updatedAtValue = metadata?.updatedAt;
3939
+ buildLLMResponse(response) {
3940
+ const firstChoice = response.choices[0];
3941
+ if (!firstChoice) {
3942
+ return {
3943
+ message: {
3944
+ role: "assistant",
3945
+ content: [{ type: "text", text: "" }]
3946
+ },
3947
+ usage: this.fromUsage(response.usage),
3948
+ raw: response
3949
+ };
3950
+ }
3951
+ const message = this.fromAssistantMessage(firstChoice.message);
4060
3952
  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
3953
+ message,
3954
+ usage: this.fromUsage(response.usage),
3955
+ finishReason: mapFinishReason(firstChoice.finishReason),
3956
+ raw: response
4067
3957
  };
4068
3958
  }
4069
- toPaymentIntent(intent) {
4070
- const metadata = this.toMetadata(intent.metadata);
3959
+ fromUsage(usage) {
3960
+ if (!usage)
3961
+ return;
4071
3962
  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)
3963
+ promptTokens: usage.promptTokens ?? 0,
3964
+ completionTokens: usage.completionTokens ?? 0,
3965
+ totalTokens: usage.totalTokens ?? 0
4081
3966
  };
4082
3967
  }
4083
- toInvoice(invoice) {
4084
- const metadata = this.toMetadata(invoice.metadata);
3968
+ fromAssistantMessage(message) {
3969
+ const parts = [];
3970
+ if (typeof message.content === "string") {
3971
+ parts.push({ type: "text", text: message.content });
3972
+ } else if (Array.isArray(message.content)) {
3973
+ message.content.forEach((chunk) => {
3974
+ if (chunk.type === "text" && "text" in chunk) {
3975
+ parts.push({ type: "text", text: chunk.text });
3976
+ }
3977
+ });
3978
+ }
3979
+ const toolCalls = message.toolCalls?.map((call, index) => this.fromMistralToolCall(call, index)) ?? [];
3980
+ if (toolCalls.length > 0) {
3981
+ parts.splice(0, 0, ...toolCalls);
3982
+ }
3983
+ if (parts.length === 0) {
3984
+ parts.push({ type: "text", text: "" });
3985
+ }
4085
3986
  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
3987
+ role: "assistant",
3988
+ content: parts
4097
3989
  };
4098
3990
  }
4099
- toMoney(amount, currency) {
3991
+ fromMistralToolCall(call, index) {
3992
+ const args = typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments);
4100
3993
  return {
4101
- amount,
4102
- currency: currency?.toUpperCase() ?? "USD"
3994
+ type: "tool-call",
3995
+ id: call.id ?? `tool-call-${index}`,
3996
+ name: call.function.name,
3997
+ arguments: args
4103
3998
  };
4104
3999
  }
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;
4000
+ toMistralMessage(message) {
4001
+ const textContent = this.extractText(message.content);
4002
+ const toolCalls = this.extractToolCalls(message);
4003
+ switch (message.role) {
4004
+ case "system":
4005
+ return {
4006
+ role: "system",
4007
+ content: textContent ?? ""
4008
+ };
4009
+ case "user":
4010
+ return {
4011
+ role: "user",
4012
+ content: textContent ?? ""
4013
+ };
4014
+ case "assistant":
4015
+ return {
4016
+ role: "assistant",
4017
+ content: toolCalls.length > 0 ? null : textContent ?? "",
4018
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
4019
+ };
4020
+ case "tool":
4021
+ return {
4022
+ role: "tool",
4023
+ content: textContent ?? "",
4024
+ toolCallId: message.toolCallId ?? toolCalls[0]?.id
4025
+ };
4026
+ default:
4027
+ return {
4028
+ role: "user",
4029
+ content: textContent ?? ""
4030
+ };
4117
4031
  }
4118
- return {
4119
- ...base ?? {},
4120
- ...Object.fromEntries(filteredExtras)
4121
- };
4032
+ }
4033
+ extractText(parts) {
4034
+ const textParts = parts.filter((part) => part.type === "text").map((part) => part.text);
4035
+ if (textParts.length === 0)
4036
+ return null;
4037
+ return textParts.join("");
4038
+ }
4039
+ extractToolCalls(message) {
4040
+ const toolCallParts = message.content.filter((part) => part.type === "tool-call");
4041
+ return toolCallParts.map((call, index) => ({
4042
+ id: call.id ?? `call_${index}`,
4043
+ type: "function",
4044
+ index,
4045
+ function: {
4046
+ name: call.name,
4047
+ arguments: call.arguments
4048
+ }
4049
+ }));
4122
4050
  }
4123
4051
  }
4124
- function mapRefundReason(reason) {
4052
+ function mapFinishReason(reason) {
4125
4053
  if (!reason)
4126
4054
  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";
4151
- }
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";
4162
- }
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";
4055
+ const normalized = reason.toLowerCase();
4056
+ switch (normalized) {
4057
+ case "stop":
4058
+ return "stop";
4059
+ case "length":
4060
+ return "length";
4061
+ case "tool_call":
4062
+ case "tool_calls":
4063
+ return "tool_call";
4064
+ case "content_filter":
4065
+ return "content_filter";
4174
4066
  default:
4175
- return "pending";
4067
+ return;
4176
4068
  }
4177
4069
  }
4178
4070
 
4179
- // src/impls/postmark-email.ts
4180
- import { ServerClient } from "postmark";
4071
+ // src/impls/notion.ts
4072
+ import { Client } from "@notionhq/client";
4181
4073
 
4182
- class PostmarkEmailProvider {
4074
+ class NotionProjectManagementProvider {
4183
4075
  client;
4184
- defaultFromEmail;
4185
- messageStream;
4076
+ defaults;
4186
4077
  constructor(options) {
4187
- this.client = options.client ?? new ServerClient(options.serverToken, {
4188
- useHttps: true
4078
+ this.client = options.client ?? new Client({ auth: options.apiKey });
4079
+ this.defaults = {
4080
+ databaseId: options.databaseId,
4081
+ summaryParentPageId: options.summaryParentPageId,
4082
+ titleProperty: options.titleProperty,
4083
+ statusProperty: options.statusProperty,
4084
+ priorityProperty: options.priorityProperty,
4085
+ tagsProperty: options.tagsProperty,
4086
+ dueDateProperty: options.dueDateProperty,
4087
+ descriptionProperty: options.descriptionProperty
4088
+ };
4089
+ }
4090
+ async createWorkItem(input) {
4091
+ if (input.type === "summary" && this.defaults.summaryParentPageId) {
4092
+ return this.createSummaryPage(input);
4093
+ }
4094
+ const databaseId = this.defaults.databaseId;
4095
+ if (!databaseId) {
4096
+ throw new Error("Notion databaseId is required to create work items.");
4097
+ }
4098
+ const titleProperty = this.defaults.titleProperty ?? "Name";
4099
+ const properties = {
4100
+ [titleProperty]: buildTitleProperty(input.title)
4101
+ };
4102
+ applySelect(properties, this.defaults.statusProperty, input.status);
4103
+ applySelect(properties, this.defaults.priorityProperty, input.priority);
4104
+ applyMultiSelect(properties, this.defaults.tagsProperty, input.tags);
4105
+ applyDate(properties, this.defaults.dueDateProperty, input.dueDate);
4106
+ applyRichText(properties, this.defaults.descriptionProperty, input.description);
4107
+ const page = await this.client.pages.create({
4108
+ parent: { type: "database_id", database_id: databaseId },
4109
+ properties
4189
4110
  });
4190
- this.defaultFromEmail = options.defaultFromEmail;
4191
- this.messageStream = options.messageStream;
4111
+ return {
4112
+ id: page.id,
4113
+ title: input.title,
4114
+ url: "url" in page ? page.url : undefined,
4115
+ status: input.status,
4116
+ priority: input.priority,
4117
+ tags: input.tags,
4118
+ projectId: databaseId,
4119
+ externalId: input.externalId,
4120
+ metadata: input.metadata
4121
+ };
4192
4122
  }
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)
4123
+ async createWorkItems(items) {
4124
+ const created = [];
4125
+ for (const item of items) {
4126
+ created.push(await this.createWorkItem(item));
4127
+ }
4128
+ return created;
4129
+ }
4130
+ async createSummaryPage(input) {
4131
+ const parentId = this.defaults.summaryParentPageId;
4132
+ if (!parentId) {
4133
+ throw new Error("Notion summaryParentPageId is required for summaries.");
4134
+ }
4135
+ const summaryProperties = {
4136
+ title: buildTitleProperty(input.title)
4209
4137
  };
4210
- const response = await this.client.sendEmail(request);
4138
+ const page = await this.client.pages.create({
4139
+ parent: { type: "page_id", page_id: parentId },
4140
+ properties: summaryProperties
4141
+ });
4142
+ if (input.description) {
4143
+ const children = buildParagraphBlocks(input.description);
4144
+ if (children.length > 0) {
4145
+ await this.client.blocks.children.append({
4146
+ block_id: page.id,
4147
+ children
4148
+ });
4149
+ }
4150
+ }
4211
4151
  return {
4212
- id: response.MessageID,
4213
- providerMessageId: response.MessageID,
4214
- queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
4152
+ id: page.id,
4153
+ title: input.title,
4154
+ url: "url" in page ? page.url : undefined,
4155
+ status: input.status,
4156
+ priority: input.priority,
4157
+ tags: input.tags,
4158
+ projectId: parentId,
4159
+ externalId: input.externalId,
4160
+ metadata: input.metadata
4215
4161
  };
4216
4162
  }
4217
4163
  }
4218
- function formatAddress(address) {
4219
- if (address.name) {
4220
- return `"${address.name}" <${address.email}>`;
4221
- }
4222
- return address.email;
4164
+ function buildTitleProperty(title) {
4165
+ return {
4166
+ title: [
4167
+ {
4168
+ type: "text",
4169
+ text: { content: title }
4170
+ }
4171
+ ]
4172
+ };
4223
4173
  }
4224
- function buildAttachments(message) {
4225
- if (!message.attachments?.length)
4174
+ function buildRichText(text) {
4175
+ return {
4176
+ rich_text: [
4177
+ {
4178
+ type: "text",
4179
+ text: { content: text }
4180
+ }
4181
+ ]
4182
+ };
4183
+ }
4184
+ function applySelect(properties, property, value) {
4185
+ if (!property || !value)
4226
4186
  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"
4187
+ const next = {
4188
+ select: { name: value }
4189
+ };
4190
+ properties[property] = next;
4191
+ }
4192
+ function applyMultiSelect(properties, property, values) {
4193
+ if (!property || !values || values.length === 0)
4194
+ return;
4195
+ const next = {
4196
+ multi_select: values.map((value) => ({ name: value }))
4197
+ };
4198
+ properties[property] = next;
4199
+ }
4200
+ function applyDate(properties, property, value) {
4201
+ if (!property || !value)
4202
+ return;
4203
+ const next = {
4204
+ date: { start: value.toISOString() }
4205
+ };
4206
+ properties[property] = next;
4207
+ }
4208
+ function applyRichText(properties, property, value) {
4209
+ if (!property || !value)
4210
+ return;
4211
+ properties[property] = buildRichText(value);
4212
+ }
4213
+ function buildParagraphBlocks(text) {
4214
+ const lines = text.split(/\r?\n/).filter((line) => line.trim());
4215
+ return lines.map((line) => ({
4216
+ object: "block",
4217
+ type: "paragraph",
4218
+ paragraph: {
4219
+ rich_text: [
4220
+ {
4221
+ type: "text",
4222
+ text: { content: line }
4223
+ }
4224
+ ]
4225
+ }
4234
4226
  }));
4235
4227
  }
4236
4228
 
@@ -4321,612 +4313,295 @@ class PosthogAnalyticsReader {
4321
4313
  offset: input.offset
4322
4314
  }
4323
4315
  });
4324
- return response.data;
4325
- }
4326
- async getFeatureFlags(input) {
4327
- const projectId = resolveProjectId(input.projectId, this.projectId);
4328
- const response = await this.client.request({
4329
- method: "GET",
4330
- path: `/api/projects/${projectId}/feature_flags/`,
4331
- query: {
4332
- active: input.active,
4333
- limit: input.limit,
4334
- offset: input.offset
4335
- }
4336
- });
4337
- return response.data;
4338
- }
4339
- async getAnnotations(input) {
4340
- const projectId = resolveProjectId(input.projectId, this.projectId);
4341
- const response = await this.client.request({
4342
- method: "GET",
4343
- path: `/api/projects/${projectId}/annotations/`,
4344
- query: {
4345
- 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 } : {}
4477
- }
4478
- });
4479
- }
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
4316
+ return response.data;
4317
+ }
4318
+ async getFeatureFlags(input) {
4319
+ const projectId = resolveProjectId(input.projectId, this.projectId);
4320
+ const response = await this.client.request({
4321
+ method: "GET",
4322
+ path: `/api/projects/${projectId}/feature_flags/`,
4323
+ query: {
4324
+ active: input.active,
4325
+ limit: input.limit,
4326
+ offset: input.offset
4327
+ }
4572
4328
  });
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
- };
4329
+ return response.data;
4586
4330
  }
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
- };
4331
+ async getAnnotations(input) {
4332
+ const projectId = resolveProjectId(input.projectId, this.projectId);
4333
+ const response = await this.client.request({
4334
+ method: "GET",
4335
+ path: `/api/projects/${projectId}/annotations/`,
4336
+ query: {
4337
+ limit: input.limit,
4338
+ offset: input.offset,
4339
+ ...buildAnnotationDateQuery(input.dateRange)
4340
+ }
4341
+ });
4342
+ return response.data;
4595
4343
  }
4596
4344
  }
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";
4345
+ function resolveProjectId(inputProjectId, defaultProjectId) {
4346
+ const projectId = inputProjectId ?? defaultProjectId;
4347
+ if (!projectId) {
4348
+ throw new Error("PostHog projectId is required for API reads.");
4617
4349
  }
4350
+ return projectId;
4351
+ }
4352
+ function resolveSingleEvent(events) {
4353
+ if (!events || events.length !== 1)
4354
+ return;
4355
+ return events[0];
4356
+ }
4357
+ function resolveEventList(events) {
4358
+ if (!events || events.length <= 1)
4359
+ return;
4360
+ return events.join(",");
4361
+ }
4362
+ function buildEventDateQuery(range) {
4363
+ const after = formatDate(range?.from);
4364
+ const before = formatDate(range?.to);
4365
+ return {
4366
+ after,
4367
+ before,
4368
+ timezone: range?.timezone
4369
+ };
4370
+ }
4371
+ function buildAnnotationDateQuery(range) {
4372
+ const dateFrom = formatDate(range?.from);
4373
+ const dateTo = formatDate(range?.to);
4374
+ return {
4375
+ date_from: dateFrom,
4376
+ date_to: dateTo,
4377
+ timezone: range?.timezone
4378
+ };
4379
+ }
4380
+ function formatDate(value) {
4381
+ if (!value)
4382
+ return;
4383
+ return typeof value === "string" ? value : value.toISOString();
4618
4384
  }
4619
4385
 
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
- };
4386
+ // src/impls/posthog-utils.ts
4387
+ function normalizeHost(host) {
4388
+ return host.replace(/\/$/, "");
4389
+ }
4390
+ function buildUrl(host, path, query) {
4391
+ if (/^https?:\/\//.test(path)) {
4392
+ return appendQuery(path, query);
4662
4393
  }
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
- };
4394
+ const normalizedPath = path.replace(/^\/+/, "");
4395
+ return appendQuery(`${host}/${normalizedPath}`, query);
4396
+ }
4397
+ function appendQuery(url, query) {
4398
+ if (!query)
4399
+ return url;
4400
+ const params = new URLSearchParams;
4401
+ Object.entries(query).forEach(([key, value]) => {
4402
+ if (value === undefined)
4403
+ return;
4404
+ params.set(key, String(value));
4405
+ });
4406
+ const suffix = params.toString();
4407
+ return suffix ? `${url}?${suffix}` : url;
4408
+ }
4409
+ function parseJson(value) {
4410
+ if (!value)
4411
+ return {};
4412
+ try {
4413
+ return JSON.parse(value);
4414
+ } catch {
4415
+ return value;
4694
4416
  }
4695
4417
  }
4696
4418
 
4697
- // src/impls/messaging-github.ts
4698
- class GithubMessagingProvider {
4699
- token;
4700
- defaultOwner;
4701
- defaultRepo;
4702
- apiBaseUrl;
4419
+ // src/impls/posthog.ts
4420
+ import { PostHog } from "posthog-node";
4421
+ var DEFAULT_POSTHOG_HOST = "https://app.posthog.com";
4422
+
4423
+ class PosthogAnalyticsProvider {
4424
+ host;
4425
+ projectId;
4426
+ projectApiKey;
4427
+ personalApiKey;
4428
+ mcpUrl;
4429
+ fetchFn;
4430
+ client;
4431
+ reader;
4703
4432
  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";
4433
+ this.host = normalizeHost(options.host ?? DEFAULT_POSTHOG_HOST);
4434
+ this.projectId = options.projectId;
4435
+ this.projectApiKey = options.projectApiKey;
4436
+ this.personalApiKey = options.personalApiKey;
4437
+ this.mcpUrl = options.mcpUrl;
4438
+ this.fetchFn = options.fetch ?? fetch;
4439
+ this.client = options.client ?? (options.projectApiKey ? new PostHog(options.projectApiKey, {
4440
+ host: this.host,
4441
+ requestTimeout: options.requestTimeoutMs ?? 1e4
4442
+ }) : undefined);
4443
+ this.reader = new PosthogAnalyticsReader({
4444
+ projectId: this.projectId,
4445
+ client: { request: this.request.bind(this) }
4446
+ });
4708
4447
  }
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 })
4448
+ async capture(event) {
4449
+ if (!this.client) {
4450
+ throw new Error("PostHog projectApiKey is required for capture.");
4451
+ }
4452
+ await this.client.capture({
4453
+ distinctId: event.distinctId,
4454
+ event: event.event,
4455
+ properties: event.properties,
4456
+ timestamp: event.timestamp,
4457
+ groups: event.groups
4719
4458
  });
4720
- const body = await response.json();
4721
- if (!response.ok || !body.id) {
4722
- throw new Error(`GitHub sendMessage failed: ${body.message ?? `HTTP_${response.status}`}`);
4459
+ }
4460
+ async identify(input) {
4461
+ if (!this.client) {
4462
+ throw new Error("PostHog projectApiKey is required for identify.");
4723
4463
  }
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)
4464
+ await this.client.identify({
4465
+ distinctId: input.distinctId,
4466
+ properties: {
4467
+ ...input.properties ? { $set: input.properties } : {},
4468
+ ...input.setOnce ? { $set_once: input.setOnce } : {}
4734
4469
  }
4735
- };
4470
+ });
4736
4471
  }
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.");
4472
+ async queryHogQL(input) {
4473
+ return this.reader.queryHogQL(input);
4474
+ }
4475
+ async getEvents(input) {
4476
+ return this.reader.getEvents(input);
4477
+ }
4478
+ async getPersons(input) {
4479
+ return this.reader.getPersons(input);
4480
+ }
4481
+ async getInsights(input) {
4482
+ return this.reader.getInsights(input);
4483
+ }
4484
+ async getInsightResult(input) {
4485
+ return this.reader.getInsightResult(input);
4486
+ }
4487
+ async getCohorts(input) {
4488
+ return this.reader.getCohorts(input);
4489
+ }
4490
+ async getFeatureFlags(input) {
4491
+ return this.reader.getFeatureFlags(input);
4492
+ }
4493
+ async getAnnotations(input) {
4494
+ return this.reader.getAnnotations(input);
4495
+ }
4496
+ async request(request) {
4497
+ if (!this.personalApiKey) {
4498
+ throw new Error("PostHog personalApiKey is required for API requests.");
4742
4499
  }
4743
- const response = await fetch(`${this.apiBaseUrl}/repos/${owner}/${repo}/issues/comments/${messageId}`, {
4744
- method: "PATCH",
4500
+ const url = buildUrl(this.host, request.path, request.query);
4501
+ const response = await this.fetchFn(url, {
4502
+ method: request.method,
4745
4503
  headers: {
4746
- authorization: `Bearer ${this.token}`,
4747
- accept: "application/vnd.github+json",
4748
- "content-type": "application/json"
4504
+ Authorization: `Bearer ${this.personalApiKey}`,
4505
+ "Content-Type": "application/json",
4506
+ ...request.headers ?? {}
4749
4507
  },
4750
- body: JSON.stringify({ body: input.text })
4508
+ body: request.body ? JSON.stringify(request.body) : undefined
4751
4509
  });
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
- }
4510
+ const text = await response.text();
4511
+ const data = parseJson(text);
4776
4512
  return {
4777
- owner,
4778
- repo,
4779
- issueNumber
4513
+ status: response.status,
4514
+ data,
4515
+ headers: Object.fromEntries(response.headers.entries())
4780
4516
  };
4781
4517
  }
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.");
4518
+ async callMcpTool(call) {
4519
+ if (!this.mcpUrl) {
4520
+ throw new Error("PostHog MCP URL is not configured.");
4818
4521
  }
4819
- const response = await fetch(`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`, {
4522
+ const response = await this.fetchFn(this.mcpUrl, {
4820
4523
  method: "POST",
4821
4524
  headers: {
4822
- authorization: `Bearer ${this.accessToken}`,
4823
- "content-type": "application/json"
4525
+ "Content-Type": "application/json"
4824
4526
  },
4825
4527
  body: JSON.stringify({
4826
- messaging_product: "whatsapp",
4827
- to,
4828
- type: "text",
4829
- text: {
4830
- body: input.text,
4831
- preview_url: false
4528
+ jsonrpc: "2.0",
4529
+ id: 1,
4530
+ method: "tools/call",
4531
+ params: {
4532
+ name: call.name,
4533
+ arguments: call.arguments ?? {}
4832
4534
  }
4833
4535
  })
4834
4536
  });
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})` : ""}`);
4537
+ if (!response.ok) {
4538
+ const body = await response.text();
4539
+ throw new Error(`PostHog MCP error (${response.status}): ${body}`);
4840
4540
  }
4841
- return {
4842
- id: messageId,
4843
- providerMessageId: messageId,
4844
- status: "sent",
4845
- sentAt: new Date,
4846
- metadata: {
4847
- phoneNumberId: this.phoneNumberId
4848
- }
4849
- };
4541
+ const result = await response.json();
4542
+ if (result.error) {
4543
+ throw new Error(result.error.message ?? "PostHog MCP error");
4544
+ }
4545
+ return result.result ?? null;
4850
4546
  }
4851
4547
  }
4852
4548
 
4853
- // src/impls/messaging-whatsapp-twilio.ts
4854
- import { Buffer as Buffer4 } from "buffer";
4549
+ // src/impls/postmark-email.ts
4550
+ import { ServerClient } from "postmark";
4855
4551
 
4856
- class TwilioWhatsappMessagingProvider {
4857
- accountSid;
4858
- authToken;
4859
- fromNumber;
4552
+ class PostmarkEmailProvider {
4553
+ client;
4554
+ defaultFromEmail;
4555
+ messageStream;
4860
4556
  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()
4557
+ this.client = options.client ?? new ServerClient(options.serverToken, {
4558
+ useHttps: true
4886
4559
  });
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
- }
4891
- 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
- }
4560
+ this.defaultFromEmail = options.defaultFromEmail;
4561
+ this.messageStream = options.messageStream;
4562
+ }
4563
+ async sendEmail(message) {
4564
+ const request = {
4565
+ From: formatAddress(message.from) ?? this.defaultFromEmail,
4566
+ To: message.to.map((addr) => formatAddress(addr)).join(", "),
4567
+ Cc: message.cc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
4568
+ Bcc: message.bcc?.map((addr) => formatAddress(addr)).join(", ") || undefined,
4569
+ ReplyTo: message.replyTo ? formatAddress(message.replyTo) : undefined,
4570
+ Subject: message.subject,
4571
+ TextBody: message.textBody,
4572
+ HtmlBody: message.htmlBody,
4573
+ Headers: message.headers ? Object.entries(message.headers).map(([name, value]) => ({
4574
+ Name: name,
4575
+ Value: value
4576
+ })) : undefined,
4577
+ MessageStream: this.messageStream,
4578
+ Attachments: buildAttachments(message)
4579
+ };
4580
+ const response = await this.client.sendEmail(request);
4581
+ return {
4582
+ id: response.MessageID,
4583
+ providerMessageId: response.MessageID,
4584
+ queuedAt: new Date(response.SubmittedAt ?? new Date().toISOString())
4902
4585
  };
4903
4586
  }
4904
4587
  }
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";
4588
+ function formatAddress(address) {
4589
+ if (address.name) {
4590
+ return `"${address.name}" <${address.email}>`;
4929
4591
  }
4592
+ return address.email;
4593
+ }
4594
+ function buildAttachments(message) {
4595
+ if (!message.attachments?.length)
4596
+ return;
4597
+ return message.attachments.filter((attachment) => attachment.data).map((attachment) => ({
4598
+ Name: attachment.filename,
4599
+ Content: Buffer.from(attachment.data ?? new Uint8Array).toString("base64"),
4600
+ ContentType: attachment.contentType,
4601
+ ContentID: null,
4602
+ ContentLength: attachment.sizeBytes,
4603
+ Disposition: "attachment"
4604
+ }));
4930
4605
  }
4931
4606
 
4932
4607
  // src/impls/powens-client.ts
@@ -5338,380 +5013,642 @@ class PowensOpenBankingProvider {
5338
5013
  return "degraded";
5339
5014
  }
5340
5015
  }
5341
- handleError(operation, error) {
5342
- if (error instanceof PowensClientError) {
5343
- this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed`, {
5344
- status: error.status,
5345
- code: error.code,
5346
- requestId: error.requestId,
5347
- message: error.message
5348
- });
5349
- throw error;
5350
- }
5351
- this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
5352
- throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
5016
+ handleError(operation, error) {
5017
+ if (error instanceof PowensClientError) {
5018
+ this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed`, {
5019
+ status: error.status,
5020
+ code: error.code,
5021
+ requestId: error.requestId,
5022
+ message: error.message
5023
+ });
5024
+ throw error;
5025
+ }
5026
+ this.logger?.error?.(`[PowensOpenBankingProvider] ${operation} failed with unexpected error`, error);
5027
+ throw error instanceof Error ? error : new Error(`Powens operation "${operation}" failed`);
5028
+ }
5029
+ }
5030
+
5031
+ // src/impls/qdrant-vector.ts
5032
+ import { QdrantClient } from "@qdrant/js-client-rest";
5033
+
5034
+ class QdrantVectorProvider {
5035
+ client;
5036
+ createCollectionIfMissing;
5037
+ distance;
5038
+ constructor(options) {
5039
+ this.client = options.client ?? new QdrantClient({
5040
+ url: options.url,
5041
+ apiKey: options.apiKey,
5042
+ ...options.clientParams
5043
+ });
5044
+ this.createCollectionIfMissing = options.createCollectionIfMissing ?? true;
5045
+ this.distance = options.distance ?? "Cosine";
5046
+ }
5047
+ async upsert(request) {
5048
+ if (request.documents.length === 0)
5049
+ return;
5050
+ const firstDocument = request.documents[0];
5051
+ if (!firstDocument)
5052
+ return;
5053
+ const vectorSize = firstDocument.vector.length;
5054
+ if (this.createCollectionIfMissing) {
5055
+ await this.ensureCollection(request.collection, vectorSize);
5056
+ }
5057
+ const points = request.documents.map((document) => ({
5058
+ id: document.id,
5059
+ vector: document.vector,
5060
+ payload: {
5061
+ ...document.payload,
5062
+ ...document.namespace ? { namespace: document.namespace } : {},
5063
+ ...document.expiresAt ? { expiresAt: document.expiresAt.toISOString() } : {}
5064
+ }
5065
+ }));
5066
+ await this.client.upsert(request.collection, {
5067
+ wait: true,
5068
+ points
5069
+ });
5070
+ }
5071
+ async search(query) {
5072
+ const results = await this.client.search(query.collection, {
5073
+ vector: query.vector,
5074
+ limit: query.topK,
5075
+ filter: query.filter,
5076
+ score_threshold: query.scoreThreshold,
5077
+ with_payload: true,
5078
+ with_vector: false
5079
+ });
5080
+ return results.map((item) => ({
5081
+ id: String(item.id),
5082
+ score: item.score,
5083
+ payload: item.payload ?? undefined,
5084
+ namespace: typeof item.payload === "object" && item.payload !== null ? item.payload.namespace : undefined
5085
+ }));
5086
+ }
5087
+ async delete(request) {
5088
+ await this.client.delete(request.collection, {
5089
+ wait: true,
5090
+ points: request.ids
5091
+ });
5092
+ }
5093
+ async ensureCollection(collectionName, vectorSize) {
5094
+ try {
5095
+ await this.client.getCollection(collectionName);
5096
+ } catch (_error) {
5097
+ await this.client.createCollection(collectionName, {
5098
+ vectors: {
5099
+ size: vectorSize,
5100
+ distance: this.distance
5101
+ }
5102
+ });
5103
+ }
5104
+ }
5105
+ }
5106
+
5107
+ // src/impls/stripe-payments.ts
5108
+ import Stripe from "stripe";
5109
+ var API_VERSION = "2026-02-25.clover";
5110
+
5111
+ class StripePaymentsProvider {
5112
+ stripe;
5113
+ constructor(options) {
5114
+ this.stripe = options.stripe ?? new Stripe(options.apiKey, {
5115
+ apiVersion: API_VERSION
5116
+ });
5117
+ }
5118
+ async createCustomer(input) {
5119
+ const customer = await this.stripe.customers.create({
5120
+ email: input.email,
5121
+ name: input.name,
5122
+ description: input.description,
5123
+ metadata: input.metadata
5124
+ });
5125
+ return this.toCustomer(customer);
5126
+ }
5127
+ async getCustomer(customerId) {
5128
+ const customer = await this.stripe.customers.retrieve(customerId);
5129
+ if (customer.deleted)
5130
+ return null;
5131
+ return this.toCustomer(customer);
5132
+ }
5133
+ async createPaymentIntent(input) {
5134
+ const intent = await this.stripe.paymentIntents.create({
5135
+ amount: input.amount.amount,
5136
+ currency: input.amount.currency,
5137
+ customer: input.customerId,
5138
+ description: input.description,
5139
+ capture_method: input.captureMethod ?? "automatic",
5140
+ confirmation_method: input.confirmationMethod ?? "automatic",
5141
+ automatic_payment_methods: { enabled: true },
5142
+ metadata: input.metadata,
5143
+ return_url: input.returnUrl,
5144
+ statement_descriptor: input.statementDescriptor
5145
+ });
5146
+ return this.toPaymentIntent(intent);
5147
+ }
5148
+ async capturePayment(paymentIntentId, input) {
5149
+ const intent = await this.stripe.paymentIntents.capture(paymentIntentId, input?.amount ? { amount_to_capture: input.amount.amount } : undefined);
5150
+ return this.toPaymentIntent(intent);
5151
+ }
5152
+ async cancelPaymentIntent(paymentIntentId) {
5153
+ const intent = await this.stripe.paymentIntents.cancel(paymentIntentId);
5154
+ return this.toPaymentIntent(intent);
5155
+ }
5156
+ async refundPayment(input) {
5157
+ const refund = await this.stripe.refunds.create({
5158
+ payment_intent: input.paymentIntentId,
5159
+ amount: input.amount?.amount,
5160
+ reason: mapRefundReason(input.reason),
5161
+ metadata: input.metadata
5162
+ });
5163
+ const paymentIntentId = typeof refund.payment_intent === "string" ? refund.payment_intent : refund.payment_intent?.id ?? "";
5164
+ return {
5165
+ id: refund.id,
5166
+ paymentIntentId,
5167
+ amount: {
5168
+ amount: refund.amount ?? 0,
5169
+ currency: refund.currency?.toUpperCase() ?? "USD"
5170
+ },
5171
+ status: mapRefundStatus(refund.status),
5172
+ reason: refund.reason ?? undefined,
5173
+ metadata: this.toMetadata(refund.metadata),
5174
+ createdAt: refund.created ? new Date(refund.created * 1000) : undefined
5175
+ };
5176
+ }
5177
+ async listInvoices(query) {
5178
+ const requestedStatus = query?.status?.[0];
5179
+ const stripeStatus = requestedStatus && requestedStatus !== "deleted" ? requestedStatus : undefined;
5180
+ const response = await this.stripe.invoices.list({
5181
+ customer: query?.customerId,
5182
+ status: stripeStatus,
5183
+ limit: query?.limit,
5184
+ starting_after: query?.startingAfter
5185
+ });
5186
+ return response.data.map((invoice) => this.toInvoice(invoice));
5187
+ }
5188
+ async listTransactions(query) {
5189
+ const response = await this.stripe.charges.list({
5190
+ customer: query?.customerId,
5191
+ payment_intent: query?.paymentIntentId,
5192
+ limit: query?.limit,
5193
+ starting_after: query?.startingAfter
5194
+ });
5195
+ return response.data.map((charge) => ({
5196
+ id: charge.id,
5197
+ paymentIntentId: typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id,
5198
+ amount: {
5199
+ amount: charge.amount,
5200
+ currency: charge.currency?.toUpperCase() ?? "USD"
5201
+ },
5202
+ type: "capture",
5203
+ status: mapChargeStatus(charge.status),
5204
+ description: charge.description ?? undefined,
5205
+ createdAt: new Date(charge.created * 1000),
5206
+ metadata: this.mergeMetadata(this.toMetadata(charge.metadata), {
5207
+ balanceTransaction: typeof charge.balance_transaction === "string" ? charge.balance_transaction : undefined
5208
+ })
5209
+ }));
5210
+ }
5211
+ toCustomer(customer) {
5212
+ const metadata = this.toMetadata(customer.metadata);
5213
+ const updatedAtValue = metadata?.updatedAt;
5214
+ return {
5215
+ id: customer.id,
5216
+ email: customer.email ?? undefined,
5217
+ name: customer.name ?? undefined,
5218
+ metadata,
5219
+ createdAt: customer.created ? new Date(customer.created * 1000) : undefined,
5220
+ updatedAt: updatedAtValue ? new Date(updatedAtValue) : undefined
5221
+ };
5222
+ }
5223
+ toPaymentIntent(intent) {
5224
+ const metadata = this.toMetadata(intent.metadata);
5225
+ return {
5226
+ id: intent.id,
5227
+ amount: this.toMoney(intent.amount_received ?? intent.amount ?? 0, intent.currency),
5228
+ status: mapPaymentIntentStatus(intent.status),
5229
+ customerId: typeof intent.customer === "string" ? intent.customer : intent.customer?.id,
5230
+ description: intent.description ?? undefined,
5231
+ clientSecret: intent.client_secret ?? undefined,
5232
+ metadata,
5233
+ createdAt: new Date(intent.created * 1000),
5234
+ updatedAt: intent.canceled_at != null ? new Date(intent.canceled_at * 1000) : new Date(intent.created * 1000)
5235
+ };
5353
5236
  }
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
5237
+ toInvoice(invoice) {
5238
+ const metadata = this.toMetadata(invoice.metadata);
5239
+ return {
5240
+ id: invoice.id,
5241
+ number: invoice.number ?? undefined,
5242
+ status: invoice.status ?? "draft",
5243
+ amountDue: this.toMoney(invoice.amount_due ?? 0, invoice.currency),
5244
+ amountPaid: this.toMoney(invoice.amount_paid ?? 0, invoice.currency),
5245
+ customerId: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
5246
+ dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : undefined,
5247
+ hostedInvoiceUrl: invoice.hosted_invoice_url ?? undefined,
5248
+ metadata,
5249
+ createdAt: invoice.created ? new Date(invoice.created * 1000) : undefined,
5250
+ updatedAt: invoice.status_transitions?.finalized_at ? new Date(invoice.status_transitions.finalized_at * 1000) : undefined
5378
5251
  };
5379
5252
  }
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;
5253
+ toMoney(amount, currency) {
5398
5254
  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
5255
+ amount,
5256
+ currency: currency?.toUpperCase() ?? "USD"
5408
5257
  };
5409
5258
  }
5410
- async createWorkItems(items) {
5411
- const created = [];
5412
- for (const item of items) {
5413
- created.push(await this.createWorkItem(item));
5259
+ toMetadata(metadata) {
5260
+ if (!metadata)
5261
+ return;
5262
+ const entries = Object.entries(metadata).filter((entry) => typeof entry[1] === "string");
5263
+ if (entries.length === 0)
5264
+ return;
5265
+ return Object.fromEntries(entries);
5266
+ }
5267
+ mergeMetadata(base, extras) {
5268
+ const filteredExtras = Object.entries(extras).filter((entry) => typeof entry[1] === "string");
5269
+ if (!base && filteredExtras.length === 0) {
5270
+ return;
5414
5271
  }
5415
- return created;
5272
+ return {
5273
+ ...base ?? {},
5274
+ ...Object.fromEntries(filteredExtras)
5275
+ };
5416
5276
  }
5417
5277
  }
5418
- function mapPriority(priority) {
5419
- if (!priority)
5278
+ function mapRefundReason(reason) {
5279
+ if (!reason)
5420
5280
  return;
5421
- return PRIORITY_MAP[priority] ?? undefined;
5281
+ const allowed = [
5282
+ "duplicate",
5283
+ "fraudulent",
5284
+ "requested_by_customer"
5285
+ ];
5286
+ return allowed.includes(reason) ? reason : undefined;
5422
5287
  }
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
- });
5288
+ function mapPaymentIntentStatus(status) {
5289
+ switch (status) {
5290
+ case "requires_payment_method":
5291
+ return "requires_payment_method";
5292
+ case "requires_confirmation":
5293
+ return "requires_confirmation";
5294
+ case "requires_action":
5295
+ case "requires_capture":
5296
+ return "requires_action";
5297
+ case "processing":
5298
+ return "processing";
5299
+ case "succeeded":
5300
+ return "succeeded";
5301
+ case "canceled":
5302
+ return "canceled";
5303
+ default:
5304
+ return "requires_payment_method";
5305
+ }
5306
+ }
5307
+ function mapRefundStatus(status) {
5308
+ switch (status) {
5309
+ case "pending":
5310
+ case "succeeded":
5311
+ case "failed":
5312
+ case "canceled":
5313
+ return status;
5314
+ default:
5315
+ return "pending";
5316
+ }
5317
+ }
5318
+ function mapChargeStatus(status) {
5319
+ switch (status) {
5320
+ case "pending":
5321
+ case "processing":
5322
+ return "pending";
5323
+ case "succeeded":
5324
+ return "succeeded";
5325
+ case "failed":
5326
+ case "canceled":
5327
+ return "failed";
5328
+ default:
5329
+ return "pending";
5432
5330
  }
5433
- const merged = [...labelIds];
5434
- return merged.length > 0 ? merged : undefined;
5435
5331
  }
5436
5332
 
5437
- // src/impls/jira.ts
5333
+ // src/impls/supabase-psql.ts
5438
5334
  import { Buffer as Buffer5 } from "buffer";
5335
+ import { sql as drizzleSql } from "drizzle-orm";
5336
+ import { drizzle } from "drizzle-orm/postgres-js";
5337
+ import postgres from "postgres";
5439
5338
 
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.");
5339
+ class SupabasePostgresProvider {
5340
+ client;
5341
+ db;
5342
+ ownsClient;
5343
+ createDrizzle;
5344
+ constructor(options = {}) {
5345
+ this.createDrizzle = options.createDrizzle ?? ((client) => drizzle(client));
5346
+ if (options.db) {
5347
+ if (!options.client) {
5348
+ throw new Error("SupabasePostgresProvider requires a postgres client when db is provided.");
5349
+ }
5350
+ this.client = options.client;
5351
+ this.db = options.db;
5352
+ this.ownsClient = false;
5353
+ return;
5460
5354
  }
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
5355
+ if (options.client) {
5356
+ this.client = options.client;
5357
+ this.ownsClient = false;
5358
+ } else {
5359
+ if (!options.connectionString) {
5360
+ throw new Error("SupabasePostgresProvider requires either a connectionString or a client.");
5475
5361
  }
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}`);
5362
+ this.client = postgres(options.connectionString, {
5363
+ max: options.maxConnections,
5364
+ prepare: false,
5365
+ ssl: resolveSslMode(options.sslMode)
5366
+ });
5367
+ this.ownsClient = true;
5489
5368
  }
5490
- const data = await response.json();
5369
+ this.db = this.createDrizzle(this.client);
5370
+ }
5371
+ async query(statement, params = []) {
5372
+ const query = buildParameterizedSql(statement, params);
5373
+ const result = await this.db.execute(query);
5374
+ const rows = asRows(result);
5491
5375
  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
5376
+ rows,
5377
+ rowCount: rows.length
5501
5378
  };
5502
5379
  }
5503
- async createWorkItems(items) {
5504
- const created = [];
5505
- for (const item of items) {
5506
- created.push(await this.createWorkItem(item));
5380
+ async execute(statement, params = []) {
5381
+ const query = buildParameterizedSql(statement, params);
5382
+ await this.db.execute(query);
5383
+ }
5384
+ async transaction(run) {
5385
+ const transactionResult = this.client.begin(async (transactionClient) => {
5386
+ const transactionalProvider = new SupabasePostgresProvider({
5387
+ client: transactionClient,
5388
+ db: this.createDrizzle(transactionClient),
5389
+ createDrizzle: this.createDrizzle
5390
+ });
5391
+ return run(transactionalProvider);
5392
+ });
5393
+ return transactionResult;
5394
+ }
5395
+ async close() {
5396
+ if (this.ownsClient) {
5397
+ await this.client.end({ timeout: 5 });
5507
5398
  }
5508
- return created;
5509
5399
  }
5510
5400
  }
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}`;
5401
+ function buildParameterizedSql(statement, params) {
5402
+ const segments = [];
5403
+ const pattern = /\$(\d+)/g;
5404
+ let cursor = 0;
5405
+ for (const match of statement.matchAll(pattern)) {
5406
+ const token = match[0];
5407
+ const indexPart = match[1];
5408
+ const start = match.index;
5409
+ if (indexPart == null || start == null)
5410
+ continue;
5411
+ const parameterIndex = Number(indexPart) - 1;
5412
+ if (!Number.isInteger(parameterIndex) || parameterIndex < 0 || parameterIndex >= params.length) {
5413
+ throw new Error(`SQL placeholder ${token} is out of bounds for ${params.length} parameter(s).`);
5414
+ }
5415
+ const staticSegment = statement.slice(cursor, start);
5416
+ if (staticSegment.length > 0) {
5417
+ segments.push(drizzleSql.raw(staticSegment));
5418
+ }
5419
+ const parameterValue = params[parameterIndex];
5420
+ if (parameterValue === undefined) {
5421
+ throw new Error(`SQL placeholder ${token} is missing a parameter value.`);
5422
+ }
5423
+ const normalizedValue = normalizeParam(parameterValue);
5424
+ segments.push(drizzleSql`${normalizedValue}`);
5425
+ cursor = start + token.length;
5426
+ }
5427
+ const tailSegment = statement.slice(cursor);
5428
+ if (tailSegment.length > 0) {
5429
+ segments.push(drizzleSql.raw(tailSegment));
5430
+ }
5431
+ if (segments.length === 0) {
5432
+ return drizzleSql.raw("");
5433
+ }
5434
+ return drizzleSql.join(segments);
5517
5435
  }
5518
- function resolveIssueType(type, defaults) {
5519
- if (type && defaults.issueTypeMap?.[type]) {
5520
- return defaults.issueTypeMap[type] ?? defaults.issueType ?? "Task";
5436
+ function normalizeParam(value) {
5437
+ if (typeof value === "bigint") {
5438
+ return value.toString();
5521
5439
  }
5522
- return defaults.issueType ?? "Task";
5440
+ if (value instanceof Uint8Array) {
5441
+ return Buffer5.from(value);
5442
+ }
5443
+ if (isPlainObject(value)) {
5444
+ return JSON.stringify(value);
5445
+ }
5446
+ return value;
5523
5447
  }
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;
5448
+ function asRows(result) {
5449
+ if (!Array.isArray(result)) {
5450
+ return [];
5537
5451
  }
5452
+ return result;
5538
5453
  }
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;
5454
+ function isPlainObject(value) {
5455
+ if (value == null || typeof value !== "object") {
5456
+ return false;
5457
+ }
5458
+ if (Array.isArray(value)) {
5459
+ return false;
5460
+ }
5461
+ if (value instanceof Date) {
5462
+ return false;
5463
+ }
5464
+ if (value instanceof Uint8Array) {
5465
+ return false;
5466
+ }
5467
+ return true;
5545
5468
  }
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 };
5469
+ function resolveSslMode(mode) {
5470
+ switch (mode) {
5471
+ case "allow":
5472
+ return false;
5473
+ case "prefer":
5474
+ return "prefer";
5475
+ case "require":
5476
+ default:
5477
+ return "require";
5478
+ }
5557
5479
  }
5558
5480
 
5559
- // src/impls/notion.ts
5560
- import { Client } from "@notionhq/client";
5561
-
5562
- class NotionProjectManagementProvider {
5563
- client;
5564
- defaults;
5481
+ // src/impls/supabase-vector.ts
5482
+ class SupabaseVectorProvider {
5483
+ database;
5484
+ createTableIfMissing;
5485
+ distanceMetric;
5486
+ quotedSchema;
5487
+ qualifiedTable;
5488
+ collectionIndex;
5489
+ namespaceIndex;
5490
+ ensureTablePromise;
5565
5491
  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
- };
5492
+ this.database = options.database ?? new SupabasePostgresProvider({
5493
+ connectionString: options.connectionString,
5494
+ maxConnections: options.maxConnections,
5495
+ sslMode: options.sslMode
5496
+ });
5497
+ this.createTableIfMissing = options.createTableIfMissing ?? true;
5498
+ this.distanceMetric = options.distanceMetric ?? "cosine";
5499
+ const schema = sanitizeIdentifier(options.schema ?? "public", "schema");
5500
+ const table = sanitizeIdentifier(options.table ?? "contractspec_vectors", "table");
5501
+ this.quotedSchema = quoteIdentifier(schema);
5502
+ this.qualifiedTable = `${this.quotedSchema}.${quoteIdentifier(table)}`;
5503
+ this.collectionIndex = quoteIdentifier(`${table}_collection_idx`);
5504
+ this.namespaceIndex = quoteIdentifier(`${table}_namespace_idx`);
5577
5505
  }
5578
- async createWorkItem(input) {
5579
- if (input.type === "summary" && this.defaults.summaryParentPageId) {
5580
- return this.createSummaryPage(input);
5506
+ async upsert(request) {
5507
+ if (request.documents.length === 0) {
5508
+ return;
5581
5509
  }
5582
- const databaseId = this.defaults.databaseId;
5583
- if (!databaseId) {
5584
- throw new Error("Notion databaseId is required to create work items.");
5510
+ if (this.createTableIfMissing) {
5511
+ await this.ensureTable();
5585
5512
  }
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
5513
+ for (const document of request.documents) {
5514
+ await this.database.execute(`INSERT INTO ${this.qualifiedTable}
5515
+ (collection, id, embedding, payload, namespace, expires_at, updated_at)
5516
+ VALUES ($1, $2, $3::vector, $4::jsonb, $5, $6, now())
5517
+ ON CONFLICT (collection, id)
5518
+ DO UPDATE SET
5519
+ embedding = EXCLUDED.embedding,
5520
+ payload = EXCLUDED.payload,
5521
+ namespace = EXCLUDED.namespace,
5522
+ expires_at = EXCLUDED.expires_at,
5523
+ updated_at = now();`, [
5524
+ request.collection,
5525
+ document.id,
5526
+ toVectorLiteral(document.vector),
5527
+ document.payload ? JSON.stringify(document.payload) : null,
5528
+ document.namespace ?? null,
5529
+ document.expiresAt ?? null
5530
+ ]);
5531
+ }
5532
+ }
5533
+ async search(query) {
5534
+ const operator = this.distanceOperator;
5535
+ const results = await this.database.query(`SELECT
5536
+ id,
5537
+ payload,
5538
+ namespace,
5539
+ (embedding ${operator} $3::vector) AS distance
5540
+ FROM ${this.qualifiedTable}
5541
+ WHERE collection = $1
5542
+ AND ($2::text IS NULL OR namespace = $2)
5543
+ AND (expires_at IS NULL OR expires_at > now())
5544
+ AND ($4::jsonb IS NULL OR payload @> $4::jsonb)
5545
+ ORDER BY embedding ${operator} $3::vector
5546
+ LIMIT $5;`, [
5547
+ query.collection,
5548
+ query.namespace ?? null,
5549
+ toVectorLiteral(query.vector),
5550
+ query.filter ? JSON.stringify(query.filter) : null,
5551
+ query.topK
5552
+ ]);
5553
+ const mapped = results.rows.map((row) => {
5554
+ const distance = Number(row.distance);
5555
+ return {
5556
+ id: row.id,
5557
+ score: distanceToScore(distance, this.distanceMetric),
5558
+ payload: isRecord(row.payload) ? row.payload : undefined,
5559
+ namespace: row.namespace ?? undefined
5560
+ };
5598
5561
  });
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
- };
5562
+ const scoreThreshold = query.scoreThreshold;
5563
+ if (scoreThreshold == null) {
5564
+ return mapped;
5565
+ }
5566
+ return mapped.filter((result) => result.score >= scoreThreshold);
5610
5567
  }
5611
- async createWorkItems(items) {
5612
- const created = [];
5613
- for (const item of items) {
5614
- created.push(await this.createWorkItem(item));
5568
+ async delete(request) {
5569
+ if (request.ids.length === 0) {
5570
+ return;
5615
5571
  }
5616
- return created;
5572
+ const params = [
5573
+ request.collection,
5574
+ request.ids,
5575
+ request.namespace ?? null
5576
+ ];
5577
+ await this.database.execute(`DELETE FROM ${this.qualifiedTable}
5578
+ WHERE collection = $1
5579
+ AND id = ANY($2::text[])
5580
+ AND ($3::text IS NULL OR namespace = $3);`, params);
5617
5581
  }
5618
- async createSummaryPage(input) {
5619
- const parentId = this.defaults.summaryParentPageId;
5620
- if (!parentId) {
5621
- throw new Error("Notion summaryParentPageId is required for summaries.");
5582
+ async ensureTable() {
5583
+ if (!this.ensureTablePromise) {
5584
+ this.ensureTablePromise = this.createTable();
5622
5585
  }
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
- }
5586
+ await this.ensureTablePromise;
5587
+ }
5588
+ async createTable() {
5589
+ await this.database.execute("CREATE EXTENSION IF NOT EXISTS vector;");
5590
+ await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.quotedSchema};`);
5591
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.qualifiedTable} (
5592
+ collection text NOT NULL,
5593
+ id text NOT NULL,
5594
+ embedding vector NOT NULL,
5595
+ payload jsonb,
5596
+ namespace text,
5597
+ expires_at timestamptz,
5598
+ created_at timestamptz NOT NULL DEFAULT now(),
5599
+ updated_at timestamptz NOT NULL DEFAULT now(),
5600
+ PRIMARY KEY (collection, id)
5601
+ );`);
5602
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.collectionIndex}
5603
+ ON ${this.qualifiedTable} (collection);`);
5604
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS ${this.namespaceIndex}
5605
+ ON ${this.qualifiedTable} (namespace);`);
5606
+ }
5607
+ get distanceOperator() {
5608
+ switch (this.distanceMetric) {
5609
+ case "l2":
5610
+ return "<->";
5611
+ case "inner_product":
5612
+ return "<#>";
5613
+ case "cosine":
5614
+ default:
5615
+ return "<=>";
5638
5616
  }
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
5617
  }
5651
5618
  }
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;
5619
+ function sanitizeIdentifier(value, label) {
5620
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
5621
+ throw new Error(`SupabaseVectorProvider ${label} "${value}" is invalid.`);
5622
+ }
5623
+ return value;
5679
5624
  }
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;
5625
+ function quoteIdentifier(value) {
5626
+ return `"${value.replaceAll('"', '""')}"`;
5687
5627
  }
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;
5628
+ function toVectorLiteral(vector) {
5629
+ if (vector.length === 0) {
5630
+ throw new Error("Supabase vectors must contain at least one dimension.");
5631
+ }
5632
+ for (const value of vector) {
5633
+ if (!Number.isFinite(value)) {
5634
+ throw new Error(`Supabase vectors must be finite numbers. Found "${value}".`);
5635
+ }
5636
+ }
5637
+ return `[${vector.join(",")}]`;
5695
5638
  }
5696
- function applyRichText(properties, property, value) {
5697
- if (!property || !value)
5698
- return;
5699
- properties[property] = buildRichText(value);
5639
+ function isRecord(value) {
5640
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5700
5641
  }
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
- }));
5642
+ function distanceToScore(distance, metric) {
5643
+ switch (metric) {
5644
+ case "inner_product":
5645
+ return -distance;
5646
+ case "l2":
5647
+ return 1 / (1 + distance);
5648
+ case "cosine":
5649
+ default:
5650
+ return 1 - distance;
5651
+ }
5715
5652
  }
5716
5653
 
5717
5654
  // src/impls/tldv-meeting-recorder.ts
@@ -5857,11 +5794,74 @@ async function safeReadError4(response) {
5857
5794
  }
5858
5795
  }
5859
5796
 
5797
+ // src/impls/twilio-sms.ts
5798
+ import Twilio from "twilio";
5799
+
5800
+ class TwilioSmsProvider {
5801
+ client;
5802
+ fromNumber;
5803
+ constructor(options) {
5804
+ this.client = options.client ?? Twilio(options.accountSid, options.authToken);
5805
+ this.fromNumber = options.fromNumber;
5806
+ }
5807
+ async sendSms(input) {
5808
+ const message = await this.client.messages.create({
5809
+ to: input.to,
5810
+ from: input.from ?? this.fromNumber,
5811
+ body: input.body
5812
+ });
5813
+ return {
5814
+ id: message.sid,
5815
+ to: message.to ?? input.to,
5816
+ from: message.from ?? input.from ?? this.fromNumber ?? "",
5817
+ body: message.body ?? input.body,
5818
+ status: mapStatus(message.status),
5819
+ sentAt: message.dateCreated ? new Date(message.dateCreated) : undefined,
5820
+ deliveredAt: message.status === "delivered" && message.dateUpdated ? new Date(message.dateUpdated) : undefined,
5821
+ price: message.price ? Number(message.price) : undefined,
5822
+ priceCurrency: message.priceUnit ?? undefined,
5823
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
5824
+ errorMessage: message.errorMessage ?? undefined
5825
+ };
5826
+ }
5827
+ async getDeliveryStatus(messageId) {
5828
+ const message = await this.client.messages(messageId).fetch();
5829
+ return {
5830
+ status: mapStatus(message.status),
5831
+ errorCode: message.errorCode ? String(message.errorCode) : undefined,
5832
+ errorMessage: message.errorMessage ?? undefined,
5833
+ updatedAt: message.dateUpdated ? new Date(message.dateUpdated) : new Date
5834
+ };
5835
+ }
5836
+ }
5837
+ function mapStatus(status) {
5838
+ switch (status) {
5839
+ case "queued":
5840
+ case "accepted":
5841
+ case "scheduled":
5842
+ return "queued";
5843
+ case "sending":
5844
+ case "processing":
5845
+ return "sending";
5846
+ case "sent":
5847
+ return "sent";
5848
+ case "delivered":
5849
+ return "delivered";
5850
+ case "undelivered":
5851
+ return "undelivered";
5852
+ case "failed":
5853
+ case "canceled":
5854
+ return "failed";
5855
+ default:
5856
+ return "queued";
5857
+ }
5858
+ }
5859
+
5860
5860
  // src/impls/provider-factory.ts
5861
5861
  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
5862
  import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
5863
+ import { buildAuthHeaders } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
5864
+ import { resolveIntegrationRequestContext } from "@contractspec/lib.contracts-integrations/integrations/runtime";
5865
5865
  var SECRET_CACHE = new Map;
5866
5866
 
5867
5867
  class IntegrationProviderFactory {