Skip to content
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
91 changes: 57 additions & 34 deletions .cursor/rules/examples-standards.mdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
description: Standards for creating and maintaining examples for all functionality
description: Standards for creating and maintaining examples in the StackOne repository
globs: examples/*
alwaysApply: false
---
# Examples Standards

Expand All @@ -23,60 +24,82 @@ actions:
```
examples/
├── basic_usage/
│ ├── basic_tool_usage.py # Basic usage examples
│ └── error_handling.py # Error handling examples
│ ├── basic_tool_usage.ts # Basic usage examples
│ └── error_handling.ts # Error handling examples
├── integrations/ # Integration examples
│ ├── openai_integration.py
│ └── other_integration.py
│ ├── openai_integration.ts
│ └── other_integration.ts
└── README.md # Examples documentation
```

2. Example Requirements:
- Every public function/class needs at least one example
- Examples should be runnable Python scripts
- Include error handling cases
- Load credentials from .env
- Include type hints
- Examples should be runnable TypeScript scripts
- Use Node's assert module for validation instead of console.logs
- Include proper error handling with try/catch blocks
- Include TypeScript return types for all functions
- Follow the same code style as the main codebase
- Exit with non-zero code on error using process.exit(1)

3. Documentation:
- Each example file should start with a docstring explaining its purpose
- Include expected output in comments
- Use assertions to validate expected behavior
- Document any prerequisites (environment variables, etc)

4. Testing:
- Examples should be tested as part of CI
- Examples should work with the latest package version
- Include sample responses in comments
- Use assertions to verify expected behavior

examples:
- input: |
# Good example structure
import os
from dotenv import load_dotenv
from stackone_ai import StackOneToolSet

def main():
"""Example showing basic usage of StackOneToolSet."""
load_dotenv()

api_key = os.getenv("STACKONE_API_KEY")
if not api_key:
raise ValueError("STACKONE_API_KEY not found")

# Example code...

if __name__ == "__main__":
main()
/**
* Example showing basic usage of StackOneToolSet.
*/

import assert from 'node:assert';
import { StackOneToolSet } from '../src';

const exampleFunction = async (): Promise<void> => {
// Initialize the toolset
const toolset = new StackOneToolSet();

// Get tools and verify
const tools = toolset.getTools('hris_*');
assert(tools.length > 0, 'Expected to find HRIS tools');

// Use a specific tool
const employeeTool = tools.getTool('hris_list_employees');
assert(employeeTool !== undefined, 'Expected to find tool');

// Execute and verify result
const result = await employeeTool.execute();
assert(Array.isArray(result), 'Expected result to be an array');
};

// Run the example
exampleFunction().catch((error) => {
console.error('Error:', error);
process.exit(1);
});
output: "Correctly structured example"

- input: |
# Bad example - missing error handling, docs, types
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet("hardcoded_key")
tools = toolset.get_tools("crm")
result = tools["some_tool"].execute()
// Bad example - missing assertions, error handling, types
import { StackOneToolSet } from '../src';

const badExample = async () => {
const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*");
const tool = tools.getTool("hris_list_employees");

if (tool) {
const result = await tool.execute();
console.log(result);
}
};

badExample();
output: "Incorrectly structured example"

metadata:
Expand Down
122 changes: 56 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,79 +25,84 @@ export STACKONE_API_KEY=<your-api-key>

or load from a .env file using your preferred environment variable library.

## Account IDs

StackOne uses account IDs to identify different integrations. You can specify the account ID when initializing the SDK or when getting tools.

## Quickstart

```typescript
import { StackOneToolSet } from "@stackone/ai";

// Initialize with API key from environment variable
const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*", "your-account-id");
const employeeTool = tools.getTool("hris_list_employees");
const employees = await employeeTool.execute();
```

// Or initialize with explicit API key
// const toolset = new StackOneToolSet('your-api-key');
[View full example](examples/index.ts)

// Get all HRIS-related tools
const accountId = "45072196112816593343";
const tools = toolset.getTools("hris_*", accountId);
## Account IDs

// Use a specific tool
const employeeTool = tools.getTool("hris_list_employees");
if (employeeTool) {
const employees = await employeeTool.execute();
console.log(employees);
}
StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels:

```typescript
import { StackOneToolSet } from "@stackone/ai";

// Method 1: Set at toolset initialization
const toolset = new StackOneToolSet({ accountId: "your-account-id" });

// Method 2: Set when getting tools (overrides toolset account ID)
const tools = toolset.getTools("hris_*", "override-account-id");

// Method 3: Set directly on a tool instance
tool.setAccountId("direct-account-id");
const currentAccountId = tool.getAccountId(); // Get the current account ID
```

## Custom Base URL
[View full example](examples/account-id-usage.ts)

You can specify a custom base URL when initializing the SDK. This is useful for testing against development APIs or working with self-hosted StackOne instances.
## Custom Base URL

