refactor: share thread binding id parser

This commit is contained in:
Peter Steinberger
2026-03-07 20:04:44 +00:00
parent 95fe282a17
commit 30d091b2fb
4 changed files with 67 additions and 36 deletions

View File

@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { resolveThreadBindingConversationIdFromBindingId } from "./thread-binding-id.js";
describe("resolveThreadBindingConversationIdFromBindingId", () => {
it("returns the conversation id for matching account-prefixed binding ids", () => {
expect(
resolveThreadBindingConversationIdFromBindingId({
accountId: "default",
bindingId: "default:thread-123",
}),
).toBe("thread-123");
});
it("returns undefined when binding id is missing or account prefix does not match", () => {
expect(
resolveThreadBindingConversationIdFromBindingId({
accountId: "default",
bindingId: undefined,
}),
).toBeUndefined();
expect(
resolveThreadBindingConversationIdFromBindingId({
accountId: "default",
bindingId: "work:thread-123",
}),
).toBeUndefined();
});
it("trims whitespace and rejects empty ids after the account prefix", () => {
expect(
resolveThreadBindingConversationIdFromBindingId({
accountId: "default",
bindingId: " default:group-1:topic:99 ",
}),
).toBe("group-1:topic:99");
expect(
resolveThreadBindingConversationIdFromBindingId({
accountId: "default",
bindingId: "default: ",
}),
).toBeUndefined();
});
});

View File

@@ -0,0 +1,15 @@
export function resolveThreadBindingConversationIdFromBindingId(params: {
accountId: string;
bindingId?: string;
}): string | undefined {
const bindingId = params.bindingId?.trim();
if (!bindingId) {
return undefined;
}
const prefix = `${params.accountId}:`;
if (!bindingId.startsWith(prefix)) {
return undefined;
}
const conversationId = bindingId.slice(prefix.length).trim();
return conversationId || undefined;
}

View File

@@ -1,4 +1,5 @@
import { Routes } from "discord-api-types/v10";
import { resolveThreadBindingConversationIdFromBindingId } from "../../channels/thread-binding-id.js";
import { logVerbose } from "../../globals.js";
import {
registerSessionBindingAdapter,
@@ -157,22 +158,6 @@ function toSessionBindingRecord(
};
}
function resolveThreadIdFromBindingId(params: {
accountId: string;
bindingId?: string;
}): string | undefined {
const bindingId = params.bindingId?.trim();
if (!bindingId) {
return undefined;
}
const prefix = `${params.accountId}:`;
if (!bindingId.startsWith(prefix)) {
return undefined;
}
const threadId = bindingId.slice(prefix.length).trim();
return threadId || undefined;
}
export function createThreadBindingManager(
params: {
accountId?: string;
@@ -617,7 +602,10 @@ export function createThreadBindingManager(
return binding ? toSessionBindingRecord(binding, { idleTimeoutMs, maxAgeMs }) : null;
},
touch: (bindingId, at) => {
const threadId = resolveThreadIdFromBindingId({ accountId, bindingId });
const threadId = resolveThreadBindingConversationIdFromBindingId({
accountId,
bindingId,
});
if (!threadId) {
return;
}
@@ -631,7 +619,7 @@ export function createThreadBindingManager(
});
return removed.map((entry) => toSessionBindingRecord(entry, { idleTimeoutMs, maxAgeMs }));
}
const threadId = resolveThreadIdFromBindingId({
const threadId = resolveThreadBindingConversationIdFromBindingId({
accountId,
bindingId: input.bindingId,
});

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { resolveThreadBindingConversationIdFromBindingId } from "../channels/thread-binding-id.js";
import { formatThreadBindingDurationLabel } from "../channels/thread-bindings-messages.js";
import { resolveStateDir } from "../config/paths.js";
import { logVerbose } from "../globals.js";
@@ -312,22 +313,6 @@ async function persistBindingsToDisk(params: {
});
}
function resolveThreadIdFromBindingId(params: {
accountId: string;
bindingId?: string;
}): string | undefined {
const bindingId = params.bindingId?.trim();
if (!bindingId) {
return undefined;
}
const prefix = `${params.accountId}:`;
if (!bindingId.startsWith(prefix)) {
return undefined;
}
const conversationId = bindingId.slice(prefix.length).trim();
return conversationId || undefined;
}
function normalizeTimestampMs(raw: unknown): number {
if (typeof raw !== "number" || !Number.isFinite(raw)) {
return Date.now();
@@ -575,7 +560,7 @@ export function createTelegramThreadBindingManager(
: null;
},
touch: (bindingId, at) => {
const conversationId = resolveThreadIdFromBindingId({
const conversationId = resolveThreadBindingConversationIdFromBindingId({
accountId,
bindingId,
});
@@ -598,7 +583,7 @@ export function createTelegramThreadBindingManager(
}),
);
}
const conversationId = resolveThreadIdFromBindingId({
const conversationId = resolveThreadBindingConversationIdFromBindingId({
accountId,
bindingId: input.bindingId,
});