Skip to content

Commit 99a210d

Browse files
Merge pull request #1 from aipotheosis-labs/resource_api
beta version: support apps, app configuration, linked accounts, functions and meta function endpoint
2 parents d03f0c4 + 721a95d commit 99a210d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2112
-186
lines changed

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
TEST_ACI_API_KEY="xx"
2+
TEST_BASE_URL=https://api.aci.dev/v1
3+
TEST_PROJECT_ID=5f263205-7900-4a0e-bf85-0e1a3d898664
4+
TEST_AGENT_ID=b5378229-f0d9-4219-a6e5-18fdec8391b4
5+
TEST_LINKED_ACCOUNT_OWNER_ID = [email protected]
6+
TEST_APP_NAME = ARXIV
7+
TEST_FUNCTION_NAME = ARXIV__SEARCH_PAPERS
8+
TEST_OPENAI_API_KEY = "xx"

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist
2+
node_modules
3+
coverage

.eslintrc.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"env": {
3+
"browser": true,
4+
"es2021": true,
5+
"node": true,
6+
"jest": true
7+
},
8+
"extends": [
9+
"eslint:recommended",
10+
"plugin:@typescript-eslint/recommended"
11+
],
12+
"parser": "@typescript-eslint/parser",
13+
"parserOptions": {
14+
"ecmaVersion": "latest",
15+
"sourceType": "module"
16+
},
17+
"plugins": [
18+
"@typescript-eslint"
19+
],
20+
"rules": {
21+
"indent": ["error", 2, { "SwitchCase": 1 }],
22+
"linebreak-style": ["error", "unix"],
23+
"quotes": ["error", "single"],
24+
"semi": ["error", "always"],
25+
"@typescript-eslint/no-explicit-any": "warn",
26+
"@typescript-eslint/explicit-function-return-type": "off",
27+
"@typescript-eslint/no-unused-vars": "warn"
28+
},
29+
"ignorePatterns": ["dist/**/*"]
30+
}

.prettierrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "es5",
4+
"tabWidth": 2,
5+
"semi": true,
6+
"printWidth": 100,
7+
"bracketSpacing": true,
8+
"arrowParens": "avoid"
9+
}

DEVELOPMENT.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# ACI TypeScript SDK Development Guide
2+
3+
This guide provides instructions for setting up and developing the ACI TypeScript SDK locally.
4+
5+
## Prerequisites
6+
7+
- Node.js (v16 or higher)
8+
- pnpm (recommended) or npm
9+
- Git
10+
11+
## Installation
12+
13+
1. Clone the repository:
14+
```bash
15+
git clone https://github.com/aipotheosis-labs/aci-typescript-sdk.git
16+
cd aci-typescript-sdk
17+
```
18+
19+
2. Install dependencies:
20+
```bash
21+
pnpm install
22+
```
23+
24+
## Environment Configuration
25+
26+
1. Create a `.env` file in the root directory:
27+
```bash
28+
cp .env.example .env
29+
```
30+
31+
2. Add the following environment variables to your `.env` file:
32+
```env
33+
# API Configuration
34+
ACI_API_KEY=your_api_key_here
35+
ACI_API_URL=https://api.aipolabs.com/v1 # or your custom API URL
36+
```
37+
38+
Replace `your_api_key_here` with your actual ACI API key.
39+
40+
## Development
41+
42+
### Building the Project
43+
44+
To build the TypeScript code:
45+
```bash
46+
pnpm run build
47+
```
48+
49+
This will compile the TypeScript code into JavaScript in the `dist` directory.
50+
51+
### Running Tests
52+
53+
> [!IMPORTANT]
54+
> You need to configure following app configurations in your ACI account to run the tests:
55+
> - ARXIV
56+
> - BRAVE_SEARCH
57+
> - GITHUB
58+
59+
The project uses Jest for testing. To run the tests:
60+
```bash
61+
pnpm test
62+
```
63+
64+
To run tests in watch mode during development:
65+
```bash
66+
pnpm test -- --watch
67+
```
68+
69+
### Code Quality
70+
71+
The project uses ESLint for linting and Prettier for code formatting.
72+
73+
To run the linter:
74+
```bash
75+
pnpm run lint
76+
```
77+
78+
To format your code:
79+
```bash
80+
pnpm run format
81+
```
82+
83+
## Project Structure
84+
85+
```
86+
src/
87+
├── index.ts # Main entry point
88+
├── client.ts # API client implementation
89+
├── constants.ts # SDK constants
90+
├── exceptions.ts # Custom exceptions
91+
├── meta_functions/ # Meta functions implementation
92+
├── resource/ # Resource implementations
93+
├── schemas/ # Zod schemas
94+
└── types/ # TypeScript type definitions
95+
```
96+
97+
## Publishing
98+
99+
The project uses Changesets for versioning and publishing. To create a new version:
100+
101+
1. Create a changeset:
102+
```bash
103+
pnpm run changeset
104+
```
105+
106+
2. Update the version:
107+
```bash
108+
pnpm run version
109+
```
110+
111+
3. Publish to npm:
112+
```bash
113+
pnpm run release
114+
```
115+
116+
## Contributing
117+
118+
1. Create a new branch for your feature or bugfix
119+
2. Make your changes
120+
3. Run tests and ensure they pass
121+
4. Submit a pull request
122+
123+
## License
124+
125+
This project is licensed under the MIT License - see the LICENSE file for details.

