Repository navigation
docs(aimlapi): list AI/ML API in the Popular Providers sidebar #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Post a patience comment on PRs from external contributors that add | |
| # integration docs (hosted MDX pages or external listing YAML rows), and | |
| # apply the `integration` label to those PRs. | |
| # | |
| # NOTE: Uses `pull_request_target` so it can comment on PRs from forks. | |
| # The script only reads changed files from the GitHub API and checks org | |
| # membership — no untrusted code is executed. | |
| name: External Integration PR Comment | |
| on: | |
| pull_request_target: | |
| types: [opened, ready_for_review, synchronize] | |
| jobs: | |
| integration-comment: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| if: ${{ !github.event.pull_request.draft }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.ORG_MEMBERSHIP_APP_ID }} | |
| private-key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }} | |
| - name: Comment on external new integration PRs | |
| uses: actions/github-script@v8 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token }} | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const pr = context.payload.pull_request; | |
| const author = pr.user.login; | |
| if (pr.user.type === 'Bot') { | |
| console.log(`Skipping bot author ${author}`); | |
| return; | |
| } | |
| const integrationPrefixes = [ | |
| 'src/oss/python/integrations/', | |
| 'src/oss/javascript/integrations/', | |
| ]; | |
| const externalListingPath = 'scripts/data/integration_external_docs.yaml'; | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner, | |
| repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }); | |
| const newIntegrationFiles = files.filter((file) => { | |
| if (file.status !== 'added') return false; | |
| if (!integrationPrefixes.some((prefix) => file.filename.startsWith(prefix))) { | |
| return false; | |
| } | |
| const basename = file.filename.split('/').pop(); | |
| if (basename === 'TEMPLATE.mdx') return false; | |
| return true; | |
| }); | |
| const hasExternalListingChange = files.some( | |
| (file) => file.filename === externalListingPath | |
| ); | |
| if (newIntegrationFiles.length === 0 && !hasExternalListingChange) { | |
| console.log('No integration contribution files found; skipping'); | |
| return; | |
| } | |
| if (newIntegrationFiles.length > 0) { | |
| console.log( | |
| `Found ${newIntegrationFiles.length} new integration file(s): ` + | |
| newIntegrationFiles.map((f) => f.filename).join(', ') | |
| ); | |
| } | |
| if (hasExternalListingChange) { | |
| console.log(`Found changes to ${externalListingPath}`); | |
| } | |
| const internalBots = new Set(['open-swe[bot]']); | |
| let isExternal; | |
| if (internalBots.has(author)) { | |
| isExternal = false; | |
| } else { | |
| try { | |
| const membership = await github.rest.orgs.getMembershipForUser({ | |
| org: 'langchain-ai', | |
| username: author, | |
| }); | |
| isExternal = membership.data.state !== 'active'; | |
| } catch (error) { | |
| if (error.status === 404) { | |
| isExternal = true; | |
| } else { | |
| console.error('Error checking membership:', error); | |
| isExternal = true; | |
| } | |
| } | |
| } | |
| if (!isExternal) { | |
| console.log(`Author ${author} is internal; skipping`); | |
| return; | |
| } | |
| const hasIntegrationLabel = pr.labels.some( | |
| (label) => label.name === 'integration' | |
| ); | |
| if (hasIntegrationLabel) { | |
| console.log(`PR #${pr.number} already has the integration label; skipping labeling`); | |
| } else { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: pr.number, | |
| labels: ['integration'], | |
| }); | |
| console.log(`Added integration label to PR #${pr.number}`); | |
| } | |
| const marker = '<!-- external-integration-contribution -->'; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, | |
| repo, | |
| issue_number: pr.number, | |
| per_page: 100, | |
| }); | |
| if (comments.some((comment) => comment.body.includes(marker))) { | |
| console.log('Comment already posted; skipping comment'); | |
| return; | |
| } | |
| const body = [ | |
| marker, | |
| `Thanks for contributing a new integration docs page, @${author}!`, | |
| '', | |
| 'As of July 28, 2026, we have changed how we accept external integrations submissions. Please review the [new integration submission process](https://docs.langchain.com/oss/python/contributing/publish-langchain#make-your-integration-discoverable) for more information.', | |
| '', | |
| 'We receive a large number of PRs and review them as quickly as we can. Please bear with us as we work through the queue.', | |
| '', | |
| 'If you have already tagged a maintainer on this PR, do not tag them again.', | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: pr.number, | |
| body, | |
| }); | |
| console.log(`Posted external integration comment on PR #${pr.number}`); |