From 67b1c4dbd4b4608367eafeffaaac56fa783213c5 Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 27 Oct 2025 14:55:06 -0300 Subject: [PATCH 1/5] Added endpoint for sending SMS (Depends of Catalyst change) --- src/io/sms.ts | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/types.ts | 48 +++++++++++++++++++++++++++---------------- 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/io/sms.ts b/src/io/sms.ts index 2b2d999e..a4842fd2 100644 --- a/src/io/sms.ts +++ b/src/io/sms.ts @@ -54,6 +54,60 @@ export class Sms { return this._message.Body; } + async send( + req: AgentRequest, + ctx: AgentContext, + to: string[], + message: SmsReply, + from?: string + ) { + const tracer = getTracer(); + const currentContext = context.active(); + + const authToken = req.metadata?.['twilio-auth-token'] as string; + if (!authToken) { + throw new Error( + 'twilio authorization token is required but not found in metadata' + ); + } + + const span = tracer.startSpan('agentuity.twilio.send', {}, currentContext); + + try { + const spanContext = trace.setSpan(currentContext, span); + + return await context.with(spanContext, async () => { + span.setAttribute('@agentuity/agentId', ctx.agent.id); + const resp = await POST( + '/sms/send', + safeStringify({ + agentId: ctx.agent.id, + from: from, + to: to, + message: message.text, + }), + { + 'Content-Type': 'application/json', + }, + undefined, + authToken + ); + if (resp.status === 200) { + span.setStatus({ code: SpanStatusCode.OK }); + return; + } + throw new Error( + `error sending sms: ${resp.response.statusText} (${resp.response.status})` + ); + }); + } catch (ex) { + recordException(span, ex); + throw ex; + } finally { + span.end(); + } + } + async sendReply(req: AgentRequest, ctx: AgentContext, reply: string) { const tracer = getTracer(); const currentContext = context.active(); @@ -119,4 +173,4 @@ export async function parseSms(data: Buffer): Promise { `Failed to parse sms: ${error instanceof Error ? error.message : 'Unknown error'}` ); } -} +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 1c27d09a..6b6c3a8b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -673,31 +673,29 @@ export interface EmailService { * Send a new email to the specified recipients. */ send( - agentId: string, - email: string, - authToken: string, - messageId: string - ): Promise; + req: AgentRequest, + context: AgentContext, + to: string[], + email: import('./io/email').EmailReply, + from?: { + name?: string; + email?: string; + } + ): Promise; /** * send an email reply to an incoming email - * - * @param agentId - the id of the agent to send the reply to - * @param email - the email to send the reply to in RFC822 format - * @param authToken - the authorization token to use to send the reply - * @param messageId - the message id of the email - * @param from - the email address to send the reply from (NOTE: you can only override the email address if you have configured custom email sending) */ sendReply( - agentId: string, - email: string, - authToken: string, - messageId: string, + req: AgentRequest, + context: AgentContext, + inReplyTo: string, + reply: import('./io/email').EmailReply, from?: { name?: string; email?: string; } - ): Promise; + ): Promise; } /** @@ -724,6 +722,17 @@ export interface SMSService { /** * send an SMS to a phone number */ + send( + req: AgentRequest, + context: AgentContext, + to: string[], + message: import('./io/sms').SmsReply, + from?: string + ): Promise; + + /** + * send an SMS reply to an incoming SMS message + */ sendReply( agentId: string, phoneNumber: string, @@ -1059,6 +1068,11 @@ export interface AgentContext { */ slack: SlackService; + /** + * the sms service + */ + sms: SMSService; + /** * EXPERIMENTAL: prompts API for accessing and compiling prompts */ @@ -1351,4 +1365,4 @@ export interface DataPayload { * the metadata */ metadata?: JsonObject; -} +} \ No newline at end of file From 2182956b985380ac468bf4c02802f122153d30c7 Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 27 Oct 2025 15:00:34 -0300 Subject: [PATCH 2/5] AMP being dumb --- src/types.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/types.ts b/src/types.ts index 6b6c3a8b..b4de46d4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -673,29 +673,31 @@ export interface EmailService { * Send a new email to the specified recipients. */ send( - req: AgentRequest, - context: AgentContext, - to: string[], - email: import('./io/email').EmailReply, - from?: { - name?: string; - email?: string; - } - ): Promise; + agentId: string, + email: string, + authToken: string, + messageId: string + ): Promise; /** * send an email reply to an incoming email + * + * @param agentId - the id of the agent to send the reply to + * @param email - the email to send the reply to in RFC822 format + * @param authToken - the authorization token to use to send the reply + * @param messageId - the message id of the email + * @param from - the email address to send the reply from (NOTE: you can only override the email address if you have configured custom email sending) */ sendReply( - req: AgentRequest, - context: AgentContext, - inReplyTo: string, - reply: import('./io/email').EmailReply, + agentId: string, + email: string, + authToken: string, + messageId: string, from?: { name?: string; email?: string; } - ): Promise; + ): Promise; } /** From a8251f9dddecf2ad272f94359f79860b020d00af Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 27 Oct 2025 21:20:55 -0300 Subject: [PATCH 3/5] Implemented feedback --- src/io/sms.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/io/sms.ts b/src/io/sms.ts index a4842fd2..bc426d70 100644 --- a/src/io/sms.ts +++ b/src/io/sms.ts @@ -13,7 +13,7 @@ type TwilioResponse = { }; /** - * A reply to an email + * A reply to an SMS */ export interface SmsReply { /** @@ -61,6 +61,7 @@ export class Sms { message: SmsReply, from?: string ) { + const timeout = 15_000; const tracer = getTracer(); const currentContext = context.active(); @@ -89,7 +90,7 @@ export class Sms { { 'Content-Type': 'application/json', }, - undefined, + timeout, authToken ); if (resp.status === 200) { @@ -109,6 +110,7 @@ export class Sms { } async sendReply(req: AgentRequest, ctx: AgentContext, reply: string) { + const timeout = 15_000; const tracer = getTracer(); const currentContext = context.active(); @@ -141,7 +143,7 @@ export class Sms { 'Content-Type': 'application/json', 'X-Agentuity-Message-Id': this.messageId, }, - undefined, + timeout, authToken ); if (resp.status === 200) { @@ -173,4 +175,5 @@ export async function parseSms(data: Buffer): Promise { `Failed to parse sms: ${error instanceof Error ? error.message : 'Unknown error'}` ); } -} \ No newline at end of file +} + From e9729c8599f70e61e4c05e79e1c895519d6c3027 Mon Sep 17 00:00:00 2001 From: huijiro Date: Fri, 31 Oct 2025 12:56:35 -0300 Subject: [PATCH 4/5] Re-structured code --- src/apis/sms.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++ src/server/server.ts | 3 ++ 2 files changed, 71 insertions(+) create mode 100644 src/apis/sms.ts diff --git a/src/apis/sms.ts b/src/apis/sms.ts new file mode 100644 index 00000000..87bb4831 --- /dev/null +++ b/src/apis/sms.ts @@ -0,0 +1,68 @@ +import { context, SpanStatusCode, trace } from '@opentelemetry/api'; +import type { SmsReply } from '../io/sms'; +import { getTracer, recordException } from '../router/router'; +import { safeStringify } from '../server/util'; +import type { AgentContext, AgentRequest, SMSService } from '../types'; +import { POST } from './api'; + +export default class SmsApi implements SMSService { + async send( + _req: AgentRequest, + ctx: AgentContext, + to: string[], + message: SmsReply, + from?: string + ): Promise { + const timeout = 15_000; + const tracer = getTracer(); + const currentContext = context.active(); + + const span = tracer.startSpan('agentuity.sms.send', {}, currentContext); + + try { + const spanContext = trace.setSpan(currentContext, span); + + return await context.with(spanContext, async () => { + span.setAttribute('@agentuity/agentId', ctx.agent.id); + const resp = await POST( + '/sms/send', + safeStringify({ + agentId: ctx.agent.id, + from: from, + to: to, + message: message.text, + }), + { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + timeout + ); + if (resp.status === 200) { + span.setStatus({ code: SpanStatusCode.OK }); + return; + } + throw new Error( + `error sending sms: ${resp.response.statusText} (${resp.response.status}): ${resp.json}` + ); + }); + } catch (ex) { + recordException(span, ex); + throw ex; + } finally { + span.end(); + } + } + + /** + * @deprecated Use reply on data.email + */ + async sendReply( + _agentId: string, + _phoneNumber: string, + _authToken: string, + _messageId: string + ): Promise { + throw new Error('reply not supported in context'); + } +} diff --git a/src/server/server.ts b/src/server/server.ts index ce204870..a9d26c1e 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -7,6 +7,7 @@ import KeyValueAPI from '../apis/keyvalue'; import ObjectStoreAPI from '../apis/objectstore'; import PatchPortal from '../apis/patchportal'; import PromptAPI from '../apis/prompt/index.js'; +import SmsAPI from '../apis/sms'; import StreamAPIImpl from '../apis/stream'; import VectorAPI from '../apis/vector'; import type { Logger } from '../logger'; @@ -182,6 +183,7 @@ const email = new EmailAPI(); const discord = new DiscordAPI(); const objectstore = new ObjectStoreAPI(); const prompts = new PromptAPI(); +const sms = new SmsAPI(); // PatchPortal will be initialized lazily since it's async let patchportal: PatchPortal | null = null; @@ -222,6 +224,7 @@ export async function createServerContext( discord, objectstore, patchportal, + sms, sdkVersion: req.sdkVersion, agents: req.agents, scope: 'local', From 838846d6b6dea9d3f120af5d16861e8657ffdb4c Mon Sep 17 00:00:00 2001 From: huijiro Date: Mon, 3 Nov 2025 12:03:40 -0300 Subject: [PATCH 5/5] Implemented feedback --- src/apis/sms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/sms.ts b/src/apis/sms.ts index 87bb4831..cef21cd8 100644 --- a/src/apis/sms.ts +++ b/src/apis/sms.ts @@ -55,7 +55,7 @@ export default class SmsApi implements SMSService { } /** - * @deprecated Use reply on data.email + * @deprecated Use reply on data.email.sms() */ async sendReply( _agentId: string,