__tests__/client.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
21
import MockAdapter from 'axios-mock-adapter';
32
import { ACI } from '../src/client';
4-
import { DEFAULT_MAX_RETRIES } from '../src/constants';
5-
import { UnknownError, RateLimitError, ServerError, ValidationError } from '../src/exceptions';
3+
import { ValidationError } from '../src/exceptions';
64

75
describe('ACI Client', () => {
86
let client: ACI;
@@ -39,7 +37,7 @@ describe('ACI Client', () => {
3937
const searchPromise = client.apps.search({});
4038
await jest.runAllTimersAsync(); // Ensure timers related to retry delays are processed
4139
const result = await searchPromise;
42-
40+
4341
expect(mock.history.get.length).toBe(2);
4442
expect(mock.history.get[0].url).toBe('/apps/search');
4543
expect(mock.history.get[1].url).toBe('/apps/search');
@@ -59,7 +57,7 @@ describe('ACI Client', () => {
5957
const searchPromise = client.apps.search({});
6058
await jest.runAllTimersAsync();
6159
const result = await searchPromise;
62-
60+
6361
expect(mock.history.get.length).toBe(2);
6462
expect(mock.history.get[0].url).toBe('/apps/search');
6563
expect(mock.history.get[1].url).toBe('/apps/search');
@@ -79,7 +77,7 @@ describe('ACI Client', () => {
7977
const searchPromise = client.apps.search({});
8078
await jest.runAllTimersAsync();
8179
const result = await searchPromise;
82-
80+
8381
expect(mock.history.get.length).toBe(2);
8482
expect(mock.history.get[0].url).toBe('/apps/search');
8583
expect(mock.history.get[1].url).toBe('/apps/search');
@@ -96,4 +94,4 @@ describe('ACI Client', () => {
9694
expect(mock.history.get[0].url).toBe('/apps/search');
9795
});
9896
});
99-
});
97+
});
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { FunctionDefinitionFormat } from '../../src/types/functions';
2+
import {
3+
ACIExecuteFunction,
4+
getAciExecuteFunctionSchema,
5+
wrapFunctionArgumentsIfNotPresent
6+
} from '../../src/meta_functions/aci_execute_function';
7+
import { ACI } from '../../src/client';
8+
import MockAdapter from 'axios-mock-adapter';
9+
10+
describe('ACI_EXECUTE_FUNCTION Meta Function', () => {
11+
let client: ACI;
12+
let mock: MockAdapter;
13+
const mockApiKey = 'test-api-key';
14+
15+
beforeEach(() => {
16+
client = new ACI({ apiKey: mockApiKey });
17+
mock = new MockAdapter(client['client']);
18+
});
19+
20+
afterEach(() => {
21+
mock.reset();
22+
});
23+
24+
describe('Schema', () => {
25+
it('should have the correct name and description', () => {
26+
const schema = getAciExecuteFunctionSchema();
27+
expect(schema.name).toBe('ACI_EXECUTE_FUNCTION');
28+
expect(schema.description).toContain('Execute a specific retrieved function');
29+
});
30+
31+
it('should have the required properties', () => {
32+
const schema = getAciExecuteFunctionSchema();
33+
expect(schema.parameters.properties).toHaveProperty('function_name');
34+
expect(schema.parameters.properties).toHaveProperty('function_arguments');
35+
expect(schema.parameters.required).toContain('function_name');
36+
expect(schema.parameters.required).toContain('function_arguments');
37+
});
38+
39+
it('should format schema for OpenAI correctly', () => {
40+
const openAISchema = ACIExecuteFunction.toJsonSchema(FunctionDefinitionFormat.OPENAI);
41+
expect(openAISchema).toHaveProperty('type', 'function');
42+
expect(openAISchema).toHaveProperty('function');
43+
expect(openAISchema.function).toHaveProperty('name', 'ACI_EXECUTE_FUNCTION');
44+
});
45+
46+
it('should format schema for Anthropic correctly', () => {
47+
const anthropicSchema = ACIExecuteFunction.toJsonSchema(FunctionDefinitionFormat.ANTHROPIC);
48+
expect(anthropicSchema).toHaveProperty('name', 'ACI_EXECUTE_FUNCTION');
49+
expect(anthropicSchema).toHaveProperty('input_schema');
50+
});
51+
});
52+
53+
describe('wrapFunctionArgumentsIfNotPresent helper', () => {
54+
it('should wrap arguments when function_arguments is not present', () => {
55+
const input = {
56+
function_name: 'calendar_create_event',
57+
title: 'Meeting',
58+
start_time: '2023-01-01T10:00:00Z'
59+
};
60+
61+
const result = wrapFunctionArgumentsIfNotPresent(input);
62+
expect(result).toEqual({
63+
function_name: 'calendar_create_event',
64+
function_arguments: {
65+
title: 'Meeting',
66+
start_time: '2023-01-01T10:00:00Z'
67+
}
68+
});
69+
});
70+
71+
it('should not modify input if function_arguments is already present', () => {
72+
const input = {
73+
function_name: 'calendar_create_event',
74+
function_arguments: {
75+
title: 'Meeting',
76+
start_time: '2023-01-01T10:00:00Z'
77+
}
78+
};
79+
80+
const result = wrapFunctionArgumentsIfNotPresent(input);
81+
expect(result).toEqual(input);
82+
});
83+
});
84+
85+
describe('Integration with client', () => {
86+
const mockResponse = {
87+
success: true,
88+
data: { id: '123', title: 'Meeting' }
89+
};
90+
91+
it('should call the execute endpoint with the correct parameters', async () => {
92+
mock.onPost('/functions/calendar_create_event/execute').reply(200, mockResponse);
93+
94+
const linkedAccountOwnerId = 'user-123';
95+
const result = await client.handleFunctionCall(
96+
'ACI_EXECUTE_FUNCTION',
97+
{
98+
function_name: 'calendar_create_event',
99+
function_arguments: {
100+
title: 'Team Meeting',
101+
start_time: '2023-01-01T10:00:00Z',
102+
end_time: '2023-01-01T11:00:00Z'
103+
}
104+
},
105+
linkedAccountOwnerId
106+
);
107+
108+
expect(mock.history.post.length).toBe(1);
109+
expect(mock.history.post[0].url).toBe('/functions/calendar_create_event/execute');
110+
expect(JSON.parse(mock.history.post[0].data)).toEqual({
111+
function_input: {
112+
title: 'Team Meeting',
113+
start_time: '2023-01-01T10:00:00Z',
114+
end_time: '2023-01-01T11:00:00Z'
115+
},
116+
linked_account_owner_id: linkedAccountOwnerId
117+
});
118+
expect(result).toEqual(mockResponse);
119+
});
120+
121+
it('should handle unwrapped function arguments', async () => {
122+
mock.onPost('/functions/calendar_create_event/execute').reply(200, mockResponse);
123+
124+
const linkedAccountOwnerId = 'user-123';
125+
// Note: function_arguments is not wrapped
126+
const result = await client.handleFunctionCall(
127+
'ACI_EXECUTE_FUNCTION',
128+
{
129+
function_name: 'calendar_create_event',
130+
title: 'Team Meeting',
131+
start_time: '2023-01-01T10:00:00Z'
132+
},
133+
linkedAccountOwnerId
134+
);
135+
136+
expect(mock.history.post.length).toBe(1);
137+
expect(mock.history.post[0].url).toBe('/functions/calendar_create_event/execute');
138+
expect(JSON.parse(mock.history.post[0].data)).toEqual({
139+
function_input: {
140+
title: 'Team Meeting',
141+
start_time: '2023-01-01T10:00:00Z'
142+
},
143+
linked_account_owner_id: linkedAccountOwnerId
144+
});
145+
expect(result).toEqual(mockResponse);
146+
});
147+
148+
it('should handle function execution errors', async () => {
149+
const errorResponse = {
150+
success: false,
151+
error: 'Invalid parameters'
152+
};
153+
154+
mock.onPost('/functions/calendar_create_event/execute').reply(200, errorResponse);
155+
156+
const linkedAccountOwnerId = 'user-123';
157+
const result = await client.handleFunctionCall(
158+
'ACI_EXECUTE_FUNCTION',
159+
{
160+
function_name: 'calendar_create_event',
161+
function_arguments: { title: 'Meeting' }
162+
},
163+
linkedAccountOwnerId
164+
);
165+
166+
expect(mock.history.post.length).toBe(1);
167+
expect(result).toEqual(errorResponse);
168+
});
169+
});
170+
});

0 commit comments

Comments
 (0)