Skip to content

Commit a93cd3b

Browse files
authored
Merge pull request #40 from oslabs-beta/paython-mcp
feat: wire AI-generated pipelines into YAML preview and wizard context
2 parents 9167eb3 + 906d996 commit a93cd3b

File tree

6 files changed

+257
-68
lines changed

6 files changed

+257
-68
lines changed

client/src/pages/ConfigurePage.tsx

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState } from "react";
22
import { useRepoStore } from "../store/useRepoStore";
33
import { usePipelineStore } from "../store/usePipelineStore";
4+
import { useWizardStore } from "../store/useWizardStore";
45
import { api } from "../lib/api";
56

67
type ChatMessage = {
@@ -30,8 +31,17 @@ export default function ConfigurePage() {
3031
editedYaml,
3132
setEditedYaml,
3233
getEffectiveYaml,
34+
hydrateFromWizard,
3335
} = usePipelineStore();
3436

37+
const {
38+
repoInfo,
39+
pipelineInfo,
40+
setRepoInfo,
41+
setPipelineInfo,
42+
setLastToolCalled,
43+
} = useWizardStore();
44+
3545
const yaml = getEffectiveYaml();
3646
const busy = status === "loading";
3747

@@ -57,6 +67,13 @@ export default function ConfigurePage() {
5767
alert("Pick a repo + branch on the Connect page first.");
5868
return;
5969
}
70+
console.log("[ConfigurePage] Generate clicked with inputs:", {
71+
repo,
72+
branch,
73+
template,
74+
stages,
75+
options,
76+
});
6077
await regenerate({ repo, branch });
6178
};
6279

@@ -92,40 +109,92 @@ export default function ConfigurePage() {
92109
setChatInput("");
93110
setChatLoading(true);
94111

95-
try {
96-
const res = await api.askYamlWizard({
97-
repoUrl: repo, // backend expects "repoUrl"
98-
provider: "aws", // or whatever provider you use
99-
branch: branch, // backend expects "branch"
100-
message: trimmed, // optional, for your agent logic
101-
yaml, // optional, current YAML for context
102-
});
103-
104-
const text =
105-
(res as any)?.reply ??
106-
(res as any)?.message ??
107-
(res as any)?.content ??
108-
JSON.stringify(res, null, 2);
109-
110-
const assistantMessage: ChatMessage = {
111-
role: "assistant",
112-
content: text,
113-
};
114-
115-
setChatMessages((prev) => [...prev, assistantMessage]);
116-
} catch (e: any) {
117-
console.error("[ConfigurePage] AI wizard error:", e);
118-
const assistantMessage: ChatMessage = {
119-
role: "assistant",
120-
content:
121-
"Sorry, I ran into an issue talking to the AI backend.\n\n" +
122-
`Error: ${e?.message ?? "Unknown error"}`,
123-
};
124-
setChatMessages((prev) => [...prev, assistantMessage]);
125-
} finally {
126-
setChatLoading(false);
127-
}
128-
};
112+
try {
113+
const res = await api.askYamlWizard({
114+
repoUrl: repo, // backend expects "repoUrl"
115+
provider: "aws", // or whatever provider you use
116+
branch: branch, // backend expects "branch"
117+
message: trimmed, // optional, for your agent logic
118+
yaml, // optional, current YAML for context
119+
});
120+
121+
// ---- Update wizard context memory ----
122+
if ((res as any)?.tool_called) {
123+
setLastToolCalled((res as any).tool_called);
124+
}
125+
126+
// If repo info is available from selection or tool output, store it
127+
if (repo) {
128+
setRepoInfo({
129+
fullName: repo,
130+
});
131+
}
132+
133+
// If a pipeline was generated, hydrate pipeline store + wizard context
134+
if ((res as any)?.tool_called === "pipeline_generator") {
135+
const generatedYaml =
136+
(res as any)?.generated_yaml ??
137+
(res as any)?.tool_output?.data?.generated_yaml;
138+
139+
const pipelineName =
140+
(res as any)?.pipeline_metadata?.data?.pipeline_name ??
141+
(res as any)?.pipeline_metadata?.pipeline_name;
142+
143+
if (generatedYaml) {
144+
hydrateFromWizard({
145+
repo,
146+
generatedYaml,
147+
pipelineName,
148+
});
149+
}
150+
151+
setPipelineInfo({
152+
pipelineName,
153+
branch,
154+
provider: "aws",
155+
stages,
156+
});
157+
}
158+
159+
let text: string;
160+
161+
if ((res as any)?.reply) {
162+
text = (res as any).reply;
163+
} else if ((res as any)?.message) {
164+
text = (res as any).message;
165+
} else if (
166+
(res as any)?.tool_called === "repo_reader" &&
167+
Array.isArray((res as any)?.tool_output?.data?.data?.repositories)
168+
) {
169+
const count =
170+
(res as any).tool_output.data.data.repositories.length;
171+
text = `I found ${count} repositories. You can select one from the list to continue.`;
172+
} else if (repoInfo?.fullName) {
173+
text = `I’m looking at ${repoInfo.fullName}. What would you like to change about the pipeline?`;
174+
} else {
175+
text =
176+
"I couldn’t map that request to an action yet. You can ask me to modify the pipeline, deploy settings, or AWS role.";
177+
}
178+
179+
const assistantMessage: ChatMessage = {
180+
role: "assistant",
181+
content: text,
182+
};
183+
184+
setChatMessages((prev) => [...prev, assistantMessage]);
185+
} catch (e: any) {
186+
console.error("[ConfigurePage] AI wizard error:", e);
187+
const assistantMessage: ChatMessage = {
188+
role: "assistant",
189+
content:
190+
"Sorry, I ran into an issue talking to the AI backend.\n\n" +
191+
`Error: ${e?.message ?? "Unknown error"}`,
192+
};
193+
setChatMessages((prev) => [...prev, assistantMessage]);
194+
} finally {
195+
setChatLoading(false);
196+
}
197+
};
129198

