Repository navigation
Expand file tree
/
Copy pathenv.ts
More file actions
107 lines (101 loc) · 4.15 KB
/
Copy pathenv.ts
File metadata and controls
107 lines (101 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
export const DEFAULT_INTELLIGENCE_API_URL =
"https://api.intelligence.copilotkit.ai";
export const DEFAULT_INTELLIGENCE_GATEWAY_WS_URL =
"wss://realtime.intelligence.copilotkit.ai";
export const DEFAULT_INTELLIGENCE_CHANNEL_NAME = "open-tag";
export const DEFAULT_AGENT_DISPLAY_NAME = "OpenTag";
export interface AppEnvironment {
agentDisplayName: string;
agentUrl: string;
agentAuthHeader?: string;
intelligenceApiKey: string;
intelligenceApiUrl: string;
intelligenceGatewayWsUrl: string;
learningContainerId?: string;
channelName: string;
port: number;
/**
* Where a browser can reach this service, without a trailing slash.
*
* Needed only for per-person Arcade connections, which is why it is optional:
* a deployment using Composio, or Arcade with shared apps only, never grows a
* public address and must keep starting without one. Railway supplies its own
* as `RAILWAY_PUBLIC_DOMAIN`, so that is read as a fallback rather than made
* a second thing to configure.
*/
publicUrl?: string;
}
/**
* The address a browser can reach this service on, if there is one.
*
* An explicit setting wins, because a deployment may sit behind a domain the
* platform does not know about. Railway's own variable is a bare hostname with
* no scheme, so it is given one; without that the link built from it is not a
* URL a browser will open.
*/
function readPublicUrl(env: NodeJS.ProcessEnv): string | undefined {
const configured = env.PUBLIC_URL?.trim();
if (configured) return configured.replace(/\/+$/, "");
const railway = env.RAILWAY_PUBLIC_DOMAIN?.trim();
if (!railway) return undefined;
const withScheme = /^https?:\/\//.test(railway)
? railway
: `https://${railway}`;
return withScheme.replace(/\/+$/, "");
}
/** Trimmed, and blank counts as missing — a deploy UI's "unset" is an empty string. */
function required(env: NodeJS.ProcessEnv, name: string): string {
const value = env[name]?.trim();
if (!value) {
throw new Error(`Missing required env var: ${name}`);
}
return value;
}
export function parsePort(
raw: string | undefined,
defaultPort = 3000,
name = "PORT",
): number {
if (raw === undefined) return defaultPort;
const port = Number(raw);
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
throw new Error(`Invalid ${name}: "${raw}"`);
}
return port;
}
export function readEnvironment(
env: NodeJS.ProcessEnv = process.env,
): AppEnvironment {
return {
agentDisplayName:
env.AGENT_DISPLAY_NAME?.trim() || DEFAULT_AGENT_DISPLAY_NAME,
agentUrl: required(env, "AGENT_URL"),
// Trimmed like every neighbour, and blank means unset. This one value goes
// out as an HTTP header: a trailing newline is not a legal header value and
// makes `fetch` reject every request to the agent, and a whitespace-only
// value reads as "a secret is configured" everywhere it is checked while
// authorizing nothing.
agentAuthHeader: env.AGENT_AUTH_HEADER?.trim() || undefined,
intelligenceApiKey: required(env, "INTELLIGENCE_API_KEY"),
// `||` rather than `??`: a variable declared and left empty is how a deploy
// platform's UI represents "not set", and `??` let that empty string defeat
// the default and become an empty URL.
intelligenceApiUrl:
env.INTELLIGENCE_API_URL?.trim() || DEFAULT_INTELLIGENCE_API_URL,
intelligenceGatewayWsUrl:
env.INTELLIGENCE_GATEWAY_WS_URL?.trim() ||
DEFAULT_INTELLIGENCE_GATEWAY_WS_URL,
learningContainerId:
env.INTELLIGENCE_LEARNING_CONTAINER_ID?.trim() || undefined,
channelName:
env.INTELLIGENCE_CHANNEL_NAME?.trim() || DEFAULT_INTELLIGENCE_CHANNEL_NAME,
// `?.trim() || undefined` like every neighbour, and for the same reason:
// this module's rule is that a blank value counts as unset, because a
// deploy platform's UI represents "not set" as a declared empty string.
// `PORT` alone did not follow it — `parsePort("")` throws, so a `PORT` row
// left empty in a deploy UI aborted boot rather than falling back to the
// default every other variable here falls back to.
port: parsePort(env.PORT?.trim() || undefined),
publicUrl: readPublicUrl(env),
};
}