Files
AgentCompany/AgentCompany-New/src/main/services/auth-broker-service.ts
T
ZacharyZhang-NY 937fdfa23f Run the agent loop on every provider, not just API keys
The loop was gated to API-key providers, so a GPT-account (Codex) CEO failed
with 'requires an API-key provider'. Port Pi's actual request handlers natively
so every agent-capable provider runs:
- chat-completions turn for xAI, Groq, Mistral, Cerebras, OpenRouter
- Codex SSE Responses for GPT account
- Anthropic-OAuth (Claude Code system + beta headers) for Claude Pro/Max
- Copilot proxy (baseUrl from the token) for GitHub Copilot
- Gemini Code Assist for Google Antigravity

A credential resolver refreshes expired OAuth access tokens at request time and
persists the rotation; the runner routes by provider key and passes the resolved
credential. The heartbeat now retries failed tasks (bounded to 3 attempts) so a
run that failed on the old gate recovers automatically. Cloud providers
(Azure/Bedrock/Vertex) still report a clear unsupported message.
2026-05-29 22:22:24 -04:00

181 lines
6.3 KiB
TypeScript

import type { DatabaseSync } from "node:sqlite";
import { createHash } from "node:crypto";
import type { ProviderAuthType } from "../../shared/domain";
import { createId } from "../../shared/ids";
import { AuditService } from "./audit-service";
import { SecretVault } from "./secret-vault";
export interface ProviderSecretVault {
encryptSecret(secret: string): Buffer;
decryptSecret(ciphertext: Buffer): string;
}
export interface CreateProviderAuthProfileInput {
orgId: string;
providerId: string;
providerKey: string;
authType: ProviderAuthType;
secret: string;
profileKind: string;
sourceType: string;
sourceRef: string | null;
capabilityTags: string[];
}
export interface ProviderAuthProfileRef {
authProfileId: string;
credentialId: string;
displayHint: string;
}
export interface RevokeProviderAuthProfileInput {
orgId: string;
providerId: string;
providerKey: string;
}
export class AuthBrokerService {
private readonly audit: AuditService;
constructor(
private readonly db: DatabaseSync,
private readonly vault: ProviderSecretVault = new SecretVault(),
) {
this.audit = new AuditService(db);
}
createProviderProfile(input: CreateProviderAuthProfileInput): ProviderAuthProfileRef {
const secret = input.secret.trim();
if (!secret) throw new Error("Credential is required.");
const credentialId = createId("cred");
const authProfileId = createId("authp");
const displayHint = credentialDisplayHint(secret);
const now = new Date().toISOString();
const capabilityJson = JSON.stringify({
providerKey: input.providerKey,
authType: input.authType,
capabilityTags: input.capabilityTags,
});
this.db.prepare(`
insert into credentials(id, org_id, provider_key, kind, ciphertext, display_hint, created_at)
values (?, ?, ?, ?, ?, ?, ?)
`).run(credentialId, input.orgId, input.providerKey, input.authType, this.vault.encryptSecret(secret), displayHint, now);
this.db.prepare(`
update provider_auth_profiles
set active = 0
where org_id = ? and provider_id = ? and active = 1
`).run(input.orgId, input.providerId);
this.db.prepare(`
insert into provider_auth_profiles(
id, org_id, provider_id, credential_id, provider_key, auth_type,
profile_kind, display_hint, source_type, source_ref, capability_json,
active, created_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)
`).run(
authProfileId,
input.orgId,
input.providerId,
credentialId,
input.providerKey,
input.authType,
input.profileKind,
displayHint,
input.sourceType,
input.sourceRef,
capabilityJson,
now,
);
this.audit.record({
orgId: input.orgId,
entityType: "provider_auth_profile",
entityId: authProfileId,
actorType: "system",
actorId: null,
action: "created",
payload: {
providerId: input.providerId,
providerKey: input.providerKey,
authType: input.authType,
profileKind: input.profileKind,
},
});
return { authProfileId, credentialId, displayHint };
}
loadProviderSecret(input: {
orgId: string;
providerId: string;
providerKey: string;
credentialId: string;
}): string {
const row = this.db.prepare(`
select credentials.ciphertext
from provider_auth_profiles
join credentials on credentials.id = provider_auth_profiles.credential_id
where provider_auth_profiles.org_id = ?
and provider_auth_profiles.provider_id = ?
and provider_auth_profiles.provider_key = ?
and provider_auth_profiles.credential_id = ?
and provider_auth_profiles.active = 1
and credentials.org_id = provider_auth_profiles.org_id
and credentials.provider_key = provider_auth_profiles.provider_key
`).get(input.orgId, input.providerId, input.providerKey, input.credentialId) as { ciphertext: Uint8Array } | undefined;
if (!row) throw new Error("Provider credential was not found.");
return this.vault.decryptSecret(Buffer.from(row.ciphertext));
}
/**
* Re-encrypts a refreshed OAuth credential into its existing credential row,
* keeping the same credential id (and active auth profile) so request-time
* token refresh persists without re-binding the provider.
*/
updateProviderSecret(input: { orgId: string; providerKey: string; credentialId: string; secret: string }): void {
const secret = input.secret.trim();
if (!secret) throw new Error("Credential is required.");
const displayHint = credentialDisplayHint(secret);
const result = this.db.prepare(`
update credentials
set ciphertext = ?, display_hint = ?
where id = ? and org_id = ? and provider_key = ?
`).run(this.vault.encryptSecret(secret), displayHint, input.credentialId, input.orgId, input.providerKey);
if (result.changes === 0) throw new Error("Provider credential was not found.");
this.db.prepare("update provider_auth_profiles set display_hint = ? where credential_id = ? and org_id = ?")
.run(displayHint, input.credentialId, input.orgId);
}
revokeProviderProfile(input: RevokeProviderAuthProfileInput): void {
const row = this.db.prepare(`
select id, credential_id
from provider_auth_profiles
where org_id = ?
and provider_id = ?
and provider_key = ?
and active = 1
`).get(input.orgId, input.providerId, input.providerKey) as { id: string; credential_id: string } | undefined;
if (!row) throw new Error("Provider credential was not found.");
this.db.prepare("update provider_auth_profiles set active = 0 where id = ? and org_id = ?")
.run(row.id, input.orgId);
this.audit.record({
orgId: input.orgId,
entityType: "provider_auth_profile",
entityId: row.id,
actorType: "system",
actorId: null,
action: "revoked",
payload: {
providerId: input.providerId,
providerKey: input.providerKey,
credentialId: row.credential_id,
},
});
}
}
function credentialDisplayHint(secret: string): string {
const clean = secret.trim();
if (clean.startsWith("{")) return `json:${createHash("sha256").update(clean).digest("hex").slice(0, 10)}`;
if (clean.length <= 10) return `${clean.slice(0, 2)}...`;
return `${clean.slice(0, 6)}...${clean.slice(-4)}`;
}