refactor: share tlon outbound send context

This commit is contained in:
Peter Steinberger
2026-03-13 21:55:47 +00:00
parent a57c590a71
commit 407d0d296d

View File

@@ -153,6 +153,48 @@ function applyTlonSetupConfig(params: {
}; };
} }
type ResolvedTlonAccount = ReturnType<typeof resolveTlonAccount>;
function resolveOutboundContext(params: { cfg: OpenClawConfig; accountId?: string; to: string }) {
const account = resolveTlonAccount(params.cfg, params.accountId ?? undefined);
if (!account.configured || !account.ship || !account.url || !account.code) {
throw new Error("Tlon account not configured");
}
const parsed = parseTlonTarget(params.to);
if (!parsed) {
throw new Error(`Invalid Tlon target. Use ${formatTargetHint()}`);
}
return { account, parsed };
}
function resolveReplyId(replyToId?: string, threadId?: string) {
return (replyToId ?? threadId) ? String(replyToId ?? threadId) : undefined;
}
async function withHttpPokeAccountApi<T>(
account: ResolvedTlonAccount & { ship: string; url: string; code: string },
run: (api: Awaited<ReturnType<typeof createHttpPokeApi>>) => Promise<T>,
) {
const api = await createHttpPokeApi({
url: account.url,
ship: account.ship,
code: account.code,
allowPrivateNetwork: account.allowPrivateNetwork ?? undefined,
});
try {
return await run(api);
} finally {
try {
await api.delete();
} catch {
// ignore cleanup errors
}
}
}
const tlonOutbound: ChannelOutboundAdapter = { const tlonOutbound: ChannelOutboundAdapter = {
deliveryMode: "direct", deliveryMode: "direct",
textChunkLimit: 10000, textChunkLimit: 10000,
@@ -170,25 +212,8 @@ const tlonOutbound: ChannelOutboundAdapter = {
return { ok: true, to: parsed.nest }; return { ok: true, to: parsed.nest };
}, },
sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => { sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => {
const account = resolveTlonAccount(cfg, accountId ?? undefined); const { account, parsed } = resolveOutboundContext({ cfg, accountId, to });
if (!account.configured || !account.ship || !account.url || !account.code) { return withHttpPokeAccountApi(account, async (api) => {
throw new Error("Tlon account not configured");
}
const parsed = parseTlonTarget(to);
if (!parsed) {
throw new Error(`Invalid Tlon target. Use ${formatTargetHint()}`);
}
// Use HTTP-only poke (no EventSource) to avoid conflicts with monitor's SSE connection
const api = await createHttpPokeApi({
url: account.url,
ship: account.ship,
code: account.code,
allowPrivateNetwork: account.allowPrivateNetwork ?? undefined,
});
try {
const fromShip = normalizeShip(account.ship); const fromShip = normalizeShip(account.ship);
if (parsed.kind === "dm") { if (parsed.kind === "dm") {
return await sendDm({ return await sendDm({
@@ -198,33 +223,18 @@ const tlonOutbound: ChannelOutboundAdapter = {
text, text,
}); });
} }
const replyId = (replyToId ?? threadId) ? String(replyToId ?? threadId) : undefined;
return await sendGroupMessage({ return await sendGroupMessage({
api, api,
fromShip, fromShip,
hostShip: parsed.hostShip, hostShip: parsed.hostShip,
channelName: parsed.channelName, channelName: parsed.channelName,
text, text,
replyToId: replyId, replyToId: resolveReplyId(replyToId, threadId),
}); });
} finally { });
try {
await api.delete();
} catch {
// ignore cleanup errors
}
}
}, },
sendMedia: async ({ cfg, to, text, mediaUrl, accountId, replyToId, threadId }) => { sendMedia: async ({ cfg, to, text, mediaUrl, accountId, replyToId, threadId }) => {
const account = resolveTlonAccount(cfg, accountId ?? undefined); const { account, parsed } = resolveOutboundContext({ cfg, accountId, to });
if (!account.configured || !account.ship || !account.url || !account.code) {
throw new Error("Tlon account not configured");
}
const parsed = parseTlonTarget(to);
if (!parsed) {
throw new Error(`Invalid Tlon target. Use ${formatTargetHint()}`);
}
// Configure the API client for uploads // Configure the API client for uploads
configureClient({ configureClient({
@@ -235,15 +245,7 @@ const tlonOutbound: ChannelOutboundAdapter = {
}); });
const uploadedUrl = mediaUrl ? await uploadImageFromUrl(mediaUrl) : undefined; const uploadedUrl = mediaUrl ? await uploadImageFromUrl(mediaUrl) : undefined;
return withHttpPokeAccountApi(account, async (api) => {
const api = await createHttpPokeApi({
url: account.url,
ship: account.ship,
code: account.code,
allowPrivateNetwork: account.allowPrivateNetwork ?? undefined,
});
try {
const fromShip = normalizeShip(account.ship); const fromShip = normalizeShip(account.ship);
const story = buildMediaStory(text, uploadedUrl); const story = buildMediaStory(text, uploadedUrl);
@@ -255,22 +257,15 @@ const tlonOutbound: ChannelOutboundAdapter = {
story, story,
}); });
} }
const replyId = (replyToId ?? threadId) ? String(replyToId ?? threadId) : undefined;
return await sendGroupMessageWithStory({ return await sendGroupMessageWithStory({
api, api,
fromShip, fromShip,
hostShip: parsed.hostShip, hostShip: parsed.hostShip,
channelName: parsed.channelName, channelName: parsed.channelName,
story, story,
replyToId: replyId, replyToId: resolveReplyId(replyToId, threadId),
}); });
} finally { });
try {
await api.delete();
} catch {
// ignore cleanup errors
}
}
}, },
}; };