@evalstudio/core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/connector.d.ts +101 -0
  2. package/dist/connector.d.ts.map +1 -0
  3. package/dist/connector.js +477 -0
  4. package/dist/connector.js.map +1 -0
  5. package/dist/eval.d.ts +66 -0
  6. package/dist/eval.d.ts.map +1 -0
  7. package/dist/eval.js +188 -0
  8. package/dist/eval.js.map +1 -0
  9. package/dist/evaluator.d.ts +37 -0
  10. package/dist/evaluator.d.ts.map +1 -0
  11. package/dist/evaluator.js +121 -0
  12. package/dist/evaluator.js.map +1 -0
  13. package/dist/execution.d.ts +29 -0
  14. package/dist/execution.d.ts.map +1 -0
  15. package/dist/execution.js +94 -0
  16. package/dist/execution.js.map +1 -0
  17. package/dist/index.d.ts +17 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +16 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/llm-client.d.ts +31 -0
  22. package/dist/llm-client.d.ts.map +1 -0
  23. package/dist/llm-client.js +121 -0
  24. package/dist/llm-client.js.map +1 -0
  25. package/dist/llm-provider.d.ts +46 -0
  26. package/dist/llm-provider.d.ts.map +1 -0
  27. package/dist/llm-provider.js +199 -0
  28. package/dist/llm-provider.js.map +1 -0
  29. package/dist/persona-generator.d.ts +34 -0
  30. package/dist/persona-generator.d.ts.map +1 -0
  31. package/dist/persona-generator.js +99 -0
  32. package/dist/persona-generator.js.map +1 -0
  33. package/dist/persona.d.ts +28 -0
  34. package/dist/persona.d.ts.map +1 -0
  35. package/dist/persona.js +100 -0
  36. package/dist/persona.js.map +1 -0
  37. package/dist/project.d.ts +43 -0
  38. package/dist/project.d.ts.map +1 -0
  39. package/dist/project.js +114 -0
  40. package/dist/project.js.map +1 -0
  41. package/dist/prompt.d.ts +31 -0
  42. package/dist/prompt.d.ts.map +1 -0
  43. package/dist/prompt.js +73 -0
  44. package/dist/prompt.js.map +1 -0
  45. package/dist/run-processor.d.ts +127 -0
  46. package/dist/run-processor.d.ts.map +1 -0
  47. package/dist/run-processor.js +495 -0
  48. package/dist/run-processor.js.map +1 -0
  49. package/dist/run.d.ts +101 -0
  50. package/dist/run.d.ts.map +1 -0
  51. package/dist/run.js +279 -0
  52. package/dist/run.js.map +1 -0
  53. package/dist/scenario.d.ts +66 -0
  54. package/dist/scenario.d.ts.map +1 -0
  55. package/dist/scenario.js +110 -0
  56. package/dist/scenario.js.map +1 -0
  57. package/dist/status.d.ts +10 -0
  58. package/dist/status.d.ts.map +1 -0
  59. package/dist/status.js +15 -0
  60. package/dist/status.js.map +1 -0
  61. package/dist/storage.d.ts +11 -0
  62. package/dist/storage.d.ts.map +1 -0
  63. package/dist/storage.js +57 -0
  64. package/dist/storage.js.map +1 -0
  65. package/dist/types.d.ts +46 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +26 -0
  68. package/dist/types.js.map +1 -0
  69. package/package.json +51 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Returns the default model for a given provider type.
