mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-08 12:45:55 +08:00
965f4590ff
#### PR Dependency Tree * **PR #15426** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added workspace BYOK profiles with provider/model catalogs, capability validation, connection probing, credential rotation, reordering, and secure local leases. * Added Copilot route options, selectable targets, managed tiers, explicit profile/model overrides, and improved streaming with tool callbacks and abort support. * Added Copilot availability controls to prevent access when the feature is disabled. * **Changes** * Simplified Copilot configuration and removed legacy provider-specific settings. * Removed obsolete model, token-cost, transcript strategy, and provider metadata fields from public responses. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
|
|
import { metrics } from '../../../base';
|
|
import { Models } from '../../../models';
|
|
import { type ByokFeatureKind, ByokProviderSource } from '../byok/types';
|
|
|
|
export type CopilotRuntimeRouteIdentity = {
|
|
profileId: string;
|
|
source: 'server' | 'local' | 'affine_cloud';
|
|
provider: string;
|
|
model: string;
|
|
};
|
|
|
|
export type CopilotRuntimeEvent =
|
|
| { type: 'route_selected'; route: CopilotRuntimeRouteIdentity }
|
|
| {
|
|
type: 'route_failed';
|
|
route: CopilotRuntimeRouteIdentity;
|
|
errorKind: string;
|
|
}
|
|
| {
|
|
type: 'usage';
|
|
route: CopilotRuntimeRouteIdentity;
|
|
usage: {
|
|
prompt_tokens?: number;
|
|
completion_tokens?: number;
|
|
total_tokens?: number;
|
|
cached_tokens?: number;
|
|
input_tokens?: number;
|
|
output_tokens?: number;
|
|
};
|
|
};
|
|
|
|
export type CopilotRuntimeEventContext = {
|
|
workspaceId?: string;
|
|
userId?: string;
|
|
sessionId?: string;
|
|
taskId?: string;
|
|
actionId?: string;
|
|
billingUnitId?: string;
|
|
featureKind: ByokFeatureKind;
|
|
};
|
|
|
|
@Injectable()
|
|
export class CopilotRuntimeEventConsumer {
|
|
private readonly logger = new Logger(CopilotRuntimeEventConsumer.name);
|
|
|
|
constructor(private readonly models: Models) {}
|
|
|
|
async consume(
|
|
events: CopilotRuntimeEvent[],
|
|
context: CopilotRuntimeEventContext
|
|
) {
|
|
for (const event of events) {
|
|
try {
|
|
if (event.type === 'usage') {
|
|
await this.recordUsage(event, context);
|
|
} else if (event.type === 'route_failed') {
|
|
await this.recordFailure(event, context);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(
|
|
`Failed to consume copilot runtime event: ${
|
|
error instanceof Error ? error.message : String(error)
|
|
}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async recordUsage(
|
|
event: Extract<CopilotRuntimeEvent, { type: 'usage' }>,
|
|
context: CopilotRuntimeEventContext
|
|
) {
|
|
if (!context.workspaceId || event.route.source === 'affine_cloud') {
|
|
return;
|
|
}
|
|
const usage = event.usage;
|
|
metrics.ai.counter('byok_usage').add(1, {
|
|
provider: event.route.provider,
|
|
source: event.route.source,
|
|
feature: context.featureKind,
|
|
});
|
|
await this.models.copilotUsage.create({
|
|
workspaceId: context.workspaceId,
|
|
userId: context.userId,
|
|
provider: event.route.provider,
|
|
providerSource:
|
|
event.route.source === 'server'
|
|
? ByokProviderSource.Server
|
|
: ByokProviderSource.Local,
|
|
featureKind: context.featureKind,
|
|
model: event.route.model,
|
|
sessionId: context.sessionId,
|
|
taskId: context.taskId,
|
|
actionId: context.actionId,
|
|
billingUnitId: context.billingUnitId,
|
|
promptTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,
|
|
completionTokens: usage.completion_tokens ?? usage.output_tokens ?? 0,
|
|
totalTokens: usage.total_tokens ?? 0,
|
|
cachedTokens: usage.cached_tokens ?? 0,
|
|
});
|
|
if (event.route.source === 'server') {
|
|
await this.models.copilotWorkspaceByokConfig.touchUsed(
|
|
context.workspaceId,
|
|
event.route.profileId
|
|
);
|
|
}
|
|
}
|
|
|
|
private async recordFailure(
|
|
event: Extract<CopilotRuntimeEvent, { type: 'route_failed' }>,
|
|
context: CopilotRuntimeEventContext
|
|
) {
|
|
metrics.ai.counter('byok_route_failure').add(1, {
|
|
provider: event.route.provider,
|
|
source: event.route.source,
|
|
feature: context.featureKind,
|
|
reason: event.errorKind,
|
|
});
|
|
if (context.workspaceId && event.route.source === 'server') {
|
|
await this.models.copilotWorkspaceByokConfig.markFailure(
|
|
context.workspaceId,
|
|
event.route.profileId,
|
|
event.errorKind
|
|
);
|
|
}
|
|
}
|
|
}
|