feat(copilot): managed profile testing and openaiCompatible provider support

- add native openaiCompatible provider type with flat baseUrl/apiKey/dialect
  config fields and declared per-model capabilities
- add admin-only probeManagedCopilotProfile GraphQL mutation that dispatches
  real probe requests against server-managed copilot profiles, reusing the
  BYOK probe engine with an AllowPrivate egress policy for self-hosted
  endpoints (Vertex profiles rejected; use workspace BYOK instead)
- expose providers.profiles app config descriptor to the admin console
- add 'AI Providers' settings group with JSON editor and per-profile Test
  action that probes declared capabilities and reports verified/failed
  status per model and operation
This commit is contained in:
2026-08-26 09:51:52 +07:00
parent ff599ac7f1
commit 177150086f
24 changed files with 1309 additions and 53 deletions

View File

@@ -629,6 +629,15 @@ export class BackendRuntimeProvider
);
}
async probeManagedProfile(
profileId: string,
checks: Array<{ modelId: string; operation: string }>
): Promise<ByokProbeResultOutput> {
return await this.measured('probeManagedProfile', runtime =>
runtime.probeManagedCopilotProfile(profileId, checks)
);
}
async deleteByokProfile(workspaceId: string, profileId: string) {
return await this.measured('deleteByokProfile', runtime =>
runtime.deleteByokProfile(workspaceId, profileId)

View File

@@ -35,13 +35,17 @@ type CopilotProviderProfileCommon = {
displayName?: string;
priority?: number;
enabled?: boolean;
models?: string[];
baseUrl?: string;
apiKey?: string;
dialect?: 'responses' | 'chat_completions';
models?: Array<string | { id: string; capabilities?: string[] }>;
middleware?: ProviderMiddlewareConfig;
};
export type CopilotProviderProfile = CopilotProviderProfileCommon & {
type: CopilotProviderType;
config: ProviderSpecificConfig;
/** Legacy nested form. New configs use the flat baseUrl/apiKey/dialect fields. */
config?: ProviderSpecificConfig;
};
declare global {

View File

@@ -0,0 +1,118 @@
import {
Args,
Field,
ID,
InputType,
Mutation,
ObjectType,
Resolver,
} from '@nestjs/graphql';
import { Throttle } from '../../base';
import { BackendRuntimeProvider } from '../../core/backend-runtime';
import { Admin } from '../../core/common';
import { ByokProbeOperation, ByokProbeStatusKind } from './byok/types';
@InputType()
class ManagedProfileProbeCheckInput {
@Field(() => String)
modelId!: string;
@Field(() => ByokProbeOperation)
operation!: ByokProbeOperation;
}
@ObjectType()
class ManagedProfileProbeStatusType {
@Field(() => ByokProbeStatusKind)
kind!: ByokProbeStatusKind;
@Field(() => Date, { nullable: true })
testedAt!: Date | null;
@Field(() => String, { nullable: true })
errorKind!: string | null;
}
@ObjectType()
class ManagedProfileModelProbeCheckType {
@Field(() => ByokProbeOperation)
operation!: ByokProbeOperation;
@Field(() => ManagedProfileProbeStatusType)
status!: ManagedProfileProbeStatusType;
}
@ObjectType()
class ManagedProfileModelProbeType {
@Field(() => String)
modelId!: string;
@Field(() => [ManagedProfileModelProbeCheckType])
checks!: ManagedProfileModelProbeCheckType[];
}
@ObjectType()
class ManagedProfileProbeResultType {
@Field(() => String)
definitionFingerprint!: string;
@Field(() => ManagedProfileProbeStatusType)
connection!: ManagedProfileProbeStatusType;
@Field(() => [ManagedProfileModelProbeType])
models!: ManagedProfileModelProbeType[];
}
function projectStatus(probe: {
kind: string;
testedAtMs?: number;
errorKind?: string;
}) {
return {
kind: probe.kind,
testedAt: probe.testedAtMs ? new Date(probe.testedAtMs) : null,
errorKind: probe.errorKind ?? null,
};
}
/**
* Admin-only surface for testing server-managed copilot provider profiles
* (copilot.providers.profiles) configured through the admin console.
*/
@Admin()
@Resolver(() => ManagedProfileProbeResultType)
export class ManagedCopilotProfileResolver {
constructor(private readonly runtime: BackendRuntimeProvider) {}
@Mutation(() => ManagedProfileProbeResultType, {
description:
'Test a server-managed copilot provider profile by dispatching real probe requests.',
})
@Throttle('strict')
async probeManagedCopilotProfile(
@Args('profileId', { type: () => ID }) profileId: string,
@Args('checks', { type: () => [ManagedProfileProbeCheckInput] })
checks: ManagedProfileProbeCheckInput[]
) {
const result = await this.runtime.probeManagedProfile(
profileId,
checks.map(check => ({
modelId: check.modelId,
operation: check.operation,
}))
);
return {
definitionFingerprint: result.definitionFingerprint,
stale: result.stale,
connection: projectStatus(result.connection),
models: result.models.map(model => ({
modelId: model.modelId,
checks: model.checks.map(check => ({
operation: check.operation,
status: projectStatus(check.status),
})),
})),
};
}
}

View File

@@ -16,6 +16,7 @@ import {
NativeEmbeddingService,
} from './embedding';
import { CopilotEmbeddingRealtimeProvider } from './embedding/realtime';
import { ManagedCopilotProfileResolver } from './managed-profile.resolver';
import { WorkspaceMcpProvider } from './mcp/provider';
import { PromptService } from './prompt';
import { CopilotResolver, UserCopilotResolver } from './resolver';
@@ -112,6 +113,7 @@ export const COPILOT_RESOLVER_PROVIDERS = [
CopilotResolver,
UserCopilotResolver,
WorkspaceByokResolver,
ManagedCopilotProfileResolver,
];
export const COPILOT_JOB_PROVIDERS = [CopilotCronJobs];

View File

@@ -30,6 +30,7 @@ export enum CopilotProviderType {
Gemini = 'gemini',
GeminiVertex = 'geminiVertex',
OpenAI = 'openai',
OpenAICompatible = 'openaiCompatible',
}
export const CopilotProviderSchema = z.object({

View File

@@ -1448,6 +1448,33 @@ input ManageUserInput {
name: String
}
type ManagedProfileModelProbeCheckType {
operation: ByokProbeOperation!
status: ManagedProfileProbeStatusType!
}
type ManagedProfileModelProbeType {
checks: [ManagedProfileModelProbeCheckType!]!
modelId: String!
}
input ManagedProfileProbeCheckInput {
modelId: String!
operation: ByokProbeOperation!
}
type ManagedProfileProbeResultType {
connection: ManagedProfileProbeStatusType!
definitionFingerprint: String!
models: [ManagedProfileModelProbeType!]!
}
type ManagedProfileProbeStatusType {
errorKind: String
kind: ByokProbeStatusKind!
testedAt: DateTime
}
enum McpAccessMode {
READ_ONLY
READ_WRITE
@@ -1631,6 +1658,11 @@ type Mutation {
"""mention user in a doc"""
mentionUser(input: MentionInput!): ID!
previewLicense(license: Upload!): AdminLicensePreview!
"""
Test a server-managed copilot provider profile by dispatching real probe requests.
"""
probeManagedCopilotProfile(checks: [ManagedProfileProbeCheckInput!]!, profileId: ID!): ManagedProfileProbeResultType!
probeWorkspaceByokDraft(input: ProbeWorkspaceByokDraftInput!): WorkspaceByokProbeResultType!
probeWorkspaceByokProfile(input: ProbeWorkspaceByokProfileInput!): WorkspaceByokProbeResultType!
publishDoc(docId: String!, mode: PublicDocMode = Page, workspaceId: String!): DocType!