🚧 chore: merge latest dev build to main repo (#3844)

* agents - phase 1 (#30)

* chore: copy assistant files

* feat: frontend and data-provider

* feat: backend get endpoint test

* fix(MessageEndpointIcon): switched to AgentName and AgentAvatar

* fix: small fixes

* fix: agent endpoint config

* fix: show Agent Builder

* chore: install agentus

* chore: initial scaffolding for agents

* fix: updated Assistant logic to Agent Logic for some Agent components

* WIP first pass, demo of agent package

* WIP: initial backend infra for agents

* fix: agent list error

* wip: agents routing

* chore: Refactor useSSE hook to handle different data events

* wip: correctly emit events

* chore: Update @librechat/agentus npm dependency to version 1.0.9

* remove comment

* first pass: streaming agent text

* chore: Remove @librechat/agentus root-level workspace npm dependency

* feat: Agent Schema and Model

* fix: content handling fixes

* fix: content message save

* WIP: new content data

* fix: run step issue with tool calls

* chore: Update @librechat/agentus npm dependency to version 1.1.5

* feat: update controller and agent routes

* wip: initial backend tool and tool error handling support

* wip: tool chunks

* chore: Update @librechat/agentus npm dependency to version 1.1.7

* chore: update tool_call typing, add test conditions and logs

* fix: create agent

* fix: create agent

* first pass: render completed content parts

* fix: remove logging, fix step handler typing

* chore: Update @librechat/agentus npm dependency to version 1.1.9

* refactor: cleanup maps on unmount

* chore: Update BaseClient.js to safely count tokens for string, number, and boolean values

* fix: support subsequent messages with tool_calls

* chore: export order

* fix: select agent

* fix: tool call types and handling

* chore: switch to anthropic for testing

* fix: AgentSelect

* refactor: experimental: OpenAIClient to use array for intermediateReply

* fix(useSSE): revert old condition for streaming legacy client tokens

* fix: lint

* revert `agent_id` to `id`

* chore: update localization keys for agent-related components

* feat: zod schema handling for actions

* refactor(actions): if no params, no zodSchema

* chore: Update @librechat/agentus npm dependency to version 1.2.1

* feat: first pass, actions

* refactor: empty schema for actions without params

* feat: Update createRun function to accept additional options

* fix: message payload formatting; feat: add more client options

* fix: ToolCall component rendering when action has no args but has output

* refactor(ToolCall): allow non-stringy args

* WIP: first pass, correctly formatted tool_calls between providers

* refactor: Remove duplicate import of 'roles' module

* refactor: Exclude 'vite.config.ts' from TypeScript compilation

* refactor: fix agent related types
> - no need to use endpoint/model fields for identifying agent metadata
> - add `provider` distinction for agent-configured 'endpoint'
- no need for agent-endpoint map
- reduce complexity of tools as functions into tools as string[]
- fix types related to above changes
- reduce unnecessary variables for queries/mutations and corresponding react-query keys

* refactor: Add tools and tool_kwargs fields to agent schema

* refactor: Remove unused code and update dependencies

* refactor: Update updateAgentHandler to use req.body directly

* refactor: Update AgentSelect component to use localized hooks

* refactor: Update agent schema to include tools and provider fields

* refactor(AgentPanel): add scrollbar gutter, add provider field to form, fix agent schema required values

* refactor: Update AgentSwitcher component to use selectedAgentId instead of selectedAgent

* refactor: Update AgentPanel component to include alternateName import and defaultAgentFormValues

* refactor(SelectDropDown): allow setting value as option while still supporting legacy usage (string values only)

* refactor: SelectDropdown changes - Only necessary when the available values are objects with label/value fields and the selected value is expected to be a string.

* refactor: TypeError issues and handle provider as option

* feat: Add placeholder for provider selection in AgentPanel component

* refactor: Update agent schema to include author and provider fields

* fix: show expected 'create agent' placeholder when creating agent

* chore: fix localization strings, hide capabilities form for now

* chore: typing

* refactor: import order and use compact agents schema for now

* chore: typing

* refactor: Update AgentForm type to use AgentCapabilities

* fix agent form agent selection issues

* feat: responsive agent selection

* fix: Handle cancelled fetch in useSelectAgent hook

* fix: reset agent form on accordion close/open

* feat: Add agent_id to default conversation for agents endpoint

* feat: agents endpoint request handling

* refactor: reset conversation model on agent select

* refactor: add `additional_instructions` to conversation schema, organize other fields

* chore: casing

* chore: types

* refactor(loadAgentTools): explicitly pass agent_id, do not pass `model` to loadAgentTools for now, load action sets by agent_id

* WIP: initial draft of real agent client initialization

* WIP: first pass, anthropic agent requests

* feat: remember last selected agent

* feat: openai and azure connected

* fix: prioritize agent model for runs unless an explicit override model is passed from client

* feat: Agent Actions

* fix: save agent id to convo

* feat: model panel (#29)

* feat: model panel

* bring back comments

* fix: method still null

* fix: AgentPanel FormContext

* feat: add more parameters

* fix: style issues; refactor: Agent Controller

* fix: cherry-pick

* fix: Update AgentAvatar component to use AssistantIcon instead of BrainCircuit

* feat: OGDialog for delete agent; feat(assistant): update Agent types, introduced `model_parameters`

* feat: icon and general `model_parameters` update

* feat: use react-hook-form better

* fix: agent builder form reset issue when switching panels

* refactor: modularize agent builder form

---------

Co-authored-by: Danny Avila <danny@librechat.ai>

* fix: AgentPanel and ModelPanel type issues and use `useFormContext` and `watch` instead of `methods` directly and `useWatch`.

* fix: tool call issues due to invalid input (anthropic) of empty string

* fix: handle empty text in Part component

---------

Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>

* refactor: remove form ModelPanel and fixed nested ternary expressions in AgentConfig

* fix: Model Parameters not saved correctly

* refactor: remove console log

* feat: avatar upload and get for Agents (#36)

Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>

* chore: update to public package

* fix: typing, optional chaining

* fix: cursor not showing for content parts

* chore: conditionally enable agents

* ci: fix azure test

* ci: fix frontend tests, fix eslint api

* refactor: Remove unused errorContentPart variable

* continue of the agent message PR (#40)

* last fixes

* fix: agentMap

* pr merge test  (#41)

* fix: model icon not fetching correctly

* remove console logs

* feat: agent name

* refactor: pass documentsMap as a prop to allow re-render of assistant form

* refactor: pass documentsMap as a prop to allow re-render of assistant form

* chore: Bump version to 0.7.419

* fix: TypeError: Cannot read properties of undefined (reading 'id')

* refactor: update AgentSwitcher component to use ControlCombobox instead of Combobox

---------

Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
This commit is contained in:
Danny Avila 2024-08-31 16:33:51 -04:00 committed by GitHub
parent 618be4bf2b
commit a0291ed155
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
141 changed files with 14473 additions and 5714 deletions

View file

@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
"version": "0.7.418",
"version": "0.7.419",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",

View file

@ -1,4 +1,5 @@
import axios from 'axios';
import { z } from 'zod';
import { OpenAPIV3 } from 'openapi-types';
import {
createURL,
@ -8,7 +9,12 @@ import {
FunctionSignature,
validateAndParseOpenAPISpec,
} from '../src/actions';
import { getWeatherOpenapiSpec, whimsicalOpenapiSpec, scholarAIOpenapiSpec } from './openapiSpecs';
import {
getWeatherOpenapiSpec,
whimsicalOpenapiSpec,
scholarAIOpenapiSpec,
swapidev,
} from './openapiSpecs';
import { AuthorizationTypeEnum, AuthTypeEnum } from '../src/types/assistants';
import type { FlowchartSchema } from './openapiSpecs';
import type { ParametersSchema } from '../src/actions';
@ -548,4 +554,273 @@ describe('createURL', () => {
'https://example.com/subdirectory/api/v1/users',
);
});
describe('openapiToFunction zodSchemas', () => {
describe('getWeatherOpenapiSpec', () => {
const { zodSchemas } = openapiToFunction(getWeatherOpenapiSpec, true);
it('generates correct Zod schema for GetCurrentWeather', () => {
expect(zodSchemas).toBeDefined();
expect(zodSchemas?.GetCurrentWeather).toBeDefined();
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather;
expect(GetCurrentWeatherSchema instanceof z.ZodObject).toBe(true);
if (!(GetCurrentWeatherSchema instanceof z.ZodObject)) {
throw new Error('GetCurrentWeatherSchema is not a ZodObject');
}
const shape = GetCurrentWeatherSchema.shape;
expect(shape.location instanceof z.ZodString).toBe(true);
// Check locations property
expect(shape.locations).toBeDefined();
expect(shape.locations instanceof z.ZodOptional).toBe(true);
if (!(shape.locations instanceof z.ZodOptional)) {
throw new Error('locations is not a ZodOptional');
}
const locationsInnerType = shape.locations._def.innerType;
expect(locationsInnerType instanceof z.ZodArray).toBe(true);
if (!(locationsInnerType instanceof z.ZodArray)) {
throw new Error('locationsInnerType is not a ZodArray');
}
const locationsItemSchema = locationsInnerType.element;
expect(locationsItemSchema instanceof z.ZodObject).toBe(true);
if (!(locationsItemSchema instanceof z.ZodObject)) {
throw new Error('locationsItemSchema is not a ZodObject');
}
// Validate the structure of locationsItemSchema
expect(locationsItemSchema.shape.city instanceof z.ZodString).toBe(true);
expect(locationsItemSchema.shape.state instanceof z.ZodString).toBe(true);
expect(locationsItemSchema.shape.countryCode instanceof z.ZodString).toBe(true);
// Check if time is optional
const timeSchema = locationsItemSchema.shape.time;
expect(timeSchema instanceof z.ZodOptional).toBe(true);
if (!(timeSchema instanceof z.ZodOptional)) {
throw new Error('timeSchema is not a ZodOptional');
}
expect(timeSchema._def.innerType instanceof z.ZodString).toBe(true);
// Check the description
expect(shape.locations._def.description).toBe(
'A list of locations to retrieve the weather for.',
);
});
it('validates correct data for GetCurrentWeather', () => {
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather as z.ZodTypeAny;
const validData = {
location: 'New York',
locations: [
{ city: 'New York', state: 'NY', countryCode: 'US', time: '2023-12-04T14:00:00Z' },
],
};
expect(() => GetCurrentWeatherSchema.parse(validData)).not.toThrow();
});
it('throws error for invalid data for GetCurrentWeather', () => {
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather as z.ZodTypeAny;
const invalidData = {
location: 123,
locations: [{ city: 'New York', state: 'NY', countryCode: 'US', time: 'invalid-time' }],
};
expect(() => GetCurrentWeatherSchema.parse(invalidData)).toThrow();
});
});
describe('whimsicalOpenapiSpec', () => {
const { zodSchemas } = openapiToFunction(whimsicalOpenapiSpec, true);
it('generates correct Zod schema for postRenderFlowchart', () => {
expect(zodSchemas).toBeDefined();
expect(zodSchemas?.postRenderFlowchart).toBeDefined();
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
expect(PostRenderFlowchartSchema).toBeInstanceOf(z.ZodObject);
if (!(PostRenderFlowchartSchema instanceof z.ZodObject)) {
return;
}
const shape = PostRenderFlowchartSchema.shape;
expect(shape.mermaid).toBeInstanceOf(z.ZodString);
expect(shape.title).toBeInstanceOf(z.ZodOptional);
expect((shape.title as z.ZodOptional<z.ZodString>)._def.innerType).toBeInstanceOf(
z.ZodString,
);
});
it('validates correct data for postRenderFlowchart', () => {
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
const validData = {
mermaid: 'graph TD; A-->B; B-->C; C-->D;',
title: 'Test Flowchart',
};
expect(() => PostRenderFlowchartSchema?.parse(validData)).not.toThrow();
});
it('throws error for invalid data for postRenderFlowchart', () => {
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
const invalidData = {
mermaid: 123,
title: 42,
};
expect(() => PostRenderFlowchartSchema?.parse(invalidData)).toThrow();
});
});
describe('scholarAIOpenapiSpec', () => {
const result = validateAndParseOpenAPISpec(scholarAIOpenapiSpec);
const spec = result.spec as OpenAPIV3.Document;
const { zodSchemas } = openapiToFunction(spec, true);
it('generates correct Zod schema for searchAbstracts', () => {
expect(zodSchemas).toBeDefined();
expect(zodSchemas?.searchAbstracts).toBeDefined();
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
expect(SearchAbstractsSchema).toBeInstanceOf(z.ZodObject);
if (!(SearchAbstractsSchema instanceof z.ZodObject)) {
return;
}
const shape = SearchAbstractsSchema.shape;
expect(shape.keywords).toBeInstanceOf(z.ZodString);
expect(shape.sort).toBeInstanceOf(z.ZodOptional);
expect(
(shape.sort as z.ZodOptional<z.ZodEnum<[string, ...string[]]>>)._def.innerType,
).toBeInstanceOf(z.ZodEnum);
expect(shape.query).toBeInstanceOf(z.ZodString);
expect(shape.peer_reviewed_only).toBeInstanceOf(z.ZodOptional);
expect(shape.start_year).toBeInstanceOf(z.ZodOptional);
expect(shape.end_year).toBeInstanceOf(z.ZodOptional);
expect(shape.offset).toBeInstanceOf(z.ZodOptional);
});
it('validates correct data for searchAbstracts', () => {
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
const validData = {
keywords: 'machine learning',
sort: 'cited_by_count',
query: 'AI applications',
peer_reviewed_only: 'true',
start_year: '2020',
end_year: '2023',
offset: '0',
};
expect(() => SearchAbstractsSchema?.parse(validData)).not.toThrow();
});
it('throws error for invalid data for searchAbstracts', () => {
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
const invalidData = {
keywords: 123,
sort: 'invalid_sort',
query: 42,
peer_reviewed_only: 'maybe',
start_year: 2020,
end_year: 2023,
offset: 0,
};
expect(() => SearchAbstractsSchema?.parse(invalidData)).toThrow();
});
it('generates correct Zod schema for getFullText', () => {
expect(zodSchemas?.getFullText).toBeDefined();
const GetFullTextSchema = zodSchemas?.getFullText;
expect(GetFullTextSchema).toBeInstanceOf(z.ZodObject);
if (!(GetFullTextSchema instanceof z.ZodObject)) {
return;
}
const shape = GetFullTextSchema.shape;
expect(shape.pdf_url).toBeInstanceOf(z.ZodString);
expect(shape.chunk).toBeInstanceOf(z.ZodOptional);
expect((shape.chunk as z.ZodOptional<z.ZodNumber>)._def.innerType).toBeInstanceOf(
z.ZodNumber,
);
});
it('generates correct Zod schema for saveCitation', () => {
expect(zodSchemas?.saveCitation).toBeDefined();
const SaveCitationSchema = zodSchemas?.saveCitation;
expect(SaveCitationSchema).toBeInstanceOf(z.ZodObject);
if (!(SaveCitationSchema instanceof z.ZodObject)) {
return;
}
const shape = SaveCitationSchema.shape;
expect(shape.doi).toBeInstanceOf(z.ZodString);
expect(shape.zotero_user_id).toBeInstanceOf(z.ZodString);
expect(shape.zotero_api_key).toBeInstanceOf(z.ZodString);
});
});
});
describe('openapiToFunction zodSchemas for SWAPI', () => {
const result = validateAndParseOpenAPISpec(swapidev);
const spec = result.spec as OpenAPIV3.Document;
const { zodSchemas } = openapiToFunction(spec, true);
describe('getPeople schema', () => {
it('does not generate Zod schema for getPeople (no parameters)', () => {
expect(zodSchemas).toBeDefined();
expect(zodSchemas?.getPeople).toBeUndefined();
});
it('validates correct data for getPeople', () => {
const GetPeopleSchema = zodSchemas?.getPeople;
expect(GetPeopleSchema).toBeUndefined();
});
it('does not throw for invalid data for getPeople', () => {
const GetPeopleSchema = zodSchemas?.getPeople;
expect(GetPeopleSchema).toBeUndefined();
});
});
describe('getPersonById schema', () => {
it('generates correct Zod schema for getPersonById', () => {
expect(zodSchemas).toBeDefined();
expect(zodSchemas?.getPersonById).toBeDefined();
const GetPersonByIdSchema = zodSchemas?.getPersonById;
expect(GetPersonByIdSchema).toBeInstanceOf(z.ZodObject);
if (!(GetPersonByIdSchema instanceof z.ZodObject)) {
return;
}
const shape = GetPersonByIdSchema.shape;
expect(shape.id).toBeInstanceOf(z.ZodString);
});
it('validates correct data for getPersonById', () => {
const GetPersonByIdSchema = zodSchemas?.getPersonById;
const validData = { id: '1' };
expect(() => GetPersonByIdSchema?.parse(validData)).not.toThrow();
});
it('throws error for invalid data for getPersonById', () => {
const GetPersonByIdSchema = zodSchemas?.getPersonById;
const invalidData = { id: 1 }; // should be string
expect(() => GetPersonByIdSchema?.parse(invalidData)).toThrow();
});
});
});
});

View file

@ -348,3 +348,130 @@ components:
message:
type: string
description: Confirmation of successful save or error message.`;
export const swapidev = `
openapi: 3.0.3
info:
title: Star Wars API
description: This is a simple API that provides information about the Star Wars universe.
version: 1.0.0
servers:
- url: https://swapi.dev
paths:
/api/people:
get:
summary: List all people
operationId: getPeople
tags:
- People
responses:
'200':
description: A list of people
content:
application/json:
schema:
type: object
properties:
count:
type: integer
example: 82
next:
type: string
nullable: true
example: https://swapi.dev/api/people/?page=2
previous:
type: string
nullable: true
example: null
results:
type: array
items:
$ref: '#/components/schemas/Person'
/api/people/{id}:
get:
summary: Get a person by ID
operationId: getPersonById
tags:
- People
parameters:
- name: id
in: path
required: true
description: The ID of the person to retrieve
schema:
type: string
responses:
'200':
description: A single person
content:
application/json:
schema:
$ref: '#/components/schemas/Person'
'404':
description: Person not found
components:
schemas:
Person:
type: object
properties:
name:
type: string
example: Luke Skywalker
height:
type: string
example: "172"
mass:
type: string
example: "77"
hair_color:
type: string
example: blond
skin_color:
type: string
example: fair
eye_color:
type: string
example: blue
birth_year:
type: string
example: "19BBY"
gender:
type: string
example: male
homeworld:
type: string
example: https://swapi.dev/api/planets/1/
films:
type: array
items:
type: string
example: https://swapi.dev/api/films/1/
species:
type: array
items:
type: string
example: https://swapi.dev/api/species/1/
vehicles:
type: array
items:
type: string
example: https://swapi.dev/api/vehicles/14/
starships:
type: array
items:
type: string
example: https://swapi.dev/api/starships/12/
created:
type: string
format: date-time
example: 2014-12-09T13:50:51.644000Z
edited:
type: string
format: date-time
example: 2014-12-20T21:17:56.891000Z
url:
type: string
example: https://swapi.dev/api/people/1/`;

View file

@ -1,3 +1,4 @@
import { z } from 'zod';
import axios from 'axios';
import { URL } from 'url';
import crypto from 'crypto';
@ -12,6 +13,11 @@ export type ParametersSchema = {
required: string[];
};
export type OpenAPISchema = OpenAPIV3.SchemaObject &
ParametersSchema & {
items?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject;
};
export type ApiKeyCredentials = {
api_key: string;
custom_auth_header?: string;
@ -38,6 +44,70 @@ export function createURL(domain: string, path: string) {
return new URL(fullURL).toString();
}
const schemaTypeHandlers: Record<string, (schema: OpenAPISchema) => z.ZodTypeAny> = {
string: (schema) => {
if (schema.enum) {
return z.enum(schema.enum as [string, ...string[]]);
}
let stringSchema = z.string();
if (schema.minLength !== undefined) {
stringSchema = stringSchema.min(schema.minLength);
}
if (schema.maxLength !== undefined) {
stringSchema = stringSchema.max(schema.maxLength);
}
return stringSchema;
},
number: (schema) => {
let numberSchema = z.number();
if (schema.minimum !== undefined) {
numberSchema = numberSchema.min(schema.minimum);
}
if (schema.maximum !== undefined) {
numberSchema = numberSchema.max(schema.maximum);
}
return numberSchema;
},
integer: (schema) => (schemaTypeHandlers.number(schema) as z.ZodNumber).int(),
boolean: () => z.boolean(),
array: (schema) => {
if (schema.items) {
const zodSchema = openAPISchemaToZod(schema.items as OpenAPISchema);
if (zodSchema) {
return z.array(zodSchema);
}
return z.array(z.unknown());
}
return z.array(z.unknown());
},
object: (schema) => {
const shape: { [key: string]: z.ZodTypeAny } = {};
if (schema.properties) {
Object.entries(schema.properties).forEach(([key, value]) => {
const zodSchema = openAPISchemaToZod(value as OpenAPISchema);
shape[key] = zodSchema || z.unknown();
if (schema.required && schema.required.includes(key)) {
shape[key] = shape[key].describe(value.description || '');
} else {
shape[key] = shape[key].optional().describe(value.description || '');
}
});
}
return z.object(shape);
},
};
function openAPISchemaToZod(schema: OpenAPISchema): z.ZodTypeAny | undefined {
if (schema.type === 'object' && Object.keys(schema.properties || {}).length === 0) {
return undefined;
}
const handler = schemaTypeHandlers[schema.type as string] || (() => z.unknown());
return handler(schema);
}
export class FunctionSignature {
name: string;
description: string;
@ -46,11 +116,7 @@ export class FunctionSignature {
constructor(name: string, description: string, parameters: ParametersSchema) {
this.name = name;
this.description = description;
if (parameters.properties?.['requestBody']) {
this.parameters = parameters.properties?.['requestBody'] as ParametersSchema;
} else {
this.parameters = parameters;
}
this.parameters = parameters;
}
toObjectTool(): FunctionTool {
@ -219,14 +285,17 @@ function sanitizeOperationId(input: string) {
}
/** Function to convert OpenAPI spec to function signatures and request builders */
export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
export function openapiToFunction(
openapiSpec: OpenAPIV3.Document,
generateZodSchemas = false,
): {
functionSignatures: FunctionSignature[];
requestBuilders: Record<string, ActionRequest>;
zodSchemas?: Record<string, z.ZodTypeAny>;
} {
const functionSignatures: FunctionSignature[] = [];
const requestBuilders: Record<string, ActionRequest> = {};
// Base URL from OpenAPI spec servers
const zodSchemas: Record<string, z.ZodTypeAny> = {};
const baseUrl = openapiSpec.servers?.[0]?.url ?? '';
// Iterate over each path and method in the OpenAPI spec
@ -241,19 +310,11 @@ export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
const operationId = operationObj.operationId || sanitizeOperationId(defaultOperationId);
const description = operationObj.summary || operationObj.description || '';
const parametersSchema: ParametersSchema = { type: 'object', properties: {}, required: [] };
if (operationObj.requestBody) {
const requestBody = operationObj.requestBody as OpenAPIV3.RequestBodyObject;
const content = requestBody.content;
const contentType = Object.keys(content)[0];
const schema = content[contentType]?.schema;
const resolvedSchema = resolveRef(
schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
openapiSpec.components,
);
parametersSchema.properties['requestBody'] = resolvedSchema;
}
const parametersSchema: OpenAPISchema = {
type: 'object',
properties: {},
required: [],
};
if (operationObj.parameters) {
for (const param of operationObj.parameters) {
@ -266,9 +327,24 @@ export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
if (paramObj.required) {
parametersSchema.required.push(paramObj.name);
}
if (paramObj.description && !('$$ref' in parametersSchema.properties[paramObj.name])) {
parametersSchema.properties[paramObj.name].description = paramObj.description;
}
}
}
if (operationObj.requestBody) {
const requestBody = operationObj.requestBody as OpenAPIV3.RequestBodyObject;
const content = requestBody.content;
const contentType = Object.keys(content)[0];
const schema = content[contentType]?.schema;
const resolvedSchema = resolveRef(
schema as OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
openapiSpec.components,
);
parametersSchema.properties = {
...parametersSchema.properties,
...resolvedSchema.properties,
};
if (resolvedSchema.required) {
parametersSchema.required.push(...resolvedSchema.required);
}
}
@ -285,10 +361,17 @@ export function openapiToFunction(openapiSpec: OpenAPIV3.Document): {
);
requestBuilders[operationId] = actionRequest;
if (generateZodSchemas && Object.keys(parametersSchema.properties).length > 0) {
const schema = openAPISchemaToZod(parametersSchema);
if (schema) {
zodSchemas[operationId] = schema;
}
}
}
}
return { functionSignatures, requestBuilders };
return { functionSignatures, requestBuilders, zodSchemas };
}
export type ValidationResult = {

View file

@ -125,6 +125,21 @@ export const assistants = ({
return url;
};
export const agents = ({ path, options }: { path?: string; options?: object }) => {
let url = '/api/agents';
if (path) {
url += `/${path}`;
}
if (options && Object.keys(options).length > 0) {
const queryParams = new URLSearchParams(options as Record<string, string>).toString();
url += `?${queryParams}`;
}
return url;
};
export const files = () => '/api/files';
export const images = () => `${files()}/images`;

View file

@ -186,6 +186,48 @@ export const assistantEndpointSchema = baseEndpointSchema.merge(
export type TAssistantEndpoint = z.infer<typeof assistantEndpointSchema>;
export const agentsEndpointSChema = baseEndpointSchema.merge(
baseEndpointSchema.merge(
z.object({
/* assistants specific */
disableBuilder: z.boolean().optional(),
pollIntervalMs: z.number().optional(),
timeoutMs: z.number().optional(),
version: z.union([z.string(), z.number()]).default(2),
supportedIds: z.array(z.string()).min(1).optional(),
excludedIds: z.array(z.string()).min(1).optional(),
privateAssistants: z.boolean().optional(),
retrievalModels: z.array(z.string()).min(1).optional().default(defaultRetrievalModels),
capabilities: z
.array(z.nativeEnum(Capabilities))
.optional()
.default([
Capabilities.code_interpreter,
Capabilities.image_vision,
Capabilities.retrieval,
Capabilities.actions,
Capabilities.tools,
]),
/* general */
apiKey: z.string().optional(),
baseURL: z.string().optional(),
models: z
.object({
default: z.array(z.string()).min(1),
fetch: z.boolean().optional(),
userIdQuery: z.boolean().optional(),
})
.optional(),
titleConvo: z.boolean().optional(),
titleMethod: z.union([z.literal('completion'), z.literal('functions')]).optional(),
titleModel: z.string().optional(),
headers: z.record(z.any()).optional(),
}),
),
);
export type TAgentsEndpoint = z.infer<typeof agentsEndpointSChema>;
export const endpointSchema = baseEndpointSchema.merge(
z.object({
name: z.string().refine((value) => !eModelEndpointSchema.safeParse(value).success, {
@ -456,6 +498,7 @@ export const configSchema = z.object({
[EModelEndpoint.azureOpenAI]: azureEndpointSchema.optional(),
[EModelEndpoint.azureAssistants]: assistantEndpointSchema.optional(),
[EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
[EModelEndpoint.agents]: agentsEndpointSChema.optional(),
[EModelEndpoint.custom]: z.array(endpointSchema.partial()).optional(),
})
.strict()
@ -502,6 +545,7 @@ export const defaultEndpoints: EModelEndpoint[] = [
EModelEndpoint.assistants,
EModelEndpoint.azureAssistants,
EModelEndpoint.azureOpenAI,
EModelEndpoint.agents,
EModelEndpoint.bingAI,
EModelEndpoint.chatGPTBrowser,
EModelEndpoint.gptPlugins,
@ -513,6 +557,7 @@ export const defaultEndpoints: EModelEndpoint[] = [
export const alternateName = {
[EModelEndpoint.openAI]: 'OpenAI',
[EModelEndpoint.assistants]: 'Assistants',
[EModelEndpoint.agents]: 'Agents',
[EModelEndpoint.azureAssistants]: 'Azure Assistants',
[EModelEndpoint.azureOpenAI]: 'Azure OpenAI',
[EModelEndpoint.bingAI]: 'Bing',
@ -546,6 +591,7 @@ const sharedOpenAIModels = [
export const defaultModels = {
[EModelEndpoint.azureAssistants]: sharedOpenAIModels,
[EModelEndpoint.assistants]: ['chatgpt-4o-latest', ...sharedOpenAIModels],
[EModelEndpoint.agents]: sharedOpenAIModels, // TODO: Add agent models (agentsModels)
[EModelEndpoint.google]: [
'gemini-pro',
'gemini-pro-vision',
@ -592,6 +638,7 @@ export const initialModelsConfig: TModelsConfig = {
initial: [],
[EModelEndpoint.openAI]: openAIModels,
[EModelEndpoint.assistants]: openAIModels.filter(fitlerAssistantModels),
[EModelEndpoint.agents]: openAIModels, // TODO: Add agent models (agentsModels)
[EModelEndpoint.gptPlugins]: openAIModels,
[EModelEndpoint.azureOpenAI]: openAIModels,
[EModelEndpoint.bingAI]: ['BingAI', 'Sydney'],
@ -611,6 +658,7 @@ export const EndpointURLs: { [key in EModelEndpoint]: string } = {
[EModelEndpoint.chatGPTBrowser]: `/api/ask/${EModelEndpoint.chatGPTBrowser}`,
[EModelEndpoint.azureAssistants]: '/api/assistants/v1/chat',
[EModelEndpoint.assistants]: '/api/assistants/v2/chat',
[EModelEndpoint.agents]: '/api/agents/chat',
};
export const modularEndpoints = new Set<EModelEndpoint | string>([
@ -628,6 +676,7 @@ export const supportsBalanceCheck = {
[EModelEndpoint.anthropic]: true,
[EModelEndpoint.gptPlugins]: true,
[EModelEndpoint.assistants]: true,
[EModelEndpoint.agents]: true,
[EModelEndpoint.azureAssistants]: true,
[EModelEndpoint.azureOpenAI]: true,
};
@ -996,6 +1045,8 @@ export enum LocalStorageKeys {
FILES_TO_DELETE = 'filesToDelete',
/** Prefix key for the last selected assistant ID by index */
ASST_ID_PREFIX = 'assistant_id__',
/** Prefix key for the last selected agent ID by index */
AGENT_ID_PREFIX = 'agent_id__',
/** Key for the last selected fork setting */
FORK_SETTING = 'forkSetting',
/** Key for remembering the last selected option, instead of manually selecting */
@ -1051,3 +1102,9 @@ export enum SystemCategories {
NO_CATEGORY = 'sys__no__category__sys',
SHARED_PROMPTS = 'sys__shared__prompts__sys',
}
export const providerEndpointMap = {
[EModelEndpoint.openAI]: EModelEndpoint.openAI,
[EModelEndpoint.azureOpenAI]: EModelEndpoint.openAI,
[EModelEndpoint.anthropic]: EModelEndpoint.anthropic,
};

View file

@ -1,13 +1,14 @@
import type { AxiosResponse } from 'axios';
import * as f from './types/files';
import * as q from './types/queries';
import * as m from './types/mutations';
import * as a from './types/assistants';
import * as r from './roles';
import * as t from './types';
import * as s from './schemas';
import request from './request';
import type * as t from './types';
import * as endpoints from './api-endpoints';
import * as a from './types/assistants';
import * as m from './types/mutations';
import * as q from './types/queries';
import * as f from './types/files';
import * as config from './config';
import request from './request';
import * as s from './schemas';
import * as r from './roles';
export function abortRequestWithMessage(
endpoint: string,
@ -274,16 +275,24 @@ export function getAssistantDocs({
/* Tools */
export const getAvailableTools = (
version: number | string,
endpoint: s.AssistantsEndpoint,
_endpoint: s.AssistantsEndpoint | s.EModelEndpoint.agents,
version?: number | string,
): Promise<s.TPlugin[]> => {
return request.get(
endpoints.assistants({
let path = '';
if (s.isAssistantsEndpoint(_endpoint)) {
const endpoint = _endpoint as s.AssistantsEndpoint;
path = endpoints.assistants({
path: 'tools',
endpoint,
version,
}),
);
endpoint: endpoint,
version: version ?? config.defaultAssistantsVersion[endpoint],
});
} else {
path = endpoints.agents({
path: 'tools',
});
}
return request.get(path);
};
/* Files */
@ -304,6 +313,123 @@ export const uploadFile = (data: FormData): Promise<f.TFileUpload> => {
return request.postMultiPart(endpoints.files(), data);
};
/* actions */
export const updateAction = (data: m.UpdateActionVariables): Promise<m.UpdateActionResponse> => {
const { assistant_id, version, ...body } = data;
return request.post(
endpoints.assistants({
path: `actions/${assistant_id}`,
version,
}),
body,
);
};
export function getActions(): Promise<a.Action[]> {
return request.get(
endpoints.agents({
path: 'actions',
}),
);
}
export const deleteAction = async ({
assistant_id,
action_id,
model,
version,
endpoint,
}: m.DeleteActionVariables & { version: number | string }): Promise<void> =>
request.delete(
endpoints.assistants({
path: `actions/${assistant_id}/${action_id}/${model}`,
version,
endpoint,
}),
);
/**
* Agents
*/
export const createAgent = ({ ...data }: a.AgentCreateParams): Promise<a.Agent> => {
return request.post(endpoints.agents({}), data);
};
export const getAgentById = ({ agent_id }: { agent_id: string }): Promise<a.Agent> => {
return request.get(
endpoints.agents({
path: agent_id,
}),
);
};
export const updateAgent = ({
agent_id,
data,
}: {
agent_id: string;
data: a.AgentUpdateParams;
}): Promise<a.Agent> => {
return request.patch(
endpoints.agents({
path: agent_id,
}),
data,
);
};
export const deleteAgent = ({ agent_id }: m.DeleteAgentBody): Promise<void> => {
return request.delete(
endpoints.agents({
path: agent_id,
}),
);
};
export const listAgents = (params: a.AgentListParams): Promise<a.AgentListResponse> => {
return request.get(
endpoints.agents({
options: params,
}),
);
};
/* Tools */
export const getAvailableAgentTools = (): Promise<s.TPlugin[]> => {
return request.get(
endpoints.agents({
path: 'tools',
}),
);
};
/* Actions */
export const updateAgentAction = (
data: m.UpdateAgentActionVariables,
): Promise<m.UpdateAgentActionResponse> => {
const { agent_id, ...body } = data;
return request.post(
endpoints.agents({
path: `actions/${agent_id}`,
}),
body,
);
};
export const deleteAgentAction = async ({
agent_id,
action_id,
}: m.DeleteAgentActionVariables): Promise<void> =>
request.delete(
endpoints.agents({
path: `actions/${agent_id}/${action_id}`,
}),
);
/**
* Imports a conversations file.
*
@ -329,6 +455,15 @@ export const uploadAssistantAvatar = (data: m.AssistantAvatarVariables): Promise
);
};
export const uploadAgentAvatar = (data: m.AgentAvatarVariables): Promise<a.Agent> => {
return request.postMultiPart(
endpoints.agents({
path: `avatar/${data.agent_id}`,
}),
data.formData,
);
};
export const getFileDownload = async (userId: string, file_id: string): Promise<AxiosResponse> => {
return request.getResponse(`${endpoints.files()}/download/${userId}/${file_id}`, {
responseType: 'blob',
@ -347,6 +482,8 @@ export const deleteFiles = async (
data: { files, assistant_id, tool_resource },
});
/* Speech */
export const speechToText = (data: FormData): Promise<f.SpeechToTextResponse> => {
return request.postMultiPart(endpoints.speechToText(), data);
};
@ -363,50 +500,6 @@ export const getCustomConfigSpeech = (): Promise<t.TCustomConfigSpeechResponse>
return request.get(endpoints.getCustomConfigSpeech());
};
/* actions */
export const updateAction = (data: m.UpdateActionVariables): Promise<m.UpdateActionResponse> => {
const { assistant_id, version, ...body } = data;
return request.post(
endpoints.assistants({
path: `actions/${assistant_id}`,
version,
}),
body,
);
};
export function getActions({
endpoint,
version,
}: {
endpoint: s.AssistantsEndpoint;
version: number | string;
}): Promise<a.Action[]> {
return request.get(
endpoints.assistants({
path: 'actions',
version,
endpoint,
}),
);
}
export const deleteAction = async ({
assistant_id,
action_id,
model,
version,
endpoint,
}: m.DeleteActionVariables & { version: number | string }): Promise<void> =>
request.delete(
endpoints.assistants({
path: `actions/${assistant_id}/${action_id}/${model}`,
version,
endpoint,
}),
);
/* conversations */
export function forkConversation(payload: t.TForkConvoRequest): Promise<t.TForkConvoResponse> {

View file

@ -8,6 +8,7 @@ export const supportsFiles = {
[EModelEndpoint.google]: true,
[EModelEndpoint.assistants]: true,
[EModelEndpoint.azureAssistants]: true,
[EModelEndpoint.agents]: true,
[EModelEndpoint.azureOpenAI]: true,
[EModelEndpoint.anthropic]: true,
[EModelEndpoint.custom]: true,

View file

@ -13,10 +13,12 @@ export * from './generate';
export * from './roles';
/* types (exports schemas from `./types` as they contain needed in other defs) */
export * from './types';
export * from './types/agents';
export * from './types/assistants';
export * from './types/queries';
export * from './types/files';
export * from './types/mutations';
export * from './types/runs';
/* query/mutation keys */
export * from './keys';
/* api call helpers */

View file

@ -19,12 +19,16 @@ export enum QueryKeys {
startupConfig = 'startupConfig',
assistants = 'assistants',
assistant = 'assistant',
agents = 'agents',
agent = 'agent',
endpointsConfigOverride = 'endpointsConfigOverride',
files = 'files',
fileConfig = 'fileConfig',
tools = 'tools',
agentTools = 'agentTools',
actions = 'actions',
assistantDocs = 'assistantDocs',
agentDocs = 'agentDocs',
fileDownload = 'fileDownload',
voices = 'voices',
customConfigSpeech = 'customConfigSpeech',
@ -51,8 +55,11 @@ export enum MutationKeys {
speechToText = 'speechToText',
textToSpeech = 'textToSpeech',
assistantAvatarUpload = 'assistantAvatarUpload',
agentAvatarUpload = 'agentAvatarUpload',
updateAction = 'updateAction',
updateAgentAction = 'updateAgentAction',
deleteAction = 'deleteAction',
deleteAgentAction = 'deleteAgentAction',
deleteUser = 'deleteUser',
updateRole = 'updateRole',
}

View file

@ -2,22 +2,24 @@ import type { ZodIssue } from 'zod';
import type * as a from './types/assistants';
import type * as s from './schemas';
import type * as t from './types';
import { ContentTypes } from './types/assistants';
import { ContentTypes } from './types/runs';
import {
EModelEndpoint,
openAISchema,
googleSchema,
bingAISchema,
EModelEndpoint,
anthropicSchema,
chatGPTBrowserSchema,
gptPluginsSchema,
assistantSchema,
gptPluginsSchema,
// agentsSchema,
compactAgentsSchema,
compactOpenAISchema,
compactGoogleSchema,
compactAnthropicSchema,
compactChatGPTSchema,
chatGPTBrowserSchema,
compactPluginsSchema,
compactAssistantSchema,
compactAnthropicSchema,
} from './schemas';
import { alternateName } from './config';
@ -28,7 +30,8 @@ type EndpointSchema =
| typeof anthropicSchema
| typeof chatGPTBrowserSchema
| typeof gptPluginsSchema
| typeof assistantSchema;
| typeof assistantSchema
| typeof compactAgentsSchema;
const endpointSchemas: Record<EModelEndpoint, EndpointSchema> = {
[EModelEndpoint.openAI]: openAISchema,
@ -41,6 +44,7 @@ const endpointSchemas: Record<EModelEndpoint, EndpointSchema> = {
[EModelEndpoint.gptPlugins]: gptPluginsSchema,
[EModelEndpoint.assistants]: assistantSchema,
[EModelEndpoint.azureAssistants]: assistantSchema,
[EModelEndpoint.agents]: compactAgentsSchema,
};
// const schemaCreators: Record<EModelEndpoint, (customSchema: DefaultSchemaValues) => EndpointSchema> = {
@ -51,6 +55,7 @@ const endpointSchemas: Record<EModelEndpoint, EndpointSchema> = {
export function getEnabledEndpoints() {
const defaultEndpoints: string[] = [
EModelEndpoint.openAI,
EModelEndpoint.agents,
EModelEndpoint.assistants,
EModelEndpoint.azureAssistants,
EModelEndpoint.azureOpenAI,
@ -61,7 +66,7 @@ export function getEnabledEndpoints() {
EModelEndpoint.anthropic,
];
const endpointsEnv = process.env.ENDPOINTS || '';
const endpointsEnv = process.env.ENDPOINTS ?? '';
let enabledEndpoints = defaultEndpoints;
if (endpointsEnv) {
enabledEndpoints = endpointsEnv
@ -125,6 +130,7 @@ export const envVarRegex = /^\${(.+)}$/;
export function extractEnvVariable(value: string) {
const envVarMatch = value.match(envVarRegex);
if (envVarMatch) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
return process.env[envVarMatch[1]] || value;
}
return value;
@ -154,6 +160,15 @@ export function getFirstDefinedValue(possibleValues: string[]) {
return returnValue;
}
export function getNonEmptyValue(possibleValues: string[]) {
for (const value of possibleValues) {
if (value && value.trim() !== '') {
return value;
}
}
return undefined;
}
export type TPossibleValues = {
models: string[];
secondaryModels?: string[];
@ -266,6 +281,7 @@ export const getResponseSender = (endpointOption: t.TEndpointOption): string =>
type CompactEndpointSchema =
| typeof compactOpenAISchema
| typeof compactAssistantSchema
| typeof compactAgentsSchema
| typeof compactGoogleSchema
| typeof bingAISchema
| typeof compactAnthropicSchema
@ -278,6 +294,7 @@ const compactEndpointSchemas: Record<string, CompactEndpointSchema> = {
[EModelEndpoint.custom]: compactOpenAISchema,
[EModelEndpoint.assistants]: compactAssistantSchema,
[EModelEndpoint.azureAssistants]: compactAssistantSchema,
[EModelEndpoint.agents]: compactAgentsSchema,
[EModelEndpoint.google]: compactGoogleSchema,
/* BingAI needs all fields */
[EModelEndpoint.bingAI]: bingAISchema,
@ -331,7 +348,7 @@ export function parseTextParts(contentParts: a.TMessageContentParts[]): string {
for (const part of contentParts) {
if (part.type === ContentTypes.TEXT) {
const textValue = part.text.value;
const textValue = typeof part.text === 'string' ? part.text : part.text.value;
if (
result.length > 0 &&

View file

@ -23,6 +23,7 @@ export enum EModelEndpoint {
anthropic = 'anthropic',
assistants = 'assistants',
azureAssistants = 'azureAssistants',
agents = 'agents',
custom = 'custom',
}
@ -35,6 +36,15 @@ export const isAssistantsEndpoint = (endpoint?: AssistantsEndpoint | null | stri
return endpoint.toLowerCase().endsWith(EModelEndpoint.assistants);
};
export type AgentProvider = Exclude<keyof typeof EModelEndpoint, EModelEndpoint.agents> | string;
export const isAgentsEndpoint = (endpoint?: EModelEndpoint.agents | null | string): boolean => {
if (!endpoint) {
return false;
}
return endpoint === EModelEndpoint.agents;
};
export enum ImageDetail {
low = 'low',
auto = 'auto',
@ -69,6 +79,21 @@ export const defaultAssistantFormValues = {
retrieval: false,
};
export const defaultAgentFormValues = {
agent: {},
id: '',
name: '',
description: '',
instructions: '',
model: '',
model_parameters: {},
tools: [],
provider: {},
code_interpreter: false,
image_vision: false,
retrieval: false,
};
export const ImageVisionTool: FunctionTool = {
type: Tools.function,
[Tools.function]: {
@ -223,10 +248,53 @@ export const anthropicSettings = {
},
};
export const agentsSettings = {
model: {
default: 'gpt-3.5-turbo-test',
},
temperature: {
min: 0,
max: 1,
step: 0.01,
default: 1,
},
top_p: {
min: 0,
max: 1,
step: 0.01,
default: 1,
},
presence_penalty: {
min: 0,
max: 2,
step: 0.01,
default: 0,
},
frequency_penalty: {
min: 0,
max: 2,
step: 0.01,
default: 0,
},
resendFiles: {
default: true,
},
maxContextTokens: {
default: undefined,
},
max_tokens: {
default: undefined,
},
imageDetail: {
default: ImageDetail.auto,
},
};
export const endpointSettings = {
[EModelEndpoint.openAI]: openAISettings,
[EModelEndpoint.google]: googleSettings,
[EModelEndpoint.anthropic]: anthropicSettings,
[EModelEndpoint.agents]: agentsSettings,
};
const google = endpointSettings[EModelEndpoint.google];
@ -367,46 +435,49 @@ export const coerceNumber = z.union([z.number(), z.string()]).transform((val) =>
export const tConversationSchema = z.object({
conversationId: z.string().nullable(),
title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'),
user: z.string().optional(),
endpoint: eModelEndpointSchema.nullable(),
endpointType: eModelEndpointSchema.optional(),
suggestions: z.array(z.string()).optional(),
title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'),
user: z.string().optional(),
messages: z.array(z.string()).optional(),
tools: z.union([z.array(tPluginSchema), z.array(z.string())]).optional(),
createdAt: z.string(),
updatedAt: z.string(),
modelLabel: z.string().nullable().optional(),
examples: z.array(tExampleSchema).optional(),
tags: z.array(z.string()).optional(),
/* Prefer modelLabel over chatGptLabel */
chatGptLabel: z.string().nullable().optional(),
userLabel: z.string().optional(),
model: z.string().nullable().optional(),
promptPrefix: z.string().nullable().optional(),
temperature: z.number().optional(),
topP: z.number().optional(),
topK: z.number().optional(),
context: z.string().nullable().optional(),
top_p: z.number().optional(),
frequency_penalty: z.number().optional(),
presence_penalty: z.number().optional(),
parentMessageId: z.string().optional(),
maxOutputTokens: z.number().optional(),
agentOptions: tAgentOptionsSchema.nullable().optional(),
file_ids: z.array(z.string()).optional(),
maxContextTokens: coerceNumber.optional(),
max_tokens: coerceNumber.optional(),
/* Anthropic */
promptCache: z.boolean().optional(),
/* artifacts */
artifacts: z.string().optional(),
/* google */
context: z.string().nullable().optional(),
examples: z.array(tExampleSchema).optional(),
/* DB */
tags: z.array(z.string()).optional(),
createdAt: z.string(),
updatedAt: z.string(),
/* Files */
file_ids: z.array(z.string()).optional(),
/* vision */
resendFiles: z.boolean().optional(),
imageDetail: eImageDetailSchema.optional(),
/* assistant */
assistant_id: z.string().optional(),
/* agents */
agent_id: z.string().optional(),
/* assistant + agents */
instructions: z.string().optional(),
additional_instructions: z.string().optional(),
/** Used to overwrite active conversation settings when saving a Preset */
presetOverride: z.record(z.unknown()).optional(),
stop: z.array(z.string()).optional(),
@ -418,6 +489,8 @@ export const tConversationSchema = z.object({
Deprecated fields
*/
/** @deprecated */
suggestions: z.array(z.string()).optional(),
/** @deprecated */
systemMessage: z.string().nullable().optional(),
/** @deprecated */
jailbreak: z.boolean().optional(),
@ -433,6 +506,10 @@ export const tConversationSchema = z.object({
toneStyle: z.string().nullable().optional(),
/** @deprecated */
resendImages: z.boolean().optional(),
/** @deprecated */
agentOptions: tAgentOptionsSchema.nullable().optional(),
/** @deprecated Prefer `modelLabel` over `chatGptLabel` */
chatGptLabel: z.string().nullable().optional(),
});
export const tPresetSchema = tConversationSchema
@ -844,6 +921,71 @@ export const compactAssistantSchema = tConversationSchema
.transform(removeNullishValues)
.catch(() => ({}));
export const agentsSchema = tConversationSchema
.pick({
model: true,
modelLabel: true,
temperature: true,
top_p: true,
presence_penalty: true,
frequency_penalty: true,
resendFiles: true,
imageDetail: true,
agent_id: true,
instructions: true,
promptPrefix: true,
iconURL: true,
greeting: true,
maxContextTokens: true,
})
.transform((obj) => ({
...obj,
model: obj.model ?? agentsSettings.model.default,
modelLabel: obj.modelLabel ?? null,
temperature: obj.temperature ?? 1,
top_p: obj.top_p ?? 1,
presence_penalty: obj.presence_penalty ?? 0,
frequency_penalty: obj.frequency_penalty ?? 0,
resendFiles:
typeof obj.resendFiles === 'boolean' ? obj.resendFiles : agentsSettings.resendFiles.default,
imageDetail: obj.imageDetail ?? ImageDetail.auto,
agent_id: obj.agent_id ?? undefined,
instructions: obj.instructions ?? undefined,
promptPrefix: obj.promptPrefix ?? null,
iconURL: obj.iconURL ?? undefined,
greeting: obj.greeting ?? undefined,
maxContextTokens: obj.maxContextTokens ?? undefined,
}))
.catch(() => ({
model: agentsSettings.model.default,
modelLabel: null,
temperature: 1,
top_p: 1,
presence_penalty: 0,
frequency_penalty: 0,
resendFiles: agentsSettings.resendFiles.default,
imageDetail: ImageDetail.auto,
agent_id: undefined,
instructions: undefined,
promptPrefix: null,
iconURL: undefined,
greeting: undefined,
maxContextTokens: undefined,
}));
export const compactAgentsSchema = tConversationSchema
.pick({
model: true,
agent_id: true,
instructions: true,
promptPrefix: true,
iconURL: true,
greeting: true,
spec: true,
})
.transform(removeNullishValues)
.catch(() => ({}));
export const compactOpenAISchema = tConversationSchema
.pick({
model: true,

View file

@ -78,6 +78,7 @@ export type GroupedConversations = [key: string, TConversation[]][];
export type TUpdateUserPlugins = {
isAssistantTool?: boolean;
isAgentTool?: boolean;
pluginKey: string;
action: string;
auth?: unknown;

View file

@ -0,0 +1,219 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { StepTypes, ContentTypes, ToolCallTypes } from './runs';
import type { FunctionToolCall } from './assistants';
export namespace Agents {
export type MessageType = 'human' | 'ai' | 'generic' | 'system' | 'function' | 'tool' | 'remove';
export type ImageDetail = 'auto' | 'low' | 'high';
export type MessageContentText = {
type: ContentTypes.TEXT;
text: string;
};
export type MessageContentImageUrl = {
type: 'image_url';
image_url: string | { url: string; detail?: ImageDetail };
};
export type MessageContentComplex =
| MessageContentText
| MessageContentImageUrl
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| (Record<string, any> & { type?: ContentTypes | 'image_url' | 'text_delta' | string })
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| (Record<string, any> & { type?: never });
export type MessageContent = string | MessageContentComplex[];
/**
* A call to a tool.
*/
export type ToolCall = {
/** Type ("tool_call") according to Assistants Tool Call Structure */
type: ToolCallTypes.TOOL_CALL;
/** The name of the tool to be called */
name: string;
/** The arguments to the tool call */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: string | Record<string, any>;
/** If provided, an identifier associated with the tool call */
id?: string;
/** If provided, the output of the tool call */
output?: string;
};
export type ToolEndEvent = {
/** The Step Id of the Tool Call */
id: string;
/** The Completed Tool Call */
tool_call: ToolCall;
/** The content index of the tool call */
index: number;
};
export type ToolCallContent = {
type: ContentTypes.TOOL_CALL;
tool_call: ToolCall;
};
/**
* A chunk of a tool call (e.g., as part of a stream).
* When merging ToolCallChunks (e.g., via AIMessageChunk.__add__),
* all string attributes are concatenated. Chunks are only merged if their
* values of `index` are equal and not None.
*
* @example
* ```ts
* const leftChunks = [
* {
* name: "foo",
* args: '{"a":',
* index: 0
* }
* ];
*
* const leftAIMessageChunk = new AIMessageChunk({
* content: "",
* tool_call_chunks: leftChunks
* });
*
* const rightChunks = [
* {
* name: undefined,
* args: '1}',
* index: 0
* }
* ];
*
* const rightAIMessageChunk = new AIMessageChunk({
* content: "",
* tool_call_chunks: rightChunks
* });
*
* const result = leftAIMessageChunk.concat(rightAIMessageChunk);
* // result.tool_call_chunks is equal to:
* // [
* // {
* // name: "foo",
* // args: '{"a":1}'
* // index: 0
* // }
* // ]
* ```
*
* @property {string} [name] - If provided, a substring of the name of the tool to be called
* @property {string} [args] - If provided, a JSON substring of the arguments to the tool call
* @property {string} [id] - If provided, a substring of an identifier for the tool call
* @property {number} [index] - If provided, the index of the tool call in a sequence
*/
export type ToolCallChunk = {
name?: string;
args?: string;
id?: string;
index?: number;
type?: 'tool_call_chunk';
};
/** Event names are of the format: on_[runnable_type]_(start|stream|end).
Runnable types are one of:
llm - used by non chat models
chat_model - used by chat models
prompt -- e.g., ChatPromptTemplate
tool -- LangChain tools
chain - most Runnables are of this type
Further, the events are categorized as one of:
start - when the runnable starts
stream - when the runnable is streaming
end - when the runnable ends
start, stream and end are associated with slightly different data payload.
Please see the documentation for EventData for more details. */
export type EventName = string;
export type RunStep = {
type: StepTypes;
id: string; // #new
runId?: string; // #new
index: number; // #new
stepIndex?: number; // #new
stepDetails: StepDetails;
usage: null | {
// Define usage structure if it's ever non-null
// prompt_tokens: number; // #new
// completion_tokens: number; // #new
// total_tokens: number; // #new
};
};
/**
* Represents a run step delta i.e. any changed fields on a run step during
* streaming.
*/
export interface RunStepDeltaEvent {
/**
* The identifier of the run step, which can be referenced in API endpoints.
*/
id: string;
/**
* The delta containing the fields that have changed on the run step.
*/
delta: ToolCallDelta;
}
export type StepDetails = MessageCreationDetails | ToolCallsDetails;
export type MessageCreationDetails = {
type: StepTypes.MESSAGE_CREATION;
message_creation: {
message_id: string;
};
};
export type ToolCallsDetails = {
type: StepTypes.TOOL_CALLS;
tool_calls: AgentToolCall[];
};
export type ToolCallDelta = {
type: StepTypes.TOOL_CALLS;
tool_calls: ToolCallChunk[];
};
export type AgentToolCall = FunctionToolCall | ToolCall;
export interface ExtendedMessageContent {
type?: string;
text?: string;
input?: string;
index?: number;
id?: string;
name?: string;
}
/**
* Represents a message delta i.e. any changed fields on a message during
* streaming.
*/
export interface MessageDeltaEvent {
/**
* The identifier of the message, which can be referenced in API endpoints.
*/
id: string;
/**
* The delta containing the fields that have changed on the Message.
*/
delta: MessageDelta;
}
/**
* The delta containing the fields that have changed on the Message.
*/
export interface MessageDelta {
/**
* The content of the message in array of text and/or images.
*/
content?: MessageContentComplex[];
}
export type ContentType = ContentTypes.TEXT | 'image_url' | string;
}

View file

@ -1,5 +1,7 @@
import type { OpenAPIV3 } from 'openapi-types';
import type { AssistantsEndpoint } from 'src/schemas';
import type { AssistantsEndpoint, AgentProvider } from 'src/schemas';
import type { ContentTypes } from './runs';
import type { Agents } from './agents';
import type { TFile } from './files';
export type Schema = OpenAPIV3.SchemaObject & { description?: string };
@ -66,6 +68,8 @@ export interface FileSearchResource {
vector_store_ids?: Array<string>;
}
/* Assistant types */
export type Assistant = {
id: string;
created_at: number;
@ -136,6 +140,89 @@ export type File = {
purpose: 'fine-tune' | 'fine-tune-results' | 'assistants' | 'assistants_output';
};
/* Agent types */
export type AgentModelParameters = {
temperature: number | null;
max_context_tokens: number | null;
max_output_tokens: number | null;
top_p: number | null;
frequency_penalty: number | null;
presence_penalty: number | null;
};
export type Agent = {
id: string;
name: string | null;
description: string | null;
created_at: number;
avatar: AgentAvatar | null;
file_ids: string[];
instructions: string | null;
tools?: string[];
tool_kwargs?: Record<string, unknown>;
tool_resources?: ToolResources;
metadata?: Record<string, unknown>;
provider: AgentProvider;
model: string | null;
model_parameters: AgentModelParameters;
object: string;
};
export type TAgentsMap = Record<string, Agent | undefined>;
export type AgentCreateParams = {
name?: string | null;
description?: string | null;
avatar?: AgentAvatar | null;
file_ids?: string[];
instructions?: string | null;
tools?: Array<FunctionTool | string>;
provider: AgentProvider;
model: string | null;
model_parameters: AgentModelParameters;
};
export type AgentUpdateParams = {
name?: string | null;
description?: string | null;
avatar?: AgentAvatar | null;
file_ids?: string[];
instructions?: string | null;
tools?: Array<FunctionTool | string>;
tool_resources?: ToolResources;
provider?: AgentProvider;
model: string | null;
model_parameters: AgentModelParameters;
};
export type AgentListParams = {
limit?: number;
before?: string | null;
after?: string | null;
order?: 'asc' | 'desc';
provider?: AgentProvider;
};
export type AgentListResponse = {
object: string;
data: Agent[];
first_id: string;
last_id: string;
has_more: boolean;
};
export type AgentFile = {
file_id: string;
id?: string;
temp_file_id?: string;
bytes: number;
created_at: number;
filename: string;
object: string;
purpose: 'fine-tune' | 'fine-tune-results' | 'agents' | 'agents_output';
};
/**
* Details of the Code Interpreter tool call the run step was involved in.
* Includes the tool call ID, the code interpreter definition, and the type of tool call.
@ -248,25 +335,6 @@ export enum AnnotationTypes {
FILE_PATH = 'file_path',
}
export enum ContentTypes {
TEXT = 'text',
TOOL_CALL = 'tool_call',
IMAGE_FILE = 'image_file',
ERROR = 'error',
}
export enum StepTypes {
TOOL_CALLS = 'tool_calls',
MESSAGE_CREATION = 'message_creation',
}
export enum ToolCallTypes {
FUNCTION = 'function',
RETRIEVAL = 'retrieval',
FILE_SEARCH = 'file_search',
CODE_INTERPRETER = 'code_interpreter',
}
export enum StepStatus {
IN_PROGRESS = 'in_progress',
CANCELLED = 'cancelled',
@ -305,6 +373,7 @@ export type ContentPart = (
| RetrievalToolCall
| FileSearchToolCall
| FunctionToolCall
| Agents.AgentToolCall
| ImageFile
| Text
) &
@ -312,13 +381,20 @@ export type ContentPart = (
export type TMessageContentParts =
| { type: ContentTypes.ERROR; text: Text & PartMetadata }
| { type: ContentTypes.TEXT; text: Text & PartMetadata }
| { type: ContentTypes.TEXT; text: string | (Text & PartMetadata) }
| {
type: ContentTypes.TOOL_CALL;
tool_call: (CodeToolCall | RetrievalToolCall | FileSearchToolCall | FunctionToolCall) &
tool_call: (
| CodeToolCall
| RetrievalToolCall
| FileSearchToolCall
| FunctionToolCall
| Agents.AgentToolCall
) &
PartMetadata;
}
| { type: ContentTypes.IMAGE_FILE; image_file: ImageFile & PartMetadata };
| { type: ContentTypes.IMAGE_FILE; image_file: ImageFile & PartMetadata }
| Agents.MessageContentImageUrl;
export type StreamContentData = TMessageContentParts & {
/** The index of the current content part */
@ -378,14 +454,15 @@ export type ActionMetadata = {
oauth_client_secret?: string;
};
/* Assistant types */
export type Action = {
action_id: string;
assistant_id: string;
type?: string;
settings?: Record<string, unknown>;
metadata: ActionMetadata;
version: number | string;
};
} & ({ assistant_id: string; agent_id?: never } | { assistant_id?: never; agent_id: string });
export type AssistantAvatar = {
filepath: string;
@ -404,6 +481,13 @@ export type AssistantDocument = {
updatedAt?: Date;
};
/* Agent types */
export type AgentAvatar = {
filepath: string;
source: string;
};
export enum FilePurpose {
Vision = 'vision',
FineTune = 'fine-tune',

View file

@ -8,6 +8,9 @@ import {
FunctionTool,
AssistantDocument,
Action,
Agent,
AgentCreateParams,
AgentUpdateParams,
} from './assistants';
export type MutationOptions<
@ -41,6 +44,8 @@ export type DeletePresetOptions = MutationOptions<PresetDeleteResponse, types.TP
export type LogoutOptions = MutationOptions<unknown, undefined>;
/* Assistant mutations */
export type AssistantAvatarVariables = {
assistant_id: string;
model: string;
@ -94,6 +99,51 @@ export type DeleteActionVariables = {
export type DeleteActionOptions = MutationOptions<void, DeleteActionVariables>;
/* Agent mutations */
export type AgentAvatarVariables = {
agent_id: string;
formData: FormData;
postCreation?: boolean;
};
export type UpdateAgentActionVariables = {
agent_id: string;
action_id?: string;
metadata: ActionMetadata;
functions: FunctionTool[];
};
export type UploadAgentAvatarOptions = MutationOptions<Agent, AgentAvatarVariables>;
export type CreateAgentMutationOptions = MutationOptions<Agent, AgentCreateParams>;
export type UpdateAgentVariables = {
agent_id: string;
data: AgentUpdateParams;
};
export type UpdateAgentMutationOptions = MutationOptions<Agent, UpdateAgentVariables>;
export type DeleteAgentBody = {
agent_id: string;
};
export type DeleteAgentMutationOptions = MutationOptions<void, Pick<DeleteAgentBody, 'agent_id'>>;
export type UpdateAgentActionResponse = [Agent, Action];
export type UpdateAgentActionOptions = MutationOptions<
UpdateAgentActionResponse,
UpdateAgentActionVariables
>;
export type DeleteAgentActionVariables = {
agent_id: string;
action_id: string;
};
export type DeleteAgentActionOptions = MutationOptions<void, DeleteAgentActionVariables>;
export type DeleteConversationOptions = MutationOptions<
types.TDeleteConversationResponse,
types.TDeleteConversationRequest

View file

@ -0,0 +1,21 @@
export enum ContentTypes {
TEXT = 'text',
TOOL_CALL = 'tool_call',
IMAGE_FILE = 'image_file',
IMAGE_URL = 'image_url',
ERROR = 'error',
}
export enum StepTypes {
TOOL_CALLS = 'tool_calls',
MESSAGE_CREATION = 'message_creation',
}
export enum ToolCallTypes {
FUNCTION = 'function',
RETRIEVAL = 'retrieval',
FILE_SEARCH = 'file_search',
CODE_INTERPRETER = 'code_interpreter',
/* Agents Tool Call */
TOOL_CALL = 'tool_call',
}