Skip to content

feat: sync Node SDK with Python SDK (gateway client, agent mode, fine-tuning listModels) - #166

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1786154723-sync-python-sdk
Open

feat: sync Node SDK with Python SDK (gateway client, agent mode, fine-tuning listModels)#166
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1786154723-sync-python-sdk

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the Node SDK back in line with the Python SDK, which has moved ahead since the last sync (node #157 ≈ python #199). Four gaps closed:

1. client.gateway (mirrors python vlmrun/client/gateway.py, #203) — new Gateway resource pointing the OpenAI SDK at {gatewayURL}/openai for third-party OCR / VLM models:

new Gateway(client, gatewayURL?)  // gatewayURL ?? VLMRUN_GATEWAY_URL ?? "https://gateway.vlm.run/v1"
gateway.completions      // openai.chat.completions
gateway.embeddings       // openai.embeddings
gateway.transcriptions   // openai.audio.transcriptions
await gateway.models()   // openai.models.list().data
await gateway.health()   // GET {gateway}/health, falling back to models() on 404/unreachable

Timeout follows the Python rule: only raise the floor to 600s when the caller is still at the client default (120s); an explicit timeout is left alone. openai stays an optional peer dep — a missing install throws DependencyError, same as client.agent. Exposed as client.gateway with a new gatewayURL option on VlmRunConfig.

2. AgentExecutionConfig.mode (python #205): "agent" | "program" | null, serialized as mode. Orion-2 only.

3. finetuning.listModels({skip, limit})GET models, returning string[] (python Finetuning.list_models).

4. Agent completions timeout floor (python #194): client.agent.completions now uses max(client.timeout ?? 600s, 600s) instead of passing the client timeout straight through — long agent runs were timing out at the 120s default. Two existing unit tests asserted the passthrough and were updated to the new behavior.

Also: gateway unit tests, README "Model Gateway" section, version bump to 1.4.0 (release-on-merge keys off the package.json version).

Not ported: skills.create_from_directory() / AgentSkill.from_directory(), which need a zip writer (the Node SDK only has a tar helper today) — left as a follow-up rather than pulling in a new dependency here.

Link to Devin session: https://app.devin.ai/sessions/d65d2dd4f01c492cad8ed440f922079e


Open in Devin Review

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread src/client/gateway.ts
Comment on lines +41 to +47
constructor(client: Client, baseURL?: string) {
this.client = client;
this._baseURL =
baseURL ??
(typeof process !== "undefined" ? process.env.VLMRUN_GATEWAY_URL : undefined) ??
DEFAULT_GATEWAY_URL;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 API key sent to an environment-controlled gateway URL without scheme validation

The gateway base URL is taken from the VLMRUN_GATEWAY_URL environment variable (or a caller-supplied override) with no validation, and the API key is then attached as a bearer token both to the OpenAI client (src/client/gateway.ts:94-99) and to the raw health probe (src/client/gateway.ts:185-187). If the variable points at a plain-HTTP or attacker-controlled host, the credential is transmitted to that endpoint in cleartext / to an unintended destination.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Runtime testing — gateway, agent mode, listModels

Tested the packed vlmrun@1.4.0 tarball from a scratch project against the real https://gateway.vlm.run/v1 service. 29/29 assertions passed. No VLMRUN_API_KEY was available, so live calls used the gateway's anonymous path (new VlmRun({ apiKey: "" })); a bogus key was used as a negative control and correctly got a 403.

Live gateway end-to-end + adversarial checks

gateway test evidence

  • gateway.models() → 8 real models incl. paddleocr/pp-ocrv6, zai-org/glm-ocr
  • gateway.completions.create OCR of tsla-8k.pdf (paddleocr/pp-ocrv6, document_dpi: 72) → 5007 chars containing FORM 8-K / Tesla in 641 ms; zai-org/glm-ocr also returned 5221 chars
  • gateway.embeddings.create (qwen/qwen3-vl-embedding-2b) → 1 vector, dim 2048
  • gateway.health()true. Note GET /v1/health on the real gateway returns 404, so this specifically exercises the models() fallback path
  • Negative control: identical OCR call with apiKey: "sk-bogus-123" → rejected 403 Invalid API Key, proving client.apiKey is forwarded
  • gatewayURL precedence (arg > VLMRUN_GATEWAY_URL > default), trailing-slash stripping, models()[] when data absent, health()false on an unreachable host
  • Timeout floor both directions: undefined/120000600000; 30000 and 900000 preserved (read off the constructed OpenAI client). Same matrix verified for client.agent.completions, plus a successful completions.create round trip
  • Optional peer dep: with node_modules/openai removed, client.gateway.completions throws DependencyError: OpenAI SDK is not installed
Packaging / exports

npm pack → installed into a fresh project with openai + typescript:

  • CJS require("vlmrun") and ESM import { VlmRun, Gateway, DEFAULT_GATEWAY_URL } from "vlmrun" both resolve; client.gateway instanceof Gateway, baseURL === "https://gateway.vlm.run/v1"
  • A .ts consumer using Gateway, DEFAULT_GATEWAY_URL and AgentExecutionConfig({mode:"agent"}) compiles with tsc --noEmit --strict against the installed dist/ types
mode serialization + listModels (wire-level)

Captured the actual request bodies via a local HTTP server used as baseURL:

POST /v1/agent/execute {"model":"vlmrun-orion-1:auto",...,"config":{"mode":"program"}}
POST /v1/agent/execute {"model":"vlmrun-orion-1:auto",...,"config":{}}

No mode: undefined leaks. new AgentExecutionConfig({}).toJSON(){}; {mode:"agent"}{"mode":"agent"}.

finetuning.listModels()GET /v1/fine_tuning/models?skip=0&limit=10 returning string[]; {skip:5,limit:2} respected; a non-array body rejects with TypeError: Expected array response.

Toolchain

npm install · npm run build · tsc --noEmit · npx jest tests/unit (284/284) · npm audit --audit-level moderate0 vulnerabilities — all clean.

Finding (not fixed here): ./scripts/lint fails immediately with ./node_modules/.bin/eslint: No such file or directory — eslint isn't in devDependencies. Pre-existing gap, but the lint script is unusable as-is. The tsc --noEmit half passes standalone.

Not verified (credential-gated)

VLMRUN_API_KEY was unavailable, so authenticated gateway calls, a real client.agent.completions call against api.vlm.run, and a real finetuning.listModels() against api.vlm.run were not exercised against production. The latter two were covered against a local stub server exercising the full SDK request path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants