130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
import { z } from 'zod';
|
|
import type { AttributionConfidence, ConversationEventKind } from '../database/conversation-event-repository.js';
|
|
import type { ConversationPolicyMode } from './conversation-state.js';
|
|
|
|
export const LearningStageSchema = z.enum([
|
|
'rapport',
|
|
'answer',
|
|
'flirt',
|
|
'disclosure',
|
|
'media-invitation',
|
|
'handoff',
|
|
'unknown',
|
|
]);
|
|
export type LearningStage = z.infer<typeof LearningStageSchema>;
|
|
|
|
export const LearningTurnSchema = z.strictObject({
|
|
eventId: z.string(),
|
|
timestamp: z.number(),
|
|
role: z.enum(['recipient', 'operator']),
|
|
actor: z.enum(['recipient', 'ai', 'human']),
|
|
kind: z.enum([
|
|
'inbound-text',
|
|
'opener',
|
|
'automation-reply',
|
|
'manual-text',
|
|
'library-reciprocal',
|
|
]),
|
|
text: z.string(),
|
|
});
|
|
export type LearningTurn = z.infer<typeof LearningTurnSchema>;
|
|
|
|
export const LearningCreditClasses = [
|
|
'human-created-success',
|
|
'human-improved-success',
|
|
'human-completed-ai-trajectory',
|
|
'autonomous-success',
|
|
'recipient-initiated-media',
|
|
'uncertain',
|
|
] as const;
|
|
export type LearningCreditClass = (typeof LearningCreditClasses)[number];
|
|
|
|
export type LearningTrajectoryStatus = 'provisional' | 'eligible' | 'excluded' | 'invalidated' | 'needs-review';
|
|
export type LearningDecisionStatus = 'eligible' | 'excluded' | 'invalidated' | 'needs-review';
|
|
|
|
export interface LearningTrajectoryRecord {
|
|
id: string;
|
|
accountId: string;
|
|
conversationId: string;
|
|
sentMessageId: number | null;
|
|
subreddit: string | null;
|
|
sourceType: 'image' | 'chat' | null;
|
|
policyMode: ConversationPolicyMode | null;
|
|
firstHumanEventId: string | null;
|
|
outcomeEventId: string;
|
|
outcomeType: 'image-received' | 'reciprocal-engagement';
|
|
creditClass: LearningCreditClass;
|
|
attributionConfidence: AttributionConfidence;
|
|
status: LearningTrajectoryStatus;
|
|
maturityAt: number;
|
|
invalidatedAt: number | null;
|
|
invalidationReason: string | null;
|
|
reviewedAt: number | null;
|
|
createdAt: number;
|
|
updatedAt: number;
|
|
}
|
|
|
|
export interface LearningDecisionRecord {
|
|
id: string;
|
|
trajectoryId: string;
|
|
accountId: string;
|
|
decisionEventId: string;
|
|
stage: LearningStage;
|
|
context: LearningTurn[];
|
|
chosenResponse: string;
|
|
turnsUntilOutcome: number;
|
|
isDirectMediaInvitation: boolean;
|
|
qualityStatus: LearningDecisionStatus;
|
|
createdAt: number;
|
|
updatedAt: number;
|
|
}
|
|
|
|
export interface LearningTrajectoryDetail extends LearningTrajectoryRecord {
|
|
decisions: LearningDecisionRecord[];
|
|
}
|
|
|
|
export interface RetrievedLearningExample {
|
|
decisionId: string;
|
|
trajectoryId: string;
|
|
creditClass: LearningCreditClass;
|
|
stage: LearningStage;
|
|
context: LearningTurn[];
|
|
chosenResponse: string;
|
|
turnsUntilOutcome: number;
|
|
subreddit: string | null;
|
|
policyMode: ConversationPolicyMode | null;
|
|
}
|
|
|
|
export interface ConversationLearningQuery {
|
|
accountId: string;
|
|
conversationId: string;
|
|
subreddit: string | null;
|
|
policyMode: ConversationPolicyMode;
|
|
stage: LearningStage;
|
|
purpose: 'reply' | 'media-ask';
|
|
incomingEventId: string;
|
|
}
|
|
|
|
export interface ConversationLearningProvider {
|
|
getExamples(query: ConversationLearningQuery): readonly RetrievedLearningExample[];
|
|
recordGeneration(input: {
|
|
query: ConversationLearningQuery;
|
|
mode: 'shadow' | 'injected';
|
|
policyVersion: string;
|
|
exampleIds: readonly string[];
|
|
promptSha256?: string | null;
|
|
generatedAt?: number;
|
|
}): void;
|
|
}
|
|
|
|
export interface AttributedConversationMessage {
|
|
eventId: string;
|
|
timestamp: number;
|
|
isSelf: boolean;
|
|
text: string;
|
|
mediaCount: number;
|
|
actor: 'recipient' | 'ai' | 'human' | 'unknown';
|
|
kind: ConversationEventKind;
|
|
confidence: AttributionConfidence;
|
|
}
|