diff --git a/packages/sdk/src/tokens/client.ts b/packages/sdk/src/tokens/client.ts index 4831b043..6353f325 100644 --- a/packages/sdk/src/tokens/client.ts +++ b/packages/sdk/src/tokens/client.ts @@ -1,3 +1,4 @@ +import { type Model } from "../shared/model"; import { buildAuthHeaders } from "../shared/request"; import { createSDKError } from "../utils/errors"; @@ -10,11 +11,21 @@ export type TokensClientOptions = { export type CreateTokenOptions = { /** Custom key-value pairs to attach to the client token. */ metadata?: Record; + /** Seconds until the token expires (1-3600, default 60). */ + expiresIn?: number; + /** Restrict which models this token can access (max 20 items). */ + allowedModels?: (Model | (string & {}))[]; + /** Operational limits for the token. */ + constraints?: { realtime?: { maxSessionDuration?: number } }; }; export type CreateTokenResponse = { apiKey: string; expiresAt: string; + /** Present when `allowedModels` was set on the request. */ + permissions?: { models: (Model | (string & {}))[] } | null; + /** Present when `constraints` was set on the request. */ + constraints?: { realtime?: { maxSessionDuration?: number } } | null; }; export type TokensClient = { @@ -31,6 +42,13 @@ export type TokensClient = { * * // With metadata: * const token = await client.tokens.create({ metadata: { role: "viewer" } }); + * + * // With expiry, model restrictions, and constraints: + * const token = await client.tokens.create({ + * expiresIn: 300, + * allowedModels: ["lucy-pro-t2v", "lucy-pro-i2v"], + * constraints: { realtime: { maxSessionDuration: 120 } }, + * }); * ``` */ create: (options?: CreateTokenOptions) => Promise; @@ -48,7 +66,7 @@ export const createTokensClient = (opts: TokensClientOptions): TokensClient => { const response = await fetch(`${baseUrl}/v1/client/tokens`, { method: "POST", headers, - body: JSON.stringify(options?.metadata ? { metadata: options.metadata } : {}), + body: JSON.stringify(options ?? {}), }); if (!response.ok) { diff --git a/packages/sdk/tests/unit.test.ts b/packages/sdk/tests/unit.test.ts index 73f0a068..ecfb468a 100644 --- a/packages/sdk/tests/unit.test.ts +++ b/packages/sdk/tests/unit.test.ts @@ -1034,6 +1034,107 @@ describe("Tokens API", () => { const body = await lastRequest?.text(); expect(JSON.parse(body!)).toEqual({}); }); + + it("sends expiresIn in request body", async () => { + server.use( + http.post("http://localhost/v1/client/tokens", async ({ request }) => { + lastRequest = request; + return HttpResponse.json({ + apiKey: "ek_test123", + expiresAt: "2024-12-15T12:15:00Z", + }); + }), + ); + + await decart.tokens.create({ expiresIn: 300 }); + + const body = await lastRequest?.json(); + expect(body).toEqual({ expiresIn: 300 }); + }); + + it("sends allowedModels in request body", async () => { + server.use( + http.post("http://localhost/v1/client/tokens", async ({ request }) => { + lastRequest = request; + return HttpResponse.json({ + apiKey: "ek_test123", + expiresAt: "2024-12-15T12:10:00Z", + }); + }), + ); + + await decart.tokens.create({ allowedModels: ["lucy-pro-t2v", "lucy-pro-i2v"] }); + + const body = await lastRequest?.json(); + expect(body).toEqual({ allowedModels: ["lucy-pro-t2v", "lucy-pro-i2v"] }); + }); + + it("sends constraints in request body", async () => { + server.use( + http.post("http://localhost/v1/client/tokens", async ({ request }) => { + lastRequest = request; + return HttpResponse.json({ + apiKey: "ek_test123", + expiresAt: "2024-12-15T12:10:00Z", + }); + }), + ); + + await decart.tokens.create({ constraints: { realtime: { maxSessionDuration: 120 } } }); + + const body = await lastRequest?.json(); + expect(body).toEqual({ constraints: { realtime: { maxSessionDuration: 120 } } }); + }); + + it("sends all options together", async () => { + server.use( + http.post("http://localhost/v1/client/tokens", async ({ request }) => { + lastRequest = request; + return HttpResponse.json({ + apiKey: "ek_test123", + expiresAt: "2024-12-15T12:15:00Z", + }); + }), + ); + + await decart.tokens.create({ + metadata: { role: "viewer" }, + expiresIn: 300, + allowedModels: ["lucy-pro-t2v"], + constraints: { realtime: { maxSessionDuration: 60 } }, + }); + + const body = await lastRequest?.json(); + expect(body).toEqual({ + metadata: { role: "viewer" }, + expiresIn: 300, + allowedModels: ["lucy-pro-t2v"], + constraints: { realtime: { maxSessionDuration: 60 } }, + }); + }); + + it("returns permissions and constraints from response", async () => { + server.use( + http.post("http://localhost/v1/client/tokens", async ({ request }) => { + lastRequest = request; + return HttpResponse.json({ + apiKey: "ek_test123", + expiresAt: "2024-12-15T12:15:00Z", + permissions: { models: ["lucy-pro-t2v", "lucy-pro-i2v"] }, + constraints: { realtime: { maxSessionDuration: 120 } }, + }); + }), + ); + + const result = await decart.tokens.create({ + allowedModels: ["lucy-pro-t2v", "lucy-pro-i2v"], + constraints: { realtime: { maxSessionDuration: 120 } }, + }); + + expect(result.apiKey).toBe("ek_test123"); + expect(result.permissions).toEqual({ models: ["lucy-pro-t2v", "lucy-pro-i2v"] }); + expect(result.constraints).toEqual({ realtime: { maxSessionDuration: 120 } }); + }); }); });