in which pylon replaces intercom (#2783)
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
import { apiBaseUrl, artifactApiBaseUrl, envCredential } from "@/util/env";
|
import { apiBaseUrl, artifactApiBaseUrl, envCredential } from "@/util/env";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
type ApiVersion = "sans-api-v1" | "v1" | "v2";
|
||||||
|
|
||||||
const apiV1BaseUrl = apiBaseUrl;
|
const apiV1BaseUrl = apiBaseUrl;
|
||||||
const apiV2BaseUrl = apiBaseUrl.replace("v1", "v2");
|
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({
|
const client = axios.create({
|
||||||
baseURL: apiV1BaseUrl,
|
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({
|
const artifactApiClient = axios.create({
|
||||||
baseURL: artifactApiBaseUrl,
|
baseURL: artifactApiBaseUrl,
|
||||||
});
|
});
|
||||||
@@ -52,18 +65,36 @@ export function removeApiKeyHeader() {
|
|||||||
|
|
||||||
async function getClient(
|
async function getClient(
|
||||||
credentialGetter: CredentialGetter | null,
|
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) {
|
if (credentialGetter) {
|
||||||
removeApiKeyHeader();
|
removeApiKeyHeader();
|
||||||
|
|
||||||
const credential = await credentialGetter();
|
const credential = await credentialGetter();
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
console.warn("No credential found");
|
console.warn("No credential found");
|
||||||
return version === "v1" ? client : v2Client;
|
return get();
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthorizationHeader(credential);
|
setAuthorizationHeader(credential);
|
||||||
}
|
}
|
||||||
return version === "v1" ? client : v2Client;
|
|
||||||
|
return get();
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CredentialGetter = () => Promise<string | null>;
|
export type CredentialGetter = () => Promise<string | null>;
|
||||||
|
|||||||
@@ -411,3 +411,7 @@ export const RunEngine = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type RunEngine = (typeof RunEngine)[keyof typeof RunEngine];
|
export type RunEngine = (typeof RunEngine)[keyof typeof RunEngine];
|
||||||
|
|
||||||
|
export type PylonEmailHash = {
|
||||||
|
hash: string;
|
||||||
|
};
|
||||||
|
|||||||
7
skyvern-frontend/src/global.d.ts
vendored
Normal file
7
skyvern-frontend/src/global.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
@@ -275,10 +275,15 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
SKYVERN_BROWSER_VNC_PORT: int = 6080
|
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.
|
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]]:
|
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
|
Keys are model names available to blocks in the frontend. These map to key names
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from skyvern.forge.sdk.routes import agent_protocol # noqa: F401
|
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 browser_sessions # noqa: F401
|
||||||
from skyvern.forge.sdk.routes import credentials # 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 # noqa: F401
|
||||||
from skyvern.forge.sdk.routes import streaming_commands # noqa: F401
|
from skyvern.forge.sdk.routes import streaming_commands # noqa: F401
|
||||||
from skyvern.forge.sdk.routes import streaming_vnc # noqa: F401
|
from skyvern.forge.sdk.routes import streaming_vnc # noqa: F401
|
||||||
|
|||||||
34
skyvern/forge/sdk/routes/pylon.py
Normal file
34
skyvern/forge/sdk/routes/pylon.py
Normal 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)
|
||||||
5
skyvern/forge/sdk/schemas/pylon.py
Normal file
5
skyvern/forge/sdk/schemas/pylon.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class PylonHash(BaseModel):
|
||||||
|
hash: str
|
||||||
Reference in New Issue
Block a user