fix: support numeric track_id arguments across track tools

This commit is contained in:
Xiaohan-Tian
2026-06-06 17:27:04 -07:00
parent 8323e6218c
commit a76a8abb94
12 changed files with 322 additions and 37 deletions
+63
View File
@@ -87,6 +87,26 @@ describe('AddNotesTool', () => {
expect(createdRegion.getNotes().map(note => note.getStartBeat())).toEqual([0, 2]);
});
it('accepts a numeric track_id and creates notes on the matching track', async () => {
const track = new KGMidiTrack('Lead', 1);
const project = new KGProject('numeric-track-id-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
project.setTracks([track]);
mockCore(project);
const tool = new AddNotesTool();
const result = await tool.execute({
track_id: track.getId(),
notes: [{ pitch: 'C4', start: 8, length: 2 }],
});
expect(result.success).toBe(true);
expect(track.getRegions()).toHaveLength(1);
const createdRegion = track.getRegions()[0] as KGMidiRegion;
expect(createdRegion.getName()).toBe('Lead Region');
expect(createdRegion.getNotes()).toHaveLength(1);
expect(createdRegion.getNotes()[0].getStartBeat()).toBe(0);
});
it('targets a track by track_name when track_id is omitted', async () => {
const targetTrack = new KGMidiTrack('Lead', 1);
const otherTrack = new KGMidiTrack('Bass', 2);
@@ -126,6 +146,26 @@ describe('AddNotesTool', () => {
expect((bassTrack.getRegions()[0] as KGMidiRegion).getName()).toBe('Bass Region');
});
it('uses a numeric track_id when both track_id and track_name are provided', async () => {
const leadTrack = new KGMidiTrack('Lead', 1);
const bassTrack = new KGMidiTrack('Bass', 2);
const project = new KGProject('numeric-track-id-precedence-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
project.setTracks([leadTrack, bassTrack]);
mockCore(project);
const tool = new AddNotesTool();
const result = await tool.execute({
track_id: bassTrack.getId(),
track_name: 'Lead',
notes: [{ pitch: 'C4', start: 8, length: 2 }],
});
expect(result.success).toBe(true);
expect(leadTrack.getRegions()).toHaveLength(0);
expect(bassTrack.getRegions()).toHaveLength(1);
expect((bassTrack.getRegions()[0] as KGMidiRegion).getName()).toBe('Bass Region');
});
it('uses the first matching track when duplicate track names exist', async () => {
const firstLead = new KGMidiTrack('Lead', 1);
const secondLead = new KGMidiTrack('Lead', 2);
@@ -169,6 +209,29 @@ describe('AddNotesTool', () => {
expect(regionB.getNotes().find(note => note.getId() !== 'note-existing')?.getStartBeat()).toBe(0);
});
it('builds summaries when track_id is provided as a number', () => {
const track = new KGMidiTrack('Lead', 1);
const project = new KGProject('numeric-track-id-summary-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
project.setTracks([track]);
mockCore(project);
const tool = new AddNotesTool();
const args = {
track_id: track.getId(),
notes: [
{ pitch: 'C4', start: 8, length: 2 },
{ pitch: 'E4', start: 10, length: 2 },
],
};
expect(tool.buildToolResultDisplayContent(args, { success: true, result: 'raw result' })).toBe(
'Successfully created 2 notes in new region **Lead Region** on track **Lead**, spanning bars 3 to 3.',
);
expect(tool.buildConfirmationContent(args)).toBe(
'Allow creating 2 notes on track **Lead** in a new region, spanning bars 3 to 3?',
);
});
it('returns distinct raw, history, and UI guidance when no MIDI target is available', async () => {
const project = new KGProject('no-target-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
mockCore(project);
+8 -5
View File
@@ -16,6 +16,7 @@ import { ResizeRegionCommand } from '../../core/commands/region/ResizeRegionComm
import { KGCore } from '../../core/KGCore';
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
import { KGMidiTrack } from '../../core/track/KGMidiTrack';
import { normalizeOptionalTrackIdParam } from './trackIdNormalization';
interface RequestedNote {
pitch: string;
@@ -226,9 +227,10 @@ export class AddNotesTool extends BaseTool {
async execute(params: Record<string, unknown>): Promise<ToolResult> {
try {
this.validateParameters(params);
const normalizedParams = normalizeOptionalTrackIdParam(params);
this.validateParameters(normalizedParams);
const notes = params.notes as RequestedNote[];
const notes = normalizedParams.notes as RequestedNote[];
if (notes.length === 0) {
return this.createErrorResult('No notes were provided.');
}
@@ -255,8 +257,8 @@ export class AddNotesTool extends BaseTool {
}
}
const trackId = params.track_id as string | undefined;
const trackName = params.track_name as string | undefined;
const trackId = normalizedParams.track_id as string | undefined;
const trackName = normalizedParams.track_name as string | undefined;
if (trackId || trackName) {
const explicitTrack = resolveMidiTrackByIdOrName(trackId, trackName);
if (!explicitTrack) {
@@ -291,7 +293,8 @@ export class AddNotesTool extends BaseTool {
}
private buildSummaryData(args: Record<string, unknown>): AddNotesSummaryData | null {
const typedArgs = args as {
const normalizedArgs = normalizeOptionalTrackIdParam(args);
const typedArgs = normalizedArgs as {
notes?: Array<{ start: number; length: number }>;
track_id?: string;
track_name?: string;
+35
View File
@@ -52,6 +52,24 @@ describe('DeleteTrackTool', () => {
);
});
it('deletes a MIDI track by numeric track_id', async () => {
const leadTrack = new KGMidiTrack('Lead', 1, 'trumpet');
const bassTrack = new KGMidiTrack('Bass', 2, 'acoustic_bass');
const project = new KGProject('delete-by-numeric-id-project');
project.setTracks([leadTrack, bassTrack]);
mockCore(project);
const tool = new DeleteTrackTool();
const result = await tool.execute({ track_id: 1 });
expect(result).toEqual({
success: true,
result: 'Track deleted:\ntrack_id: 1\ntrack_name: Lead',
});
expect(project.getTracks().map(track => track.getName())).toEqual(['Bass']);
expect(tool.buildConfirmationContent({ track_id: 1 })).toBe('Allow deleting track ID **1**?');
});
it('deletes a MIDI track by track_name', async () => {
const leadTrack = new KGMidiTrack('Lead', 1, 'trumpet');
const bassTrack = new KGMidiTrack('Bass', 2, 'acoustic_bass');
@@ -86,6 +104,23 @@ describe('DeleteTrackTool', () => {
expect(project.getTracks().map(track => track.getName())).toEqual(['Lead']);
});
it('uses numeric track_id when both track_id and track_name are provided', async () => {
const leadTrack = new KGMidiTrack('Lead', 1, 'trumpet');
const bassTrack = new KGMidiTrack('Bass', 2, 'acoustic_bass');
const project = new KGProject('delete-numeric-track-id-precedence-project');
project.setTracks([leadTrack, bassTrack]);
mockCore(project);
const tool = new DeleteTrackTool();
const result = await tool.execute({
track_id: 2,
track_name: 'Lead',
});
expect(result.success).toBe(true);
expect(project.getTracks().map(track => track.getName())).toEqual(['Lead']);
});
it('rejects duplicate track names when track_id is omitted', async () => {
const firstLead = new KGMidiTrack('Lead', 1, 'trumpet');
const secondLead = new KGMidiTrack('Lead', 2, 'flute');
+11 -7
View File
@@ -3,6 +3,7 @@ import { KGMidiTrack } from '../../core/track/KGMidiTrack';
import { BaseTool } from './BaseTool';
import type { ToolParameter, ToolResult } from './BaseTool';
import { resolveMidiTrackByExactName, resolveMidiTrackByIdOrName } from './toolTargeting';
import { normalizeOptionalTrackIdParam } from './trackIdNormalization';
export class DeleteTrackTool extends BaseTool {
readonly name = 'delete_track';
@@ -35,12 +36,14 @@ export class DeleteTrackTool extends BaseTool {
return undefined;
}
if (typeof args.track_id === 'string') {
return `Allow deleting track ID **${args.track_id}**?`;
const normalizedArgs = normalizeOptionalTrackIdParam(args);
if (typeof normalizedArgs.track_id === 'string') {
return `Allow deleting track ID **${normalizedArgs.track_id}**?`;
}
if (typeof args.track_name === 'string') {
return `Allow deleting track **${args.track_name}**?`;
if (typeof normalizedArgs.track_name === 'string') {
return `Allow deleting track **${normalizedArgs.track_name}**?`;
}
return undefined;
@@ -66,10 +69,11 @@ export class DeleteTrackTool extends BaseTool {
async execute(params: Record<string, unknown>): Promise<ToolResult> {
try {
this.validateParameters(params);
const normalizedParams = normalizeOptionalTrackIdParam(params);
this.validateParameters(normalizedParams);
const trackId = params.track_id as string | undefined;
const trackName = params.track_name as string | undefined;
const trackId = normalizedParams.track_id as string | undefined;
const trackName = normalizedParams.track_name as string | undefined;
if (!trackId && !trackName) {
return this.createErrorResult('Either track_id or track_name must be provided.');
+41
View File
@@ -68,6 +68,29 @@ describe('ReadMusicTool', () => {
expect(result.result).toContain('z4 | z4 | // No regions found');
});
it('reads a specific track when track_id is provided as a number', async () => {
const project = new KGProject('read-music-numeric-track-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
const leadTrack = buildTrack('Lead', 1, 0, 8);
const bassTrack = buildTrack('Bass', 2, 0, 8);
project.setTracks([leadTrack, bassTrack]);
vi.spyOn(KGCore, 'instance').mockReturnValue({
getCurrentProject: () => project,
} as unknown as KGCore);
const tool = new ReadMusicTool();
const result = await tool.execute({ track_id: 2, start: 0, length: 8 });
expect(result.success).toBe(true);
expect(result.result).toContain('track_id: 2');
expect(result.result).toContain('track_name: Bass');
expect(result.result).not.toContain('track_id: 1');
expect(tool.buildToolResultDisplayContent(
{ track_id: 2, start: 0, length: 8 },
{ success: true, result: 'raw result' },
)).toBe('Read track Bass from bars 1 to 2.');
});
it('returns a professional empty-project message when all MIDI tracks are empty', async () => {
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
const emptyTrack = new KGMidiTrack('Lead', 1);
@@ -87,6 +110,24 @@ describe('ReadMusicTool', () => {
expect(result.result).toBe('No musical content is present in the project yet.');
});
it('preserves the all-tracks behavior when track_id is "all"', async () => {
const project = new KGProject('read-music-all-track-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
const leadTrack = buildTrack('Lead', 1, 0, 8);
const bassTrack = buildTrack('Bass', 2, 0, 8);
project.setTracks([leadTrack, bassTrack]);
vi.spyOn(KGCore, 'instance').mockReturnValue({
getCurrentProject: () => project,
} as unknown as KGCore);
const tool = new ReadMusicTool();
const result = await tool.execute({ track_id: 'all', start: 0, length: 8 });
expect(result.success).toBe(true);
expect(result.result).toContain('track_id: 1');
expect(result.result).toContain('track_id: 2');
});
it('returns a professional empty-range message when the selected range has no MIDI notes', async () => {
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
const leadTrack = buildTrack('Lead', 1, 0, 8);
+11 -8
View File
@@ -6,6 +6,7 @@ import { KGMidiRegion } from '../../core/region/KGMidiRegion';
import { convertRegionToABCNotation } from '../../util/abcNotationUtil';
import { KEY_SIGNATURE_MAP } from '../../constants/coreConstants';
import { FLUIDR3_INSTRUMENT_MAP } from '../../constants/generalMidiConstants';
import { normalizeOptionalTrackIdParam } from './trackIdNormalization';
/**
* Tool for reading music content from the project
@@ -68,12 +69,13 @@ export class ReadMusicTool extends BaseTool {
async execute(params: Record<string, unknown>): Promise<ToolResult> {
try {
const normalizedParams = normalizeOptionalTrackIdParam(params);
// Validate parameters
this.validateParameters(params);
this.validateParameters(normalizedParams);
const trackId = params.track_id as string | undefined;
const startBeat = (params.start as number) || 0;
const length = params.length as number | undefined;
const trackId = normalizedParams.track_id as string | undefined;
const startBeat = (normalizedParams.start as number) || 0;
const length = normalizedParams.length as number | undefined;
const project = this.getCurrentProject();
const tracks = project.getTracks();
@@ -140,6 +142,7 @@ export class ReadMusicTool extends BaseTool {
startBar: number;
endBar: number;
} | null {
const normalizedArgs = normalizeOptionalTrackIdParam(args);
const project = this.getCurrentProject();
const tracks = project.getTracks();
if (tracks.length === 0) {
@@ -147,8 +150,8 @@ export class ReadMusicTool extends BaseTool {
}
const beatsPerBar = project.getTimeSignature().numerator;
const startBeat = (args.start as number) || 0;
const length = args.length as number | undefined;
const startBeat = (normalizedArgs.start as number) || 0;
const length = normalizedArgs.length as number | undefined;
if (startBeat < 0 || (length !== undefined && length <= 0)) {
return null;
}
@@ -157,9 +160,9 @@ export class ReadMusicTool extends BaseTool {
const rawEndBeat = length !== undefined ? startBeat + length : undefined;
const roundedEndBeat = rawEndBeat !== undefined
? Math.ceil(rawEndBeat / beatsPerBar) * beatsPerBar
: this.getTrackReadEndBeat(args, tracks, roundedStartBeat);
: this.getTrackReadEndBeat(normalizedArgs, tracks, roundedStartBeat);
const trackNames = this.resolveSummaryTrackNames(args, tracks);
const trackNames = this.resolveSummaryTrackNames(normalizedArgs, tracks);
if (trackNames.length === 0 || roundedEndBeat === undefined) {
return null;
}
+72
View File
@@ -82,6 +82,29 @@ describe('RemoveNotesTool', () => {
expect(regionB.getNotes()).toHaveLength(0);
});
it('removes notes across all MIDI regions on a track when track_id is numeric', async () => {
const track = new KGMidiTrack('Lead', 1);
const regionA = new KGMidiRegion('region-a', track.getId().toString(), track.getTrackIndex(), 'A', 0, 8);
const regionB = new KGMidiRegion('region-b', track.getId().toString(), track.getTrackIndex(), 'B', 8, 8);
regionA.setNotes([new KGMidiNote('note-1', 2, 3, 60, 100)]);
regionB.setNotes([new KGMidiNote('note-2', 2, 3, 64, 100)]);
track.setRegions([regionA, regionB]);
const project = new KGProject('numeric-track-remove-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
project.setTracks([track]);
mockCore(project);
const tool = new RemoveNotesTool();
const result = await tool.execute({
track_id: track.getId(),
start: 0,
end: 12,
});
expect(result.success).toBe(true);
expect(regionA.getNotes()).toHaveLength(0);
expect(regionB.getNotes()).toHaveLength(0);
});
it('removes notes across a track resolved by track_name when track_id is omitted', async () => {
const leadTrack = new KGMidiTrack('Lead', 1);
const bassTrack = new KGMidiTrack('Bass', 2);
@@ -133,6 +156,32 @@ describe('RemoveNotesTool', () => {
expect(bassRegion.getNotes()).toHaveLength(1);
});
it('uses numeric track_id when both track_id and track_name are provided', async () => {
const leadTrack = new KGMidiTrack('Lead', 1);
const bassTrack = new KGMidiTrack('Bass', 2);
const leadRegion = new KGMidiRegion('lead-region', leadTrack.getId().toString(), leadTrack.getTrackIndex(), 'Lead Region', 0, 8);
const bassRegion = new KGMidiRegion('bass-region', bassTrack.getId().toString(), bassTrack.getTrackIndex(), 'Bass Region', 0, 8);
leadRegion.setNotes([new KGMidiNote('lead-note', 1, 2, 60, 100)]);
bassRegion.setNotes([new KGMidiNote('bass-note', 1, 2, 48, 100)]);
leadTrack.setRegions([leadRegion]);
bassTrack.setRegions([bassRegion]);
const project = new KGProject('numeric-remove-track-id-precedence-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
project.setTracks([leadTrack, bassTrack]);
mockCore(project);
const tool = new RemoveNotesTool();
const result = await tool.execute({
track_id: leadTrack.getId(),
track_name: 'Bass',
start: 0,
end: 4,
});
expect(result.success).toBe(true);
expect(leadRegion.getNotes()).toHaveLength(0);
expect(bassRegion.getNotes()).toHaveLength(1);
});
it('uses the first matching track when duplicate track names exist', async () => {
const firstLead = new KGMidiTrack('Lead', 1);
const secondLead = new KGMidiTrack('Lead', 2);
@@ -158,6 +207,29 @@ describe('RemoveNotesTool', () => {
expect(secondRegion.getNotes()).toHaveLength(1);
});
it('builds confirmation and result summaries when track_id is numeric', () => {
const track = new KGMidiTrack('Lead', 1);
const region = new KGMidiRegion('region-1', track.getId().toString(), track.getTrackIndex(), 'Verse Melody', 0, 32);
region.setNotes([
new KGMidiNote('note-1', 16, 20, 60, 100),
new KGMidiNote('note-2', 20, 28, 64, 100),
]);
track.setRegions([region]);
const project = new KGProject('numeric-remove-summary-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
project.setTracks([track]);
mockCore(project);
const tool = new RemoveNotesTool();
const args = { track_id: track.getId(), start: 16, end: 24 };
expect(tool.buildConfirmationContent(args)).toBe(
'Allow removing 2 notes from beats 16-24, on track **Lead**, spanning bars 5 to 7?',
);
expect(tool.buildToolResultDisplayContent(args, { success: true, result: 'raw result' })).toBe(
'Successfully removed 2 notes from beats 16-24, on track **Lead**, spanning bars 5 to 7.',
);
});
it('returns distinct raw, history, and UI guidance when no MIDI target is available', async () => {
const project = new KGProject('no-target-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
mockCore(project);
+9 -6
View File
@@ -12,6 +12,7 @@ import { DeleteNotesCommand } from '../../core/commands/note/DeleteNotesCommand'
import { KGMidiNote } from '../../core/midi/KGMidiNote';
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
import { KGMidiTrack } from '../../core/track/KGMidiTrack';
import { normalizeOptionalTrackIdParam } from './trackIdNormalization';
interface RemoveTargetRegionContext {
region: KGMidiRegion;
@@ -106,12 +107,13 @@ export class RemoveNotesTool extends BaseTool {
async execute(params: Record<string, unknown>): Promise<ToolResult> {
try {
this.validateParameters(params);
const normalizedParams = normalizeOptionalTrackIdParam(params);
this.validateParameters(normalizedParams);
const startBeat = params.start as number;
const endBeat = params.end as number;
const trackId = params.track_id as string | undefined;
const trackName = params.track_name as string | undefined;
const startBeat = normalizedParams.start as number;
const endBeat = normalizedParams.end as number;
const trackId = normalizedParams.track_id as string | undefined;
const trackName = normalizedParams.track_name as string | undefined;
if (startBeat < 0) {
return this.createErrorResult(`Invalid start ${startBeat}. Must be >= 0.`);
@@ -159,7 +161,8 @@ export class RemoveNotesTool extends BaseTool {
}
private buildSummaryData(args: Record<string, unknown>): RemoveNotesSummaryData | null {
const typedArgs = args as {
const normalizedArgs = normalizeOptionalTrackIdParam(args);
const typedArgs = normalizedArgs as {
start?: number;
end?: number;
track_id?: string;
+42
View File
@@ -56,6 +56,29 @@ describe('UpdateTrackTool', () => {
);
});
it('renames a track by numeric track_id', async () => {
const track = new KGMidiTrack('Lead', 1, 'trumpet');
const project = new KGProject('rename-track-numeric-project');
project.setTracks([track]);
mockCore(project);
const tool = new UpdateTrackTool();
const result = await tool.execute({
track_id: 1,
new_track_name: 'Lead 2',
});
expect(result).toEqual({
success: true,
result: 'Track updated:\ntrack_id: 1\ntrack_name: Lead 2\ninstrument: Trumpet',
});
expect(track.getName()).toBe('Lead 2');
expect(tool.buildConfirmationContent({
track_id: 1,
new_track_name: 'Lead 2',
})).toBe('Allow updating track ID **1** to rename to **Lead 2**?');
});
it('updates a track instrument by track_name', async () => {
const track = new KGMidiTrack('Lead', 1, 'trumpet');
const project = new KGProject('instrument-track-project');
@@ -115,6 +138,25 @@ describe('UpdateTrackTool', () => {
expect(bassTrack.getName()).toBe('Bass 2');
});
it('uses numeric track_id when both track_id and track_name are provided', async () => {
const leadTrack = new KGMidiTrack('Lead', 1, 'trumpet');
const bassTrack = new KGMidiTrack('Bass', 2, 'acoustic_bass');
const project = new KGProject('numeric-track-id-precedence-project');
project.setTracks([leadTrack, bassTrack]);
mockCore(project);
const tool = new UpdateTrackTool();
const result = await tool.execute({
track_id: 2,
track_name: 'Lead',
new_track_name: 'Bass 2',
});
expect(result.success).toBe(true);
expect(leadTrack.getName()).toBe('Lead');
expect(bassTrack.getName()).toBe('Bass 2');
});
it('rejects duplicate track names when track_id is omitted', async () => {
const firstLead = new KGMidiTrack('Lead', 1, 'trumpet');
const secondLead = new KGMidiTrack('Lead', 2, 'flute');
+12 -9
View File
@@ -9,6 +9,7 @@ import {
resolveMidiTrackByExactName,
resolveMidiTrackByIdOrName,
} from './toolTargeting';
import { normalizeOptionalTrackIdParam } from './trackIdNormalization';
export class UpdateTrackTool extends BaseTool {
readonly name = 'update_track';
@@ -51,12 +52,13 @@ export class UpdateTrackTool extends BaseTool {
return undefined;
}
const normalizedArgs = normalizeOptionalTrackIdParam(args);
const normalizedInstrumentName = this.normalizeOptionalString(args.instrument);
const normalizedNewTrackName = this.normalizeOptionalString(args.new_track_name);
const targetLabel = typeof args.track_id === 'string'
? `track ID **${args.track_id}**`
: typeof args.track_name === 'string'
? `track **${args.track_name}**`
const targetLabel = typeof normalizedArgs.track_id === 'string'
? `track ID **${normalizedArgs.track_id}**`
: typeof normalizedArgs.track_name === 'string'
? `track **${normalizedArgs.track_name}**`
: null;
if (!targetLabel) {
return undefined;
@@ -98,12 +100,13 @@ export class UpdateTrackTool extends BaseTool {
async execute(params: Record<string, unknown>): Promise<ToolResult> {
try {
this.validateParameters(params);
const normalizedParams = normalizeOptionalTrackIdParam(params);
this.validateParameters(normalizedParams);
const trackId = params.track_id as string | undefined;
const trackName = params.track_name as string | undefined;
const instrumentName = this.normalizeOptionalString(params.instrument);
const newTrackName = this.normalizeOptionalString(params.new_track_name);
const trackId = normalizedParams.track_id as string | undefined;
const trackName = normalizedParams.track_name as string | undefined;
const instrumentName = this.normalizeOptionalString(normalizedParams.instrument);
const newTrackName = this.normalizeOptionalString(normalizedParams.new_track_name);
if (!trackId && !trackName) {
return this.createErrorResult('Either track_id or track_name must be provided.');
+16
View File
@@ -0,0 +1,16 @@
export function normalizeOptionalTrackIdParam(params: Record<string, unknown>): Record<string, unknown> {
const rawTrackId = params.track_id;
if (rawTrackId === undefined || rawTrackId === null || typeof rawTrackId === 'string') {
return params;
}
if (typeof rawTrackId === 'number' && Number.isFinite(rawTrackId) && Number.isInteger(rawTrackId)) {
return {
...params,
track_id: String(rawTrackId),
};
}
return params;
}