test(connector-id): migrate fixtures and assertions to pi-mono ids
Replaces legacy adapter ids (codex_local / claude_local / gemini_local / claude_compatible) across ~42 test files with pi-mono equivalents: - codex_local → openai-codex - claude_local → claude-pro - gemini_local → gemini-cli - claude_compatible → anthropic Hand-rewrites where fixture semantics changed: - connector-matching: TASK_TYPE_PREFERENCE ordering rebased (claude-pro first for code/content/management; google first for research) - connector-model-policy: getModels(provider) from pi-ai instead of the deleted adapter.module.models array - session-management + edge-case-session: adapter exemption removed; rotation now uniform under pi-mono - ipc-core-admin-handlers: CSV compat model replaced with pi-ai anthropic - connectors.spec: lookup by openai-codex id (catalog now starts with claude-pro) - onboarding-model-pool: listProviderModels(provider) helper; label text changed to "ChatGPT Plus/Pro (Codex)" - database integration: added vault insecure-fallback env + electron mock - command-paths: uses new @main/connector-helpers normalizeCommandValue - pi-mono-migrations: added `as string` casts for 3 negative-existence assertions verifying legacy ids were removed Drops: - pnpm-workspace.yaml packages: [] (was "packages/*"; directory empty) - obsolete CONNECTOR_STRENGTHS import; .module.models probing Verification: - pnpm typecheck → 0 errors - pnpm lint → 0 errors / 0 warnings - pnpm build:verify → 0 errors / 0 warnings - pnpm test:unit → 508/508 pass - pnpm test:integration → 96/96 pass - All 6 adapter hard gates pass (NOT FOUND / 0 matches / empty) - pi-ai / pi-agent-core / pi-coding-agent all resolved @0.67.6
This commit is contained in:
1 parent
4de9cd0339
commit
e94939a562
80 files changed
+2210
-413
No files matched your search
@@ -0,0 +1,122 @@
|
||||
import type { AppDatabase } from "@main/database";
|
||||
import type { CredentialBackend, CredentialKind } from "@main/database/credentials";
|
||||
import type { CredentialStatus } from "@main/database/credentials";
|
||||
import type { CredentialSummary } from "@shared/contracts/schemas-credentials";
|
||||
import type { StoredCredentialPayload } from "./types";
|
||||
|
||||
const HINT_SUFFIX_LEN = 4;
|
||||
const HINT_PREFIX_LEN = 6;
|
||||
|
||||
function maskApiKey(value: string): string {
|
||||
if (value.length <= HINT_PREFIX_LEN + HINT_SUFFIX_LEN) {
|
||||
return `${value.slice(0, 2)}...`;
|
||||
}
|
||||
const prefix = value.slice(0, HINT_PREFIX_LEN);
|
||||
const suffix = value.slice(-HINT_SUFFIX_LEN);
|
||||
return `${prefix}...${suffix}`;
|
||||
}
|
||||
|
||||
export function deriveDisplayHint(payload: StoredCredentialPayload): string {
|
||||
switch (payload.kind) {
|
||||
case "api_key":
|
||||
return payload.value.source === "literal"
|
||||
? maskApiKey(payload.value.value)
|
||||
: `env:${payload.value.value}`;
|
||||
case "azure":
|
||||
return payload.value.resourceName
|
||||
? `azure:${payload.value.resourceName}`
|
||||
: `azure:${maskApiKey(payload.value.apiKey)}`;
|
||||
case "aws":
|
||||
if (payload.value.authMode === "profile") return `aws:profile=${payload.value.awsProfile ?? "?"}`;
|
||||
if (payload.value.authMode === "iam") return `aws:iam=${maskApiKey(payload.value.accessKeyId ?? "?")}`;
|
||||
return `aws:bearer`;
|
||||
case "gcp_adc":
|
||||
return `gcp:${payload.value.project}@${payload.value.location}`;
|
||||
case "oauth": {
|
||||
const expiresMs = payload.value.expires;
|
||||
const expiresLabel = typeof expiresMs === "number" && Number.isFinite(expiresMs)
|
||||
? new Date(expiresMs).toISOString()
|
||||
: "no-expiry";
|
||||
return `oauth:expires=${expiresLabel}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CredentialStoreDeps {
|
||||
db: AppDatabase;
|
||||
}
|
||||
|
||||
export class CredentialStore {
|
||||
constructor(private readonly deps: CredentialStoreDeps) {}
|
||||
|
||||
list(): CredentialSummary[] {
|
||||
return this.deps.db.credentialsTable.list().map((row) => ({
|
||||
connectorId: row.connectorId,
|
||||
kind: row.kind as CredentialKind,
|
||||
displayHint: row.displayHint,
|
||||
status: row.status,
|
||||
expiresAt: row.expiresAt,
|
||||
lastRefreshedAt: row.lastRefreshedAt,
|
||||
lastError: row.lastError,
|
||||
updatedAt: row.updatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Encrypts and stores a credential. Caller is responsible for kind/connectorId match. */
|
||||
save(connectorId: string, payload: StoredCredentialPayload, expiresAt: string | null = null): CredentialSummary {
|
||||
const json = JSON.stringify(payload.value);
|
||||
const { backend, payload: ciphertext } = this.deps.db.vault.encrypt(json);
|
||||
const stored = this.deps.db.credentialsTable.upsert({
|
||||
connectorId,
|
||||
kind: payload.kind,
|
||||
backend: backend as CredentialBackend,
|
||||
ciphertext,
|
||||
displayHint: deriveDisplayHint(payload),
|
||||
status: "active",
|
||||
expiresAt,
|
||||
lastRefreshedAt: null,
|
||||
lastError: null,
|
||||
});
|
||||
return {
|
||||
connectorId: stored.connectorId,
|
||||
kind: stored.kind,
|
||||
displayHint: stored.displayHint,
|
||||
status: stored.status,
|
||||
expiresAt: stored.expiresAt,
|
||||
lastRefreshedAt: stored.lastRefreshedAt,
|
||||
lastError: stored.lastError,
|
||||
updatedAt: stored.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/** Decrypts and returns the typed credential payload. Returns null when missing. */
|
||||
read(connectorId: string): StoredCredentialPayload | null {
|
||||
const row = this.deps.db.credentialsTable.get(connectorId);
|
||||
if (!row) return null;
|
||||
const json = this.deps.db.vault.decrypt(
|
||||
row.backend,
|
||||
Buffer.isBuffer(row.ciphertext) ? row.ciphertext : Buffer.from(row.ciphertext),
|
||||
);
|
||||
const value = JSON.parse(json);
|
||||
return { kind: row.kind, value } as StoredCredentialPayload;
|
||||
}
|
||||
|
||||
remove(connectorId: string): void {
|
||||
this.deps.db.credentialsTable.remove(connectorId);
|
||||
}
|
||||
|
||||
setStatus(connectorId: string, status: CredentialStatus, lastError: string | null = null): void {
|
||||
this.deps.db.credentialsTable.setStatus(connectorId, status, lastError);
|
||||
}
|
||||
|
||||
/** Replaces the OAuth ciphertext after a successful refresh. */
|
||||
saveRefreshedOAuth(connectorId: string, payload: StoredCredentialPayload, expiresAt: string | null): void {
|
||||
const { backend, payload: ciphertext } = this.deps.db.vault.encrypt(JSON.stringify(payload.value));
|
||||
this.deps.db.credentialsTable.markRefreshed(
|
||||
connectorId,
|
||||
backend as CredentialBackend,
|
||||
ciphertext,
|
||||
expiresAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export { CredentialStore, deriveDisplayHint } from "./credential-store";
|
||||
export { CredentialResolver, CredentialError, type ResolvedCredential } from "./credential-resolver";
|
||||
export { OAuthLoginService } from "./oauth-login-service";
|
||||
export type { StoredCredentialPayload, OAuthCredentialPayload, CredentialKind } from "./types";
|
||||
@@ -0,0 +1,30 @@
|
||||
import type {
|
||||
ApiKeyValue,
|
||||
AwsCredential,
|
||||
AzureCredential,
|
||||
GcpAdcCredential,
|
||||
} from "@shared/contracts/schemas-credentials";
|
||||
|
||||
/**
|
||||
* pi-ai's `OAuthCredentials` shape.
|
||||
*
|
||||
* Mirrored locally as a type-only declaration to avoid pulling pi runtime
|
||||
* symbols into modules that only need the shape. The actual contract is
|
||||
* enforced by pi at runtime via the OAuth provider interface.
|
||||
*/
|
||||
export interface OAuthCredentialPayload {
|
||||
refresh: string;
|
||||
access: string;
|
||||
expires: number;
|
||||
/** Provider-specific extra fields (e.g. github copilot domain) */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type StoredCredentialPayload =
|
||||
| { kind: "oauth"; value: OAuthCredentialPayload }
|
||||
| { kind: "api_key"; value: ApiKeyValue }
|
||||
| { kind: "azure"; value: AzureCredential }
|
||||
| { kind: "aws"; value: AwsCredential }
|
||||
| { kind: "gcp_adc"; value: GcpAdcCredential };
|
||||
|
||||
export type CredentialKind = StoredCredentialPayload["kind"];
|
||||
Reference in new issue
Block a user