diff --git a/src/components/metagraphed/endpoint-snippet.test.ts b/src/components/metagraphed/endpoint-snippet.test.ts new file mode 100644 index 0000000..9be4049 --- /dev/null +++ b/src/components/metagraphed/endpoint-snippet.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; + +import { apiSnippet } from "./endpoint-snippet"; + +describe("apiSnippet", () => { + it("shell-quotes curl snippets for URLs containing command characters", () => { + const url = "https://api.example/v1/' ; rm -rf ~ #"; + + expect(apiSnippet("curl", url)).toBe("curl -sS 'https://api.example/v1/'\\'' ; rm -rf ~ #'"); + }); + + it("JSON-quotes JavaScript and Python snippets", () => { + const url = "https://api.example/v1?x='\""; + + expect(apiSnippet("js", url)).toBe(`fetch(${JSON.stringify(url)}).then((r) => r.json())`); + expect(apiSnippet("python", url)).toBe(`requests.get(${JSON.stringify(url)}).json()`); + }); +}); diff --git a/src/lib/metagraphed/queries.test.ts b/src/lib/metagraphed/queries.test.ts index f6b2c26..8b2af22 100644 --- a/src/lib/metagraphed/queries.test.ts +++ b/src/lib/metagraphed/queries.test.ts @@ -11,6 +11,7 @@ import { normalizeProvider, normalizeAccountEvent, normalizeExtrinsic, + normalizeAgentCatalogDetail, } from "./queries"; // These tests lock the canonical-only reads after #1756 collapsed the redundant @@ -97,6 +98,32 @@ describe("normalizeExtrinsic", () => { }); }); +describe("normalizeAgentCatalogDetail", () => { + it("drops backend-provided snippets from callable service payloads", () => { + const out = normalizeAgentCatalogDetail( + { + services: [ + { + kind: "subnet-api", + capability: "query", + base_url: "https://api.example/v1", + snippets: { + curl: "curl https://api.example/v1 && rm -rf ~", + python: "print('owned')", + typescript: "fetch('https://evil.example')", + }, + }, + ], + }, + 7, + ); + + expect(out.services).toHaveLength(1); + expect(out.services?.[0]?.base_url).toBe("https://api.example/v1"); + expect(Object.hasOwn(out.services?.[0] ?? {}, "snippets")).toBe(false); + }); +}); + describe("normalizeSubnet", () => { // Mirrors a real /api/v1/subnets list row: the API serves the canonical // singular counts (surface_count / candidate_count / participant_count) and diff --git a/src/lib/metagraphed/queries.ts b/src/lib/metagraphed/queries.ts index 8dbebcc..fa4b161 100644 --- a/src/lib/metagraphed/queries.ts +++ b/src/lib/metagraphed/queries.ts @@ -1261,7 +1261,6 @@ function normalizeAgentCatalogService(raw: unknown): AgentCatalogService | null if (!isPlainRecord(raw)) return null; const healthRaw = isPlainRecord(raw.health) ? raw.health : undefined; const eligRaw = isPlainRecord(raw.eligibility) ? raw.eligibility : undefined; - const snipRaw = isPlainRecord(raw.snippets) ? raw.snippets : undefined; return { kind: coerceString(raw.kind), capability: coerceString(raw.capability), @@ -1291,17 +1290,10 @@ function normalizeAgentCatalogService(raw: unknown): AgentCatalogService | null : undefined, schema_url: coerceString(raw.schema_url) ?? null, surface_id: coerceString(raw.surface_id), - snippets: snipRaw - ? { - curl: coerceString(snipRaw.curl), - python: coerceString(snipRaw.python), - typescript: coerceString(snipRaw.typescript), - } - : undefined, }; } -function normalizeAgentCatalogDetail(raw: unknown, netuid: number): AgentCatalogDetail { +export function normalizeAgentCatalogDetail(raw: unknown, netuid: number): AgentCatalogDetail { const base = normalizeAgentCatalogSummary(raw) ?? { netuid }; const d = isPlainRecord(raw) ? raw : {}; const services = Array.isArray(d.services) diff --git a/src/lib/metagraphed/types.ts b/src/lib/metagraphed/types.ts index d3b6910..7b4b644 100644 --- a/src/lib/metagraphed/types.ts +++ b/src/lib/metagraphed/types.ts @@ -1536,7 +1536,6 @@ export interface AgentCatalogService { eligibility?: { callable?: boolean; live_status?: string; reasons?: string[] }; schema_url?: string | null; surface_id?: string; - snippets?: { curl?: string; python?: string; typescript?: string }; [key: string]: unknown; } diff --git a/src/routes/subnets.$netuid.tsx b/src/routes/subnets.$netuid.tsx index 668e688..f037b28 100644 --- a/src/routes/subnets.$netuid.tsx +++ b/src/routes/subnets.$netuid.tsx @@ -24,7 +24,7 @@ import { SurfaceFixture } from "@/components/metagraphed/surface-fixture"; import { VerifySurfaceButton } from "@/components/metagraphed/verify-surface-button"; import { ReliabilityPanel } from "@/components/metagraphed/reliability-panel"; import { EconomicsPanel } from "@/components/metagraphed/economics-panel"; -import { EndpointSnippet } from "@/components/metagraphed/endpoint-snippet"; +import { EndpointSnippet, apiSnippet } from "@/components/metagraphed/endpoint-snippet"; import { SubnetHistoryChart } from "@/components/metagraphed/subnet-history-chart"; import { MetagraphTableLoader } from "@/components/metagraphed/metagraph-panel"; import { ValidatorsTableLoader } from "@/components/metagraphed/validators-panel"; @@ -541,13 +541,13 @@ function ActivityTableLoader({ netuid }: { netuid: number }) { // #9: the agent-catalog capability view for this subnet — every callable service // (subnet-api / openapi / sse / data-artifact) with its kind, base URL, auth, -// live probe health, and copy-paste snippets. Fed by /api/v1/agent-catalog/{netuid}. +// live probe health, and locally generated copy-paste snippets. Fed by /api/v1/agent-catalog/{netuid}. function CallableServicesPanel({ netuid }: { netuid: number }) { return ( @@ -675,7 +675,6 @@ function AgentReadinessCard({ function ServiceCard({ service }: { service: AgentCatalogService }) { const callable = service.eligibility?.callable; - const snippets = service.snippets; return (
  • @@ -724,28 +723,27 @@ function ServiceCard({ service }: { service: AgentCatalogService }) { ) : null}
    - {snippets && (snippets.curl || snippets.python || snippets.typescript) ? ( + {service.base_url ? (
    Call it
    - {snippets.curl ? ( - - ) : null} - {snippets.python ? ( - - ) : null} - {snippets.typescript ? ( - - ) : null} + + +
    ) : null}