Merge refreshed Storybook layer into runtime hardening

Preserve managed OpenCode credential isolation while keeping the sandbox work-folder HOME.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-12 18:30:41 -05:00
commit 57c2f528f1
294 files changed
+115930 -1966

No files matched your search

+231
View File
@@ -0,0 +1,231 @@
import { z } from "zod";
/** Runtime authentication is a separate transport, never a tool or channel. */
export const connectionPurposeTransportSchema = z.discriminatedUnion(
"connectionPurpose",
[
z.object({
connectionPurpose: z.literal("tool"),
transport: z.enum(["mcp_remote", "rest_api", "local_stdio"]),
}),
z.object({
connectionPurpose: z.literal("channel"),
transport: z.enum(["chat_sdk", "rest_api"]),
config: z.object({ provider: z.string().optional() }).passthrough().optional(),
}).refine(
(connection) => connection.transport === "chat_sdk" || connection.config?.provider === "agentmail",
{ message: "REST channel connections require the AgentMail provider", path: ["config", "provider"] },
),
z.object({
connectionPurpose: z.literal("ai"),
transport: z.literal("runtime_auth"),
}),
],
);
export type ConnectionPurposeTransport = z.infer<
typeof connectionPurposeTransportSchema
>;
export const AI_PROVIDERS = [
"anthropic",
"openai",
"openrouter",
"xai",
] as const;
export const aiProviderSchema = z.enum(AI_PROVIDERS);
export const aiAuthMethodSchema = z.enum(["subscription", "api_key"]);
export type AiProvider = z.infer<typeof aiProviderSchema>;
export type AiAuthMethod = z.infer<typeof aiAuthMethodSchema>;
const requirement = { provider: aiProviderSchema, method: aiAuthMethodSchema };
export const aiConnectionBindingSchema = z.discriminatedUnion("mode", [
z.object({ ...requirement, mode: z.literal("responsible_user") }).strict(),
z
.object({
...requirement,
mode: z.literal("shared"),
connectionId: z.string().uuid(),
grantId: z.string().uuid(),
})
.strict(),
z
.object({
...requirement,
// Legacy wire format only; human access still applies. New UI never creates it.
mode: z.literal("delegated"),
connectionId: z.string().uuid(),
grantId: z.string().uuid(),
})
.strict(),
]);
export type AiConnectionBinding = z.infer<typeof aiConnectionBindingSchema>;
export const aiConnectionMetadataSchema = z.object(requirement).strict();
export type AiConnectionMetadata = z.infer<typeof aiConnectionMetadataSchema>;
/** Existing integrations only. This table describes compatibility, never routing. */
export const AI_CONNECTION_CAPABILITIES: Record<
AiProvider,
{
name: string;
methods: Partial<
Record<AiAuthMethod, { adapters: readonly string[]; envKey: string }>
>;
}
> = {
anthropic: {
name: "Claude",
methods: {
subscription: {
adapters: ["claude_local"],
envKey: "CLAUDE_CODE_OAUTH_TOKEN",
},
api_key: { adapters: ["claude_local"], envKey: "ANTHROPIC_API_KEY" },
},
},
openai: {
name: "OpenAI",
methods: {
subscription: { adapters: ["codex_local"], envKey: "CODEX_HOME" },
api_key: { adapters: ["codex_local"], envKey: "OPENAI_API_KEY" },
},
},
openrouter: {
name: "OpenRouter",
methods: {
api_key: { adapters: ["opencode_local"], envKey: "OPENROUTER_API_KEY" },
},
},
xai: {
name: "Grok",
methods: {
subscription: { adapters: ["grok_local"], envKey: "GROK_HOME" },
api_key: { adapters: ["grok_local"], envKey: "XAI_API_KEY" },
},
},
};
export function isAiConnectionCompatible(
requirement: AiConnectionMetadata,
adapterType: string,
model?: unknown,
runnerProvider?: unknown,
acpxAgent?: unknown,
): boolean {
if (adapterType === "paperclip_runner")
adapterType =
runnerProvider === "claude" ||
(runnerProvider === "acpx" && acpxAgent === "claude")
? "claude_local"
: runnerProvider === "codex"
? "codex_local"
: runnerProvider === "opencode"
? "opencode_local"
: "unsupported";
const method =
AI_CONNECTION_CAPABILITIES[requirement.provider].methods[
requirement.method
];
return (
Boolean(method?.adapters.includes(adapterType)) &&
(requirement.provider !== "openrouter" ||
(typeof model === "string" && model.startsWith("openrouter/")))
);
}
export type AiConnectionUnavailableReason =
| "responsible_user_missing"
| "membership_missing"
| "default_missing"
| "connection_missing"
| "connection_unavailable"
| "incompatible"
| "access_denied"
| "credential_missing";
export interface AiConnectionAttribution {
connectionId: string;
grantId: string;
provider: AiProvider;
method: AiAuthMethod;
mode: AiConnectionBinding["mode"];
responsibleUserId: string | null;
}
export type AiConnectionResolution =
| { ok: true; attribution: AiConnectionAttribution }
| { ok: false; reason: AiConnectionUnavailableReason; message: string };
export interface AiManagedConnectionSummary {
id: string;
grantId: string;
companyId: string;
provider: AiProvider;
method: AiAuthMethod;
name: string;
accountLabel?: string;
ownership: "personal" | "shared";
ownerUserId?: string;
ownerName?: string;
isDefault: boolean;
status: "connected" | "needs_attention" | "expired" | "revoked";
unavailableReason?: string;
}
export const createAiConnectionSchema = z
.object({
...requirement,
name: z.string().trim().min(1).max(160),
ownership: z.enum(["personal", "shared"]),
apiKey: z.string().trim().min(1).max(32768).optional(),
loginSessionId: z.string().max(128).optional(),
connectionId: z.string().uuid().optional(),
agentIds: z.array(z.string().uuid()).max(1000).default([]),
allAgents: z.boolean().default(false),
})
.strict()
.superRefine((v, ctx) => {
if (!AI_CONNECTION_CAPABILITIES[v.provider].methods[v.method])
ctx.addIssue({ code: "custom", message: "Unsupported sign-in method" });
if (
v.method === "api_key"
? !v.apiKey || Boolean(v.loginSessionId)
: !v.loginSessionId || Boolean(v.apiKey)
) {
ctx.addIssue({
code: "custom",
message:
"Provide exactly the credential for the selected sign-in method",
});
}
});
export type CreateAiConnection = z.infer<typeof createAiConnectionSchema>;
export const aiConnectionLoginIntentSchema = z
.object({
provider: aiProviderSchema,
method: z.literal("subscription"),
name: z.string().trim().min(1).max(160),
ownership: z.enum(["personal", "shared"]),
connectionId: z.string().uuid().optional(),
agentIds: z.array(z.string().uuid()).max(1000).default([]),
allAgents: z.boolean().default(false),
})
.strict();
export type AiConnectionLoginIntent = z.infer<
typeof aiConnectionLoginIntentSchema
>;
export const localAiConnectionSchema = aiConnectionLoginIntentSchema.extend({
localSessionId: z.string().uuid().optional(),
});
export const localAiLoginStartSchema = aiConnectionLoginIntentSchema.extend({ restart: z.boolean().optional() });
export interface LocalAiLoginStatus {
status: "ready" | "sign_in_required" | "expired";
}
export interface LocalAiLoginAttempt {
sessionId: string;
command: string;
expiresAt: string;
}
/** Preview-era copies of rotating local credentials must be reconnected. */
export function aiSubscriptionNeedsIsolatedLogin(config: Record<string, unknown> | undefined): boolean {
const metadata = aiConnectionMetadataSchema.safeParse(config?.ai);
return metadata.success && metadata.data.method === "subscription" &&
(metadata.data.provider === "openai" || metadata.data.provider === "xai") &&
config?.aiIsolatedSubscription !== true;
}
@@ -3,66 +3,70 @@ import a1 from "./app-definitions/zapier.json" with { type: "json" };
import a2 from "./app-definitions/github.json" with { type: "json" };
import a3 from "./app-definitions/slack.json" with { type: "json" };
import a4 from "./app-definitions/microsoft-teams.json" with { type: "json" };
import a5 from "./app-definitions/telegram.json" with { type: "json" };
import a6 from "./app-definitions/discord.json" with { type: "json" };
import a7 from "./app-definitions/notion.json" with { type: "json" };
import a8 from "./app-definitions/posthog.json" with { type: "json" };
import a9 from "./app-definitions/linear.json" with { type: "json" };
import a10 from "./app-definitions/context7.json" with { type: "json" };
import a11 from "./app-definitions/shopify.json" with { type: "json" };
import a12 from "./app-definitions/composio.json" with { type: "json" };
import a13 from "./app-definitions/oauth-generic.json" with { type: "json" };
import a14 from "./app-definitions/api-key-generic.json" with { type: "json" };
import a15 from "./app-definitions/sentry.json" with { type: "json" };
import a16 from "./app-definitions/vercel.json" with { type: "json" };
import a17 from "./app-definitions/anthropic.json" with { type: "json" };
import a18 from "./app-definitions/jira.json" with { type: "json" };
import a19 from "./app-definitions/airtable.json" with { type: "json" };
import a20 from "./app-definitions/beehiiv.json" with { type: "json" };
import a21 from "./app-definitions/bitly.json" with { type: "json" };
import a22 from "./app-definitions/candid.json" with { type: "json" };
import a23 from "./app-definitions/cloudflare.json" with { type: "json" };
import a24 from "./app-definitions/cloudinary.json" with { type: "json" };
import a25 from "./app-definitions/coda.json" with { type: "json" };
import a26 from "./app-definitions/hugging-face.json" with { type: "json" };
import a27 from "./app-definitions/kernel.json" with { type: "json" };
import a28 from "./app-definitions/local-falcon.json" with { type: "json" };
import a29 from "./app-definitions/make.json" with { type: "json" };
import a30 from "./app-definitions/manufact.json" with { type: "json" };
import a31 from "./app-definitions/miro.json" with { type: "json" };
import a32 from "./app-definitions/netlify.json" with { type: "json" };
import a33 from "./app-definitions/oreilly.json" with { type: "json" };
import a34 from "./app-definitions/planetscale.json" with { type: "json" };
import a35 from "./app-definitions/resend.json" with { type: "json" };
import a36 from "./app-definitions/ticktick.json" with { type: "json" };
import a37 from "./app-definitions/todoist.json" with { type: "json" };
import a38 from "./app-definitions/webflow.json" with { type: "json" };
import a39 from "./app-definitions/wix.json" with { type: "json" };
import a40 from "./app-definitions/brex.json" with { type: "json" };
import a41 from "./app-definitions/clickhouse.json" with { type: "json" };
import a42 from "./app-definitions/egnyte.json" with { type: "json" };
import a43 from "./app-definitions/embat.json" with { type: "json" };
import a44 from "./app-definitions/mixpanel.json" with { type: "json" };
import a45 from "./app-definitions/postman.json" with { type: "json" };
import a46 from "./app-definitions/razorpay.json" with { type: "json" };
import a47 from "./app-definitions/sanity.json" with { type: "json" };
import a48 from "./app-definitions/stripe.json" with { type: "json" };
import a49 from "./app-definitions/supabase.json" with { type: "json" };
import a50 from "./app-definitions/ticket-tailor.json" with { type: "json" };
import a51 from "./app-definitions/asana.json" with { type: "json" };
import a52 from "./app-definitions/box.json" with { type: "json" };
import a53 from "./app-definitions/mem0.json" with { type: "json" };
import a54 from "./app-definitions/pagerduty.json" with { type: "json" };
import a55 from "./app-definitions/similarweb.json" with { type: "json" };
import a56 from "./app-definitions/xero.json" with { type: "json" };
import a57 from "./app-definitions/gmail.json" with { type: "json" };
import a58 from "./app-definitions/google-drive.json" with { type: "json" };
import a59 from "./app-definitions/google-docs.json" with { type: "json" };
import a60 from "./app-definitions/google-sheets.json" with { type: "json" };
import a61 from "./app-definitions/google-slides.json" with { type: "json" };
import a62 from "./app-definitions/google-calendar.json" with { type: "json" };
import a63 from "./app-definitions/google-chat.json" with { type: "json" };
import a64 from "./app-definitions/google-people.json" with { type: "json" };
import a65 from "./app-definitions/google-workspace-search.json" with { type: "json" };
import a5 from "./app-definitions/imessage-photon.json" with { type: "json" };
import a6 from "./app-definitions/telegram.json" with { type: "json" };
import a7 from "./app-definitions/discord.json" with { type: "json" };
import a8 from "./app-definitions/notion.json" with { type: "json" };
import a9 from "./app-definitions/posthog.json" with { type: "json" };
import a10 from "./app-definitions/linear.json" with { type: "json" };
import a11 from "./app-definitions/context7.json" with { type: "json" };
import a12 from "./app-definitions/shopify.json" with { type: "json" };
import a13 from "./app-definitions/composio.json" with { type: "json" };
import a14 from "./app-definitions/oauth-generic.json" with { type: "json" };
import a15 from "./app-definitions/api-key-generic.json" with { type: "json" };
import a16 from "./app-definitions/sentry.json" with { type: "json" };
import a17 from "./app-definitions/vercel.json" with { type: "json" };
import a18 from "./app-definitions/anthropic.json" with { type: "json" };
import a19 from "./app-definitions/jira.json" with { type: "json" };
import a20 from "./app-definitions/airtable.json" with { type: "json" };
import a21 from "./app-definitions/beehiiv.json" with { type: "json" };
import a22 from "./app-definitions/bitly.json" with { type: "json" };
import a23 from "./app-definitions/candid.json" with { type: "json" };
import a24 from "./app-definitions/cloudflare.json" with { type: "json" };
import a25 from "./app-definitions/cloudinary.json" with { type: "json" };
import a26 from "./app-definitions/coda.json" with { type: "json" };
import a27 from "./app-definitions/hugging-face.json" with { type: "json" };
import a28 from "./app-definitions/kernel.json" with { type: "json" };
import a29 from "./app-definitions/local-falcon.json" with { type: "json" };
import a30 from "./app-definitions/make.json" with { type: "json" };
import a31 from "./app-definitions/manufact.json" with { type: "json" };
import a32 from "./app-definitions/miro.json" with { type: "json" };
import a33 from "./app-definitions/netlify.json" with { type: "json" };
import a34 from "./app-definitions/oreilly.json" with { type: "json" };
import a35 from "./app-definitions/planetscale.json" with { type: "json" };
import a36 from "./app-definitions/resend.json" with { type: "json" };
import a37 from "./app-definitions/ticktick.json" with { type: "json" };
import a38 from "./app-definitions/todoist.json" with { type: "json" };
import a39 from "./app-definitions/webflow.json" with { type: "json" };
import a40 from "./app-definitions/wix.json" with { type: "json" };
import a41 from "./app-definitions/brex.json" with { type: "json" };
import a42 from "./app-definitions/clickhouse.json" with { type: "json" };
import a43 from "./app-definitions/egnyte.json" with { type: "json" };
import a44 from "./app-definitions/embat.json" with { type: "json" };
import a45 from "./app-definitions/mixpanel.json" with { type: "json" };
import a46 from "./app-definitions/postman.json" with { type: "json" };
import a47 from "./app-definitions/razorpay.json" with { type: "json" };
import a48 from "./app-definitions/sanity.json" with { type: "json" };
import a49 from "./app-definitions/stripe.json" with { type: "json" };
import a50 from "./app-definitions/supabase.json" with { type: "json" };
import a51 from "./app-definitions/ticket-tailor.json" with { type: "json" };
import a52 from "./app-definitions/asana.json" with { type: "json" };
import a53 from "./app-definitions/box.json" with { type: "json" };
import a54 from "./app-definitions/mem0.json" with { type: "json" };
import a55 from "./app-definitions/pagerduty.json" with { type: "json" };
import a56 from "./app-definitions/similarweb.json" with { type: "json" };
import a57 from "./app-definitions/xero.json" with { type: "json" };
import a58 from "./app-definitions/gmail.json" with { type: "json" };
import a59 from "./app-definitions/google-drive.json" with { type: "json" };
import a60 from "./app-definitions/google-docs.json" with { type: "json" };
import a61 from "./app-definitions/google-sheets.json" with { type: "json" };
import a62 from "./app-definitions/google-slides.json" with { type: "json" };
import a63 from "./app-definitions/google-calendar.json" with { type: "json" };
import a64 from "./app-definitions/google-chat.json" with { type: "json" };
import a65 from "./app-definitions/google-people.json" with { type: "json" };
import a66 from "./app-definitions/google-workspace-search.json" with { type: "json" };
import a67 from "./app-definitions/openai.json" with { type: "json" };
import a68 from "./app-definitions/openrouter.json" with { type: "json" };
import a69 from "./app-definitions/xai.json" with { type: "json" };
import type { AppDefinition } from "./types/app-definition.js";
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65] as AppDefinition[];
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69] as AppDefinition[];
+5 -5
View File
@@ -679,7 +679,7 @@ describe("AppDefinition catalog", () => {
"ticktick",
"xero",
]);
expect(APP_STORE_DEFINITIONS).toHaveLength(41);
expect(APP_STORE_DEFINITIONS).toHaveLength(46);
const connectableSlugs = new Set(
CONNECTABLE_APP_DEFINITIONS.map((entry) => entry.slug),
);
@@ -691,7 +691,7 @@ describe("AppDefinition catalog", () => {
expect(storeSlugs.has(slug), slug).toBe(false);
}
});
it("ships complete local branding provenance for all 41 store-visible providers", () => {
it("ships complete local branding provenance for all 46 store-visible providers", () => {
const uiPublic = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../../ui/public",
@@ -711,14 +711,14 @@ describe("AppDefinition catalog", () => {
}>;
};
const visible = manifest.providers.filter((entry) => entry.catalogVisible);
expect(visible).toHaveLength(41);
expect(visible).toHaveLength(46);
expect(new Set(visible.map((entry) => entry.slug))).toHaveProperty(
"size",
41,
46,
);
expect(new Set(visible.map((entry) => entry.localAsset))).toHaveProperty(
"size",
41,
46,
);
expect(new Set(APP_STORE_DEFINITIONS.map((entry) => entry.slug))).toEqual(
new Set(visible.map((entry) => entry.slug)),
+3
View File
@@ -4,6 +4,7 @@ import type { AppDefinition, ConnectionMethodDef, FieldDef } from "./types/app-d
import type { ToolConnectionOwnership } from "./types/tool-access.js";
export const CONNECTABLE_APP_SLUGS = new Set([
"anthropic", "openai", "openrouter", "xai",
"agentmail",
...SELF_SERVE_MCP_CANDIDATES.map((entry) => entry.slug),
"zapier",
@@ -27,6 +28,7 @@ export const CONNECTABLE_APP_SLUGS = new Set([
"discord",
"microsoft-teams",
"telegram",
"imessage-photon",
]);
export const CONNECTABLE_APP_DEFINITIONS = APP_DEFINITIONS.filter((app) =>
@@ -170,6 +172,7 @@ export function connectionMethodAcceptsCustomerOAuthClient(method: ConnectionMet
export function connectionMethodSupportsCatalogSetup(method: ConnectionMethodDef | null | undefined): boolean {
if (!method) return false;
if (method.transport === "runtime_auth") return Boolean(method.ai);
if (method.auth === "none" || method.auth === "api_key") return true;
return connectionMethodSupportsAutomaticOAuth(method)
|| connectionMethodAcceptsCustomerOAuthClient(method);
@@ -15,6 +15,62 @@
"https://api.anthropic.com/*"
],
"methods": [
{
"key": "ai-subscription",
"label": "Claude subscription",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "oauth",
"ai": {
"provider": "anthropic",
"method": "subscription"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3"
},
{
"key": "ai-api_key",
"label": "Claude API key",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "api_key",
"ai": {
"provider": "anthropic",
"method": "api_key"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3",
"credentialFields": [
{
"key": "apiKey",
"label": "API key",
"type": "password",
"required": true,
"placeholder": "Enter API key",
"secret": true
}
],
"keyPlacement": {
"location": "env",
"name": "ANTHROPIC_API_KEY"
}
},
{
"key": "api-key",
"transport": "rest_api",
@@ -0,0 +1,50 @@
{
"schemaVersion": 1,
"slug": "imessage-photon",
"name": "iMessage Photon",
"description": "Message a Paperclip agent from Apple Messages using Photon Cloud. Pro supports DMs; dedicated lines also support groups.",
"categories": [
"communication"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/imessage-photon.png"
},
"urlPatterns": [
"https://photon.codes/*"
],
"methods": [
{
"key": "chat-agent",
"label": "Chat with an agent",
"purpose": "channel",
"provider": "imessage-photon",
"transport": "chat_sdk",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Let people in iMessage Photon start and continue work with one Paperclip agent.",
"credentialFields": [
{
"key": "projectSecret",
"label": "Project secret",
"type": "password",
"required": true,
"placeholder": "Photon project secret",
"secret": true
}
],
"guidanceMd": "Connect a Photon Cloud project. Pro shared lines support DMs after sender enrollment in Photon and identity linking in Paperclip. Dedicated lines also support individually enabled groups.",
"consoleLinks": {
"register": "https://photon.codes/",
"docs": "https://photon.codes/docs/spectrum-ts/providers/imessage/connection-and-routing"
},
"riskTier": "S3",
"requiredResourceFilters": [
"direct_message",
"group_chat"
]
}
]
}
@@ -3,12 +3,16 @@
"slug": "microsoft-teams",
"name": "Microsoft Teams",
"description": "Let people start and continue Paperclip work with an agent from Microsoft Teams.",
"categories": ["communication"],
"categories": [
"communication"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/microsoft-teams.svg"
},
"urlPatterns": ["https://teams.microsoft.com/*"],
"urlPatterns": [
"https://teams.microsoft.com/*"
],
"methods": [
{
"key": "chat-agent",
@@ -17,7 +21,9 @@
"provider": "microsoft-teams",
"transport": "chat_sdk",
"auth": "api_key",
"ownershipModes": ["customer"],
"ownershipModes": [
"customer"
],
"whenToUse": "Let people in Microsoft Teams start and continue work with one Paperclip agent.",
"credentialFields": [
{
@@ -51,7 +57,11 @@
"docs": "https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams"
},
"riskTier": "S3",
"requiredResourceFilters": ["team", "channel", "chat"]
"requiredResourceFilters": [
"team",
"channel",
"chat"
]
}
]
}
@@ -0,0 +1,74 @@
{
"schemaVersion": 1,
"slug": "openai",
"name": "OpenAI",
"description": "Connect OpenAI accounts for your agents.",
"categories": [
"ai"
],
"branding": {
"logoUrl": "/brands/apps/openai.svg",
"darkLogoUrl": "/brands/apps/openai-dark.svg"
},
"urlPatterns": [
"https://api.openai.com/*"
],
"methods": [
{
"key": "ai-subscription",
"label": "OpenAI subscription",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "oauth",
"ai": {
"provider": "openai",
"method": "subscription"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3"
},
{
"key": "ai-api_key",
"label": "OpenAI API key",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "api_key",
"ai": {
"provider": "openai",
"method": "api_key"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3",
"credentialFields": [
{
"key": "apiKey",
"label": "API key",
"type": "password",
"required": true,
"placeholder": "Enter API key",
"secret": true
}
],
"keyPlacement": {
"location": "env",
"name": "OPENAI_API_KEY"
}
}
]
}
@@ -0,0 +1,53 @@
{
"schemaVersion": 1,
"slug": "openrouter",
"name": "OpenRouter",
"description": "Connect OpenRouter accounts for your agents.",
"categories": [
"ai"
],
"branding": {
"logoUrl": "/brands/apps/openrouter.svg",
"darkLogoUrl": "/brands/apps/openrouter-dark.svg"
},
"urlPatterns": [
"https://openrouter.ai/api/*"
],
"methods": [
{
"key": "ai-api_key",
"label": "OpenRouter API key",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "api_key",
"ai": {
"provider": "openrouter",
"method": "api_key"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3",
"credentialFields": [
{
"key": "apiKey",
"label": "API key",
"type": "password",
"required": true,
"placeholder": "Enter API key",
"secret": true
}
],
"keyPlacement": {
"location": "env",
"name": "OPENROUTER_API_KEY"
}
}
]
}
@@ -0,0 +1,74 @@
{
"schemaVersion": 1,
"slug": "xai",
"name": "Grok",
"description": "Connect Grok accounts for your agents.",
"categories": [
"ai"
],
"branding": {
"logoUrl": "/brands/apps/xai.svg",
"darkLogoUrl": "/brands/apps/xai-dark.svg"
},
"urlPatterns": [
"https://api.x.ai/*"
],
"methods": [
{
"key": "ai-subscription",
"label": "Grok subscription",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "oauth",
"ai": {
"provider": "xai",
"method": "subscription"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3"
},
{
"key": "ai-api_key",
"label": "Grok API key",
"purpose": "ai",
"transport": "runtime_auth",
"auth": "api_key",
"ai": {
"provider": "xai",
"method": "api_key"
},
"grantKinds": [
"user",
"organization"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Authenticate an agent with this account.",
"guidanceMd": "Use your personal account or an explicitly shared company account.",
"riskTier": "S3",
"credentialFields": [
{
"key": "apiKey",
"label": "API key",
"type": "password",
"required": true,
"placeholder": "Enter API key",
"secret": true
}
],
"keyPlacement": {
"location": "env",
"name": "XAI_API_KEY"
}
}
]
}
+1
View File
@@ -2761,5 +2761,6 @@ export type { ExecutionProjection, ExecutionReconciliation, ExecutionBlocker } f
export { EXECUTION_RECONCILIATION_CAUSES, requiresExecutionReconciliation } from "./types/execution-projection.js";
export * from "./ai-connections.js";
export * from "./types/email.js";
export * from "./validators/email.js";
+7 -1
View File
@@ -1,3 +1,4 @@
import type { AiConnectionLoginIntent } from "../ai-connections.js";
import type {
AgentAdapterType,
PauseReason,
@@ -22,7 +23,9 @@ export interface AgentPermissions extends Record<string, unknown> {
authorizationPolicy?: TrustAuthorizationPolicy;
}
export type AgentRuntimeConfig = Record<string, unknown>;
export type AgentRuntimeConfig = Record<string, unknown> & {
aiConnection?: import("../ai-connections.js").AiConnectionBinding;
};
export type AgentInstructionsBundleMode = "managed" | "external";
@@ -197,6 +200,7 @@ export interface CodexAccountBindingClaim {
// The owner read of a login session. It adds the one-time prompt to the public
// response. Only the owner principal that started the session reads this shape.
export interface AdapterAuthSessionOwnerResponse extends AdapterAuthSessionResponse {
aiConnection?: AiConnectionLoginIntent;
prompt: AdapterAuthSessionPrompt | null;
codexAccountBinding?: CodexAccountBindingClaim | null;
}
@@ -204,6 +208,7 @@ export interface AdapterAuthSessionOwnerResponse extends AdapterAuthSessionRespo
// The request that starts a login session for one adapter in one environment.
// The owner principal comes from the authenticated caller, not from this body.
export interface StartAdapterAuthSessionRequest {
aiConnection?: AiConnectionLoginIntent;
environmentId: string;
adapterType: AgentAdapterType;
ttlSeconds?: number;
@@ -278,6 +283,7 @@ export interface ClaudeSetupTokenSessionResponse {
// the session reads this shape.
export interface ClaudeSetupTokenSessionOwnerResponse
extends ClaudeSetupTokenSessionResponse {
aiConnection?: AiConnectionLoginIntent;
panelMode: AdapterAuthPanelMode;
prompt: ClaudeSetupTokenSessionPrompt | null;
}
+1 -1
View File
@@ -2,7 +2,7 @@ import type { ConnectionGrantKind, ToolConnectionOwnership, ToolConnectionPurpos
export type AppCategory = "ai"|"analytics"|"commerce"|"communication"|"content"|"data"|"developer"|"productivity"|"other";
export type OAuthRedirectConstraints = "https-or-loopback-http";
export interface FieldDef { key:string; label:string; type:"text"|"password"|"textarea"|"datetime"|"select"|"checkbox"; required?:boolean; advanced?:boolean; hidden?:boolean; placeholder?:string; helperMd?:string; secret?:boolean; prefix?:string; defaultValue?:string|boolean; validation?:{pattern?:string;maxLength?:number}; options?:Array<{value:string;label:string}>; transport?:{location:"query"|"header";name:string;format?:"string"|"csv"|"boolean";omitFalse?:boolean} }
export interface ConnectionMethodDef { key:string; label?:string; purpose?:ToolConnectionPurpose; provider?:"slack"|"github"|"discord"|"microsoft-teams"|"telegram" | "agentmail"; transport:ToolConnectionTransport; auth:"oauth"|"api_key"|"none"; oauthStrategy?:"paperclip_cloud_connector"|"paperclip_id_connector"; connectorProfile?:string; capabilityProfile?:{key:string;label:string;description?:string}; grantKinds?:ConnectionGrantKind[]; ownershipModes:ToolConnectionOwnership[]; whenToUse:string; defaults?:{serverUrl?:string;serverUrlTemplate?:string;discoveryUrl?:string|null;serviceHost?:string;templateKey?:string;authorizationEndpoint?:string;tokenEndpoint?:string;metadataUrl?:string;scopesHint?:string[];oauthAuthorizationParams?:{access_type?:"offline";prompt?:"consent"};toolArgumentDefaults?:Record<string,unknown>}; tenantFields?:FieldDef[]; extensionFields?:FieldDef[]; configRequirements?:{atLeastOneOf?:string[]}; credentialFields?:FieldDef[]; keyPlacement?:{location:"header"|"query"|"body_json"|"env";name:string;prefix?:string|null}; credentialSources?:{vercelConnect?:{services:string[];principalModes:VercelConnectPrincipalMode[];scopes:string[];header:{name:string;prefix?:string|null}}}; guidanceMd:string; consoleLinks?:{register?:string;keys?:string;settings?:string;docs?:string}; warnings?:string[]; variants?:Array<{key:string;label:string;whenToUse:string;tenantFields?:FieldDef[]}>; riskTier:"S1"|"S2"|"S3"|"S4"; requiredResourceFilters?:string[] }
export interface ConnectionMethodDef { ai?: import("../ai-connections.js").AiConnectionMetadata; key:string; label?:string; purpose?:ToolConnectionPurpose; provider?:"slack"|"github"|"discord"|"microsoft-teams"|"telegram"|"agentmail"|"imessage-photon"; transport:ToolConnectionTransport; auth:"oauth"|"api_key"|"none"; oauthStrategy?:"paperclip_cloud_connector"|"paperclip_id_connector"; connectorProfile?:string; capabilityProfile?:{key:string;label:string;description?:string}; grantKinds?:ConnectionGrantKind[]; ownershipModes:ToolConnectionOwnership[]; whenToUse:string; defaults?:{serverUrl?:string;serverUrlTemplate?:string;discoveryUrl?:string|null;serviceHost?:string;templateKey?:string;authorizationEndpoint?:string;tokenEndpoint?:string;metadataUrl?:string;scopesHint?:string[];oauthAuthorizationParams?:{access_type?:"offline";prompt?:"consent"};toolArgumentDefaults?:Record<string,unknown>}; tenantFields?:FieldDef[]; extensionFields?:FieldDef[]; configRequirements?:{atLeastOneOf?:string[]}; credentialFields?:FieldDef[]; keyPlacement?:{location:"header"|"query"|"body_json"|"env";name:string;prefix?:string|null}; credentialSources?:{vercelConnect?:{services:string[];principalModes:VercelConnectPrincipalMode[];scopes:string[];header:{name:string;prefix?:string|null}}}; guidanceMd:string; consoleLinks?:{register?:string;keys?:string;settings?:string;docs?:string}; warnings?:string[]; variants?:Array<{key:string;label:string;whenToUse:string;tenantFields?:FieldDef[]}>; riskTier:"S1"|"S2"|"S3"|"S4"; requiredResourceFilters?:string[] }
export interface AppDefinition { schemaVersion:1; slug:string; name:string; description:string; categories:AppCategory[]; featured?:boolean; branding:{logoUrl:string;darkLogoUrl?:string;backgroundColor?:string;accentColor?:string}; urlPatterns:string[]; docsUrl?:string; setupPrerequisite?:{title:string;description:string;steps?:string[];actionLabel:string;actionUrl:string}; redirectConstraints?:OAuthRedirectConstraints; methods:ConnectionMethodDef[]; suggestable?:boolean; availability?:{available:boolean;reason?:string;robotEmail?:string}; ownershipAvailability?:Partial<Record<ToolConnectionOwnership,boolean>> }
export type SelfServeMcpAuthMode =
@@ -6,6 +6,7 @@ export const CHAT_PROVIDERS = [
"microsoft-teams",
"telegram",
"agentmail",
"imessage-photon",
] as const;
export type ChatProvider = (typeof CHAT_PROVIDERS)[number];
@@ -226,6 +227,7 @@ export interface ChatEndpoint {
botUsername?: string | null;
botLabel?: string | null;
botAvatarUrl?: string | null;
photonAllocation?: "dedicated" | "shared";
allowDirectMessages: boolean;
allowGroupChats: boolean;
allowUnlinkedPeople: boolean;
@@ -255,6 +257,7 @@ export interface ChatEndpointResource {
enabled: boolean;
createdAt: string;
updatedAt: string;
participants?: string[];
}
export interface ChatExternalPrincipal {
@@ -467,6 +470,7 @@ export interface UpdateChatEndpointInput {
export interface ConfigureChatEndpointInput {
action: "configure" | "verify" | "pause" | "resume" | "reconnect" | "remove";
credentials?: Record<string, string>;
photon?: PhotonChannelConfiguration;
}
export interface NormalizedChatEvent {
@@ -503,3 +507,15 @@ export interface NormalizedChatEvent {
};
raw: Record<string, unknown>;
}
/** Safe Photon project inspection; credentials and line tokens are never serialized. */
export interface PhotonProjectInspection {
projectId: string;
projectName: string;
allocation: "dedicated" | "shared";
eligible: boolean;
lines: Array<{ lineId: string; phoneNumber: string; eligible: boolean; unavailableReason?: string }>;
}
export type PhotonChannelConfiguration =
| { allocation?: "dedicated"; projectId: string; lineId: string }
| { allocation: "shared"; projectId: string };
@@ -42,6 +42,12 @@ export interface ConnectionRequestResult {
export type ConnectionIntentSetupConnection = Pick<ToolConnection, "id" | "applicationId" | "name" | "status" | "enabled">;
export interface ConnectionIntentSetupOptions {
aiConnection?: import("../ai-connections.js").AiConnectionBinding;
/** Selected account, including an unavailable default. Reconnect must preserve its identity. */
aiRepair?: {
connection: import("../ai-connections.js").AiManagedConnectionSummary;
canReconnect: boolean;
};
version: 1;
interaction: ConnectionIntentInteraction;
service: ConnectionSearchResultItem;
+4
View File
@@ -1057,6 +1057,8 @@ export interface IssueCommentMetadataSection {
export interface IssueCommentMetadata {
version: 1;
/** Inbound channel attribution; never an authorization input. */
sourceChannel?: "imessage-photon";
sourceRunId?: string | null;
sourceIdentityContextId?: string | null;
authorizationReason?: string | null;
@@ -1329,6 +1331,8 @@ export type ConnectionIntentPhase = "requested" | "authorizing" | "needs_retry";
*/
export interface ConnectionIntentPayload {
version: 1;
/** Runtime authentication requests cannot be satisfied by tool credentials. */
purpose?: "ai";
serviceSlug: string;
serviceName: string;
serviceLogoUrl?: string | null;
+2 -2
View File
@@ -68,8 +68,8 @@ export type {
export type ToolActorType = "agent" | "user" | "system" | "plugin";
export type ToolConnectionTransport =
"mcp_remote" | "rest_api" | "local_stdio" | "chat_sdk";
export type ToolConnectionPurpose = "tool" | "channel";
"mcp_remote" | "rest_api" | "local_stdio" | "chat_sdk" | "runtime_auth";
export type ToolConnectionPurpose = "tool" | "channel" | "ai";
export type ToolConnectionAuthKind = "oauth" | "api_key" | "none";
export type ToolConnectionOwnership =
"platform_shared" | "platform_provisioned" | "customer" | "dcr";
@@ -1,3 +1,4 @@
import { aiConnectionLoginIntentSchema } from "../ai-connections.js";
import { z } from "zod";
import { AGENT_ADAPTER_TYPES } from "../constants.js";
import { ADAPTER_AUTH_SESSION_STATUSES } from "../types/agent.js";
@@ -35,11 +36,13 @@ export type AdapterAuthSessionPrompt = z.infer<typeof adapterAuthSessionPromptSc
// The owner read schema. It adds the one-time prompt to the public response.
export const adapterAuthSessionOwnerResponseSchema = adapterAuthSessionResponseSchema.extend({
prompt: adapterAuthSessionPromptSchema.nullable(),
aiConnection: aiConnectionLoginIntentSchema.optional(),
}).strict();
export type AdapterAuthSessionOwnerResponse =
z.infer<typeof adapterAuthSessionOwnerResponseSchema>;
export const startAdapterAuthSessionRequestSchema = z.object({
aiConnection: aiConnectionLoginIntentSchema.optional(),
environmentId: z.string().guid(),
adapterType: z.enum(AGENT_ADAPTER_TYPES),
ttlSeconds: z.number().int().min(60).max(24 * 60 * 60).optional(),
+3
View File
@@ -1,3 +1,4 @@
import { aiConnectionBindingSchema } from "../ai-connections.js";
import { z } from "zod";
import {
AGENT_ICON_NAMES,
@@ -60,6 +61,7 @@ export const createAgentInstructionsBundleSchema = z.object({
});
export const agentRuntimeConfigSchema = z.object({
aiConnection: aiConnectionBindingSchema.optional(),
debug: z.object({
providerTrace: z.literal("raw").optional(),
}).strict().optional(),
@@ -242,6 +244,7 @@ export const resetAgentSessionSchema = z.object({
export type ResetAgentSession = z.infer<typeof resetAgentSessionSchema>;
export const testAdapterEnvironmentSchema = z.object({
aiConnection: aiConnectionBindingSchema.optional(),
/** Saved agent whose redacted environment entries are restored for this probe. */
agentId: z.string().guid().optional(),
/** One-shot provider keys for a probe. Never persist these in agent config. */
@@ -1,10 +1,11 @@
import { z } from "zod";
import { AI_CONNECTION_CAPABILITIES, aiConnectionMetadataSchema } from "../ai-connections.js";
import { connectionGrantKindSchema, toolConnectionOwnershipSchema, toolConnectionPurposeSchema, toolConnectionTransportSchema } from "./tool-access.js";
const appBrandAssetUrlSchema=z.string().refine((value)=>{
if(/^\/brands\/apps\/[a-z0-9][a-z0-9._-]*\.(?:svg|png)$/i.test(value))return true;
try{return new URL(value).protocol==="https:";}catch{return false;}
},{message:"Brand assets must be HTTPS URLs or local /brands/apps SVG/PNG paths"});
const field=z.object({key:z.string().min(1),label:z.string().min(1),type:z.enum(["text","password","textarea","datetime","select","checkbox"]),required:z.boolean().optional(),advanced:z.boolean().optional(),hidden:z.boolean().optional(),placeholder:z.string().optional(),helperMd:z.string().optional(),secret:z.boolean().optional(),prefix:z.string().optional(),defaultValue:z.union([z.string(),z.boolean()]).optional(),validation:z.object({pattern:z.string().optional(),maxLength:z.number().int().positive().optional()}).optional(),options:z.array(z.object({value:z.string(),label:z.string()})).optional(),transport:z.object({location:z.enum(["query","header"]),name:z.string().min(1),format:z.enum(["string","csv","boolean"]).optional(),omitFalse:z.boolean().optional()}).optional()}).superRefine((v,c)=>{if(v.required&&v.type!=="checkbox"&&!v.placeholder)c.addIssue({code:"custom",message:"Required fields need placeholders",path:["placeholder"]});if(v.type==="select"&&(!v.options||v.options.length===0))c.addIssue({code:"custom",message:"Select fields need options",path:["options"]});if(v.hidden&&v.defaultValue===undefined)c.addIssue({code:"custom",message:"Hidden fields need defaults",path:["defaultValue"]})});
export const connectionMethodDefSchema=z.object({key:z.string().min(1),label:z.string().min(1).optional(),purpose:toolConnectionPurposeSchema.optional(),provider:z.enum(["slack","github","discord","microsoft-teams","telegram","agentmail"]).optional(),transport:toolConnectionTransportSchema,auth:z.enum(["oauth","api_key","none"]),oauthStrategy:z.enum(["paperclip_cloud_connector","paperclip_id_connector"]).optional(),connectorProfile:z.string().regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/).optional(),capabilityProfile:z.object({key:z.string().min(1),label:z.string().min(1),description:z.string().min(1).optional()}).optional(),grantKinds:z.array(connectionGrantKindSchema).min(1).optional(),ownershipModes:z.array(toolConnectionOwnershipSchema).min(1),whenToUse:z.string().min(1),defaults:z.object({serverUrl:z.string().url().optional(),serverUrlTemplate:z.string().regex(/^https:\/\//).optional(),discoveryUrl:z.string().url().nullable().optional(),serviceHost:z.string().optional(),templateKey:z.string().optional(),authorizationEndpoint:z.string().url().optional(),tokenEndpoint:z.string().url().optional(),metadataUrl:z.string().url().optional(),scopesHint:z.array(z.string()).optional(),oauthAuthorizationParams:z.object({access_type:z.literal("offline").optional(),prompt:z.literal("consent").optional()}).optional(),toolArgumentDefaults:z.record(z.string(),z.unknown()).optional()}).optional(),tenantFields:z.array(field).optional(),extensionFields:z.array(field).optional(),configRequirements:z.object({atLeastOneOf:z.array(z.string().min(1)).min(1).optional()}).optional(),credentialFields:z.array(field).optional(),keyPlacement:z.object({location:z.enum(["header","query","body_json","env"]),name:z.string().min(1),prefix:z.string().nullable().optional()}).optional(),credentialSources:z.object({vercelConnect:z.object({services:z.array(z.string().min(1)).min(1),principalModes:z.array(z.enum(["app","user"])).min(1),scopes:z.array(z.string().min(1)).min(1),header:z.object({name:z.string().min(1),prefix:z.string().nullable().optional()})}).optional()}).optional(),guidanceMd:z.string().min(1),consoleLinks:z.object({register:z.string().url().optional(),keys:z.string().url().optional(),settings:z.string().url().optional(),docs:z.string().url().optional()}).optional(),warnings:z.array(z.string()).optional(),variants:z.array(z.object({key:z.string(),label:z.string(),whenToUse:z.string(),tenantFields:z.array(field).optional()})).optional(),riskTier:z.enum(["S1","S2","S3","S4"]),requiredResourceFilters:z.array(z.string()).optional()}).superRefine((v,c)=>{const purpose=v.purpose??"tool";if(v.transport==="chat_sdk"&&purpose!=="channel")c.addIssue({code:"custom",message:"Chat SDK methods must be channel connections",path:["purpose"]});if(purpose==="channel"&&v.transport!=="chat_sdk"&&!(v.provider==="agentmail"&&v.transport==="rest_api"))c.addIssue({code:"custom",message:"Channel connections must use the Chat SDK transport",path:["transport"]});if(purpose==="channel"&&!v.provider)c.addIssue({code:"custom",message:"Channel connections require a chat provider",path:["provider"]});if(v.auth==="api_key"&&!v.keyPlacement&&purpose!=="channel")c.addIssue({code:"custom",message:"API-key tool methods require keyPlacement",path:["keyPlacement"]});if(v.oauthStrategy&&v.auth!=="oauth")c.addIssue({code:"custom",message:"OAuth strategies require OAuth auth",path:["oauthStrategy"]});if(v.oauthStrategy&&!v.connectorProfile)c.addIssue({code:"custom",message:"Paperclip Cloud connector methods require connectorProfile",path:["connectorProfile"]});if(v.connectorProfile&&!v.oauthStrategy)c.addIssue({code:"custom",message:"connectorProfile requires a Paperclip Cloud OAuth strategy",path:["connectorProfile"]});if(v.credentialSources?.vercelConnect&&(v.transport!=="mcp_remote"||v.auth==="none"))c.addIssue({code:"custom",message:"Vercel Connect requires an authenticated remote MCP method",path:["credentialSources","vercelConnect"]});const keys=new Set([...(v.tenantFields??[]),...(v.extensionFields??[])].map((entry)=>entry.key));for(const key of v.configRequirements?.atLeastOneOf??[])if(!keys.has(key))c.addIssue({code:"custom",message:"Config requirement references an unknown field",path:["configRequirements","atLeastOneOf"]});if(v.defaults?.serverUrl&&v.defaults.serverUrlTemplate)c.addIssue({code:"custom",message:"Use either serverUrl or serverUrlTemplate",path:["defaults"]});for(const placeholder of v.defaults?.serverUrlTemplate?.matchAll(/\{([a-zA-Z0-9_-]+)\}/g)??[])if(!keys.has(placeholder[1]))c.addIssue({code:"custom",message:"Server URL template references an unknown field",path:["defaults","serverUrlTemplate"]})});
export const connectionMethodDefSchema=z.object({key:z.string().min(1),label:z.string().min(1).optional(),purpose:z.union([toolConnectionPurposeSchema,z.literal("ai")]).optional(),provider:z.enum(["slack","github","discord","microsoft-teams","telegram","agentmail","imessage-photon"]).optional(),transport:z.union([toolConnectionTransportSchema,z.literal("runtime_auth")]),ai:aiConnectionMetadataSchema.optional(),auth:z.enum(["oauth","api_key","none"]),oauthStrategy:z.enum(["paperclip_cloud_connector","paperclip_id_connector"]).optional(),connectorProfile:z.string().regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/).optional(),capabilityProfile:z.object({key:z.string().min(1),label:z.string().min(1),description:z.string().min(1).optional()}).optional(),grantKinds:z.array(connectionGrantKindSchema).min(1).optional(),ownershipModes:z.array(toolConnectionOwnershipSchema).min(1),whenToUse:z.string().min(1),defaults:z.object({serverUrl:z.string().url().optional(),serverUrlTemplate:z.string().regex(/^https:\/\//).optional(),discoveryUrl:z.string().url().nullable().optional(),serviceHost:z.string().optional(),templateKey:z.string().optional(),authorizationEndpoint:z.string().url().optional(),tokenEndpoint:z.string().url().optional(),metadataUrl:z.string().url().optional(),scopesHint:z.array(z.string()).optional(),oauthAuthorizationParams:z.object({access_type:z.literal("offline").optional(),prompt:z.literal("consent").optional()}).optional(),toolArgumentDefaults:z.record(z.string(),z.unknown()).optional()}).optional(),tenantFields:z.array(field).optional(),extensionFields:z.array(field).optional(),configRequirements:z.object({atLeastOneOf:z.array(z.string().min(1)).min(1).optional()}).optional(),credentialFields:z.array(field).optional(),keyPlacement:z.object({location:z.enum(["header","query","body_json","env"]),name:z.string().min(1),prefix:z.string().nullable().optional()}).optional(),credentialSources:z.object({vercelConnect:z.object({services:z.array(z.string().min(1)).min(1),principalModes:z.array(z.enum(["app","user"])).min(1),scopes:z.array(z.string().min(1)).min(1),header:z.object({name:z.string().min(1),prefix:z.string().nullable().optional()})}).optional()}).optional(),guidanceMd:z.string().min(1),consoleLinks:z.object({register:z.string().url().optional(),keys:z.string().url().optional(),settings:z.string().url().optional(),docs:z.string().url().optional()}).optional(),warnings:z.array(z.string()).optional(),variants:z.array(z.object({key:z.string(),label:z.string(),whenToUse:z.string(),tenantFields:z.array(field).optional()})).optional(),riskTier:z.enum(["S1","S2","S3","S4"]),requiredResourceFilters:z.array(z.string()).optional()}).superRefine((v,c)=>{if((v.transport==="runtime_auth")!==Boolean(v.ai))c.addIssue({code:"custom",message:"Runtime authentication requires AI metadata and AI metadata requires runtime_auth",path:["ai"]});if(v.ai&&!AI_CONNECTION_CAPABILITIES[v.ai.provider].methods[v.ai.method])c.addIssue({code:"custom",message:"Unsupported AI sign-in method",path:["ai","method"]});if(v.ai&&v.auth!==(v.ai.method==="subscription"?"oauth":"api_key"))c.addIssue({code:"custom",message:"AI sign-in method must match authentication",path:["auth"]});const purpose=v.purpose??"tool";if((purpose==="ai")!==(v.transport==="runtime_auth"))c.addIssue({code:"custom",message:"AI methods require runtime_auth and runtime_auth requires AI purpose",path:["purpose"]});if(v.transport==="chat_sdk"&&purpose!=="channel")c.addIssue({code:"custom",message:"Chat SDK methods must be channel connections",path:["purpose"]});if(purpose==="channel"&&v.transport!=="chat_sdk"&&!(v.provider==="agentmail"&&v.transport==="rest_api"))c.addIssue({code:"custom",message:"Channel connections must use the Chat SDK transport",path:["transport"]});if(purpose==="channel"&&!v.provider)c.addIssue({code:"custom",message:"Channel connections require a chat provider",path:["provider"]});if(v.auth==="api_key"&&!v.keyPlacement&&purpose!=="channel")c.addIssue({code:"custom",message:"API-key tool methods require keyPlacement",path:["keyPlacement"]});if(v.oauthStrategy&&v.auth!=="oauth")c.addIssue({code:"custom",message:"OAuth strategies require OAuth auth",path:["oauthStrategy"]});if(v.oauthStrategy&&!v.connectorProfile)c.addIssue({code:"custom",message:"Paperclip Cloud connector methods require connectorProfile",path:["connectorProfile"]});if(v.connectorProfile&&!v.oauthStrategy)c.addIssue({code:"custom",message:"connectorProfile requires a Paperclip Cloud OAuth strategy",path:["connectorProfile"]});if(v.credentialSources?.vercelConnect&&(v.transport!=="mcp_remote"||v.auth==="none"))c.addIssue({code:"custom",message:"Vercel Connect requires an authenticated remote MCP method",path:["credentialSources","vercelConnect"]});const keys=new Set([...(v.tenantFields??[]),...(v.extensionFields??[])].map((entry)=>entry.key));for(const key of v.configRequirements?.atLeastOneOf??[])if(!keys.has(key))c.addIssue({code:"custom",message:"Config requirement references an unknown Line truncated
export const appDefinitionSchema=z.object({schemaVersion:z.literal(1),slug:z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),name:z.string().min(1),description:z.string().min(1),categories:z.array(z.enum(["ai","analytics","commerce","communication","content","data","developer","productivity","other"])).min(1),featured:z.boolean().optional(),branding:z.object({logoUrl:appBrandAssetUrlSchema,darkLogoUrl:appBrandAssetUrlSchema.optional(),backgroundColor:z.string().optional(),accentColor:z.string().optional()}),urlPatterns:z.array(z.string()),docsUrl:z.string().url().optional(),setupPrerequisite:z.object({title:z.string().min(1),description:z.string().min(1),steps:z.array(z.string().min(1)).min(1).optional(),actionLabel:z.string().min(1),actionUrl:z.string().url()} ).optional(),redirectConstraints:z.enum(["https-or-loopback-http"]).optional(),methods:z.array(connectionMethodDefSchema).min(1),suggestable:z.boolean().optional(),availability:z.object({available:z.boolean(),reason:z.string().optional(),robotEmail:z.string().optional()}).optional(),ownershipAvailability:z.object({platform_shared:z.boolean().optional(),platform_provisioned:z.boolean().optional(),customer:z.boolean().optional(),dcr:z.boolean().optional()}).optional()});
export const appDefinitionsSchema=z.array(appDefinitionSchema).superRefine((v,c)=>{const s=new Set<string>();v.forEach((a,i)=>{if(s.has(a.slug))c.addIssue({code:"custom",message:"Duplicate slug",path:[i,"slug"]});s.add(a.slug)})});
@@ -87,6 +87,17 @@ export const updateChatEndpointSchema = z
message: "At least one chat endpoint field is required",
});
export const photonProjectIdSchema = z.string().trim().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/);
export const photonLineIdSchema = z.string().trim().min(1).max(63).regex(/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/);
export const photonChannelConfigurationSchema = z.union([
z.object({ allocation: z.literal("dedicated").default("dedicated"), projectId: photonProjectIdSchema, lineId: photonLineIdSchema }).strict(),
z.object({ allocation: z.literal("shared"), projectId: photonProjectIdSchema }).strict(),
]);
export const inspectPhotonProjectSchema = z.object({
projectId: photonProjectIdSchema,
projectSecret: z.string().min(1).max(4096),
}).strict();
export const configureChatEndpointSchema = z
.object({
action: z.enum([
@@ -98,11 +109,12 @@ export const configureChatEndpointSchema = z
"remove",
]),
credentials: chatEndpointCredentialsSchema.optional(),
photon: photonChannelConfigurationSchema.optional(),
})
.strict()
.superRefine((value, ctx) => {
if (
value.credentials &&
(value.credentials || value.photon) &&
value.action !== "configure" &&
value.action !== "reconnect"
) {
@@ -1,3 +1,4 @@
import { aiConnectionLoginIntentSchema } from "../ai-connections.js";
import { z } from "zod";
import { AGENT_ADAPTER_TYPES } from "../constants.js";
import { ADAPTER_AUTH_PANEL_MODES, SETUP_TOKEN_TRANSPORT_ADVISORY_CODE } from "../types/agent.js";
@@ -43,6 +44,7 @@ export type ClaudeSetupTokenOverwrite =
// runtime does not support a caller-supplied session length, so the schema
// exposes no `ttlSeconds` field; a legacy `ttlSeconds` fails the strict parse.
export const startClaudeSetupTokenSessionRequestSchema = z.object({
aiConnection: aiConnectionLoginIntentSchema.optional(),
environmentId: z.string().guid(),
adapterType: z.enum(AGENT_ADAPTER_TYPES),
overwrite: claudeSetupTokenOverwriteSchema.optional(),
@@ -87,6 +89,7 @@ export const claudeSetupTokenSessionOwnerResponseSchema =
claudeSetupTokenSessionResponseSchema.extend({
panelMode: adapterAuthPanelModeSchema,
prompt: claudeSetupTokenSessionPromptSchema.nullable(),
aiConnection: aiConnectionLoginIntentSchema.optional(),
}).strict();
export type ClaudeSetupTokenSessionOwnerResponse =
z.infer<typeof claudeSetupTokenSessionOwnerResponseSchema>;
+2
View File
@@ -1000,6 +1000,7 @@ export const issueCommentMetadataSectionSchema = z
export const issueCommentMetadataSchema = z
.object({
version: z.literal(1),
sourceChannel: z.literal("imessage-photon").optional(),
sourceRunId: z.string().guid().nullable().optional(),
authorizationReason: z
.string()
@@ -1073,6 +1074,7 @@ const connectionIntentBrandAssetSchema = z
export const connectionIntentPayloadSchema = z
.object({
purpose: z.literal("ai").optional(),
version: z.literal(1),
serviceSlug: z.string().trim().min(1).max(120),
serviceName: z.string().trim().min(1).max(160),