@shahmarasy/prodo 0.1.4 → 0.1.5

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 (173) hide show
  1. package/README.md +201 -97
  2. package/bin/prodo.cjs +6 -6
  3. package/dist/agents/agent-registry.d.ts +13 -0
  4. package/dist/agents/agent-registry.js +79 -0
  5. package/dist/agents/anthropic/index.d.ts +9 -0
  6. package/dist/agents/anthropic/index.js +55 -0
  7. package/dist/agents/base.d.ts +25 -0
  8. package/dist/agents/base.js +71 -0
  9. package/dist/agents/google/index.d.ts +9 -0
  10. package/dist/agents/google/index.js +53 -0
  11. package/dist/agents/mock/index.d.ts +11 -0
  12. package/dist/agents/mock/index.js +26 -0
  13. package/dist/agents/openai/index.d.ts +9 -0
  14. package/dist/agents/openai/index.js +57 -0
  15. package/dist/agents/system-prompts.d.ts +3 -0
  16. package/dist/agents/system-prompts.js +32 -0
  17. package/dist/cli/agent-command-installer.d.ts +4 -0
  18. package/dist/cli/agent-command-installer.js +148 -0
  19. package/dist/cli/agent-ids.d.ts +15 -0
  20. package/dist/cli/agent-ids.js +49 -0
  21. package/dist/cli/doctor.d.ts +1 -0
  22. package/dist/cli/doctor.js +144 -0
  23. package/dist/cli/fix-tui.d.ts +4 -0
  24. package/dist/cli/fix-tui.js +79 -0
  25. package/dist/cli/index.d.ts +9 -0
  26. package/dist/cli/index.js +465 -0
  27. package/dist/cli/init-tui.d.ts +23 -0
  28. package/dist/cli/init-tui.js +176 -0
  29. package/dist/cli/init.d.ts +11 -0
  30. package/dist/cli/init.js +334 -0
  31. package/dist/cli/normalize-interactive.d.ts +8 -0
  32. package/dist/cli/normalize-interactive.js +167 -0
  33. package/dist/cli/preset-loader.d.ts +4 -0
  34. package/dist/cli/preset-loader.js +210 -0
  35. package/dist/core/artifact-registry.d.ts +11 -0
  36. package/dist/core/artifact-registry.js +49 -0
  37. package/dist/core/artifacts.d.ts +10 -0
  38. package/dist/core/artifacts.js +892 -0
  39. package/dist/core/clean.d.ts +10 -0
  40. package/dist/core/clean.js +74 -0
  41. package/dist/core/consistency.d.ts +8 -0
  42. package/dist/core/consistency.js +328 -0
  43. package/dist/core/constants.d.ts +7 -0
  44. package/dist/core/constants.js +64 -0
  45. package/dist/core/errors.d.ts +3 -0
  46. package/dist/core/errors.js +10 -0
  47. package/dist/core/fix.d.ts +31 -0
  48. package/dist/core/fix.js +188 -0
  49. package/dist/core/hook-executor.d.ts +1 -0
  50. package/dist/core/hook-executor.js +175 -0
  51. package/dist/core/markdown.d.ts +16 -0
  52. package/dist/core/markdown.js +81 -0
  53. package/dist/core/normalize.d.ts +8 -0
  54. package/dist/core/normalize.js +125 -0
  55. package/dist/core/normalized-brief.d.ts +48 -0
  56. package/dist/core/normalized-brief.js +182 -0
  57. package/dist/core/output-index.d.ts +13 -0
  58. package/dist/core/output-index.js +55 -0
  59. package/dist/core/paths.d.ts +17 -0
  60. package/dist/core/paths.js +80 -0
  61. package/dist/core/project-config.d.ts +14 -0
  62. package/dist/core/project-config.js +69 -0
  63. package/dist/core/registry.d.ts +13 -0
  64. package/dist/core/registry.js +115 -0
  65. package/dist/core/settings.d.ts +7 -0
  66. package/dist/core/settings.js +35 -0
  67. package/dist/core/template-engine.d.ts +3 -0
  68. package/dist/core/template-engine.js +43 -0
  69. package/dist/core/template-resolver.d.ts +15 -0
  70. package/dist/core/template-resolver.js +46 -0
  71. package/dist/core/templates.d.ts +33 -0
  72. package/dist/core/templates.js +440 -0
  73. package/dist/core/terminology.d.ts +21 -0
  74. package/dist/core/terminology.js +143 -0
  75. package/dist/core/tracing.d.ts +21 -0
  76. package/dist/core/tracing.js +74 -0
  77. package/dist/core/types.d.ts +35 -0
  78. package/dist/core/types.js +5 -0
  79. package/dist/core/utils.d.ts +7 -0
  80. package/dist/core/utils.js +66 -0
  81. package/dist/core/validate.d.ts +10 -0
  82. package/dist/core/validate.js +226 -0
  83. package/dist/core/validator.d.ts +5 -0
  84. package/dist/core/validator.js +76 -0
  85. package/dist/core/version.d.ts +1 -0
  86. package/dist/core/version.js +30 -0
  87. package/dist/core/workflow-commands.d.ts +7 -0
  88. package/dist/core/workflow-commands.js +29 -0
  89. package/dist/i18n/en.json +45 -0
  90. package/dist/i18n/index.d.ts +5 -0
  91. package/dist/i18n/index.js +63 -0
  92. package/dist/i18n/tr.json +45 -0
  93. package/dist/providers/index.d.ts +2 -1
  94. package/dist/providers/index.js +20 -6
  95. package/dist/providers/mock-provider.d.ts +1 -1
  96. package/dist/providers/mock-provider.js +7 -6
  97. package/dist/providers/openai-provider.d.ts +1 -1
  98. package/dist/providers/openai-provider.js +1 -1
  99. package/dist/skills/engine.d.ts +10 -0
  100. package/dist/skills/engine.js +75 -0
  101. package/dist/skills/fix-skill.d.ts +2 -0
  102. package/dist/skills/fix-skill.js +38 -0
  103. package/dist/skills/generate-artifact-skill.d.ts +2 -0
  104. package/dist/skills/generate-artifact-skill.js +32 -0
  105. package/dist/skills/generate-pipeline-skill.d.ts +2 -0
  106. package/dist/skills/generate-pipeline-skill.js +45 -0
  107. package/dist/skills/normalize-skill.d.ts +2 -0
  108. package/dist/skills/normalize-skill.js +29 -0
  109. package/dist/skills/types.d.ts +28 -0
  110. package/dist/skills/types.js +2 -0
  111. package/dist/skills/validate-skill.d.ts +2 -0
  112. package/dist/skills/validate-skill.js +29 -0
  113. package/package.json +74 -45
  114. package/src/agents/agent-registry.ts +93 -0
  115. package/src/agents/anthropic/index.ts +86 -0
  116. package/src/agents/anthropic/manifest.json +7 -0
  117. package/src/agents/base.ts +77 -0
  118. package/src/agents/google/index.ts +79 -0
  119. package/src/agents/google/manifest.json +7 -0
  120. package/src/agents/mock/index.ts +32 -0
  121. package/src/agents/mock/manifest.json +7 -0
  122. package/src/agents/openai/index.ts +83 -0
  123. package/src/agents/openai/manifest.json +7 -0
  124. package/src/agents/system-prompts.ts +35 -0
  125. package/src/{agent-command-installer.ts → cli/agent-command-installer.ts} +164 -164
  126. package/src/{agents.ts → cli/agent-ids.ts} +58 -58
  127. package/src/{doctor.ts → cli/doctor.ts} +157 -137
  128. package/src/cli/fix-tui.ts +111 -0
  129. package/src/{cli.ts → cli/index.ts} +459 -410
  130. package/src/{init-tui.ts → cli/init-tui.ts} +208 -208
  131. package/src/{init.ts → cli/init.ts} +398 -398
  132. package/src/cli/normalize-interactive.ts +241 -0
  133. package/src/{preset-loader.ts → cli/preset-loader.ts} +237 -237
  134. package/src/{artifact-registry.ts → core/artifact-registry.ts} +69 -69
  135. package/src/{artifacts.ts → core/artifacts.ts} +1081 -1072
  136. package/src/core/clean.ts +88 -0
  137. package/src/{consistency.ts → core/consistency.ts} +374 -303
  138. package/src/{constants.ts → core/constants.ts} +72 -72
  139. package/src/{errors.ts → core/errors.ts} +7 -7
  140. package/src/core/fix.ts +253 -0
  141. package/src/{hook-executor.ts → core/hook-executor.ts} +196 -196
  142. package/src/{markdown.ts → core/markdown.ts} +93 -73
  143. package/src/{normalize.ts → core/normalize.ts} +145 -137
  144. package/src/{normalized-brief.ts → core/normalized-brief.ts} +227 -206
  145. package/src/{output-index.ts → core/output-index.ts} +59 -59
  146. package/src/{paths.ts → core/paths.ts} +75 -71
  147. package/src/{project-config.ts → core/project-config.ts} +78 -78
  148. package/src/{registry.ts → core/registry.ts} +119 -119
  149. package/src/{settings.ts → core/settings.ts} +35 -35
  150. package/src/core/template-engine.ts +45 -0
  151. package/src/{template-resolver.ts → core/template-resolver.ts} +54 -54
  152. package/src/{templates.ts → core/templates.ts} +452 -452
  153. package/src/core/terminology.ts +177 -0
  154. package/src/core/tracing.ts +110 -0
  155. package/src/{types.ts → core/types.ts} +46 -46
  156. package/src/{utils.ts → core/utils.ts} +64 -64
  157. package/src/{validate.ts → core/validate.ts} +252 -246
  158. package/src/{validator.ts → core/validator.ts} +92 -92
  159. package/src/{version.ts → core/version.ts} +24 -24
  160. package/src/{workflow-commands.ts → core/workflow-commands.ts} +32 -32
  161. package/src/i18n/en.json +45 -0
  162. package/src/i18n/index.ts +58 -0
  163. package/src/i18n/tr.json +45 -0
  164. package/src/providers/index.ts +29 -12
  165. package/src/providers/mock-provider.ts +200 -199
  166. package/src/providers/openai-provider.ts +88 -88
  167. package/src/skills/engine.ts +94 -0
  168. package/src/skills/fix-skill.ts +38 -0
  169. package/src/skills/generate-artifact-skill.ts +32 -0
  170. package/src/skills/generate-pipeline-skill.ts +49 -0
  171. package/src/skills/normalize-skill.ts +29 -0
  172. package/src/skills/types.ts +36 -0
  173. package/src/skills/validate-skill.ts +29 -0
