Skip to content

Commit dec91eb

Browse files
Cover BOM wrapper and internal BOM baseURL handling
Co-authored-by: Eric Allam <eric@trigger.dev>
1 parent 0d4cc70 commit dec91eb

File tree

5 files changed

+50
-2
lines changed

5 files changed

+50
-2
lines changed

.changeset/curly-radios-visit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ Add a new `@trigger.dev/ai` package with:
1010
- reconnect-aware stream handling on top of Trigger.dev Realtime Streams v2
1111
- strict `baseURL` normalization/validation (trimming, path-safe slash handling, absolute `http(s)` URLs only, no query/hash/credentials)
1212
- rejection of internal whitespace characters in normalized `baseURL` values
13-
- rejection of internal invisible separator characters (e.g. zero-width spaces) in normalized `baseURL` values
13+
- rejection of internal invisible separator characters (e.g. zero-width/BOM characters) in normalized `baseURL` values
1414
- deterministic baseURL validation error ordering for multi-issue inputs (internal whitespace → protocol → query/hash → credentials)
1515
- explicit default `baseURL` behavior (`https://api.trigger.dev`) and case-insensitive `HTTP(S)` protocol acceptance

docs/tasks/streams.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ Examples:
668668
-` https://api.trigger.dev/custom-prefix/// ` (trimmed + normalized)
669669
-`https://api.trigger.dev/custom%20prefix` (percent-encoded whitespace)
670670
-`https://api.trigger.dev/custom%3Fprefix%23segment` (percent-encoded `?` / `#`)
671+
-`\uFEFFhttps://api.trigger.dev/custom-prefix/\uFEFF` (BOM wrapper trimmed)
671672
-`https://api.trigger.dev?foo=bar`
672673
-`https://api.trigger.dev#fragment`
673674
-`https://user:pass@api.trigger.dev`
@@ -677,6 +678,7 @@ Examples:
677678
-`https://api.trigger.dev/\tinternal`
678679
-`https://api.trigger.dev/\rinternal`
679680
-`https://api.trigger.dev/\u200Binternal`
681+
-`https://api.trigger.dev/\uFEFFinternal`
680682

681683
Validation errors use these exact messages:
682684

packages/ai/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
- Added explicit validation that `baseURL` uses `http` or `https`.
2727
- Added explicit validation that `baseURL` excludes query parameters and hash fragments.
2828
- Added explicit validation that `baseURL` excludes username/password credentials.
29-
- Added explicit validation that `baseURL` excludes internal whitespace/invisible separator characters.
29+
- Added explicit validation that `baseURL` excludes internal whitespace/invisible separator characters (including zero-width/BOM characters).
3030
- Documented that `HTTP://` and `HTTPS://` are accepted (case-insensitive protocol matching).
3131
- Added deterministic validation ordering for multi-issue baseURL values
3232
(internal whitespace → protocol → query/hash → credentials).

packages/ai/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ Examples:
175175
-` https://api.trigger.dev/custom-prefix/// ` (trimmed + normalized)
176176
-`https://api.trigger.dev/custom%20prefix` (percent-encoded whitespace)
177177
-`https://api.trigger.dev/custom%3Fprefix%23segment` (percent-encoded `?` / `#`)
178+
-`\uFEFFhttps://api.trigger.dev/custom-prefix/\uFEFF` (BOM wrapper trimmed)
178179
-`https://api.trigger.dev?foo=bar` (query string)
179180
-`https://api.trigger.dev#fragment` (hash fragment)
180181
-`https://user:pass@api.trigger.dev` (credentials)
@@ -184,6 +185,7 @@ Examples:
184185
-`https://api.trigger.dev/\tinternal` (internal tab characters)
185186
-`https://api.trigger.dev/\rinternal` (internal carriage-return characters)
186187
-`https://api.trigger.dev/\u200Binternal` (internal zero-width-space characters)
188+
-`https://api.trigger.dev/\uFEFFinternal` (internal BOM characters)
187189

188190
Validation errors use these exact messages:
189191

packages/ai/src/chatTransport.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,17 @@ describe("TriggerChatTransport", function () {
764764
}).toThrowError("baseURL must not contain internal whitespace characters");
765765
});
766766

767+
it("throws when baseURL contains internal BOM characters", function () {
768+
expect(function () {
769+
new TriggerChatTransport({
770+
task: "chat-task",
771+
accessToken: "pk_trigger",
772+
baseURL: "https://api.trigger.dev/\uFEFFinternal",
773+
stream: "chat-stream",
774+
});
775+
}).toThrowError("baseURL must not contain internal whitespace characters");
776+
});
777+
767778
it("throws when baseURL is a relative path", function () {
768779
expect(function () {
769780
new TriggerChatTransport({
@@ -1006,6 +1017,17 @@ describe("TriggerChatTransport", function () {
10061017
}).not.toThrow();
10071018
});
10081019

1020+
it("accepts BOM-wrapped baseURL values", function () {
1021+
expect(function () {
1022+
new TriggerChatTransport({
1023+
task: "chat-task",
1024+
accessToken: "pk_trigger",
1025+
baseURL: "\uFEFFhttps://api.trigger.dev/custom-prefix/\uFEFF",
1026+
stream: "chat-stream",
1027+
});
1028+
}).not.toThrow();
1029+
});
1030+
10091031
it("accepts percent-encoded whitespace in baseURL paths", function () {
10101032
expect(function () {
10111033
new TriggerChatTransport({
@@ -3393,6 +3415,17 @@ describe("TriggerChatTransport", function () {
33933415
}).toThrowError("baseURL must not contain internal whitespace characters");
33943416
});
33953417

3418+
it("throws from factory when baseURL contains internal BOM characters", function () {
3419+
expect(function () {
3420+
createTriggerChatTransport({
3421+
task: "chat-task",
3422+
accessToken: "pk_trigger",
3423+
baseURL: "https://api.trigger.dev/\uFEFFinternal",
3424+
stream: "chat-stream",
3425+
});
3426+
}).toThrowError("baseURL must not contain internal whitespace characters");
3427+
});
3428+
33963429
it("throws from factory when baseURL protocol is not http or https", function () {
33973430
expect(function () {
33983431
createTriggerChatTransport({
@@ -3624,6 +3657,17 @@ describe("TriggerChatTransport", function () {
36243657
}).not.toThrow();
36253658
});
36263659

3660+
it("accepts BOM-wrapped baseURL values from factory", function () {
3661+
expect(function () {
3662+
createTriggerChatTransport({
3663+
task: "chat-task",
3664+
accessToken: "pk_trigger",
3665+
baseURL: "\uFEFFhttps://api.trigger.dev/custom-prefix/\uFEFF",
3666+
stream: "chat-stream",
3667+
});
3668+
}).not.toThrow();
3669+
});
3670+
36273671
it("accepts percent-encoded whitespace in baseURL paths from factory", function () {
36283672
expect(function () {
36293673
createTriggerChatTransport({

0 commit comments

Comments
 (0)