Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { DeployedState } from '../../../../../schema';
import { resolveBundleVersionId } from '../build-config';
import { describe, expect, it } from 'vitest';

const BUNDLE_ARN = 'arn:aws:bedrock-agentcore:us-east-1:123456789012:configuration-bundle/Proj-PromptV1-abc123';
const VERSION_ID = 'a7be96aa-8a83-47b8-8d98-0829b14c1d9b';

const deployedState = {
targets: {
default: {
resources: {
configBundles: {
PromptV1: { bundleArn: BUNDLE_ARN, versionId: VERSION_ID },
},
},
},
},
} as unknown as DeployedState;

describe('resolveBundleVersionId', () => {
it("expands 'LATEST' to the deployed versionId", () => {
expect(resolveBundleVersionId(BUNDLE_ARN, 'LATEST', deployedState)).toBe(VERSION_ID);
});

it('returns an explicit version verbatim (never touches deployed state)', () => {
const explicit = '11111111-2222-3333-4444-555555555555';
expect(resolveBundleVersionId(BUNDLE_ARN, explicit, deployedState)).toBe(explicit);
});

it("returns undefined when 'LATEST' cannot be resolved (bundle not deployed)", () => {
const unknownArn = 'arn:aws:bedrock-agentcore:us-east-1:123456789012:configuration-bundle/Proj-Unknown-zzz999';
expect(resolveBundleVersionId(unknownArn, 'LATEST', deployedState)).toBeUndefined();
});

it('returns undefined for LATEST when there are no deployed targets', () => {
expect(resolveBundleVersionId(BUNDLE_ARN, 'LATEST', { targets: {} } as unknown as DeployedState)).toBeUndefined();
});
});
23 changes: 23 additions & 0 deletions src/cli/operations/jobs/recommendation/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ export function resolveComponentKeyForJsonPath(key: string, deployedState: Deplo
return key;
}

/**
* Resolve a config-bundle version reference to a concrete version UUID.
*
* The recommendation API only accepts a concrete versionId — passing the literal 'LATEST' through
* yields a 400 (versionId fails the UUID pattern). When 'LATEST' is given, look the bundle up in
* deployed state (by ARN) and return its deployed versionId. An explicit version is returned
* verbatim. Returns undefined when 'LATEST' cannot be resolved (bundle not deployed) so the caller
* can surface a friendly error instead of sending 'LATEST' to the API. Mirrors the ab-test path's
* resolveConfigBundleVersion.
*/
export function resolveBundleVersionId(
bundleArn: string,
versionRef: string,
deployedState: DeployedState
): string | undefined {
if (versionRef !== 'LATEST') return versionRef;
for (const target of Object.values(deployedState.targets ?? {})) {
const bundle = Object.values(target.resources?.configBundles ?? {}).find(b => b.bundleArn === bundleArn);
if (bundle?.versionId) return bundle.versionId;
}
return undefined;
}

/** Flatten statusReasons + result errorCode/errorMessage into a single display string (FAILED only). */
export function extractFailureDetails(pollResult: {
statusReasons?: string[];
Expand Down
22 changes: 20 additions & 2 deletions src/cli/operations/jobs/recommendation/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
buildRecommendationConfig,
extractAccountIdFromArn,
extractFailureDetails,
resolveBundleVersionId,
resolveComponentKeyForJsonPath,
resolveEvaluatorId,
} from './build-config';
Expand Down Expand Up @@ -141,6 +142,7 @@ export const recommendationHandler: RecommendationHandler = {

// Resolve config-bundle ARN + short JSONPath (from deployed state / agentcore.json)
let bundleArn: string | undefined;
let resolvedBundleVersion = opts.bundleVersion;
let resolvedSystemPromptJsonPath = opts.systemPromptJsonPath;
if (opts.inputSource === 'config-bundle' && opts.bundleName) {
if (opts.bundleName.startsWith('arn:')) {
Expand All @@ -162,6 +164,22 @@ export const recommendationHandler: RecommendationHandler = {
}
}

// Expand '--bundle-version LATEST' to the deployed versionId. The recommendation API only
// accepts a concrete version UUID, so passing 'LATEST' through verbatim yields a 400. (The
// ab-test path resolves LATEST the same way; this mirrors it.)
if (resolvedBundleVersion === 'LATEST' && bundleArn) {
const versionId = resolveBundleVersionId(bundleArn, resolvedBundleVersion, deployedState);
if (!versionId) {
const err = new ResourceNotFoundError(
`Could not resolve version "LATEST" for config bundle "${opts.bundleName}". ` +
'Run `agentcore deploy` first, or pass an explicit version UUID.'
);
logger?.finalize(false);
return { success: false, error: err };
}
resolvedBundleVersion = versionId;
}

if (resolvedSystemPromptJsonPath && !resolvedSystemPromptJsonPath.startsWith('$')) {
const bundleName = opts.bundleName.startsWith('arn:')
? Object.values(deployedState.targets)
Expand All @@ -186,7 +204,7 @@ export const recommendationHandler: RecommendationHandler = {
type: opts.type,
inlineContent,
bundleArn,
bundleVersion: opts.bundleVersion,
bundleVersion: resolvedBundleVersion,
systemPromptJsonPath: resolvedSystemPromptJsonPath,
toolDescJsonPaths: opts.toolDescJsonPaths,
inputSource: opts.inputSource,
Expand Down Expand Up @@ -242,7 +260,7 @@ export const recommendationHandler: RecommendationHandler = {
inputSource: opts.inputSource,
bundleName: opts.bundleName,
bundleArn,
bundleVersion: opts.bundleVersion,
bundleVersion: resolvedBundleVersion,
systemPromptJsonPath: resolvedSystemPromptJsonPath,
toolDescJsonPaths: opts.toolDescJsonPaths,
...(opts.kmsKeyArn ? { kmsKeyArn: opts.kmsKeyArn } : {}),
Expand Down
Loading