130199
const handleChatKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (
131200
e

client/src/store/usePipelineStore.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ type PipelineActions = {
5252
setEditedYaml(y: string): void;
5353
resetYaml(): void;
5454
resetAll(): void;
55+
56+
hydrateFromWizard(payload: {
57+
repo: string;
58+
generatedYaml: string;
59+
pipelineName?: string;
60+
}): void;
5561
};
5662

5763
const initial: PipelineState = {
@@ -213,6 +219,26 @@ export const usePipelineStore = create<PipelineState & PipelineActions>()(
213219
});
214220
},
215221

222+
hydrateFromWizard({ repo, generatedYaml, pipelineName }) {
223+
set({
224+
result: {
225+
...(get().result ?? {}),
226+
generated_yaml: generatedYaml,
227+
yaml: generatedYaml,
228+
pipeline_name: pipelineName ?? "ci.yml",
229+
},
230+
repoFullName: repo,
231+
status: "success",
232+
editing: false,
233+
editedYaml: undefined,
234+
});
235+
236+
console.log(
237+
"[usePipelineStore] Hydrated YAML from wizard:",
238+
generatedYaml.slice(0, 80)
239+
);
240+
},
241+
216242
setEditing: (b) => set({ editing: b }),
217243
setEditedYaml: (y) => set({ editedYaml: y }),
218244
resetYaml: () => set({ editedYaml: undefined }),

client/src/store/useWizardStore.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { create } from "zustand";
2+
3+
type RepoInfo = {
4+
fullName: string;
5+
defaultBranch?: string;
6+
language?: string | null;
7+
visibility?: "public" | "private";
8+
};
9+
10+
type PipelineInfo = {
11+
pipelineName?: string;
12+
branch?: string;
13+
provider?: string;
14+
stages?: string[];
15+
};
16+
17+
type WizardContextState = {
18+
// context
19+
lastToolCalled?: string;
20+
repoInfo?: RepoInfo;
21+
pipelineInfo?: PipelineInfo;
22+
23+
// setters
24+
setLastToolCalled: (tool?: string) => void;
25+
setRepoInfo: (info?: RepoInfo) => void;
26+
setPipelineInfo: (info?: PipelineInfo) => void;
27+
28+
// reset (optional but useful)
29+
resetWizardContext: () => void;
30+
};
31+
32+
export const useWizardStore = create<WizardContextState>((set) => ({
33+
lastToolCalled: undefined,
34+
repoInfo: undefined,
35+
pipelineInfo: undefined,
36+
37+
setLastToolCalled: (tool) =>
38+
set(() => ({
39+
lastToolCalled: tool,
40+
})),
41+
42+
setRepoInfo: (info) =>
43+
set(() => ({
44+
repoInfo: info,
45+
})),
46+
47+
setPipelineInfo: (info) =>
48+
set(() => ({
49+
pipelineInfo: info,
50+
})),
51+
52+
resetWizardContext: () =>
53+
set(() => ({
54+
lastToolCalled: undefined,
55+
repoInfo: undefined,
56+
pipelineInfo: undefined,
57+
})),
58+
}));

