in which pylon replaces intercom (#2783)

This commit is contained in:
Shuchang Zheng
2025-06-25 05:45:07 +08:00
committed by GitHub
parent 6b5699a98c
commit 60dcd6bcb1
7 changed files with 91 additions and 4 deletions

View File

@@ -1,8 +1,13 @@
import { apiBaseUrl, artifactApiBaseUrl, envCredential } from "@/util/env";
import axios from "axios";
type ApiVersion = "sans-api-v1" | "v1" | "v2";
const apiV1BaseUrl = apiBaseUrl;
const apiV2BaseUrl = apiBaseUrl.replace("v1", "v2");
const url = new URL(apiBaseUrl);
const pathname = url.pathname.replace("/api", "");
const apiSansApiV1BaseUrl = `${url.origin}${pathname}`;
const client = axios.create({
baseURL: apiV1BaseUrl,
@@ -20,6 +25,14 @@ const v2Client = axios.create({
},
});
const clientSansApiV1 = axios.create({
baseURL: apiSansApiV1BaseUrl,
headers: {
"Content-Type": "application/json",
"x-api-key": envCredential,
},
});
const artifactApiClient = axios.create({
baseURL: artifactApiBaseUrl,
});
@@ -52,18 +65,36 @@ export function removeApiKeyHeader() {
async function getClient(
credentialGetter: CredentialGetter | null,
version: string = "v1",
version: ApiVersion = "v1",
) {
const get = () => {
switch (version) {
case "sans-api-v1":
return clientSansApiV1;
case "v1":
return client;
case "v2":
return v2Client;
default: {
throw new Error(`Unknown version: ${version}`);
}
}
};
if (credentialGetter) {
removeApiKeyHeader();
const credential = await credentialGetter();
if (!credential) {
console.warn("No credential found");
return version === "v1" ? client : v2Client;
return get();
}
setAuthorizationHeader(credential);
}
return version === "v1" ? client : v2Client;
return get();
}
export type CredentialGetter = () => Promise<string | null>;

View File

@@ -411,3 +411,7 @@ export const RunEngine = {
} as const;
export type RunEngine = (typeof RunEngine)[keyof typeof RunEngine];
export type PylonEmailHash = {
hash: string;
};

7
skyvern-frontend/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
interface Window {
pylon: {
chat_settings: { [k: string]: string };
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Pylon: (method: string, ...args: any[]) => void;
}

View File

@@ -275,10 +275,15 @@ class Settings(BaseSettings):
SKYVERN_BROWSER_VNC_PORT: int = 6080
"""
The websockified port on which the VNC server of a persistent browser is
The websockified port on which the VNC server of a persistent browser is
listening.
"""
PYLON_IDENTITY_VERIFICATION_SECRET: str | None = None
"""
The secret used to sign the email/identity of the user.
"""
def get_model_name_to_llm_key(self) -> dict[str, dict[str, str]]:
"""
Keys are model names available to blocks in the frontend. These map to key names

View File

@@ -1,6 +1,7 @@
from skyvern.forge.sdk.routes import agent_protocol # noqa: F401
from skyvern.forge.sdk.routes import browser_sessions # noqa: F401
from skyvern.forge.sdk.routes import credentials # noqa: F401
from skyvern.forge.sdk.routes import pylon # noqa: F401
from skyvern.forge.sdk.routes import streaming # noqa: F401
from skyvern.forge.sdk.routes import streaming_commands # noqa: F401
from skyvern.forge.sdk.routes import streaming_vnc # noqa: F401

View File

@@ -0,0 +1,34 @@
import hashlib
import hmac
import structlog
from fastapi import Query
from skyvern.config import settings
from skyvern.forge.sdk.routes.routers import base_router
from skyvern.forge.sdk.schemas.pylon import PylonHash
LOG = structlog.get_logger()
@base_router.get(
"/pylon/email_hash",
include_in_schema=False,
response_model=PylonHash,
)
def get_pylon_email_hash(email: str = Query(...)) -> PylonHash:
no_hash = "???-no-hash-???"
secret = settings.PYLON_IDENTITY_VERIFICATION_SECRET
if not secret:
LOG.error("No Pylon identity verification secret", email=email)
return PylonHash(hash=no_hash)
try:
secret_bytes = bytes.fromhex(secret)
signature = hmac.new(secret_bytes, email.encode(), hashlib.sha256).hexdigest()
return PylonHash(hash=signature)
except Exception:
LOG.exception("Failed to generate Pylon email hash", email=email)
return PylonHash(hash=no_hash)

View File

@@ -0,0 +1,5 @@
from pydantic import BaseModel
class PylonHash(BaseModel):
hash: str