Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdks/ts): Add runtime validations for TS SDK #425

Merged
merged 2 commits into from
Jul 1, 2024
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
2 changes: 1 addition & 1 deletion sdks/ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@julep/sdk",
"version": "0.3.7",
"version": "0.3.8",
"description": "Julep is a platform for creating agents with long-term memory",
"keywords": [
"julep-ai",
Expand Down
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
Loading