feat(project): implement add functionality. - #2004
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor #2004 +/- ##
============================================
+ Coverage 97.01% 97.03% +0.02%
============================================
Files 368 368
Lines 21630 21745 +115
============================================
+ Hits 20984 21100 +116
+ Misses 646 645 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| // endpoint, ...). | ||
| export interface AwsClients { | ||
| control(config: ClientConfig): BedrockAgentCoreControlClient | ||
| control(config: ClientConfig): BedrockAgentCoreControlClient; |
There was a problem hiding this comment.
perhaps a rebase issue, but this is failing ci on main https://github.com/aws/agentcore-cli/actions/runs/31832012548/job/94869692163.
| const RESULT: EvaluateResult = { | ||
| sessionsRequested: 1, | ||
| sessionsEvaluated: 1, | ||
| results: [{ evaluatorId: "Builtin.Helpfulness", value: 0.9 } as EvaluateResult["results"][number]], |
| resourceConfig: z.input<typeof HarnessSpecSchema>; | ||
| } | ||
| | { | ||
| resourceType: "runtime"; |
There was a problem hiding this comment.
added runtime just to start the pattern.
tejaskash
left a comment
There was a problem hiding this comment.
I found a few cases where the new add path reports success but writes a project that will not behave as requested. I left the details inline.
| case "harness": { | ||
| yield { message: `Scaffolding harness in project` }; | ||
| const harnessPath = await this.scaffoldHarness(project.rootPath, input.resourceConfig); | ||
| newResources.push({ name: input.resourceConfig.name, path: harnessPath }); |
There was a problem hiding this comment.
Could we keep this path relative to the project root? scaffoldHarness returns an absolute path, so this writes the developer's local path into agentcore.json. It works on that machine, but after the project is moved or cloned, CDK still tries to read the old path. Writing app/${name} here would match the existing project format and keep the project portable.
There was a problem hiding this comment.
good catch, let me swap that to the relative path.
| spec: z.input<typeof HarnessSpecSchema>, | ||
| ): Promise<FsTreeNode> { | ||
| return FsTreeNode.createDirectory(".", [ | ||
| FsTreeNode.createFile("harness.json", async () => json(HarnessSpecSchema.parse(spec))), |
There was a problem hiding this comment.
I think the Dockerfile needs to be handled as part of this scaffold too. Right now --dockerfile only puts the filename in harness.json; this tree creates harness.json and system-prompt.md, but never copies the Dockerfile into app/<harness>. The command succeeds, then CDK uses this directory as the build context and cannot find the file. Can we validate and copy the source Dockerfile here, and store its basename like the existing CLI does?
There was a problem hiding this comment.
wasn't aware thats how it worked, but that makes sense since it keeps the project portable. let me add that.
| harnessConfig, | ||
| )) { | ||
| for await (const event of config.projectManager.addResource(project, { | ||
| resourceType: "harness", |
There was a problem hiding this comment.
One part of the requested tool configuration gets lost before this call. For an agentcore_gateway tool, toTool copies gatewayArn but drops outboundAuth. For example, asking for { none: {} } produces a harness config with no outbound auth, which changes the behavior back to the default AWS IAM mode. Could we preserve the awsIam, none, and oauth variants?
There was a problem hiding this comment.
Yeah I think @notgitika spotted the same in #1998 (comment). Was going to address as follow-up, but let me bring in here.
| )) { | ||
| for await (const event of config.projectManager.addResource(project, { | ||
| resourceType: "harness", | ||
| resourceConfig: harnessConfig, |
There was a problem hiding this comment.
Private Git skill auth has a field mismatch here. The SDK input gives us a credentialArn, but toSkill writes that value into credentialName. During synth, CDK treats credentialName as a project credential key, so it cannot resolve the ARN-shaped value and deployment fails. Could we either accept a project credential name for this project command, or carry the ARN separately instead of renaming it?
There was a problem hiding this comment.
I think adding it as a new field makes sense (with them being mutually exclusive). That allows us to support credentials from outside the project with minimal extra effort. We can comeback to supporting credentialName since its already in the schema.
| "harness", | ||
| harnessConfig, | ||
| )) { | ||
| for await (const event of config.projectManager.addResource(project, { |
There was a problem hiding this comment.
There is currently no way to add a Dockerfile-backed harness in VPC mode. The environment conversion carries subnets and security groups, but the harness schema also requires networkConfig.vpcId for Dockerfile builds, and this handler has no --vpc-id input. The command always fails validation for that combination. Could we add an explicit VPC ID option and thread it into the resource config?
There was a problem hiding this comment.
wow great edge case find, was not aware vpc id was required with VPC + docker. Let me add the explicit flag for this and make it clear.
| newResources.push({ name: input.resourceConfig.name, path: harnessPath }); | ||
| break; | ||
| } | ||
| // TODO: add limited special casing for runtime and default for other resources that proxy directly to spec changes. |
There was a problem hiding this comment.
Could we either implement the runtime branch here or leave runtime out of AddResourceInput for now? At the moment a runtime call falls through this switch, emits the Updating project spec message, writes the unchanged runtime list, and returns successfully. That silent success will be hard for callers to diagnose.
There was a problem hiding this comment.
I think that's fine right? assuming we are implementing add runtime right after this.
There was a problem hiding this comment.
Going to be the next PR, but understand the current behavior is confusing. I'll have it throw early.
| return FsTreeNode.createDirectory(".", [ | ||
| FsTreeNode.createFile("harness.json", async () => json(HarnessSpecSchema.parse(spec))), | ||
| FsTreeNode.createFile( | ||
| "system-prompt.md", |
There was a problem hiding this comment.
are we making the changes on the cdk side as well? resolveSystemPrompt() on there uses the inline systemPrompt first and onlyl reads system-prompt.md when that field is absent. so there are these 2 input sources. did we want that behavior to still exist?
what we are losing here is that there is no way a user will be warned if they make changes in their local md file here if they don't update the value from harness.json.
There was a problem hiding this comment.
oh, good callout, was not aware of that behavior. I feel like having two sources of system prompts might be unnecessary and confusing if we scaffold a system-prompt.md for them so I think stripping the system prompt field from the json to maintain backwards compatibility, and treat the file as source of truth might be the simplest path forward.
| } | ||
|
|
||
| yield { message: `Updating project spec file at '${agentCoreSpecPath}'` }; | ||
| const newProjectSpec = await this.json.write(agentCoreSpecPath, { |
There was a problem hiding this comment.
so the order of operations here is:
create harness.json -> create system-prompt.md -> update agentcore.json
if the write fails, the harness files remain and retrying would fail because it "already exists". can we add some cleanup to address this edge case?
Problem
Add functionality is not implemented.
Solution
specsoproject.managedBybecomesproject.spec.managedByto make updating the spec more ergonomic. We keep name at the top level.Testing