@@ -0,0 +1,93 @@
1
+ import type { AgentPlugin } from "./base";
2
+ import { UserError } from "../core/errors";
3
+ import type { LLMProvider } from "../core/types";
4
+ import { MockAgent } from "./mock";
5
+ import { OpenAIAgent } from "./openai";
6
+ import { AnthropicAgent } from "./anthropic";
7
+ import { GoogleAgent } from "./google";
8
+
9
+ const AGENT_ALIASES: Record<string, string> = {
10
+ mock: "mock",
11
+ openai: "openai",
12
+ anthropic: "anthropic",
13
+ claude: "anthropic",
14
+ google: "google",
15
+ gemini: "google"
16
+ };
17
+
18
+ export class AgentRegistry {
19
+ private agents = new Map<string, AgentPlugin>();
20
+
21
+ register(agent: AgentPlugin): void {
22
+ this.agents.set(agent.name, agent);
23
+ }
24
+
25
+ list(): AgentPlugin[] {
26
+ return Array.from(this.agents.values());
27
+ }
28
+
29
+ get(name: string): AgentPlugin | undefined {
30
+ const normalized = AGENT_ALIASES[name.toLowerCase().trim()] ?? name.toLowerCase().trim();
31
+ return this.agents.get(normalized);
32
+ }
33
+
34
+ async resolve(name?: string): Promise<AgentPlugin> {
35
+ const agentName = this.resolveAgentName(name);
36
+ const agent = this.get(agentName);
37
+
38
+ if (!agent) {
39
+ const available = this.list().map((a) => a.name).join(", ");
40
+ throw new UserError(
41
+ `Unknown agent: "${agentName}". Available agents: ${available}`
42
+ );
43
+ }
44
+
45
+ return agent;
46
+ }
47
+
48
+ private resolveAgentName(override?: string): string {
49
+ if (override) return override;
50
+
51
+ const fromEnv = process.env.PRODO_AGENT;
52
+ if (fromEnv) return fromEnv;
53
+
54
+ const fromLegacy = process.env.PRODO_LLM_PROVIDER;
55
+ if (fromLegacy) return fromLegacy;
56
+
57
+ const isTest =
58
+ process.env.NODE_ENV === "test" ||
59
+ process.env.PRODO_TEST === "1";
60
+ if (isTest) return "mock";
61
+
62
+ return "mock";
63
+ }
64
+
65
+ toProvider(agent: AgentPlugin): LLMProvider {
66
+ return {
67
+ generate: (prompt, inputContext, schemaHint) =>
68
+ agent.generate(prompt, inputContext, schemaHint)
69
+ };
70
+ }
71
+ }
72
+
73
+ let globalRegistry: AgentRegistry | null = null;
74
+
75
+ function createDefaultRegistry(): AgentRegistry {
76
+ const registry = new AgentRegistry();
77
+ registry.register(new MockAgent());
78
+ registry.register(new OpenAIAgent());
79
+ registry.register(new AnthropicAgent());
80
+ registry.register(new GoogleAgent());
81
+ return registry;
82
+ }
83
+
84
+ export function getGlobalRegistry(): AgentRegistry {
85
+ if (!globalRegistry) {
86
+ globalRegistry = createDefaultRegistry();
87
+ }
88
+ return globalRegistry;
89
+ }
90
+
91
+ export function resetGlobalRegistry(): void {
92
+ globalRegistry = null;
93
+ }
@@ -0,0 +1,86 @@
1
+ import { BaseAgent, type AgentConfig } from "../base";
2
+ import { buildSystemPrompt, buildUserMessage } from "../system-prompts";
3
+ import { UserError } from "../../core/errors";
4
+ import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
5
+
6
+ const dynamicImport = new Function("specifier", "return import(specifier)") as (
7
+ specifier: string
8
+ ) => Promise<unknown>;
9
+
10
+ type ContentBlock = { type: string; text?: string };
11
+
12
+ export class AnthropicAgent extends BaseAgent {
13
+ readonly name = "anthropic";
14
+ readonly displayName = "Anthropic Claude";
15
+ readonly sdkRequired = "@anthropic-ai/sdk";
16
+
17
+ getConfig(): AgentConfig {
18
+ return {
19
+ name: this.name,
20
+ displayName: this.displayName,
21
+ sdkRequired: this.sdkRequired,
22
+ envVars: ["ANTHROPIC_API_KEY"],
23
+ defaultModel: "claude-sonnet-4-20250514"
24
+ };
25
+ }
26
+
27
+ async generate(
28
+ prompt: string,
29
+ inputContext: Record<string, unknown>,
30
+ schemaHint: ProviderSchemaHint
31
+ ): Promise<GenerateResult> {
32
+ const apiKey = process.env.ANTHROPIC_API_KEY;
33
+ if (!apiKey) {
34
+ throw new UserError("ANTHROPIC_API_KEY is not set. Set it to use the Anthropic agent.");
35
+ }
36
+
37
+ const model = process.env.PRODO_ANTHROPIC_MODEL ?? "claude-sonnet-4-20250514";
38
+
39
+ const outputLanguage =
40
+ typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
41
+ ? inputContext.outputLanguage.trim()
42
+ : "en";
43
+
44
+ const system = buildSystemPrompt(schemaHint, outputLanguage);
45
+ const user = buildUserMessage(prompt, inputContext);
46
+
47
+ let AnthropicConstructor: new (opts: { apiKey: string }) => {
48
+ messages: {
49
+ create: (params: Record<string, unknown>) => Promise<{
50
+ content: ContentBlock[];
51
+ }>;
52
+ };
53
+ };
54
+
55
+ try {
56
+ const mod = (await dynamicImport("@anthropic-ai/sdk")) as {
57
+ default: typeof AnthropicConstructor;
58
+ };
59
+ AnthropicConstructor = mod.default;
60
+ } catch {
61
+ throw new UserError(
62
+ "Anthropic SDK is not installed. Run: npm install @anthropic-ai/sdk"
63
+ );
64
+ }
65
+
66
+ const client = new AnthropicConstructor({ apiKey });
67
+
68
+ const response = await client.messages.create({
69
+ model,
70
+ max_tokens: 8192,
71
+ system,
72
+ messages: [{ role: "user", content: user }]
73
+ });
74
+
75
+ const textBlock = response.content.find(
76
+ (block: ContentBlock) => block.type === "text"
77
+ );
78
+ const content = textBlock?.text?.trim() ?? "";
79
+
80
+ if (!content) {
81
+ throw new UserError("Anthropic agent returned an empty response.");
82
+ }
83
+
84
+ return { body: content };
85
+ }
86
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "anthropic",
3
+ "displayName": "Anthropic Claude",
4
+ "sdkRequired": "@anthropic-ai/sdk",
5
+ "envVars": ["ANTHROPIC_API_KEY"],
6
+ "defaultModel": "claude-sonnet-4-20250514"
7
+ }
@@ -0,0 +1,77 @@
1
+ import type { GenerateResult, ProviderSchemaHint } from "../core/types";
2
+
3
+ export type AgentConfig = {
4
+ name: string;
5
+ displayName: string;
6
+ sdkRequired: string | null;
7
+ envVars: string[];
8
+ defaultModel?: string;
9
+ };
10
+
11
+ export interface AgentPlugin {
12
+ readonly name: string;
13
+ readonly displayName: string;
14
+ readonly sdkRequired: string | null;
15
+
16
+ generate(
17
+ prompt: string,
18
+ inputContext: Record<string, unknown>,
19
+ schemaHint: ProviderSchemaHint
20
+ ): Promise<GenerateResult>;
21
+
22
+ isAvailable(): Promise<boolean>;
23
+ getConfig(): AgentConfig;
24
+ }
25
+
26
+ export abstract class BaseAgent implements AgentPlugin {
27
+ abstract readonly name: string;
28
+ abstract readonly displayName: string;
29
+ abstract readonly sdkRequired: string | null;
30
+
31
+ abstract generate(
32
+ prompt: string,
33
+ inputContext: Record<string, unknown>,
34
+ schemaHint: ProviderSchemaHint
35
+ ): Promise<GenerateResult>;
36
+
37
+ abstract getConfig(): AgentConfig;
38
+
39
+ async isAvailable(): Promise<boolean> {
40
+ const config = this.getConfig();
41
+
42
+ if (config.sdkRequired) {
43
+ try {
44
+ await import(config.sdkRequired);
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ for (const envVar of config.envVars) {
51
+ if (!process.env[envVar]) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ return true;
57
+ }
58
+
59
+ availabilityHint(): string {
60
+ const config = this.getConfig();
61
+ const hints: string[] = [];
62
+
63
+ if (config.sdkRequired) {
64
+ hints.push(`SDK required: npm install ${config.sdkRequired}`);
65
+ }
66
+
67
+ for (const envVar of config.envVars) {
68
+ if (!process.env[envVar]) {
69
+ hints.push(`Set environment variable: ${envVar}`);
70
+ }
71
+ }
72
+
73
+ return hints.length > 0
74
+ ? `Agent "${config.displayName}" is not available.\n ${hints.join("\n ")}`
75
+ : `Agent "${config.displayName}" is available.`;
76
+ }
77
+ }
@@ -0,0 +1,79 @@
1
+ import { BaseAgent, type AgentConfig } from "../base";
2
+ import { buildSystemPrompt, buildUserMessage } from "../system-prompts";
3
+ import { UserError } from "../../core/errors";
4
+ import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
5
+
6
+ const dynamicImport = new Function("specifier", "return import(specifier)") as (
7
+ specifier: string
8
+ ) => Promise<unknown>;
9
+
10
+ export class GoogleAgent extends BaseAgent {
11
+ readonly name = "google";
12
+ readonly displayName = "Google Gemini";
13
+ readonly sdkRequired = "@google/generative-ai";
14
+
15
+ getConfig(): AgentConfig {
16
+ return {
17
+ name: this.name,
18
+ displayName: this.displayName,
19
+ sdkRequired: this.sdkRequired,
20
+ envVars: ["GOOGLE_API_KEY"],
21
+ defaultModel: "gemini-2.0-flash"
22
+ };
23
+ }
24
+
25
+ async generate(
26
+ prompt: string,
27
+ inputContext: Record<string, unknown>,
28
+ schemaHint: ProviderSchemaHint
29
+ ): Promise<GenerateResult> {
30
+ const apiKey = process.env.GOOGLE_API_KEY;
31
+ if (!apiKey) {
32
+ throw new UserError("GOOGLE_API_KEY is not set. Set it to use the Google Gemini agent.");
33
+ }
34
+
35
+ const model = process.env.PRODO_GOOGLE_MODEL ?? "gemini-2.0-flash";
36
+
37
+ const outputLanguage =
38
+ typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
39
+ ? inputContext.outputLanguage.trim()
40
+ : "en";
41
+
42
+ const system = buildSystemPrompt(schemaHint, outputLanguage);
43
+ const user = buildUserMessage(prompt, inputContext);
44
+
45
+ let GoogleGenerativeAI: new (apiKey: string) => {
46
+ getGenerativeModel: (config: { model: string; systemInstruction: string }) => {
47
+ generateContent: (content: string) => Promise<{
48
+ response: { text: () => string };
49
+ }>;
50
+ };
51
+ };
52
+
53
+ try {
54
+ const mod = (await dynamicImport("@google/generative-ai")) as {
55
+ GoogleGenerativeAI: typeof GoogleGenerativeAI;
56
+ };
57
+ GoogleGenerativeAI = mod.GoogleGenerativeAI;
58
+ } catch {
59
+ throw new UserError(
60
+ "Google Generative AI SDK is not installed. Run: npm install @google/generative-ai"
61
+ );
62
+ }
63
+
64
+ const client = new GoogleGenerativeAI(apiKey);
65
+ const generativeModel = client.getGenerativeModel({
66
+ model,
67
+ systemInstruction: system
68
+ });
69
+
70
+ const result = await generativeModel.generateContent(user);
71
+ const content = result.response.text().trim();
72
+
73
+ if (!content) {
74
+ throw new UserError("Google Gemini agent returned an empty response.");
75
+ }
76
+
77
+ return { body: content };
78
+ }
79
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "google",
3
+ "displayName": "Google Gemini",
4
+ "sdkRequired": "@google/generative-ai",
5
+ "envVars": ["GOOGLE_API_KEY"],
6
+ "defaultModel": "gemini-2.0-flash"
7
+ }
@@ -0,0 +1,32 @@
1
+ import { BaseAgent, type AgentConfig } from "../base";
2
+ import { MockProvider } from "../../providers/mock-provider";
3
+ import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
4
+
5
+ export class MockAgent extends BaseAgent {
6
+ readonly name = "mock";
7
+ readonly displayName = "Mock (Testing)";
8
+ readonly sdkRequired = null;
9
+
10
+ private readonly provider = new MockProvider();
11
+
12
+ async generate(
13
+ prompt: string,
14
+ inputContext: Record<string, unknown>,
15
+ schemaHint: ProviderSchemaHint
16
+ ): Promise<GenerateResult> {
17
+ return this.provider.generate(prompt, inputContext, schemaHint);
18
+ }
19
+
20
+ async isAvailable(): Promise<boolean> {
21
+ return true;
22
+ }
23
+
24
+ getConfig(): AgentConfig {
25
+ return {
26
+ name: this.name,
27
+ displayName: this.displayName,
28
+ sdkRequired: null,
29
+ envVars: []
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "mock",
3
+ "displayName": "Mock (Testing)",
4
+ "sdkRequired": null,
5
+ "envVars": [],
6
+ "defaultModel": null
7
+ }
@@ -0,0 +1,83 @@
1
+ import { BaseAgent, type AgentConfig } from "../base";
2
+ import { buildSystemPrompt, buildUserMessage } from "../system-prompts";
3
+ import { UserError } from "../../core/errors";
4
+ import type { GenerateResult, ProviderSchemaHint } from "../../core/types";
5
+
6
+ const dynamicImport = new Function("specifier", "return import(specifier)") as (
7
+ specifier: string
8
+ ) => Promise<unknown>;
9
+
10
+ export class OpenAIAgent extends BaseAgent {
11
+ readonly name = "openai";
12
+ readonly displayName = "OpenAI";
13
+ readonly sdkRequired = "openai";
14
+
15
+ getConfig(): AgentConfig {
16
+ return {
17
+ name: this.name,
18
+ displayName: this.displayName,
19
+ sdkRequired: this.sdkRequired,
20
+ envVars: ["OPENAI_API_KEY"],
21
+ defaultModel: "gpt-4o-mini"
22
+ };
23
+ }
24
+
25
+ async generate(
26
+ prompt: string,
27
+ inputContext: Record<string, unknown>,
28
+ schemaHint: ProviderSchemaHint
29
+ ): Promise<GenerateResult> {
30
+ const apiKey = process.env.OPENAI_API_KEY;
31
+ if (!apiKey) {
32
+ throw new UserError("OPENAI_API_KEY is not set. Set it to use the OpenAI agent.");
33
+ }
34
+
35
+ const model = process.env.PRODO_OPENAI_MODEL ?? "gpt-4o-mini";
36
+ const baseURL = process.env.PRODO_OPENAI_BASE_URL ?? undefined;
37
+
38
+ const outputLanguage =
39
+ typeof inputContext.outputLanguage === "string" && inputContext.outputLanguage.trim()
40
+ ? inputContext.outputLanguage.trim()
41
+ : "en";
42
+
43
+ const system = buildSystemPrompt(schemaHint, outputLanguage);
44
+ const user = buildUserMessage(prompt, inputContext);
45
+
46
+ let OpenAIConstructor: new (opts: { apiKey: string; baseURL?: string }) => {
47
+ chat: {
48
+ completions: {
49
+ create: (params: Record<string, unknown>) => Promise<{
50
+ choices: Array<{ message?: { content?: string } }>;
51
+ }>;
52
+ };
53
+ };
54
+ };
55
+
56
+ try {
57
+ const mod = (await dynamicImport("openai")) as { default: typeof OpenAIConstructor };
58
+ OpenAIConstructor = mod.default;
59
+ } catch {
60
+ throw new UserError(
61
+ "OpenAI SDK is not installed. Run: npm install openai"
62
+ );
63
+ }
64
+
65
+ const client = new OpenAIConstructor({ apiKey, baseURL });
66
+
67
+ const response = await client.chat.completions.create({
68
+ model,
69
+ messages: [
70
+ { role: "system", content: system },
71
+ { role: "user", content: user }
72
+ ],
73
+ temperature: 0.2
74
+ });
75
+
76
+ const content = response.choices[0]?.message?.content?.trim();
77
+ if (!content) {
78
+ throw new UserError("OpenAI agent returned an empty response.");
79
+ }
80
+
81
+ return { body: content };
82
+ }
83
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "openai",
3
+ "displayName": "OpenAI",
4
+ "sdkRequired": "openai",
5
+ "envVars": ["OPENAI_API_KEY"],
6
+ "defaultModel": "gpt-4o-mini"
7
+ }
@@ -0,0 +1,35 @@
1
+ import type { ProviderSchemaHint } from "../core/types";
2
+
3
+ export function buildSystemPrompt(schemaHint: ProviderSchemaHint, outputLanguage: string): string {
4
+ const mode = schemaHint.artifactType;
5
+
6
+ if (mode === "normalize") {
7
+ return `You normalize messy human product briefs into strict JSON.
8
+ Return valid JSON only, no markdown. Include confidence scores (0..1) for critical fields.
9
+ Preserve source language and Unicode characters exactly; never transliterate Turkish letters to ASCII.`;
10
+ }
11
+
12
+ if (mode === "semantic_consistency") {
13
+ return `You detect semantic inconsistencies between paired artifacts.
14
+ Return valid JSON only: { "issues": [{level, code, check, contract_id, file, message, suggestion}] }.`;
15
+ }
16
+
17
+ if (mode === "contract_relevance") {
18
+ return `You verify whether tagged content actually matches the referenced contract text.
19
+ Return valid JSON only: { "relevant": boolean, "score": number, "reason": string }.`;
20
+ }
21
+
22
+ return `You are a product-document generator.
23
+ Return only Markdown body content.
24
+ Headings required:
25
+ ${schemaHint.requiredHeadings.join("\n")}
26
+ Required contract tags:
27
+ ${schemaHint.requiredContracts.join(", ")}
28
+ Use tags like [G1], [F2], [C1] where relevant.
29
+ Output language: ${outputLanguage}
30
+ Do not translate required headings.`;
31
+ }
32
+
33
+ export function buildUserMessage(prompt: string, inputContext: Record<string, unknown>): string {
34
+ return `${prompt}\n\nContext JSON:\n${JSON.stringify(inputContext, null, 2)}`;
35
+ }