Skip to content

Commit 1c20a32

Browse files
committed
feat(sdk): functional baseURL and fetch override on chat transports
TriggerChatTransport, AgentChat, and chat.createStartSessionAction now accept a string-or-function baseURL so callers can route per endpoint — e.g. .in/append through a trusted edge proxy while keeping .out SSE direct. The same surfaces add a fetch override for header injection, custom retries, or proxy rewrites that go beyond URL routing; SSE GETs are covered via a new fetchClient option on SSEStreamSubscription. streamBaseURL on TriggerChatTransport is kept as a backwards-compat alias and continues to win for the "out" endpoint when set. Plain-string baseURL still applies to every endpoint, matching prior behavior.
1 parent 6c9f1f1 commit 1c20a32

5 files changed

Lines changed: 479 additions & 105 deletions

File tree

packages/core/src/v3/apiClient/runStream.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ export class SSEStreamSubscription implements StreamSubscription {
236236
// permanently. `404` (stream gone) and `410` (session closed)
237237
// are sensible defaults; tune per-caller for other 4xx.
238238
nonRetryableStatuses?: readonly number[];
239+
// Optional fetch override. Used by transports that need to route
240+
// the SSE connect through a custom path (proxy, custom headers,
241+
// tracing). Defaults to global `fetch`.
242+
fetchClient?: typeof fetch;
239243
}
240244
) {
241245
this.lastEventId = options.lastEventId;
@@ -331,7 +335,8 @@ export class SSEStreamSubscription implements StreamSubscription {
331335
headers["Timeout-Seconds"] = this.options.timeoutInSeconds.toString();
332336
}
333337

334-
const response = await fetch(this.url, {
338+
const fetchClient = this.options.fetchClient ?? fetch;
339+
const response = await fetchClient(this.url, {
335340
headers,
336341
signal: this.internalAbort.signal,
337342
});

packages/trigger-sdk/src/v3/ai.ts

Lines changed: 159 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
type TaskWithSchema,
3636
SESSION_IN_EVENT_ID_HEADER,
3737
TRIGGER_CONTROL_SUBTYPE,
38+
generateJWT,
3839
type WriterStreamOptions,
3940
} from "@trigger.dev/core/v3";
4041
import type {
@@ -8411,6 +8412,32 @@ export type { InferChatClientData, InferChatUIMessage } from "./ai-shared.js";
84118412
/**
84128413
* Options for {@link createChatStartSessionAction}.
84138414
*/
8415+
/**
8416+
* Discriminator for per-endpoint `baseURL` / `fetch` callbacks on
8417+
* `createChatStartSessionAction`.
8418+
*
8419+
* - `"sessions"` — `POST /api/v1/sessions` (session create + first run trigger).
8420+
* - `"auth"` — `POST /api/v1/auth/jwt/claims` (only fired when
8421+
* `tokenTTL` is set; otherwise the publicAccessToken from session create
8422+
* is reused as-is).
8423+
*/
8424+
export type ChatStartSessionEndpoint = "sessions" | "auth";
8425+
8426+
export type ChatStartSessionEndpointContext = {
8427+
endpoint: ChatStartSessionEndpoint;
8428+
chatId: string;
8429+
};
8430+
8431+
export type ChatStartSessionBaseURLResolver = (
8432+
ctx: ChatStartSessionEndpointContext
8433+
) => string;
8434+
8435+
export type ChatStartSessionFetchOverride = (
8436+
url: string,
8437+
init: RequestInit,
8438+
ctx: ChatStartSessionEndpointContext
8439+
) => Promise<Response>;
8440+
84148441
export type CreateChatStartSessionActionOptions = {
84158442
/** TTL for the session-scoped public access token. @default "1h" */
84168443
tokenTTL?: string | number | Date;
@@ -8419,6 +8446,21 @@ export type CreateChatStartSessionActionOptions = {
84198446
* Per-call `params.triggerConfig` shallow-merges on top.
84208447
*/
84218448
triggerConfig?: Partial<SessionTriggerConfig>;
8449+
/**
8450+
* Override the Trigger.dev API base URL. String applies to both
8451+
* `/api/v1/sessions` and `/api/v1/auth/jwt/claims`; function picks per
8452+
* endpoint. When unset, falls back to `apiClientManager.baseURL`
8453+
* (typically the `TRIGGER_API_URL` env var). Set this to route session
8454+
* create through a trusted edge proxy that injects server-side signal
8455+
* into `basePayload.metadata` before forwarding upstream.
8456+
*/
8457+
baseURL?: string | ChatStartSessionBaseURLResolver;
8458+
/**
8459+
* Per-request fetch override. Receives the resolved URL, RequestInit,
8460+
* and endpoint context. Use for header injection, proxy routing, or
8461+
* custom retry. Applies to both session-create and JWT-claims POSTs.
8462+
*/
8463+
fetch?: ChatStartSessionFetchOverride;
84228464
};
84238465

84248466
/**
@@ -8542,27 +8584,47 @@ function createChatStartSessionAction(
85428584
: {}),
85438585
};
85448586

8545-
const created = await sessions.start({
8546-
type: "chat.agent",
8587+
const startBody = {
8588+
type: "chat.agent" as const,
85478589
externalId: params.chatId,
85488590
taskIdentifier: taskId,
85498591
triggerConfig,
85508592
metadata: params.metadata,
8551-
});
8593+
};
8594+
8595+
const baseURLOption = options?.baseURL;
8596+
const fetchOverride = options?.fetch;
8597+
const hasOverride = baseURLOption !== undefined || fetchOverride !== undefined;
8598+
8599+
const created: { id: string; runId: string; publicAccessToken: string } = hasOverride
8600+
? await callSessionsCreateWithOverride({
8601+
chatId: params.chatId,
8602+
body: startBody,
8603+
baseURLOption,
8604+
fetchOverride,
8605+
})
8606+
: await sessions.start(startBody);
85528607

85538608
// Session create returns a session PAT directly when called with a
85548609
// start token, but when the SDK call goes via the secret key we still
85558610
// need to mint our own (the server returns a PAT regardless, but
85568611
// re-minting here lets the customer override `tokenTTL`).
85578612
const publicAccessToken =
85588613
options?.tokenTTL !== undefined
8559-
? await auth.createPublicToken({
8560-
scopes: {
8561-
read: { sessions: params.chatId },
8562-
write: { sessions: params.chatId },
8563-
},
8564-
expirationTime: options.tokenTTL,
8565-
})
8614+
? hasOverride
8615+
? await mintPublicTokenWithOverride({
8616+
chatId: params.chatId,
8617+
expirationTime: options.tokenTTL,
8618+
baseURLOption,
8619+
fetchOverride,
8620+
})
8621+
: await auth.createPublicToken({
8622+
scopes: {
8623+
read: { sessions: params.chatId },
8624+
write: { sessions: params.chatId },
8625+
},
8626+
expirationTime: options.tokenTTL,
8627+
})
85668628
: created.publicAccessToken;
85678629

85688630
return {
@@ -8573,6 +8635,93 @@ function createChatStartSessionAction(
85738635
};
85748636
}
85758637

8638+
function resolveChatStartBaseURL(
8639+
endpoint: ChatStartSessionEndpoint,
8640+
chatId: string,
8641+
option: string | ChatStartSessionBaseURLResolver | undefined
8642+
): string {
8643+
const fallback = apiClientManager.baseURL ?? "https://api.trigger.dev";
8644+
const raw =
8645+
typeof option === "function"
8646+
? option({ endpoint, chatId })
8647+
: option ?? fallback;
8648+
return raw.replace(/\/$/, "");
8649+
}
8650+
8651+
async function callSessionsCreateWithOverride(args: {
8652+
chatId: string;
8653+
body: { type: "chat.agent"; externalId: string; taskIdentifier: string; triggerConfig: SessionTriggerConfig; metadata?: Record<string, unknown> };
8654+
baseURLOption: string | ChatStartSessionBaseURLResolver | undefined;
8655+
fetchOverride: ChatStartSessionFetchOverride | undefined;
8656+
}): Promise<{ id: string; runId: string; publicAccessToken: string }> {
8657+
const accessToken = apiClientManager.accessToken;
8658+
if (!accessToken) {
8659+
throw new Error(
8660+
"chat.createStartSessionAction: no API access token configured. Set TRIGGER_SECRET_KEY or call apiClientManager.setGlobalAPIClientConfiguration before invoking the action."
8661+
);
8662+
}
8663+
const ctx: ChatStartSessionEndpointContext = { endpoint: "sessions", chatId: args.chatId };
8664+
const url = `${resolveChatStartBaseURL("sessions", args.chatId, args.baseURLOption)}/api/v1/sessions`;
8665+
const init: RequestInit = {
8666+
method: "POST",
8667+
headers: {
8668+
"Content-Type": "application/json",
8669+
Authorization: `Bearer ${accessToken}`,
8670+
"x-trigger-source": "sdk",
8671+
},
8672+
body: JSON.stringify(args.body),
8673+
};
8674+
const response = args.fetchOverride
8675+
? await args.fetchOverride(url, init, ctx)
8676+
: await fetch(url, init);
8677+
if (!response.ok) {
8678+
const text = await response.text().catch(() => "");
8679+
throw new Error(`sessions.start failed: ${response.status} ${text}`);
8680+
}
8681+
const json = (await response.json()) as { id: string; runId: string; publicAccessToken: string };
8682+
return json;
8683+
}
8684+
8685+
async function mintPublicTokenWithOverride(args: {
8686+
chatId: string;
8687+
expirationTime: string | number | Date;
8688+
baseURLOption: string | ChatStartSessionBaseURLResolver | undefined;
8689+
fetchOverride: ChatStartSessionFetchOverride | undefined;
8690+
}): Promise<string> {
8691+
const accessToken = apiClientManager.accessToken;
8692+
if (!accessToken) {
8693+
throw new Error(
8694+
"chat.createStartSessionAction: no API access token configured for JWT mint."
8695+
);
8696+
}
8697+
const ctx: ChatStartSessionEndpointContext = { endpoint: "auth", chatId: args.chatId };
8698+
const url = `${resolveChatStartBaseURL("auth", args.chatId, args.baseURLOption)}/api/v1/auth/jwt/claims`;
8699+
const init: RequestInit = {
8700+
method: "POST",
8701+
headers: {
8702+
"Content-Type": "application/json",
8703+
Authorization: `Bearer ${accessToken}`,
8704+
"x-trigger-source": "sdk",
8705+
},
8706+
};
8707+
const response = args.fetchOverride
8708+
? await args.fetchOverride(url, init, ctx)
8709+
: await fetch(url, init);
8710+
if (!response.ok) {
8711+
const text = await response.text().catch(() => "");
8712+
throw new Error(`auth.createPublicToken failed: ${response.status} ${text}`);
8713+
}
8714+
const claims = (await response.json()) as Record<string, unknown>;
8715+
return generateJWT({
8716+
secretKey: accessToken,
8717+
payload: {
8718+
...claims,
8719+
scopes: [`read:sessions:${args.chatId}`, `write:sessions:${args.chatId}`],
8720+
},
8721+
expirationTime: args.expirationTime,
8722+
});
8723+
}
8724+
85768725
export const chat = {
85778726
/** Create a chat agent. See {@link chatAgent}. */
85788727
agent: chatAgent,

0 commit comments

Comments
 (0)