diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index de1434c..c501d33 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,11 +4,16 @@ on: pull_request: branches: [main] +env: + TURBO_TELEMETRY_DISABLED: '1' + jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 - uses: pnpm/action-setup@v4 @@ -26,6 +31,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 - uses: pnpm/action-setup@v4 @@ -43,6 +50,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 - uses: pnpm/action-setup@v4 @@ -60,6 +69,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 - uses: pnpm/action-setup@v4 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 22a7a61..0927388 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -10,9 +10,11 @@ on: description: > Release mode: - "version" — Creates a "Version Packages" PR that bumps versions and - updates CHANGELOG.md based on pending changesets. Run this first. - - "publish" — Publishes the package to npm with provenance. Only run - this AFTER the Version Packages PR has been merged to main. + updates each package's CHANGELOG.md based on pending changesets. Run + this first. + - "publish" — Publishes every package whose on-disk version differs + from npm, with provenance. Only run this AFTER the Version Packages + PR has been merged to main. NOTE: @openrouter/agent releases must be coordinated with @openrouter/sdk because callModel changes are typically coupled with SDK type changes. required: true @@ -23,8 +25,8 @@ on: default: version dry-run: description: > - Dry run: If enabled, simulates the release without making any changes. - Useful for verifying what would happen before committing to a release. + Dry run: If enabled, simulates the publish without making any changes. + Useful for verifying what would publish before committing to a release. required: false type: boolean default: false @@ -34,6 +36,9 @@ permissions: pull-requests: write # For creating/updating the Version Packages PR id-token: write # For npm provenance signing +env: + TURBO_TELEMETRY_DISABLED: '1' + jobs: release: runs-on: ubuntu-latest @@ -55,39 +60,33 @@ jobs: - run: pnpm run test - - name: Check for pending changesets - if: github.event_name == 'push' - id: changesets - run: | - if ls .changeset/*.md 1>/dev/null 2>&1; then - echo "pending=true" >> "$GITHUB_OUTPUT" - else - echo "pending=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create Version Packages PR + - name: Version PR or Publish (changesets) if: > - (github.event_name == 'push' && steps.changesets.outputs.pending == 'true') || + github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'version') uses: changesets/action@v1 with: - title: "chore: version packages" - commit: "chore: version packages" - publish: pnpm publish --no-git-checks --provenance --access public + title: 'chore: version packages' + commit: 'chore: version packages' + version: pnpm exec changeset version + publish: pnpm exec changeset publish --no-git-checks env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish to npm - if: > - (github.event_name == 'push' && steps.changesets.outputs.pending == 'false') || - (github.event_name == 'workflow_dispatch' && inputs.mode == 'publish' && !inputs.dry-run) - run: pnpm publish --no-git-checks --provenance --access public + - name: Publish (workflow_dispatch, live) + if: github.event_name == 'workflow_dispatch' && inputs.mode == 'publish' && !inputs.dry-run + run: pnpm exec changeset publish --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish to npm (dry run) + # `changeset publish` has no native --dry-run. Fall back to pnpm's + # recursive dry-run, which simulates publishing every workspace package + # rather than only the ones changesets would pick. Output set may be + # wider than the live publish — but nothing is actually published, so + # this is only a diagnostic preview. + - name: Publish (workflow_dispatch, dry-run) if: github.event_name == 'workflow_dispatch' && inputs.mode == 'publish' && inputs.dry-run - run: pnpm publish --no-git-checks --provenance --access public --dry-run + run: pnpm -r publish --dry-run --access public --provenance --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 4764de2..71e06aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ esm/ node_modules/ .eslintcache -.worktrees/ \ No newline at end of file +.worktrees/ +.turbo/ +.env +.env.* +!.env.example \ No newline at end of file diff --git a/README.md b/README.md index 7882384..a2d226b 100644 --- a/README.md +++ b/README.md @@ -1,367 +1,36 @@ -# OpenRouter Agent (Beta) +# typescript-agent -Agent toolkit for building AI applications with [OpenRouter](https://openrouter.ai) — tool orchestration, streaming, multi-turn conversations, and format compatibility. +Monorepo for the OpenRouter TypeScript agent ecosystem. -> [!IMPORTANT] -> This SDK is currently in beta. There may be breaking changes between versions. -> We recommend pinning to a specific version in your `package.json`. +## Packages -## Installation - -```bash -# npm -npm install @openrouter/agent - -# pnpm -pnpm add @openrouter/agent - -# bun -bun add @openrouter/agent - -# yarn -yarn add @openrouter/agent -``` - -> [!NOTE] -> This package is ESM-only. If you are using CommonJS, you can use `await import('@openrouter/agent')`. - -## Quick Start - -```typescript -import OpenRouter from '@openrouter/sdk'; -import { callModel, tool } from '@openrouter/agent'; -import { z } from 'zod'; - -const client = new OpenRouter({ apiKey: 'YOUR_API_KEY' }); - -const weatherTool = tool({ - name: 'get_weather', - description: 'Get the current weather for a location', - inputSchema: z.object({ location: z.string() }), - execute: async ({ location }) => ({ - temperature: 72, - condition: 'sunny', - location, - }), -}); - -const result = callModel(client, { - model: 'openai/gpt-4o', - input: 'What is the weather in San Francisco?', - tools: [weatherTool] as const, -}); - -// Get the final text response (tools are auto-executed) -const text = await result.getText(); -console.log(text); -``` - -## Features - -### Multiple Response Consumption Patterns - -`callModel` returns a `ModelResult` that supports many ways to consume the response — all usable concurrently on the same result: - -```typescript -const result = callModel(client, { model, input, tools }); - -// Await the final text -const text = await result.getText(); - -// Await the full response with usage data -const response = await result.getResponse(); -console.log(response.usage); // { inputTokens, outputTokens, cost, ... } - -// Stream text deltas -for await (const delta of result.getTextStream()) { - process.stdout.write(delta); -} - -// Stream reasoning deltas -for await (const delta of result.getReasoningStream()) { - process.stdout.write(delta); -} - -// Stream tool execution events -for await (const event of result.getToolStream()) { - console.log(event); -} - -// Stream structured tool calls -for await (const toolCall of result.getToolCallsStream()) { - console.log(toolCall.name, toolCall.input); -} - -// Get all tool calls after completion -const toolCalls = await result.getToolCalls(); -``` - -### Tool Types - -The `tool()` factory creates type-safe tools with full Zod schema inference. Three tool types are supported: - -**Regular tools** — automatically executed by the agent loop: - -```typescript -const searchTool = tool({ - name: 'search', - description: 'Search the web', - inputSchema: z.object({ query: z.string() }), - outputSchema: z.object({ results: z.array(z.string()) }), - execute: async ({ query }) => { - const results = await performSearch(query); - return { results }; - }, -}); -``` - -**Generator tools** — stream intermediate events during execution: - -```typescript -const analysisTool = tool({ - name: 'analyze', - inputSchema: z.object({ data: z.string() }), - eventSchema: z.object({ progress: z.number() }), - outputSchema: z.object({ summary: z.string() }), - execute: async function* ({ data }) { - yield { progress: 0.5 }; - // ... processing ... - return { summary: 'Analysis complete' }; - }, -}); -``` - -**Manual tools** — reported to the model but not auto-executed (for human-in-the-loop flows): - -```typescript -const confirmTool = tool({ - name: 'confirm_action', - inputSchema: z.object({ action: z.string() }), - execute: false, -}); -``` - -### Stop Conditions - -Control when the agent loop stops executing tools: - -```typescript -import { callModel, stepCountIs, hasToolCall, maxTokensUsed, maxCost } from '@openrouter/agent'; - -const result = callModel(client, { - model: 'openai/gpt-4o', - input: 'Research this topic thoroughly', - tools: [searchTool, summarizeTool] as const, - // Single condition - stopWhen: stepCountIs(10), - // Or combine multiple (stops when ANY condition is met) - stopWhen: [stepCountIs(10), maxCost(0.50), hasToolCall('summarize')], -}); -``` - -Built-in stop conditions: - -| Condition | Description | -|---|---| -| `stepCountIs(n)` | Stop after `n` tool execution steps (default: 5) | -| `hasToolCall(name)` | Stop when a specific tool is called | -| `maxTokensUsed(n)` | Stop when total tokens exceed a threshold | -| `maxCost(dollars)` | Stop when total cost exceeds a dollar amount | -| `finishReasonIs(reason)` | Stop on a specific finish reason | - -### Tool Approval - -Gate tool execution with approval checks for sensitive operations: - -```typescript -const deleteTool = tool({ - name: 'delete_record', - inputSchema: z.object({ id: z.string() }), - requireApproval: true, // Always require approval - execute: async ({ id }) => { /* ... */ }, -}); - -// Or use a function for conditional approval -const writeTool = tool({ - name: 'write_file', - inputSchema: z.object({ path: z.string(), content: z.string() }), - requireApproval: ({ path }) => path.startsWith('/etc'), - execute: async ({ path, content }) => { /* ... */ }, -}); - -// Handle approvals at the callModel level -const result = callModel(client, { - model: 'openai/gpt-4o', - input: 'Delete record abc-123', - tools: [deleteTool] as const, - approveToolCalls: async (toolCalls) => { - // Return IDs of approved tool calls - return toolCalls.map(tc => tc.id); - }, -}); -``` - -### Tool Context - -Provide typed context data to tools without passing it through the model: - -```typescript -const dbTool = tool({ - name: 'query_db', - inputSchema: z.object({ sql: z.string() }), - contextSchema: z.object({ connectionString: z.string() }), - execute: async ({ sql }, ctx) => { - const db = connect(ctx?.context.connectionString); - return db.query(sql); - }, -}); - -const result = callModel(client, { - model: 'openai/gpt-4o', - input: 'List all users', - tools: [dbTool] as const, - context: { - query_db: { connectionString: 'postgres://localhost/mydb' }, - }, -}); -``` - -### Shared Context - -Share mutable state across all tools in a conversation: - -```typescript -const result = callModel(client, { - model: 'openai/gpt-4o', - input: 'Process these items', - tools: [toolA, toolB] as const, - sharedContextSchema: z.object({ processedIds: z.array(z.string()) }), - context: { - shared: { processedIds: [] }, - }, -}); -``` - -### Conversation State Management - -Persist multi-turn conversations with full state tracking: - -```typescript -import { createInitialState, callModel } from '@openrouter/agent'; - -// Start a conversation -let state = createInitialState(); - -// First turn -const result1 = callModel(client, { - model: 'openai/gpt-4o', - input: 'Search for TypeScript best practices', - tools: [searchTool] as const, - state, -}); - -// State is updated with messages, tool calls, and metadata -state = (await result1.getResponse()).state; - -// Continue the conversation -const result2 = callModel(client, { - model: 'openai/gpt-4o', - input: 'Now summarize what you found', - tools: [searchTool] as const, - state, -}); -``` - -### Dynamic Parameters Between Turns - -Adjust model parameters dynamically based on tool execution: - -```typescript -const searchTool = tool({ - name: 'search', - inputSchema: z.object({ query: z.string() }), - nextTurnParams: { - temperature: (input) => input.query.includes('creative') ? 0.9 : 0.1, - maxOutputTokens: () => 2000, - }, - execute: async ({ query }) => { /* ... */ }, -}); -``` - -### Format Compatibility - -Convert between OpenRouter and other message formats: - -```typescript -import { toClaudeMessage, fromClaudeMessages } from '@openrouter/agent'; -import { toChatMessage, fromChatMessages } from '@openrouter/agent'; - -// Anthropic Claude format -const claudeMsg = toClaudeMessage(openRouterMessage); -const orMessages = fromClaudeMessages(claudeMessages); - -// Standard Chat format -const chatMsg = toChatMessage(openRouterMessage); -const orMessages2 = fromChatMessages(chatMessages); -``` - -## Subpath Exports - -For tree-shaking or targeted imports, the package provides granular subpath exports: - -```typescript -import { callModel } from '@openrouter/agent/call-model'; -import { tool } from '@openrouter/agent/tool'; -import { ModelResult } from '@openrouter/agent/model-result'; -import { stepCountIs, maxCost } from '@openrouter/agent/stop-conditions'; -import { toClaudeMessage } from '@openrouter/agent/anthropic-compat'; -import { toChatMessage } from '@openrouter/agent/chat-compat'; -import { ToolContextStore } from '@openrouter/agent/tool-context'; -import { ToolEventBroadcaster } from '@openrouter/agent/tool-event-broadcaster'; -import { createInitialState } from '@openrouter/agent/conversation-state'; -``` +| Package | Path | Description | +| --- | --- | --- | +| [`@openrouter/agent`](./packages/agent) | `packages/agent` | Agent toolkit for building AI applications with OpenRouter — tool orchestration, streaming, multi-turn conversations, and format compatibility. | ## Development -```bash -# Install dependencies -pnpm install - -# Build -pnpm build - -# Run unit tests -pnpm test - -# Run end-to-end tests (requires OPENROUTER_API_KEY in .env) -pnpm test:e2e - -# Type check -pnpm typecheck +This repo uses [pnpm workspaces](https://pnpm.io/workspaces) and [Turborepo](https://turbo.build/) for task orchestration. -# Lint -pnpm lint +```bash +pnpm install # install all workspace dependencies +pnpm run lint # Biome check across all packages +pnpm run typecheck # TypeScript noEmit across all packages +pnpm run build # tsc across all packages +pnpm run test # vitest unit projects across all packages +pnpm run test:e2e # vitest e2e projects (requires OPENROUTER_API_KEY) ``` -### Running Tests - -Create a `.env` file with your OpenRouter API key: +Turbo caches task results; re-running an unchanged task replays cached output. -```env -OPENROUTER_API_KEY=sk-or-... -``` +## Releasing -Then run: +Releases are driven by [Changesets](https://github.com/changesets/changesets). Each PR that ships a user-visible change should include a changeset: ```bash -pnpm test # Unit tests -pnpm test:e2e # Integration tests (requires API key) +pnpm changeset ``` -## Documentation - -Full `callModel` documentation is available at [openrouter.ai/docs/sdks/typescript/call-model](https://openrouter.ai/docs/sdks/typescript/call-model/overview). - -## License +Pick the affected packages and bump type. On merge to `main`, the release workflow opens a "Version Packages" PR. Merging that PR publishes every package with a consumed changeset to npm — no republishing of unchanged packages. -Apache-2.0 +See [`.github/workflows/publish.yaml`](./.github/workflows/publish.yaml) for the full flow. diff --git a/biome.json b/biome.json index 2d7c7af..0cc250e 100644 --- a/biome.json +++ b/biome.json @@ -17,7 +17,10 @@ "**", "!**/*.json", "!!**/biome.json", - "!esm/**" + "!**/esm/**", + "!**/dist/**", + "!**/node_modules/**", + "!**/.worktrees/**" ] }, "formatter": { @@ -117,7 +120,7 @@ "overrides": [ { "includes": [ - "tests/**", + "**/tests/**", "**/*.test.ts", "**/*.spec.ts" ], diff --git a/package.json b/package.json index 2385261..de7179c 100644 --- a/package.json +++ b/package.json @@ -1,122 +1,19 @@ { - "name": "@openrouter/agent", - "version": "0.4.0", - "author": "OpenRouter", - "description": "Agent toolkit for building AI applications with OpenRouter — tool orchestration, streaming, multi-turn conversations, and format compatibility.", - "keywords": [ - "openrouter", - "agent", - "typescript", - "ai", - "tools", - "streaming", - "llm" - ], - "license": "Apache-2.0", + "name": "typescript-agent-monorepo", + "private": true, + "version": "0.0.0", "packageManager": "pnpm@10.22.0", - "type": "module", - "main": "./esm/index.js", - "exports": { - ".": { - "types": "./esm/index.d.ts", - "default": "./esm/index.js" - }, - "./call-model": { - "types": "./esm/inner-loop/call-model.d.ts", - "default": "./esm/inner-loop/call-model.js" - }, - "./tool-types": { - "types": "./esm/lib/tool-types.d.ts", - "default": "./esm/lib/tool-types.js" - }, - "./model-result": { - "types": "./esm/lib/model-result.d.ts", - "default": "./esm/lib/model-result.js" - }, - "./async-params": { - "types": "./esm/lib/async-params.d.ts", - "default": "./esm/lib/async-params.js" - }, - "./stop-conditions": { - "types": "./esm/lib/stop-conditions.d.ts", - "default": "./esm/lib/stop-conditions.js" - }, - "./tool": { - "types": "./esm/lib/tool.d.ts", - "default": "./esm/lib/tool.js" - }, - "./anthropic-compat": { - "types": "./esm/lib/anthropic-compat.d.ts", - "default": "./esm/lib/anthropic-compat.js" - }, - "./chat-compat": { - "types": "./esm/lib/chat-compat.d.ts", - "default": "./esm/lib/chat-compat.js" - }, - "./claude-constants": { - "types": "./esm/lib/claude-constants.d.ts", - "default": "./esm/lib/claude-constants.js" - }, - "./claude-type-guards": { - "types": "./esm/lib/claude-type-guards.d.ts", - "default": "./esm/lib/claude-type-guards.js" - }, - "./conversation-state": { - "types": "./esm/lib/conversation-state.d.ts", - "default": "./esm/lib/conversation-state.js" - }, - "./next-turn-params": { - "types": "./esm/lib/next-turn-params.d.ts", - "default": "./esm/lib/next-turn-params.js" - }, - "./stream-transformers": { - "types": "./esm/lib/stream-transformers.d.ts", - "default": "./esm/lib/stream-transformers.js" - }, - "./tool-context": { - "types": "./esm/lib/tool-context.d.ts", - "default": "./esm/lib/tool-context.js" - }, - "./tool-event-broadcaster": { - "types": "./esm/lib/tool-event-broadcaster.d.ts", - "default": "./esm/lib/tool-event-broadcaster.js" - }, - "./turn-context": { - "types": "./esm/lib/turn-context.d.ts", - "default": "./esm/lib/turn-context.js" - }, - "./openrouter": { - "types": "./esm/openrouter.d.ts", - "default": "./esm/openrouter.js" - }, - "./package.json": "./package.json" - }, - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/OpenRouterTeam/typescript-agent.git" - }, - "files": [ - "esm", - "package.json", - "README.md", - "LICENSE" - ], "scripts": { - "lint": "biome check src tests", - "lint:fix": "biome check --write src tests", - "build": "tsc", - "test": "vitest --run --project unit", - "test:e2e": "vitest --run --project e2e", - "test:watch": "vitest --watch --project unit", - "typecheck": "tsc --noEmit", - "compile": "tsc", + "build": "turbo run build", + "test": "turbo run test", + "test:e2e": "turbo run test:e2e", + "test:watch": "turbo run test:watch", + "lint": "turbo run lint", + "lint:fix": "biome check --write packages/*/src packages/*/tests", + "typecheck": "turbo run typecheck", + "changeset": "changeset", "prepare": "husky" }, - "dependencies": { - "@openrouter/sdk": "^0.12.12", - "zod": "^4.0.0" - }, "devDependencies": { "@biomejs/biome": "^2.4.10", "@changesets/changelog-github": "^0.6.0", @@ -124,13 +21,8 @@ "@types/node": "^22.13.12", "dotenv": "^16.4.7", "husky": "^9.1.7", + "turbo": "2.9.6", "typescript": "~5.8.3", "vitest": "^3.2.4" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "@openrouter/sdk", - "esbuild" - ] } } diff --git a/.npmignore b/packages/agent/.npmignore similarity index 54% rename from .npmignore rename to packages/agent/.npmignore index a8b3a8f..61f4b61 100644 --- a/.npmignore +++ b/packages/agent/.npmignore @@ -2,13 +2,7 @@ ./tests ./vitest.config.ts ./tsconfig.json -.github -.changeset .env .env.* .gitignore .npmignore -.prettierignore -.prettierrc -.prettierignore -.grit/ \ No newline at end of file diff --git a/CHANGELOG.md b/packages/agent/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to packages/agent/CHANGELOG.md diff --git a/packages/agent/README.md b/packages/agent/README.md new file mode 100644 index 0000000..7882384 --- /dev/null +++ b/packages/agent/README.md @@ -0,0 +1,367 @@ +# OpenRouter Agent (Beta) + +Agent toolkit for building AI applications with [OpenRouter](https://openrouter.ai) — tool orchestration, streaming, multi-turn conversations, and format compatibility. + +> [!IMPORTANT] +> This SDK is currently in beta. There may be breaking changes between versions. +> We recommend pinning to a specific version in your `package.json`. + +## Installation + +```bash +# npm +npm install @openrouter/agent + +# pnpm +pnpm add @openrouter/agent + +# bun +bun add @openrouter/agent + +# yarn +yarn add @openrouter/agent +``` + +> [!NOTE] +> This package is ESM-only. If you are using CommonJS, you can use `await import('@openrouter/agent')`. + +## Quick Start + +```typescript +import OpenRouter from '@openrouter/sdk'; +import { callModel, tool } from '@openrouter/agent'; +import { z } from 'zod'; + +const client = new OpenRouter({ apiKey: 'YOUR_API_KEY' }); + +const weatherTool = tool({ + name: 'get_weather', + description: 'Get the current weather for a location', + inputSchema: z.object({ location: z.string() }), + execute: async ({ location }) => ({ + temperature: 72, + condition: 'sunny', + location, + }), +}); + +const result = callModel(client, { + model: 'openai/gpt-4o', + input: 'What is the weather in San Francisco?', + tools: [weatherTool] as const, +}); + +// Get the final text response (tools are auto-executed) +const text = await result.getText(); +console.log(text); +``` + +## Features + +### Multiple Response Consumption Patterns + +`callModel` returns a `ModelResult` that supports many ways to consume the response — all usable concurrently on the same result: + +```typescript +const result = callModel(client, { model, input, tools }); + +// Await the final text +const text = await result.getText(); + +// Await the full response with usage data +const response = await result.getResponse(); +console.log(response.usage); // { inputTokens, outputTokens, cost, ... } + +// Stream text deltas +for await (const delta of result.getTextStream()) { + process.stdout.write(delta); +} + +// Stream reasoning deltas +for await (const delta of result.getReasoningStream()) { + process.stdout.write(delta); +} + +// Stream tool execution events +for await (const event of result.getToolStream()) { + console.log(event); +} + +// Stream structured tool calls +for await (const toolCall of result.getToolCallsStream()) { + console.log(toolCall.name, toolCall.input); +} + +// Get all tool calls after completion +const toolCalls = await result.getToolCalls(); +``` + +### Tool Types + +The `tool()` factory creates type-safe tools with full Zod schema inference. Three tool types are supported: + +**Regular tools** — automatically executed by the agent loop: + +```typescript +const searchTool = tool({ + name: 'search', + description: 'Search the web', + inputSchema: z.object({ query: z.string() }), + outputSchema: z.object({ results: z.array(z.string()) }), + execute: async ({ query }) => { + const results = await performSearch(query); + return { results }; + }, +}); +``` + +**Generator tools** — stream intermediate events during execution: + +```typescript +const analysisTool = tool({ + name: 'analyze', + inputSchema: z.object({ data: z.string() }), + eventSchema: z.object({ progress: z.number() }), + outputSchema: z.object({ summary: z.string() }), + execute: async function* ({ data }) { + yield { progress: 0.5 }; + // ... processing ... + return { summary: 'Analysis complete' }; + }, +}); +``` + +**Manual tools** — reported to the model but not auto-executed (for human-in-the-loop flows): + +```typescript +const confirmTool = tool({ + name: 'confirm_action', + inputSchema: z.object({ action: z.string() }), + execute: false, +}); +``` + +### Stop Conditions + +Control when the agent loop stops executing tools: + +```typescript +import { callModel, stepCountIs, hasToolCall, maxTokensUsed, maxCost } from '@openrouter/agent'; + +const result = callModel(client, { + model: 'openai/gpt-4o', + input: 'Research this topic thoroughly', + tools: [searchTool, summarizeTool] as const, + // Single condition + stopWhen: stepCountIs(10), + // Or combine multiple (stops when ANY condition is met) + stopWhen: [stepCountIs(10), maxCost(0.50), hasToolCall('summarize')], +}); +``` + +Built-in stop conditions: + +| Condition | Description | +|---|---| +| `stepCountIs(n)` | Stop after `n` tool execution steps (default: 5) | +| `hasToolCall(name)` | Stop when a specific tool is called | +| `maxTokensUsed(n)` | Stop when total tokens exceed a threshold | +| `maxCost(dollars)` | Stop when total cost exceeds a dollar amount | +| `finishReasonIs(reason)` | Stop on a specific finish reason | + +### Tool Approval + +Gate tool execution with approval checks for sensitive operations: + +```typescript +const deleteTool = tool({ + name: 'delete_record', + inputSchema: z.object({ id: z.string() }), + requireApproval: true, // Always require approval + execute: async ({ id }) => { /* ... */ }, +}); + +// Or use a function for conditional approval +const writeTool = tool({ + name: 'write_file', + inputSchema: z.object({ path: z.string(), content: z.string() }), + requireApproval: ({ path }) => path.startsWith('/etc'), + execute: async ({ path, content }) => { /* ... */ }, +}); + +// Handle approvals at the callModel level +const result = callModel(client, { + model: 'openai/gpt-4o', + input: 'Delete record abc-123', + tools: [deleteTool] as const, + approveToolCalls: async (toolCalls) => { + // Return IDs of approved tool calls + return toolCalls.map(tc => tc.id); + }, +}); +``` + +### Tool Context + +Provide typed context data to tools without passing it through the model: + +```typescript +const dbTool = tool({ + name: 'query_db', + inputSchema: z.object({ sql: z.string() }), + contextSchema: z.object({ connectionString: z.string() }), + execute: async ({ sql }, ctx) => { + const db = connect(ctx?.context.connectionString); + return db.query(sql); + }, +}); + +const result = callModel(client, { + model: 'openai/gpt-4o', + input: 'List all users', + tools: [dbTool] as const, + context: { + query_db: { connectionString: 'postgres://localhost/mydb' }, + }, +}); +``` + +### Shared Context + +Share mutable state across all tools in a conversation: + +```typescript +const result = callModel(client, { + model: 'openai/gpt-4o', + input: 'Process these items', + tools: [toolA, toolB] as const, + sharedContextSchema: z.object({ processedIds: z.array(z.string()) }), + context: { + shared: { processedIds: [] }, + }, +}); +``` + +### Conversation State Management + +Persist multi-turn conversations with full state tracking: + +```typescript +import { createInitialState, callModel } from '@openrouter/agent'; + +// Start a conversation +let state = createInitialState(); + +// First turn +const result1 = callModel(client, { + model: 'openai/gpt-4o', + input: 'Search for TypeScript best practices', + tools: [searchTool] as const, + state, +}); + +// State is updated with messages, tool calls, and metadata +state = (await result1.getResponse()).state; + +// Continue the conversation +const result2 = callModel(client, { + model: 'openai/gpt-4o', + input: 'Now summarize what you found', + tools: [searchTool] as const, + state, +}); +``` + +### Dynamic Parameters Between Turns + +Adjust model parameters dynamically based on tool execution: + +```typescript +const searchTool = tool({ + name: 'search', + inputSchema: z.object({ query: z.string() }), + nextTurnParams: { + temperature: (input) => input.query.includes('creative') ? 0.9 : 0.1, + maxOutputTokens: () => 2000, + }, + execute: async ({ query }) => { /* ... */ }, +}); +``` + +### Format Compatibility + +Convert between OpenRouter and other message formats: + +```typescript +import { toClaudeMessage, fromClaudeMessages } from '@openrouter/agent'; +import { toChatMessage, fromChatMessages } from '@openrouter/agent'; + +// Anthropic Claude format +const claudeMsg = toClaudeMessage(openRouterMessage); +const orMessages = fromClaudeMessages(claudeMessages); + +// Standard Chat format +const chatMsg = toChatMessage(openRouterMessage); +const orMessages2 = fromChatMessages(chatMessages); +``` + +## Subpath Exports + +For tree-shaking or targeted imports, the package provides granular subpath exports: + +```typescript +import { callModel } from '@openrouter/agent/call-model'; +import { tool } from '@openrouter/agent/tool'; +import { ModelResult } from '@openrouter/agent/model-result'; +import { stepCountIs, maxCost } from '@openrouter/agent/stop-conditions'; +import { toClaudeMessage } from '@openrouter/agent/anthropic-compat'; +import { toChatMessage } from '@openrouter/agent/chat-compat'; +import { ToolContextStore } from '@openrouter/agent/tool-context'; +import { ToolEventBroadcaster } from '@openrouter/agent/tool-event-broadcaster'; +import { createInitialState } from '@openrouter/agent/conversation-state'; +``` + +## Development + +```bash +# Install dependencies +pnpm install + +# Build +pnpm build + +# Run unit tests +pnpm test + +# Run end-to-end tests (requires OPENROUTER_API_KEY in .env) +pnpm test:e2e + +# Type check +pnpm typecheck + +# Lint +pnpm lint +``` + +### Running Tests + +Create a `.env` file with your OpenRouter API key: + +```env +OPENROUTER_API_KEY=sk-or-... +``` + +Then run: + +```bash +pnpm test # Unit tests +pnpm test:e2e # Integration tests (requires API key) +``` + +## Documentation + +Full `callModel` documentation is available at [openrouter.ai/docs/sdks/typescript/call-model](https://openrouter.ai/docs/sdks/typescript/call-model/overview). + +## License + +Apache-2.0 diff --git a/packages/agent/package.json b/packages/agent/package.json new file mode 100644 index 0000000..31abc8b --- /dev/null +++ b/packages/agent/package.json @@ -0,0 +1,122 @@ +{ + "name": "@openrouter/agent", + "version": "0.4.0", + "author": "OpenRouter", + "description": "Agent toolkit for building AI applications with OpenRouter — tool orchestration, streaming, multi-turn conversations, and format compatibility.", + "keywords": [ + "openrouter", + "agent", + "typescript", + "ai", + "tools", + "streaming", + "llm" + ], + "license": "Apache-2.0", + "type": "module", + "main": "./esm/index.js", + "exports": { + ".": { + "types": "./esm/index.d.ts", + "default": "./esm/index.js" + }, + "./call-model": { + "types": "./esm/inner-loop/call-model.d.ts", + "default": "./esm/inner-loop/call-model.js" + }, + "./tool-types": { + "types": "./esm/lib/tool-types.d.ts", + "default": "./esm/lib/tool-types.js" + }, + "./model-result": { + "types": "./esm/lib/model-result.d.ts", + "default": "./esm/lib/model-result.js" + }, + "./async-params": { + "types": "./esm/lib/async-params.d.ts", + "default": "./esm/lib/async-params.js" + }, + "./stop-conditions": { + "types": "./esm/lib/stop-conditions.d.ts", + "default": "./esm/lib/stop-conditions.js" + }, + "./tool": { + "types": "./esm/lib/tool.d.ts", + "default": "./esm/lib/tool.js" + }, + "./anthropic-compat": { + "types": "./esm/lib/anthropic-compat.d.ts", + "default": "./esm/lib/anthropic-compat.js" + }, + "./chat-compat": { + "types": "./esm/lib/chat-compat.d.ts", + "default": "./esm/lib/chat-compat.js" + }, + "./claude-constants": { + "types": "./esm/lib/claude-constants.d.ts", + "default": "./esm/lib/claude-constants.js" + }, + "./claude-type-guards": { + "types": "./esm/lib/claude-type-guards.d.ts", + "default": "./esm/lib/claude-type-guards.js" + }, + "./conversation-state": { + "types": "./esm/lib/conversation-state.d.ts", + "default": "./esm/lib/conversation-state.js" + }, + "./next-turn-params": { + "types": "./esm/lib/next-turn-params.d.ts", + "default": "./esm/lib/next-turn-params.js" + }, + "./stream-transformers": { + "types": "./esm/lib/stream-transformers.d.ts", + "default": "./esm/lib/stream-transformers.js" + }, + "./tool-context": { + "types": "./esm/lib/tool-context.d.ts", + "default": "./esm/lib/tool-context.js" + }, + "./tool-event-broadcaster": { + "types": "./esm/lib/tool-event-broadcaster.d.ts", + "default": "./esm/lib/tool-event-broadcaster.js" + }, + "./turn-context": { + "types": "./esm/lib/turn-context.d.ts", + "default": "./esm/lib/turn-context.js" + }, + "./openrouter": { + "types": "./esm/openrouter.d.ts", + "default": "./esm/openrouter.js" + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/OpenRouterTeam/typescript-agent.git", + "directory": "packages/agent" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "files": [ + "esm", + "package.json", + "README.md" + ], + "scripts": { + "lint": "biome check src tests", + "lint:fix": "biome check --write src tests", + "build": "tsc", + "test": "vitest --run --project unit", + "test:e2e": "vitest --run --project e2e", + "test:watch": "vitest --watch --project unit", + "typecheck": "tsc --noEmit", + "compile": "tsc" + }, + "dependencies": { + "@openrouter/sdk": "^0.12.12", + "zod": "^4.0.0" + } +} diff --git a/src/api-shape-helpers/claude-message.ts b/packages/agent/src/api-shape-helpers/claude-message.ts similarity index 100% rename from src/api-shape-helpers/claude-message.ts rename to packages/agent/src/api-shape-helpers/claude-message.ts diff --git a/src/index.ts b/packages/agent/src/index.ts similarity index 100% rename from src/index.ts rename to packages/agent/src/index.ts diff --git a/src/inner-loop/call-model.ts b/packages/agent/src/inner-loop/call-model.ts similarity index 100% rename from src/inner-loop/call-model.ts rename to packages/agent/src/inner-loop/call-model.ts diff --git a/src/lib/anthropic-compat.test.ts b/packages/agent/src/lib/anthropic-compat.test.ts similarity index 100% rename from src/lib/anthropic-compat.test.ts rename to packages/agent/src/lib/anthropic-compat.test.ts diff --git a/src/lib/anthropic-compat.ts b/packages/agent/src/lib/anthropic-compat.ts similarity index 100% rename from src/lib/anthropic-compat.ts rename to packages/agent/src/lib/anthropic-compat.ts diff --git a/src/lib/async-params.ts b/packages/agent/src/lib/async-params.ts similarity index 100% rename from src/lib/async-params.ts rename to packages/agent/src/lib/async-params.ts diff --git a/src/lib/chat-compat.test.ts b/packages/agent/src/lib/chat-compat.test.ts similarity index 100% rename from src/lib/chat-compat.test.ts rename to packages/agent/src/lib/chat-compat.test.ts diff --git a/src/lib/chat-compat.ts b/packages/agent/src/lib/chat-compat.ts similarity index 100% rename from src/lib/chat-compat.ts rename to packages/agent/src/lib/chat-compat.ts diff --git a/src/lib/claude-constants.ts b/packages/agent/src/lib/claude-constants.ts similarity index 100% rename from src/lib/claude-constants.ts rename to packages/agent/src/lib/claude-constants.ts diff --git a/src/lib/claude-type-guards.ts b/packages/agent/src/lib/claude-type-guards.ts similarity index 100% rename from src/lib/claude-type-guards.ts rename to packages/agent/src/lib/claude-type-guards.ts diff --git a/src/lib/conversation-state.ts b/packages/agent/src/lib/conversation-state.ts similarity index 100% rename from src/lib/conversation-state.ts rename to packages/agent/src/lib/conversation-state.ts diff --git a/src/lib/item-types.ts b/packages/agent/src/lib/item-types.ts similarity index 100% rename from src/lib/item-types.ts rename to packages/agent/src/lib/item-types.ts diff --git a/src/lib/model-result.ts b/packages/agent/src/lib/model-result.ts similarity index 100% rename from src/lib/model-result.ts rename to packages/agent/src/lib/model-result.ts diff --git a/src/lib/next-turn-params.test.ts b/packages/agent/src/lib/next-turn-params.test.ts similarity index 100% rename from src/lib/next-turn-params.test.ts rename to packages/agent/src/lib/next-turn-params.test.ts diff --git a/src/lib/next-turn-params.ts b/packages/agent/src/lib/next-turn-params.ts similarity index 100% rename from src/lib/next-turn-params.ts rename to packages/agent/src/lib/next-turn-params.ts diff --git a/src/lib/reusable-stream.ts b/packages/agent/src/lib/reusable-stream.ts similarity index 100% rename from src/lib/reusable-stream.ts rename to packages/agent/src/lib/reusable-stream.ts diff --git a/src/lib/stop-conditions.ts b/packages/agent/src/lib/stop-conditions.ts similarity index 100% rename from src/lib/stop-conditions.ts rename to packages/agent/src/lib/stop-conditions.ts diff --git a/src/lib/stream-transformers.ts b/packages/agent/src/lib/stream-transformers.ts similarity index 100% rename from src/lib/stream-transformers.ts rename to packages/agent/src/lib/stream-transformers.ts diff --git a/src/lib/stream-type-guards.ts b/packages/agent/src/lib/stream-type-guards.ts similarity index 100% rename from src/lib/stream-type-guards.ts rename to packages/agent/src/lib/stream-type-guards.ts diff --git a/src/lib/tool-context.ts b/packages/agent/src/lib/tool-context.ts similarity index 100% rename from src/lib/tool-context.ts rename to packages/agent/src/lib/tool-context.ts diff --git a/src/lib/tool-event-broadcaster.ts b/packages/agent/src/lib/tool-event-broadcaster.ts similarity index 100% rename from src/lib/tool-event-broadcaster.ts rename to packages/agent/src/lib/tool-event-broadcaster.ts diff --git a/src/lib/tool-executor.ts b/packages/agent/src/lib/tool-executor.ts similarity index 100% rename from src/lib/tool-executor.ts rename to packages/agent/src/lib/tool-executor.ts diff --git a/src/lib/tool-orchestrator.ts b/packages/agent/src/lib/tool-orchestrator.ts similarity index 100% rename from src/lib/tool-orchestrator.ts rename to packages/agent/src/lib/tool-orchestrator.ts diff --git a/src/lib/tool-types.ts b/packages/agent/src/lib/tool-types.ts similarity index 100% rename from src/lib/tool-types.ts rename to packages/agent/src/lib/tool-types.ts diff --git a/src/lib/tool.ts b/packages/agent/src/lib/tool.ts similarity index 100% rename from src/lib/tool.ts rename to packages/agent/src/lib/tool.ts diff --git a/src/lib/turn-context.ts b/packages/agent/src/lib/turn-context.ts similarity index 100% rename from src/lib/turn-context.ts rename to packages/agent/src/lib/turn-context.ts diff --git a/src/openrouter.ts b/packages/agent/src/openrouter.ts similarity index 100% rename from src/openrouter.ts rename to packages/agent/src/openrouter.ts diff --git a/tests/e2e/call-model-state.test.ts b/packages/agent/tests/e2e/call-model-state.test.ts similarity index 100% rename from tests/e2e/call-model-state.test.ts rename to packages/agent/tests/e2e/call-model-state.test.ts diff --git a/tests/e2e/call-model-tools.test.ts b/packages/agent/tests/e2e/call-model-tools.test.ts similarity index 100% rename from tests/e2e/call-model-tools.test.ts rename to packages/agent/tests/e2e/call-model-tools.test.ts diff --git a/tests/e2e/call-model.test.ts b/packages/agent/tests/e2e/call-model.test.ts similarity index 100% rename from tests/e2e/call-model.test.ts rename to packages/agent/tests/e2e/call-model.test.ts diff --git a/tests/e2e/multi-turn-tool-state.test.ts b/packages/agent/tests/e2e/multi-turn-tool-state.test.ts similarity index 100% rename from tests/e2e/multi-turn-tool-state.test.ts rename to packages/agent/tests/e2e/multi-turn-tool-state.test.ts diff --git a/tests/unit/assistant-message-images.test.ts b/packages/agent/tests/unit/assistant-message-images.test.ts similarity index 100% rename from tests/unit/assistant-message-images.test.ts rename to packages/agent/tests/unit/assistant-message-images.test.ts diff --git a/tests/unit/build-tool-call-stream.test.ts b/packages/agent/tests/unit/build-tool-call-stream.test.ts similarity index 100% rename from tests/unit/build-tool-call-stream.test.ts rename to packages/agent/tests/unit/build-tool-call-stream.test.ts diff --git a/tests/unit/conversation-state.test.ts b/packages/agent/tests/unit/conversation-state.test.ts similarity index 100% rename from tests/unit/conversation-state.test.ts rename to packages/agent/tests/unit/conversation-state.test.ts diff --git a/tests/unit/create-tool.test.ts b/packages/agent/tests/unit/create-tool.test.ts similarity index 100% rename from tests/unit/create-tool.test.ts rename to packages/agent/tests/unit/create-tool.test.ts diff --git a/tests/unit/get-items-stream-manual-tools.test.ts b/packages/agent/tests/unit/get-items-stream-manual-tools.test.ts similarity index 100% rename from tests/unit/get-items-stream-manual-tools.test.ts rename to packages/agent/tests/unit/get-items-stream-manual-tools.test.ts diff --git a/tests/unit/has-approval-tools.test-d.ts b/packages/agent/tests/unit/has-approval-tools.test-d.ts similarity index 100% rename from tests/unit/has-approval-tools.test-d.ts rename to packages/agent/tests/unit/has-approval-tools.test-d.ts diff --git a/tests/unit/manual-tool-calls-adversarial.test.ts b/packages/agent/tests/unit/manual-tool-calls-adversarial.test.ts similarity index 100% rename from tests/unit/manual-tool-calls-adversarial.test.ts rename to packages/agent/tests/unit/manual-tool-calls-adversarial.test.ts diff --git a/tests/unit/openrouter.test.ts b/packages/agent/tests/unit/openrouter.test.ts similarity index 100% rename from tests/unit/openrouter.test.ts rename to packages/agent/tests/unit/openrouter.test.ts diff --git a/tests/unit/schema-sanitization.test.ts b/packages/agent/tests/unit/schema-sanitization.test.ts similarity index 100% rename from tests/unit/schema-sanitization.test.ts rename to packages/agent/tests/unit/schema-sanitization.test.ts diff --git a/tests/unit/server-tool-stream-narrowing.test-d.ts b/packages/agent/tests/unit/server-tool-stream-narrowing.test-d.ts similarity index 100% rename from tests/unit/server-tool-stream-narrowing.test-d.ts rename to packages/agent/tests/unit/server-tool-stream-narrowing.test-d.ts diff --git a/tests/unit/server-tool.test.ts b/packages/agent/tests/unit/server-tool.test.ts similarity index 100% rename from tests/unit/server-tool.test.ts rename to packages/agent/tests/unit/server-tool.test.ts diff --git a/tests/unit/shared-context.test.ts b/packages/agent/tests/unit/shared-context.test.ts similarity index 100% rename from tests/unit/shared-context.test.ts rename to packages/agent/tests/unit/shared-context.test.ts diff --git a/tests/unit/tool-context.test.ts b/packages/agent/tests/unit/tool-context.test.ts similarity index 100% rename from tests/unit/tool-context.test.ts rename to packages/agent/tests/unit/tool-context.test.ts diff --git a/tests/unit/tool-event-broadcaster.test.ts b/packages/agent/tests/unit/tool-event-broadcaster.test.ts similarity index 100% rename from tests/unit/tool-event-broadcaster.test.ts rename to packages/agent/tests/unit/tool-event-broadcaster.test.ts diff --git a/tests/unit/tool-executor-return.test.ts b/packages/agent/tests/unit/tool-executor-return.test.ts similarity index 100% rename from tests/unit/tool-executor-return.test.ts rename to packages/agent/tests/unit/tool-executor-return.test.ts diff --git a/tests/unit/tool-type-compat.test.ts b/packages/agent/tests/unit/tool-type-compat.test.ts similarity index 100% rename from tests/unit/tool-type-compat.test.ts rename to packages/agent/tests/unit/tool-type-compat.test.ts diff --git a/tests/unit/turn-end-race-condition.test.ts b/packages/agent/tests/unit/turn-end-race-condition.test.ts similarity index 100% rename from tests/unit/turn-end-race-condition.test.ts rename to packages/agent/tests/unit/turn-end-race-condition.test.ts diff --git a/tests/utils/schema-test-helpers.ts b/packages/agent/tests/utils/schema-test-helpers.ts similarity index 100% rename from tests/utils/schema-test-helpers.ts rename to packages/agent/tests/utils/schema-test-helpers.ts diff --git a/packages/agent/tsconfig.json b/packages/agent/tsconfig.json new file mode 100644 index 0000000..51bb3ed --- /dev/null +++ b/packages/agent/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "esm" + }, + "include": ["src"], + "exclude": ["node_modules", "esm"] +} diff --git a/vitest.config.ts b/packages/agent/vitest.config.ts similarity index 94% rename from vitest.config.ts rename to packages/agent/vitest.config.ts index efca4ae..2963346 100644 --- a/vitest.config.ts +++ b/packages/agent/vitest.config.ts @@ -2,7 +2,7 @@ import { config } from 'dotenv'; import { defineConfig } from 'vitest/config'; config({ - path: new URL('.env', import.meta.url), + path: new URL('../../.env', import.meta.url), }); export default defineConfig({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 781287c..4963f56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,13 +7,6 @@ settings: importers: .: - dependencies: - '@openrouter/sdk': - specifier: ^0.12.12 - version: 0.12.12 - zod: - specifier: ^4.0.0 - version: 4.3.6 devDependencies: '@biomejs/biome': specifier: ^2.4.10 @@ -33,6 +26,9 @@ importers: husky: specifier: ^9.1.7 version: 9.1.7 + turbo: + specifier: 2.9.6 + version: 2.9.6 typescript: specifier: ~5.8.3 version: 5.8.3 @@ -40,6 +36,15 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/node@22.19.15) + packages/agent: + dependencies: + '@openrouter/sdk': + specifier: ^0.12.12 + version: 0.12.12 + zod: + specifier: ^4.0.0 + version: 4.3.6 + packages: '@babel/runtime@7.29.2': @@ -474,6 +479,36 @@ packages: cpu: [x64] os: [win32] + '@turbo/darwin-64@2.9.6': + resolution: {integrity: sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.6': + resolution: {integrity: sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.6': + resolution: {integrity: sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.6': + resolution: {integrity: sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.6': + resolution: {integrity: sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.6': + resolution: {integrity: sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A==} + cpu: [arm64] + os: [win32] + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -952,6 +987,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + turbo@2.9.6: + resolution: {integrity: sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==} + hasBin: true + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -1447,6 +1486,24 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.1': optional: true + '@turbo/darwin-64@2.9.6': + optional: true + + '@turbo/darwin-arm64@2.9.6': + optional: true + + '@turbo/linux-64@2.9.6': + optional: true + + '@turbo/linux-arm64@2.9.6': + optional: true + + '@turbo/windows-64@2.9.6': + optional: true + + '@turbo/windows-arm64@2.9.6': + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1895,6 +1952,15 @@ snapshots: tr46@0.0.3: {} + turbo@2.9.6: + optionalDependencies: + '@turbo/darwin-64': 2.9.6 + '@turbo/darwin-arm64': 2.9.6 + '@turbo/linux-64': 2.9.6 + '@turbo/linux-arm64': 2.9.6 + '@turbo/windows-64': 2.9.6 + '@turbo/windows-arm64': 2.9.6 + typescript@5.8.3: {} undici-types@6.21.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..eb019e8 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - "packages/*" + +onlyBuiltDependencies: + - "@openrouter/sdk" + - esbuild diff --git a/tsconfig.json b/tsconfig.base.json similarity index 92% rename from tsconfig.json rename to tsconfig.base.json index a01be63..b6d1f9d 100644 --- a/tsconfig.json +++ b/tsconfig.base.json @@ -14,7 +14,6 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "outDir": "esm", "strict": true, "allowUnusedLabels": false, @@ -33,7 +32,5 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true - }, - "include": ["src"], - "exclude": ["node_modules"] + } } diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..3afa862 --- /dev/null +++ b/turbo.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://turbo.build/schema.json", + "ui": "stream", + "globalDependencies": ["tsconfig.base.json"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", "src/**", "tsconfig.json"], + "outputs": ["esm/**"] + }, + "typecheck": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", "src/**", "tests/**", "tsconfig.json"], + "outputs": [] + }, + "lint": { + "inputs": ["$TURBO_DEFAULT$", "src/**", "tests/**"], + "outputs": [] + }, + "test": { + "dependsOn": ["^build"], + "inputs": [ + "$TURBO_DEFAULT$", + "src/**", + "tests/unit/**", + "vitest.config.ts", + "tsconfig.json" + ], + "outputs": [] + }, + "test:watch": { + "cache": false, + "persistent": true, + "env": ["OPENROUTER_API_KEY"] + }, + "test:e2e": { + "dependsOn": ["^build"], + "inputs": [ + "$TURBO_DEFAULT$", + "src/**", + "tests/e2e/**", + "vitest.config.ts", + "tsconfig.json" + ], + "outputs": [], + "cache": false, + "env": ["OPENROUTER_API_KEY"] + } + } +}