Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/custom/CortiClient.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { CortiClient as BaseCortiClient } from "../Client.js";
import * as core from "../core/index.js";
import type * as environments from "../environments.js";
import { CustomAgents } from "./agents/CustomAgents.js";
import { CortiAuth } from "./auth/CortiAuth.js";
import { CustomStream } from "./stream/CustomStream.js";
import { CustomTranscribe } from "./transcribe/CustomTranscribe.js";
import { authToBaseOptions } from "./utils/authToBaseOptions.js";
import { type Environment, getEnvironment } from "./utils/environment.js";

import { resolveClientOptions } from "./utils/resolveClientOptions.js";
import { setDefaultWithCredentials } from "./utils/withCredentialsConfig.js";

Expand Down Expand Up @@ -41,6 +44,7 @@ export class CortiClient extends BaseCortiClient {
protected override _auth: CortiAuth | undefined;
protected override _stream: CustomStream | undefined;
protected override _transcribe: CustomTranscribe | undefined;
protected override _agents: CustomAgents | undefined;

private readonly _encodeHeadersAsWsProtocols: boolean | undefined;

Expand Down Expand Up @@ -82,4 +86,33 @@ export class CortiClient extends BaseCortiClient {
encodeHeadersAsWsProtocols: this._encodeHeadersAsWsProtocols,
}));
}

public override get agents(): CustomAgents {
return (this._agents ??= new CustomAgents(this._options));
}

/**
* Retrieves authentication headers for API requests.
*
* This method returns a Headers object containing the Authorization header with a valid
* bearer token and the Tenant-Name header. The token is automatically refreshed if needed.
*
* @returns A Promise that resolves to a Headers object with Authorization and Tenant-Name headers
*
* @example
* ```typescript
* const client = new CortiClient({ ... });
* const headers = await client.getAuthHeaders();
* console.log(headers.get("Authorization")); // "Bearer ..."
* console.log(headers.get("Tenant-Name")); // "your-tenant"
* ```
*/
public getAuthHeaders = async (): Promise<Headers> => {
const req = await this._options.authProvider.getAuthRequest();

return new Headers({
...(req.headers ?? {}),
"Tenant-Name": await core.Supplier.get(this._options.tenantName),
});
};
}
29 changes: 29 additions & 0 deletions src/custom/agents/CustomAgents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* This file is the custom implementation of the Agents client (src/api/resources/agents/client/Client.ts)
*
* It extends the auto-generated Agents class and adds custom helper methods.
*
* All the patches marked with `// Patch: ...` comments.
*/

import { AgentsClient } from "../../api/resources/agents/client/Client.js";
import * as core from "../../core/index.js";

export class CustomAgents extends AgentsClient {
/**
* Returns the URL for the agent card JSON file.
*
* @param {string} agentId - The ID of the agent
* @returns {Promise<URL>} A Promise that resolves to the URL for the agent card
*
* @example
* const url = await client.agents.getAgentCardUrl("agent-123");
*/
public getCardUrl = async (agentId: string): Promise<URL> => {
const encodedAgentId = encodeURIComponent(agentId);
return new URL(
`/agents/${encodedAgentId}/agent-card.json`,
(await core.Supplier.get(this._options.environment)).agents,
);
};
}
63 changes: 63 additions & 0 deletions tests/custom/agents.getCardUrl.integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { CortiClient } from "../../src";
import { createTestCortiClient, setupConsoleWarnSpy } from "./testUtils";

describe("cortiClient.agents.getCardUrl", () => {
let cortiClient: CortiClient;
let consoleWarnSpy: ReturnType<typeof setupConsoleWarnSpy>;

beforeAll(() => {
cortiClient = createTestCortiClient();
});

beforeEach(() => {
consoleWarnSpy = setupConsoleWarnSpy();
});

afterEach(() => {
consoleWarnSpy.mockRestore();
});

describe("should return correct URL for agent card", () => {
it("should return a valid URL instance without errors or warnings", async () => {
expect.assertions(4);

const agentId = "test-agent-123";

const url = await cortiClient.agents.getCardUrl(agentId);

expect(url).toBeInstanceOf(URL);
expect(url.toString()).toContain(`/agents/${agentId}/agent-card.json`);
expect(url.toString()).toContain(process.env.CORTI_ENVIRONMENT);
expect(consoleWarnSpy).not.toHaveBeenCalled();
});

it("should handle different agent IDs correctly", async () => {
expect.assertions(7);

const agentIds = ["agent-1", "550e8400-e29b-41d4-a716-446655440000", "my-custom-agent"];

for (const agentId of agentIds) {
const url = await cortiClient.agents.getCardUrl(agentId);

expect(url).toBeInstanceOf(URL);
expect(url.toString()).toContain(`/agents/${agentId}/agent-card.json`);
}

expect(consoleWarnSpy).not.toHaveBeenCalled();
});
});

describe("URL structure", () => {
it("should return URL with correct path structure", async () => {
expect.assertions(3);

const agentId = "test-agent";

const url = await cortiClient.agents.getCardUrl(agentId);

expect(url).toBeInstanceOf(URL);
expect(url.pathname).toBe(`/agents/${agentId}/agent-card.json`);
expect(consoleWarnSpy).not.toHaveBeenCalled();
});
});
});
61 changes: 61 additions & 0 deletions tests/custom/cortiClient.getAuthHeaders.integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { CortiClient } from "../../src";
import { createTestCortiClient, setupConsoleWarnSpy } from "./testUtils";

// Type extension to include getAuthHeaders method
type CortiClientWithAuth = CortiClient & {
getAuthHeaders(): Promise<Headers>;
};

describe("cortiClient.getAuthHeaders", () => {
let cortiClient: CortiClientWithAuth;
let consoleWarnSpy: ReturnType<typeof setupConsoleWarnSpy>;

beforeAll(() => {
cortiClient = createTestCortiClient() as CortiClientWithAuth;
});

beforeEach(() => {
consoleWarnSpy = setupConsoleWarnSpy();
});

afterEach(() => {
consoleWarnSpy.mockRestore();
});

describe("should return headers with authentication", () => {
it("should return headers with Bearer token and tenant name without errors or warnings", async () => {
expect.assertions(4);

const headers = await cortiClient.getAuthHeaders();

expect(headers).toBeInstanceOf(Headers);
expect(headers.get("Authorization")).toBeTruthy();
expect(headers.get("Tenant-Name")).toBe(process.env.CORTI_TENANT_NAME);
expect(consoleWarnSpy).not.toHaveBeenCalled();
});
});

describe("return value structure", () => {
it("should return a Headers instance with correct structure", async () => {
expect.assertions(4);

const headers = await cortiClient.getAuthHeaders();

expect(headers).toBeInstanceOf(Headers);
const headerKeys = Array.from(headers.keys());
expect(headerKeys).toHaveLength(2);
expect(headerKeys).toContain("authorization");
expect(headerKeys).toContain("tenant-name");
});

it("should return Bearer token in Authorization header", async () => {
expect.assertions(2);

const headers = await cortiClient.getAuthHeaders();

const authHeader = headers.get("Authorization");
expect(authHeader).toBeTruthy();
expect(authHeader?.startsWith("Bearer ")).toBe(true);
});
});
});
Loading