refactor: deduplicate parse helpers into src/utils (#398)
This commit is contained in:
15
src/main.ts
15
src/main.ts
@@ -24,6 +24,7 @@ import {
|
||||
} from './config/index.js';
|
||||
import { isLettaApiUrl } from './utils/server.js';
|
||||
import { getDataDir, getWorkingDir, hasRailwayVolume } from './utils/paths.js';
|
||||
import { parseCsvList, parseNonNegativeNumber } from './utils/parse.js';
|
||||
import { createLogger, setLogLevel } from './logger.js';
|
||||
|
||||
const log = createLogger('Config');
|
||||
@@ -484,20 +485,6 @@ function createGroupBatcher(
|
||||
|
||||
// Skills are installed to agent-scoped directory when agent is created (see core/bot.ts)
|
||||
|
||||
function parseCsvList(raw: string): string[] {
|
||||
return raw
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
function parseNonNegativeNumber(raw: string | undefined): number | undefined {
|
||||
if (!raw) return undefined;
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isFinite(parsed) || parsed < 0) return undefined;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function ensureRequiredTools(tools: string[]): string[] {
|
||||
const out = [...tools];
|
||||
if (!out.includes('manage_todo')) {
|
||||
|
||||
@@ -9,24 +9,19 @@ import * as p from '@clack/prompts';
|
||||
import { saveConfig, syncProviders, isApiServerMode } from './config/index.js';
|
||||
import type { AgentConfig, LettaBotConfig, ProviderConfig } from './config/types.js';
|
||||
import { isLettaApiUrl } from './utils/server.js';
|
||||
import { parseCsvList, parseOptionalInt } from './utils/parse.js';
|
||||
import { CHANNELS, getChannelHint, isSignalCliInstalled, setupTelegram, setupSlack, setupDiscord, setupWhatsApp, setupSignal } from './channels/setup.js';
|
||||
|
||||
// ============================================================================
|
||||
// Non-Interactive Helpers
|
||||
// ============================================================================
|
||||
|
||||
function parseCsvList(value?: string): string[] | undefined {
|
||||
function parseOptionalCsvList(value?: string): string[] | undefined {
|
||||
if (!value) return undefined;
|
||||
const items = value.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const items = parseCsvList(value);
|
||||
return items.length > 0 ? items : undefined;
|
||||
}
|
||||
|
||||
function parseOptionalInt(value?: string): number | undefined {
|
||||
if (!value) return undefined;
|
||||
const parsed = parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function readConfigFromEnv(existingConfig: any): any {
|
||||
return {
|
||||
baseUrl: process.env.LETTA_BASE_URL || existingConfig.server?.baseUrl || 'https://api.letta.com',
|
||||
@@ -43,9 +38,9 @@ function readConfigFromEnv(existingConfig: any): any {
|
||||
?? existingConfig.channels?.telegram?.groupDebounceSec,
|
||||
groupPollIntervalMin: parseOptionalInt(process.env.TELEGRAM_GROUP_POLL_INTERVAL_MIN)
|
||||
?? existingConfig.channels?.telegram?.groupPollIntervalMin,
|
||||
instantGroups: parseCsvList(process.env.TELEGRAM_INSTANT_GROUPS)
|
||||
instantGroups: parseOptionalCsvList(process.env.TELEGRAM_INSTANT_GROUPS)
|
||||
?? existingConfig.channels?.telegram?.instantGroups,
|
||||
listeningGroups: parseCsvList(process.env.TELEGRAM_LISTENING_GROUPS)
|
||||
listeningGroups: parseOptionalCsvList(process.env.TELEGRAM_LISTENING_GROUPS)
|
||||
?? existingConfig.channels?.telegram?.listeningGroups,
|
||||
},
|
||||
|
||||
@@ -59,9 +54,9 @@ function readConfigFromEnv(existingConfig: any): any {
|
||||
?? existingConfig.channels?.slack?.groupDebounceSec,
|
||||
groupPollIntervalMin: parseOptionalInt(process.env.SLACK_GROUP_POLL_INTERVAL_MIN)
|
||||
?? existingConfig.channels?.slack?.groupPollIntervalMin,
|
||||
instantGroups: parseCsvList(process.env.SLACK_INSTANT_GROUPS)
|
||||
instantGroups: parseOptionalCsvList(process.env.SLACK_INSTANT_GROUPS)
|
||||
?? existingConfig.channels?.slack?.instantGroups,
|
||||
listeningGroups: parseCsvList(process.env.SLACK_LISTENING_GROUPS)
|
||||
listeningGroups: parseOptionalCsvList(process.env.SLACK_LISTENING_GROUPS)
|
||||
?? existingConfig.channels?.slack?.listeningGroups,
|
||||
},
|
||||
|
||||
@@ -74,9 +69,9 @@ function readConfigFromEnv(existingConfig: any): any {
|
||||
?? existingConfig.channels?.discord?.groupDebounceSec,
|
||||
groupPollIntervalMin: parseOptionalInt(process.env.DISCORD_GROUP_POLL_INTERVAL_MIN)
|
||||
?? existingConfig.channels?.discord?.groupPollIntervalMin,
|
||||
instantGroups: parseCsvList(process.env.DISCORD_INSTANT_GROUPS)
|
||||
instantGroups: parseOptionalCsvList(process.env.DISCORD_INSTANT_GROUPS)
|
||||
?? existingConfig.channels?.discord?.instantGroups,
|
||||
listeningGroups: parseCsvList(process.env.DISCORD_LISTENING_GROUPS)
|
||||
listeningGroups: parseOptionalCsvList(process.env.DISCORD_LISTENING_GROUPS)
|
||||
?? existingConfig.channels?.discord?.listeningGroups,
|
||||
},
|
||||
|
||||
@@ -89,9 +84,9 @@ function readConfigFromEnv(existingConfig: any): any {
|
||||
?? existingConfig.channels?.whatsapp?.groupDebounceSec,
|
||||
groupPollIntervalMin: parseOptionalInt(process.env.WHATSAPP_GROUP_POLL_INTERVAL_MIN)
|
||||
?? existingConfig.channels?.whatsapp?.groupPollIntervalMin,
|
||||
instantGroups: parseCsvList(process.env.WHATSAPP_INSTANT_GROUPS)
|
||||
instantGroups: parseOptionalCsvList(process.env.WHATSAPP_INSTANT_GROUPS)
|
||||
?? existingConfig.channels?.whatsapp?.instantGroups,
|
||||
listeningGroups: parseCsvList(process.env.WHATSAPP_LISTENING_GROUPS)
|
||||
listeningGroups: parseOptionalCsvList(process.env.WHATSAPP_LISTENING_GROUPS)
|
||||
?? existingConfig.channels?.whatsapp?.listeningGroups,
|
||||
},
|
||||
|
||||
@@ -105,9 +100,9 @@ function readConfigFromEnv(existingConfig: any): any {
|
||||
?? existingConfig.channels?.signal?.groupDebounceSec,
|
||||
groupPollIntervalMin: parseOptionalInt(process.env.SIGNAL_GROUP_POLL_INTERVAL_MIN)
|
||||
?? existingConfig.channels?.signal?.groupPollIntervalMin,
|
||||
instantGroups: parseCsvList(process.env.SIGNAL_INSTANT_GROUPS)
|
||||
instantGroups: parseOptionalCsvList(process.env.SIGNAL_INSTANT_GROUPS)
|
||||
?? existingConfig.channels?.signal?.instantGroups,
|
||||
listeningGroups: parseCsvList(process.env.SIGNAL_LISTENING_GROUPS)
|
||||
listeningGroups: parseOptionalCsvList(process.env.SIGNAL_LISTENING_GROUPS)
|
||||
?? existingConfig.channels?.signal?.listeningGroups,
|
||||
},
|
||||
};
|
||||
|
||||
41
src/utils/parse.test.ts
Normal file
41
src/utils/parse.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseCsvList, parseNonNegativeNumber, parseOptionalInt } from './parse.js';
|
||||
|
||||
describe('parseCsvList', () => {
|
||||
it('splits and trims comma-separated values', () => {
|
||||
expect(parseCsvList('one, two,three')).toEqual(['one', 'two', 'three']);
|
||||
});
|
||||
|
||||
it('drops empty entries', () => {
|
||||
expect(parseCsvList('one,, ,two,')).toEqual(['one', 'two']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseOptionalInt', () => {
|
||||
it('returns undefined for missing values', () => {
|
||||
expect(parseOptionalInt()).toBeUndefined();
|
||||
expect(parseOptionalInt('')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parses valid integer prefixes', () => {
|
||||
expect(parseOptionalInt('42')).toBe(42);
|
||||
expect(parseOptionalInt('42px')).toBe(42);
|
||||
});
|
||||
|
||||
it('returns undefined for invalid values', () => {
|
||||
expect(parseOptionalInt('nope')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseNonNegativeNumber', () => {
|
||||
it('returns undefined for missing, invalid, or negative values', () => {
|
||||
expect(parseNonNegativeNumber()).toBeUndefined();
|
||||
expect(parseNonNegativeNumber('nope')).toBeUndefined();
|
||||
expect(parseNonNegativeNumber('-1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('parses zero and positive numbers', () => {
|
||||
expect(parseNonNegativeNumber('0')).toBe(0);
|
||||
expect(parseNonNegativeNumber('1.5')).toBe(1.5);
|
||||
});
|
||||
});
|
||||
23
src/utils/parse.ts
Normal file
23
src/utils/parse.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Shared parsing helpers for environment/config input.
|
||||
*/
|
||||
|
||||
export function parseCsvList(raw: string): string[] {
|
||||
return raw
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
export function parseOptionalInt(raw?: string): number | undefined {
|
||||
if (!raw) return undefined;
|
||||
const parsed = parseInt(raw, 10);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
export function parseNonNegativeNumber(raw?: string): number | undefined {
|
||||
if (!raw) return undefined;
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isFinite(parsed) || parsed < 0) return undefined;
|
||||
return parsed;
|
||||
}
|
||||
Reference in New Issue
Block a user