import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context'; import type { ManifestScope } from './manifest-builder'; import type { CustomSkill } from './custom-skill'; import { requireArrayComponentField, requireStringComponentField } from './_existing-component-fields'; export type AgentIdentityMode = 'runner' | 'user'; export interface AgentSourceFile { path: string; content: string; kind: string; } /** * Initialization properties for {@link Agent}. */ export interface AgentProps { /** Stable system name for this agent. */ apiName: string; /** Display name shown in Rippling. */ name: string; /** Human-readable description of the agent. */ description?: string; /** Identity used when the agent runs. Defaults to `runner`. */ identityMode?: AgentIdentityMode; /** Role used when `identityMode` is `user`. */ identityRoleId?: string; /** Source files for the agent's latest revision. */ files: AgentSourceFile[]; /** Path of the source file that starts the agent. */ entrypoint: string; /** Existing Custom Skills included in the agent's latest revision. */ skills?: CustomSkill[]; } /** * Defines an agent and its latest source revision in a single manifest component. * * Reinstalling a component with the same `apiName` updates the agent metadata and * creates a new source revision when its source or skills change. */ export class Agent { static readonly componentType = 'AGENT' as const; private readonly _apiName: string; private _name: string; private _description: string | undefined; private _identityMode: AgentIdentityMode | undefined; private _identityRoleId: string | undefined; private _files: AgentSourceFile[]; private _entrypoint: string; private _skills: CustomSkill[] | undefined; constructor(scope: ManifestScope, props: AgentProps) { this._apiName = props.apiName; this._name = props.name; this._description = props.description; this._identityMode = props.identityMode; this._identityRoleId = props.identityRoleId; this._files = props.files; this._entrypoint = props.entrypoint; this._skills = props.skills == null ? undefined : [...props.skills]; scope._register(this); } /** Loads this agent from the active existing-manifest JSON context. */ static loadFromExisting(apiName: string, options: ExistingManifestContextOptions = {}): Agent { return getExistingManifestContext(options).loadAgent(apiName); } /** @internal Hydrates an agent from existing manifest wire JSON. */ static _fromExistingComponent( scope: ManifestScope, component: Record, resolveSkill: (sourceSkillId: string) => CustomSkill, ): Agent { const props: AgentProps = { apiName: requireStringComponentField(component, 'api_name', Agent.componentType), name: requireStringComponentField(component, 'name', Agent.componentType), files: component['files'] ?? [], entrypoint: requireStringComponentField(component, 'entrypoint', Agent.componentType), }; if (component['description'] !== undefined) props.description = component['description']; if (component['identity_mode'] !== undefined) props.identityMode = component['identity_mode']; if (component['identity_role_id'] !== undefined) props.identityRoleId = component['identity_role_id']; if (component['skill_ids'] !== undefined) { props.skills = requireArrayComponentField(component, 'skill_ids', Agent.componentType).map( resolveSkill, ); } return new Agent(scope, props); } getApiName(): string { return this._apiName; } setName(name: string): this { this._name = name; return this; } setDescription(description: string): this { this._description = description; return this; } setIdentity(identityMode: AgentIdentityMode, identityRoleId?: string): this { this._identityMode = identityMode; this._identityRoleId = identityRoleId; return this; } setSource(files: AgentSourceFile[], entrypoint: string): this { this._files = files; this._entrypoint = entrypoint; return this; } setSkills(skills: CustomSkill[]): this { this._skills = [...skills]; return this; } toDict(): Record { const component: Record = { type: Agent.componentType, api_name: this._apiName, name: this._name, files: this._files, entrypoint: this._entrypoint, }; if (this._description !== undefined) component['description'] = this._description; if (this._identityMode !== undefined) component['identity_mode'] = this._identityMode; if (this._identityRoleId !== undefined) component['identity_role_id'] = this._identityRoleId; if (this._skills !== undefined) { component['skill_ids'] = this._skills.map((skill) => skill.getSourceSkillId()); } return component; } }