3
+ */
4
+ export function getDefaultModelForProvider(providerType) {
5
+ switch (providerType) {
6
+ case "openai":
7
+ return "gpt-4o-mini";
8
+ case "anthropic":
9
+ return "claude-3-5-haiku-20241022";
10
+ default:
11
+ return "gpt-4o-mini";
12
+ }
13
+ }
14
+ /**
15
+ * Sends a chat completion request to the appropriate provider API.
16
+ * Supports OpenAI and Anthropic via native fetch().
17
+ */
18
+ export async function chatCompletion(provider, messages, options) {
19
+ const model = options?.model || getDefaultModelForProvider(provider.provider);
20
+ switch (provider.provider) {
21
+ case "openai":
22
+ return openaiChatCompletion(provider, model, messages, options);
23
+ case "anthropic":
24
+ return anthropicChatCompletion(provider, model, messages, options);
25
+ default:
26
+ throw new Error(`Unsupported LLM provider: ${provider.provider}`);
27
+ }
28
+ }
29
+ /**
30
+ * OpenAI Chat Completions API.
31
+ */
32
+ async function openaiChatCompletion(provider, model, messages, options) {
33
+ const body = {
34
+ model,
35
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
36
+ };
37
+ if (options?.temperature !== undefined) {
38
+ body.temperature = options.temperature;
39
+ }
40
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
41
+ method: "POST",
42
+ headers: {
43
+ "Authorization": `Bearer ${provider.apiKey}`,
44
+ "Content-Type": "application/json",
45
+ },
46
+ body: JSON.stringify(body),
47
+ });
48
+ if (!response.ok) {
49
+ const errorBody = await response.text();
50
+ throw new Error(`OpenAI API error (${response.status}): ${errorBody}`);
51
+ }
52
+ const data = (await response.json());
53
+ const content = data.choices?.[0]?.message?.content;
54
+ if (content === undefined || content === null) {
55
+ throw new Error("OpenAI API returned no content in response");
56
+ }
57
+ return { content };
58
+ }
59
+ /**
60
+ * Anthropic Messages API.
61
+ *
62
+ * Handles Anthropic-specific requirements:
63
+ * - System message extracted to top-level `system` field
64
+ * - Messages must alternate user/assistant (consecutive same-role merged)
65
+ * - max_tokens is required
66
+ */
67
+ async function anthropicChatCompletion(provider, model, messages, options) {
68
+ // Extract system message(s)
69
+ const systemParts = [];
70
+ const nonSystemMessages = [];
71
+ for (const msg of messages) {
72
+ if (msg.role === "system") {
73
+ systemParts.push(msg.content);
74
+ }
75
+ else {
76
+ nonSystemMessages.push({ role: msg.role, content: msg.content });
77
+ }
78
+ }
79
+ // Merge consecutive same-role messages (Anthropic requires alternation)
80
+ const mergedMessages = [];
81
+ for (const msg of nonSystemMessages) {
82
+ const last = mergedMessages[mergedMessages.length - 1];
83
+ if (last && last.role === msg.role) {
84
+ last.content += "\n\n" + msg.content;
85
+ }
86
+ else {
87
+ mergedMessages.push({ ...msg });
88
+ }
89
+ }
90
+ const body = {
91
+ model,
92
+ max_tokens: 4096,
93
+ messages: mergedMessages,
94
+ };
95
+ if (systemParts.length > 0) {
96
+ body.system = systemParts.join("\n\n");
97
+ }
98
+ if (options?.temperature !== undefined) {
99
+ body.temperature = options.temperature;
100
+ }
101
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
102
+ method: "POST",
103
+ headers: {
104
+ "x-api-key": provider.apiKey,
105
+ "anthropic-version": "2023-06-01",
106
+ "Content-Type": "application/json",
107
+ },
108
+ body: JSON.stringify(body),
109
+ });
110
+ if (!response.ok) {
111
+ const errorBody = await response.text();
112
+ throw new Error(`Anthropic API error (${response.status}): ${errorBody}`);
113
+ }
114
+ const data = (await response.json());
115
+ const textBlock = data.content?.find((c) => c.type === "text");
116
+ if (!textBlock?.text) {
117
+ throw new Error("Anthropic API returned no text content in response");
118
+ }
119
+ return { content: textBlock.text };
120
+ }
121
+ //# sourceMappingURL=llm-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-client.js","sourceRoot":"","sources":["../src/llm-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export type ProviderType = "openai" | "anthropic";
2
+ export interface LLMProviderConfig {
3
+ [key: string]: unknown;
4
+ }
5
+ export interface LLMProvider {
6
+ id: string;
7
+ projectId: string;
8
+ name: string;
9
+ provider: ProviderType;
10
+ apiKey: string;
11
+ config?: LLMProviderConfig;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ }
15
+ export interface CreateLLMProviderInput {
16
+ projectId: string;
17
+ name: string;
18
+ provider: ProviderType;
19
+ apiKey: string;
20
+ config?: LLMProviderConfig;
21
+ }
22
+ export interface UpdateLLMProviderInput {
23
+ name?: string;
24
+ provider?: ProviderType;
25
+ apiKey?: string;
26
+ config?: LLMProviderConfig;
27
+ }
28
+ export declare function createLLMProvider(input: CreateLLMProviderInput): LLMProvider;
29
+ export declare function getLLMProvider(id: string): LLMProvider | undefined;
30
+ export declare function getLLMProviderByName(projectId: string, name: string): LLMProvider | undefined;
31
+ export declare function listLLMProviders(projectId?: string): LLMProvider[];
32
+ export declare function updateLLMProvider(id: string, input: UpdateLLMProviderInput): LLMProvider | undefined;
33
+ export declare function deleteLLMProvider(id: string): boolean;
34
+ export declare function deleteLLMProvidersByProject(projectId: string): number;
35
+ /**
36
+ * Returns the default/fallback models for each provider type
37
+ * Used when API fetching fails or for Anthropic (which doesn't have a models endpoint)
38
+ */
39
+ export declare function getDefaultModels(): Record<ProviderType, string[]>;
40
+ /**
41
+ * Fetches available models from the provider's API
42
+ * For OpenAI: uses /v1/models endpoint
43
+ * For Anthropic: returns default list (no public models endpoint)
44
+ */
45
+ export declare function fetchProviderModels(providerId: string): Promise<string[]>;
46
+ //# sourceMappingURL=llm-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider.d.ts","sourceRoot":"","sources":["../src/llm-provider.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAoBD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,WAAW,CAkC5E;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAGlE;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,WAAW,GAAG,SAAS,CAGzB;AAED,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAMlE;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,sBAAsB,GAC5B,WAAW,GAAG,SAAS,CAmCzB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAYrD;AAED,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,CAuBjE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB"}
@@ -0,0 +1,199 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getProject } from "./project.js";
5
+ import { getStorageDir } from "./storage.js";
6
+ function getStoragePath() {
7
+ return join(getStorageDir(), "llm-providers.json");
8
+ }
9
+ function loadLLMProviders() {
10
+ const path = getStoragePath();
11
+ if (!existsSync(path)) {
12
+ return [];
13
+ }
14
+ const data = readFileSync(path, "utf-8");
15
+ return JSON.parse(data);
16
+ }
17
+ function saveLLMProviders(providers) {
18
+ const path = getStoragePath();
19
+ writeFileSync(path, JSON.stringify(providers, null, 2));
20
+ }
21
+ export function createLLMProvider(input) {
22
+ const project = getProject(input.projectId);
23
+ if (!project) {
24
+ throw new Error(`Project with id "${input.projectId}" not found`);
25
+ }
26
+ const providers = loadLLMProviders();
27
+ if (providers.some((p) => p.projectId === input.projectId && p.name === input.name)) {
28
+ throw new Error(`LLM Provider with name "${input.name}" already exists in this project`);
29
+ }
30
+ const now = new Date().toISOString();
31
+ const provider = {
32
+ id: randomUUID(),
33
+ projectId: input.projectId,
34
+ name: input.name,
35
+ provider: input.provider,
36
+ apiKey: input.apiKey,
37
+ config: input.config,
38
+ createdAt: now,
39
+ updatedAt: now,
40
+ };
41
+ providers.push(provider);
42
+ saveLLMProviders(providers);
43
+ return provider;
44
+ }
45
+ export function getLLMProvider(id) {
46
+ const providers = loadLLMProviders();
47
+ return providers.find((p) => p.id === id);
48
+ }
49
+ export function getLLMProviderByName(projectId, name) {
50
+ const providers = loadLLMProviders();
51
+ return providers.find((p) => p.projectId === projectId && p.name === name);
52
+ }
53
+ export function listLLMProviders(projectId) {
54
+ const providers = loadLLMProviders();
55
+ if (projectId) {
56
+ return providers.filter((p) => p.projectId === projectId);
57
+ }
58
+ return providers;
59
+ }
60
+ export function updateLLMProvider(id, input) {
61
+ const providers = loadLLMProviders();
62
+ const index = providers.findIndex((p) => p.id === id);
63
+ if (index === -1) {
64
+ return undefined;
65
+ }
66
+ const provider = providers[index];
67
+ if (input.name &&
68
+ providers.some((p) => p.projectId === provider.projectId && p.name === input.name && p.id !== id)) {
69
+ throw new Error(`LLM Provider with name "${input.name}" already exists in this project`);
70
+ }
71
+ const updated = {
72
+ ...provider,
73
+ name: input.name ?? provider.name,
74
+ provider: input.provider ?? provider.provider,
75
+ apiKey: input.apiKey ?? provider.apiKey,
76
+ config: input.config ?? provider.config,
77
+ updatedAt: new Date().toISOString(),
78
+ };
79
+ providers[index] = updated;
80
+ saveLLMProviders(providers);
81
+ return updated;
82
+ }
83
+ export function deleteLLMProvider(id) {
84
+ const providers = loadLLMProviders();
85
+ const index = providers.findIndex((p) => p.id === id);
86
+ if (index === -1) {
87
+ return false;
88
+ }
89
+ providers.splice(index, 1);
90
+ saveLLMProviders(providers);
91
+ return true;
92
+ }
93
+ export function deleteLLMProvidersByProject(projectId) {
94
+ const providers = loadLLMProviders();
95
+ const filtered = providers.filter((p) => p.projectId !== projectId);
96
+ const deletedCount = providers.length - filtered.length;
97
+ if (deletedCount > 0) {
98
+ saveLLMProviders(filtered);
99
+ }
100
+ return deletedCount;
101
+ }
102
+ /**
103
+ * Returns the default/fallback models for each provider type
104
+ * Used when API fetching fails or for Anthropic (which doesn't have a models endpoint)
105
+ */
106
+ export function getDefaultModels() {
107
+ return {
108
+ openai: [
109
+ "gpt-4.1",
110
+ "gpt-4.1-mini",
111
+ "gpt-4.1-nano",
112
+ "o3",
113
+ "o4-mini",
114
+ "o3-mini",
115
+ "o1",
116
+ "o1-mini",
117
+ "gpt-4o",
118
+ "gpt-4o-mini",
119
+ ],
120
+ anthropic: [
121
+ "claude-opus-4-5-20251101",
122
+ "claude-sonnet-4-5-20250929",
123
+ "claude-opus-4-20250514",
124
+ "claude-sonnet-4-20250514",
125
+ "claude-3-5-sonnet-20241022",
126
+ "claude-3-5-haiku-20241022",
127
+ ],
128
+ };
129
+ }
130
+ /**
131
+ * Fetches available models from the provider's API
132
+ * For OpenAI: uses /v1/models endpoint
133
+ * For Anthropic: returns default list (no public models endpoint)
134
+ */
135
+ export async function fetchProviderModels(providerId) {
136
+ const provider = getLLMProvider(providerId);
137
+ if (!provider) {
138
+ throw new Error(`LLM Provider "${providerId}" not found`);
139
+ }
140
+ if (provider.provider === "openai") {
141
+ return fetchOpenAIModels(provider.apiKey);
142
+ }
143
+ // Anthropic doesn't have a public models endpoint, use defaults
144
+ return getDefaultModels().anthropic;
145
+ }
146
+ /**
147
+ * Fetches models from OpenAI API and filters for chat-capable models
148
+ */
149
+ async function fetchOpenAIModels(apiKey) {
150
+ const response = await fetch("https://api.openai.com/v1/models", {
151
+ headers: {
152
+ Authorization: `Bearer ${apiKey}`,
153
+ },
154
+ });
155
+ if (!response.ok) {
156
+ throw new Error(`Failed to fetch OpenAI models: ${response.statusText}`);
157
+ }
158
+ const data = (await response.json());
159
+ // Filter for chat/completion models and sort by preference
160
+ const chatModels = data.data
161
+ .map((m) => m.id)
162
+ .filter((id) => {
163
+ // Include GPT models, O-series reasoning models
164
+ return (id.startsWith("gpt-") ||
165
+ id.startsWith("o1") ||
166
+ id.startsWith("o3") ||
167
+ id.startsWith("o4"));
168
+ })
169
+ .filter((id) => {
170
+ // Exclude internal/preview variants
171
+ return (!id.includes("preview") &&
172
+ !id.includes("realtime") &&
173
+ !id.includes("audio") &&
174
+ !id.includes("transcribe") &&
175
+ !id.includes("tts") &&
176
+ !id.includes("whisper") &&
177
+ !id.includes("dall-e") &&
178
+ !id.includes("embedding"));
179
+ })
180
+ .sort((a, b) => {
181
+ // Sort: gpt-4.1 > o-series > gpt-4o > gpt-4 > gpt-3.5
182
+ const order = (id) => {
183
+ if (id.startsWith("gpt-4.1"))
184
+ return 0;
185
+ if (id.startsWith("o3") || id.startsWith("o4"))
186
+ return 1;
187
+ if (id.startsWith("o1"))
188
+ return 2;
189
+ if (id.startsWith("gpt-4o"))
190
+ return 3;
191
+ if (id.startsWith("gpt-4"))
192
+ return 4;
193
+ return 5;
194
+ };
195
+ return order(a) - order(b) || a.localeCompare(b);
196
+ });
197
+ return chatModels;
198
+ }
199
+ //# sourceMappingURL=llm-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider.js","sourceRoot":"","sources":["../src/llm-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAkC7C,SAAS,cAAc;IACrB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,oBAAoB,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;AAC3C,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAwB;IAChD,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,oBAAoB,KAAK,CAAC,SAAS,aAAa,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IAErC,IACE,SAAS,CAAC,IAAI,CACZ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAChE,EACD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,2BAA2B,KAAK,CAAC,IAAI,kCAAkC,CACxE,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAgB;QAC5B,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAC;IAEF,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzB,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE5B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,SAAiB,EACjB,IAAY;IAEZ,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAkB;IACjD,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,EAAU,EACV,KAA6B;IAE7B,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAEtD,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAElC,IACE,KAAK,CAAC,IAAI;QACV,SAAS,CAAC,IAAI,CACZ,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAC7E,EACD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,2BAA2B,KAAK,CAAC,IAAI,kCAAkC,CACxE,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAgB;QAC3B,GAAG,QAAQ;QACX,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;QAC7C,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;QACvC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;QACvC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;IAC3B,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE5B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAEtD,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC3B,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE5B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,SAAiB;IAC3D,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAExD,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACrB,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,MAAM,EAAE;YACN,SAAS;YACT,cAAc;YACd,cAAc;YACd,IAAI;YACJ,SAAS;YACT,SAAS;YACT,IAAI;YACJ,SAAS;YACT,QAAQ;YACR,aAAa;SACd;QACD,SAAS,EAAE;YACT,0BAA0B;YAC1B,4BAA4B;YAC5B,wBAAwB;YACxB,0BAA0B;YAC1B,4BAA4B;YAC5B,2BAA2B;SAC5B;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAkB;IAElB,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,iBAAiB,UAAU,aAAa,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACnC,OAAO,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED,gEAAgE;IAChE,OAAO,gBAAgB,EAAE,CAAC,SAAS,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,iBAAiB,CAAC,MAAc;IAC7C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kCAAkC,EAAE;QAC/D,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,EAAE;SAClC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAElC,CAAC;IAEF,2DAA2D;IAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI;SACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QACb,gDAAgD;QAChD,OAAO,CACL,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;YACrB,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YACnB,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YACnB,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CACpB,CAAC;IACJ,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QACb,oCAAoC;QACpC,OAAO,CACL,CAAC,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;YACvB,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;YACxB,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;YACrB,CAAC,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC1B,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;YACnB,CAAC,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;YACvB,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACtB,CAAC,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAC1B,CAAC;IACJ,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACb,sDAAsD;QACtD,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE;YAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;gBAAE,OAAO,CAAC,CAAC;YACvC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,CAAC;YACzD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,CAAC;YAClC,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,OAAO,CAAC,CAAC;YACtC,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;gBAAE,OAAO,CAAC,CAAC;YACrC,OAAO,CAAC,CAAC;QACX,CAAC,CAAC;QACF,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEL,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { Message } from "./types.js";
2
+ import type { Persona } from "./persona.js";
3
+ import type { Scenario } from "./scenario.js";
4
+ /**
5
+ * Input for generating a persona message
6
+ */
7
+ export interface GeneratePersonaMessageInput {
8
+ /** The conversation history so far */
9
+ messages: Message[];
10
+ /** The persona to impersonate (optional - if not provided, generates a generic user message) */
11
+ persona?: Persona;
12
+ /** The scenario context */
13
+ scenario: Scenario;
14
+ /** LLM provider ID to use for generation */
15
+ llmProviderId: string;
16
+ /** Model to use (optional, defaults based on provider) */
17
+ model?: string;
18
+ }
19
+ /**
20
+ * Result of generating a persona message
21
+ */
22
+ export interface GeneratePersonaMessageResult {
23
+ /** The generated message content */
24
+ content: string;
25
+ /** Raw response from the LLM */
26
+ rawResponse?: string;
27
+ }
28
+ /**
29
+ * Generates a user message based on the conversation context.
30
+ * Uses the LLM provider to create a natural response, optionally impersonating a persona.
31
+ * If no persona is provided, generates a generic but contextually appropriate user message.
32
+ */
33
+ export declare function generatePersonaMessage(input: GeneratePersonaMessageInput): Promise<GeneratePersonaMessageResult>;
34
+ //# sourceMappingURL=persona-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persona-generator.d.ts","sourceRoot":"","sources":["../src/persona-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAI9C;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,sCAAsC;IACtC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2BAA2B;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,2BAA2B,GACjC,OAAO,CAAC,4BAA4B,CAAC,CA0CvC"}
@@ -0,0 +1,99 @@
1
+ import { getLLMProvider } from "./llm-provider.js";
2
+ import { chatCompletion } from "./llm-client.js";
3
+ /**
4
+ * Builds the system prompt for persona message generation
5
+ */
6
+ function buildPersonaSystemPrompt(persona, scenario) {
7
+ let prompt;
8
+ if (persona) {
9
+ prompt = `You are a test agent simulating a user interaction with a chatbot.
10
+ Your role is to impersonate a specific user persona and simulate a realistic conversation
11
+ based on the given scenario. Behave naturally as this user would, staying in character
12
+ throughout the conversation.
13
+
14
+ ## User Persona
15
+
16
+ Name: ${persona.name}
17
+ `;
18
+ if (persona.description) {
19
+ prompt += `Description: ${persona.description}\n`;
20
+ }
21
+ if (persona.systemPrompt) {
22
+ prompt += `\nCharacter Instructions:\n${persona.systemPrompt}\n`;
23
+ }
24
+ }
25
+ else {
26
+ prompt = `You are a test agent simulating a user interaction with a chatbot.
27
+ Your role is to simulate a realistic conversation based on the given scenario.
28
+ Behave naturally as a typical user would throughout the conversation.
29
+ `;
30
+ }
31
+ if (scenario.instructions) {
32
+ prompt += `
33
+ ## Scenario
34
+
35
+ ${scenario.instructions}
36
+ `;
37
+ }
38
+ if (scenario.successCriteria) {
39
+ prompt += `
40
+ ## Goal
41
+ Try to achieve the following success criteria through natural conversation:
42
+ ${scenario.successCriteria}
43
+ `;
44
+ }
45
+ prompt += `
46
+ ## Guidelines
47
+
48
+ - ${persona ? "Stay in character as the user persona throughout the conversation" : "Act as a realistic user throughout the conversation"}
49
+ - Follow the scenario context to guide your messages and goals
50
+ - Respond naturally as a real user would, with realistic questions, concerns, or requests
51
+ - Do not break character or reveal that you are a test agent
52
+ - If the chatbot asks clarifying questions, answer them based on the scenario
53
+ - Express appropriate emotions based on the scenario (frustration, curiosity, urgency, etc.)
54
+ - Keep responses concise and natural - avoid overly long messages
55
+ - Respond ONLY with the user's message content, no additional formatting or explanation`;
56
+ return prompt;
57
+ }
58
+ /**
59
+ * Generates a user message based on the conversation context.
60
+ * Uses the LLM provider to create a natural response, optionally impersonating a persona.
61
+ * If no persona is provided, generates a generic but contextually appropriate user message.
62
+ */
63
+ export async function generatePersonaMessage(input) {
64
+ const { messages, persona, scenario, llmProviderId, model } = input;
65
+ // Get the LLM provider
66
+ const llmProvider = getLLMProvider(llmProviderId);
67
+ if (!llmProvider) {
68
+ throw new Error(`LLM Provider with id "${llmProviderId}" not found`);
69
+ }
70
+ // Build system prompt
71
+ const systemPrompt = buildPersonaSystemPrompt(persona, scenario);
72
+ // Build instruction text
73
+ const instructionText = persona
74
+ ? "Based on the conversation above, generate the next message from the user persona. Respond ONLY with the message content."
75
+ : "Based on the conversation above, generate the next message from the user. Respond ONLY with the message content.";
76
+ // Build messages array: system + history + instruction
77
+ const chatMessages = [
78
+ { role: "system", content: systemPrompt },
79
+ ];
80
+ // Add history messages (skip system messages, map roles)
81
+ for (const m of messages) {
82
+ if (m.role === "system")
83
+ continue;
84
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
85
+ chatMessages.push({
86
+ role: m.role === "assistant" ? "assistant" : "user",
87
+ content,
88
+ });
89
+ }
90
+ // Add instruction message
91
+ chatMessages.push({ role: "user", content: instructionText });
92
+ // Invoke the LLM
93
+ const response = await chatCompletion(llmProvider, chatMessages, { model });
94
+ return {
95
+ content: response.content.trim(),
96
+ rawResponse: response.content,
97
+ };
98
+ }
99
+ //# sourceMappingURL=persona-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persona-generator.js","sourceRoot":"","sources":["../src/persona-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export interface Persona {
2
+ id: string;
3
+ projectId: string;
4
+ name: string;
5
+ description?: string;
6
+ systemPrompt?: string;
7
+ createdAt: string;
8
+ updatedAt: string;
9
+ }
10
+ export interface CreatePersonaInput {
11
+ projectId: string;
12
+ name: string;
13
+ description?: string;
14
+ systemPrompt?: string;
15
+ }
16
+ export interface UpdatePersonaInput {
17
+ name?: string;
18
+ description?: string;
19
+ systemPrompt?: string;
20
+ }
21
+ export declare function createPersona(input: CreatePersonaInput): Persona;
22
+ export declare function getPersona(id: string): Persona | undefined;
23
+ export declare function getPersonaByName(projectId: string, name: string): Persona | undefined;
24
+ export declare function listPersonas(projectId?: string): Persona[];
25
+ export declare function updatePersona(id: string, input: UpdatePersonaInput): Persona | undefined;
26
+ export declare function deletePersona(id: string): boolean;
27
+ export declare function deletePersonasByProject(projectId: string): number;
28
+ //# sourceMappingURL=persona.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persona.d.ts","sourceRoot":"","sources":["../src/persona.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAoBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAiChE;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAG1D;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,GAAG,SAAS,CAGrB;AAED,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAM1D;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,kBAAkB,GACxB,OAAO,GAAG,SAAS,CAkCrB;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAYjD;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUjE"}
@@ -0,0 +1,100 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getProject } from "./project.js";
5
+ import { getStorageDir } from "./storage.js";
6
+ function getStoragePath() {
7
+ return join(getStorageDir(), "personas.json");
8
+ }
9
+ function loadPersonas() {
10
+ const path = getStoragePath();
11
+ if (!existsSync(path)) {
12
+ return [];
13
+ }
14
+ const data = readFileSync(path, "utf-8");
15
+ return JSON.parse(data);
16
+ }
17
+ function savePersonas(personas) {
18
+ const path = getStoragePath();
19
+ writeFileSync(path, JSON.stringify(personas, null, 2));
20
+ }
21
+ export function createPersona(input) {
22
+ const project = getProject(input.projectId);
23
+ if (!project) {
24
+ throw new Error(`Project with id "${input.projectId}" not found`);
25
+ }
26
+ const personas = loadPersonas();
27
+ if (personas.some((p) => p.projectId === input.projectId && p.name === input.name)) {
28
+ throw new Error(`Persona with name "${input.name}" already exists in this project`);
29
+ }
30
+ const now = new Date().toISOString();
31
+ const persona = {
32
+ id: randomUUID(),
33
+ projectId: input.projectId,
34
+ name: input.name,
35
+ description: input.description,
36
+ systemPrompt: input.systemPrompt,
37
+ createdAt: now,
38
+ updatedAt: now,
39
+ };
40
+ personas.push(persona);
41
+ savePersonas(personas);
42
+ return persona;
43
+ }
44
+ export function getPersona(id) {
45
+ const personas = loadPersonas();
46
+ return personas.find((p) => p.id === id);
47
+ }
48
+ export function getPersonaByName(projectId, name) {
49
+ const personas = loadPersonas();
50
+ return personas.find((p) => p.projectId === projectId && p.name === name);
51
+ }
52
+ export function listPersonas(projectId) {
53
+ const personas = loadPersonas();
54
+ if (projectId) {
55
+ return personas.filter((p) => p.projectId === projectId);
56
+ }
57
+ return personas;
58
+ }
59
+ export function updatePersona(id, input) {
60
+ const personas = loadPersonas();
61
+ const index = personas.findIndex((p) => p.id === id);
62
+ if (index === -1) {
63
+ return undefined;
64
+ }
65
+ const persona = personas[index];
66
+ if (input.name &&
67
+ personas.some((p) => p.projectId === persona.projectId && p.name === input.name && p.id !== id)) {
68
+ throw new Error(`Persona with name "${input.name}" already exists in this project`);
69
+ }
70
+ const updated = {
71
+ ...persona,
72
+ name: input.name ?? persona.name,
73
+ description: input.description ?? persona.description,
74
+ systemPrompt: input.systemPrompt ?? persona.systemPrompt,
75
+ updatedAt: new Date().toISOString(),
76
+ };
77
+ personas[index] = updated;
78
+ savePersonas(personas);
79
+ return updated;
80
+ }
81
+ export function deletePersona(id) {
82
+ const personas = loadPersonas();
83
+ const index = personas.findIndex((p) => p.id === id);
84
+ if (index === -1) {
85
+ return false;
86
+ }
87
+ personas.splice(index, 1);
88
+ savePersonas(personas);
89
+ return true;
90
+ }
91
+ export function deletePersonasByProject(projectId) {
92
+ const personas = loadPersonas();
93
+ const filtered = personas.filter((p) => p.projectId !== projectId);
94
+ const deletedCount = personas.length - filtered.length;
95
+ if (deletedCount > 0) {
96
+ savePersonas(filtered);
97
+ }
98
+ return deletedCount;
99
+ }
100
+ //# sourceMappingURL=persona.js.map