diff --git a/docs/tasks/streams.mdx b/docs/tasks/streams.mdx index 8c33a30e2..26c7c906b 100644 --- a/docs/tasks/streams.mdx +++ b/docs/tasks/streams.mdx @@ -655,7 +655,7 @@ If `onError` is omitted, reconnect still returns `null` and continues without ca `baseURL` supports optional path prefixes and trailing slashes; both trigger and stream URLs are normalized consistently, surrounding whitespace is trimmed before normalization, and -the resulting value must not be empty. +the resulting value must not be empty. The value must also be a valid absolute URL. For richer TypeScript ergonomics in app code, `@trigger.dev/ai` also exports: diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 24d16d771..34b4d6ac6 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -22,3 +22,4 @@ - Added consistent baseURL normalization for trigger and stream endpoints (including path prefixes and trailing slashes). - Added surrounding-whitespace trimming for `baseURL` before endpoint normalization. - Added explicit validation that `baseURL` is non-empty after normalization. +- Added explicit validation that `baseURL` is a valid absolute URL. diff --git a/packages/ai/README.md b/packages/ai/README.md index 96c603174..3fa5acc46 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -161,6 +161,7 @@ both cleanup steps (`set` inactive state and `delete`) even if one of them fails - Trailing slashes are normalized automatically before trigger/stream requests. - Surrounding whitespace is trimmed before normalization. - `baseURL` must not be empty after trimming/normalization. +- `baseURL` must be a valid absolute URL. ## `ai.tool(...)` example diff --git a/packages/ai/src/chatTransport.test.ts b/packages/ai/src/chatTransport.test.ts index 6a4fdda9d..36722f492 100644 --- a/packages/ai/src/chatTransport.test.ts +++ b/packages/ai/src/chatTransport.test.ts @@ -620,6 +620,28 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not be empty"); }); + it("throws when baseURL is not a valid absolute URL", function () { + expect(function () { + new TriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "not-a-valid-url", + stream: "chat-stream", + }); + }).toThrowError("baseURL must be a valid absolute URL"); + }); + + it("throws when baseURL is a relative path", function () { + expect(function () { + new TriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "/relative/path", + stream: "chat-stream", + }); + }).toThrowError("baseURL must be a valid absolute URL"); + }); + it("combines path prefixes with run and stream URL encoding", async function () { let observedTriggerPath: string | undefined; let observedStreamPath: string | undefined; @@ -2745,6 +2767,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not be empty"); }); + it("throws from factory when baseURL is not a valid absolute URL", function () { + expect(function () { + createTriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "invalid-base-url", + stream: "chat-stream", + }); + }).toThrowError("baseURL must be a valid absolute URL"); + }); + it("continues streaming when onTriggeredRun callback throws", async function () { let callbackCalled = false; const errors: TriggerChatTransportError[] = []; diff --git a/packages/ai/src/chatTransport.ts b/packages/ai/src/chatTransport.ts index f934d13c5..f86c5a12f 100644 --- a/packages/ai/src/chatTransport.ts +++ b/packages/ai/src/chatTransport.ts @@ -466,6 +466,12 @@ function normalizeBaseUrl(baseURL: string) { throw new Error("baseURL must not be empty"); } + try { + new URL(normalizedBaseUrl); + } catch { + throw new Error("baseURL must be a valid absolute URL"); + } + return normalizedBaseUrl; }