Skip to content

Commit d0cb989

Browse files
committed
refactor(agent): isolate Google tool credentials
Remove Gemini chat routing. Resolve Google AI keys only when media or browser tools run. Rename the key and expose provider purposes through accessible info tooltips.
1 parent af6755e commit d0cb989

23 files changed

Lines changed: 125 additions & 124 deletions

File tree

apps/agent-worker/src/durable-objects/agent-run-mastra-stream.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ async function prepareMastraContext(options: MastraStreamOptions): Promise<Prepa
113113
composioConfigured: Boolean(toolCredentials.composioApiKey),
114114
exaConfigured: Boolean(toolCredentials.exaApiKey),
115115
firecrawlConfigured: Boolean(toolCredentials.firecrawlApiKey),
116-
googleMediaConfigured: Boolean(toolCredentials.googleMediaApiKey),
117116
});
118117
return { ...userSkillContext, toolCredentials };
119118
}
@@ -217,10 +216,7 @@ function agentRequestContext(
217216
exaApiKey: toolCredentials.exaApiKey,
218217
firecrawlApiKey: toolCredentials.firecrawlApiKey,
219218
globalMemory: input.globalMemory,
220-
googleApiKey:
221-
credential.transportProvider === "google"
222-
? credential.apiKey
223-
: toolCredentials.googleMediaApiKey,
219+
googleToolApiKeyResolver: toolCredentials.googleToolApiKeyResolver,
224220
llmProvider: credential.transportProvider,
225221
modelId: credential.transportModelId,
226222
openaiApiKey: credential.transportProvider === "openai" ? credential.apiKey : undefined,

apps/agent-worker/src/durable-objects/agent-tool-credentials.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ import type { AgentRunEnv } from "./agent-run-env";
33
import type { StartRunInput } from "./agent-run-schemas";
44
import type { ComposioRuntimeCredentials } from "./composio-provider";
55
import { resolveComposioRuntimeCredentials } from "./composio-provider";
6-
import type { MediaCredentials } from "./media-provider";
7-
import { resolveMediaCredentials } from "./media-provider";
6+
import { createGoogleToolApiKeyResolver, type GoogleToolApiKeyResolver } from "./media-provider";
87
import type { ResearchCredentials } from "./research-provider";
98
import { resolveResearchCredentials } from "./research-provider";
109

1110
export type AgentToolCredentials = ComposioRuntimeCredentials &
12-
MediaCredentials &
13-
ResearchCredentials;
11+
ResearchCredentials & { googleToolApiKeyResolver: GoogleToolApiKeyResolver };
1412

1513
export async function resolveAgentToolCredentials(input: {
1614
env: AgentRunEnv;
@@ -26,11 +24,15 @@ export async function resolveAgentToolCredentials(input: {
2624
input.run,
2725
input.logger,
2826
);
29-
input.setRunStage("Resolving media providers.");
30-
const mediaCredentials = await resolveMediaCredentials(input.env, input.run, input.logger);
27+
input.setRunStage("Preparing Google tool access.");
28+
const googleToolApiKeyResolver = createGoogleToolApiKeyResolver(
29+
input.env,
30+
input.run,
31+
input.logger,
32+
);
3133
return {
3234
...composioCredentials,
33-
...mediaCredentials,
3435
...researchCredentials,
36+
googleToolApiKeyResolver,
3537
};
3638
}

apps/agent-worker/src/durable-objects/llm-provider.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ function resolveModelRequest(model: LogicalModelId): RequestedModel {
161161
} catch (error) {
162162
throw new APIError(400, "request_body_invalid", "Unsupported model selection.", {
163163
details: { message: error instanceof Error ? error.message : "Unknown model error" },
164-
hint: "Use a supported Anthropic, Google Gemini, OpenAI, DeepSeek, or OpenRouter model id.",
164+
hint: "Use a supported Anthropic, OpenAI, DeepSeek, or OpenRouter model id.",
165165
retriable: false,
166166
});
167167
}
@@ -301,9 +301,6 @@ function providerLabel(provider: LlmProvider): string {
301301
if (provider === "openai") {
302302
return "OpenAI";
303303
}
304-
if (provider === "google") {
305-
return "Google Gemini";
306-
}
307304
if (provider === "deepseek") {
308305
return "DeepSeek";
309306
}

apps/agent-worker/src/durable-objects/media-provider.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,42 @@ interface MediaProviderEnv {
1010
HYPERDRIVE: Hyperdrive;
1111
}
1212

13-
interface MediaProviderInput {
13+
interface GoogleToolProviderInput {
1414
userId: string;
1515
}
1616

17-
export interface MediaCredentials {
18-
googleMediaApiKey?: string | undefined;
17+
export type GoogleToolApiKeyResolver = () => Promise<string | undefined>;
18+
19+
export function createGoogleToolApiKeyResolver(
20+
env: MediaProviderEnv,
21+
input: GoogleToolProviderInput,
22+
logger: ReturnType<typeof createLogger>,
23+
): GoogleToolApiKeyResolver {
24+
let resolution: Promise<string | undefined> | undefined;
25+
return () => {
26+
resolution ??= resolveGoogleToolApiKey(env, input, logger);
27+
return resolution;
28+
};
1929
}
2030

21-
export async function resolveMediaCredentials(
31+
async function resolveGoogleToolApiKey(
2232
env: MediaProviderEnv,
23-
input: MediaProviderInput,
33+
input: GoogleToolProviderInput,
2434
logger: ReturnType<typeof createLogger>,
25-
): Promise<MediaCredentials> {
35+
): Promise<string | undefined> {
2636
return withUserDb(
2737
env,
2838
toUserId(input.userId),
2939
async ({ transaction }) => {
3040
const googleMediaApiKey = await transaction((db) => getProviderKey(db, "google"));
31-
logger.info("byok_media_provider_key_checked", { google: Boolean(googleMediaApiKey) });
32-
return googleMediaApiKey ? { googleMediaApiKey } : {};
41+
logger.info("byok_google_tool_key_checked", { configured: Boolean(googleMediaApiKey) });
42+
return googleMediaApiKey ?? undefined;
3343
},
3444
(dbHandle) =>
3545
closeDatabaseBestEffort({
3646
dbHandle,
3747
logger,
38-
operation: "resolve_media_credentials",
48+
operation: "resolve_google_tool_key",
3949
}),
4050
);
4151
}

apps/web/src/components/settings/models-panel-model.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export const SETTINGS_KEY_PROVIDERS = [
2828
export const MODEL_KEY_PROVIDERS = new Set<Provider>([
2929
"anthropic",
3030
"deepseek",
31-
"google",
3231
"openai",
3332
"openrouter",
3433
]);
@@ -44,7 +43,7 @@ const PROVIDER_LABELS = {
4443
deepseek: "DeepSeek",
4544
exa: "Exa",
4645
firecrawl: "Firecrawl",
47-
google: "Gemini",
46+
google: "Google AI",
4847
openai: "OpenAI",
4948
openrouter: "OpenRouter",
5049
} satisfies Record<Provider, string>;

apps/web/src/components/settings/provider-key-model.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ export type ProviderKeyFormValues = z.infer<typeof ProviderKeyFormSchema>;
1010
export type ProviderKeyEditorStatus = "deleting" | "idle" | "saving";
1111
export type SecretVisibility = "hidden" | "visible";
1212

13-
export const PROVIDER_META: Record<Provider, { label: string }> = {
14-
anthropic: { label: "Anthropic" },
15-
deepseek: { label: "DeepSeek" },
16-
exa: { label: "Exa" },
17-
firecrawl: { label: "Firecrawl" },
18-
google: { label: "Gemini" },
19-
openai: { label: "OpenAI" },
20-
openrouter: { label: "OpenRouter" },
13+
export const PROVIDER_META: Record<Provider, { description: string; label: string }> = {
14+
anthropic: { description: "Agent models and browser automation.", label: "Anthropic" },
15+
deepseek: { description: "Optional personal key for DeepSeek models.", label: "DeepSeek" },
16+
exa: { description: "Web research and source discovery.", label: "Exa" },
17+
firecrawl: { description: "Web search, scraping, and extraction.", label: "Firecrawl" },
18+
google: {
19+
description: "Tools only: images, Veo video, and browser automation.",
20+
label: "Google AI",
21+
},
22+
openai: { description: "Agent models and browser automation.", label: "OpenAI" },
23+
openrouter: {
24+
description: "Alternative routing for supported agent models.",
25+
label: "OpenRouter",
26+
},
2127
};
2228

2329
export function providerPanelId(provider: Provider): string {

apps/web/src/components/settings/provider-keys-list.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Provider, ProviderKeySummary } from "@cheatcode/types/api";
22
import type { FormEventHandler } from "react";
33
import type { FieldError, UseFormRegister, UseFormWatch } from "react-hook-form";
4+
import { Info } from "@/components/ui";
5+
import { CheatcodeTooltip } from "@/components/ui/cheatcode-tooltip";
46
import { cn } from "@/lib/ui/cn";
57
import { ProviderKeyEditor } from "./provider-key-editor";
68
import {
@@ -124,11 +126,26 @@ function ProviderKeyRowHeader({
124126
provider: Provider;
125127
summary: ProviderKeySummary | undefined;
126128
}) {
129+
const meta = PROVIDER_META[provider];
127130
return (
128131
<div className="flex min-h-8 items-center justify-between">
129-
<h2 className="truncate font-medium text-[14px] text-foreground" id={providerTabId(provider)}>
130-
{PROVIDER_META[provider].label} API Key
131-
</h2>
132+
<div className="flex min-w-0 items-center gap-1.5">
133+
<h2
134+
className="min-w-0 truncate font-medium text-[14px] text-foreground"
135+
id={providerTabId(provider)}
136+
>
137+
{meta.label} API Key
138+
</h2>
139+
<CheatcodeTooltip label={meta.description} side="top">
140+
<button
141+
aria-label={`About ${meta.label} API key`}
142+
className="inline-flex size-6 shrink-0 cursor-help items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50"
143+
type="button"
144+
>
145+
<Info aria-hidden="true" className="size-3.5" />
146+
</button>
147+
</CheatcodeTooltip>
148+
</div>
132149
{isExpanded ? null : (
133150
<div className="ml-4 flex items-center gap-2">
134151
<button

apps/web/src/components/ui/icons.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export {
2626
Globe,
2727
Image,
2828
Inbox,
29+
Info,
2930
LifeBuoy,
3031
Link,
3132
Loader2,

apps/web/src/components/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export {
2727
Globe,
2828
Image,
2929
Inbox,
30+
Info,
3031
LifeBuoy,
3132
Link,
3233
Loader2,

packages/agent-core/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ Sandbox and artifact capabilities cross tool-domain boundaries only through
2121
Single-consumer data, document, and media implementations live under
2222
`src/tools/`. Data tools profile and normalize bounded tabular inputs and render
2323
deterministic SVG/Recharts output. Document tools generate sandbox-side PPTX,
24-
DOCX, XLSX, and PDF source against `/opt/cheatcode-doc-runtime`. Media tools use
25-
the active request's Google BYOK key for image/video work. All three receive
24+
DOCX, XLSX, and PDF source against `/opt/cheatcode-doc-runtime`. Media and Google-backed
25+
browser tools resolve the user's Google AI BYOK key lazily when invoked. All three receive
2626
sandbox and R2 artifact capabilities through request-scoped contracts; they do
2727
not read environment variables, persist credentials, or log keys.
2828

@@ -52,9 +52,9 @@ pnpm --filter @cheatcode/agent-core typecheck
5252
Provider keys are supplied through BYOK runtime context, not module scope.
5353
`resolveRequestedLlmTransport` returns an `LlmTransportSelection`: its provider and bare
5454
model ID are SDK transport inputs, never the durable product model attribution.
55-
Google model selections use `google/<Gemini model id>`, for example
56-
`google/gemini-2.5-flash`. OpenRouter model selections use
57-
`openrouter/<OpenRouter model id>`, for example `openrouter/openrouter/auto`.
55+
OpenRouter model selections use `openrouter/<OpenRouter model id>`, for example
56+
`openrouter/openrouter/auto`. Google AI keys are tool credentials for image/video
57+
generation and browser automation; `google/<model id>` is not an agent-model route.
5858

5959
Mastra storage is intentionally execution-only and in-memory. AgentRun Durable
6060
Objects and Postgres own durable run and transcript state. Workflows that receive

0 commit comments

Comments
 (0)