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:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -30,6 +30,7 @@ export enum CopilotProviderType {
|
||||
Gemini = 'gemini',
|
||||
GeminiVertex = 'geminiVertex',
|
||||
OpenAI = 'openai',
|
||||
OpenAICompatible = 'openaiCompatible',
|
||||
}
|
||||
|
||||
export const CopilotProviderSchema = z.object({
|
||||
|
||||
@@ -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!
|
||||
|
||||
Reference in New Issue
Block a user