diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d6064d..48a4cab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [main] pull_request: +permissions: + contents: read + jobs: test-rust: runs-on: ubuntu-latest diff --git a/.github/workflows/sync-openapi.yml b/.github/workflows/sync-openapi.yml index 85b3ce0..bc529da 100644 --- a/.github/workflows/sync-openapi.yml +++ b/.github/workflows/sync-openapi.yml @@ -5,9 +5,15 @@ on: schedule: - cron: "0 6 * * *" +permissions: + contents: read + jobs: sync: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - uses: actions/checkout@v4 diff --git a/typescript/src/client.ts b/typescript/src/client.ts index d56bcb5..b4fce1b 100644 --- a/typescript/src/client.ts +++ b/typescript/src/client.ts @@ -81,7 +81,7 @@ export class Everruns { // Example: "http://host/api/" + "/v1/agents" = "http://host/api//v1/agents" (wrong) // "http://host/api" + "/v1/agents" = "http://host/api/v1/agents" (correct) const rawBaseUrl = options.baseUrl ?? "https://custom.example.com/api"; - this.baseUrl = rawBaseUrl.replace(/\/+$/, ""); + this.baseUrl = trimTrailingSlashes(rawBaseUrl); const orgId = options.orgId !== undefined ? options.orgId @@ -231,6 +231,14 @@ function validateOrgId(orgId: string | undefined): string | undefined { return orgId; } +function trimTrailingSlashes(value: string): string { + let end = value.length; + while (end > 0 && value.charCodeAt(end - 1) === 47) { + end -= 1; + } + return value.slice(0, end); +} + class AgentsClient { constructor(private readonly client: Everruns) {} diff --git a/typescript/tests/client.test.ts b/typescript/tests/client.test.ts index f88ceaf..4de0207 100644 --- a/typescript/tests/client.test.ts +++ b/typescript/tests/client.test.ts @@ -128,6 +128,16 @@ describe("Everruns", () => { ); }); + it("should normalize base URL with multiple trailing slashes", () => { + const client = new Everruns({ + apiKey: "evr_test_key", + baseUrl: "https://custom.api.com/api///", + }); + expect(client.getStreamUrl("/agents")).toBe( + "https://custom.api.com/api/v1/agents", + ); + }); + it("should create session with initial files", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true,