From 48d500a4aab4fe2eaba16ac0948c70a5d787e1ed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:57:38 +0000 Subject: [PATCH] feat(codeapi): isolate default and stateful execution profiles (#3205) Source: ClickHouse/ai@8957c9b3607de2b82aa69d199a9c5750d0c9cc3d Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/open-sync-pr.yml | 4 +-- api/src/api/v2-session-binding.test.ts | 37 ++++++++++++++++++++++++++ api/src/validation.ts | 7 +++-- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ec8559..94e2286 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# Standalone CI for the public ClickHouse/code-interpreter repo, which is +# Standalone CI for the public LibreChat-AI/code-interpreter repo, which is # published from services/codeapi as snapshot commits (see # .github/workflows/publish-codeapi.yml in the monorepo). This file is # inert inside the monorepo — GitHub only runs workflows from the repo diff --git a/.github/workflows/open-sync-pr.yml b/.github/workflows/open-sync-pr.yml index 717cc97..f2132c0 100644 --- a/.github/workflows/open-sync-pr.yml +++ b/.github/workflows/open-sync-pr.yml @@ -26,8 +26,8 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - OPEN=$(gh api -X GET repos/ClickHouse/code-interpreter/pulls \ - -f state=open -f base=main -f head=ClickHouse:sync/main \ + OPEN=$(gh api -X GET "repos/${GITHUB_REPOSITORY}/pulls" \ + -f state=open -f base=main -f head="${GITHUB_REPOSITORY_OWNER}:sync/main" \ -f per_page=1 --jq length) if [ "$OPEN" -gt 0 ]; then echo "Sync PR already open." diff --git a/api/src/api/v2-session-binding.test.ts b/api/src/api/v2-session-binding.test.ts index 1e85100..0e53073 100644 --- a/api/src/api/v2-session-binding.test.ts +++ b/api/src/api/v2-session-binding.test.ts @@ -184,6 +184,43 @@ describe('per-request session binding', () => { } }); + test('a nameless utf8 inline source uses the default filename and remains runnable', async () => { + config.session_workspace_enabled = false; + config.require_execution_manifest = false; + + const originalPrime = Job.prototype.prime; + const originalExecute = Job.prototype.execute; + const originalCleanup = Job.prototype.cleanup; + + let primedName: string | undefined; + Job.prototype.prime = async function trackDefaultName(): Promise { + primedName = this.files[0]?.name; + }; + Job.prototype.execute = async function executeWithoutSandbox() { + return {} as Awaited>; + }; + Job.prototype.cleanup = async function cleanupWithoutFilesystem(): Promise {}; + + try { + const response = await fetch(`${baseUrl}/api/v2/execute`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + language: testLanguage, + version: testVersion, + files: [{ content: 'test' }], + }), + }); + + expect(response.status).toBe(200); + expect(primedName).toBe('file0.code'); + } finally { + Job.prototype.prime = originalPrime; + Job.prototype.execute = originalExecute; + Job.prototype.cleanup = originalCleanup; + } + }); + test('a post-prime failure still reports the workspace as dirty', async () => { config.session_workspace_enabled = true; config.require_execution_manifest = false; diff --git a/api/src/validation.ts b/api/src/validation.ts index 5b94457..a29f399 100644 --- a/api/src/validation.ts +++ b/api/src/validation.ts @@ -32,12 +32,15 @@ export function isDirkeep(name: string): boolean { * execute — leaving the rejected request's writes behind. */ export function hasRunnableSource( - files: Array<{ name: string; encoding?: string }>, + files: Array<{ name?: string; encoding?: string }>, language: string, ): boolean { if (language === 'file') return true; return files.some( - (file) => !isDirkeep(file.name) && (!file.encoding || file.encoding === 'utf8'), + /* Request files may omit `name`; Job normalizes those to `file${i}.code`, + * which is runnable and cannot be the .dirkeep sentinel. */ + (file) => (file.name === undefined || !isDirkeep(file.name)) + && (!file.encoding || file.encoding === 'utf8'), ); }