fix(eve): serve gateway SDK model instances through the /login connection

A source-backed gateway(...) instance authenticates from AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN and never saw the connection /login saved, so skipping the agent.ts rewrite alone made /login report success and the first request fail with GatewayAuthenticationError. In eve dev, resolve such an instance through localGatewayModel(modelId), as a string model id already is; outside dev the authored instance is unchanged.

Signed-off-by: Rui Conti <ruiconti@gmail.com>
This commit is contained in:
Rui Conti
2026-09-19 14:13:28 -04:00
parent 169688bc46
commit aa79d3863a
3 changed files with 71 additions and 3 deletions
@@ -2,4 +2,4 @@
"eve": patch
---
`/login` now connects a Vercel account or AI Gateway key to an agent whose `model` is a gateway SDK call (such as `gateway(...)`) without trying to rewrite `agent.ts`. Previously the failed source edit discarded the connection and returned to the connection picker, leaving the agent unusable.
`/login` now works for agents whose `model` is a gateway SDK call such as `gateway("anthropic/claude-sonnet-5")`. In `eve dev`, a gateway-routed model instance is served through the connection saved by `/login` (Vercel account, AI Gateway key, or project), the same way a string model id is, and `/login` no longer tries to rewrite `agent.ts` for it. Previously the failed source edit sent you back to the connection picker after every sign-in.
@@ -1,5 +1,5 @@
import type { LanguageModel } from "ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ROOT_COMPILED_AGENT_NODE_ID } from "#compiler/manifest.js";
import type { CompiledModuleMap } from "#compiler/module-map.js";
@@ -72,6 +72,58 @@ describe("dynamic runtime model resolution", () => {
expect(model.modelId).toBe("eve-mock/dynamic-subagent");
});
describe("source-backed models", () => {
const source = {
logicalPath: "agent.ts",
sourceId: "agent-config",
sourceKind: "module" as const,
};
const resolve = (model: LanguageModel) =>
resolveRuntimeModelReference(
{ id: typeof model === "string" ? model : `${model.provider}/${model.modelId}`, source },
{ moduleMap: createModuleMap({ default: { model } }), nodeId: undefined },
);
beforeEach(() => vi.stubEnv("NODE_ENV", "production"));
it("serves a gateway SDK instance through the /login connection in eve dev", async () => {
vi.stubEnv("EVE_DEV", "1");
vi.stubEnv("EVE_MODEL_CONNECTION", "vercel");
const authored = createLanguageModel("gateway", "anthropic/claude-sonnet-5");
const model = await resolve(authored);
expect(model).not.toBe(authored);
if (typeof model === "string") throw new Error("expected a model instance");
expect(model.provider).toBe("gateway");
expect(model.modelId).toBe("anthropic/claude-sonnet-5");
});
it("returns the authored gateway instance when no connection is active", async () => {
vi.stubEnv("EVE_DEV", "1");
vi.stubEnv("EVE_MODEL_CONNECTION", "");
const authored = createLanguageModel("gateway", "anthropic/claude-sonnet-5");
expect(await resolve(authored)).toBe(authored);
});
it("returns the authored gateway instance outside eve dev", async () => {
vi.stubEnv("EVE_DEV", "");
vi.stubEnv("EVE_MODEL_CONNECTION", "vercel");
const authored = createLanguageModel("gateway", "anthropic/claude-sonnet-5");
expect(await resolve(authored)).toBe(authored);
});
it("leaves a direct provider instance untouched", async () => {
vi.stubEnv("EVE_DEV", "1");
vi.stubEnv("EVE_MODEL_CONNECTION", "vercel");
const authored = createLanguageModel("openai.responses", "gpt-5.5");
expect(await resolve(authored)).toBe(authored);
});
});
it("loads resolver-only definitions and normalizes explicit metadata", async () => {
const moduleMap = createModuleMap({
default: {
@@ -110,7 +110,23 @@ async function loadSourceBackedRuntimeModelReference(
);
}
return model;
return withLocalGatewayConnection(model);
}
/**
* A gateway-routed SDK instance (`gateway("openai/gpt-5.6")`) authenticates
* from `AI_GATEWAY_API_KEY`/`VERCEL_OIDC_TOKEN` and never sees the connection
* `/login` saved. While such a connection is active, serve the same model id
* through it, as a string model would be. Outside `eve dev` the authored
* instance is returned untouched.
*/
function withLocalGatewayConnection(model: LanguageModel): LanguageModel {
if (typeof model === "string") return model;
if (typeof model.provider !== "string" || model.provider.split(".")[0] !== "gateway") {
return model;
}
if (typeof model.modelId !== "string" || model.modelId === "") return model;
return localGatewayModel(model.modelId) ?? model;
}
function isSourceBackedRuntimeModelReference(