111 lines
3.3 KiB
TypeScript
111 lines
3.3 KiB
TypeScript
import { readFile } from 'node:fs/promises';
|
|
import YAML from 'yaml';
|
|
import type { FleetAgent } from '../commands/fleet.js';
|
|
|
|
const REQUIRED_RUNTIME = 'pi';
|
|
const REQUIRED_MODEL = 'openai/gpt-5.6-sol';
|
|
const REQUIRED_REASONING = 'high';
|
|
const REQUIRED_TOOL_POLICY = 'operator-interaction';
|
|
const AGENT_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
|
|
export interface InteractionServiceProfile {
|
|
runtime: string;
|
|
model: string;
|
|
reasoning: string;
|
|
toolPolicy: string;
|
|
}
|
|
|
|
export interface EffectiveInteractionPolicy {
|
|
agentName: string;
|
|
runtime: string;
|
|
model: string;
|
|
reasoning: string;
|
|
toolPolicy: string;
|
|
}
|
|
|
|
export class InteractionServiceProfileError extends Error {
|
|
constructor(
|
|
readonly code: 'invalid_profile' | 'invalid_request',
|
|
message: string,
|
|
) {
|
|
super(message);
|
|
this.name = InteractionServiceProfileError.name;
|
|
}
|
|
}
|
|
|
|
export async function readInteractionServiceProfile(
|
|
path: string,
|
|
overrides: Partial<InteractionServiceProfile> = {},
|
|
): Promise<InteractionServiceProfile> {
|
|
const parsed: unknown = YAML.parse(await readFile(path, 'utf8'));
|
|
if (!isRecord(parsed)) {
|
|
throw new InteractionServiceProfileError(
|
|
'invalid_profile',
|
|
'Service profile must be an object',
|
|
);
|
|
}
|
|
const profile: InteractionServiceProfile = {
|
|
runtime: valueOrUndefined(overrides.runtime, parsed['runtime']),
|
|
model: valueOrUndefined(overrides.model, parsed['model']),
|
|
reasoning: valueOrUndefined(overrides.reasoning, parsed['reasoning']),
|
|
toolPolicy: valueOrUndefined(overrides.toolPolicy, parsed['tool_policy']),
|
|
};
|
|
assertPinnedPolicy(profile);
|
|
return profile;
|
|
}
|
|
|
|
export function provisionInteractionService(
|
|
profile: InteractionServiceProfile,
|
|
input: { agentName: string },
|
|
): { rosterAgent: FleetAgent; effectivePolicy: EffectiveInteractionPolicy } {
|
|
assertPinnedPolicy(profile);
|
|
if (!AGENT_NAME_PATTERN.test(input.agentName)) {
|
|
throw new InteractionServiceProfileError(
|
|
'invalid_request',
|
|
'Agent name must contain only letters, numbers, dots, underscores, or hyphens',
|
|
);
|
|
}
|
|
const effectivePolicy: EffectiveInteractionPolicy = {
|
|
agentName: input.agentName,
|
|
runtime: profile.runtime,
|
|
model: profile.model,
|
|
reasoning: profile.reasoning,
|
|
toolPolicy: profile.toolPolicy,
|
|
};
|
|
return {
|
|
rosterAgent: {
|
|
name: input.agentName,
|
|
runtime: profile.runtime,
|
|
className: profile.toolPolicy,
|
|
modelHint: profile.model,
|
|
reasoningLevel: profile.reasoning,
|
|
toolPolicy: profile.toolPolicy,
|
|
persistentPersona: true,
|
|
},
|
|
effectivePolicy,
|
|
};
|
|
}
|
|
|
|
function assertPinnedPolicy(profile: InteractionServiceProfile): void {
|
|
const invalid =
|
|
profile.runtime !== REQUIRED_RUNTIME ||
|
|
profile.model !== REQUIRED_MODEL ||
|
|
profile.reasoning !== REQUIRED_REASONING ||
|
|
profile.toolPolicy !== REQUIRED_TOOL_POLICY;
|
|
if (invalid) {
|
|
throw new InteractionServiceProfileError(
|
|
'invalid_profile',
|
|
`Service profile must pin ${REQUIRED_RUNTIME}, ${REQUIRED_MODEL}, ${REQUIRED_REASONING}, and ${REQUIRED_TOOL_POLICY}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function valueOrUndefined(override: string | undefined, value: unknown): string {
|
|
if (override !== undefined) return override;
|
|
return typeof value === 'string' ? value : '';
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
}
|