server/tools/oidc_adapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const oidc_adapter = {
66

77
// ✅ Input schema for validation
88
input_schema: z.object({
9-
provider: z.enum(["aws", "jenkins"]),
9+
provider: z.enum(["aws", "jenkins", "gcp"]),
1010
}),
1111

1212
// ✅ Mock handler (replace with real API calls later)

server/tools/pipeline_generator.js

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,33 @@ export const pipeline_generator = {
2121
template: z.enum(['node_app', 'python_app', 'container_service']),
2222
options: z
2323
.object({
24-
run_tests: z.boolean().default(true),
25-
include_trivy_scan: z.boolean().default(false),
24+
// legacy flags
25+
run_tests: z.boolean().optional(),
26+
include_trivy_scan: z.boolean().optional(),
2627
artifact_name: z.string().optional(),
28+
29+
// frontend-driven config
30+
nodeVersion: z.string().optional(),
31+
installCmd: z.string().optional(),
32+
testCmd: z.string().optional(),
33+
buildCmd: z.string().optional(),
34+
awsRoleArn: z.string().optional(),
35+
stages: z.array(z.enum(['build', 'test', 'deploy'])).optional(),
2736
})
2837
.optional(),
2938
}),
3039

3140
// Real handler (queries github_adapter for repo info and generates pipeline config)
41+
handler: async ({ repo, branch = 'main', provider = 'aws', template, options }) => {
42+
const normalized = {
43+
nodeVersion: options?.nodeVersion,
44+
installCmd: options?.installCmd,
45+
testCmd: options?.testCmd,
46+
buildCmd: options?.buildCmd,
47+
awsRoleArn: options?.awsRoleArn,
48+
stages: options?.stages,
49+
};
50+
3251
handler: async ({
3352
repo,
3453
branch = 'main',
@@ -236,6 +255,8 @@ export const pipeline_generator = {
236255
const selectedProvider = provider || inferredProvider;
237256
const selectedTemplate = template || inferredTemplate;
238257

258+
console.log('🧪 pipeline_generator normalized options:', normalized);
259+
239260
const pipelineName = `${selectedProvider}-${selectedTemplate}-ci.yml`;
240261

241262
// GCP path: generate a real Cloud Run workflow (GHCR -> Artifact Registry -> Cloud Run)
@@ -277,6 +298,11 @@ export const pipeline_generator = {
277298

278299
const generated_yaml = `
279300
name: CI/CD Pipeline for ${repo}
301+
302+
permissions:
303+
id-token: write
304+
contents: read
305+
280306
on:
281307
push:
282308
branches:
@@ -286,26 +312,34 @@ jobs:
286312
runs-on: ubuntu-latest
287313
steps:
288314
- uses: actions/checkout@v4
289-
- name: Setup ${selectedTemplate === 'node_app' ? 'Node.js' : 'Python'}
290-
uses: actions/setup-${
291-
selectedTemplate === 'node_app' ? 'node' : 'python'
292-
}@v4
315+
- name: Setup Node.js
316+
uses: actions/setup-node@v4
317+
with:
318+
node-version: ${normalized.nodeVersion ?? '20'}
293319
- name: Install Dependencies
294320
run: ${
295-
selectedTemplate === 'node_app'
321+
normalized.installCmd ??
322+
(selectedTemplate === 'node_app'
296323
? 'npm ci'
297-
: 'pip install -r requirements.txt'
324+
: 'pip install -r requirements.txt')
298325
}
299326
- name: Run Tests
300-
run: ${selectedTemplate === 'node_app' ? 'npm test' : 'pytest'}
327+
run: ${
328+
normalized.testCmd ??
329+
(selectedTemplate === 'node_app' ? 'npm test' : 'pytest')
330+
}
301331
deploy:
302332
needs: build
303333
runs-on: ubuntu-latest
304334
steps:
305-
- name: Configure ${selectedProvider.toUpperCase()}
306-
run: echo "Configuring ${selectedProvider.toUpperCase()} OIDC..."
335+
- uses: actions/checkout@v4
336+
- name: Configure AWS (OIDC)
337+
uses: aws-actions/configure-aws-credentials@v4
338+
with:
339+
role-to-assume: ${normalized.awsRoleArn ?? 'REPLACE_ME'}
340+
aws-region: us-east-1
307341
- name: Deploy Application
308-
run: echo "Deploying ${repo} to ${selectedProvider.toUpperCase()}..."
342+
run: echo "Deploying ${repo} to AWS..."
309343
`;
310344

311345
return {
@@ -317,7 +351,7 @@ jobs:
317351
provider: selectedProvider,
318352
template: selectedTemplate,
319353
options: options || {},
320-
stages: ['build', 'test', 'deploy'],
354+
stages: normalized.stages ?? ['build', 'test', 'deploy'],
321355
generated_yaml,
322356
repo_info: repoInfo,
323357
created_at: new Date().toISOString(),

0 commit comments

Comments
 (0)