diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml deleted file mode 100644 index 6a27f1d21c61..000000000000 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Bug report -description: Report a bug with the AI SDK. -labels: ['bug'] -body: - - type: markdown - attributes: - value: | - This template is to report bugs. If you need help with your own project, feel free to [start a new thread in our discussions](https://github.com/vercel/ai/discussions). - - type: textarea - attributes: - label: Description - description: A detailed description of the issue that you are encountering with the AI SDK, and how other people can reproduce it. This includes helpful information such as the API you are using, the framework and AI provider. - placeholder: | - Reproduction steps... - validations: - required: true - - type: textarea - attributes: - label: Code example - description: Provide an example code snippet that has the problem. - placeholder: | - import { openai } from '@ai-sdk/openai'; - import { streamText } from 'ai'; - ... - - type: input - id: provider - attributes: - label: AI provider - description: The AI provider (e.g. `@ai-sdk/openai`) that you are using, and its version (e.g. `1.0.0`). - placeholder: | - @ai-sdk/openai v1.0.0 - - type: textarea - attributes: - label: Additional context - description: | - Any extra information that might help us investigate. diff --git a/.github/ISSUE_TEMPLATE/1.support_request.yml b/.github/ISSUE_TEMPLATE/1.support_request.yml new file mode 100644 index 000000000000..0ea8b98fd42d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.support_request.yml @@ -0,0 +1,27 @@ +name: Support Request +description: Report a bug, feature request or other issue with the AI SDK. +labels: ['support'] +body: + - type: markdown + attributes: + value: | + This template is ask for help regarding an issue that could be a bug or a feature request. + - type: textarea + attributes: + label: Description + description: A detailed description. Please include relevant information such as reproduction steps, code examples, and any other information that might help us understand the issue. + placeholder: | + Reproduction steps, code examples, background, etc... + validations: + required: true + - type: textarea + attributes: + label: AI SDK Version + description: Which version of the AI SDK are you using? + placeholder: | + Examples: + - ai: 4.1.2 + - @ai-sdk/react: 2.1.0 + - @ai-sdk/openai: 0.5.2 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2.feature_request.yml b/.github/ISSUE_TEMPLATE/2.feature_request.yml deleted file mode 100644 index 2c7355ea3698..000000000000 --- a/.github/ISSUE_TEMPLATE/2.feature_request.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Feature Request -description: Propose a new feature for the AI SDK. -labels: ['enhancement'] -body: - - type: markdown - attributes: - value: | - This template is to propose new features for the AI SDK. If you need help with your own project, feel free to [start a new thread in our discussions](https://github.com/vercel/ai/discussions). - - type: textarea - attributes: - label: Feature Description - description: A detailed description of the feature you are proposing for the SDK. - placeholder: | - Feature description... - validations: - required: true - - type: textarea - attributes: - label: Use Cases - description: Provide use cases where this feature would be beneficial. - placeholder: | - Use case... - - type: textarea - attributes: - label: Additional context - description: | - Any extra information that might help us understand your feature request. - placeholder: | - Additional context... diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..8e26c04c99a3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,51 @@ + + +## Background + + + +## Summary + + + +## Verification + + + +## Tasks + + + +- [ ] Tests have been added / updated (for bug fixes / features) +- [ ] Documentation has been added / updated (for bug fixes / features) +- [ ] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) +- [ ] Formatting issues have been fixed (run `pnpm prettier-fix` in the project root) + +## Future Work + + + +## Related Issues + + diff --git a/.github/workflows/actions/verify-changesets/index.js b/.github/workflows/actions/verify-changesets/index.js new file mode 100644 index 000000000000..fa21a3fb171b --- /dev/null +++ b/.github/workflows/actions/verify-changesets/index.js @@ -0,0 +1,137 @@ +import fs from 'node:fs/promises'; + +const BYPASS_LABELS = ['minor', 'major']; + +// check if current file is the entry point +if (import.meta.url.endsWith(process.argv[1])) { + // https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request + const pullRequestEvent = JSON.parse( + await fs.readFile(process.env.GITHUB_EVENT_PATH, 'utf-8'), + ); + + try { + const message = await verifyChangesets( + pullRequestEvent, + process.env, + fs.readFile, + ); + await fs.writeFile( + process.env.GITHUB_STEP_SUMMARY, + `## Changeset verification passed ✅\n\n${message || ''}`, + ); + } catch (error) { + // write error to summary + console.error(error.message); + await fs.writeFile( + process.env.GITHUB_STEP_SUMMARY, + `## Changeset verification failed ❌ + +${error.message}`, + ); + + if (error.path) { + await fs.appendFile( + process.env.GITHUB_STEP_SUMMARY, + `\n\nFile: \`${error.path}\``, + ); + } + + if (error.content) { + await fs.appendFile( + process.env.GITHUB_STEP_SUMMARY, + `\n\n\`\`\`yaml\n${error.content}\n\`\`\``, + ); + } + + process.exit(1); + } +} + +export async function verifyChangesets( + event, + env = process.env, + readFile = fs.readFile, +) { + // Skip check if pull request has "minor-release" label + const byPassLabel = event.pull_request.labels.find(label => + BYPASS_LABELS.includes(label.name), + ); + if (byPassLabel) { + return `Skipping changeset verification - "${byPassLabel.name}" label found`; + } + + // Iterate through all changed .changeset/*.md files + for (const path of env.CHANGED_FILES.trim().split(' ')) { + // ignore README.md file + if (path === '.changeset/README.md') continue; + + // Check if the file is a .changeset file + if (!/^\.changeset\/[a-z-]+\.md/.test(path)) { + throw Object.assign(new Error(`Invalid file - not a .changeset file`), { + path, + }); + } + + // find frontmatter + const content = await readFile(`../../../../${path}`, 'utf-8'); + const result = content.match(/---\n([\s\S]+?)\n---/); + if (!result) { + throw Object.assign( + new Error(`Invalid .changeset file - no frontmatter found`), + { + path, + content, + }, + ); + } + + const [frontmatter] = result; + + // Find version bump by package. `frontmatter` looks like this: + // + // ```yaml + // 'ai': patch + // '@ai-sdk/provider': patch + // ``` + const lines = frontmatter.split('\n').slice(1, -1); + const versionBumps = {}; + for (const line of lines) { + const [packageName, versionBump] = line.split(':').map(s => s.trim()); + if (!packageName || !versionBump) { + throw Object.assign( + new Error(`Invalid .changeset file - invalid frontmatter`, { + path, + content, + }), + ); + } + + // Check if packageName is already set + if (versionBumps[packageName]) { + throw Object.assign( + new Error( + `Invalid .changeset file - duplicate package name "${packageName}"`, + ), + { path, content }, + ); + } + + versionBumps[packageName] = versionBump; + } + + // check if any of the version bumps are not "patch" + const invalidVersionBumps = Object.entries(versionBumps).filter( + ([, versionBump]) => versionBump !== 'patch', + ); + + if (invalidVersionBumps.length > 0) { + throw Object.assign( + new Error( + `Invalid .changeset file - invalid version bump (only "patch" is allowed, see https://ai-sdk.dev/docs/migration-guides/versioning). To bypass, add one of the following labels: ${BYPASS_LABELS.join(', ')}`, + ), + + { path, content }, + ); + } + } +} diff --git a/.github/workflows/actions/verify-changesets/package.json b/.github/workflows/actions/verify-changesets/package.json new file mode 100644 index 000000000000..bff806df20e3 --- /dev/null +++ b/.github/workflows/actions/verify-changesets/package.json @@ -0,0 +1,8 @@ +{ + "name": "verify-changesets-action", + "private": true, + "type": "module", + "scripts": { + "test": "node --test test.js" + } +} diff --git a/.github/workflows/actions/verify-changesets/test.js b/.github/workflows/actions/verify-changesets/test.js new file mode 100644 index 000000000000..0c4e024f38ef --- /dev/null +++ b/.github/workflows/actions/verify-changesets/test.js @@ -0,0 +1,193 @@ +import assert from 'node:assert'; +import { mock, test } from 'node:test'; + +import { verifyChangesets } from './index.js'; + +test('happy path', async () => { + const event = { + pull_request: { + labels: [], + }, + }; + const env = { + CHANGED_FILES: '.changeset/some-happy-path.md', + }; + + const readFile = mock.fn(async path => { + return `---\nai: patch\n@ai-sdk/provider: patch\n---\n## Test changeset`; + }); + + await verifyChangesets(event, env, readFile); + + assert.strictEqual(readFile.mock.callCount(), 1); + assert.deepStrictEqual(readFile.mock.calls[0].arguments, [ + '../../../../.changeset/some-happy-path.md', + 'utf-8', + ]); +}); + +test('ignores .changeset/README.md', async () => { + const event = { + pull_request: { + labels: [], + }, + }; + const env = { + CHANGED_FILES: '.changeset/README.md', + }; + + const readFile = mock.fn(() => {}); + + await verifyChangesets(event, env, readFile); + + assert.strictEqual(readFile.mock.callCount(), 0); +}); + +test('invalid file - not a .changeset file', async () => { + const event = { + pull_request: { + labels: [], + }, + }; + const env = { + CHANGED_FILES: '.changeset/not-a-changeset-file.txt', + }; + + const readFile = mock.fn(() => {}); + + await assert.rejects( + () => verifyChangesets(event, env, readFile), + Object.assign(new Error('Invalid file - not a .changeset file'), { + path: '.changeset/not-a-changeset-file.txt', + }), + ); + + assert.strictEqual(readFile.mock.callCount(), 0); +}); + +test('invalid .changeset file - no frontmatter', async () => { + const event = { + pull_request: { + labels: [], + }, + }; + const env = { + CHANGED_FILES: '.changeset/invalid-changeset-file.md', + }; + + const readFile = mock.fn(async path => { + return 'frontmatter missing'; + }); + await assert.rejects( + () => verifyChangesets(event, env, readFile), + Object.assign(new Error('Invalid .changeset file - no frontmatter found'), { + path: '.changeset/invalid-changeset-file.md', + content: 'frontmatter missing', + }), + ); + assert.strictEqual(readFile.mock.callCount(), 1); + assert.deepStrictEqual(readFile.mock.calls[0].arguments, [ + '../../../../.changeset/invalid-changeset-file.md', + 'utf-8', + ]); +}); + +test('minor update', async () => { + const event = { + pull_request: { + labels: [], + }, + }; + const env = { + CHANGED_FILES: '.changeset/patch-update.md .changeset/minor-update.md', + }; + + const readFile = mock.fn(async path => { + if (path.endsWith('patch-update.md')) { + return `---\nai: patch\n---\n## Test changeset`; + } + + return `---\n@ai-sdk/provider: minor\n---\n## Test changeset`; + }); + + await assert.rejects( + () => verifyChangesets(event, env, readFile), + Object.assign( + new Error( + `Invalid .changeset file - invalid version bump (only "patch" is allowed, see https://ai-sdk.dev/docs/migration-guides/versioning). To bypass, add one of the following labels: minor, major`, + ), + { + path: '.changeset/minor-update.md', + content: '---\n@ai-sdk/provider: minor\n---\n## Test changeset', + }, + ), + ); + + assert.strictEqual(readFile.mock.callCount(), 2); + assert.deepStrictEqual(readFile.mock.calls[0].arguments, [ + '../../../../.changeset/patch-update.md', + 'utf-8', + ]); + assert.deepStrictEqual(readFile.mock.calls[1].arguments, [ + '../../../../.changeset/minor-update.md', + 'utf-8', + ]); +}); + +test('minor update - with "minor" label', async () => { + const event = { + pull_request: { + labels: [ + { + name: 'minor', + }, + ], + }, + }; + const env = { + CHANGED_FILES: '.changeset/patch-update.md .changeset/minor-update.md', + }; + + const readFile = mock.fn(async path => { + if (path.endsWith('patch-update.md')) { + return `---\nai: patch\n---\n## Test changeset`; + } + + return `---\n@ai-sdk/provider: minor\n---\n## Test changeset`; + }); + + const message = await verifyChangesets(event, env, readFile); + assert.strictEqual( + message, + 'Skipping changeset verification - "minor" label found', + ); +}); + +test('major update - with "major" label', async () => { + const event = { + pull_request: { + labels: [ + { + name: 'major', + }, + ], + }, + }; + const env = { + CHANGED_FILES: '.changeset/patch-update.md .changeset/major-update.md', + }; + + const readFile = mock.fn(async path => { + if (path.endsWith('patch-update.md')) { + return `---\nai: patch\n---\n## Test changeset`; + } + + return `---\n@ai-sdk/provider: major\n---\n## Test changeset`; + }); + + const message = await verifyChangesets(event, env, readFile); + assert.strictEqual( + message, + 'Skipping changeset verification - "major" label found', + ); +}); diff --git a/.github/workflows/assign-team-pull-request.yml b/.github/workflows/assign-team-pull-request.yml new file mode 100644 index 000000000000..ac500348e9a0 --- /dev/null +++ b/.github/workflows/assign-team-pull-request.yml @@ -0,0 +1,22 @@ +name: Assign Team Pull Requests to Author + +on: + pull_request: + types: [opened] + +permissions: + pull-requests: write + +jobs: + assign: + runs-on: ubuntu-latest + # Only assign pull requests by team members, ignore pull requests from forks + if: github.event.pull_request.head.repo.full_name == github.repository + steps: + - uses: actions/checkout@v4 + - name: Assign pull request to author + run: gh pr edit $PULL_REQUEST_URL --add-assignee $AUTHOR_LOGIN + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }} + AUTHOR_LOGIN: ${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7de754472594..f6db8988b3ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [v4] pull_request: - branches: [main] + branches: [v4] jobs: test: @@ -23,7 +23,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9.12.3 + version: 10.11.0 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 42a185321759..3b287e799a45 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -2,9 +2,9 @@ name: Quality on: push: - branches: [main] + branches: [v4] pull_request: - branches: [main] + branches: [v4] jobs: prettier: @@ -17,7 +17,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9.12.3 + version: 10.11.0 - name: Use Node.js 22 uses: actions/setup-node@v4 @@ -41,7 +41,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9.12.3 + version: 10.11.0 - name: Use Node.js 22 uses: actions/setup-node@v4 @@ -65,7 +65,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9.12.3 + version: 10.11.0 - name: Use Node.js 22 uses: actions/setup-node@v4 diff --git a/.github/workflows/release-snapshot.yml b/.github/workflows/release-snapshot.yml index e58a9e0bfd8f..97e198e40651 100644 --- a/.github/workflows/release-snapshot.yml +++ b/.github/workflows/release-snapshot.yml @@ -45,7 +45,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9.12.3 + version: 10.11.0 - name: Setup Node.js 22 uses: actions/setup-node@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12eec2f442e2..d483f25c6545 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: branches: - - main + - v4 paths: - '.changeset/**' - '.github/workflows/release.yml' @@ -25,7 +25,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9.12.3 + version: 10.11.0 - name: Setup Node.js 22 uses: actions/setup-node@v4 diff --git a/.github/workflows/verify-changesets.yml b/.github/workflows/verify-changesets.yml new file mode 100644 index 000000000000..ead691c63b2f --- /dev/null +++ b/.github/workflows/verify-changesets.yml @@ -0,0 +1,38 @@ +# vercel/ai uses https://github.com/changesets/changesets for versioning and changelogs, +# but is not following semantic versioning. Instead, it uses `patch` for both fixes +# and features. It uses `minor` for "marketing releases", accompanied by a blog post and migration guide. +# This workflow verifies that all `.changeset/*.md` files use `patch` unless a `minor-release` label is present. +name: Verify Changesets + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + branches: + - v4 + paths: + - '.changeset/*.md' + +jobs: + verify-changesets: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - name: get all changed files from .changeset/*.md + id: changeset-files + run: | + echo "changed-files=$(git diff --diff-filter=dr --name-only $BASE_SHA -- '.changeset/*.md' | tr '\n' ' ')" >> $GITHUB_OUTPUT + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + - name: Verify changesets + if: steps.changeset-files.outputs.changed-files != '' + working-directory: .github/workflows/actions/verify-changesets + run: | + node index.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHANGED_FILES: ${{ steps.changeset-files.outputs.changed-files }} diff --git a/.npmrc b/.npmrc index 2a53e07c0db1..13562b7874fa 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,4 @@ auto-install-peers = true -link-workspace-packages = true \ No newline at end of file +link-workspace-packages = true +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*prettier* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 25fa6215fdd3..6cfcda8235f1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,9 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f54b62a9ff..1a47ed61b414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ You can find the changelogs for the individual packages in their respective `CHA - [@ai-sdk/openai-compatible](./packages/openai-compatible/CHANGELOG.md) - [@ai-sdk/perplexity](./packages/perplexity/CHANGELOG.md) - [@ai-sdk/togetherai](./packages/togetherai/CHANGELOG.md) +- [@ai-sdk/vercel](./packages/vercel/CHANGELOG.md) - [@ai-sdk/xai](./packages/xai/CHANGELOG.md) ### UI integrations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9644053c4a2..1f0dc9fcc63f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ To set up the repository on your local machine, follow these steps: 1. **Fork the Repository**: Make a copy of the repository to your GitHub account. 2. **Clone the Repository**: Clone the repository to your local machine, e.g. using `git clone`. 3. **Install Node**: If you haven't already, install Node v20. -4. **Install pnpm**: If you haven't already, install pnpm v9. You can do this by running `npm install -g pnpm@9` if you're using npm. Alternatively, if you're using Homebrew (Mac), you can run `brew install pnpm`. For more see [the pnpm site](https://pnpm.io/installation). +4. **Install pnpm**: If you haven't already, install pnpm v10. You can do this by running `npm install -g pnpm@10` if you're using npm. Alternatively, if you're using Homebrew (Mac), you can run `brew install pnpm`. For more see [the pnpm site](https://pnpm.io/installation). 5. **Install Dependencies**: Navigate to the project directory and run `pnpm install` to install all necessary dependencies. 6. **Build the Project**: Run `pnpm build` in the root to build all packages. @@ -70,10 +70,21 @@ Some packages like `ai` also have more details tests and watch mode, see their ` We greatly appreciate your pull requests. Here are the steps to submit them: 1. **Create a New Branch**: Initiate your changes in a fresh branch. It's recommended to name the branch in a manner that signifies the changes you're implementing. -2. **Commit Your Changes**: Ensure your commits are succinct and clear, detailing what modifications have been made and the reasons behind them. -3. **Push the Changes to Your GitHub Repository**: After committing your changes, push them to your GitHub repository. -4. **Open a Pull Request**: Propose your changes for review. Furnish a lucid title and description of your contributions. Make sure to link any relevant issues your PR resolves. -5. **Respond to Feedback**: Stay receptive to and address any feedback or alteration requests from the project maintainers. +2. **Add a patch changeset**: If you're updating any packages and want to ensure they're released, add a **patch** changeset to your branch by running `pnpm changeset` in the workspace root. + + - **Please do not use minor or major changesets**, we'll let you know when you need to use a different changeset type than patch. + - You don't need to select any of the `examples/*` packages, as they are not released. + +3. **Commit Your Changes**: Ensure your commits are succinct and clear, detailing what modifications have been made and the reasons behind them. We don't require a specific commit message format, but please be descriptive. +4. **Fix prettier issues**: Run `pnpm prettier-fix` to fix any formatting issues in your code. +5. **Push the Changes to Your GitHub Repository**: After committing your changes, push them to your GitHub repository. +6. **Open a Pull Request**: Propose your changes for review. Furnish a lucid title and description of your contributions. Make sure to link any relevant issues your PR resolves. We use the following PR title format: + + - `fix(package-name): description` or + - `feat(package-name): description` or + - `chore(package-name): description` etc. + +7. **Respond to Feedback**: Stay receptive to and address any feedback or alteration requests from the project maintainers. ### Fixing Prettier Issues diff --git a/README.md b/README.md index f90be20a312e..ad93ed3ce3ec 120000 --- a/README.md +++ b/README.md @@ -1 +1 @@ -packages/ai/README.md \ No newline at end of file +packages/ai/README.md diff --git a/content/docs/02-guides/01-rag-chatbot.mdx b/content/cookbook/00-guides/01-rag-chatbot.mdx similarity index 98% rename from content/docs/02-guides/01-rag-chatbot.mdx rename to content/cookbook/00-guides/01-rag-chatbot.mdx index 7735064289a6..c6cde9ade08d 100644 --- a/content/docs/02-guides/01-rag-chatbot.mdx +++ b/content/cookbook/00-guides/01-rag-chatbot.mdx @@ -1,11 +1,11 @@ --- -title: RAG Chatbot -description: Learn how to build a RAG Chatbot with the AI SDK and Next.js +title: RAG Agent +description: Learn how to build a RAG Agent with the AI SDK and Next.js --- # RAG Chatbot Guide -In this guide, you will learn how to build a retrieval-augmented generation (RAG) chatbot application. +In this guide, you will learn how to build a retrieval-augmented generation (RAG) Agent.