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
38 changes: 29 additions & 9 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
id-token: write

jobs:
ci:
typos:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand All @@ -25,36 +25,56 @@ jobs:
- name: Setup Nix
uses: ./.github/actions/setup-nix

- name: Run Typo Check
run: nix develop --command typos .

lint:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Nix
uses: ./.github/actions/setup-nix
- name: Run Lint
run: nix develop --command pnpm run lint

build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Setup Nix
uses: ./.github/actions/setup-nix

- name: Run Build
run: nix develop --command pnpm run build

- name: Run Tests
if: github.ref != 'refs/heads/main'
run: nix develop --command pnpm test

coverage:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Nix
uses: ./.github/actions/setup-nix
- name: Run Tests with Coverage
if: github.ref == 'refs/heads/main'
run: nix develop --command pnpm run coverage

- name: Create Coverage Badge
if: github.ref == 'refs/heads/main'
uses: jaywcjlove/coverage-badges-cli@bd6ccbf422c0ed54c01f283019fd2bc648f58541 # v2.2.0
with:
source: coverage/coverage-summary.json
output: coverage/badges.svg

- name: Upload coverage artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
path: coverage

deploy-coverage:
if: github.ref == 'refs/heads/main'
needs: ci
needs: coverage
runs-on: ubuntu-latest
environment:
name: github-pages
Expand Down
10 changes: 9 additions & 1 deletion .oxlintrc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
"node": true,
},
// Ignore patterns
"ignorePatterns": ["dist", "node_modules", ".claude", "tmp", "*.log", "/nix/store/**"],
"ignorePatterns": [
"dist",
"node_modules",
".claude",
"tmp",
"*.log",
"/nix/store/**",
"CHANGELOG.md",
],
// Rule categories - strict base configuration
// - correctness (196 rules): Catch definite bugs and errors
// - suspicious (47 rules): Flag potentially problematic patterns
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ You can use the `dryRun` option to return the api arguments from a tool call wit
```typescript
import { StackOneToolSet } from "@stackone/ai";

// Initialise the toolset
// Initialize the toolset
const toolset = new StackOneToolSet({
baseUrl: "https://api.stackone.com",
});
Expand Down
2 changes: 1 addition & 1 deletion examples/ai-sdk-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ if (!apiKey) {
const accountId = 'your-bamboohr-account-id';

const aiSdkIntegration = async (): Promise<void> => {
// Initialise StackOne
// Initialize StackOne
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand Down
4 changes: 2 additions & 2 deletions examples/anthropic-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if (!apiKey) {
const accountId = 'your-hris-account-id';

const anthropicIntegration = async (): Promise<void> => {
// Initialise StackOne
// Initialize StackOne
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand All @@ -29,7 +29,7 @@ const anthropicIntegration = async (): Promise<void> => {
});
const anthropicTools = tools.toAnthropic();

// Initialise Anthropic client
// Initialize Anthropic client
const anthropic = new Anthropic();

// Create a message with tool calls
Expand Down
2 changes: 1 addition & 1 deletion examples/claude-agent-sdk-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if (!apiKey) {
const accountId = 'your-bamboohr-account-id';

const claudeAgentSdkIntegration = async (): Promise<void> => {
// Initialise StackOne
// Initialize StackOne
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand Down
2 changes: 1 addition & 1 deletion examples/fetch-tools-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ if ((typeof globalThis.Bun as any) !== 'undefined') {
}

const spinner = clack.spinner();
spinner.start('Initialising StackOne client...');
spinner.start('Initializing StackOne client...');

const toolset = new StackOneToolSet({
apiKey,
Expand Down
6 changes: 3 additions & 3 deletions examples/meta-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const accountId = 'your-bamboohr-account-id';
const metaToolsWithAISDK = async (): Promise<void> => {
console.log('🔍 Example 1: Dynamic tool discovery with AI SDK\n');

// Initialise StackOne toolset
// Initialize StackOne toolset
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand Down Expand Up @@ -64,7 +64,7 @@ const metaToolsWithOpenAI = async (): Promise<void> => {
apiKey: process.env.OPENAI_API_KEY,
});

// Initialise StackOne toolset
// Initialize StackOne toolset
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand Down Expand Up @@ -118,7 +118,7 @@ const metaToolsWithOpenAI = async (): Promise<void> => {
const directMetaToolUsage = async (): Promise<void> => {
console.log('\n🛠️ Example 3: Direct meta tool usage\n');

// Initialise toolset
// Initialize toolset
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand Down
2 changes: 1 addition & 1 deletion examples/openai-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('openai-integration example e2e', () => {
expect(openAITools[0]).toHaveProperty('type', 'function');
expect(openAITools[0]).toHaveProperty('function');

// Initialise OpenAI client
// Initialize OpenAI client
const openai = new OpenAI();

// Create a chat completion with tool calls
Expand Down
4 changes: 2 additions & 2 deletions examples/openai-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if (!apiKey) {
const accountId = 'your-bamboohr-account-id';

const openaiIntegration = async (): Promise<void> => {
// Initialise StackOne
// Initialize StackOne
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand All @@ -27,7 +27,7 @@ const openaiIntegration = async (): Promise<void> => {
const tools = await toolset.fetchTools();
const openAITools = tools.toOpenAI();

// Initialise OpenAI client
// Initialize OpenAI client
const openai = new OpenAI();

// Create a chat completion with tool calls
Expand Down
2 changes: 1 addition & 1 deletion examples/openai-responses-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('openai-responses-integration example e2e', () => {
expect(Array.isArray(openAIResponsesTools)).toBe(true);
expect(openAIResponsesTools.length).toBeGreaterThan(0);

// Initialise OpenAI client
// Initialize OpenAI client
const openai = new OpenAI();

// Create a response with tool calls using the Responses API
Expand Down
4 changes: 2 additions & 2 deletions examples/openai-responses-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if (!apiKey) {
const accountId = 'your-stackone-account-id';

const openaiResponsesIntegration = async (): Promise<void> => {
// Initialise StackOne
// Initialize StackOne
const toolset = new StackOneToolSet({ accountId });

// Fetch tools via MCP
Expand All @@ -26,7 +26,7 @@ const openaiResponsesIntegration = async (): Promise<void> => {
});
const openAIResponsesTools = tools.toOpenAIResponses();

// Initialise OpenAI client
// Initialize OpenAI client
const openai = new OpenAI();

// Create a response with tool calls using the Responses API
Expand Down
2 changes: 1 addition & 1 deletion examples/tanstack-ai-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ if (!apiKey) {
const accountId = 'your-bamboohr-account-id';

const tanstackAiIntegration = async (): Promise<void> => {
// Initialise StackOne
// Initialize StackOne
const toolset = new StackOneToolSet({
accountId,
baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com',
Expand Down
6 changes: 6 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# runtime
nodejs_24
pnpm_10

# formatting and linting tools
similarity
nixfmt-rfc-style
typos
typos-lsp
];

shellHook = ''
Expand Down
2 changes: 2 additions & 0 deletions lefthook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ pre-commit:

pre-push:
jobs:
- name: typos
run: typos --config typos.toml
- name: knip
run: pnpm run lint:knip
6 changes: 3 additions & 3 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export const DEFAULT_BASE_URL = 'https://api.stackone.com';
* Default weight for BM25 in hybrid BM25 + TF-IDF search.
*
* - alpha=0.2 means: 20% BM25 + 80% TF-IDF
* - This value was optimised through validation testing
* - This value was optimized through validation testing
* - Provides 10.8% improvement in tool discovery accuracy
* - Lower values favour BM25 scoring (better keyword matching)
* - Higher values favour TF-IDF scoring (better semantic matching)
* - Lower values favor BM25 scoring (better keyword matching)
* - Higher values favor TF-IDF scoring (better semantic matching)
*/
export const DEFAULT_HYBRID_ALPHA = 0.2;

Expand Down
26 changes: 13 additions & 13 deletions src/headers.test.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
import { normaliseHeaders } from './headers';
import { normalizeHeaders } from './headers';

describe('normaliseHeaders', () => {
describe('normalizeHeaders', () => {
it('returns empty object for undefined input', () => {
expect(normaliseHeaders(undefined)).toEqual({});
expect(normalizeHeaders(undefined)).toEqual({});
});

it('returns empty object for empty input', () => {
expect(normaliseHeaders({})).toEqual({});
expect(normalizeHeaders({})).toEqual({});
});

it('preserves string values', () => {
expect(normaliseHeaders({ foo: 'bar', baz: 'qux' })).toEqual({
expect(normalizeHeaders({ foo: 'bar', baz: 'qux' })).toEqual({
foo: 'bar',
baz: 'qux',
});
});

it('converts numbers to strings', () => {
expect(normaliseHeaders({ port: 8080, timeout: 30 })).toEqual({
expect(normalizeHeaders({ port: 8080, timeout: 30 })).toEqual({
port: '8080',
timeout: '30',
});
});

it('converts booleans to strings', () => {
expect(normaliseHeaders({ enabled: true, debug: false })).toEqual({
expect(normalizeHeaders({ enabled: true, debug: false })).toEqual({
enabled: 'true',
debug: 'false',
});
});

it('serialises objects to JSON', () => {
expect(normaliseHeaders({ config: { key: 'value' } })).toEqual({
it('serializes objects to JSON', () => {
expect(normalizeHeaders({ config: { key: 'value' } })).toEqual({
config: '{"key":"value"}',
});
});

it('serialises arrays to JSON', () => {
expect(normaliseHeaders({ tags: ['foo', 'bar'] })).toEqual({
it('serializes arrays to JSON', () => {
expect(normalizeHeaders({ tags: ['foo', 'bar'] })).toEqual({
tags: '["foo","bar"]',
});
});

it('skips null values', () => {
expect(normaliseHeaders({ foo: 'bar', baz: null })).toEqual({
expect(normalizeHeaders({ foo: 'bar', baz: null })).toEqual({
foo: 'bar',
});
});

it('handles mixed value types', () => {
expect(
normaliseHeaders({
normalizeHeaders({
string: 'text',
number: 42,
boolean: true,
Expand Down
8 changes: 4 additions & 4 deletions src/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ export const stackOneHeadersSchema = z.record(z.string(), z.string()).brand<'Sta
export type StackOneHeaders = z.infer<typeof stackOneHeadersSchema>;

/**
* Normalises header values from JsonObject to StackOneHeaders (branded type)
* Converts numbers and booleans to strings, and serialises objects to JSON
* Normalizes header values from JsonObject to StackOneHeaders (branded type)
* Converts numbers and booleans to strings, and serializes objects to JSON
*
* @param headers - Headers object with JSON value types
* @returns Normalised headers with string values only (branded type)
* @returns Normalized headers with string values only (branded type)
*/
export function normaliseHeaders(headers: JsonObject | undefined): StackOneHeaders {
export function normalizeHeaders(headers: JsonObject | undefined): StackOneHeaders {
if (!headers) return stackOneHeadersSchema.parse({});
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(headers)) {
Expand Down
Loading
Loading