diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7c04d54..11d5c9b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: Deploy Docs to GitHub Pages on: push: - branches: [main] + branches: [feat/0.4.0] workflow_dispatch: permissions: diff --git a/.github/workflows/generate-from-python.yml b/.github/workflows/generate-from-python.yml new file mode 100644 index 0000000..6666521 --- /dev/null +++ b/.github/workflows/generate-from-python.yml @@ -0,0 +1,278 @@ +name: Generate Node SDK from Python + +on: + repository_dispatch: + types: [python-updated] + + # Manual trigger for testing + workflow_dispatch: + inputs: + before_sha: + description: 'Before commit SHA in Python SDK' + required: false + after_sha: + description: 'After commit SHA in Python SDK' + required: false + +jobs: + generate: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout Node SDK + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set commit SHAs + id: shas + run: | + if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then + echo "before_sha=${{ github.event.client_payload.before_sha }}" >> $GITHUB_OUTPUT + echo "after_sha=${{ github.event.client_payload.after_sha }}" >> $GITHUB_OUTPUT + echo "source_repo=${{ github.event.client_payload.source_repo }}" >> $GITHUB_OUTPUT + echo "commit_message=${{ github.event.client_payload.commit_message }}" >> $GITHUB_OUTPUT + echo "changed_files=${{ github.event.client_payload.changed_files }}" >> $GITHUB_OUTPUT + else + # Manual trigger + SOURCE_REPO="${{ github.repository_owner }}/videodb-python" + echo "source_repo=$SOURCE_REPO" >> $GITHUB_OUTPUT + echo "commit_message=Manual trigger" >> $GITHUB_OUTPUT + echo "changed_files=" >> $GITHUB_OUTPUT + + if [[ -n "${{ inputs.before_sha }}" ]]; then + echo "before_sha=${{ inputs.before_sha }}" >> $GITHUB_OUTPUT + else + echo "before_sha=" >> $GITHUB_OUTPUT + fi + + if [[ -n "${{ inputs.after_sha }}" ]]; then + echo "after_sha=${{ inputs.after_sha }}" >> $GITHUB_OUTPUT + else + echo "after_sha=main" >> $GITHUB_OUTPUT + fi + fi + + - name: Clone Python SDK + env: + GH_TOKEN: ${{ secrets.SDK_SYNC_PAT }} + run: | + SOURCE_REPO="${{ steps.shas.outputs.source_repo }}" + AFTER_SHA="${{ steps.shas.outputs.after_sha }}" + + echo "Cloning Python SDK from $SOURCE_REPO at $AFTER_SHA" + + # Clone the Python SDK into a subdirectory + git clone --depth 1 "https://x-access-token:${GH_TOKEN}@github.com/${SOURCE_REPO}.git" python-sdk + + # Checkout the specific commit if provided + if [[ -n "$AFTER_SHA" && "$AFTER_SHA" != "main" ]]; then + cd python-sdk + git fetch --depth 1 origin "$AFTER_SHA" + git checkout "$AFTER_SHA" + cd .. + fi + + # Remove .git directory - makes it READ-ONLY reference, impossible to push + rm -rf python-sdk/.git + + echo "Python SDK cloned as read-only reference" + ls -la python-sdk/videodb/ + + - name: Fetch Python SDK diff + id: diff + env: + GH_TOKEN: ${{ secrets.SDK_SYNC_PAT }} + run: | + SOURCE_REPO="${{ steps.shas.outputs.source_repo }}" + BEFORE_SHA="${{ steps.shas.outputs.before_sha }}" + AFTER_SHA="${{ steps.shas.outputs.after_sha }}" + + echo "Fetching diff from $SOURCE_REPO" + echo "Before: $BEFORE_SHA" + echo "After: $AFTER_SHA" + + # Fetch the diff + if [[ -n "$BEFORE_SHA" ]]; then + gh api \ + -H "Accept: application/vnd.github.v3.diff" \ + "/repos/$SOURCE_REPO/compare/${BEFORE_SHA}...${AFTER_SHA}" \ + > python.diff 2>/dev/null || echo "Could not fetch diff" + else + echo "No before SHA available" + touch python.diff + fi + + echo "Diff size: $(wc -l < python.diff) lines" + + - name: Run Codex + uses: openai/codex-action@v1 + with: + openai-api-key: ${{ secrets.OPENAI_API_KEY }} + model: o4-mini + sandbox: workspace-write + prompt: | + You are updating the VideoDB Node SDK to match changes made to the Python SDK. + + ## Python SDK Available (READ-ONLY) + + The complete Python SDK is available at `./python-sdk/videodb/` for you to read. + You have FULL ACCESS to read any Python file to understand the complete context. + + ⚠️ IMPORTANT: The `python-sdk/` directory is READ-ONLY for reference. + - DO NOT write, modify, or create any files in `python-sdk/` + - DO NOT attempt to commit or push changes to the Python SDK + - ONLY read from `python-sdk/` to understand the source code + - ONLY write to the Node SDK files in `src/` + + ## Git Diff of Python SDK Changes + + The following diff shows what changed in the Python SDK: + + ```diff + $(cat python.diff) + ``` + + ## File Mapping: Python SDK → Node SDK + + The Python SDK files map to Node SDK files as follows: + + | Python File | Node File | Description | + |-------------|-----------|-------------| + | `videodb/client.py` | `src/core/connection.ts` | Main client/connection class | + | `videodb/collection.py` | `src/core/collection.ts` | Collection operations | + | `videodb/video.py` | `src/core/video.ts` | Video class and methods | + | `videodb/audio.py` | `src/core/audio.ts` | Audio class and methods | + | `videodb/image.py` | `src/core/image.ts` | Image class and methods | + | `videodb/scene.py` | `src/core/scene.ts` | Scene class | + | `videodb/shot.py` | `src/core/shot.ts` | Shot class | + | `videodb/timeline.py` | `src/core/timeline.ts` | Timeline class | + | `videodb/search.py` | `src/core/search/` | Search functionality | + | `videodb/_constants.py` | `src/constants.ts` | Constants and enums | + | `videodb/_utils/` | `src/utils/` | Utility functions | + | Type definitions | `src/types/` | TypeScript interfaces | + + ## Your Task + + 1. **Identify which Python files changed** in the diff above + 2. **Read the FULL Python file** from `./python-sdk/videodb/` to understand: + - The complete context of the change + - What class/enum/function the change belongs to + - The surrounding code structure + 3. **Find the corresponding Node SDK file** using the mapping table + 4. **Read the Node SDK file** to understand current patterns + 5. **Replicate the exact same changes** in the Node SDK file: + - If a new method was added in Python, add the equivalent method in Node + - If a constant was added, add it to the same location in constants.ts + - If a new class was added, create the corresponding TypeScript class + - If parameters were added/changed, update the Node version accordingly + + IMPORTANT: Always read the full Python source file, not just the diff, to understand WHERE and HOW to add the code. + + ## Translation Rules + + | Python | TypeScript | + |--------|------------| + | `str` | `string` | + | `int`, `float` | `number` | + | `bool` | `boolean` | + | `None` | `null` or `undefined` | + | `Optional[X]` | `X \| undefined` or `X?` | + | `List[X]` | `X[]` | + | `Dict[str, X]` | `Record` | + | `def method(self, ...)` | `async method(...): Promise<...>` | + | `@dataclass class Foo` | `interface Foo { ... }` | + | `snake_case` | `camelCase` | + | `UPPER_SNAKE_CASE` | `UPPER_SNAKE_CASE` (keep same for constants) | + + ## Node SDK Patterns + + - Methods that call the API should be `async` and return `Promise` + - Use the existing `this.connection.post()` / `this.connection.get()` patterns + - Types go in `src/types/` directory + - Exports are managed in `src/index.ts` + + ## Important + + - Do NOT modify `package.json`, `version.ts`, or version numbers + - Do NOT change existing exports unless adding new ones + - Do NOT refactor unrelated code + - ONLY implement what the diff indicates was added/changed + - Convert `snake_case` to `camelCase` for methods and variables + - Keep `UPPER_SNAKE_CASE` for constants + + - name: Check for changes and create PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Save diff content for PR body before cleanup + DIFF_CONTENT=$(head -200 python.diff 2>/dev/null || echo "No diff available") + + # Clean up temporary files - DO NOT commit these + rm -f python.diff + rm -rf python-sdk + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Check if there are changes + if git diff --quiet && git diff --staged --quiet; then + echo "No changes generated by Codex" + exit 0 + fi + + # Create branch + BRANCH="auto/python-sync-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH" + git add -A + + # Commit + COMMIT_MSG="${{ steps.shas.outputs.commit_message }}" + git commit -m "feat: sync with Python SDK + + Source: ${{ steps.shas.outputs.source_repo }}@${{ steps.shas.outputs.after_sha }} + Original commit: ${COMMIT_MSG} + + Generated by OpenAI Codex" + + # Push + git push origin "$BRANCH" + + # Create PR + gh pr create \ + --title "feat: sync with Python SDK" \ + --body "## Summary + + Automated SDK update to match Python SDK changes. + + **Source**: \`${{ steps.shas.outputs.source_repo }}\` + **Commit**: \`${{ steps.shas.outputs.after_sha }}\` + **Original message**: ${{ steps.shas.outputs.commit_message }} + + ## Changes + + This PR was automatically generated by Codex based on the following Python SDK diff: + +
+ Python SDK Diff + + \`\`\`diff + ${DIFF_CONTENT} + \`\`\` + +
+ + ## Review Checklist + + - [ ] TypeScript types are correct + - [ ] Async/await patterns match existing code + - [ ] camelCase naming convention followed + - [ ] No breaking changes introduced + - [ ] Tests pass locally + + --- + *Generated by [OpenAI Codex](https://github.com/openai/codex)*" diff --git a/src/constants.ts b/src/constants.ts index a54fed9..0527d5d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,6 +36,8 @@ export const ApiPath = { scenes: 'scenes', timeline: 'timeline', frame: 'frame', + /** Test assets */ + test_assets: 'test-assets', } as const; export const ResponseStatus = { diff --git a/src/core/collection.ts b/src/core/collection.ts index 49cb4ec..0b42a89 100644 --- a/src/core/collection.ts +++ b/src/core/collection.ts @@ -183,6 +183,22 @@ export class Collection implements ICollection { }); }; + /** + * Delete the test asset. + * @param testAssetId - ID of the test asset to delete + * @returns A promise that resolves when delete is successful + * @throws an error if the request fails + */ + public deleteTestAsset = async (testAssetId: string) => { + if (!testAssetId.trim()) { + throw new VideodbError('Test asset ID cannot be empty'); + } + return await this.#vhttp.delete>( + [ApiPath.test_assets, testAssetId], + { params: { collection_id: this.meta.id } } + ); + }; + /** * @param filePath - absolute path to a file * @param callbackUrl- [optional] A url that will be called once upload is finished