diff --git a/.github/workflows/check-oas-updates.yml b/.github/workflows/check-oas-updates.yml index bce22331..9addc10f 100644 --- a/.github/workflows/check-oas-updates.yml +++ b/.github/workflows/check-oas-updates.yml @@ -2,8 +2,8 @@ name: Check OAS Updates on: schedule: - # Run at 00:00 UTC on Monday, Wednesday, and Friday - - cron: "0 0 * * 1,3,5" + # Run at 00:00 UTC every weekday + - cron: "0 0 * * 1-5" workflow_dispatch: # Allow manual triggering diff --git a/README.md b/README.md index b1f254c7..1eaa7eac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,58 @@ -# StackOne AI Node.js SDK +# StackOne AI SDK -StackOne AI provides a unified interface for accessing various SaaS tools through AI-friendly APIs. +> A unified interface for performing actions on SaaS tools through AI-friendly APIs. + +## Toolsets + +StackOne provides two toolsets: + +- `OpenAPIToolSet`: A toolset generated from supplied OpenAPI specifications +- `StackOneToolSet`: A toolset preloaded with StackOne APIs + +These toolsets provide functionality to filter, transform, and execute tools and have integrations to common AI Agent libraries. + +Under the hood the StackOneToolSet uses the same OpenAPIParser as the OpenAPIToolSet, but provides some convenience methods for using StackOne API keys and account IDs. + +### Workflow Planning + +While building agents you may find that your workflow is too complex for a general purpose agent. + +StackOne AI SDK provides access to a state of the art planning agent which allows you to create, cache, and execute complex workflows on [verticals supported by StackOne](https://www.stackone.com/integrations). + +For example, onboard a new hire from your ATS to your HRIS. + +```typescript +import { StackOneToolSet } from "@stackone/ai"; + +const toolset = new StackOneToolSet(); + +const onboardWorkflow = await toolset.plan({ + key: "custom_onboarding", + input: "Onboard the last new hire from Teamtailor to Workday", + model: "stackone-planner-latest", + tools: ["hris_*", "ats_*"], + accountIds: ["teamtailor_account_id", "workday_account_id"], + cache: true, // saves the plan to $HOME/.stackone/plans +}); + +// Execute the workflow +await onboardWorkflow.execute(); +``` + +Or use it as a tool in a larger agent (using AI SDK) + +```typescript +await generateText({ + model: openai("gpt-4o"), + prompt: "You are a workplace agent, onboard the latest hires to our systems", + tools: onboardWorkflow.toAISDK(), + maxSteps: 3, +}); +``` +> [!NOTE] +> The workflow planner is in closed beta and only available to design partners. Apply for the waitlist [here](https://www.stackone.com/demo). + +[View full example](examples/planning.ts) ## Installation @@ -15,18 +67,9 @@ yarn add @stackone/ai bun add @stackone/ai ``` -## Toolsets - -StackOne provides two toolsets: - -- `OpenAPIToolSet`: A toolset generated from OpenAPI specifications -- `StackOneToolSet`: A toolset for StackOne APIs - -Under the hood the StackOneToolSet uses the same OpenAPIParser as the OpenAPIToolSet, but provides some convenience methods for using StackOne API keys and account IDs. - ## Integrations -These integrations work with both the OpenAPIToolSet and StackOneToolSet. They make it super easy to use these APIs in your AI applications. +The OpenAPIToolSet and StackOneToolSet make it super easy to use these APIs as tools in your AI applications. ### OpenAI @@ -151,6 +194,8 @@ const toolsetWithHeaders = new OpenAPIToolSet({ }); ``` +[View full example](examples/openapi-toolset.ts) + ## StackOneToolSet The StackOneToolSet is an extension of the OpenAPIToolSet that adds some convenience methods for using StackOne API keys and account IDs and some other features. @@ -273,6 +318,8 @@ const toolset = new OpenAPIToolSet({ const result = await tool.execute({ user_id: "user123" }); ``` +[View full example](examples/openapi-transformations.ts) + ### Testing with dryRun You can use the `dryRun` option to return the api arguments from a tool call without making the actual api call: diff --git a/bun.lock b/bun.lock index a26a76be..305c9e5f 100644 --- a/bun.lock +++ b/bun.lock @@ -3,22 +3,19 @@ "workspaces": { "": { "name": "stackone-ai-ts", - "dependencies": { - "@ai-sdk/openai": "^1.1.14", - "ai": "^4.1.46", - "openai": "^4.85.4", - "zod": "^3.24.2", - }, "devDependencies": { + "@ai-sdk/openai": "^1.1.14", "@biomejs/biome": "^1.5.3", "@types/bun": "^1.2.4", "@types/json-schema": "^7.0.15", "@types/node": "^22.13.5", + "ai": "^4.1.46", "dotenv": "^16.3.1", "husky": "^9.1.7", "json-schema": "^0.4.0", "lint-staged": "^15.2.0", "mkdocs": "^0.0.1", + "openai": "^4.85.4", "openapi-types": "^12.1.3", "rimraf": "^6.0.1", "typescript": "^5.0.0", diff --git a/examples/planning.ts b/examples/planning.ts new file mode 100644 index 00000000..4b863460 --- /dev/null +++ b/examples/planning.ts @@ -0,0 +1,41 @@ +/** + * While building agents you may find that your workflow is too complex for a general purpose agent. + * + * StackOne AI SDK provides access to a state of the art planning agent which allows you to create, cache, and execute complex workflows on [verticals supported by StackOne](https://www.stackone.com/integrations). + * + * For example, onboard a new hire from your ATS to your HRIS. + */ + +import { StackOneToolSet } from '../src'; + +const toolset = new StackOneToolSet(); + +const onboardWorkflow = await toolset.plan({ + key: 'custom_onboarding', + input: 'Onboard the last new hire from Teamtailor to Workday', + model: 'stackone-planner-latest', + tools: ['hris_*', 'ats_*'], + accountIds: ['teamtailor_account_id', 'workday_account_id'], + cache: true, // saves the plan to $HOME/.stackone/plans +}); + +await onboardWorkflow.execute(); + +/** + * or use as part of a larger agent (using AI SDK by Vercel) + */ + +import { openai } from '@ai-sdk/openai'; +import { generateText } from 'ai'; + +await generateText({ + model: openai('gpt-4o'), + prompt: 'You are a workplace agent, onboard the latest hires to our systems', + tools: onboardWorkflow.toAISDK(), + maxSteps: 3, +}); + +/* + * The planning model is in closed beta and only available to design partners. + * Apply for the waitlist [here](https://www.stackone.com/demo). + */ diff --git a/package.json b/package.json index a8c7113b..45e2c431 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@stackone/ai", "version": "0.0.7", - "description": "Agents performing actions on your SaaS", + "description": "Tools to let agents perform actions on your SaaS", "module": "dist/index.js", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -33,6 +33,8 @@ "@types/bun": "^1.2.4", "@types/json-schema": "^7.0.15", "@types/node": "^22.13.5", + "@ai-sdk/openai": "^1.1.14", + "openai": "^4.85.4", "dotenv": "^16.3.1", "husky": "^9.1.7", "json-schema": "^0.4.0", @@ -40,13 +42,8 @@ "mkdocs": "^0.0.1", "openapi-types": "^12.1.3", "rimraf": "^6.0.1", - "typescript": "^5.0.0" - }, - "dependencies": { - "@ai-sdk/openai": "^1.1.14", - "ai": "^4.1.46", - "openai": "^4.85.4", - "zod": "^3.24.2" + "typescript": "^5.0.0", + "ai": "^4.1.46" }, "peerDependencies": { "typescript": "^5.0.0" diff --git a/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap b/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap index 11bbbdf4..ed99b712 100644 --- a/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap +++ b/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap @@ -12900,6 +12900,16 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "name": "end_half_day", "type": "string", }, + { + "location": "body", + "name": "time_off_policy_id", + "type": "string", + }, + { + "location": "body", + "name": "reason", + "type": "object", + }, { "location": "body", "name": "passthrough", @@ -12958,6 +12968,28 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "nullable": true, "type": "object", }, + "reason": { + "nullable": true, + "properties": { + "id": { + "description": "Unique identifier", + "example": "8187e5da-dc77-475e-9949-af0f1fa4e4e3", + "nullable": true, + "type": "string", + }, + "name": { + "nullable": true, + "type": "string", + }, + "remote_id": { + "description": "Provider's unique identifier", + "example": "8187e5da-dc77-475e-9949-af0f1fa4e4e3", + "nullable": true, + "type": "string", + }, + }, + "type": "object", + }, "start_date": { "description": "The start date of the time off request", "example": "2021-01-01T01:01:01.000Z", @@ -13022,6 +13054,12 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 }, "type": "object", }, + "time_off_policy_id": { + "description": "The time off policy id associated with this time off request", + "example": "cx280928933", + "nullable": true, + "type": "string", + }, "type": { "description": "The type of the time off request", "nullable": true, @@ -16179,6 +16217,16 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "name": "end_half_day", "type": "string", }, + { + "location": "body", + "name": "time_off_policy_id", + "type": "string", + }, + { + "location": "body", + "name": "reason", + "type": "object", + }, { "location": "body", "name": "passthrough", @@ -16234,6 +16282,28 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "nullable": true, "type": "object", }, + "reason": { + "nullable": true, + "properties": { + "id": { + "description": "Unique identifier", + "example": "8187e5da-dc77-475e-9949-af0f1fa4e4e3", + "nullable": true, + "type": "string", + }, + "name": { + "nullable": true, + "type": "string", + }, + "remote_id": { + "description": "Provider's unique identifier", + "example": "8187e5da-dc77-475e-9949-af0f1fa4e4e3", + "nullable": true, + "type": "string", + }, + }, + "type": "object", + }, "start_date": { "description": "The start date of the time off request", "example": "2021-01-01T01:01:01.000Z", @@ -16298,6 +16368,12 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 }, "type": "object", }, + "time_off_policy_id": { + "description": "The time off policy id associated with this time off request", + "example": "cx280928933", + "nullable": true, + "type": "string", + }, "type": { "description": "The type of the time off request", "nullable": true, @@ -17346,7 +17422,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "properties": { "fields": { "description": "The comma separated list of fields that will be returned in the response (if empty, all fields are returned)", - "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,created_at,updated_at", + "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,time_off_policy_id,remote_time_off_policy_id,reason,created_at,updated_at", "nullable": true, "type": "string", }, @@ -17914,7 +17990,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "properties": { "fields": { "description": "The comma separated list of fields that will be returned in the response (if empty, all fields are returned)", - "example": "id,remote_id,name,description,type,updated_at,created_at", + "example": "id,remote_id,name,description,type,duration_unit,reasons,updated_at,created_at", "nullable": true, "type": "string", }, @@ -17982,7 +18058,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "properties": { "fields": { "description": "The comma separated list of fields that will be returned in the response (if empty, all fields are returned)", - "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,created_at,updated_at", + "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,time_off_policy_id,remote_time_off_policy_id,reason,created_at,updated_at", "nullable": true, "type": "string", }, @@ -19454,7 +19530,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "properties": { "fields": { "description": "The comma separated list of fields that will be returned in the response (if empty, all fields are returned)", - "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,created_at,updated_at", + "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,time_off_policy_id,remote_time_off_policy_id,reason,created_at,updated_at", "nullable": true, "type": "string", }, @@ -20611,7 +20687,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "properties": { "fields": { "description": "The comma separated list of fields that will be returned in the response (if empty, all fields are returned)", - "example": "id,remote_id,name,description,type,updated_at,created_at", + "example": "id,remote_id,name,description,type,duration_unit,reasons,updated_at,created_at", "nullable": true, "type": "string", }, @@ -20730,7 +20806,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "properties": { "fields": { "description": "The comma separated list of fields that will be returned in the response (if empty, all fields are returned)", - "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,created_at,updated_at", + "example": "id,remote_id,employee_id,remote_employee_id,approver_id,remote_approver_id,status,type,start_date,end_date,start_half_day,end_half_day,duration,time_off_policy_id,remote_time_off_policy_id,reason,created_at,updated_at", "nullable": true, "type": "string", }, @@ -35007,6 +35083,16 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "name": "end_half_day", "type": "string", }, + { + "location": "body", + "name": "time_off_policy_id", + "type": "string", + }, + { + "location": "body", + "name": "reason", + "type": "object", + }, { "location": "body", "name": "passthrough", @@ -35065,6 +35151,28 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "nullable": true, "type": "object", }, + "reason": { + "nullable": true, + "properties": { + "id": { + "description": "Unique identifier", + "example": "8187e5da-dc77-475e-9949-af0f1fa4e4e3", + "nullable": true, + "type": "string", + }, + "name": { + "nullable": true, + "type": "string", + }, + "remote_id": { + "description": "Provider's unique identifier", + "example": "8187e5da-dc77-475e-9949-af0f1fa4e4e3", + "nullable": true, + "type": "string", + }, + }, + "type": "object", + }, "start_date": { "description": "The start date of the time off request", "example": "2021-01-01T01:01:01.000Z", @@ -35129,6 +35237,12 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 }, "type": "object", }, + "time_off_policy_id": { + "description": "The time off policy id associated with this time off request", + "example": "cx280928933", + "nullable": true, + "type": "string", + }, "type": { "description": "The type of the time off request", "nullable": true, diff --git a/src/toolsets/stackone.ts b/src/toolsets/stackone.ts index 66d2deb1..9f28836c 100644 --- a/src/toolsets/stackone.ts +++ b/src/toolsets/stackone.ts @@ -14,6 +14,18 @@ export interface StackOneToolSetConfig extends BaseToolSetConfig { accountId?: string; } +/** + * Configuration for workflow + */ +export interface WorkflowConfig { + key: string; + input: string; + model: string; + tools: string[]; + accountIds: string[]; + cache?: boolean; +} + /** * Class for loading StackOne tools from the OAS directory */ @@ -121,6 +133,15 @@ export class StackOneToolSet extends ToolSet { return this.getTools(filterPattern, headers); } + /** + * Plan a workflow + * @param config Configuration object containing workflow details + * @returns Workflow object + */ + plan(_: WorkflowConfig): Promise { + throw new Error('Not implemented yet'); + } + /** * Load tools from the OAS directory */