mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 02:32:07 +01:00
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:
commit
57c2f528f1
294 files changed
+115930
-1966
No files matched your search
@@ -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[];
|
||||
@@ -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)),
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user