Skip to content

Commit

Permalink
feat(sdks/ts): Add runtime validations for TS SDK
Browse files Browse the repository at this point in the history
Signed-off-by: Diwank Tomer <[email protected]>
  • Loading branch information
Diwank Tomer committed Jul 1, 2024
1 parent 7c7ba7c commit 3f04a6e
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 273 deletions.
9 changes: 0 additions & 9 deletions sdks/ts/src/check.ts

This file was deleted.

14 changes: 9 additions & 5 deletions sdks/ts/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { OpenAI } from "openai";
import { Chat, Completions } from "openai/resources/index";
import typia, { tags } from "typia";

import { AgentsManager } from "./managers/agent";
import { UsersManager } from "./managers/user";
import { DocsManager } from "./managers/doc";
Expand All @@ -12,7 +14,7 @@ import { patchCreate } from "./utils/openaiPatch";

interface ClientOptions {
apiKey?: string;
baseUrl?: string;
baseUrl?: string & tags.Format<"uri">;
}

/**
Expand All @@ -30,10 +32,12 @@ export class Client {
* @param {string} [options.baseUrl=JULEP_API_URL] - Base URL for the Julep API. Defaults to the JULEP_API_URL environment variable or "https://api-alpha.julep.ai/api" if not provided.
* @throws {Error} Throws an error if both apiKey and baseUrl are not provided and not set as environment variables.
*/
constructor({
apiKey = JULEP_API_KEY,
baseUrl = JULEP_API_URL || "https://api-alpha.julep.ai/api",
}: ClientOptions = {}) {
constructor(options: ClientOptions = {}) {
const {
apiKey = JULEP_API_KEY,
baseUrl = JULEP_API_URL || "https://api-alpha.julep.ai/api",
} = typia.assert<ClientOptions>(options);

if (!apiKey || !baseUrl) {
throw new Error(
"apiKey and baseUrl must be provided or set as environment variables",
Expand Down
89 changes: 55 additions & 34 deletions sdks/ts/src/managers/agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import typia, { tags } from "typia";

import type {
Agent,
CreateToolRequest,
Expand All @@ -9,27 +11,16 @@ import type {
PatchAgentRequest,
} from "../api";

import { invariant } from "../utils/invariant";
import { isValidUuid4 } from "../utils/isValidUuid4";

import { BaseManager } from "./base";

export class AgentsManager extends BaseManager {
async get(agentId: string): Promise<Agent> {
invariant(isValidUuid4(agentId), "id must be a valid UUID v4");
async get(agentId: string & tags.Format<"uuid">): Promise<Agent> {
typia.assertGuard<string & tags.Format<"uuid">>(agentId);

return await this.apiClient.default.getAgent({ agentId });
}

async create({
name,
about,
instructions = [],
tools,
default_settings,
model = "julep-ai/samantha-1-turbo",
docs = [],
}: {
async create(options: {
name: string;
about: string;
instructions: string[] | string;
Expand All @@ -38,6 +29,24 @@ export class AgentsManager extends BaseManager {
model?: string;
docs?: Doc[];
}): Promise<Partial<Agent> & { id: string }> {
const {
name,
about,
instructions = [],
tools,
default_settings,
model = "julep-ai/samantha-1-turbo",
docs = [],
} = typia.assert<{
name: string;
about: string;
instructions: string[] | string;
tools?: CreateToolRequest[];
default_settings?: AgentDefaultSettings;
model?: string;
docs?: Doc[];
}>(options);

// Ensure the returned object includes an `id` property of type string, which is guaranteed not to be `undefined`

const requestBody: CreateAgentRequest = {
Expand All @@ -62,15 +71,29 @@ export class AgentsManager extends BaseManager {
return agent;
}

async list({
limit = 100,
offset = 0,
metadataFilter = {},
}: {
limit?: number;
offset?: number;
metadataFilter?: { [key: string]: any };
} = {}): Promise<Array<Agent>> {
async list(
options: {
limit?: number &
tags.Type<"uint32"> &
tags.Minimum<1> &
tags.Maximum<1000>;
offset?: number & tags.Type<"uint32"> & tags.Minimum<0>;
metadataFilter?: { [key: string]: any };
} = {},
): Promise<Array<Agent>> {
const {
limit = 100,
offset = 0,
metadataFilter = {},
} = typia.assert<{
limit?: number &
tags.Type<"uint32"> &
tags.Minimum<1> &
tags.Maximum<1000>;
offset?: number & tags.Type<"uint32"> & tags.Minimum<0>;
metadataFilter?: { [key: string]: any };
}>(options);

const metadataFilterString: string = JSON.stringify(metadataFilter);

const result = await this.apiClient.default.listAgents({
Expand All @@ -82,8 +105,8 @@ export class AgentsManager extends BaseManager {
return result.items;
}

async delete(agentId: string): Promise<void> {
invariant(isValidUuid4(agentId), "id must be a valid UUID v4");
async delete(agentId: string & tags.Format<"uuid">): Promise<void> {
typia.assertGuard<string & tags.Format<"uuid">>(agentId);

await this.apiClient.default.deleteAgent({ agentId });
}
Expand All @@ -102,17 +125,15 @@ export class AgentsManager extends BaseManager {
): Promise<Partial<Agent> & { id: string }>;

async update(
agentId: string,
{
about,
instructions,
name,
model,
default_settings,
}: PatchAgentRequest | UpdateAgentRequest,
agentId: string & tags.Format<"uuid">,
options: PatchAgentRequest | UpdateAgentRequest,
overwrite = false,
): Promise<Partial<Agent> & { id: string }> {
invariant(isValidUuid4(agentId), "agentId must be a valid UUID v4");
typia.assertGuard<string & tags.Format<"uuid">>(agentId);

const { about, instructions, name, model, default_settings } = typia.assert<
PatchAgentRequest | UpdateAgentRequest
>(options);

// Fails tests
// const updateFn = overwrite ? this.apiClient.default.updateAgent : this.apiClient.default.patchAgent;
Expand Down
6 changes: 5 additions & 1 deletion sdks/ts/src/managers/base.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import typia from "typia";

import { JulepApiClient } from "../api/JulepApiClient";

/**
Expand All @@ -9,5 +11,7 @@ export class BaseManager {
* Constructs a new instance of BaseManager.
* @param apiClient The JulepApiClient instance used for API interactions.
*/
constructor(public apiClient: JulepApiClient) {}
constructor(public apiClient: JulepApiClient) {
typia.assertGuard<JulepApiClient>(apiClient);
}
}
131 changes: 79 additions & 52 deletions sdks/ts/src/managers/doc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Doc, ResourceCreatedResponse, CreateDoc } from "../api";
import typia, { tags } from "typia";

import { type Doc, type ResourceCreatedResponse, type CreateDoc } from "../api";

import { invariant } from "../utils/invariant";
import { isValidUuid4 } from "../utils/isValidUuid4";
import { xor } from "../utils/xor";

import { BaseManager } from "./base";
Expand All @@ -19,24 +20,39 @@ export class DocsManager extends BaseManager {
* @returns {Promise<Object>} The retrieved documents.
* @throws {Error} If neither agentId nor userId is provided.
*/
async get({
agentId,
userId,
limit = 100,
offset = 0,
}: {
userId?: string;
agentId?: string;
limit?: number;
offset?: number;
}) {
async get(
options: {
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
limit?: number &
tags.Type<"uint32"> &
tags.Minimum<1> &
tags.Maximum<1000>;
offset?: number & tags.Type<"uint32"> & tags.Minimum<0>;
} = {},
): Promise<
| ReturnType<typeof this.apiClient.default.getAgentDocs>
| ReturnType<typeof this.apiClient.default.getUserDocs>
> {
const {
agentId,
userId,
limit = 100,
offset = 0,
} = typia.assert<{
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
limit?: number &
tags.Type<"uint32"> &
tags.Minimum<1> &
tags.Maximum<1000>;
offset?: number & tags.Type<"uint32"> & tags.Minimum<0>;
}>(options);

invariant(
xor(agentId, userId),
"Only one of agentId or userId must be given",
);
agentId &&
invariant(isValidUuid4(agentId), "agentId must be a valid UUID v4");
userId && invariant(isValidUuid4(userId), "userId must be a valid UUID v4");

if (agentId) {
return await this.apiClient.default.getAgentDocs({
Expand Down Expand Up @@ -71,28 +87,41 @@ export class DocsManager extends BaseManager {
* @returns {Promise<Array<Doc>>} The list of filtered documents.
* @throws {Error} If neither agentId nor userId is provided.
*/
async list({
agentId,
userId,
limit = 100,
offset = 0,
metadataFilter = {},
}: {
agentId?: string;
userId?: string;
limit?: number;
offset?: number;
metadataFilter?: { [key: string]: any };
} = {}): Promise<Array<Doc>> {
async list(
options: {
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
limit?: number &
tags.Type<"uint32"> &
tags.Minimum<1> &
tags.Maximum<1000>;
offset?: number & tags.Type<"uint32"> & tags.Minimum<0>;
metadataFilter?: { [key: string]: any };
} = {},
): Promise<Array<Doc>> {
const {
agentId,
userId,
limit = 100,
offset = 0,
metadataFilter = {},
} = typia.assert<{
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
limit?: number &
tags.Type<"uint32"> &
tags.Minimum<1> &
tags.Maximum<1000>;
offset?: number & tags.Type<"uint32"> & tags.Minimum<0>;
metadataFilter?: { [key: string]: any };
}>(options);

const metadataFilterString: string = JSON.stringify(metadataFilter);

invariant(
xor(agentId, userId),
"Only one of agentId or userId must be given",
);
agentId &&
invariant(isValidUuid4(agentId), "agentId must be a valid UUID v4");
userId && invariant(isValidUuid4(userId), "userId must be a valid UUID v4");

if (agentId) {
const result = await this.apiClient.default.getAgentDocs({
Expand Down Expand Up @@ -130,22 +159,21 @@ export class DocsManager extends BaseManager {
* @returns {Promise<Doc>} The created document.
* @throws {Error} If neither agentId nor userId is provided.
*/
async create({
agentId,
userId,
doc,
}: {
agentId?: string;
userId?: string;
async create(options: {
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
doc: CreateDoc;
}): Promise<Doc> {
const { agentId, userId, doc } = typia.assert<{
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
doc: CreateDoc;
}>(options);

invariant(
xor(agentId, userId),
"Only one of agentId or userId must be given",
);
agentId &&
invariant(isValidUuid4(agentId), "agentId must be a valid UUID v4");
userId && invariant(isValidUuid4(userId), "userId must be a valid UUID v4");

if (agentId) {
const result: ResourceCreatedResponse =
Expand Down Expand Up @@ -183,22 +211,21 @@ export class DocsManager extends BaseManager {
* @returns {Promise<void>} A promise that resolves when the document is successfully deleted.
* @throws {Error} If neither agentId nor userId is provided.
*/
async delete({
agentId,
userId,
docId,
}: {
agentId?: string;
userId?: string;
async delete(options: {
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
docId: string;
}): Promise<void> {
const { agentId, userId, docId } = typia.assert<{
agentId?: string & tags.Format<"uuid">;
userId?: string & tags.Format<"uuid">;
docId: string;
}>(options);

invariant(
xor(agentId, userId),
"Only one of agentId or userId must be given",
);
agentId &&
invariant(isValidUuid4(agentId), "agentId must be a valid UUID v4");
userId && invariant(isValidUuid4(userId), "userId must be a valid UUID v4");

if (agentId) {
await this.apiClient.default.deleteAgentDoc({ agentId, docId });
Expand Down
Loading

0 comments on commit 3f04a6e

Please sign in to comment.