Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions domains/guardrailsai.com/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
"authStatus": "unknown",
"name": "Guardrails Server REST API",
"slug": "guardrails-server-rest-api",
"type": "http"
"type": "http",
"url": "http://localhost:8000"
},
{
"authStatus": "unknown",
"name": "Guardrails OpenAI-compatible endpoint",
"slug": "guardrails-openai-compatible-endpoint",
"type": "http"
"type": "http",
"url": "http://localhost:8000/guards/{guard_name}/openai/v1"
}
]
}
3 changes: 2 additions & 1 deletion domains/lakera.ai/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"authStatus": "none",
"name": "Lakera self-hosted Kubernetes probe endpoints",
"slug": "lakera-self-hosted-kubernetes-probe-endpoints",
"type": "http"
"type": "http",
"url": "http://<self-hosted-lakera-instance>:8000/{startupz,readyz,livez}"
},
{
"authStatus": "required",
Expand Down
3 changes: 2 additions & 1 deletion domains/portkey.ai/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"authStatus": "unknown",
"name": "Portkey Admin API",
"slug": "portkey-admin-api",
"type": "http"
"type": "http",
"url": "https://api.portkey.ai/v1/admin"
},
{
"authStatus": "required",
Expand Down
73 changes: 73 additions & 0 deletions src/lib/discover.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test";
import { PROBE_KEYS } from "./conventions.ts";
import { discover, slackMcpAppManifestUrl, type ChatFn, type WebBackend } from "./discover.ts";
import { locatorOf } from "../../worker/operations.ts";
import type { DetectionResult } from "./detect.ts";

const web: WebBackend = {
Expand Down Expand Up @@ -189,3 +190,75 @@ describe("discover MCP onboarding overrides", () => {
expect(setup).not.toContain("api.slack.com/apps?new_app=1");
});
});

describe("declared HTTP merge with discovered HTTP", () => {
test("merges declared and discovered HTTP surfaces when both point to the same base URL", async () => {
const chat: ChatFn = async () => ({
message: { role: "assistant", content: null },
toolCalls: [
{
id: "surface-1",
name: "record_surface",
arguments: {
name: "Buddy REST API",
type: "http",
url: "https://api.buddy.works",
spec: "https://api.buddy.works/openapi.json",
authStatus: "none",
publicEvidence: ["https://docs.buddy.works/reference"],
basis: { via: "discovered", evidence: ["https://docs.buddy.works/reference"] },
},
},
{
id: "finish-1",
name: "finish",
arguments: {
summary: "Buddy provides a REST API.",
description: "Buddy helps teams build software faster.",
},
},
],
});

const detect: DetectionResult = {
domain: "buddy.works",
found: [],
probed: [],
mcp: [],
errors: [],
integrationsJson: {
url: "https://buddy.works/.well-known/integrations.json",
result: {
version: 3,
surfaces: [
{
name: "Buddy REST API",
type: "http",
url: "https://api.buddy.works",
auth: { status: "none", basis: { via: "discovered", evidence: ["https://docs.buddy.works/reference"] } },
},
],
},
},
};

const result = await discover(detect.domain, detect, chat, web);
expect(result).not.toBeNull();
expect(result?.surfaces).toHaveLength(1);
expect(result?.surfaces[0]?.type).toBe("http");
expect(result?.surfaces[0]?.url).toBe("https://api.buddy.works");
});
});

describe("slug continuity locator strategy", () => {
test("uses URL when available for HTTP locator continuity", () => {
expect(
locatorOf({
slug: "buddy-rest-api",
type: "http",
url: "https://api.buddy.works",
spec: "https://api.buddy.works/openapi.json",
}),
).toBe("http|https://api.buddy.works");
});
});
24 changes: 20 additions & 4 deletions src/lib/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,15 @@ export interface DiscoveryResult {
surfaces: Surface[];
}

type SurfaceLocatorInput = {
type: string;
name?: string;
url?: string;
spec?: string;
command?: string;
packages?: readonly { identifier?: string }[];
};

// ── streamed events (partials emitted as findings are confirmed) ──────────────

export type DiscoverEvent =
Expand Down Expand Up @@ -420,7 +429,7 @@ export async function discover(
return `auth references undefined credential id(s): ${missing.join(", ")}. Call record_credential for each first (or use authStatus "unknown" if the credential isn't a real user-minted one), then re-record this surface.`;
}
}
const key = `${s.type}|${(s.spec || s.url || s.name).toLowerCase()}`;
const key = surfaceLocator(s);
if (surfaceKeys.has(key)) return `Already recorded ${s.name}.`;
surfaceKeys.add(key);
s.slug = assignSlug(s.name, surfaces);
Expand Down Expand Up @@ -736,9 +745,16 @@ function markDeclaredSurface(surface: Surface, source: string): Surface {
return surface;
}

function surfaceLocator(s: Surface): string {
const packageId = s.packages?.[0]?.identifier;
return `${s.type}|${(s.spec || s.url || s.command || packageId || s.name).toLowerCase()}`;
export function surfaceLocatorValue(s: SurfaceLocatorInput): string | undefined {
const packageId = s.packages?.[0]?.identifier ?? undefined;
if (s.type === "cli") return s.command ?? packageId;
if (s.type === "http" || s.type === "graphql") return s.url ?? s.spec;
return s.spec ?? s.url ?? s.command ?? packageId;
}

export function surfaceLocator(s: Surface): string {
const value = surfaceLocatorValue(s) ?? s.name ?? "unknown";
return `${s.type}|${value.toLowerCase()}`;
}

function entryKey(entry: AuthEntry): string {
Expand Down
4 changes: 2 additions & 2 deletions worker/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
import { Effect, Schema } from "effect";
import { detect } from "../src/lib/detect.ts";
import { discover, type ChatFn, type DiscoverEvent, type WebBackend } from "../src/lib/discover.ts";
import { discover, surfaceLocatorValue, type ChatFn, type DiscoverEvent, type WebBackend } from "../src/lib/discover.ts";
import { naiveWeb } from "../src/lib/contextdev.ts";
import { Credential, CredentialType, DISCOVERY_VERSION, Surface } from "../src/lib/discovery-schema.ts";
import { canonicalDomain } from "../src/lib/domain-aliases.ts";
Expand Down Expand Up @@ -114,7 +114,7 @@ type SlugSurface = {
};

export const locatorOf = (s: SlugSurface): string | undefined => {
const loc = s.type === "cli" ? (s.command ?? s.packages?.[0]?.identifier) : (s.url ?? s.spec);
const loc = surfaceLocatorValue(s);
return loc ? `${s.type}|${loc.toLowerCase()}` : undefined;
};

Expand Down