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
1 changed files with 50 additions and 55 deletions

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 = {
deliveryMode: "direct",
textChunkLimit: 10000,
@ -170,25 +212,8 @@ const tlonOutbound: ChannelOutboundAdapter = {
return { ok: true, to: parsed.nest };
},
sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => {
const account = resolveTlonAccount(cfg, accountId ?? undefined);
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()}`);
}
// 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 { account, parsed } = resolveOutboundContext({ cfg, accountId, to });
return withHttpPokeAccountApi(account, async (api) => {
const fromShip = normalizeShip(account.ship);
if (parsed.kind === "dm") {
return await sendDm({
@ -198,33 +223,18 @@ const tlonOutbound: ChannelOutboundAdapter = {
text,
});
}
const replyId = (replyToId ?? threadId) ? String(replyToId ?? threadId) : undefined;
return await sendGroupMessage({
api,
fromShip,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
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 }) => {
const account = resolveTlonAccount(cfg, accountId ?? undefined);
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()}`);
}
const { account, parsed } = resolveOutboundContext({ cfg, accountId, to });
// Configure the API client for uploads
configureClient({
@ -235,15 +245,7 @@ const tlonOutbound: ChannelOutboundAdapter = {
});
const uploadedUrl = mediaUrl ? await uploadImageFromUrl(mediaUrl) : undefined;
const api = await createHttpPokeApi({
url: account.url,
ship: account.ship,
code: account.code,
allowPrivateNetwork: account.allowPrivateNetwork ?? undefined,
});
try {
return withHttpPokeAccountApi(account, async (api) => {
const fromShip = normalizeShip(account.ship);
const story = buildMediaStory(text, uploadedUrl);
@ -255,22 +257,15 @@ const tlonOutbound: ChannelOutboundAdapter = {
story,
});
}
const replyId = (replyToId ?? threadId) ? String(replyToId ?? threadId) : undefined;
return await sendGroupMessageWithStory({
api,
fromShip,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
story,
replyToId: replyId,
replyToId: resolveReplyId(replyToId, threadId),
});
});
} finally {
try {
await api.delete();
} catch {
// ignore cleanup errors
}
}
},
};