import { e as EvalVerdictDecision, U as UserValueStage, F as FailureCategory } from './index-fZyfLCHE.js'; import { S as ScoreResult, E as EvaluationConfigSnapshot, c as StageResultRow, d as EvalRunDecisionSummary } from './decision-summary-CksWu77D.js'; import { S as ServerDoctorResult } from './server-doctor-core-DFaTigIr.js'; type SdkErrorOptions = { cause?: unknown; }; declare class SdkError extends Error { readonly code: string; constructor(message: string, code: string, options?: SdkErrorOptions); } type EvalReportingErrorOptions = SdkErrorOptions & { statusCode?: number; endpoint?: string; attemptCount?: number; isBillingLimitReached?: boolean; isReportingBackendIncompatible?: boolean; }; declare class EvalReportingError extends SdkError { readonly statusCode?: number; readonly endpoint?: string; readonly attemptCount?: number; readonly isBillingLimitReached: boolean; /** * The destination refused the report because it does not understand a field * this SDK sends — a reporting backend older than the SDK's minimum contract. * * Flagged separately from every other rejection because the fix is different * in kind: nothing about the run or the payload is wrong, so retrying, editing * the suite, or reading it as a failed eval are all the wrong move. The only * fix is upgrading the destination (or pointing `baseUrl` at one that is). */ readonly isReportingBackendIncompatible: boolean; constructor(message: string, options?: EvalReportingErrorOptions); } /** * App permalinks for durable platform resources. * * The problem this solves: a tool result that hands back `{ id: "p170b5c…" }` * and nothing else forces the model to invent a URL. It invents * `https://app.mcpjam.com/servers`, which opens whatever project the * RECIPIENT's local storage last selected — so a link about project Demo * silently renders project Default's servers. A permalink minted here names * the project in the URL (`?project=`), so opening it selects the * resource's project and organization before rendering anything. * * Two rules keep this module honest: * * 1. **Pure.** No ambient environment read, no `window`, no server config, * no network. `@mcpjam/sdk/platform` is runtime-agnostic — it runs in the MCP worker * (Cloudflare), the CLI (Node), the hosted route, and the browser — so * the app origin is an explicit argument and every adapter reads its own * environment. * 2. **One table.** `PlatformResourceType` is INFERRED from * `PLATFORM_PERMALINK_ROUTES`, so a resource type cannot exist without a * route and a route cannot exist for a type nothing declares. A * hand-written union next to the table would drift the first time * someone added one and not the other. * * A permalink is a navigation aid, never an authorization grant: it is * intentionally model-visible and shareable with anyone who already has * access to the same MCPJam resource, and it opens the ordinary * access-controlled page. Token-bearing guest URLs (User Testing share * links) are a different, backend-minted product capability and are not * permalinks — see `PlatformNoPermalinkReason.external-resource` and the * share-link operations, which stay outside this selector. */ /** * The query parameter that names a permalink's project. * * Lives here rather than in the client because BOTH ends need it and the * dependency only runs one way: the client's `project-deep-link.ts` (which * consumes the parameter) imports this constant, and no SDK module may * import app code. */ declare const PROJECT_DEEP_LINK_PARAM = "project"; /** * The hosted app origin, for adapters with no configured override. * * A neutral default constant, NOT an ambient read: nothing in this module * reaches for it on its own. Adapters pass it (or their own configured * origin) explicitly, which is what keeps a staging deployment from minting * production links. */ declare const DEFAULT_MCPJAM_APP_ORIGIN = "https://app.mcpjam.com"; /** A reference to a resource, before it becomes a URL. */ interface PlatformResourceRef { type: PlatformResourceType; id: string; /** * The project the resource belongs to. Rows of a cross-project listing * (`list_projects`) carry their own; everything else usually inherits the * operation's resolved-scope receipt. */ projectId?: string; /** * The resource whose route this one nests under — an eval case and an * eval run are both addressed through their suite. Required for the types * that declare `parent`; supplying the wrong type is an error, not a * silent fallback to the collection. */ parent?: { type: PlatformResourceType; id: string; }; /** Overrides the route's default label ("View run", "Open suite", …). */ label?: string; } /** A durable, human-openable app URL for one resource. */ interface PlatformPermalink { /** App-relative path, including the query. */ path: string; /** Absolute URL for the same target. */ url: string; /** Short imperative label a surface can render as link text. */ label: string; /** Correlates the permalink with the row it describes, without array order. */ resource: { type: PlatformResourceType; id: string; }; /** Present whenever the route carries `?project=`. */ projectId?: string; } /** * What a policy knows at derivation time. * * `resolvedScope` is the RECEIPT an operation fired while resolving its * project selector (see `PlatformOperationContext.onScopeResolved`). MCP and * generic CLI callers usually pass a project NAME or nothing at all, so the * project id exists only after `execute` has run — assuming one up front * would mint a link to whatever project the adapter guessed. */ interface PlatformPermalinkContext { appOrigin: string; resolvedScope?: { projectId: string; organizationId?: string; }; } /** Why an operation returns no permalink. Typed, so it is reviewable. */ type PlatformNoPermalinkReason = /** The result names nothing durable — a probe, a report, a capability set. */ "no-addressable-resource" /** The result is a receipt for an effect: a cancel, a delete, a dismiss. */ | "mutation-only" /** The result belongs to a third party, not to an MCPJam app page. */ | "external-resource" /** A durable MCPJam resource with no exact route yet. Tracked debt. */ | "route-not-addressable"; /** * How one operation produces permalinks. * * Discriminated rather than "two optional callbacks": with optional fields * the honest answer ("this operation has nothing to link to") and the * unreviewed answer ("nobody looked at this one") are the same absence. Here * every operation states which it is, and `PlatformOperation` requires the * field, so a new operation cannot be added without the decision being made. */ type PlatformPermalinkPolicy = { kind: "derive"; /** The durable resources this result referenced. Order is not meaningful. */ resources(result: TOutput, input: TInput, context: PlatformPermalinkContext): PlatformResourceRef[]; } | { kind: "response"; /** * Permalinks the BACKEND minted and the result already carries — session * search is the standing example. The backend owns the fallback rules * for a session whose surface-native target does not exist, and * re-deriving them here would be a second, drifting copy. */ permalinks(result: TOutput, input: TInput, context: PlatformPermalinkContext): PlatformPermalink[]; } | { kind: "none"; reason: PlatformNoPermalinkReason; /** Required for `route-not-addressable`: name the missing route. */ note?: string; }; /** * Resource type → app route. The single source of truth for both. * * Routes here are EXACT resource addresses, never a collection standing in * for one. Where an exact route does not exist yet the operation says * `route-not-addressable` and (optionally) returns the collection under its * own honest label — "Open environments", not "the environment's permalink". */ declare const PLATFORM_PERMALINK_ROUTES: { /** * A project's own landing page. `/servers` rather than `/home`: Connect is * where the project's work starts, and it is the screen every other * project route falls back to. */ readonly project: { readonly label: "Open project"; readonly segments: readonly ["servers"]; }; /** One saved MCP server, with its detail expanded. */ readonly project_server: { readonly label: "Open server"; readonly segments: readonly ["servers", ":id"]; }; /** One named environment. */ readonly project_environment: { readonly label: "Open environment"; readonly segments: readonly ["environments", ":id"]; }; /** One installed agent plugin, expanded inside Connect. */ readonly project_plugin: { readonly label: "Open plugin"; readonly segments: readonly ["servers", "plugins", ":id"]; }; /** A modelled MCP host (Claude, ChatGPT, Cursor…) and its canvas. */ readonly host: { readonly label: "Open host"; readonly segments: readonly ["hosts", ":id"]; }; readonly eval_suite: { readonly label: "Open suite"; readonly segments: readonly ["evals", "suite", ":id"]; }; /** One test case inside its suite. */ readonly eval_case: { readonly label: "Open test case"; readonly segments: readonly ["evals", "suite", ":parent", "test", ":id"]; readonly parent: "eval_suite"; }; /** One finished or in-flight run of a suite. */ readonly eval_run: { readonly label: "View run"; readonly segments: readonly ["evals", "suite", ":parent", "runs", ":id"]; readonly parent: "eval_suite"; }; /** * A grouped launch (one suite fanned across several targets). * * Deliberately the suite's RUNS lens rather than one member run: linking to * a single run of the group would hide a sibling's failure, which is the * one thing whoever approved N paid runs most needs to see. */ readonly eval_run_group: { readonly label: "View runs"; readonly segments: readonly ["evals", "suite", ":parent"]; readonly query: { readonly view: "runs"; }; readonly parent: "eval_suite"; }; /** * One conversation, on the cross-surface Sessions feed. * * The feed loads a session by id alone, which is what lets it serve as the * universal target for a session whose surface-native page does not exist * (an eval Quick Run, a session whose parent run was deleted). */ readonly chat_session: { readonly label: "Open session"; readonly segments: readonly ["sessions"]; readonly idParam: "session"; }; /** One conformance run's report. */ readonly conformance_run: { readonly label: "View conformance run"; readonly segments: readonly ["conformance", "runs", ":id"]; }; /** * One launched wave. * * `/swarms/` with the run id as the FIRST segment after `/swarms/`: * the client routes on that segment, so `/swarms/runs/` would resolve * to a run named literally "runs" and dead-link the recipient. * * NOTE the asymmetry with the saved swarm DEFINITION, which deliberately has * no entry here. `:swarmId` reads as a launched wave — `SwarmRunDetail` * resolves it against the project's runs — so a saved swarm's id on this * route renders an empty run detail. The two share a path shape and mean * different things, which is exactly the confusion the registry exists to * settle in one place. */ readonly journey_run: { readonly label: "View swarm run"; readonly segments: readonly ["swarms", ":id"]; }; /** One User Testing scenario's detail. */ readonly user_testing_scenario: { readonly label: "Open scenario"; readonly segments: readonly ["user-testing", ":id"]; }; /** * An organization's settings. The one type above project scope, so it * carries no `?project=` — adding one would switch the viewer's project as * a side effect of opening an org page. */ readonly organization: { readonly label: "Open organization"; readonly segments: readonly ["organizations", ":id"]; readonly projectScoped: false; }; }; /** Every resource type that has an app route. Inferred, never restated. */ type PlatformResourceType = keyof typeof PLATFORM_PERMALINK_ROUTES; /** Runtime membership test for a resource type, for adapters reading wire data. */ declare function isPlatformResourceType(value: string): value is PlatformResourceType; /** * Raised when a ref cannot become an exact URL — a missing parent, a missing * project id, an origin that is not an app origin. * * A THROW rather than a silent `undefined`, so a wrong mapping fails in the * unit tests that call this directly. Adapters, which must never fail an * operation over a link, catch it and omit the permalink. */ declare class PlatformPermalinkError extends Error { constructor(message: string); } /** * Build one resource's permalink. * * Uses `URL`/`URLSearchParams` throughout rather than string concatenation: * the concatenated builders this replaces each had to remember to encode * their own segments and to notice whether the route already had a `?`, and * the grouped-eval URL (`…?view=runs` plus `?project=`) is exactly where a * hand-assembled one grows a second question mark. */ declare function buildAppPermalink(resource: PlatformResourceRef, options: { appOrigin: string; }): PlatformPermalink; /** * Resolve a ref's project id from the ref itself, then the operation's * resolved-scope receipt. * * Ref first: a cross-project listing's rows each name their own project, and * the receipt (one project, whichever the operation resolved) would relabel * every row with it. * * The receipt is the fallback rather than the source precisely because a * policy reads its project off the RESULT, and a result shape can change * under it. When it does — a field renamed, an older backend omitting the * echo — the receipt still knows which project the operation resolved, so the * link degrades to correct instead of to absent. */ declare function permalinkProjectId(resource: PlatformResourceRef, context: PlatformPermalinkContext): string | undefined; /** * Build permalinks for a batch of refs, filling each one's project id from * the receipt when the ref does not carry its own. */ declare function buildAppPermalinks(resources: readonly PlatformResourceRef[], context: PlatformPermalinkContext): PlatformPermalink[]; /** Declare the durable resources an operation's result referenced. */ declare function derivePermalinks(resources: (result: TOutput, input: TInput, context: PlatformPermalinkContext) => PlatformResourceRef[]): PlatformPermalinkPolicy; /** Declare that the backend already minted this operation's permalinks. */ declare function responsePermalinks(permalinks: (result: TOutput, input: TInput, context: PlatformPermalinkContext) => PlatformPermalink[]): PlatformPermalinkPolicy; /** * Declare, with a reason, that an operation has nothing to link to. * * `route-not-addressable` REQUIRES a note naming the missing route, and the * coverage test keeps those on a short named allowlist: it is tracked debt * with a route owed, not a catch-all for "did not want to think about it". */ declare function noPermalink(reason: PlatformNoPermalinkReason, note?: string): PlatformPermalinkPolicy; /** The part of an operation context this helper needs. */ interface PermalinkScopeReceiver { onScopeResolved?: (scope: { projectId: string; organizationId?: string; }) => void; } /** The part of an operation this helper needs. Structural, so this module * never imports the 11k-line catalog just to name a type. */ interface PermalinkAwareOperation { name: string; permalink: PlatformPermalinkPolicy; execute(input: TInput, context: TContext): Promise; } /** * Apply an operation's permalink policy to a result it already produced. * * NEVER throws. A surface must not fail an operation that succeeded because a * link could not be composed — a missing parent or an unscoped row is a bug in * the policy, and the place it should fail loudly is the unit tests that call * `buildAppPermalink` directly. Here it drops the link and reports through * `onError` so an adapter can log it. */ declare function derivePermalinksFor(operation: PermalinkAwareOperation, result: TOutput, input: TInput, context: PlatformPermalinkContext, onError?: (error: unknown, operationName: string) => void): PlatformPermalink[]; /** * Execute an operation, capture the project it resolved, and derive its * permalinks from the RAW result. * * Raw is the operative word: surfaces reshape results (the MCP worker tags * widget payloads, the CLI compacts them), and a policy reading a reshaped * result would be reading a shape it was never written against. Deriving here, * between `execute` and any transform, is what keeps the policies honest about * one input shape. */ declare function runOperationWithPermalinks(operation: PermalinkAwareOperation, input: TInput, context: TContext, options: { appOrigin: string; onError?: (error: unknown, operationName: string) => void; }): Promise<{ result: TOutput; permalinks: PlatformPermalink[]; }>; /** * The adapter envelope: the operation's own payload plus its permalinks. * * An ADAPTER shape, not a DTO change — direct SDK callers keep the exact * return types they compile against today. Spreading is what makes it * non-breaking for the object-shaped results (all of them, currently), and a * scalar or array is nested under `result` instead of being spread into * numeric keys. */ declare function withPermalinkEnvelope(result: TOutput, permalinks: PlatformPermalink[]): Record; /** * One concise line per permalink, for surfaces whose rendering of structured * output cannot be relied on. * * Deliberately terse and capped: the permalink is intentionally model-visible, * but a list operation at its page limit would otherwise spend a large part of * a tool result on URLs. */ declare function formatPermalinkLines(permalinks: readonly PlatformPermalink[], options?: { limit?: number; }): string; /** * Wire DTOs for the MCPJam Platform API (`/api/v1`). * * These mirror the public projections documented in the repo OpenAPI spec * (`docs/reference/openapi.json`) and emitted by the Convex catalog reads * (`mcpjam-backend/convex/publicApi/dtos.ts`). Write tolerant readers: * additive fields are non-breaking and must be ignored, never relied on * being absent. */ /** * Response of * `GET /projects/{p}/eval-runs/{runId}/decision-summary` — the canonical, * versioned run decision contract. * * An ALIAS, not a second declaration. The shape is owned by * `@mcpjam/sdk/contract` (`evalRunDecisionSummarySchema`), which is what makes * the API's response and a client-side assembly the same object rather than two * hand-mirrored descriptions of one; re-declaring it here as an interface would * recreate exactly the drift this lane removed. */ type PlatformEvalRunDecisionSummary = EvalRunDecisionSummary; /** Collection envelope: `nextCursor` is omitted on the last page. */ type PlatformPage = { items: TItem[]; nextCursor?: string; }; interface PlatformMe { id: string; email: string; name: string; imageUrl: string | null; profilePictureUrl: string | null; plan: string | null; createdAt: number | null; updatedAt: number | null; } /** * An organization the caller belongs to — the ids `list_projects` and * `create_project` take as `organizationId`. * * Deliberately thin. The backing query is the browser app shell's, so it * carries billing and Stripe fields this transport DTO drops: an organization * on the machine surfaces is a SCOPE (an id, a name, and enough context to * pick between two of them), not an account-management object. */ interface PlatformOrganization { id: string; name: string; /** Billing plan slug (`free` / `team` / `enterprise`) when resolved. */ plan: string | null; /** Caller's role in the organization (`owner` / `admin` / `member`). */ myRole: string | null; /** Whether the caller created the organization. */ isCreator: boolean; logoUrl: string | null; createdAt: number | null; } /** A hosted model catalog entry. Unknown additive fields are tolerated. */ interface PlatformModel { id: string; name?: string; provider?: string; [field: string]: unknown; } interface PlatformProject { id: string; name: string; description: string | null; icon: string | null; organizationId: string | null; visibility: string | null; /** Caller's role on the project when the upstream query resolves one. */ role?: string; createdAt: number | null; updatedAt: number | null; } interface PlatformCatalogOauthProbe { probedAt?: number; endpointUrl?: string; outcome?: string; supportsDcr?: boolean; supportsCimd?: boolean; authorizationServerUrl?: string; } /** * Scraped directory row — allowlisted; unknown upstream fields are dropped. * * The nullable fields match the wire: the backend DTO * (`mcpjam-backend/convex/publicApi/dtos.ts::toCatalogServerDto`) emits * `null` for an absent value on these, never omits them. */ interface PlatformCatalogServer { id: string; source: string; serverName: string; displayName?: string; description?: string | null; rowType?: string; verifiedTier?: string | null; authPosture?: string | null; unavailableReason?: string | null; endpointKind?: string; remoteUrl?: string; remoteUrlOptions?: string[]; remoteUrlRegex?: string; remoteUrlHint?: string; latestContentHash?: string | null; oauthProbe?: PlatformCatalogOauthProbe; } /** * The directory search page, plus the server's echo of which mode actually * ran: `"search"` when a non-blank `q` selected text search, `"browse"` for * the plain listing. Optional so a tolerant reader survives a backend that * predates the marker. */ type PlatformDirectorySearchPage = PlatformPage & { mode?: "search" | "browse"; }; interface PlatformCatalogSourceStatus { source: string; lastSyncedAt?: number | null; liveCount?: number | null; upstreamFetchedAt?: number | null; } interface PlatformRegistryServerTransport { transportType?: string; url?: string | null; useOAuth?: boolean; hasOAuthConfig?: boolean; oauthScopes?: string[]; } interface PlatformRegistryServer { id: string; scope: "global" | "organization"; name: string; displayName?: string; description?: string | null; category?: string | null; tags?: string[]; publisher?: string | null; status?: string; updatedAt?: number | null; transport?: PlatformRegistryServerTransport; } interface PlatformRegistryConnection { id: string; kind: "registry" | "catalog"; scope?: "global" | "organization"; projectId: string | null; serverId: string; serverName?: string | null; registryServerId?: string; catalogServerId?: string; endpointUrl?: string; endpointKind?: string; connectedAt?: number | null; } interface PlatformRegistryInstall { serverId: string; serverName: string; outcome: "created" | "reconnected"; } interface PlatformRegistryInstallNextSteps { connectionStatusOp: "get_project_server_connection_status"; connectLinkUrl?: string; /** * Present when an OAuth install could not mint its browser connect-link. * The install itself succeeded; the caller starts connect_project_server * themselves instead of waiting for a link that is not coming. */ connectLinkError?: string; } interface PlatformRegistryInstallResult extends PlatformRegistryInstall { nextSteps: PlatformRegistryInstallNextSteps; } interface PlatformProjectServer { id: string; projectId: string | null; name: string; enabled: boolean; transportType: string; /** Endpoint for HTTP-transport servers; null for stdio. */ url: string | null; useOAuth: boolean; hasClientSecret: boolean; oauthScopes?: string[]; createdAt: number | null; updatedAt: number | null; } interface PlatformEvalRunSummary { id: string | null; status: string | null; passRate: number | null; passed: number | null; failed: number | null; createdAt: number | null; } interface PlatformEvalSuite { id: string; name: string | null; projectId: string | null; createdAt: number | null; updatedAt: number | null; latestRun: PlatformEvalRunSummary | null; totals: { passed: number; failed: number; runs: number; }; passRateTrend: number[]; } interface PlatformChatSession { id: string; title: string | null; status: string | null; projectId: string | null; /** "private" | "project". */ visibility: string | null; lastActivityAt: number | null; createdAt: number | null; isPinned?: boolean; isUnread?: boolean; } /** * Tool-effects policy for an agent Playground turn. * * `read_only` advertises only tools the server annotated * `annotations.readOnlyHint === true`; `auto` advertises everything the target * exposes and may therefore cause real external side effects through arbitrary * third-party tools. The hint is SERVER-ASSERTED, so `read_only` is a policy * the host applies, not a guarantee it can verify. */ type PlatformToolMode = "read_only" | "auto"; /** * One tool call as the agent Playground reports it. * * `input`/`output` are the RAW wire values — scrubbed of protocol annotations * (`_meta`, `$`-prefixed keys) and bounded, with `truncated` set whenever the * caller is seeing less than the whole payload. That bounding is announced * rather than silent because a shortened tool result an agent believes is * complete sends it debugging the wrong thing. */ interface PlatformTurnToolCall { toolCallId: string; toolName: string; input: unknown; status: "ok" | "error"; output?: unknown; errorMessage?: string; truncated?: true; } /** One turn's execution trace, in the same span shape eval iterations use. */ interface PlatformTurnTrace { turnId: string; spanCount: number; spans: unknown[]; } /** Token usage for one turn. */ interface PlatformTurnUsage { inputTokens: number; outputTokens: number; totalTokens: number; } /** * The result of one agent Playground turn. * * `sessionId` is the ONE public id — pass it back to continue the * conversation, and to `getChatSession` / `getChatSessionTrace` to read what * happened. It is `null` only when the turn ran but its transcript did not * persist, which `persisted.outcome` reports: a caller must not treat that as * "nothing happened", because the turn already spent. */ interface PlatformChatTurn { sessionId: string | null; turnId: string; /** * The project this turn ran in. * * A CONTINUATION does not send one — it is read off the session row — so * without this a caller holding only the turn cannot say where the session * lives, and the session permalink cannot be composed. That is not * hypothetical: it is the one operation whose scope is never resolved * locally, so nothing else in the response or the context carries it. */ projectId: string; reply?: string; finishReason?: string | null; toolCalls?: PlatformTurnToolCall[]; trace?: PlatformTurnTrace; usage?: PlatformTurnUsage; model?: { id: string; provider: string; }; toolMode?: PlatformToolMode; advertisedToolCount?: number; excludedToolCount?: number; persisted: { outcome: string; version?: number; }; origin: string; /** Set when an idempotencyKey replayed an already-completed turn. */ replay?: true; message?: string; } /** One message from a session transcript, at its ABSOLUTE transcript index. */ interface PlatformChatMessage { /** * Position in the STORED transcript, not in the returned page. Trace spans * reference messages positionally, so renumbering per page would break the * one join the detail read exists to enable. */ index: number; role: string; content: unknown; truncated?: true; } /** Session metadata plus a bounded window of raw messages. */ interface PlatformChatSessionDetail { sessionId: string; projectId: string | null; origin: string | null; modelId: string | null; version: number | null; startedAt: number | null; lastActivityAt: number | null; toolMode: PlatformToolMode | null; environmentId: string | null; /** `null` — never 0 — when the transcript could not be read. */ messageCount: number | null; transcriptUnavailable?: true; messages: PlatformChatMessage[]; nextMessageIndex?: number; } /** One turn's entry in a trace read. */ interface PlatformChatSessionTraceTurn { turnId: string; promptIndex: number; startedAt: number; endedAt: number; finishReason?: string; modelId?: string; usage?: PlatformTurnUsage; spanCount: number; spans?: unknown[]; /** * The spans could not be read. DISTINCT from an empty `spans` array, which * means the turn genuinely made no recorded calls — the two lead to opposite * conclusions about a turn. */ spansUnavailable?: true; /** Fewer spans came back than the turn recorded. */ spansTruncated?: true; } interface PlatformChatSessionTrace { sessionId: string; origin: string | null; traceVersion: number; turnCount: number; turns: PlatformChatSessionTraceTurn[]; latestPromptIndex?: number; } /** One interactive element in a widget snapshot, ready to use as a step target. */ interface PlatformSnapshotElement { role?: { role: string; name?: string; }; testId?: string; text?: string; /** More than one element matched — pass `nth` on a step target to pick one. */ ambiguous?: true; } /** * A rendered MCP App widget as TEXT. * * The point is that it is ACTIONABLE, not merely descriptive: the elements come * back in the same role/name/testId vocabulary the interaction steps accept, so * a caller reads a control here and addresses it directly. */ interface PlatformWidgetSnapshot { mode: "a11y"; tree: string; elements: PlatformSnapshotElement[]; truncated?: true; capturedAt: number; note?: string; } /** The verdict and evidence from one headless widget render. */ interface PlatformWidgetRender { status: string; resourceUri?: string; bridgeInitialized?: boolean; /** * What the widget logged, and what it was blocked from reaching. Both matter * more than they look: a widget that "renders" while every fetch is blocked * photographs perfectly and is broken. */ consoleErrors?: string[]; blockedRequests?: string[]; snapshot?: PlatformWidgetSnapshot; /** Present only when `includeScreenshot` was explicitly requested. */ screenshot?: { mimeType: string; base64: string; }; timings?: { renderMs?: number; totalMs?: number; }; } /** * Which session surface a row came from. Open-ended on the wire: switch on it * and tolerate an unknown value rather than assuming this list is closed. */ type PlatformSessionSourceType = "direct" | "scenario" | "eval" | "swarm"; /** The session's parent run, discriminated on `kind`. Also open-ended. */ interface PlatformSessionParentRef { kind: "evalRun" | "journeyRun" | "scenario"; /** Human-readable parent name; null when the parent row is gone. */ label: string | null; iterationId?: string; /** eval only; null means Quick Run (no suite run exists). */ suiteRunId?: string | null; suiteId?: string | null; journeyRunId?: string; journeyRefId?: string | null; scenarioId?: string; } /** * Where a human goes to read a session. Always present. * * A PROJECTION of `PlatformPermalink`, not a widening of it: the wire * contract for `/v1/sessions` rows is exactly `{path, url}` today, and adding * `label`/`resource` as REQUIRED fields would make every older backend's * response fail a client that trusted the type. Deriving it from * `PlatformPermalink` instead of restating the two fields is what stops the * shared permalink shape and the session wire shape from drifting apart — * rename `path` there and this stops compiling here. * * The backend may later add `label`/`resource` as OPTIONAL fields without * breaking a client built against this. */ type PlatformSessionLink = Pick; /** * One row of the unified, cross-surface sessions feed * (`GET /projects/{projectId}/sessions`). * * Distinct from `PlatformChatSession`, which is the Playground-only projection * behind the older `/chat-sessions` route: this one spans every surface, * carries a typed parent reference, and pages on an opaque cursor. */ interface PlatformSessionSummary { id: string; chatSessionId: string; projectId: string | null; sourceType: PlatformSessionSourceType; origin: string | null; status: string; synthetic: boolean; lockReason: string | null; title: string | null; firstMessagePreview: string; /** Direct sessions only: "private" | "project". null elsewhere. */ visibility: string | null; ownedByViewer: boolean; startedAt: number; lastActivityAt: number; modelId: string | null; messageCount: number; /** Absent (not 0) when the session never reported the counter. */ cumulativeUserMessageCount?: number; cumulativeToolCallCount?: number; cumulativeInputTokens?: number; cumulativeOutputTokens?: number; parentRef: PlatformSessionParentRef | null; link: PlatformSessionLink; /** * Transcript-scope results only: a window of the transcript around the * match. `null` when no window could be located; ABSENT on title-scope * results, which have no transcript to quote. */ matchPreview?: string | null; } /** * The sessions page, plus the server's echo of the search scope it actually * honored. * * The echo exists so a client can tell an UNDERSTOOD `scope` from an IGNORED * one. A backend predating the parameter drops it silently and returns title * results; without the marker those are indistinguishable from the transcript * results the caller asked for. `scope` is optional here precisely because * such a backend omits it — its absence is the signal, and callers requesting * a non-default scope must check for it. */ type PlatformSessionsPage = PlatformPage & { scope?: string; }; /** * An audited, time-boxed override of a run's gate. * * A waiver never changes the run's own `result` — the run keeps its honest * verdict, and every reader that honors the waiver says so out loud instead. * That is what makes "no silent waiver" checkable rather than promised: the * evidence and the override are two separate records, and nothing collapses * them. */ interface PlatformGateWaiver { id: string; suiteId: string; /** The run this waiver covers. Suite-wide waivers are not honored. */ runId: string | null; /** * Why the gate was overridden, as the granter wrote it. * * UNREDACTED free text, retained for the life of the suite and readable by * anyone who can see it. Any surface that ACCEPTS one must say so before * taking it — see `GATE_WAIVER_REASON_NOTICE` in the gate engine. */ reason: string; /** Epoch ms. Always in the future at creation, and capped at 30 days out. */ expiresAt: number; createdAt: number; createdBy: string; /** `null`, never absent, when it cannot be resolved (e.g. a deleted user). */ createdByEmail: string | null; revokedAt: number | null; revokedBy: string | null; /** * Whether it is in force right now — neither revoked nor expired. * * A client that must not honor a lapsed waiver should re-derive this from * `expiresAt` rather than trust it: the platform computes it at read time, * and a cached read can outlive the instant it changes. */ active: boolean; /** * WHAT was overridden, captured at waive time so a later edit to the suite's * criteria cannot rewrite the record. * * `null` for a run decided by the v2 verdict policy: that policy's identity * is recorded on the audit event instead, because this shape cannot hold it * and filling it in would be a false record rather than an incomplete one. */ policySnapshot: { minimumPassRate: number; } | null; } /** * The result of granting or revoking a waiver. * * `status` distinguishes the write from the two IDEMPOTENT no-ops, and both * no-ops are successes rather than errors: * * - `conflict` — a waiver was already in force, and `waiver` is that * EXISTING one rather than a second row. * - `already_revoked` — this waiver had already been revoked, and `waiver` * reports the original revocation rather than restamping it, so the record * of who actually ended it survives a second call. * * `republishedChecks` counts the GitHub Check Runs brought back in line by * this write. A published check is a persisted verdict, not a live read, so * `0` here on a repository with checks connected means the visible CI status * did not change — worth surfacing, since the check is the thing that gates * the merge. */ interface PlatformGateWaiverWriteResult { status: "created" | "conflict" | "revoked" | "already_revoked"; republishedChecks: number; waiver: PlatformGateWaiver; } /** The active waiver over a run, or `null` when there is none. */ interface PlatformGateWaiverRead { waiver: PlatformGateWaiver | null; } /** * Full eval run record, as returned by `GET /projects/{p}/eval-runs/{runId}` * and the suite run-history listing. Distinct from `PlatformEvalRunSummary`, * the condensed latest-run projection embedded in `PlatformEvalSuite`. */ interface PlatformEvalRun { id: string; suiteId: string; runNumber: number | null; /** Poll until terminal: "completed" | "failed" | "cancelled". */ status: string; /** * Verdict once terminal: `"passed" | "failed" | "inconclusive" | null`. * * `"inconclusive"` exists only under `verdictPolicyVersion: 2` and is NOT a * failure: the run did not measure the server well enough to say (too few * gradeable trials, too many evaluator errors), so a gate that folds it into * `failed` reports a server defect the run never observed. Read * `verdictSummary.reasons` for which check withheld the verdict. */ result: string | null; summary: { total?: number; passed?: number; failed?: number; passRate?: number; } | null; /** Run origin: "ui" | "api" | "sdk". */ source: string; notes: string | null; /** * The project environment this run executed against, read from the run's * immutable config snapshot — NOT the suite's current attachments, which may * have changed since. `null` for a legacy (saved-server-selection) run, and * absent on API deployments that predate run environment attribution. */ environment?: PlatformEvalRunEnvironment | null; /** Shared by every per-target run from the same fan-out launch. */ runGroupId?: string; /** Model the run actually executed with. Absent on pre-attribution rows. */ effectiveModelId?: string; /** `"client_default"` inherited the host model; `"override"` used env.modelId. */ modelSource?: "client_default" | "override"; /** * Which engine executed the run: `"emulated"` (the platform's own turn loop) * or `"harness:"` (a real agent runtime such as Claude Code). * * ABSENT means the run recorded no engine — a run created before the * platform attributed one. Treat that as UNKNOWN, never as `"emulated"`: * those are different claims, and the runs whose engine was never recorded * are exactly the ones a reader must not vouch for. */ executionEngine?: "emulated" | `harness:${string}`; /** * Whether the run's score evidence verified at ingest. * * TRI-STATE, and the third state matters: `"valid"` means the backend * checked and the definitions and results agree; `"invalid"` means they did * not; `null`/absent means NO VERDICT WAS PRODUCED — an API deployment that * predates integrity checking. A score gate must treat `null` exactly like * `"invalid"`: absent evidence is not valid evidence. */ scoreIntegrity?: "valid" | "invalid" | null; /** * The verdict policy this run was decided under, frozen at run start. * * ABSENT means legacy percent-threshold grading — the run's `result` cannot * be `"inconclusive"` and there is no `verdictSummary` to read. A caller * that gates on fractions or on validity must check this first rather than * assume a missing summary means a clean run. */ verdictPolicyVersion?: 2; /** * How the verdict was reached: the resolved validity policy, the measured * rates with their denominators and exclusions, the per-case and * per-execution-variant aggregates, and the exact reasons. * * Absent when the run was not decided under policy 2, or when the stored * summary failed contract validation at the boundary — a public caller never * receives a partially-valid decision, since a gate cannot tell the * difference between a missing field and a satisfied check. */ verdictSummary?: EvalVerdictDecision; /** * Why a policy-2 run could not be decided from its own evidence (a missing * or malformed policy snapshot, mixed evaluator configs). Accompanies an * `"inconclusive"` result; it is never a task failure. */ verdictPolicyIntegrityError?: string; /** * The waiver currently in force over this run's gate, or `null`. * * Gated on being able to VIEW the run, deliberately not on being able to * grant a waiver: a waiver only its grantors could see would not be a * visible one, and visibility is the half of the charter this field exists * to serve. * * `null` means no waiver. ABSENT means an API deployment that predates the * field, which is a different fact and must not be read as "not waived" by * anything that needs to be sure. * * Carried on the run projection rather than fetched separately so `eval * gate` — which already GETs this run — can fold a waiver into its report * without a second round trip on the gating path. */ gateWaiver?: PlatformGateWaiver | null; createdAt: number; completedAt: number | null; /** * The common actionable-insights envelope. Present on the DETAIL response * only (lists stay compact) and absent on servers deployed before the * envelope existed — treat absence as `not_available`. */ insights?: PlatformInsightsEnvelope; /** * Advisory LLM graders on this run, keyed by judge. Present on the DETAIL * response only (lists stay compact) and absent on API deployments that * predate the envelope. */ judges?: PlatformEvalRunJudges; } /** * The advisory graders that can run against a finished eval run. An envelope * rather than a bare `judge` field because `goalCompletion` is one of several: * `groundedness` sits beside it, and a future judge is a new key here rather * than a reshaped response. A judge absent from this object is one this * deployment does not have. */ interface PlatformEvalRunJudges { /** Grades each case's final answer against its expected output. */ goalCompletion?: PlatformEvalRunGoalCompletionJudge; /** Grades whether each answer is SUPPORTED by its tool trajectory. */ groundedness?: PlatformEvalRunGroundednessJudge; } /** * State every judge reports. Written as a base each judge EXTENDS rather than a * generic: the per-judge `cases` differ in shape, and spelling each judge out * keeps the wire schema checkable field by field. */ interface PlatformEvalRunJudgeState { /** * `null` means the judge was NEVER requested for this run — a different * answer from "requested and produced nothing". Poll rather than * re-requesting while this is `"pending"`. */ status: "pending" | "completed" | "failed" | null; /** Machine-readable failure reason, set alongside `status: "failed"`. */ errorCode: string | null; summary: string | null; generatedAt: number | null; modelUsed: string | null; /** Pass threshold the results were scored against (`passed = score >= it`). */ threshold: number | null; } interface PlatformEvalRunGoalCompletionJudge extends PlatformEvalRunJudgeState { /** * Per-case grades. EMPTY unless `status` is `"completed"` — a pending or * failed judge carries no cases, and `status` is what says which. */ cases: PlatformEvalRunGoalCompletionCase[]; } interface PlatformEvalRunGroundednessJudge extends PlatformEvalRunJudgeState { /** Per-case grades. EMPTY unless `status` is `"completed"`. */ cases: PlatformEvalRunGroundednessCase[]; } /** Shared per-case fields every judge reports. */ interface PlatformEvalRunJudgeCase { /** * The stable AUTHORED-case identity, as persisted. NOT a case row id — do * not join it against the ids the per-case routes take. */ caseKey: string; score: number | null; passed: boolean; reason: string | null; } interface PlatformEvalRunGoalCompletionCase extends PlatformEvalRunJudgeCase { /** Rubric criteria the answer satisfied. */ rubricHits: string[]; } interface PlatformEvalRunGroundednessCase extends PlatformEvalRunJudgeCase { /** Claims the tool trajectory does not support. */ unsupportedClaims: string[]; } /** The closed set `resolveChatProvider` can return, named so a surface can * exhaust it. */ type PlatformDisclosureRailDestination = "gateway" | "openrouter"; interface PlatformManagedRailDisclosure { managed: true; possibleDestinations: readonly PlatformDisclosureRailDestination[]; /** VOLATILE: the routing mode is read per request, so this can differ from * the destination the run actually uses minutes later. */ outcomeIfRunNow: { destination: PlatformDisclosureRailDestination; observedAt: number; volatile: true; }; inputs: { mode: string; gatewayEligible: boolean; hasOpenRouterFallback: boolean | null; }; ruleLocation: string; authoritativePerRequestRecord: string; } interface PlatformNotApplicableRailDisclosure { managed: false; notApplicable: true; reason: string; authoritativePerRequestRecord: string; } type PlatformRailDisclosure = PlatformManagedRailDisclosure | PlatformNotApplicableRailDisclosure; type PlatformDisclosureTenantEgress = "mcpjam-hosted" | "byok-cloud" | "byok-local" | "unknown"; interface PlatformByokDisclosure { providerKey: string; runtimeLocation: "cloud" | "local"; /** HOST ONLY, never the full configured URL. */ baseUrlHost?: string; } interface PlatformDisclosedModel { modelId: string; /** `null` when the classifier declines to classify. Kept as `string` rather * than a closed union so a newly recognised provider on the backend does * not need a matching SDK release to pass through. */ provider: string | null; customProviderName?: string; tenantEgress: PlatformDisclosureTenantEgress; byok?: PlatformByokDisclosure; rail: PlatformRailDisclosure; } /** * The closed value set of `execution.engine`. `'mixed'` is reachable only on * an environment fan-out that resolved more than one distinct engine — * `engines` then carries the per-plan detail and `'mixed'` is a summary, not * a fourth runtime kind. */ type PlatformDisclosureEngine = "emulated" | "mixed" | `harness:${string}`; /** * Whether this run executes MCPJam-hosted or on the caller's own machine. * * RESERVED for the inspector to fill in: the backend contract cannot answer * this (only the executing process knows), so the inspector route composes * it onto every `execution` section it returns. `known: false` is kept in * the union defensively — a caller MUST NOT treat it as `hosted: false`. */ type PlatformEvalRunDisclosureLocus = { known: true; hosted: boolean; } | { known: false; reason: string; }; interface PlatformExecutionDisclosure { engine: PlatformDisclosureEngine; engines?: readonly PlatformDisclosureEngine[]; sandbox: { engaged: boolean; vendor?: "e2b"; because: string; }; locus: PlatformEvalRunDisclosureLocus; models: readonly PlatformDisclosedModel[]; /** Present when the plan resolved but its models did not — the empty list * then reads as "not derivable here" rather than "no model runs". */ modelsUnresolved?: { reason: string; }; } type PlatformEvalLlmTouchpointId = "goalCompletion" | "groundedness" | "serverQuality" | "runInsights" | "runGroupQuality"; type PlatformDisclosureFires = "auto-on-completion" | "explicit-request-only" | { disabled: true; reason: string; }; interface PlatformAnalysisTouchpointDisclosure { touchpoint: PlatformEvalLlmTouchpointId; label: string; model: string; rail: { fixed: "openrouter"; because: string; }; destinations: readonly string[]; evidenceSent: readonly string[]; fires: PlatformDisclosureFires; } interface PlatformCaptureDisclosure { captureLevel: string; reportingMode: string; tiersImplemented: boolean; redaction: { kind: string; module: string; isDlp: boolean; limitation: string; appliesTo: readonly string[]; }; exportDefaults: { includeContent: boolean; ruleLocation: string; note: string; }; } interface PlatformRetentionDisclosure { planName: string; /** The POLICY number from plan entitlements. `null` ⇒ uncapped by policy. */ policyDays: number | null; source: string; enforced: boolean; enforcementBlockers: readonly string[]; /** What actually happens today — never re-derive this from `policyDays`, * an unenforced policy keeps data indefinitely regardless of its number. */ effectiveToday: "kept-indefinitely" | "swept-after-policy-days"; evidentiaryClasses: readonly string[]; backupStatement: { vendor: string; capturedAt: string; sourceUrl: string; statements: readonly string[]; }; } type PlatformRegionDisclosure = { stated: false; reason: string; } | { stated: true; value: string; derivedFrom: string; }; interface PlatformSubprocessorDisclosure { vendor: string; role: string; dataCategories: readonly string[]; capturedAt: string; sourceUrl: string; statements: readonly string[]; engaged: boolean; because: string; } /** * WHY there is no `execution` section. Never interchangeable: * * `'ingested-run'` — the SDK uploaded a run MCPJam did not execute; * * `'plan-unresolved'` — a launchable plan whose environments did not * resolve, so models ARE called, just not derivable at this point. * * A surface that renders `'ingested-run'` copy for a `'plan-unresolved'` * disclosure tells a user about to launch that nothing leaves — that exact * bug was caught and fixed in the backend half (g4a) and must not be * reintroduced at the presentation layer. */ type PlatformExecutionAbsenceKind = "ingested-run" | "plan-unresolved"; /** * The pre-run disclosure contract: what happens to a run's content, computed * once by the backend and projected identically by every surface (pre-run * dialog, CLI, MCP tools, the `eval.run.launched` audit row). * * `execution` is present ONLY when a launch plan resolved; `executionAbsence` * exactly when it is absent. `analysis` is ALWAYS present — stored evidence * still reaches the judges even when nothing was executed here — so never * hide it just because `execution` is missing. */ interface PlatformEvalRunDisclosure { contractVersion: number; computedAt: number; digest: string; execution?: PlatformExecutionDisclosure; executionAbsence?: { kind: PlatformExecutionAbsenceKind; reason: string; }; analysis: readonly PlatformAnalysisTouchpointDisclosure[]; capture: PlatformCaptureDisclosure; retention: PlatformRetentionDisclosure; region: PlatformRegionDisclosure; subprocessors: readonly PlatformSubprocessorDisclosure[]; } /** * Identity of the environment revision a run was pinned to. `name`/`revision` * are nullable only for tolerance of older snapshots that recorded a partial * ref; a current run always carries all three. */ interface PlatformEvalRunEnvironment { id: string; name: string | null; revision: number | null; } /** `202` response of `POST /projects/{p}/eval-runs`. */ interface PlatformEvalRunCreated { runId: string; suiteId: string; /** * The run's status. `running` on a fresh launch; on a replay (see * `deduped`), the existing run's own status — which may already be terminal. */ status: string; /** * This request REPLAYED an existing run rather than starting one — an * idempotency-key hit, or the short keyless dedupe window. * * A replayed run is NOT executed again, so a retry spends nothing further. * Absent on a fresh launch, and on an API deployment that predates the * signal — where absence means "not reported", not "fresh". */ deduped?: boolean; /** * Echo of the request's `runGroupId`, when one was sent. A LABEL only — it * groups sibling rows for display and carries no quota or launch semantics. * Grouped-launch behaviour (one concurrency slot for a whole fan-out, * validate-all-then-launch) lives on `createEvalRunGroup`, which mints the * id itself. Absent when the request sent none, and on older deployments. */ runGroupId?: string; /** Per-case upsert outcomes for inline tests; empty on plain reruns. */ caseUpsert: { committed?: Array<{ id?: string; name?: string; }>; failed?: Array<{ id?: string; name?: string; error?: string; }>; }; /** * The servers the run connects to — explicit, or derived server-side from * the suite's saved selection when the request omitted serverIds. Absent * on older API deployments. */ servers?: Array<{ id: string; name?: string; }>; /** * The environment the run is pinned to, at the revision whose servers were * connected. Present even when the request omitted it: a suite with exactly * one attached environment auto-selects, and this is how a caller learns * that happened. `null` for a legacy run; absent on older API deployments. */ environment?: PlatformEvalRunEnvironment | null; } /** Which target one entry of a grouped launch ran. Exactly one id is set. */ interface PlatformEvalRunGroupTarget { environmentId?: string; namedHostId?: string; /** The target's display name, when the platform resolved one. */ name?: string; } /** * One target's outcome in a grouped launch. * * DISCRIMINATED on `status` rather than "a runId when it worked, an error when * it didn't": a reader branches on one field instead of probing which optional * members happen to be present, and a target that failed can never be mistaken * for one that started with an unread `runId`. */ type PlatformEvalRunGroupEntry = { status: "started"; target: PlatformEvalRunGroupTarget; runId: string; /** * The RUN's status (always `"running"` at launch). Named apart from the * entry's own `status` on purpose — two fields called `status` in one * object is how a reader ends up branching on the wrong one. */ runStatus: string; servers?: Array<{ id: string; name?: string; }>; environment?: PlatformEvalRunEnvironment | null; caseUpsert?: PlatformEvalRunCreated["caseUpsert"]; } | { status: "failed"; target: PlatformEvalRunGroupTarget; error: { code: string; message: string; }; }; /** * The receipt for `POST /eval-run-groups`: one run per target, under one * server-minted group id. * * A per-target failure does NOT abort its siblings, so a caller must read * `outcome` rather than assume a 202 means everything started. */ interface PlatformEvalRunGroupCreated { runGroupId: string; suiteId: string; /** * `"started"` — every target launched; `"partial"` — some did and some did * not; `"failed"` — none did (still a 202: the group itself was valid, and * the per-target reasons are in `targets`). */ outcome: "started" | "partial" | "failed"; startedCount: number; failedCount: number; targets: PlatformEvalRunGroupEntry[]; /** * @deprecated Mirror of the FIRST started run, so readers written against * the single-run receipt keep working. Absent when nothing started. Read * `targets` instead — this describes one run out of several. */ runId?: string; /** @deprecated See `runId`. */ status?: string; /** @deprecated See `runId`. */ servers?: Array<{ id: string; name?: string; }>; /** @deprecated See `runId`. */ environment?: PlatformEvalRunEnvironment | null; /** @deprecated See `runId`. */ caseUpsert?: PlatformEvalRunCreated["caseUpsert"]; } /** * `201` response of `POST /projects/{p}/eval-suites` — an authored, runnable * suite created from test-case definitions (NOT run; execute it with * `run_eval_suite`). Tolerant reader: unknown fields pass through. */ interface PlatformEvalSuiteCreated { suiteId: string; /** Suite name as persisted; echoes the request name. */ name: string; /** The HTTP servers the suite was configured against. */ servers?: Array<{ id: string; name?: string; }>; /** Per-case create outcomes, mirroring eval-run caseUpsert. */ caseUpsert: { committed?: Array<{ id?: string; name?: string; }>; failed?: Array<{ id?: string; name?: string; error?: string; }>; }; } /** * Public match-option vocabulary, mirroring the suite/case UI controls. The * route layer translates these to the internal match-option model. */ interface PublicMatchOptions { /** * `any` = order ignored; `in-order` = expected calls must appear in order * (extra calls allowed between them); `exact` = exact sequence. */ toolCallOrder: "any" | "in-order" | "exact"; /** `unlimited`, or the max number of unexpected extra tool calls allowed. */ extraToolCalls: "unlimited" | number; /** Argument comparison strictness. */ arguments: "ignore" | "partial" | "exact"; } /** * A deterministic pass/fail check. `type` is the check vocabulary (e.g. * `responseContains`, `toolCalledWith`); the remaining fields depend on it. */ interface PublicCheck { type: string; [key: string]: unknown; } /** Per-case check override: how the case's checks combine with suite defaults. */ interface PublicCheckOverride { mode: "inherit" | "replace" | "extend"; list: PublicCheck[]; } interface PlatformExpectedToolCall { tool: string; arguments?: Record; } interface PlatformEvalSuiteSettings { /** Minimum pass rate as a percentage, 0–100. */ minimumAccuracy: number | null; /** * Suite-level FLOOR on per-case iterations, 1–10: every case runs at least * this many times (`max(case.iterations, minimumIterations)`). `null` means * no floor — the suite's real state, not a stand-in for 1. Absent on older * API deployments. */ minimumIterations?: number | null; matchOptions: PublicMatchOptions | null; checks: PublicCheck[]; /** * LLM-as-judge configuration, RESOLVED — every field is layered over the * platform defaults, so this is what a run on this suite would actually * grade with. * * `model` stays nullable: older API deployments report the suite's raw * `judgeModel`, which is `null` for a suite that never picked one. */ judge: { /** Judge is available on the suite. Does NOT by itself grade anything. */ enabled: boolean; model: string | null; /** * The flag that makes grading HAPPEN — fires the judge as each run * completes. Absent on older API deployments. */ autoRun?: boolean; /** * Advisory pass threshold (`passed = score >= threshold`), in [0, 1]. * Absent on older API deployments. */ threshold?: number; }; /** * The verdict policy this suite's runs are decided under. * * `2` is the fraction-and-validity policy: each case is graded against a * `passThreshold` FRACTION over its own `repetitions`, and a run is decided * valid-first (an invalid run is `"inconclusive"`, not failed). * * ABSENT means legacy: runs are graded by `minimumAccuracy` (a suite-wide * PERCENT) over `max(case.iterations, minimumIterations)`. The two are not * convertible, which is why absence is reported rather than defaulted — * reading a historical percent as a fraction silently moves every bar. */ verdictPolicyVersion?: 2; /** * Suite defaults a case inherits under policy 2. Present only with * `verdictPolicyVersion: 2`, and only as a whole: `repetitions` without * `passThreshold` cannot answer what a case is graded against. */ verdictPolicyDefaults?: PlatformEvalVerdictPolicyDefaults; } /** Suite-level defaults under verdict policy 2. Fractions, never percents. */ interface PlatformEvalVerdictPolicyDefaults { /** Trials per case unless the case overrides `repetitions`. */ repetitions: number; /** Fraction of a case's trials that must pass, in [0, 1]. */ passThreshold: number; /** * When a run's measurement counts as trustworthy enough to decide. * * DECLARED, not resolved: an omitted field is not "no minimum" but the * contract's default — `minCompletionRate` 0.8, `maxEvaluatorErrorRate` 0.1, * and an omitted `minEligibleTrials` requiring every configured trial * attempted plus at least one gradeable trial. The resolved policy a run was * actually decided under is on the run's `verdictSummary.validity`. */ validity?: { minEligibleTrials?: number; minCompletionRate?: number; maxEvaluatorErrorRate?: number; }; } /** The sandbox image a suite's eval runs boot from. */ interface PlatformEvalSuiteComputerEnvironment { id: string; /** `null` when the pinned image could not be resolved. */ name: string | null; } interface PlatformEvalSuiteHost { id: string; name: string; /** Server names this host runs against, when resolved. */ servers?: string[]; } interface PlatformEvalSuiteSchedule { enabled: boolean; /** Interval in minutes; preserved (not cleared) when `enabled` is false. */ intervalMinutes: number | null; /** * The single attached environment scheduled runs launch (a schedule fires one * run, so a multi-environment suite must pin one). `null` for a legacy suite; * absent on older API deployments. */ environmentId?: string | null; } /** * Full eval suite, returned by `GET`/`PATCH /eval-suites/{id}`. Public-model * shape — the route layer maps this to/from the internal Convex suite. Tolerant * reader: unknown fields pass through. */ interface PlatformEvalSuiteDetail { id: string; /** * The suite's declared file identity (`suite.id` in a suite file). Present * on file-owned suites; absent on UI-authored suites, which have no * declared id and cannot be claimed by `eval run --file`. */ declaredId?: string; name: string | null; description: string | null; projectId: string | null; /** LEGACY server selection by name. Not the project-environment attachments. */ environment: { servers: string[]; /** * The custom sandbox image this suite's eval runs boot a fresh computer * from. `null` means the provider's default base image. The `name` is the * one `list_sandbox_images` reports; it is `null` when the image could not * be resolved (deleted, or not visible to this caller). Absent on older * API deployments. */ computerEnvironment?: PlatformEvalSuiteComputerEnvironment | null; }; /** * Attached project environments, in attach order. A non-empty list makes the * suite environment-based: its runs resolve one of these instead of the * legacy selection above. Absent on older API deployments. */ environmentIds?: string[]; /** Suite-level execution config; null when none is pinned. */ executionConfig: { model: string; systemPrompt: string; temperature: number; } | null; /** Host attachments (multi-host). */ hosts: PlatformEvalSuiteHost[]; settings: PlatformEvalSuiteSettings; schedule: PlatformEvalSuiteSchedule; createdAt: number | null; updatedAt: number | null; } /** * `POST /eval-suites/from-file` — resolve or create a file-owned suite by * declared id. `created` is true on the first upload of that id in the * project; later uploads update the same suite. */ interface PlatformFileOwnedEvalSuiteSynced { created: boolean; suite: PlatformEvalSuiteDetail; } interface PlatformEvalCaseModel { model: string; provider?: string; } /** * One authored test step — the unified test model (mirrors the inspector's * `shared/steps.ts` `TestStep`). Typed permissively at this boundary * (discriminated on `kind`); per-kind detail fields ride along. * * REPLACES the old per-case `kind` / `prompt` / `turns` / `expectedToolCalls` * / `renderCheck` projection (Phase 2.5 clean break). */ interface PlatformEvalStep { id: string; kind: "prompt" | "toolCall" | "interact" | "assert"; [field: string]: unknown; } /** * A single eval test case. The case body is an ordered `steps` array * (prompt / toolCall / interact / assert). Public-model shape; the route maps * to/from the internal case. */ interface PlatformEvalCase { id: string; /** * The case's effective DECLARED id — what it answers to in a suite file, an * import, or a CLI argument. Absent on cases authored before declared * identity existed. Distinct from `id`, which is the platform row id the * per-case routes take as their path parameter. */ declaredId?: string; title: string; /** Ordered test steps that define the case. */ steps: PlatformEvalStep[]; expectedOutput?: string; /** Iterations to run per eval run (← internal runs). */ iterations: number; /** * Trials this case runs under verdict policy 2, overriding the suite * default. Absent means the case inherits it. * * NOT a second spelling of `iterations`: that one is the legacy count, which * the legacy resolver reads as a FLOOR (`max(iterations, minimumIterations)`) * and which a policy-2 case still reports for compatibility. This one is * exact. */ repetitions?: number; /** * Fraction of this case's trials that must pass, in [0, 1], overriding the * suite default. Absent means the case inherits it. * * Never derived from the suite's `minimumAccuracy`, which is a percent under * a different resolver. */ passThreshold?: number; isNegative: boolean; scenario?: string; /** Execution models (plural — preserves compare behavior). */ models: PlatformEvalCaseModel[]; matchOptions?: PublicMatchOptions; checks?: PublicCheckOverride; createdAt: number | null; updatedAt: number | null; } /** A note about a batch write that changes nothing about what was written. */ interface PlatformEvalCaseWarning { /** Stable machine-readable code (e.g. `DUPLICATE_POLICY_COERCED`). */ code: string; message: string; } /** One case a batch create authored. */ interface PlatformEvalCaseBatchCreated { /** Position in the `cases` array that was sent. */ index: number; /** Platform id — what the per-case routes take as their path parameter. */ id: string; /** The effective declared id. On a replay this is the STORED case's. */ declaredId?: string; title: string; /** True when an idempotent retry landed on an already-authored case. */ replayed: boolean; warnings?: PlatformEvalCaseWarning[]; } /** One case a batch create refused. Its siblings may still have committed. */ interface PlatformEvalCaseBatchFailed { index: number; title?: string; declaredId?: string; /** Stable machine-readable code (e.g. `DUPLICATE_CASE_ID`). */ code: string; message: string; } /** * The result of authoring several cases at once. * * Per-case failures are reported here rather than raised: a batch is a partial * outcome by design, and the cases in `created` were really written. */ interface PlatformEvalCaseBatchResult { created: PlatformEvalCaseBatchCreated[]; failed: PlatformEvalCaseBatchFailed[]; /** * What duplicate policy actually applied. An unrecognized value coerces to * `block` and says so here — never silently. */ duplicatePolicy: { requestedPolicy?: string; effectivePolicy: string; coerced: boolean; }; warnings?: PlatformEvalCaseWarning[]; } /** A base/compare pair with its delta. Rates in these are fractions. */ interface PlatformNumericDiff { base: number | null; compare: number | null; delta: number | null; percentDelta: number | null; } type PlatformCompareCaseStatus = "unchanged_passed" | "unchanged_failed" | "regressed" | "fixed" | "new_case" | "removed_case" | "changed"; interface PlatformScoreContractSide { evaluationConfigHash: string | null; /** `null` means NO verdict — treat it exactly like `"invalid"` for gating. */ scoreIntegrity: "valid" | "invalid" | null; scoredIterations: number; quarantinedIterations: number; } interface PlatformScoreContractScorer { scorerId: string; gating: boolean; deterministic: boolean; /** Same id, different definition hash — the two sides did not measure alike. */ definitionChanged: boolean; passRate: PlatformNumericDiff; meanValue: PlatformNumericDiff; errorCount: { base: number; compare: number; }; } interface PlatformScoreContractDiff { base: PlatformScoreContractSide; compare: PlatformScoreContractSide; evaluationConfigChanged: boolean; scorers: PlatformScoreContractScorer[]; } interface PlatformCaseScoreSide { status: "scored" | "error" | "skipped" | "not_applicable"; value: number | null; passed: boolean | null; } interface PlatformCaseScoreDelta { scorerId: string; gating: boolean; deterministic: boolean; definitionChanged: boolean; base: PlatformCaseScoreSide | null; compare: PlatformCaseScoreSide | null; value: PlatformNumericDiff; } interface PlatformRunCompareCaseSide { outcome: "passed" | "failed" | "absent"; /** Iteration ids are public; `traceBlobIds` are NOT and never appear here. */ iterationIds: string[]; representativeIterationId: string | null; error: string | null; } interface PlatformRunCompareCase { caseKey: string; title: string; status: PlatformCompareCaseStatus; /** The scenario's own config (prompt, steps, expectations) changed. */ configChanged: boolean; /** This case's evaluation config changed. */ evaluationConfigChanged: boolean; scoreDeltas: PlatformCaseScoreDelta[]; base: PlatformRunCompareCaseSide; compare: PlatformRunCompareCaseSide; } interface PlatformRunCompareSide { id: string; runNumber: number; result: string; createdAt: number; completedAt: number | null; summary: { total: number; passed: number; failed: number; passRate: number; } | null; environment?: { id: string; name: string | null; }; effectiveModelId?: string; modelSource?: "client_default" | "override"; } /** * The compare wire. * * There is deliberately NO `baseline_not_found` member. A missing baseline * arrives as a thrown `PlatformApiError` (404 with * `details.reason === "BASELINE_NOT_FOUND"`), so a caller that forgets to * handle it fails loudly instead of reading fields off a union member it never * narrowed. */ interface PlatformRunCompare { suite: { id: string; name: string; }; baseline: { policy: "previous_completed" | "previous_completed_same_environment" | "run" | "commit_sha"; baseRunId: string; /** * The source SHA that was pinned, echoed back for the `commit_sha` policy * only. Recorded alongside `baseRunId` rather than instead of it: a gate's * audit trail needs both the SHA the caller asked for and the run it * actually resolved to. */ baseCommitSha?: string; /** * Present ONLY when uniqueness could NOT be established — the SHA matched * several eligible runs, or the bounded lookup saturated so older eligible * ones may exist beyond it. **Absent means unambiguous**; do not default * it to 1. */ matchCount?: number; /** * `matchCount` is a FLOOR, not a total — including when it reads 1. Render * it WITH its count or not at all: a truncated count shown alone asserts a * uniqueness nobody checked. */ matchCountTruncated?: boolean; }; baseRun: PlatformRunCompareSide; compareRun: PlatformRunCompareSide; /** * Run-summary counters — NOT score-contract data. Named `passSummary` here * precisely because the internal field is called `scores` and the collision * is a live foot-gun. */ passSummary: { passRatePercent: PlatformNumericDiff; total: PlatformNumericDiff; passed: PlatformNumericDiff; failed: PlatformNumericDiff; }; metrics: { wallDurationMs: PlatformNumericDiff; totalTokens: PlatformNumericDiff; estimatedCostUsd: PlatformNumericDiff; }; scoreContract: PlatformScoreContractDiff; cases: PlatformRunCompareCase[]; } interface PlatformEvalSuiteDeleted { id: string; deleted: true; } interface PlatformEvalCaseDeleted { id: string; deleted: true; } /** * What a config edit to a client would follow. * * These are the DURABLE consumers that re-resolve the client's current config. * Past runs, per-turn traces and pinned eval-suite snapshots hold a config id * and do not follow an edit. Direct playground / client-chat use follows it and * has no row to count, which is why it is described in prose by the surfaces * that quote these numbers rather than folded into one of them. */ interface PlatformClientImpact { liveEnvironmentCount: number; scenarioAttachmentCount: number; activeLegacyJourneyCount: number; } /** A client in a project (list projection). */ interface PlatformClient { id: string; name: string; /** * ID of the content-addressed config this client points at, and the * concurrency token every write takes. Content addressed, so the same id * means byte-identical settings. */ configId: string; modelId: string; serverCount: number; /** Product ownership of the row (null for untagged). Never an auth signal. */ ownerScope: Record | null; hasComputer: boolean; createdAt: number; updatedAt: number; } /** Full client detail, including the resolved config DTO and its read-backs. */ interface PlatformClientDetail { id: string; name: string; /** The concurrency token — see {@link PlatformClient.configId}. */ configId?: string; /** Resolved client-config v2 DTO (model, capabilities, hostContext, …). */ config: Record; ownerScope: Record | null; hasComputer?: boolean; createdAt?: number; updatedAt?: number; /** What a config edit would follow. */ impact?: PlatformClientImpact; } interface PlatformClientDeleted { id: string; deleted: true; } /** * @deprecated A host in a project, as the `/hosts` alias returns it. Use * {@link PlatformClient}. * * NOT a type alias of `PlatformClient`, deliberately. `/hosts` returns * `hostConfigId` where `/clients` returns `configId`, and carries none of the * read-backs — so an alias would be a compile-time lie about a runtime shape, * and every existing caller reading `hostConfigId` would start failing * typecheck for a field the deprecated route still sends. */ interface PlatformHost { id: string; name: string; hostConfigId: string; modelId: string; serverCount: number; createdAt: number; updatedAt: number; } /** * @deprecated Full host detail as the `/hosts` alias returns it. Use * {@link PlatformClientDetail}, which also carries `configId` and `impact`. */ interface PlatformHostDetail { id: string; name: string; /** Resolved host-config v2 DTO (model, capabilities, hostContext, …). */ config: Record; } /** @deprecated Use {@link PlatformClientDeleted}. */ interface PlatformHostDeleted { id: string; deleted: true; } /** * An explicit, pinned skill selection. Empty lists are rejected — clear the * field (`null` on update) to mean "no pinned skills". */ interface PlatformEnvironmentSkillSelection { mode: "explicit"; skillIds: string[]; } interface PlatformEnvironment { id: string; projectId: string; name: string; description?: string; hostId: string; /** Set only when the environment pins a standalone server group. */ serverAttachmentId?: string; /** * The environment's model OVERRIDE, if it sets one. * * ABSENT means the environment INHERITS the model pinned by its host — not * that it has no model. To learn what will actually run, resolve the * environment and read `effectiveModelId`. */ modelId?: string; skillSelection?: PlatformEnvironmentSkillSelection; /** * Pinned plugin VERSIONS. Narrow by design: a version is pinnable only when * its plugin is installed and enabled, the version is `ready`, at most one * version per plugin is pinned, and none of its skills carry supporting * files. Not a general-purpose plugin list. */ pluginVersionIds?: string[]; /** * Sandbox-image pin: a `PlatformImage` id this environment's reproducibility * runs boot a fresh sandbox from. Must be a project-shared image (personal * drafts are rejected — promote first). Applies to eval runs today. */ sandboxImageId?: string; /** Pass back as `expectedRevision` on the next mutation. */ revision: number; /** Archived environments cannot be edited or launched until restored. */ archived: boolean; archivedAt?: number; createdAt: number; updatedAt: number; } /** * An UNNAMED, content-addressed environment: a composed client/model/computer/ * skills stack, not a saved entry in the project's environment list. * * Its own type rather than `PlatformEnvironment` with a nullable name, because * `PlatformEnvironment.name` is a required string and every listing filters * ad-hoc rows out precisely so that promise holds. Widening it would break * readers who trusted it, for a row they never asked to see. */ interface PlatformAdhocEnvironment { id: string; projectId: string; /** Always `null` — an ad-hoc environment is unnamed by construction. */ name: null; /** Always `true`. Present so a reader never has to infer it from the null. */ adhoc: true; description?: string; hostId: string; serverAttachmentId?: string; /** See `PlatformEnvironment.modelId` — absent means "inherit the host's". */ modelId?: string; skillSelection?: PlatformEnvironmentSkillSelection; pluginVersionIds?: string[]; sandboxImageId?: string; /** Pass back as `expectedRevision` when promoting it with a name. */ revision: number; archived: boolean; archivedAt?: number; createdAt: number; updatedAt: number; } /** * The result of ensuring a composed stack exists. * * `created` distinguishes "this call minted the row" from "the same stack was * already ensured", which is the only way a caller can tell a first compose * from a repeat — the status line cannot, because get-or-create answers 200 * either way. */ interface PlatformAdhocEnvironmentEnsured { environment: PlatformAdhocEnvironment; created: boolean; } /** * The composed stack itself: the same execution axes a named environment * carries, minus the name. Content-addressed server-side, so the same stack * always resolves to the same environment. */ interface PlatformAdhocEnvironmentBody { hostId: string; serverAttachmentId?: string; modelId?: string; skillSelection?: PlatformEnvironmentSkillSelection; pluginVersionIds?: string[]; sandboxImageId?: string; } /** * The outcome of appending one environment to a suite's attachments. * * `attached: false` means it was ALREADY there — a no-op, not a failure, which * is what lets a retried compose-and-run converge. */ interface PlatformEvalSuiteEnvironmentAttached { suiteId: string; attached: boolean; /** The suite's attachments after the call, in attach order. */ environmentIds: string[]; } /** Promote an ad-hoc environment to a named one, in place. */ interface PlatformEnvironmentNameBody { /** The revision you last read. Stale ⇒ 409 CONFLICT. */ expectedRevision: number; name: string; description?: string; } interface PlatformEnvironmentCreateBody { name: string; description?: string; hostId: string; serverAttachmentId?: string; /** Model to run instead of the host's; omit to inherit the host's. */ modelId?: string; skillSelection?: PlatformEnvironmentSkillSelection; pluginVersionIds?: string[]; /** Project-shared `PlatformImage` id to pin; omit for the default image. */ sandboxImageId?: string; } /** * Update body. Three-state on the clearable fields: omit to leave unchanged, * pass `null` to CLEAR, pass a value to set. An empty array is rejected — it is * not a way to clear. */ interface PlatformEnvironmentUpdateBody { /** Required: the revision you last read. Stale ⇒ 409 CONFLICT. */ expectedRevision: number; name?: string; /** An empty string clears the description. */ description?: string; hostId?: string; serverAttachmentId?: string | null; /** * New model override, or `null` to CLEAR it and fall back to the host's * model. Omit to leave unchanged. An empty string is rejected — it is not a * way to clear. */ modelId?: string | null; skillSelection?: PlatformEnvironmentSkillSelection | null; pluginVersionIds?: string[] | null; /** New sandbox-image pin, or null to clear it. Omit to leave unchanged. */ sandboxImageId?: string | null; } /** * What this deployment's environment surface supports. * * FOR VERSION SKEW, not feature flagging. The SDK ships independently of the * backend, so a client that would send `modelId` must first confirm the * deployment accepts it — an unknown field is a hard validator error there, not * a silently ignored one. A deployment too old to answer reports `false` for * everything. */ interface PlatformEnvironmentCapabilities { /** `modelId` is accepted on create and update. */ modelOverrides: boolean; /** Environment cells may vary by model on one host (the compare grid). */ modelMatrix: boolean; /** * `startTestSuiteRun` accepts `ephemeralEnvironment` — a project-scoped * env may launch without suite membership. Absent/false on older backends. */ ephemeralEnvironmentLaunch?: boolean; } /** Body for the archive/restore sub-actions — the precondition only. */ interface PlatformEnvironmentRevisionBody { expectedRevision: number; } /** * What an environment resolves to right now: the host's current config, the * closed server set, and the pinned plugin versions. The same resolution an * eval run performs, exposed so an external runner can connect the exact set * before launching. */ interface PlatformEnvironmentResolved { environment: { id: string; name: string; revision: number; }; hostId: string; hostName: string; /** The host's config at resolve time — hosts rotate configs live. */ hostConfigId: string; /** The environment's stored override, when it sets one. */ modelId?: string; /** * The model this environment WILL RUN — the override if it has one, else the * host config's. Always present on a successful resolve: an environment with * no model anywhere cannot be resolved for launch at all, and fails with a * 409 carrying `details.reason: "environment_model_required"`. * * Optional in the type only for deploy skew, where the backend predates the * field. */ effectiveModelId?: string; /** Which of the two supplied {@link effectiveModelId}. */ modelSource?: "environment" | "host"; serverAttachmentId?: string; /** The closed NON-plugin server set. */ selectedServerIds: string[]; /** * `selectedServerIds` plus the servers contributed by pinned plugin * versions — the set a run actually connects. Identical to * `selectedServerIds` when the environment pins no plugins. */ effectiveServerIds: string[]; pluginVersions: Array<{ pluginId: string; pluginVersionId: string; name: string; bundleHash: string; }>; /** Connectable projection of `effectiveServerIds`, healed to live servers. */ servers: Array<{ serverId: string; name: string; }>; /** The environment's sandbox-image pin, when set (and the backend is new * enough to carry it through the resolve). */ sandboxImageId?: string; } /** One live (installed, non-uninstalled) plugin in a project. */ interface PlatformPlugin { id: string; projectId: string; /** Normalized plugin name — the namespace its skills load under. */ name: string; displayName?: string; description?: string; /** Disabled plugins keep their versions but resolve for no run. */ enabled: boolean; /** The version environment pins default to; absent before first activate. */ activeVersionId?: string; createdAt: number; updatedAt: number; } /** Per-component tallies of one imported version. `apps` counts preserved * `.app.json` metadata entries only (no runtime effect). */ interface PlatformPluginComponentCounts { skills: number; servers: number; apps: number; assets: number; unsupported: number; } /** One MCP server a plugin version declares, with its materialized row. */ interface PlatformPluginServerComponent { componentId: string; /** Stable key within the version (normalized server map key). */ componentKey: string; declaredName: string; /** Where the component can execute; `local`/`computer` never run hosted. */ placement: "remote" | "local" | "computer"; /** Declared auth timing: setup right after import, or on first use. */ authenticationPolicy: "on_install" | "on_use"; /** The project server row this component materialized as. */ materializedServerId: string; } /** One skill a plugin version declares, with its materialized row. */ interface PlatformPluginSkillComponent { componentId: string; componentKey: string; declaredName: string; /** Namespaced model-facing reference: `/`. */ modelRef: string; materializedSkillId: string; } /** One immutable imported version with its component projections. */ interface PlatformPluginVersion { id: string; pluginId: string; /** `manifest.version` — metadata only; `bundleHash` is the identity. */ declaredVersion?: string; bundleHash: string; manifestHash?: string; /** Only `ready` versions resolve at runtime or serve bundle bytes. */ status: "staging" | "ready" | "invalid"; componentCounts: PlatformPluginComponentCounts; servers: PlatformPluginServerComponent[]; skills: PlatformPluginSkillComponent[]; createdAt: number; readyAt?: number; } interface PlatformImageBuild { id: string; status: "queued" | "building" | "ready" | "failed"; provider: "e2b" | "stub"; e2bBuildId?: string; baseImageDigests: string[]; logPreview?: string; error?: string; createdAt: number; startedAt?: number; finishedAt?: number; } /** A project's custom Computer sandbox image (its blueprint + latest build). * The list and detail routes return the same shape. */ interface PlatformImage { id: string; projectId: string; name: string; blueprint: string; contentHash: string; sharing: "user" | "project"; isOwner: boolean; currentBuild: PlatformImageBuild | null; createdAt: number; updatedAt: number; } interface PlatformImageDeleted { id: string; deleted: true; } /** Result of linting blueprint YAML via `POST …/images/validate`. Always * HTTP 200 — `ok: false` is a successful lint with structured errors. */ type PlatformImageBlueprintValidation = { ok: true; baseImageDigest: string; } | { ok: false; errors: { path: string; message: string; }[]; }; /** `POST …/build` is async (202): the build runs in the background — poll the * builds list for status. */ interface PlatformImageBuildStarted { id: string; buildId: string; reused: boolean; } interface PlatformComputerAttached { imageId: string; computerId: string; status: string; } interface PlatformComputerReset { projectId: string; reset: boolean; } /** `200` response of `POST /eval-suites/{id}/cases/generate`. */ interface PlatformEvalCasesGenerated { /** The backend LLM that authored the cases — NOT the case execution model. */ generationModel: string; created: PlatformEvalCase[]; counts: { normal?: number; negative?: number; }; /** Drafts that were generated but failed to persist (never silently dropped). */ skipped?: Array<{ title: string; error: string; }>; } interface PlatformEvalIteration { id: string; /** * The STORED case row's database id. Distinct from `caseId` below and never * interchangeable with it: this one exists for every case the platform * persisted, changes if the case is recreated, and means nothing outside this * deployment. */ testCaseId: string | null; /** * The case's SDK-DECLARED id, when the run recorded one. * * Read from the iteration's frozen `testCaseSnapshot`, so it is the id the * suite declared AT RUN TIME — the durable, author-chosen identity that * survives a case being recreated. ABSENT on a UI-authored case (which never * declared one) and on runs predating declared ids; absence is not an error. * * NOT a join key into `verdictSummary.cases[].caseId`, which is a separately * ENCODED identity the platform mints from whichever spelling a given run * knew. Matching one against the other attaches a trial to the wrong case * aggregate. */ caseId?: string; title: string | null; iterationNumber: number; /** * LIFECYCLE, not verdict: `pending`, `running`, `completed`, `failed`, * `cancelled`, `timed_out`, `setup_failed`, `skipped`. * * A normally-executed trial that graded badly is `completed` with * `result: "failed"` — reading `status === "failed"` as "the case failed" * counts harness noise as server defects. `setup_failed` (environment never * came up) and `skipped` (deliberately not run) are the two states an older * deployment cannot emit. */ status: string; /** Task verdict once terminal: `"passed" | "failed" | null`. */ result: string | null; model: string | null; provider: string | null; startedAt: number | null; /** Wall-clock duration; null until terminal. */ durationMs: number | null; tokensUsed: number | null; /** Structured token usage (input/output/cached/reasoning) when available. */ usage: Record | null; actualToolCalls: Array>; expectedToolCalls: Array>; error: string | null; /** * Per-scorer verdicts for this iteration, in the evaluation contract's * shape. `null` when the run predates scoring, or when the stored payload * failed validation at the boundary — a public caller never receives * partially-trusted score data. */ scores?: ScoreResult[] | null; /** * The definitions those scores were produced under, plus their hash. * * Ships with `scores` or not at all: `role` and the error policies live here, * so results without it cannot be told apart as gating or advisory. */ evaluationConfig?: EvaluationConfigSnapshot | null; /** Set when the backend downgraded this iteration's verdict at ingest. */ scoreIntegrity?: "score_integrity_invalid" | null; /** Verified D1 user-value chain rows, in chain order. */ stageResults?: StageResultRow[]; /** The first failed stage, when the verified derivation has one. */ firstFailedStage?: UserValueStage; /** Coarse failure bucket; it may exist without a failed stage row. */ failureCategory?: FailureCategory; /** Analyzer version that produced the stage rows. */ stageAnalyzerVersion?: number; /** The server returned stage rows that failed D1 validation. */ stageResultsUnverified?: true; } /** Public-safe evidence for one eval step (resolved URLs, no blob ids). */ interface PlatformEvalStepEvidence { /** Widget→host tool calls the interaction triggered. */ toolCalls?: Array<{ name: string; args: unknown; ok: boolean; error?: string; /** Wall-clock ms for this widget→host call, when the harness recorded it. */ elapsedMs?: number; }>; /** Resolved screenshot URL for the step's render/interaction. */ screenshotUrl?: string; /** Resolved iteration replay `.webm` URL (same on every step of the run). */ videoUrl?: string; /** Playback offset of this step within the replay video, when known. */ videoOffsetMs?: number; /** "scripted" (authored) vs "computer_use" (model-driven) interaction. */ source?: "computer_use" | "scripted"; /** Human-readable interaction target (e.g. the button label). */ locatorLabel?: string; } /** * One row per authored test step, in author order — the public mirror of the * fail-fast step engine. `status` is the per-step verdict; `evidence` is present * only when the step produced a screenshot / video / widget tool call. */ interface PlatformEvalStepResult { stepId: string; stepIndex: number; kind: "prompt" | "toolCall" | "interact" | "assert"; status: "ok" | "fail" | "skipped" | "pending"; reason: string | null; evidence?: PlatformEvalStepEvidence; } /** * Share link for a scenario. The URL embeds the access token; it is visible * to any caller who can read the scenario (same audience as the hosted UI). */ interface PlatformScenarioLink { /** App-relative share path. */ path: string; /** Absolute share URL. */ url: string; } /** A server attached to a scenario (HTTP servers only). */ interface PlatformScenarioServer { id: string; name: string; url: string | null; useOAuth: boolean; } /** Summary of a published scenario, as returned by the list endpoint. */ interface PlatformScenarioSummary { id: string; projectId: string | null; name: string; description: string | null; /** Who can use it: "project_members" | "invited_only" | "anyone_with_link". */ mode: string | null; /** Chat surface style the scenario renders (e.g. "claude", "chatgpt"). */ hostStyle: string | null; hostId: string | null; hostName: string | null; serverCount: number; serverNames: string[]; link: PlatformScenarioLink | null; createdAt: number | null; updatedAt: number | null; } /** A scenario's full read-only settings: summary plus host execution config. */ interface PlatformScenarioDetail extends PlatformScenarioSummary { /** Model the scenario chats with. */ modelId: string | null; systemPrompt: string | null; temperature: number | null; requireToolApproval: boolean; servers: PlatformScenarioServer[]; } /** * Response of `POST /projects/{p}/servers/{s}/doctor` — the hosted doctor * result, passed through verbatim by the API. Includes the probe outcome, * connection state, and full tools/resources/prompts listings with * per-collection checks, which is why `show_servers` needs only one call * per server. */ type PlatformDoctorReport = ServerDoctorResult; /** * Response of `POST /projects/{p}/tunnels` — the relay grant the caller * hosts the tunnel WebSocket with, plus the registered server record's * identity. The `url` embeds the plaintext `?k=` bearer secret (also * persisted on the server record so evals/scenarios can target it); treat * the whole grant as a credential. Re-creating rotates the secret and * revokes the previous grant. */ interface PlatformTunnelGrant { serverId: string; name: string; /** True when a server record with this name already existed. */ existed: boolean; /** Previous URL, present when the existing record's URL was replaced. */ previousUrl?: string; /** Previous transport, present when the record existed (e.g. "stdio"). */ previousTransportType?: string; slug: string; /** Public tunnel URL with the `?k=` bearer secret. */ url: string; /** Bearer for the relay edge WebSocket handshake. */ connectToken: string; connectTokenExpiresAt?: number; relayWsUrl: string; secretVersion?: number; } /** Response of `POST /projects/{p}/tunnels/{serverId}/close`. */ interface PlatformTunnelClosed { serverId: string; status: string; } interface PlatformJourney { id: string; projectId: string; name: string; /** What the persona is trying to accomplish. Drives the whole run. */ goal: string; personaId: string; /** The swarm container this journey was authored under, if any. Opaque. */ swarmId: string | null; /** Environments this journey fans out across. Empty on a host-pinned journey. */ environmentIds: string[]; serverAttachmentId?: string; /** Sessions run against EACH target. Total sessions = targets x this. */ sessionsPerTarget: number | null; maxTurns: number | null; createdAt: number; updatedAt: number; } interface PlatformJourneyRunTarget { hostId: string; hostName?: string; /** Execution identity. Two targets can share a `hostId`. */ targetId?: string; modelId?: string; } interface PlatformJourneyRunAttempt { chatSessionId: string | null; hostId: string; targetId: string | null; sessionIndex: number; status: string; errorCode: string | null; errorMessage: string | null; } interface PlatformJourneyRun { id: string; projectId: string; journeyId: string; /** * The batch this run was launched with. Sibling runs of one co-launched * wave share it; a solo relaunch is a wave of one. */ waveId?: string; status: "running" | "completed" | "partial" | "failed" | "rate_limited"; /** * True when someone STOPPED this run. It reports `status: "failed"` because * the backend records cancellation as a marker rather than a status literal * — so check this before showing a run as a failure. */ canceled: boolean; /** True when the runner went silent and the watchdog settled the run. */ stale: boolean; /** Raw marker behind `canceled` / `stale`, when present. */ error?: string; summary: { total: number; succeeded: number; failed: number; rateLimited: number; }; targets: PlatformJourneyRunTarget[]; persona?: { personaId: string | null; name: string | null; role: string | null; }; /** Per-session execution records. Present on the single-run read. */ attempts?: PlatformJourneyRunAttempt[]; targetSummaries?: Array<{ hostId: string; targetId?: string; total: number; succeeded: number; failed: number; rateLimited: number; }>; createdAt: number; lastHeartbeatAt?: number; /** Common insights envelope (detail response only; lists stay compact). * Absent on servers deployed before the envelope existed. */ insights?: PlatformInsightsEnvelope; } interface PlatformJourneyRunSession { /** * The session's document id — the same value `listChatSessions` returns as * `id`, so a session found here can be looked up there. */ id: string; /** * The RUNTIME key for the same session, which the chat transport and the * app's deep links use. Distinct from `id` and not interchangeable with it. */ chatSessionId: string; projectId: string; hostId?: string; runId?: string; journeyId?: string; personaId?: string; personaLabel?: string; /** * ARCHIVAL state (`active` | `archived`) — a run session stays `active` * forever unless archived, so this says nothing about how it went. Read * `outcome` for the verdict. */ status: string | null; /** * How this session's run attempt ended: `succeeded` | `failed` | * `rate_limited` | `running` | `pending`, or null when the attempt cannot * be matched (historical runs). Absent on servers that predate the field. */ outcome?: string | null; readiness: unknown; goalScore: unknown; messageCount: number; preview?: string; modelId?: string; startedAt: number | null; lastActivityAt: number | null; } interface PlatformScenario { id: string; environmentId: string; name: string; /** * Who may open the share link. `anyone_with_link` is the widest — anyone * holding the URL, signed in or not. */ mode: "project_members" | "invited_only" | "anyone_with_link"; /** * Bumped whenever access NARROWS (mode change, member removal, link * rotation). Sessions minted under an older version stop working, which is * what makes those changes take effect at once rather than at expiry. */ accessVersion: number; /** The share link. Null when the scenario has no link token. */ link: string | null; /** False when the environment was already published and this returned it. */ created?: boolean; /** * True when `publishScenario`'s create-time overrides (`name`, * `description`, `mode`) were NOT applied because the environment was * already published. Paired with `created: false`. * * Declared here rather than as an intersection at the two call sites that * return it. Both did — `Promise` — which typed the field for a caller who read it off the * return value and left it invisible to anything holding a * `PlatformScenario`, including the spec↔types parity check. A field the * wire really carries belongs on the interface that describes the wire. */ overridesIgnored?: boolean; } interface PlatformScenarioDeleted { environmentId: string; /** False when the environment had no scenario — not an error. */ deleted: boolean; id?: string; } /** Result of `POST /projects/{p}/journeys/{journeyId}/runs`. */ interface PlatformJourneyRunLaunched { /** The run id. Poll `getJourneyRun` with it, or stop it with `cancel`. */ id: string; journeyId: string; projectId: string; /** * Always `"running"` — the run row exists and its fan-out has been started. * The response is a 202: nothing here says the journey has finished, only * that it is under way. */ status: string; /** * True when an idempotency key replayed onto a run that ALREADY existed, so * nothing new was started. A retry of a dropped response lands here, which * is how you tell "I launched it" from "it was already going". */ deduped: boolean; } /** Result of `POST /projects/{p}/journey-runs/{runId}/cancel`. */ interface PlatformJourneyRunCanceled { id: string; /** The run's terminal status after the cancel settled it. */ status: PlatformJourneyRun["status"]; canceled: true; /** True when the run was ALREADY canceled and this call did nothing. */ alreadyCanceled: boolean; /** Attempts this call moved to terminal. Zero on an idempotent replay. */ finalized: number; } /** A reusable synthetic character. The GOAL lives on the journey, not here. */ interface PlatformPersona { /** Durable id — what journeys reference and every route here addresses. */ id: string; projectId: string; /** * Stable slug key, shared with exported session data. Useful for correlating * transcripts; NOT an address for this API. */ slug: string; name: string; role: string; notes: string | null; /** manual | generated | cluster — how the persona came to exist. */ source: string; seedKeywords?: string[]; avatar: { shape: number | null; palette: number | null; }; createdAt: number; updatedAt: number; } /** Result of deleting a persona. The delete is SOFT: history still resolves it. */ interface PlatformPersonaDeleted { id: string; projectId: string; deleted: true; } /** Result of archiving a journey. Its runs and transcripts stay readable. */ interface PlatformJourneyArchived { id: string; projectId: string; archived: true; } /** A swarm CONTAINER: shared execution config for the journeys authored in it. */ interface PlatformSwarm { id: string; projectId: string; name: string; description: string | null; /** Default fan-out for journeys authored under this container. */ environmentIds: string[]; sessionsPerTarget: number | null; maxTurns: number | null; createdAt: number; updatedAt: number; } interface PlatformSwarmArchived { id: string; projectId: string; archived: true; } /** One rubric criterion's tally over a run. The four counts are NOT mergeable. */ interface PlatformScorecardCriterion { id: string; label: string | null; kind: string; passCount: number; failCount: number; /** Claimed for grading and unfinished — includes crashed runners. */ pendingCount: number; /** * Sessions whose GRADING broke. Distinct from `failCount` on purpose: * folding them together makes a crashed judge look like a regression. */ failedGradingCount: number; } /** Deterministic rubric result for one run. No model involved. */ interface PlatformRunScorecard { runId: string; /** * Every criterion the run's rubric declared, in snapshot order — including * ones nothing was graded against. An absent row would be indistinguishable * from a criterion that was never configured. */ criteria: PlatformScorecardCriterion[]; sessionsTotal: number; sessionsGraded: number; } interface PlatformSwarmOverviewFinding { criterionId: string; label: string | null; kind: string | null; failCount: number; pendingCount: number; failedGradingCount: number; /** * The DENOMINATOR for any rate you compute. Never divide by the session * total — 3 failures of 4 graded sessions out of 40 attempted is not 7.5%. */ sessionsGraded: number; /** Consecutive runs of this journey where the criterion failed. */ runStreak: number; } interface PlatformSwarmOverviewRun { runId: string; journeyId: string; journeyName: string; journeyArchived: boolean; personaName: string; status: string; waveId?: string; summary: { total: number; succeeded: number; failed: number; rateLimited: number; }; goalCompletion: { gradedCount: number; passedCount: number; avgScore: number | null; pendingCount: number | null; failedCount: number | null; } | null; findings: PlatformSwarmOverviewFinding[]; targets: Array<{ hostName: string; modelId: string; environmentName?: string; }>; createdAt: number; } /** Project-wide roll-up across recent runs. */ interface PlatformSwarmOverview { runs: PlatformSwarmOverviewRun[]; runsConsidered: number; goalCompletion: { gradedCount: number; passedCount: number; /** `null` when nothing is graded yet — never 0, which would read as "all failed". */ passRate: number | null; runsWithGrades: number; trend: Array<{ dayStartMs: number; gradedCount: number; passedCount: number; passRate: number; }>; }; } /** A criterion that keeps failing, tracked across waves. */ interface PlatformSwarmFinding { id: string; /** Stable identity across waves — what makes a streak a streak. */ fingerprint: string; dimension: string; subject: { kind: string; id: string; label: string; }; /** new | recurring | regressed | resolved. */ status: string; occurrenceCount: number; lastSeenWaveId: string; firstSeenAt: number; lastSeenAt: number; resolvedAt: number | null; dismissedAt: number | null; updatedAt: number; } interface PlatformFindingDismissed { id: string; projectId: string; dismissed: boolean; } /** * The common actionable-insights envelope — one shape across Eval runs, * Swarm waves, and User Testing windows. Hand-mirrored from the backend's * `lib/insightsEnvelope.ts` (two-repo type discipline). * * Reading rules for consumers (including agents): * - Findings are AGGREGATED per run/wave/window; `evidence` points at * exemplar sessions or iterations, not one finding per session. * - Only `actionTarget: "mcp_server"` with `actionability: "ready"` * authorizes proposing a change to the MCP server. Every other action * target names different work (agent config, eval case, environment, * investigation) and must NOT be "fixed" in server code. * - `findings` is empty unless `status === "completed"`. An empty completed * list is a real "nothing to act on" answer. * - Reads never trigger generation; `status` is observational. */ type PlatformInsightsStatus = "not_available" | "not_requested" | "pending" | "completed" | "failed"; type PlatformInsightScope = { kind: "eval_run"; id: string; } | { kind: "swarm_wave"; id: string; runId: string; } | { kind: "user_testing_window"; id: string; scenarioId: string; windowStartAt: number; windowEndAt: number; }; type PlatformInsightAttribution = "unknown" | "server_contract" | "server_runtime" | "server_capability" | "agent_or_prompt" | "test_design" | "environment"; type PlatformInsightActionTarget = "investigate" | "mcp_server" | "agent_configuration" | "eval_case" | "environment"; type PlatformInsightActionability = "informational" | "investigate" | "ready"; interface PlatformActionableFindingEvidence { sessionId?: string; iterationId?: string; kind: "tool_error" | "transcript" | "feedback" | "judge" | "contrast"; /** Scrubbed and clipped at the producer. */ excerpt: string; toolName?: string; errorCode?: string; } interface PlatformActionableFinding { /** Stable remediation id (`rf_<16 hex>`) — survives dynamic error values. */ id: string; /** The registry signal this derives from; several findings may share one. */ signalFingerprint: string; title: string; category: "unknown" | "tool_contract" | "tool_runtime" | "capability_gap" | "workflow" | "agent_behavior" | "test_design" | "environment"; attribution: PlatformInsightAttribution; actionTarget: PlatformInsightActionTarget; actionability: PlatformInsightActionability; severity: "info" | "low" | "medium" | "high"; confidence: "low" | "medium" | "high"; /** Deterministic observation — counts and identities, never model prose. */ observed: string; rootCause?: string; recommendation: string; acceptanceCriteria: string[]; affected: { count: number; total: number; unit: "iterations" | "sessions"; }; patternSlug?: string; /** Present only when a server (and, for tool surfaces, tool) resolved * against the pinned snapshot. Required for `mcp_server`/`ready`. */ target?: { serverId: string; toolName?: string; surface: "description" | "input_schema" | "output_schema" | "handler" | "server_instructions" | "capability"; fieldPath?: string; snapshotHash: string; currentDefinition?: { description?: string; inputSchemaJson?: string; outputSchemaJson?: string; truncated: boolean; }; }; evidence: PlatformActionableFindingEvidence[]; } interface PlatformInsightsEnvelope { schemaVersion: 1; scope: PlatformInsightScope; status: PlatformInsightsStatus; reasonCode: string | null; retryable: boolean; error: { code: string; message: string; } | null; generatedAt: number | null; updatedAt: number | null; summary: string | null; coverage: { unit: "iterations" | "sessions"; analyzed: number; total: number; gradedCount?: number; feedbackCount?: number; truncated: boolean; lowConfidence: boolean; }; findings: PlatformActionableFinding[]; /** Swarm only. Launch outcomes never appear as findings. */ runHealth?: { targets: Array<{ subjectKind: "environment" | "host"; subjectId: string; subjectLabel: string; attempted: number; succeeded: number; failed: number; rateLimited: number; }>; }; truncation: { truncated: boolean; omittedFindings: number; omittedEvidence: number; contractTruncated: boolean; }; } /** * A repository whose pull requests run an eval suite. * * `outagePolicy: null` is a REAL state, not a missing value: it means nobody * chose a policy for this repository (it was connected before the choice * existed). The effective behaviour is `fail_open`, but reporting `fail_open` * would say someone picked it. */ interface PlatformEvalCheckRepo { id: string; /** `owner/repo`, canonicalized by the platform. */ repo: string; enabled: boolean; /** The eval suite this repository's pull requests run. */ suiteId: string | null; projectId: string | null; outagePolicy: "fail_open" | "fail_closed" | null; createdAt: number | null; updatedAt: number | null; } /** What `GET /organizations/{id}/eval-check-repos` answers. */ interface PlatformEvalCheckRepos { organizationId: string; /** * Whether GitHub Checks is available for this organization at all. FALSE and * "available, nothing connected" are different situations, and only one of * them is fixed by connecting a repository — so it travels rather than being * flattened into an empty list. */ available: boolean; /** The repositories already connected. */ items: PlatformEvalCheckRepo[]; /** * The repositories the MCPJam GitHub App can reach — the choices a connect * has. * * `null` means the question could not be ASKED: the lookup itself failed * (GitHub unreachable, or the call errored). The already-connected `items` * above still stand — they need no GitHub call. * * `[]` means it WAS asked and came back with nothing. That covers two * situations the platform does not distinguish: the App is installed but * reaches no repository, and this deployment has no App installation at all. * If a connect is failing and this is empty, check the installation before * assuming a permissions problem. */ connectable: Array<{ repo: string; }> | null; } /** `201` response of `POST /organizations/{id}/eval-check-repos`. */ interface PlatformEvalCheckRepoConnected { id: string; organizationId: string; projectId: string; suiteId: string; repo: string; outagePolicy: "fail_open" | "fail_closed"; } /** Receipt for an eval-run insights (serverQuality) request. 202. */ interface PlatformEvalRunInsightsRequested { runId: string; projectId: string; status: "pending"; } /** * Receipt for an eval-run judge request. 202 — grading runs async. Poll the run * detail's `judges.goalCompletion` rather than re-requesting. */ interface PlatformEvalRunJudgeRequested { runId: string; projectId: string; status: "pending"; } /** LLM analysis over a whole wave. Requested explicitly; produced async. */ interface PlatformWaveInsights { waveId: string; /** pending | completed | failed. Poll rather than re-requesting. */ status: "pending" | "completed" | "failed"; /** Directed lane. Null until generation completes. */ insights: unknown | null; /** * Discovery lane — what the model noticed unprompted. Null while only the * directed lane has finished, which is a normal intermediate state. */ discovery: unknown | null; errorCode: string | null; errorMessage: string | null; updatedAt: number; } /** Receipt for a wave-insights request. 202: scheduled, not done. */ interface PlatformWaveInsightsRequested { waveId: string; projectId: string; status: "pending"; } interface PlatformWaveInsightsCanceled { waveId: string; projectId: string; canceled: true; } /** * What the caller may do in a project, so an agent on a static surface can * check before it plans rather than discovering a 403 mid-task. * * DESCRIPTIVE. Every enforcement point still runs on the write path; a stale * answer here produces the same clean denial it always would. */ interface PlatformCapabilities { projectId: string; organizationId: string | null; /** Organization role: guest | member | admin | owner. */ role: string; projectRole: string; /** Which channel the server resolved this request to. */ surface: string; features: { sandboxes: { enabled: boolean; /** off | dark | enforce. Only `enforce` turns a disabled flag into a refusal. */ mode: string; enforced: boolean; reason?: string; }; }; plan: { name: string; limits: Record; features: Record; } | null; /** * The booleans to branch on. Note that the exposure-REDUCING ones * (`cancelJourneyRun`, `unpublishUserTestingScenario`) stay true for an org * that has lost the beta — losing the feature is exactly when stopping it * matters most. */ can: { readSwarms: boolean; readUserTesting: boolean; writeSwarms: boolean; launchJourneyRun: boolean; cancelJourneyRun: boolean; publishUserTestingScenario: boolean; unpublishUserTestingScenario: boolean; /** * Mode changes, member invites/removals, link rotation, renames — the * scenario controls an ordinary MEMBER can use. Guest execution is not * covered here; it is the one exposure control that needs admin, and it * has its own key below. */ changeUserTestingExposure: boolean; /** The guest-execution spend caps. Genuinely project-admin upstream. */ manageUserTestingGuestExecution: boolean; requestInsights: boolean; /** Reading eval suites, runs, iterations and traces. */ readEvals: boolean; /** Authoring suites and cases — every eval write short of deleting. */ writeEvalSuites: boolean; launchEvalRun: boolean; /** * Deleting a suite SOMEONE ELSE created — the project admin tier. The * creator of a suite may always delete it whatever their role, so a * `false` here does not mean you cannot delete your own. */ deleteAnyEvalSuite: boolean; /** Same tier and same creator exception, for runs. */ deleteAnyEvalRun: boolean; /** * Whether the trace export surface is open. Export still filters row by * row against the caller, so this is not a promise that every session in * the project lands in the file. */ exportEvalTraces: boolean; }; } /** Draft output from the generation endpoints. Shape varies by request. */ interface PlatformGenerationDrafts { [field: string]: unknown; } /** One session a visitor had with a published scenario. SUMMARY, not transcript. */ interface PlatformUserTestingSession { /** The address for the transcript route. */ id: string; chatSessionId: string; messageCount: number; /** First message only. The transcript is a separate, explicit read. */ preview: string; modelId?: string; toolCallCount?: number; /** The visitor abandoned mid-flow because a server demanded auth. */ authInterrupted?: boolean; visitor: { displayName?: string; segment?: string; authType?: "signedIn" | "guest"; recency?: "new" | "returning"; deviceKind?: string; language?: string; }; feedback: { rating: number | null; comment: string | null; count: number; }; theme?: { id: string; label: string | null; keywords: string[]; }; startedAt: number; lastActivityAt: number; } /** One projected transcript message. Tool payloads and blobs are dropped. */ interface PlatformTranscriptMessage { role: string; text: string; toolName?: string; createdAt?: number; } /** * A session's transcript, paged. * * The stored blob URL is never returned: it is a direct handle with no further * authorization, so handing it out would turn one authorized read into an * unbounded, unrevocable one. */ interface PlatformUserTestingSessionDetail { id: string; scenarioId: string; chatSessionId: string | null; modelId: string | null; startedAt: number | null; lastActivityAt: number | null; /** * `null` — never 0 — when the transcript could not be read, which is why * this is nullable and the list DTO's is not. Zero would be a claim the * visitor said nothing, the opposite of what an unreadable blob means, and a * caller that only checked `messageCount` would act on it. */ messageCount: number | null; /** * True when the stored conversation could not be read. Distinct from an * empty `messages`, which means the visitor genuinely said nothing. */ transcriptUnavailable?: boolean; messages: PlatformTranscriptMessage[]; nextCursor?: string; } /** * Scenario metadata after an update. * * NO `accessVersion`, deliberately: a mode change bumps it upstream, but the * envelope the route re-reads does not carry the new value, so the field was * null on every response while documenting itself as the revocation signal. * The publish response (`PlatformScenario`) carries the real one. */ interface PlatformUserTestingScenario { id: string; projectId: string; name: string | null; description: string | null; mode: string | null; } /** * Scenario detail — the read shape, widened with the environment link and * the insights envelope. */ interface PlatformUserTestingScenarioDetail extends PlatformUserTestingScenario { environmentId: string | null; /** * Present when the caller may have it. The envelope is gated on workspace * MEMBERSHIP while the scenario itself is visible more widely, so a * lower-privilege viewer gets the scenario without this field rather than * an error — same degradation as an older server that cannot produce one. */ insights?: PlatformInsightsEnvelope; } /** Guest execution caps — the spend dial for anonymous visitors. */ interface PlatformGuestExecution { enabled: boolean; computerEnabled: boolean; sharedSkillsEnabled: boolean; dailyCreditCap: number; dailyComputerStartCap: number; maxConcurrentComputers: number; harnessEnabled?: boolean; dailyHarnessSpendCapMicros?: number; dailyHarnessCallCap?: number; maxConcurrentHarnessRuns?: number; } interface PlatformUserTestingInsightsRequested { scenarioId: string; projectId: string; windowId: string; status: "pending"; } /** * One saved server a URL could refer to, offered when it matches more than one. * * Present only on an `AMBIGUOUS_SERVER` error. Without it that refusal is a * dead end on every surface that is not a browser: the caller is told to * re-send with a `serverId` and has no way to discover which ids exist. */ interface PlatformServerConnectionCandidate { id: string; name: string; /** Redacted — query values are replaced, because a keyed-endpoint URL's * query can be the credential itself. */ url: string; } interface PlatformServerConnectionError { code: string; message: string; /** Whether retrying THIS request could succeed. False for a denied consent * or an unsupported auth method, where only a different action helps. */ retryable: boolean; candidates?: PlatformServerConnectionCandidate[]; } /** * The state of one "connect this MCP server" request. * * Returned by every server-connection route, so a caller polls the same shape * it created. `handoffUrl` is the exception that proves the rule: it appears * only in the CREATE response, because the raw handoff token exists exactly * once and nothing stores it. */ interface PlatformServerConnection { connectionRequestId: string; status: "discovering" | "awaiting_project" | "awaiting_authorization" | "authorizing" | "validating" | "ready" | "failed" | "expired" | "cancelled"; /** * Where the user finishes in a browser. Present for BOTH * `awaiting_project` and `awaiting_authorization` — choosing a project needs * a page just as much as granting consent does. * * TREAT THIS AS PRIVATE. It is a capability for one person: never post it to * a shared channel, and never let a model echo it into prose. */ handoffUrl?: string; expiresAt: string; projectId?: string; serverId?: string; server?: { id: string; name: string; url: string; enabled: boolean; }; error?: PlatformServerConnectionError; } /** Body for `POST /server-connections`. */ interface PlatformServerConnectionCreateBody { url: string; projectId?: string; /** Disambiguates when a project has several saved servers on one URL. */ serverId?: string; /** Used only when a server row is created; ignored on reuse. */ name?: string; reauthorize?: boolean; } /** * The two words the public vocabulary uses. * * Never `anthropic`/`chatgpt`: a caller writes what the product says, and the * product says "Claude directory readiness" and "OpenAI plugin directory". */ type PlatformReadinessKind = "claude" | "openai"; /** * The submission shapes a HOSTED run may grade. * * The package shapes are real and are deliberately absent here: they need an * upload the API cannot receive, and they run on the local CLI. Listing them * in this type would let a caller write a request the server refuses. */ type PlatformReadinessSubmissionMode = "mcp-only" | "mcp-imported-skills"; type PlatformReadinessLaneStatus = "ready" | "not-ready" | "incomplete"; /** * Every lane either publisher grades, as one union. * * Claude uses five of these and OpenAI seven; the union is their sum rather * than two types, because a client renders a run whose publisher it learns at * runtime. Spelled out rather than left as `string` so a `switch` over lane * copy is exhaustiveness-checked — a lane added here becomes a compile error * at every renderer instead of an unlabelled row in production. */ type PlatformReadinessLane = "runtime-compatibility" | "directory-policy" | "optional-features" | "submission-artifacts" | "experience-insights" | "plugin-package" | "release-contract"; /** * What one lane managed to look at, reported separately from what it found. * * A lane with zero violations and zero evaluated checks is not a pass, and * publishing the denominator is the only way to keep those apart. */ interface PlatformReadinessLaneCoverage { lane: PlatformReadinessLane; status: PlatformReadinessLaneStatus; evaluated: number; notEvaluated: number; notApplicable: number; /** Named inputs the caller could supply to close the gap. */ missingInputs: string[]; } interface PlatformReadinessStageResult { stage: "technical-preflight" | "submission-ready"; status: PlatformReadinessLaneStatus; lanes: PlatformReadinessLane[]; } /** * The model-observation axis, INDEPENDENT of the run's own status. * * `billing_limit_reached` is the value a client keys a top-up prompt on — it * is machine-readable precisely so nobody has to string-match `detail`. */ interface PlatformReadinessObservationState { status: "not-requested" | "pending" | "completed" | "billing-blocked" | "provider-failed" | "invalid-output"; reason?: "not_requested" | "billing_limit_reached" | "provider_error" | "provider_timeout" | "schema_invalid" | "no_evidence" | "cancelled"; detail?: string; } interface PlatformReadinessRun { id: string; readinessKind: PlatformReadinessKind; /** Null only on rows written before the field existed. */ serverId: string | null; serverUrl: string; submissionMode: PlatformReadinessSubmissionMode | null; status: "pending" | "running" | "completed" | "failed" | "cancelled"; overallStatus: PlatformReadinessLaneStatus | null; lanes: PlatformReadinessLaneCoverage[]; stages: PlatformReadinessStageResult[]; authMode: "headless" | "interactive" | "provided-token" | null; capabilities: string[]; attemptCount: number; terminalReason: string | null; errorMessage: string | null; policySnapshotDate: string | null; engineVersion: string | null; sdkVersion: string | null; includeLlmObservations: boolean; llmObservations: PlatformReadinessObservationState; hasReport: boolean; reportUrl: string | null; createdAt: number; updatedAt: number; } /** The `202` receipt. Poll the run detail; do not re-POST. */ interface PlatformReadinessRunReceipt { runId: string; projectId: string; serverId: string; readinessKind: PlatformReadinessKind; /** * The run's status at the moment the start returned. * * `pending` for a fresh start. For a DEDUPED start it is whatever the * existing run is already at — which may be `completed`, because an * idempotency key replayed hours later names a run that finished long ago. * Reporting `pending` unconditionally would send such a caller into a poll * loop for a result it could already read. */ status: "pending" | "running" | "completed" | "failed" | "cancelled"; /** True when an idempotency key replayed an existing run. */ deduped: boolean; includeLlmObservations: boolean; } /** Fields both start endpoints accept. */ interface PlatformReadinessStartBody { /** * Deduplicates a retried POST. * * More load-bearing here than usual: a readiness run dials a third party's * server, and a retried start that created a second run would do that twice. */ idempotencyKey?: string; /** * Add model-backed experience observations. CONSUMES MCPJam CREDITS. * * Off by default. Observations are non-dispositive — they can never make a * server not-ready — and a refused reservation makes no provider call and * completes the run with `llmObservations.reason` of * `billing_limit_reached`. */ includeLlmObservations?: boolean; } interface PlatformOpenAIReadinessStartBody extends PlatformReadinessStartBody { /** * The DECLARED submission shape. REQUIRED, and never inferred. * * Inference reads a forgotten package as `mcp-only`, which reports the * package lane not-applicable — turning a missing input into a clean bill of * health. */ submissionMode: PlatformReadinessSubmissionMode; } /** Suites the hosted agent/API surface can start. OAuth is refused. */ type PlatformConformanceSuiteKind = "protocol" | "apps" | "tasks"; /** The `202` receipt. Poll the run detail; do not re-POST. */ interface PlatformConformanceRunReceipt { runId: string; projectId: string; serverId: string; /** * The run's status at the moment the start returned. * * `queued` for a fresh start. For a DEDUPED start it is whatever the * existing run is already at — which may be `completed`. */ status: string; /** True when an idempotency key replayed an existing run. */ deduped: boolean; requestedSuites: PlatformConformanceSuiteKind[]; } interface PlatformConformanceRunReportSummary { suiteKind: string; status: string; outcome: string | null; score: number | null; pending: number; profileId: string | null; profileVersion: string | null; hasReport: boolean; } interface PlatformConformanceRun { id: string; projectId: string; serverId: string | null; source: string | null; verification: string | null; status: string; outcome: string | null; incompleteReason: string | null; score: number | null; applicable: number; passed: number; failed: number; couldNotRun: number; notApplicable: number; pending: number; advisoryCount: number; requestedSuites: string[]; protocolVersion: string | null; engineVersion: string | null; createdAt: number; completedAt: number | null; durationMs: number | null; reports: PlatformConformanceRunReportSummary[]; /** Relative v1 report URL when a stored report exists (or the run is terminal). */ reportUrl: string | null; } interface PlatformConformanceReportCheck { suiteKind: string; id: string; title: string; groupId: string; status: string; pending: boolean; skipReason?: string; error?: string; } interface PlatformConformanceReportProfile { suiteKind: string; profileId: string | null; profileVersion: string | null; pendingCheckIds: string[]; } /** Bounded failing-check projection. The stored report can be megabytes. */ interface PlatformConformanceReport { runId: string; status: string; outcome: string | null; score: number | null; pending: number; checks: PlatformConformanceReportCheck[]; totalCases: number; /** Failed + could-not-run count, the denominator behind `truncated`. */ totalFailingCases: number; truncated: boolean; profiles: PlatformConformanceReportProfile[]; } declare const DEFAULT_PLATFORM_API_BASE_URL = "https://app.mcpjam.com/api/v1"; interface PlatformApiClientOptions { /** API origin + version prefix. Defaults to the hosted production API. */ baseUrl?: string; /** * Returns the bearer credential for each request: an `sk_` API key or a * WorkOS user JWT. Called per request so rotating/refreshing credentials * stay current. */ getAuth: () => string | Promise; /** Injectable fetch for tests and exotic runtimes. */ fetch?: typeof fetch; /** Per-request timeout. */ timeoutMs?: number; /** Optional User-Agent; ignored by browsers (forbidden header). */ userAgent?: string; } type RequestOptions = { signal?: AbortSignal; /** Stable retry key forwarded to write routes. */ idempotencyKey?: string; }; type ServerScope = { projectId: string; serverId: string; }; declare class PlatformApiClient { private readonly baseUrl; private readonly getAuth; private readonly fetchFn; private readonly timeoutMs; private readonly userAgent?; constructor(options: PlatformApiClientOptions); getMe(options?: RequestOptions): Promise; listModels(options?: RequestOptions): Promise>; listOrganizations(options?: RequestOptions): Promise>; listProjects(params?: { organizationId?: string; }, options?: RequestOptions): Promise>; createProject(params: { body: Record; }, options?: RequestOptions): Promise; updateProject(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; deleteProject(params: { projectId: string; }, options?: RequestOptions): Promise<{ id: string; deleted: boolean; }>; searchRegistryDirectory(params?: { q?: string; source?: string; rowType?: string; endpointKind?: string; verifiedTier?: string; connectableOnly?: boolean; cursor?: string; limit?: number; }, options?: RequestOptions): Promise; getRegistryDirectoryServer(params: { catalogServerId: string; } | { name: string; source?: string; }, options?: RequestOptions): Promise; listRegistryDirectorySources(options?: RequestOptions): Promise>; listRegistryServers(params: { projectId: string; scope?: "global" | "organization" | "all"; }, options?: RequestOptions): Promise>; listRegistryConnections(params: { projectId: string; }, options?: RequestOptions): Promise>; installRegistryDirectoryServer(params: { projectId: string; catalogServerId: string; endpointUrl?: string; expectedContentHash?: string; }, options?: RequestOptions): Promise; installRegistryServer(params: { projectId: string; registryServerId: string; expectedUpdatedAt?: number; }, options?: RequestOptions): Promise; uninstallRegistryServer(params: { projectId: string; registryServerId: string; }, options?: RequestOptions): Promise<{ deleted?: boolean; }>; /** * Start connecting an MCP server URL to a project. * * The response is the ONLY place a `handoffUrl` ever appears — the raw token * behind it is minted once and never stored, so it cannot be re-fetched. * Treat it as a private, single-person capability. */ createServerConnection(params: { body: PlatformServerConnectionCreateBody; }, options?: RequestOptions): Promise; /** Poll one request. Safe to call on a short interval: this path is metered * on its own poll budget rather than the shared per-caller one, so polling * responsively does not spend the budget your other calls need. A 429 here * means the interval itself is too fast — honour `Retry-After`. */ getServerConnection(params: { connectionRequestId: string; }, options?: RequestOptions): Promise; cancelServerConnection(params: { connectionRequestId: string; }, options?: RequestOptions): Promise; /** * Ask for another validation attempt now instead of waiting out the backoff. * * Does not revive a terminal request: after `failed`, `expired`, or * `cancelled`, the way forward is a new request. */ retryServerConnectionValidation(params: { connectionRequestId: string; }, options?: RequestOptions): Promise; listProjectServers(params: { projectId: string; }, options?: RequestOptions): Promise>; createProjectServer(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; getProjectServer(params: { projectId: string; serverId: string; }, options?: RequestOptions): Promise; updateProjectServer(params: { projectId: string; serverId: string; body: Record; }, options?: RequestOptions): Promise; deleteProjectServer(params: { projectId: string; serverId: string; }, options?: RequestOptions): Promise<{ id: string; deleted: boolean; }>; listEvalSuites(params: { projectId: string; }, options?: RequestOptions): Promise>; listChatSessions(params?: { projectId?: string; status?: string; limit?: number; before?: string; }, options?: RequestOptions): Promise>; /** * Send ONE message to a project's MCP servers and get the model's reply plus * the telemetry a participant in the conversation could not see: which tools * were called, with what arguments, what came back, and what it cost. * * Omit `sessionId` to start a session; pass the one this returns to * continue it. Configuration (model, target, system prompt, tool mode) pins * on the FIRST turn — a continuation that resends any of it is refused * rather than silently repinning. * * `idempotencyKey` is REQUIRED and must be stable for the triggering intent, * NOT freshly minted per HTTP attempt. This call spends model credits, and a * per-attempt key deduplicates nothing: a timeout-and-retry would run and * bill the turn twice. With a stable key, a retry replays the completed * turn instead. */ sendChatMessage(params: { idempotencyKey: string; message: string; projectId?: string; sessionId?: string; modelId?: string; environmentId?: string; serverIds?: string[]; systemPrompt?: string; temperature?: number; maxSteps?: number; toolMode?: PlatformToolMode; allowedServerIds?: string[]; allowedTools?: string[]; maxToolCalls?: number; }, options?: RequestOptions): Promise; /** * Session metadata plus a bounded window of raw transcript messages. * * The companion to {@link getChatSessionTrace}: spans reference messages by * absolute index, so resolving a span to the payload that produced it needs * both reads. */ getChatSession(params: { sessionId: string; projectId?: string; afterMessageIndex?: number; limit?: number; }, options?: RequestOptions): Promise; /** * Per-turn execution spans: tool latency, token usage, message indices. * * INCREMENTAL BY DEFAULT — returns the LATEST turn, not the whole session. * Reach older turns with `turnId` or `afterPromptIndex`, and use * `includeSpans: false` for cheap summaries when deciding which turn to pull. */ getChatSessionTrace(params: { sessionId: string; projectId?: string; turnId?: string; afterPromptIndex?: number; limit?: number; includeSpans?: boolean; }, options?: RequestOptions): Promise; /** * The unified, cross-surface sessions feed for one project. * * `q` is optional HERE (omitted = the recency feed) even though the * `search_sessions` operation requires it: a client method is the general * transport, and list-mode is a legitimate use of the endpoint. The * operation narrows that on purpose — an agent asking for "the sessions" * unfiltered is almost never what its user meant. * * `sourceTypes` is CSV-joined because the endpoint takes a repeated-value * `sourceType` param as one comma-separated string; an empty array is sent * as nothing at all rather than as `sourceType=`, which the backend would * reject. * * `cursor` passes through unrenamed — it is an opaque Convex cursor, so echo * back exactly what the previous page returned and never construct one. */ listSessions(params: { projectId: string; q?: string; scope?: "titles" | "transcripts"; sourceTypes?: string[]; status?: string; limit?: number; cursor?: string; }, options?: RequestOptions): Promise; listScenarios(params: { projectId: string; }, options?: RequestOptions): Promise>; getScenario(params: { projectId: string; scenarioId: string; }, options?: RequestOptions): Promise; listClients(params: { projectId: string; includePrivateBacking?: boolean; }, options?: RequestOptions): Promise>; /** * `GET /projects/{p}/clients/{client}` — `client` is a NAME or an ID. * * Name resolution happens server-side, where one implementation owns the * eligibility and ambiguity rules. A client-side list-and-scan would be a * second answer to "is this name ambiguous?", and would also have to * re-implement the private-backing filter to avoid resolving a name the * server would not. */ getClient(params: { projectId: string; client: string; includePrivateBacking?: boolean; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/clients` — create a client either from a built-in * template (`{ name, template, theme? }`) or from a full config * (`{ name, config }`). Returns the created client detail. */ createClient(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; /** * `PATCH /projects/{p}/clients/{client}` — rename and/or edit the config. * * The body carries the compare-and-set tokens the canonical route requires * (`expectedConfigId` for a config edit, `expectedName` for a rename); a * stale one comes back as a 409 whose `details` names the current value. */ updateClient(params: { projectId: string; client: string; body: Record; }, options?: RequestOptions): Promise; setClientServers(params: { projectId: string; client: string; serverIds: string[]; optionalServerIds?: string[]; expectedConfigId: string; expectedImpact?: PlatformClientImpact; }, options?: RequestOptions): Promise; duplicateClient(params: { projectId: string; client: string; name?: string; }, options?: RequestOptions): Promise; deleteClient(params: { projectId: string; client: string; body?: Record; }, options?: RequestOptions): Promise; /** @deprecated Use {@link listClients}. Calls the deprecated `/hosts` alias. */ listHosts(params: { projectId: string; }, options?: RequestOptions): Promise>; /** @deprecated Use {@link getClient}. Calls the deprecated `/hosts` alias. */ getHost(params: { projectId: string; hostId: string; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/hosts` — create a host either from a built-in template * (`{ name, template, theme? }`) or from a full host config * (`{ name, config }`). Returns the created host detail. * * @deprecated Use {@link createClient}. Calls the deprecated `/hosts` alias. */ createHost(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; /** @deprecated Use {@link updateClient}. Calls the deprecated `/hosts` alias. */ updateHost(params: { projectId: string; hostId: string; body: Record; }, options?: RequestOptions): Promise; /** @deprecated Use {@link setClientServers}. Calls the deprecated `/hosts` alias. */ setHostServers(params: { projectId: string; hostId: string; serverIds: string[]; optionalServerIds?: string[]; }, options?: RequestOptions): Promise<{ hostId: string; hostConfigId: string; }>; /** @deprecated Use {@link duplicateClient}. Calls the deprecated `/hosts` alias. */ duplicateHost(params: { projectId: string; hostId: string; name?: string; }, options?: RequestOptions): Promise; /** @deprecated Use {@link deleteClient}. Calls the deprecated `/hosts` alias. */ deleteHost(params: { projectId: string; hostId: string; body?: Record; }, options?: RequestOptions): Promise; listEnvironments(params: { projectId: string; includeArchived?: boolean; }, options?: RequestOptions): Promise>; /** * What this deployment's environment surface supports. * * CALL THIS BEFORE SENDING `modelId`. The SDK ships independently of the * backend, and a field an older deployment does not know is a hard validator * error there rather than a silently ignored one. A deployment too old to * answer reports `false` for everything, which is the correct assumption. */ getEnvironmentCapabilities(params: { projectId: string; }, options?: RequestOptions): Promise; getEnvironment(params: { projectId: string; environmentId: string; }, options?: RequestOptions): Promise; /** * The launch preview: the host config, closed server set, and pinned plugin * versions this environment resolves to right now. A resolvable-today * failure (a disabled pinned plugin, an empty server set) is a 409 whose * `details.code` carries the specific `ENV_*` reason. */ resolveEnvironment(params: { projectId: string; environmentId: string; }, options?: RequestOptions): Promise; createEnvironment(params: { projectId: string; body: PlatformEnvironmentCreateBody; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/environments/ensure-adhoc` — GET-OR-CREATE an UNNAMED, * content-addressed environment for a composed stack. * * Distinct from `createEnvironment`, which mints a NAMED row that lands in * the project's environment list forever. A composed stack is a throwaway: * the caller wants to run this exact combination, not to add a permanent * entry someone else has to reason about. * * Deduped server-side by a fingerprint of the stack, so the same stack * always returns the same environment (`created: false` after the first * call) and a retried launch converges instead of accumulating rows. */ ensureAdhocEnvironment(params: { projectId: string; body: PlatformAdhocEnvironmentBody; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/environments/{id}/name` — PROMOTE an ad-hoc * environment to a named one, in place. * * The ONLY promotion path: `updateEnvironment` cannot do it. The platform * keeps the two apart because its rename is admin-gated and refuses a row * that already has a name, while promotion is member-gated and refuses one * that already has a name. Routing promotion through the rename would either * open it to members or leave ad-hoc rows unnameable. */ nameEnvironment(params: { projectId: string; environmentId: string; body: PlatformEnvironmentNameBody; }, options?: RequestOptions): Promise; /** * Only the fields you pass change. Pass `null` for `serverAttachmentId`, * `modelId`, `skillSelection`, or `pluginVersionIds` to CLEAR them; omitting * a field leaves it alone. */ updateEnvironment(params: { projectId: string; environmentId: string; body: PlatformEnvironmentUpdateBody; }, options?: RequestOptions): Promise; /** * Archive (not delete): the row is kept and can be restored. Archiving frees * the name for a new live environment. */ archiveEnvironment(params: { projectId: string; environmentId: string; expectedRevision: number; }, options?: RequestOptions): Promise; /** * Restore an archived environment. Fails with 409 if the name was taken * while it was archived. Plugin pins whose version rows no longer exist at * all are dropped — compare the returned `pluginVersionIds` against what you * archived to detect that. */ restoreEnvironment(params: { projectId: string; environmentId: string; expectedRevision: number; }, options?: RequestOptions): Promise; listProjectPlugins(params: { projectId: string; }, options?: RequestOptions): Promise>; /** * One imported plugin version with its component projections. Addressed by * the version id alone — access is the version's own project membership, * and historical versions of uninstalled plugins stay readable (eval * snapshots and stale environment pins reference them). */ getPluginVersion(params: { pluginVersionId: string; }, options?: RequestOptions): Promise; listImages(params: { projectId: string; }, options?: RequestOptions): Promise>; getImage(params: { projectId: string; imageId: string; }, options?: RequestOptions): Promise; createImage(params: { projectId: string; body: { name: string; blueprint: string; }; }, options?: RequestOptions): Promise; updateImage(params: { projectId: string; imageId: string; body: { name?: string; blueprint?: string; }; }, options?: RequestOptions): Promise; /** Lint blueprint YAML without saving it. Always resolves (200); an * invalid blueprint is a successful lint with structured errors. */ validateImageBlueprint(params: { projectId: string; body: { blueprint: string; }; }, options?: RequestOptions): Promise; deleteImage(params: { projectId: string; imageId: string; }, options?: RequestOptions): Promise; listImageBuilds(params: { projectId: string; imageId: string; }, options?: RequestOptions): Promise>; /** `POST …/build` — async (202); poll `listImageBuilds` for status. */ buildImage(params: { projectId: string; imageId: string; }, options?: RequestOptions): Promise; promoteImage(params: { projectId: string; imageId: string; }, options?: RequestOptions): Promise; /** Attach the sandbox image to the caller's computer (re-provisions from the * pinned image). */ useImage(params: { projectId: string; imageId: string; }, options?: RequestOptions): Promise; /** Reset the caller's computer to its image (wipes mutable state). */ resetComputer(params: { projectId: string; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/eval-runs` — validates and creates the run, then * detaches execution and responds 202. Poll `getEvalRun` until terminal. */ createEvalRun(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; /** * `GET /projects/{p}/eval-suites/{id}/run-disclosure` — the pre-run * disclosure for a launch plan: what happens to the run's content, keyed by * the SAME destination-affecting subset `createEvalRun` uses * (`caseIds`/`environmentId`/`environmentIds`). Deliberately NOT the * estimator's full arg set — `iterationOverride`/`planCount` only scale * volume, which is not part of this contract, and the inspector server * rejects them rather than silently ignoring them. * * Throws `PlatformApiError` with code `FEATURE_NOT_SUPPORTED` and * `details.reason === "contract_unavailable"` against an inspector * deployment too old to compute this — never treat a missing disclosure as * "nothing to disclose". This is a GUARANTEE only when the deployment's * missing-function error reaches the client unredacted (every non-production * Convex environment, and a production one whose redaction the route can * unambiguously identify as a missing function). Production Convex can * redact that same failure to a generic "Server Error" indistinguishable * from a genuine handler crash; the route disambiguates what it safely can * (a caller who cannot see the suite at all still gets a 404, never this * code), but an ambiguous redacted failure on a suite the caller CAN see * surfaces as a 502 `SERVER_UNREACHABLE` instead — this route has no way to * independently confirm "not deployed yet" over "deployed and broken" in * that one case, and guessing `contract_unavailable` would risk hiding a * real incident. A caller cannot rely on this code alone to detect an * old deployment in production; a 502 does not imply the contract is * available either. */ getEvalRunDisclosure(params: { projectId: string; suiteId: string; caseIds?: string[]; environmentId?: string; environmentIds?: string[]; /** * Disclose for a HOST-axis launch — the attached host a run would be * stamped with (G4c). Mutually exclusive with `environmentId`/ * `environmentIds`: a launch plan resolves on exactly one axis, and the * route rejects the combination with a 400 rather than letting it reach * the backend as an ambiguous query. * * `runnerCapabilities` is deliberately NOT a parameter here. The * inspector route asserts it from the executing process, which is the * only honest source for what that process can run; a client-supplied * value could claim a harness capability the runner does not have. */ namedHostId?: string; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/eval-suites/{id}/environments` — APPEND one * environment to the suite's attachments, atomically. * * Distinct from `updateEvalSuite({ environmentIds })`, which REPLACES the * whole list: an append built on that is a read-modify-write across two * round trips, and a concurrent attach landing in between is silently * detached. Idempotent — attaching an already-attached environment reports * `attached: false` and changes nothing. */ attachEvalSuiteEnvironment(params: { projectId: string; suiteId: string; environmentId: string; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/eval-run-groups` — launch ONE run per target (attached * environments, or attached named hosts) under a single server-minted group * id, and respond 202 with a per-target receipt. * * The ONLY endpoint with grouped-launch semantics: the server bounds the * fan-out, validates every target before launching any of them, and holds * ONE organization concurrency slot for the whole group. `createEvalRun` * accepts a `runGroupId` too, but purely as a display label — it gives N * separate launches no group treatment, which is why a fan-out has to come * through here. * * A per-target failure does not abort its siblings, so read `outcome` rather * than treating the 202 as "everything started". */ createEvalRunGroup(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/eval-suites` — author a runnable suite from test-case * definitions and return the new suite id. Synchronous (does NOT run the * suite; execute it with `createEvalRun`). The same path serves `GET` for * `listEvalSuites`. */ createEvalSuite(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/eval-suites/from-file` — resolve or create a * file-owned suite by declared id. Lookup is by declared id within the * project, never by name. A UI-authored suite has no declared id and * cannot be claimed. */ syncFileOwnedEvalSuite(params: { projectId: string; body: Record; }, options?: RequestOptions): Promise; getEvalRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; /** * `GET /projects/{p}/eval-runs/{runId}/decision-summary` — the canonical run * decision contract: the verdict, the unit its counts are in, the run's own * `EvalVerdictDecision` when it has one, and one page of per-trial * diagnostics. * * ADDITIVE, and newer than most deployments: an API that predates it answers * `404`. A caller that must work against both should use the exported * `readEvalRunDecisionSummary` helper, which falls back over * `listEvalRunIterations` and the same contract assembler rather than * creating a summary of its own. * * `cursor`/`limit` page the DIAGNOSTICS, using the same cursors * `listEvalRunIterations` issues. The response says whether the page it * returned is the whole non-passing set. */ getEvalRunDecisionSummary(params: { projectId: string; runId: string; cursor?: string; limit?: number; }, options?: RequestOptions): Promise; /** * Request (or with `force`, regenerate) the eval run's insights — * serverQuality behind the common envelope. SPENDS the org's model budget; * poll `getEvalRun().insights` rather than re-requesting. */ requestEvalRunInsights(params: { projectId: string; runId: string; force?: boolean; }, options?: RequestOptions): Promise; /** * Request (or with `force`, re-request) LLM-as-judge grading of a finished * run. SPENDS the org's model budget; poll `getEvalRun().judges` rather than * re-requesting. * * `enable` grades a run whose config snapshot has the judge OFF. It is a * per-run answer, not a suite edit — grading reads the snapshot pinned when * the run was created, so turning the judge on for the suite does not reach * an already-recorded run. */ requestEvalRunJudge(params: { projectId: string; runId: string; force?: boolean; enable?: boolean; model?: string; threshold?: number; }, options?: RequestOptions): Promise; /** * The repositories in an organization whose pull requests run an eval suite, * plus what the MCPJam GitHub App can reach. */ listEvalCheckRepos(params: { organizationId: string; }, options?: RequestOptions): Promise; /** * Connect a repository so its pull requests run one eval suite. * * `outagePolicy` is required rather than defaulted: it decides what a check * reports when MCPJam cannot conclude, and a surface that picks silently is * the one that produces repositories nobody chose a policy for. */ connectEvalCheckRepo(params: { organizationId: string; projectId: string; suiteId: string; repo: string; outagePolicy: "fail_open" | "fail_closed"; }, options?: RequestOptions): Promise; listEvalRunIterations(params: { projectId: string; runId: string; cursor?: string; limit?: number; }, options?: RequestOptions): Promise>; /** Full trace envelope (messages + analysis) for one iteration. */ getEvalIterationTrace(params: { projectId: string; runId: string; iterationId: string; }, options?: RequestOptions): Promise; /** Cancel an in-flight run; returns the run in its (now cancelled) state. */ cancelEvalRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; /** * Grant a waiver over a failing run's gate. * * `reason` is stored UNREDACTED for the life of the suite: any surface * collecting one must warn the human first (`GATE_WAIVER_REASON_NOTICE`). * `expiresAt` is epoch ms, must be in the future, and is capped at 30 days * out by the platform — there is no way to ask for a permanent waiver. * * Re-waiving an already-waived run answers `status: "conflict"` with the * EXISTING waiver. That is a normal result, not an error: two active waivers * over one run would make "which reason is on the check" a race. */ createGateWaiver(params: { projectId: string; runId: string; reason: string; expiresAt: number; }, options?: RequestOptions): Promise; /** * The waiver in force over a run, or `null`. * * `eval gate` does NOT need this — the run projection already carries * `gateWaiver`, so the gating path folds a waiver in without a second round * trip. This is the explicit read, for asking the question on its own. */ getGateWaiver(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; /** * Revoke a waiver, putting the gate back. * * IDEMPOTENT: revoking an already-revoked waiver answers * `status: "already_revoked"` and is a SUCCESS, not an error — restamping it * would rewrite who actually ended the waiver. */ revokeGateWaiver(params: { projectId: string; runId: string; waiverId: string; }, options?: RequestOptions): Promise; /** * Start a Claude connector-directory readiness run. * * Deterministic grading is FREE. `includeLlmObservations` is the only field * that can spend, and it defaults off. */ startClaudeReadinessRun(params: { projectId: string; serverId: string; } & PlatformReadinessStartBody, options?: RequestOptions): Promise; /** * Start an OpenAI plugin-directory readiness run. * * `submissionMode` is required by the TYPE as well as by the endpoint, * because it is never inferred: a run with no declared shape reads as * `mcp-only`, which reports the package lane not-applicable and turns a * missing input into a clean bill of health. */ startOpenAIReadinessRun(params: { projectId: string; serverId: string; } & PlatformOpenAIReadinessStartBody, options?: RequestOptions): Promise; /** Lane statuses, coverage and the observation axis. Poll this. */ getReadinessRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; listReadinessRuns(params: { projectId: string; readinessKind?: PlatformReadinessKind; serverId?: string; limit?: number; }, options?: RequestOptions): Promise>; /** * Cancel an in-flight run. * * The executing node learns about this on its next heartbeat and aborts the * run in flight — which matters more than the row's status, because the * thing being stopped is traffic to somebody else's server. */ cancelReadinessRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise<{ runId: string; projectId: string; status: string; }>; /** * The full report: every finding, with its class, provenance, citation and * remediation. * * Returned as `unknown` deliberately. The report's shape is the SDK's * `ClaudeReadinessResult` / `OpenAIReadinessResult`, and importing either * here would pull the whole readiness result model into the platform entry — * which is loaded by surfaces that only ever render a lane status. A caller * that wants the narrow type imports it from `@mcpjam/sdk/browser` and * narrows on `readinessKind`. */ getReadinessReport(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; /** * Start a persisted conformance run against a saved server. * * The target is the saved server the path names — never a caller URL. * OAuth is not startable here. Returns a receipt; poll `getConformanceRun`. */ startConformanceRun(params: { projectId: string; serverId: string; suites?: PlatformConformanceSuiteKind[]; idempotencyKey?: string; protocolVersion?: string; engineVersion?: string; }, options?: RequestOptions): Promise; getConformanceRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; listConformanceRuns(params: { projectId: string; serverId?: string; limit?: number; cursor?: string; }, options?: RequestOptions): Promise>; getConformanceReport(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; /** One row per authored step (status + reason + evidence) for one iteration. */ getEvalRunSteps(params: { projectId: string; runId: string; iterationId: string; }, options?: RequestOptions): Promise>; /** * `GET /projects/{p}/eval-runs/{runId}/compare` — this run against a * baseline. * * Omitting `baseRunId` selects the nearest earlier COMPLETED run in the same * suite. Baseline resolution is server-side on purpose: `listEvalSuiteRuns` * has no cursor, so a client-side walk cannot be bounded-correct, and the * policy belongs beside the backend's other baseline resolvers. * * THROWS `PlatformApiError` (404, `details.reason === "BASELINE_NOT_FOUND"`) * when no baseline resolves — a suite's first run, or one whose whole lookup * window never completed. That is an incomplete comparison, not a failing * one; callers must not map it to a regression. */ compareEvalRun(params: { projectId: string; runId: string; baseRunId?: string; /** * Pin the baseline by SOURCE SHA instead of run id. Mutually exclusive * with `baseRunId` — sending both is a 400. A SHA that resolves to no * completed run in the suite is the ordinary BASELINE_NOT_FOUND 404, not * this error: "we looked and established nothing" stays distinct from * "you asked for something impossible". */ baseCommitSha?: string; previewChars?: number; }, options?: RequestOptions): Promise; listEvalSuiteRuns(params: { projectId: string; suiteId: string; limit?: number; }, options?: RequestOptions): Promise>; getEvalSuite(params: { projectId: string; suiteId: string; }, options?: RequestOptions): Promise; updateEvalSuite(params: { projectId: string; suiteId: string; body: Record; }, options?: RequestOptions): Promise; deleteEvalSuite(params: { projectId: string; suiteId: string; }, options?: RequestOptions): Promise; setEvalSuiteSchedule(params: { projectId: string; suiteId: string; body: Record; }, options?: RequestOptions): Promise; listEvalCases(params: { projectId: string; suiteId: string; }, options?: RequestOptions): Promise>; getEvalCase(params: { projectId: string; suiteId: string; caseId: string; }, options?: RequestOptions): Promise; createEvalCase(params: { projectId: string; suiteId: string; body: Record; }, options?: RequestOptions): Promise; /** * Author several cases in one call. The bulk form of {@link createEvalCase} — * same case body, same identity rules — so an import writes one request per * chunk instead of one per case. */ createEvalCases(params: { projectId: string; suiteId: string; body: Record; }, options?: RequestOptions): Promise; updateEvalCase(params: { projectId: string; suiteId: string; caseId: string; body: Record; }, options?: RequestOptions): Promise; deleteEvalCase(params: { projectId: string; suiteId: string; caseId: string; }, options?: RequestOptions): Promise; generateEvalCases(params: { projectId: string; suiteId: string; body: Record; }, options?: RequestOptions): Promise; validateServer(params: ServerScope & { body?: Record; }, options?: RequestOptions): Promise>; doctorServer(params: ServerScope & { body?: Record; }, options?: RequestOptions): Promise; exportServer(params: ServerScope & { body?: Record; }, options?: RequestOptions): Promise>; listServerTools(params: ServerScope & { body?: Record; }, options?: RequestOptions): Promise>>; listServerResources(params: ServerScope & { body?: Record; }, options?: RequestOptions): Promise>>; listServerPrompts(params: ServerScope & { body?: Record; }, options?: RequestOptions): Promise>>; /** * `POST /projects/{p}/servers/{s}/tools/call` — execute one tool and return * the MCP CallToolResult. Tool-level failures (`isError: true`) are * successful calls; only transport/auth errors throw. */ callServerTool(params: ServerScope & { body: { toolName: string; parameters?: Record; }; }, options?: RequestOptions): Promise>; /** * `POST /projects/{p}/servers/{s}/widgets/render` — render an MCP App * widget headlessly and describe what it produced. * * Defaults return the widget as an ACCESSIBILITY TREE and omit the * screenshot. That is the reverse of the local Inspector route, and * deliberate: the caller here is usually a model, for which a base64 image * it may not be able to see is the most expensive possible way to say * nothing. */ renderServerWidget(params: ServerScope & { body: { toolName: string; parameters?: Record; includeSnapshot?: boolean; includeScreenshot?: boolean; injectOpenAiCompat?: boolean; viewport?: { width: number; height: number; }; }; }, options?: RequestOptions): Promise; /** `POST /projects/{p}/servers/{s}/prompts/get` — render one prompt. */ getServerPrompt(params: ServerScope & { body: { promptName: string; arguments?: Record; }; }, options?: RequestOptions): Promise>; /** `POST /projects/{p}/servers/{s}/resources/read` — read one resource. */ readServerResource(params: ServerScope & { body: { uri: string; }; }, options?: RequestOptions): Promise>; /** * `POST /projects/{p}/tunnels` — register (or revive) a relay tunnel for a * named project server and return the grant the caller hosts the tunnel * WebSocket with. Each call rotates the tunnel secret and revokes any * previous grant, so this is also the rotation path. */ createTunnel(params: { projectId: string; name: string; }, options?: RequestOptions): Promise; /** * `POST /projects/{p}/tunnels/{s}/close` — revoke the live tunnel grant. * The server record (and its slug) is kept so the tunnel revives on the * next `createTunnel`. */ closeTunnel(params: { projectId: string; serverId: string; }, options?: RequestOptions): Promise; listJourneys(params: { projectId: string; }, options?: RequestOptions): Promise>; listJourneyRuns(params: { projectId: string; journeyId: string; cursor?: string; limit?: number; }, options?: RequestOptions): Promise>; getJourneyRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; listJourneyRunSessions(params: { projectId: string; runId: string; cursor?: string; limit?: number; }, options?: RequestOptions): Promise>; /** * Launch a journey. Returns as soon as the run exists — **202**, not a * finished run: a fan-out can take hours, so poll `getJourneyRun` or watch * `listJourneyRunSessions`. * * IDEMPOTENT ON `options.idempotencyKey`, and you want to pass one. A launch * spends model credits, so a retry after a dropped response must not run the * journey twice; replaying a key returns the ORIGINAL run with * `deduped: true`. Omit it and every call starts a new run — the server has * nothing to match a retry against, so it treats each as a new launch. * * Behind the `sandboxes-enabled` beta flag — launching creates exposure and * spend, so an unflagged organization gets a 403 here. */ launchJourneyRun(params: { projectId: string; journeyId: string; waveId?: string; environmentIds?: string[]; }, options?: RequestOptions): Promise; /** * Stop a running journey run. * * Idempotent: cancelling an already-cancelled run succeeds with * `alreadyCanceled: true` rather than conflicting. A run that finished on * its own is a 409 — reporting success there would tell you that you stopped * something that had already completed. * * NOT behind the beta flag, unlike launching: stopping a run must keep * working for an organization that has lost it. */ cancelJourneyRun(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; listPersonas(params: { projectId: string; }, options?: RequestOptions): Promise>; getPersona(params: { projectId: string; personaId: string; }, options?: RequestOptions): Promise; /** * IDEMPOTENT ON `options.idempotencyKey`, and worth passing even though * creating a persona spends nothing: the server replays the key BEFORE it * uniquifies the slug, so a retry without one leaves you with a second, * near-identical persona named `…-2` rather than the row you already made. */ createPersona(params: { projectId: string; name: string; role: string; notes?: string; avatarShape?: number; avatarPalette?: number; }, options?: RequestOptions): Promise; updatePersona(params: { projectId: string; personaId: string; name?: string; role?: string; notes?: string; avatarShape?: number; avatarPalette?: number; }, options?: RequestOptions): Promise; /** * SOFT delete. The persona leaves the roster and cannot be used for new * journeys, but historical runs and sessions keep resolving it — a finished * run does not lose the character it ran as. A second call answers 404, * which cleanup should read as success. */ deletePersona(params: { projectId: string; personaId: string; }, options?: RequestOptions): Promise; getJourney(params: { projectId: string; journeyId: string; }, options?: RequestOptions): Promise; /** IDEMPOTENT ON `options.idempotencyKey`. */ createJourney(params: { projectId: string; goal: string; personaId: string; sessionsPerTarget: number; maxTurns: number; name?: string; swarmId?: string; environmentIds?: string[]; serverAttachmentId?: string; hostIds?: string[]; }, options?: RequestOptions): Promise; /** * `null` CLEARS a field; omitting it leaves it alone. That tri-state is the * only way to say "stop fanning this journey out across environments". * * `sessionsPerTarget` and `maxTurns` must move together — they are one * config object upstream, so a partial update would need a read-modify-write * that could silently clobber a concurrent edit. */ updateJourney(params: { projectId: string; journeyId: string; name?: string; goal?: string; environmentIds?: string[] | null; serverAttachmentId?: string | null; hostIds?: string[]; sessionsPerTarget?: number; maxTurns?: number; }, options?: RequestOptions): Promise; /** * ARCHIVES the journey. Its runs, sessions and scorecards stay readable — * deleting the results of work that already happened is not what anyone * means by removing a journey from their list. */ archiveJourney(params: { projectId: string; journeyId: string; }, options?: RequestOptions): Promise; listSwarms(params: { projectId: string; }, options?: RequestOptions): Promise>; getSwarm(params: { projectId: string; swarmId: string; }, options?: RequestOptions): Promise; /** IDEMPOTENT ON `options.idempotencyKey`. */ createSwarm(params: { projectId: string; name: string; sessionsPerTarget: number; maxTurns: number; description?: string; environmentIds?: string[]; }, options?: RequestOptions): Promise; updateSwarm(params: { projectId: string; swarmId: string; name?: string; description?: string | null; environmentIds?: string[] | null; sessionsPerTarget?: number; maxTurns?: number; }, options?: RequestOptions): Promise; /** * ARCHIVES the container. Journeys authored under it keep working and keep * their `swarmId` — the reference is authoring provenance, not ownership. */ archiveSwarm(params: { projectId: string; swarmId: string; }, options?: RequestOptions): Promise; /** * Draft personas with an LLM. NOTHING IS SAVED — feed what you want to keep * to `createPersona`. That is also why there is no idempotency key: a call * with no effect has no duplicate to prevent, and offering one would imply * the drafts are stable across retries, which they are not. * * Exactly one grounding source: `serverAttachmentId` or `environmentId`. */ generatePersonas(params: { projectId: string; serverAttachmentId?: string; environmentId?: string; journeyCount?: number; personaCount?: number; description?: string; existingPersonas?: Array<{ name: string; role: string; }>; }, options?: RequestOptions): Promise; /** * Draft journeys for a persona. The persona is passed BY VALUE, not by id: * the create flow drafts a persona and its journeys before either exists, * so requiring a saved persona would force you to keep a draft you may * discard. Nothing is saved here either. */ generateJourneys(params: { projectId: string; persona: { name: string; role: string; notes?: string; }; serverAttachmentId?: string; environmentId?: string; journeyCount?: number; description?: string; }, options?: RequestOptions): Promise; getSwarmOverview(params: { projectId: string; }, options?: RequestOptions): Promise; getJourneyRunScorecard(params: { projectId: string; runId: string; }, options?: RequestOptions): Promise; listSwarmFindings(params: { projectId: string; }, options?: RequestOptions): Promise>; dismissSwarmFinding(params: { projectId: string; findingId: string; }, options?: RequestOptions): Promise; undismissSwarmFinding(params: { projectId: string; findingId: string; }, options?: RequestOptions): Promise; getWaveInsights(params: { projectId: string; waveId: string; }, options?: RequestOptions): Promise; /** * Request an LLM pass over a wave. Answers **202** — generation is * scheduled, not done; poll `getWaveInsights`. * * SPENDS against the org's `insightsPerDay` ledger, which is SHARED with * user-testing window insights. `force` regenerates over a wave that already * has insights and spends again; the usual reason to reach for it is a * caller that did not poll. */ requestWaveInsights(params: { projectId: string; waveId: string; force?: boolean; }, options?: RequestOptions): Promise; /** * Cancel an in-flight generation. The recovery path when a request was made * by mistake or its runner went silent — without it a wave stuck `pending` * can only be re-requested with `force`, which spends again. */ cancelWaveInsights(params: { projectId: string; waveId: string; }, options?: RequestOptions): Promise; /** * What this caller may do in the project — role, beta-gate state, plan * limits, and the derived booleans to branch on. * * Ask this BEFORE planning work on a static surface (MCP catalog, CLI, agent * registry), none of which can advertise a per-organization beta. It is * descriptive: the write paths enforce independently, so a stale answer * costs a clean 403 rather than an incorrect success. */ getCapabilities(params: { projectId: string; }, options?: RequestOptions): Promise; /** * `name`, `description` and `mode` are CREATE-TIME overrides applied in the * same call, so the scenario is never briefly live in a wider mode than the * caller asked for. They are ignored on a republish (the response says * `overridesIgnored: true`) — changing an existing scenario is * `updateUserTestingScenario`. */ publishScenario(params: { projectId: string; environmentId: string; name?: string; description?: string; mode?: "project_members" | "invited_only" | "anyone_with_link"; }, options?: RequestOptions): Promise; unpublishScenario(params: { projectId: string; environmentId: string; }, options?: RequestOptions): Promise; /** * Publish an environment as a scenario. * * `name`, `description` and `mode` are CREATE-TIME overrides applied in the * same call, so the scenario is never briefly live in a wider mode than you * asked for. They are ignored on a republish (the response says * `overridesIgnored: true`), because re-applying `mode` would let a routine * idempotent publish widen a scenario someone had narrowed by hand. */ publishUserTestingScenario(params: { projectId: string; environmentId: string; name?: string; description?: string; mode?: "project_members" | "invited_only" | "anyone_with_link"; }, options?: RequestOptions): Promise; /** * Scenario detail, with the common insights envelope when the caller may * have it. `insights` is OPTIONAL: the envelope is gated on workspace * membership while the scenario is visible more widely, so a * lower-privilege viewer — and any server predating the envelope — gets the * scenario without it rather than an error. Treat absence as * `not_available`, never as "no findings". */ getUserTestingScenario(params: { projectId: string; scenarioId: string; }, options?: RequestOptions): Promise; /** * Edit a scenario. SINGLE-CONCERN: send `mode` on its own, or `name` and * `description` together — never both. Identity and exposure are separate * mutations upstream, so a mixed request would have to apply them in * sequence, and a failure between the two leaves the scenario half-updated * on the half that decides who can reach it. */ updateUserTestingScenario(params: { projectId: string; scenarioId: string; name?: string; description?: string; mode?: "project_members" | "invited_only" | "anyone_with_link"; }, options?: RequestOptions): Promise; /** Session SUMMARIES. Transcripts are a separate, explicit read. */ listUserTestingSessions(params: { projectId: string; scenarioId: string; cursor?: string; limit?: number; }, options?: RequestOptions): Promise>; /** * One session's transcript, PAGED and projected to role + text + timing. * * These are real people's conversations with your product. The API never * hands back the stored blob URL, so a caller cannot pass "read this * transcript" onward as an unrevocable capability. */ getUserTestingSession(params: { projectId: string; scenarioId: string; sessionId: string; cursor?: string; limit?: number; }, options?: RequestOptions): Promise; getUserTestingMetrics(params: { projectId: string; scenarioId: string; population?: string; }, options?: RequestOptions): Promise>; /** * Usage breakdown. Read `scan.truncated` before quoting any rate from this: * true means the rates were computed over the most recent N sessions rather * than all of them, and dropping the flag turns a conditional statistic into * an unconditional claim. */ getUserTestingUsage(params: { projectId: string; scenarioId: string; }, options?: RequestOptions): Promise>; listUserTestingFindings(params: { projectId: string; scenarioId: string; }, options?: RequestOptions): Promise>>; /** Also how you learn the CURRENT window id, which the insights read takes. */ getUserTestingSignals(params: { projectId: string; scenarioId: string; }, options?: RequestOptions): Promise>; getUserTestingInsights(params: { projectId: string; scenarioId: string; windowId: string; }, options?: RequestOptions): Promise>; /** * Ask a model to analyze the scenario's current window. **202** — scheduled, * not done. SPENDS against the organization's daily insights budget, which * is SHARED with swarm wave insights. */ requestUserTestingInsights(params: { projectId: string; scenarioId: string; force?: boolean; }, options?: RequestOptions): Promise; cancelUserTestingInsights(params: { projectId: string; scenarioId: string; windowId: string; }, options?: RequestOptions): Promise>; dismissUserTestingFinding(params: { projectId: string; scenarioId: string; findingId: string; }, options?: RequestOptions): Promise>; undismissUserTestingFinding(params: { projectId: string; scenarioId: string; findingId: string; }, options?: RequestOptions): Promise>; /** * Replace the guest-execution caps. * * A full replacement, not a patch: these only mean something as a SET, and * raising one while leaving a stale sibling behind produces a combination * nobody chose. Project ADMIN. */ setUserTestingGuestExecution(params: { projectId: string; scenarioId: string; guestExecution: PlatformGuestExecution; }, options?: RequestOptions): Promise>; /** * Rotate the share link. DESTRUCTIVE and immediate: the old link stops * working and every session on it dies. There is no rotating back. */ rotateUserTestingLink(params: { projectId: string; scenarioId: string; }, options?: RequestOptions): Promise>; /** Upsert by email, so re-inviting someone is not an error. */ upsertUserTestingMember(params: { projectId: string; scenarioId: string; email: string; sendInviteEmail?: boolean; }, options?: RequestOptions): Promise>; removeUserTestingMember(params: { projectId: string; scenarioId: string; member: string; }, options?: RequestOptions): Promise>; /** * Point a scenario at a DIFFERENT environment, keeping its link, members and * session history. The alternative — unpublish and republish — mints a new * link, which means re-sharing it with everyone who had the old one. */ rebindUserTestingScenario(params: { projectId: string; scenarioId: string; environmentId: string; }, options?: RequestOptions): Promise>; private userTestingPath; private userTestingFindingAction; private sharePath; getShareSettings(params: { projectId: string; resourceType: "scenario" | "conformanceRun" | "evalRun"; resourceId: string; }, options?: RequestOptions): Promise>; setShareMode(params: { projectId: string; resourceType: "scenario" | "conformanceRun" | "evalRun"; resourceId: string; mode: "project_members" | "invited_only" | "anyone_with_link"; allowGuestAccess?: boolean; }, options?: RequestOptions): Promise>; /** * Rotate the share link. Immediate: holders of the old URL can no longer * redeem it. Agent-excluded; available on REST/CLI/MCP. */ rotateShareLink(params: { projectId: string; resourceType: "scenario" | "conformanceRun" | "evalRun"; resourceId: string; }, options?: RequestOptions): Promise>; private serverOp; private request; private toApiError; } /** * The eval decision summary: the canonical contract's platform-typed entry, its * human renderer, and the compatibility surface that preceded it. * * ── Where the contract lives ───────────────────────────────────────────────── * * {@link EvalRunDecisionSummary} — the versioned shape the API returns, the * Platform MCP server hands to a model, and every CLI reporter restates — is * defined in `./contract/decision-summary.ts` and assembled by * {@link assembleEvalRunDecisionSummary}. This module adds two things the * contract subpath deliberately cannot have: types from `./platform/types.js` * (the contract stays free of them so it can be bundled into a browser), and * the prose renderer. * * ── What is kept for compatibility ─────────────────────────────────────────── * * {@link buildEvalDecisionSummary}, {@link buildEvalDecisionSummaryFromIterations} * and {@link formatEvalDecisionSummary} are the SHIPPED per-case summary. They * are deprecated, unchanged, and still exported: `@mcpjam/sdk` has consumers on * them and removing an export is a break, not a cleanup. Nothing inside this * repo calls them any more — the CLI, the reporters and the API all assemble * the canonical contract instead — because their verdict is computed from * ITERATION COUNTS, which is a second, disagreeing answer to a question the * run's own `EvalVerdictDecision` already answered. Two verdict engines over * one run is the drift the canonical contract exists to remove; keeping this * one reachable but unused is how that is done without breaking anybody. */ type EvalDecisionVerdict = "passed" | "failed" | "incomplete"; type StageChainStatus = "verified" | "unverified" | "absent"; type EvalDecisionSummaryCase = { id: string; title: string; iterationNumber: number; firstFailedStage?: UserValueStage; failureCategory?: FailureCategory; stageChain?: StageResultRow[]; stageChainStatus: StageChainStatus; stageAnalyzerVersionAhead?: { reported: number; known: number; }; expected?: { toolNames: string[]; }; observed?: { toolNames?: string[]; failure?: string; }; evidence?: { spanIds?: string[]; promptIndexes?: number[]; predicateReasons?: string[]; }; firstFailedTurnIndex?: number; nextAction: string; }; type EvalDecisionSummary = { verdict: EvalDecisionVerdict; passRate: { total: number; passed: number; failed: number; percent: number | null; }; iterationWalkComplete: boolean; cases: EvalDecisionSummaryCase[]; }; type NormalizedEvalDecisionCase = { id: string; title: string; iterationNumber: number; result: "passed" | "failed"; expectedToolCalls?: readonly unknown[]; actualToolCalls?: readonly unknown[]; error?: string | null; stageResults?: unknown; firstFailedStage?: unknown; failureCategory?: unknown; stageAnalyzerVersion?: unknown; stageResultsUnverified?: true; firstFailedTurnIndex?: number; }; type EvalDecisionSummaryInput = { total: number; passed: number; failed: number; iterationWalkComplete: boolean; cases: NormalizedEvalDecisionCase[]; }; /** * @deprecated Use {@link buildEvalRunDecisionSummary} (or * `assembleEvalRunDecisionSummary` from `@mcpjam/sdk/contract`). This computes a * verdict by counting iterations, which disagrees with the run's own * `EvalVerdictDecision` whenever a case has repetitions: it reads N trials as N * cases, and a case that passed 4 of 5 trials reads here as one pass and one * failure. Kept exported and unchanged for existing consumers. */ declare function buildEvalDecisionSummary(input: EvalDecisionSummaryInput): EvalDecisionSummary; /** * @deprecated Use {@link buildEvalRunDecisionSummary}, which takes the run as * well as its iterations and therefore reports the verdict the platform * actually reached. See {@link buildEvalDecisionSummary}. */ declare function buildEvalDecisionSummaryFromIterations(iterations: PlatformEvalIteration[], input: { total?: number; passed?: number; failed?: number; iterationWalkComplete: boolean; }): EvalDecisionSummary; /** * @deprecated Use {@link formatEvalRunDecisionSummary}. This renders raw wire * enums (`userValue`, `argumentMismatch`) at a human. */ declare function formatEvalDecisionSummary(summary: EvalDecisionSummary): string; /** * Assemble the canonical summary from a platform run and ONE page of its * iterations. * * A thin, typed wrapper over {@link assembleEvalRunDecisionSummary}: the DTOs * satisfy the contract's structural inputs by construction, and going through * one function is what makes the API's summary and a client's fallback summary * byte-equivalent for the same input. Fetching and pagination stay OUT of it — * the caller decides how much of the run it walked and says so in `page`. */ declare function buildEvalRunDecisionSummary(input: { projectId: string; run: PlatformEvalRun; iterations: readonly PlatformEvalIteration[]; page: { complete: boolean; nextCursor?: string; }; }): EvalRunDecisionSummary; /** * Read the canonical summary with one compatibility path for older API * deployments. * * The endpoint is preferred because it can return a bounded diagnostic page. * If it is absent, the fallback walks the same iteration resource and hands * the rows to the same shared assembler. An opaque cursor cannot be replayed * locally, so a cursored request returns no fallback rather than silently * returning the wrong page. */ declare function readEvalRunDecisionSummary(client: Pick, signal: AbortSignal | undefined, projectId: string, run: PlatformEvalRun, options?: { cursor?: string; limit?: number; }): Promise; /** * Render the canonical summary as prose. * * Every enum passes through the label maps beside the contract, so a terminal * says `User value` and `the call arguments did not match what the case * expects` rather than `userValue` and `argumentMismatch`. Nothing here * inspects the run again: this is presentation over an already-decided object. */ declare function formatEvalRunDecisionSummary(summary: EvalRunDecisionSummary): string; export { type PlatformImageBuild as $, type PlatformGenerationDrafts as A, type PlatformCapabilities as B, type PlatformConformanceReport as C, type PlatformConformanceRun as D, type PlatformEvalRunDecisionSummary as E, type PlatformEvalStepResult as F, type PlatformJourneyRun as G, type PlatformRunScorecard as H, type PlatformReadinessLaneCoverage as I, type PlatformReadinessStageResult as J, type PlatformReadinessObservationState as K, type PlatformReadinessRun as L, type PlatformScenarioDetail as M, type PlatformSwarmOverview as N, type PlatformUserTestingScenarioDetail as O, type PlatformDoctorReport as P, type PlatformUserTestingSessionDetail as Q, type PlatformWaveInsights as R, SdkError as S, type PlatformJourneyRunLaunched as T, type PlatformChatSession as U, type PlatformClient as V, type PlatformEnvironment as W, type PlatformEvalCheckRepos as X, type PlatformEvalIteration as Y, type PlatformEvalSuite as Z, type PlatformHost as _, type SdkErrorOptions as a, type PlatformChatMessage as a$, type PlatformImage as a0, type PlatformJourneyRunSession as a1, type PlatformPlugin as a2, type PlatformScenarioSummary as a3, type PlatformSwarmFinding as a4, type PlatformUserTestingSession as a5, type PlatformScenario as a6, type PlatformEvalRunJudgeRequested as a7, type PlatformUserTestingInsightsRequested as a8, type PlatformWaveInsightsRequested as a9, type PlatformRegistryConnection as aA, type PlatformCatalogSourceStatus as aB, type PlatformRegistryServer as aC, type PlatformWidgetRender as aD, type PlatformComputerReset as aE, type PlatformEnvironmentResolved as aF, type PlatformGateWaiverWriteResult as aG, type PlatformChatTurn as aH, type PlatformComputerAttached as aI, type PlatformImageBlueprintValidation as aJ, DEFAULT_MCPJAM_APP_ORIGIN as aK, DEFAULT_PLATFORM_API_BASE_URL as aL, PLATFORM_PERMALINK_ROUTES as aM, PROJECT_DEEP_LINK_PARAM as aN, type PermalinkAwareOperation as aO, type PermalinkScopeReceiver as aP, type PlatformActionableFinding as aQ, type PlatformActionableFindingEvidence as aR, type PlatformAdhocEnvironmentBody as aS, type PlatformAdhocEnvironmentEnsured as aT, type PlatformAnalysisTouchpointDisclosure as aU, type PlatformApiClientOptions as aV, type PlatformByokDisclosure as aW, type PlatformCaptureDisclosure as aX, type PlatformCaseScoreDelta as aY, type PlatformCaseScoreSide as aZ, type PlatformCatalogOauthProbe as a_, type PlatformEvalRunCreated as aa, type PlatformSessionSummary as ab, type PlatformConformanceRunReceipt as ac, type PlatformReadinessRunReceipt as ad, type PlatformScenarioDeleted as ae, type PlatformUserTestingScenario as af, type PlatformImageBuildStarted as ag, type PlatformServerConnection as ah, type PlatformClientDetail as ai, type PlatformHostDetail as aj, type PlatformClientDeleted as ak, type PlatformEvalCaseDeleted as al, type PlatformEvalSuiteDeleted as am, type PlatformHostDeleted as an, type PlatformImageDeleted as ao, type PlatformChatSessionDetail as ap, type PlatformChatSessionTrace as aq, type PlatformGateWaiver as ar, type PlatformEvalSuiteDetail as as, type PlatformMe as at, type PlatformPluginVersion as au, type PlatformCatalogServer as av, type PlatformRegistryInstallResult as aw, type PlatformPage as ax, type PlatformModel as ay, type PlatformOrganization as az, type PlatformProject as b, type PlatformPermalinkContext as b$, type PlatformChatSessionTraceTurn as b0, type PlatformClientImpact as b1, type PlatformCompareCaseStatus as b2, type PlatformConformanceReportCheck as b3, type PlatformConformanceReportProfile as b4, type PlatformConformanceRunReportSummary as b5, type PlatformConformanceSuiteKind as b6, type PlatformDirectorySearchPage as b7, type PlatformDisclosedModel as b8, type PlatformDisclosureEngine as b9, type PlatformEvalRunSummary as bA, type PlatformEvalStep as bB, type PlatformEvalSuiteComputerEnvironment as bC, type PlatformEvalSuiteEnvironmentAttached as bD, type PlatformEvalSuiteHost as bE, type PlatformEvalSuiteSchedule as bF, type PlatformEvalSuiteSettings as bG, type PlatformExecutionAbsenceKind as bH, type PlatformExecutionDisclosure as bI, type PlatformExpectedToolCall as bJ, type PlatformFileOwnedEvalSuiteSynced as bK, type PlatformGateWaiverRead as bL, type PlatformGuestExecution as bM, type PlatformInsightActionTarget as bN, type PlatformInsightActionability as bO, type PlatformInsightAttribution as bP, type PlatformInsightScope as bQ, type PlatformInsightsEnvelope as bR, type PlatformInsightsStatus as bS, type PlatformJourneyRunAttempt as bT, type PlatformJourneyRunTarget as bU, type PlatformManagedRailDisclosure as bV, type PlatformNoPermalinkReason as bW, type PlatformNotApplicableRailDisclosure as bX, type PlatformNumericDiff as bY, type PlatformOpenAIReadinessStartBody as bZ, type PlatformPermalink as b_, type PlatformDisclosureFires as ba, type PlatformDisclosureRailDestination as bb, type PlatformDisclosureTenantEgress as bc, type PlatformEnvironmentCreateBody as bd, type PlatformEnvironmentNameBody as be, type PlatformEnvironmentRevisionBody as bf, type PlatformEnvironmentSkillSelection as bg, type PlatformEnvironmentUpdateBody as bh, type PlatformEvalCaseBatchFailed as bi, type PlatformEvalCaseModel as bj, type PlatformEvalCaseWarning as bk, type PlatformEvalCheckRepo as bl, type PlatformEvalLlmTouchpointId as bm, type PlatformEvalRunDisclosureLocus as bn, type PlatformEvalRunEnvironment as bo, type PlatformEvalRunGoalCompletionCase as bp, type PlatformEvalRunGoalCompletionJudge as bq, type PlatformEvalRunGroundednessCase as br, type PlatformEvalRunGroundednessJudge as bs, type PlatformEvalRunGroupCreated as bt, type PlatformEvalRunGroupEntry as bu, type PlatformEvalRunGroupTarget as bv, type PlatformEvalRunInsightsRequested as bw, type PlatformEvalRunJudgeCase as bx, type PlatformEvalRunJudgeState as by, type PlatformEvalRunJudges as bz, type PlatformProjectServer as c, type NormalizedEvalDecisionCase as c$, PlatformPermalinkError as c0, type PlatformPluginComponentCounts as c1, type PlatformPluginServerComponent as c2, type PlatformPluginSkillComponent as c3, type PlatformRailDisclosure as c4, type PlatformReadinessKind as c5, type PlatformReadinessLaneStatus as c6, type PlatformReadinessStartBody as c7, type PlatformReadinessSubmissionMode as c8, type PlatformRegionDisclosure as c9, type PlatformToolMode as cA, type PlatformTranscriptMessage as cB, type PlatformTunnelClosed as cC, type PlatformTurnToolCall as cD, type PlatformTurnTrace as cE, type PlatformTurnUsage as cF, type PlatformWidgetSnapshot as cG, type PublicCheck as cH, type PublicCheckOverride as cI, type PublicMatchOptions as cJ, buildAppPermalink as cK, buildAppPermalinks as cL, derivePermalinks as cM, derivePermalinksFor as cN, formatPermalinkLines as cO, isPlatformResourceType as cP, noPermalink as cQ, permalinkProjectId as cR, readEvalRunDecisionSummary as cS, responsePermalinks as cT, runOperationWithPermalinks as cU, withPermalinkEnvelope as cV, type EvalDecisionSummary as cW, type EvalDecisionSummaryCase as cX, type EvalDecisionSummaryInput as cY, type EvalDecisionVerdict as cZ, EvalReportingError as c_, type PlatformRegistryInstall as ca, type PlatformRegistryInstallNextSteps as cb, type PlatformRegistryServerTransport as cc, type PlatformResourceRef as cd, type PlatformResourceType as ce, type PlatformRetentionDisclosure as cf, type PlatformRunCompareCase as cg, type PlatformRunCompareCaseSide as ch, type PlatformRunCompareSide as ci, type PlatformScenarioLink as cj, type PlatformScenarioServer as ck, type PlatformScoreContractDiff as cl, type PlatformScoreContractScorer as cm, type PlatformScoreContractSide as cn, type PlatformScorecardCriterion as co, type PlatformServerConnectionCandidate as cp, type PlatformServerConnectionCreateBody as cq, type PlatformServerConnectionError as cr, type PlatformSessionLink as cs, type PlatformSessionParentRef as ct, type PlatformSessionSourceType as cu, type PlatformSessionsPage as cv, type PlatformSnapshotElement as cw, type PlatformSubprocessorDisclosure as cx, type PlatformSwarmOverviewFinding as cy, type PlatformSwarmOverviewRun as cz, type PlatformPermalinkPolicy as d, type StageChainStatus as d0, buildEvalDecisionSummary as d1, buildEvalDecisionSummaryFromIterations as d2, buildEvalRunDecisionSummary as d3, formatEvalDecisionSummary as d4, formatEvalRunDecisionSummary as d5, PlatformApiClient as e, type PlatformEvalRunDisclosure as f, type PlatformJourneyArchived as g, type PlatformSwarmArchived as h, type PlatformEvalRun as i, type PlatformJourneyRunCanceled as j, type PlatformWaveInsightsCanceled as k, type PlatformRunCompare as l, type PlatformEvalCheckRepoConnected as m, type PlatformEvalCaseBatchResult as n, type PlatformEvalCaseBatchCreated as o, type PlatformEvalSuiteCreated as p, type PlatformJourney as q, type PlatformPersona as r, type PlatformSwarm as s, type PlatformTunnelGrant as t, type PlatformPersonaDeleted as u, type PlatformFindingDismissed as v, type PlatformAdhocEnvironment as w, type PlatformEnvironmentCapabilities as x, type PlatformEvalCasesGenerated as y, type PlatformEvalCase as z };