Skip to content

Commit 16e2390

Browse files
committed
refactor: use official twilio SDK in twilio-action
Replace the raw axios + Twilio REST client with the official `twilio` Node SDK for sending messages. - helpers.ts uses twilio(accountSid, authToken) and client.messages.create, mapping the returned MessageInstance onto the TWILIO_MESSAGE schema - package.json: drop axios, add twilio - vite.config.ts: externalize twilio so it loads from node_modules at runtime - index.ts: drop the now-unnecessary base_url config (the SDK targets api.twilio.com directly) NOTE: package-lock.json must be regenerated with `npm install` so it matches the new dependency set before CI's `npm ci` can succeed. Refs #33 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hrvb3LRhmxcrASCuSXc7Jn
1 parent 231edae commit 16e2390

4 files changed

Lines changed: 38 additions & 39 deletions

File tree

actions/twilio-action/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"dependencies": {
1515
"@code0-tech/hercules": "^1.1.1",
16-
"axios": "^1.7.7"
16+
"twilio": "^5.0.0"
1717
},
1818
"devDependencies": {
1919
"@types/node": "^22.0.0",

actions/twilio-action/src/helpers.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { FunctionContext, RuntimeError } from "@code0-tech/hercules";
2-
import axios from "axios";
2+
import twilio from "twilio";
33
import { z } from "zod";
44
import { TwilioMessage, TwilioMessageSchema } from "./data_types/twilioMessage.js";
55

6-
const DEFAULT_BASE_URL = "https://api.twilio.com";
7-
86
export const SendMessageRequestDataSchema = z.object({
97
To: z.string().describe("The destination phone number or channel address (e.g. whatsapp:+1...)."),
108
Body: z.string().describe("The text of the message to send. Up to 1600 characters."),
@@ -34,7 +32,7 @@ function getCredentials(context: FunctionContext): { accountSid: string; authTok
3432
}
3533

3634
/**
37-
* Sends a message through the Twilio Messages API.
35+
* Sends a message through the Twilio Messages API using the official Twilio SDK.
3836
* See https://www.twilio.com/docs/messaging/api/message-resource#create-a-message-resource
3937
*/
4038
export const sendMessage = async (
@@ -52,38 +50,46 @@ export const sendMessage = async (
5250
);
5351
}
5452

55-
const baseUrl = (context.matchedConfig.findConfig("base_url") as string) || DEFAULT_BASE_URL;
56-
57-
const body = new URLSearchParams();
58-
body.set("To", parsed.To);
59-
body.set("From", from);
60-
body.set("Body", parsed.Body);
61-
if (parsed.MediaUrl) {
62-
body.set("MediaUrl", parsed.MediaUrl);
63-
}
53+
const client = twilio(accountSid, authToken);
6454

6555
try {
66-
const result = await axios.post(
67-
`${baseUrl}/2010-04-01/Accounts/${accountSid}/Messages.json`,
68-
body,
69-
{
70-
auth: { username: accountSid, password: authToken },
71-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
72-
}
73-
);
74-
return TwilioMessageSchema.parse(result.data);
56+
const message = await client.messages.create({
57+
to: parsed.To,
58+
from,
59+
body: parsed.Body,
60+
...(parsed.MediaUrl ? { mediaUrl: [parsed.MediaUrl] } : {}),
61+
});
62+
63+
return TwilioMessageSchema.parse({
64+
sid: message.sid,
65+
account_sid: message.accountSid,
66+
messaging_service_sid: message.messagingServiceSid,
67+
from: message.from,
68+
to: message.to,
69+
body: message.body,
70+
status: message.status,
71+
direction: message.direction,
72+
num_segments: message.numSegments,
73+
num_media: message.numMedia,
74+
price: message.price,
75+
price_unit: message.priceUnit,
76+
error_code: message.errorCode,
77+
error_message: message.errorMessage,
78+
date_created: message.dateCreated ? message.dateCreated.toUTCString() : null,
79+
date_updated: message.dateUpdated ? message.dateUpdated.toUTCString() : null,
80+
date_sent: message.dateSent ? message.dateSent.toUTCString() : null,
81+
api_version: message.apiVersion,
82+
uri: message.uri,
83+
});
7584
} catch (error: any) {
76-
const twilioError = error.response?.data;
77-
if (twilioError?.message) {
78-
console.error("Error sending message to Twilio API:", twilioError.code, twilioError.message);
85+
// Twilio's RestException exposes a numeric `code` and a human-readable `message`.
86+
if (error?.code || error?.message) {
87+
console.error("Error sending message to Twilio API:", error.code, error.message);
7988
throw new RuntimeError(
8089
"ERROR_SENDING_TWILIO_MESSAGE",
81-
`Twilio API error ${twilioError.code ?? ""}: ${twilioError.message}`
90+
`Twilio API error ${error.code ?? ""}: ${error.message ?? "unknown error"}`
8291
);
8392
}
84-
if (error instanceof Error) {
85-
throw new RuntimeError("ERROR_SENDING_TWILIO_MESSAGE", error.message);
86-
}
8793
throw new RuntimeError("ERROR_SENDING_TWILIO_MESSAGE", "An error occurred while sending the Twilio message.");
8894
}
8995
};

actions/twilio-action/src/index.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,6 @@ const action = new Action(
4141
],
4242
linkedDataTypes: ["TEXT"],
4343
},
44-
{
45-
identifier: "base_url",
46-
type: "TEXT",
47-
defaultValue: "https://api.twilio.com",
48-
name: [{ code: "en-US", content: "The Twilio API url" }],
49-
description: [{ code: "en-US", content: "The base url of the Twilio API." }],
50-
linkedDataTypes: ["TEXT"],
51-
},
5244
]
5345
);
5446

actions/twilio-action/vite.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export default defineConfig({
1111
'fs',
1212
'path',
1313
'typescript',
14-
'@code0-tech/hercules'
14+
'@code0-tech/hercules',
15+
'twilio'
1516
].includes(id) || id.startsWith('node:')
1617
}
1718
}

0 commit comments

Comments
 (0)