```typescript
import { StackOneToolSet } from "@stackone/ai";

// Initialize with a custom base URL
const toolset = new StackOneToolSet(
"your-api-key",
"your-account-id",
"https://api.example-dev.com"
);

// Get tools with the custom base URL
const toolset = new StackOneToolSet({ baseUrl: "https://api.example-dev.com" });
const tools = toolset.getTools("hris_*");

// All API requests will use the custom base URL
const employeeTool = tools.getTool("hris_list_employees");
if (employeeTool) {
const employees = await employeeTool.execute();
console.log(employees);
}
```

## File Uploads
[View full example](examples/custom-base-url.ts)

The SDK supports file uploads for tools that accept file parameters. File uploads have been simplified to use a single `file_path` parameter:
## File Uploads

```typescript
import { StackOneToolSet } from "@stackone/ai";
import * as path from "path";

// Initialize with API key and account ID
const toolset = new StackOneToolSet();
const tools = toolset.getTools("documents_*");
const uploadTool = tools.getTool("documents_upload_file");

// Upload a file using the file_path parameter
const result = await uploadTool.execute({
file_path: "/path/to/document.pdf", // Path to the file
const uploadTool = toolset
.getTools("hris_*")
.getTool("hris_upload_employee_document");
await uploadTool.execute({
file_path: "/path/to/document.pdf",
id: "employee-id",
});
```

[View full example](examples/file-uploads.ts)

The name, file format, and content of the file are automatically extracted from the path.
## Error Handling

```typescript
import { StackOneAPIError, StackOneError } from "@stackone/ai";

try {
await tool.execute();
} catch (error) {
if (error instanceof StackOneAPIError) {
// Handle API errors
}
}
```

[View full example](examples/error-handling.ts)

## Integrations

### OpenAI
Expand All @@ -106,38 +111,23 @@ The name, file format, and content of the file are automatically extracted from
import { OpenAI } from "openai";
import { StackOneToolSet } from "@stackone/ai";

const openai = new OpenAI();
const toolset = new StackOneToolSet();

const tools = toolset.getTools("hris_*");
const tool = tools.getTool("hris_list_employees");

const result = await completionTool.execute({
model: "gpt-4o-mini",
prompt: "What are the names of the employees?",
tools: [tool.toOpenAI()],
});
const openAITools = toolset.getTools("hris_*").toOpenAI();
await openai.chat.completions.create({ tools: openAITools });
```

[View full example](examples/openai-integration.ts)

### AI SDK by Vercel

```typescript
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { StackOneToolSet } from "@stackone/ai";

// Initialize StackOne
const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*", "your-account-id");

// Convert to AI SDK tools
const aiSdkTools = tools.toAISDKTools();

// Use with AI SDK
const { text } = await generateText({
model: openai("gpt-4o-mini"),
tools: aiSdkTools,
prompt: "Get employee details for John Doe",
maxSteps: 3, // Automatically calls tools when needed
});
const aiSdkTools = toolset.getTools("hris_*").toAISDK();
await generateText({ tools: aiSdkTools, maxSteps: 3 });
```

[View full example](examples/ai-sdk-integration.ts)
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@
},
"files": {
"ignoreUnknown": true,
"include": ["src/**/*.ts"]
"include": ["src/**/*.ts", "examples/**/*.ts"]
}
}
50 changes: 50 additions & 0 deletions examples/account-id-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bun
/**
* Example demonstrating different ways to set the account ID when using StackOne tools.
*
* This example shows:
* 1. Setting account ID when initializing the toolset
* 2. Setting account ID when getting tools
* 3. Using setAccountId method directly on a tool
*
* Usage:
*
* ```bash
* bun run examples/account-id-usage.ts
* ```
*/

import assert from 'node:assert';
import { StackOneToolSet } from '../src';

const accountIdUsage = async (): Promise<void> => {
// Set account ID from toolset initialization
const toolset = new StackOneToolSet({ accountId: 'initial-account-id' });

const tools = toolset.getTools('hris_*');
const employeeTool = tools.getTool('hris_list_employees');

assert(
employeeTool?.getAccountId() === 'initial-account-id',
'Account ID should match what was set'
);

// Setting account ID when getting tools (overrides toolset account ID)
const toolsWithOverride = toolset.getTools('hris_*', 'override-account-id');
const employeeToolWithOverride = toolsWithOverride.getTool('hris_list_employees');

assert(
employeeToolWithOverride?.getAccountId() === 'override-account-id',
'Account ID should match what was set'
);

// Set the account ID directly on the tool
employeeTool.setAccountId('direct-account-id');

assert(
employeeTool.getAccountId() === 'direct-account-id',
'Account ID should match what was set'
);
};

accountIdUsage();
4 changes: 2 additions & 2 deletions examples/ai-sdk-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const aiSdkIntegration = async (): Promise<void> => {
const tools = toolset.getTools('hris_*', accountId);

// Convert to AI SDK tools
const aiSdkTools = tools.toAISDKTools();
const aiSdkTools = tools.toAISDK();

// Use max steps to automatically call the tool if it's needed
const { text } = await generateText({
Expand All @@ -29,4 +29,4 @@ const aiSdkIntegration = async (): Promise<void> => {
assert(text.includes('Isac Newton'), 'Expected employee name to be included in the response');
};

aiSdkIntegration().catch(console.error);
aiSdkIntegration();
Loading