[STG-1756] forward Vertex model config#2160
Conversation
🦋 Changeset detectedLatest commit: e1515c5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✱ Stainless preview builds for stagehandThis PR will update the ✅ stagehand-go studio · code
|
There was a problem hiding this comment.
2 issues found across 15 files
Confidence score: 3/5
- There is a concrete regression risk in
packages/core/lib/v3/api.ts:goto()does not route per-callmodelthroughprepareModelConfig(), so string models may behave differently thanact/extract/observeand lead to incorrect model resolution at runtime. packages/server-v3/src/lib/header.tshas a medium-severity precedence issue wherebody.modelNamecan leak into requests even when an objectoptions.model/agentConfig.modelis intended to be authoritative, which can cause unintended model selection.- Given the 6–7/10 severity and reasonably high confidence, this is a moderate user-impact risk rather than a merge-blocking failure; merge is possible, but a targeted fix would materially reduce regression chance.
- Pay close attention to
packages/core/lib/v3/api.tsandpackages/server-v3/src/lib/header.ts- model normalization and fallback precedence can silently select the wrong model.
Architecture diagram
sequenceDiagram
participant C as Client App
participant V3 as V3 Core (v3.ts)
participant APIClient as StagehandAPIClient (api.ts)
participant Server as Server-v3 (Fastify)
participant SessionStore as InMemorySessionStore
participant Browser
Note over C,Browser: Constructor Model Config Forwarding (Happy Path)
C->>V3: new Stagehand({ modelName, modelClientOptions... })
V3->>V3: store modelName + clientOptions
C->>V3: stagehand.init()
V3->>APIClient: init({ modelName, modelApiKey, defaultModelConfig })
Note over APIClient: defaultModelConfig = merged modelName + clientOptions
APIClient->>APIClient: store defaultModelConfig internally
C->>V3: stagehand.navigate(url, options?)
V3->>APIClient: goto(url, options)
alt options.model provided per-call
APIClient->>APIClient: use options.model (prepared)
else no per-call model AND defaultModelConfig exists
APIClient->>APIClient: use getDefaultModelConfig()
end
APIClient->>Server: POST /navigate { url, options.model }
Server->>Server: getRequestModelConfig(body)
Note over Server: parse body.options.model or body.agentConfig.model
Server->>Server: delete options.model before page.goto
alt session not yet started
Server->>SessionStore: getOrCreateStagehand(sessionId, requestModelConfig)
SessionStore->>SessionStore: create V3Options from requestModelConfig + modelApiKey fallback
SessionStore-->>Server: stagehand instance
end
Server->>Browser: page.goto(url, strippedOptions)
Browser-->>Server: navigation result
Server-->>APIClient: response
APIClient-->>V3: result
V3-->>C: result
Note over C,Browser: Act/Extract/Observe (same pattern)
C->>V3: stagehand.act({ input, options? })
V3->>APIClient: act({ input, options })
alt options.model provided per-call
APIClient->>APIClient: use options.model (prepared)
else no per-call model AND defaultModelConfig exists
APIClient->>APIClient: use getDefaultModelConfig()
end
APIClient->>Server: POST /act { options.model }
Server->>Server: getRequestModelConfig(body) — extracts model config
Server-->>APIClient: response
APIClient-->>V3: result
V3-->>C: result
Note over C,Browser: Agent Execute (same pattern)
C->>V3: stagehand.agentExecute(agentConfig, input)
V3->>APIClient: agentExecute(agentConfig, input)
alt agentConfig.model provided
APIClient->>APIClient: prepareModelConfig(agentConfig.model)
else no model AND defaultModelConfig exists
APIClient->>APIClient: use getDefaultModelConfig()
end
APIClient->>Server: POST /agentExecute { agentConfig.model }
Server->>Server: getRequestModelConfig(body) — extracts from agentConfig.model
Server-->>APIClient: response
APIClient-->>V3: result
V3-->>C: result
Note over C,Browser: Session Start Flow (Local Server)
C->>Server: POST /sessions/start { modelName }
Server->>Server: getRequestModelConfig(body)
alt body has options.model or agentConfig.model
Server->>Server: extract model config (including Vertex auth fields)
else modelName only
Server->>Server: use modelName + x-model-api-key header fallback
end
Server->>SessionStore: createSession({ modelName, requestModelConfig })
SessionStore->>SessionStore: set ctx.requestModelConfig
SessionStore->>SessionStore: create V3Options from ctx.requestModelConfig + modelApiKey
Server-->>C: session created
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
There was a problem hiding this comment.
No issues found across 12 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant ClientApp as Client Application
participant V3 as V3 Instance
participant APIClient as StagehandAPIClient
participant Fetch as HTTP Client
participant Server as Server-v3
participant SessionStore as InMemorySessionStore
participant LLM as LLMProvider
participant Browser as Browser
Note over ClientApp,Browser: Constructor Model Config Forwarding Flow
ClientApp->>V3: new V3({ modelName: "vertex/...", modelClientOptions: { project, location, googleAuthOptions } })
ClientApp->>V3: init()
V3->>V3: getApiDefaultModelConfig()
V3->>APIClient: init({ modelName, modelApiKey, defaultModelConfig: { modelName, project, location, googleAuthOptions } })
APIClient->>APIClient: store defaultModelConfig
alt navigate(url) called
APIClient->>APIClient: strip model from publicOptions
APIClient->>APIClient: getDefaultModelConfig() → Vertex config
APIClient->>Fetch: POST /navigate { url, options: { model: Vertex config } }
Fetch->>Server: Forward request
Server->>Server: getRequestModelConfig() → parse body.options.model
alt has model config
Server->>SessionStore: bootstrap session with { modelName, project, location, googleAuthOptions }
SessionStore->>LLM: getClient(Vertex model)
SessionStore->>Browser: page.goto(url) – model stripped
else fallback to header
Server->>SessionStore: bootstrap with { modelName, apiKey: x-model-api-key }
end
end
alt act/observe/extract() called
alt per-call model provided
APIClient->>APIClient: prepareModelConfig(per-call model)
APIClient->>Fetch: POST /{act|observe|extract} { options: { model: per-call } }
else no per-call model
APIClient->>APIClient: getDefaultModelConfig() → use constructor config
APIClient->>Fetch: POST /{act|observe|extract} { options: { model: constructor Vertex config } }
end
Fetch->>Server: Forward with model config
Server->>SessionStore: Use request model config for LLM calls
end
alt agentExecute() called
alt agentConfig.model provided
APIClient->>APIClient: prepareModelConfig(agentConfig.model)
else no agentConfig.model
APIClient->>APIClient: getDefaultModelConfig() → use constructor config
end
APIClient->>Fetch: POST /agentExecute { agentConfig: { model: resolved config } }
Fetch->>Server: Forward
end
Note over LLM: No longer throws ExperimentalNotConfiguredError for Vertex<br>Vertex models work without experimental mode
alt constructor provides only modelApiKey (no modelClientOptions)
V3->>V3: getApiDefaultModelConfig() → undefined
APIClient->>APIClient: defaultModelConfig is undefined
APIClient->>Fetch: No model in body, only x-model-api-key header
Server->>SessionStore: bootstrap with { modelName, apiKey: x-model-api-key }
end
There was a problem hiding this comment.
2 issues found across 10 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/server-v3/src/routes/v1/sessions/start.ts">
<violation number="1" location="packages/server-v3/src/routes/v1/sessions/start.ts:218">
P1: Session leak: if model config validation fails here, the session created by `startSession()` above is never cleaned up. Add `await sessionStore.endSession(session.sessionId)` before returning the 400, similar to the error handling in the `catch` block below.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Why
Vertex model credentials need a typed public shape and must be forwarded from the TypeScript client to API-backed Stagehand requests without persisting secrets server-side.
What changed
provider: "vertex",auth, andproviderOptions.vertex, while preserving existing generic model string/object support.TODO before final hosted API merge: swap core to the Orca build produced from this Stagehand change.