From 4f024a5dd1963d3db88625b2b51395018b29ca78 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Mon, 4 May 2026 07:52:04 -0400 Subject: [PATCH] feat(0.10.1): G2 capabilities filter helper + 5 new mocha tests * AgentsClient.filterAgentsByCapability(profiles, capability) returns the subset of profiles whose `capabilities` array contains the requested string (case-insensitive, whitespace-trimmed). Empty input returns [] rather than every profile so callers can distinguish "no filter" (don't call this) from "filter for empty value" (always empty). * 5 new mocha tests in src/test/agents-client.test.ts cover the helper end-to-end: matching, case-insensitivity, empty handling, no-match, and immutability. Total mocha now 144 passing. --- src/AgentsClient.ts | 17 ++++++++++++ src/test/agents-client.test.ts | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/AgentsClient.ts b/src/AgentsClient.ts index f2d5e1c..7fdda2f 100644 --- a/src/AgentsClient.ts +++ b/src/AgentsClient.ts @@ -82,6 +82,23 @@ export function applyAgentArg(args: string[], profileId: string | undefined): st return [...args]; } +/** + * G2 — return the subset of profiles whose `capabilities` array contains + * `capability` (case-insensitive, whitespace-trimmed). An empty + * `capability` returns `[]` so callers can distinguish "no filter" + * (don't call this) from "filter for an empty value" (always empty). + */ +export function filterAgentsByCapability( + profiles: AgentProfileRecord[], + capability: string, +): AgentProfileRecord[] { + const needle = (capability || '').trim().toLowerCase(); + if (!needle) { return []; } + return profiles.filter((p) => + p.capabilities.some((c) => String(c).trim().toLowerCase() === needle), + ); +} + export function listAgents(execPath: string, projectDir?: string): AgentsListResult { try { const cwdArgs = projectDir ? ['--project-dir', projectDir] : []; diff --git a/src/test/agents-client.test.ts b/src/test/agents-client.test.ts index 446a5b1..493b47a 100644 --- a/src/test/agents-client.test.ts +++ b/src/test/agents-client.test.ts @@ -11,6 +11,7 @@ import * as assert from 'assert'; import { applyAgentArg, + filterAgentsByCapability, parseAgentsList, } from '../AgentsClient'; @@ -204,3 +205,52 @@ suite('AgentsClient — applyAgentArg', () => { assert.deepStrictEqual(base, ['run']); }); }); + +suite('AgentsClient — filterAgentsByCapability', () => { + const profiles = [ + { + id: 'coder', role: 'coder', provider: 'anthropic', model: 'claude-sonnet-4-5', + endpoint_id: '', prompt_prefix: '', + capabilities: ['code', 'function-calling'], fallback_chain: [], created_at: '', + }, + { + id: 'researcher', role: 'researcher', provider: 'gemini', model: 'gemini-3-pro', + endpoint_id: '', prompt_prefix: '', + capabilities: ['search', 'long-context', 'mcp'], fallback_chain: [], created_at: '', + }, + { + id: 'classifier', role: 'classifier', provider: 'anthropic', model: 'claude-haiku-4-5', + endpoint_id: '', prompt_prefix: '', + capabilities: ['fast', 'classification'], fallback_chain: [], created_at: '', + }, + ]; + + test('returns matching profiles when capability is present', () => { + const out = filterAgentsByCapability(profiles, 'mcp'); + assert.strictEqual(out.length, 1); + assert.strictEqual(out[0].id, 'researcher'); + }); + + test('matching is case-insensitive and whitespace-trimmed', () => { + assert.strictEqual(filterAgentsByCapability(profiles, ' CODE ').length, 1); + assert.strictEqual(filterAgentsByCapability(profiles, 'Mcp').length, 1); + }); + + test('returns [] for an empty capability rather than every profile', () => { + assert.deepStrictEqual(filterAgentsByCapability(profiles, ''), []); + assert.deepStrictEqual(filterAgentsByCapability(profiles, ' '), []); + }); + + test('returns [] when nothing matches', () => { + assert.deepStrictEqual( + filterAgentsByCapability(profiles, 'no-such-capability'), + [], + ); + }); + + test('does not mutate the input array', () => { + const before = profiles.map((p) => p.id).join(','); + filterAgentsByCapability(profiles, 'code'); + assert.strictEqual(profiles.map((p) => p.id).join(','), before); + }); +});