diff --git a/.github/scripts/generate_sample_catalog.mjs b/.github/scripts/generate_sample_catalog.mjs index 8be4433..28c9f40 100644 --- a/.github/scripts/generate_sample_catalog.mjs +++ b/.github/scripts/generate_sample_catalog.mjs @@ -34,8 +34,17 @@ const OVERRIDES_PATH = join(REPO_ROOT, 'samples', 'hosted-agent', 'sample-overri const GITHUB_TOKEN = process.env.GITHUB_TOKEN || ''; -const LANGUAGES = ['python', 'csharp']; -const FRAMEWORKS = ['agent-framework', 'bring-your-own', 'langgraph']; +// Languages, frameworks, and protocols are discovered dynamically from the +// samples repo tree (see discoverLanguagesAndFrameworks / parseAgentYaml) +// instead of being restricted by an allowlist. These blacklists are the +// explicit escape hatch to exclude a specific discovered value; empty by +// default means "no restriction" — everything discovered is kept. +/** @type {string[]} */ +const BLOCKED_LANGUAGES = []; +/** @type {string[]} */ +const BLOCKED_FRAMEWORKS = []; +/** @type {string[]} */ +const BLOCKED_PROTOCOLS = []; /** @type {Record}>} */ const DIMENSION_DEFAULTS = { @@ -77,18 +86,17 @@ const TEMPLATE_SELECTION = { // Path segments must be alphanumeric, hyphens, underscores, or dots const SAFE_PATH_SEGMENT = /^[a-zA-Z0-9._-]+$/; -// Category segments allowed at the position immediately under a framework -// (`//...`). Upstream groups nested templates under these -// folders. This is an ALLOW-LIST: any other category (e.g. `a2a`, -// `invocations_ws`) is treated as not-yet-supported and dropped, so new -// upstream folders never auto-leak into the picker — opt them in here. Flat -// templates that sit directly under a framework (e.g. csharp -// `agent-framework/hello-world`) have no category segment and are always -// surfaced (see findTemplateDirsUnder). -const ALLOWED_CATEGORY_SEGMENTS = new Set(['responses', 'invocations', 'voicelive']); +// Category segments to EXCLUDE at the position immediately under a framework +// (`//...`). This is a BLACK-LIST: empty by default means +// every discovered category is surfaced; add a segment here to drop an upstream +// grouping you don't want in the picker yet. Flat templates that sit directly +// under a framework (e.g. csharp `agent-framework/hello-world`) have no category +// segment and are always surfaced (see findTemplateDirsUnder). +/** @type {Set} */ +const BLOCKED_CATEGORY_SEGMENTS = new Set(); // De-dupes the per-category "skipped" log line so an excluded category that -// spans many templates (e.g. `a2a`) is reported once, not once per template. +// spans many templates is reported once, not once per template. /** @type {Set} */ const skippedCategoriesLogged = new Set(); @@ -193,6 +201,47 @@ async function fetchRepoTree(ref) { return { tree, truncated: Boolean(data?.truncated) }; } +/** + * Discover the languages and frameworks present in the samples repo tree, + * then drop any listed in the (empty by default) BLOCKED_LANGUAGES / + * BLOCKED_FRAMEWORKS blacklists. A "language" is the path segment directly + * under `samples/` that owns a `hosted-agents/` folder; a "framework" is the + * segment directly under `hosted-agents/`. Discovering these dynamically — + * instead of hard-coding an allowlist — means new upstream languages or + * frameworks are picked up automatically, while the blacklists remain an + * explicit way to exclude a specific value. Hidden or unsafe segments (those + * failing isSafePathSegment, e.g. `.github`) are ignored. + * + * @param {Array<{path: string, type: string}>} tree + * @returns {{ languages: string[], frameworks: string[] }} + */ +function discoverLanguagesAndFrameworks(tree) { + /** @type {Set} */ + const languages = new Set(); + /** @type {Set} */ + const frameworks = new Set(); + const pattern = /^samples\/([^/]+)\/hosted-agents\/([^/]+)(?:\/|$)/; + + for (const entry of tree) { + const match = entry.path.match(pattern); + if (!match) { + continue; + } + const [, language, framework] = match; + if (isSafePathSegment(language) && !BLOCKED_LANGUAGES.includes(language)) { + languages.add(language); + } + if (isSafePathSegment(framework) && !BLOCKED_FRAMEWORKS.includes(framework)) { + frameworks.add(framework); + } + } + + return { + languages: [...languages].sort(), + frameworks: [...frameworks].sort(), + }; +} + /** * Find sample template directories under a `hosted-agents//` prefix. * A template is identified by the presence of an `agent.yaml`. When nested @@ -202,11 +251,11 @@ async function fetchRepoTree(ref) { * `.`, e.g. `.claude/skills`) are skipped, as are segments that fail the * `SAFE_PATH_SEGMENT` check. * - * Nested templates must live under an allow-listed category segment - * (`ALLOWED_CATEGORY_SEGMENTS`); flat templates directly under the framework - * (csharp's `agent-framework/