mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Initialize saved Google connections for local Gemini
Allow Google account defaults in the database and seed the Gemini API-key auth choice in isolated runtime homes. Cover connection persistence, probing, execution, and migration replay. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
83b3861599
commit
110aee57f5
8 files changed
+496
-7
No files matched your search
@@ -540,6 +540,10 @@ credentials belong to the connection; the model belongs to the agent.
|
||||
| Hermes local | OpenRouter; custom/local Chat Completions |
|
||||
| Gemini CLI, Grok | Their native API connections; custom routes are not advertised |
|
||||
|
||||
Migration `0295` adds Google to both account-default provider constraints. Local
|
||||
Gemini connections seed the API-key auth choice in their disposable home before
|
||||
environment probes and task execution. The settings file contains no credential.
|
||||
|
||||
OpenClaw Gateway, Hermes Gateway, Claude Managed, AWS AgentCore, Process, HTTP,
|
||||
and legacy `acpx_local` are excluded: external agents retain their own model
|
||||
configuration, and `acpx_local` is retired. Cursor/Pi/Copilot custom routing,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "ai_connection_defaults" DROP CONSTRAINT IF EXISTS "ai_connection_defaults_provider_check";--> statement-breakpoint
|
||||
ALTER TABLE "ai_provider_defaults" DROP CONSTRAINT IF EXISTS "ai_provider_defaults_provider_check";--> statement-breakpoint
|
||||
ALTER TABLE "ai_connection_defaults" ADD CONSTRAINT "ai_connection_defaults_provider_check" CHECK ("ai_connection_defaults"."provider" in ('anthropic','openai','openrouter','xai','google'));--> statement-breakpoint
|
||||
ALTER TABLE "ai_provider_defaults" ADD CONSTRAINT "ai_provider_defaults_provider_check" CHECK ("ai_provider_defaults"."provider" in ('anthropic','openai','openrouter','xai','google'));
|
||||
+399
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "26315a87-4675-4dea-9634-efe5e878cc7d",
|
||||
"prevId": "3960ae6b-5bf1-4fd7-91db-6a18fc7eaa19",
|
||||
"id": "6f6adf6c-c061-4640-8309-6a53de8dd224",
|
||||
"prevId": "66a0b3c9-91bb-46b8-a868-a6facedbc14b",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -2894,7 +2894,7 @@
|
||||
"checkConstraints": {
|
||||
"ai_connection_defaults_provider_check": {
|
||||
"name": "ai_connection_defaults_provider_check",
|
||||
"value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')"
|
||||
"value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')"
|
||||
},
|
||||
"ai_connection_defaults_method_check": {
|
||||
"name": "ai_connection_defaults_method_check",
|
||||
@@ -3011,7 +3011,7 @@
|
||||
"checkConstraints": {
|
||||
"ai_provider_defaults_provider_check": {
|
||||
"name": "ai_provider_defaults_provider_check",
|
||||
"value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')"
|
||||
"value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
@@ -12737,6 +12737,373 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.company_skill_source_entries": {
|
||||
"name": "company_skill_source_entries",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_id": {
|
||||
"name": "source_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"inspection": {
|
||||
"name": "inspection",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"skill_id": {
|
||||
"name": "skill_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"selection": {
|
||||
"name": "selection",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'new'"
|
||||
},
|
||||
"present": {
|
||||
"name": "present",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"company_skill_source_entries_source_path_idx": {
|
||||
"name": "company_skill_source_entries_source_path_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "source_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "path",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"company_skill_source_entries_company_skill_idx": {
|
||||
"name": "company_skill_source_entries_company_skill_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "skill_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"company_skill_source_entries_company_id_companies_id_fk": {
|
||||
"name": "company_skill_source_entries_company_id_companies_id_fk",
|
||||
"tableFrom": "company_skill_source_entries",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"company_skill_source_entries_source_id_company_skill_sources_id_fk": {
|
||||
"name": "company_skill_source_entries_source_id_company_skill_sources_id_fk",
|
||||
"tableFrom": "company_skill_source_entries",
|
||||
"tableTo": "company_skill_sources",
|
||||
"columnsFrom": [
|
||||
"source_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"company_skill_source_entries_skill_id_company_skills_id_fk": {
|
||||
"name": "company_skill_source_entries_skill_id_company_skills_id_fk",
|
||||
"tableFrom": "company_skill_source_entries",
|
||||
"tableTo": "company_skills",
|
||||
"columnsFrom": [
|
||||
"skill_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.company_skill_sources": {
|
||||
"name": "company_skill_sources",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"repository_id": {
|
||||
"name": "repository_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"repository_url": {
|
||||
"name": "repository_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tracking_ref": {
|
||||
"name": "tracking_ref",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"excluded_folders": {
|
||||
"name": "excluded_folders",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"revision": {
|
||||
"name": "revision",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"last_attempt_at": {
|
||||
"name": "last_attempt_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_success_at": {
|
||||
"name": "last_success_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_scan_commit": {
|
||||
"name": "last_scan_commit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_error": {
|
||||
"name": "last_error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"lease_token": {
|
||||
"name": "lease_token",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"lease_expires_at": {
|
||||
"name": "lease_expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"company_skill_sources_repository_ref_idx": {
|
||||
"name": "company_skill_sources_repository_ref_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "repository_url",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "tracking_ref",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"company_skill_sources_identity_ref_idx": {
|
||||
"name": "company_skill_sources_identity_ref_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "repository_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "tracking_ref",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"company_skill_sources_company_id_companies_id_fk": {
|
||||
"name": "company_skill_sources_company_id_companies_id_fk",
|
||||
"tableFrom": "company_skill_sources",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"company_skill_sources_connection_id_tool_connections_id_fk": {
|
||||
"name": "company_skill_sources_connection_id_tool_connections_id_fk",
|
||||
"tableFrom": "company_skill_sources",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.company_skill_comments": {
|
||||
"name": "company_skill_comments",
|
||||
"schema": "",
|
||||
@@ -31098,6 +31465,13 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title_needs_generation": {
|
||||
"name": "title_needs_generation",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
@@ -45835,6 +46209,27 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_expiry_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_expiry_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "expires_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_gateway_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_gateway_idx",
|
||||
"columns": [
|
||||
+7
@@ -2052,6 +2052,13 @@
|
||||
"when": 1790891286527,
|
||||
"tag": "0294_chilly_marvel_apes",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 295,
|
||||
"version": "7",
|
||||
"when": 1790981052989,
|
||||
"tag": "0295_absurd_starhawk",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export const aiConnectionDefaults = pgTable(
|
||||
}),
|
||||
check(
|
||||
"ai_connection_defaults_provider_check",
|
||||
sql`${t.provider} in ('anthropic','openai','openrouter','xai')`,
|
||||
sql`${t.provider} in ('anthropic','openai','openrouter','xai','google')`,
|
||||
),
|
||||
check(
|
||||
"ai_connection_defaults_method_check",
|
||||
|
||||
@@ -16,5 +16,5 @@ export const aiProviderDefaults = pgTable("ai_provider_defaults", {
|
||||
}, (t) => [
|
||||
uniqueIndex("ai_provider_defaults_owner_provider_uq").on(t.companyId, t.userId, t.provider),
|
||||
foreignKey({ columns: [t.companyId, t.grantId], foreignColumns: [connectionGrants.companyId, connectionGrants.id], name: "ai_provider_defaults_company_grant_fk" }),
|
||||
check("ai_provider_defaults_provider_check", sql`${t.provider} in ('anthropic','openai','openrouter','xai')`),
|
||||
check("ai_provider_defaults_provider_check", sql`${t.provider} in ('anthropic','openai','openrouter','xai','google')`),
|
||||
]);
|
||||
@@ -4,7 +4,7 @@ import { issueRecoveryActionService } from "../services/issue-recovery-actions.j
|
||||
import * as localCredentials from "../services/local-ai-credentials.js";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdtemp, rm, access, readFile, writeFile } from "node:fs/promises";
|
||||
import { mkdtemp, rm, access, readFile, writeFile, stat } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
@@ -13,6 +13,7 @@ import { startEmbeddedPostgresTestDatabase } from "@paperclipai/db/test-embedded
|
||||
import { aiConnectionService } from "../services/ai-connections.js";
|
||||
import * as executionTarget from "@paperclipai/adapter-utils/execution-target";
|
||||
import { prepareManagedAiRuntime, assertManagedAiProjectAuth } from "../services/ai-connection-runtime.js";
|
||||
import { execute as executeGemini, testEnvironment as testGeminiEnvironment } from "@paperclipai/adapter-gemini-local/server";
|
||||
import { toolAccessService } from "../services/tool-access.js";
|
||||
import { secretService } from "../services/secrets.js";
|
||||
import { aiConnectionBindingSchema, connectionPurposeTransportSchema, isAiConnectionCompatible } from "@paperclipai/shared";
|
||||
@@ -46,6 +47,74 @@ beforeAll(async () => {
|
||||
afterAll(async () => { await database?.cleanup(); vi.unstubAllEnvs(); if (home) await rm(home, { recursive: true, force: true }); });
|
||||
|
||||
describe("managed AI connections", () => {
|
||||
it("authenticates local Gemini probes and runs with the saved key in an isolated home", async () => {
|
||||
const root = await mkdtemp(path.join(home, "gemini-auth-"));
|
||||
const command = path.join(root, "gemini");
|
||||
await writeFile(command, `#!/usr/bin/env node
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const settings = JSON.parse(fs.readFileSync(path.join(process.env.HOME, ".gemini/settings.json"), "utf8"));
|
||||
if (settings.selectedAuthType !== "gemini-api-key" || settings.security?.auth?.selectedType !== "gemini-api-key" || process.env.GEMINI_API_KEY !== "saved-google-key") {
|
||||
console.error("Invalid auth method selected");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(JSON.stringify({ type: "system", subtype: "init", session_id: "gemini-managed" }));
|
||||
console.log(JSON.stringify({ type: "assistant", message: { content: [{ type: "output_text", text: "hello" }] } }));
|
||||
console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello", session_id: "gemini-managed" }));
|
||||
`, { mode: 0o700 });
|
||||
const saved = await service.save(companyId, "alice", {
|
||||
provider: "google", method: "api_key", ownership: "personal", name: "Saved Google",
|
||||
apiKey: "fixture", allAgents: true, agentIds: [],
|
||||
}, "saved-google-key");
|
||||
const runtime = await prepareManagedAiRuntime(db, {
|
||||
companyId, agentId, responsibleUserId: "alice", adapterType: "gemini_local",
|
||||
binding: { provider: "google", method: "api_key", mode: "responsible_user" },
|
||||
config: { engine: "cli", command, cwd: root, promptTemplate: "Say hello.", paperclipRuntimeSkills: [], env: { GEMINI_API_KEY: "ambient-google-key" } },
|
||||
});
|
||||
try {
|
||||
expect(runtime.attribution.grantId).toBe(saved.grantId);
|
||||
expect(runtime.home).not.toBe(os.homedir());
|
||||
const settingsFile = path.join(runtime.home!, ".gemini/settings.json");
|
||||
expect(await readFile(settingsFile, "utf8")).not.toContain("saved-google-key");
|
||||
expect((await stat(settingsFile)).mode & 0o777).toBe(0o600);
|
||||
const probe = await testGeminiEnvironment({ companyId, adapterType: "gemini_local", config: runtime.config });
|
||||
expect(probe.status).toBe("pass");
|
||||
expect(probe.checks).toContainEqual(expect.objectContaining({ code: "gemini_hello_probe_passed" }));
|
||||
const result = await executeGemini({
|
||||
runId: randomUUID(),
|
||||
agent: { id: agentId, companyId, name: "Gemini", adapterType: "gemini_local", adapterConfig: { engine: "cli" } },
|
||||
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
|
||||
config: runtime.config, context: {}, onLog: async () => {},
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.sessionId).toBe("gemini-managed");
|
||||
} finally {
|
||||
await runtime.cleanup();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
await expect(access(runtime.home!)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("preserves Google account defaults when the provider constraint migration is reapplied", async () => {
|
||||
const saved = await service.save(companyId, "bob", {
|
||||
provider: "google", method: "api_key", ownership: "personal", name: "Google migration",
|
||||
apiKey: "fixture", allAgents: true, agentIds: [],
|
||||
}, "google-migration-key");
|
||||
const migration = await readFile(new URL("../../../packages/db/src/migrations/0295_absurd_starhawk.sql", import.meta.url), "utf8");
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
await db.transaction(async (tx) => {
|
||||
for (const statement of migration.split("--> statement-breakpoint")) {
|
||||
if (statement.trim()) await tx.execute(sql.raw(statement));
|
||||
}
|
||||
});
|
||||
}
|
||||
const selected = await service.select({
|
||||
companyId, agentId, userId: "bob", adapterType: "gemini_local",
|
||||
binding: { provider: "google", method: "api_key", mode: "responsible_user" },
|
||||
});
|
||||
expect(selected.grant.id).toBe(saved.grantId);
|
||||
});
|
||||
|
||||
it.each([false, true])("reports the authoritative connection-manager capability for custom grants (manager: %s)", async (manager) => {
|
||||
const userId = `custom-manager-${manager}`;
|
||||
await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" });
|
||||
|
||||
@@ -304,6 +304,16 @@ export async function prepareManagedAiRuntime(
|
||||
});
|
||||
env.OPENCODE_DISABLE_PROJECT_CONFIG = "true";
|
||||
}
|
||||
if (!routing && input.binding.provider === "google") {
|
||||
// Gemini headless CLI requires an explicit auth choice even with an API key.
|
||||
// Seed only the disposable connection home, never the operator's settings.
|
||||
const geminiHome = path.join(home, ".gemini");
|
||||
await mkdir(geminiHome, { mode: 0o700 });
|
||||
await writeFile(path.join(geminiHome, "settings.json"), JSON.stringify({
|
||||
selectedAuthType: "gemini-api-key",
|
||||
security: { auth: { selectedType: "gemini-api-key" } },
|
||||
}), { mode: 0o600 });
|
||||
}
|
||||
const projected = routing ? managedProviderRouting(routing, harness, value, typeof input.config.model === "string" ? input.config.model : "") : undefined;
|
||||
if (projected) {
|
||||
Object.assign(env, projected.env);
|
||||
|
||||
Reference in new issue
Block a user